summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 12:48:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 12:48:01 +0000
commitb2d2d555a704148968cb7e566735a2a1b1a2f189 (patch)
tree18549ff498338f40ecf7aa327620abf4c1c3ee43
parentInitial commit. (diff)
downloadchrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.tar.xz
chrony-b2d2d555a704148968cb7e566735a2a1b1a2f189.zip
Adding upstream version 4.5.upstream/4.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--COPYING339
-rw-r--r--FAQ1159
-rw-r--r--INSTALL165
-rw-r--r--Makefile.in143
-rw-r--r--NEWS1017
-rw-r--r--README141
-rw-r--r--addressing.h67
-rw-r--r--addrfilt.c405
-rw-r--r--addrfilt.h80
-rw-r--r--array.c145
-rw-r--r--array.h59
-rw-r--r--candm.h850
-rw-r--r--client.c3538
-rw-r--r--clientlog.c1111
-rw-r--r--clientlog.h67
-rw-r--r--cmac.h48
-rw-r--r--cmac_gnutls.c189
-rw-r--r--cmac_nettle.c117
-rw-r--r--cmdmon.c1871
-rw-r--r--cmdmon.h40
-rw-r--r--cmdparse.c428
-rw-r--r--cmdparse.h63
-rw-r--r--conf.c2647
-rw-r--r--conf.h176
-rwxr-xr-xconfigure1161
-rw-r--r--contrib/andrew_bishop_1114
-rw-r--r--contrib/andrew_bishop_295
-rw-r--r--contrib/bryan_christianson_1/README.txt103
-rwxr-xr-xcontrib/bryan_christianson_1/chronylogrotate.sh58
-rw-r--r--contrib/bryan_christianson_1/org.chrony-project.chronyc.plist22
-rw-r--r--contrib/bryan_christianson_1/org.chrony-project.chronyd.plist19
-rw-r--r--contrib/erik_bryer_165
-rw-r--r--contrib/ken_gillett_1100
-rw-r--r--contrib/stephan_boettcher_1162
-rw-r--r--contrib/wolfgang_weisselberg1118
-rw-r--r--doc/Makefile.in76
-rw-r--r--doc/chrony.conf.adoc3160
-rw-r--r--doc/chrony.conf.man.in5043
-rw-r--r--doc/chronyc.adoc1559
-rw-r--r--doc/chronyc.man.in2756
-rw-r--r--doc/chronyd.adoc235
-rw-r--r--doc/chronyd.man.in278
-rw-r--r--doc/faq.adoc1172
-rw-r--r--doc/installation.adoc200
-rw-r--r--examples/chrony-wait.service46
-rw-r--r--examples/chrony.conf.example112
-rw-r--r--examples/chrony.conf.example247
-rw-r--r--examples/chrony.conf.example3334
-rw-r--r--examples/chrony.keys.example15
-rw-r--r--examples/chrony.logrotate8
-rw-r--r--examples/chrony.nm-dispatcher.dhcp49
-rw-r--r--examples/chrony.nm-dispatcher.onoffline29
-rw-r--r--examples/chronyd-restricted.service58
-rw-r--r--examples/chronyd.service48
-rw-r--r--getdate.c2601
-rw-r--r--getdate.h28
-rw-r--r--getdate.y1039
-rw-r--r--hash.h57
-rw-r--r--hash_gnutls.c145
-rw-r--r--hash_intmd5.c71
-rw-r--r--hash_nettle.c124
-rw-r--r--hash_nss.c114
-rw-r--r--hash_tomcrypt.c126
-rw-r--r--hwclock.c334
-rw-r--r--hwclock.h54
-rw-r--r--keys.c441
-rw-r--r--keys.h47
-rw-r--r--local.c781
-rw-r--r--local.h229
-rw-r--r--localp.h74
-rw-r--r--logging.c387
-rw-r--r--logging.h143
-rw-r--r--main.c706
-rw-r--r--main.h35
-rw-r--r--manual.c332
-rw-r--r--manual.h46
-rw-r--r--md5.c315
-rw-r--r--md5.h56
-rw-r--r--memory.c98
-rw-r--r--memory.h45
-rw-r--r--nameserv.c166
-rw-r--r--nameserv.h52
-rw-r--r--nameserv_async.c130
-rw-r--r--nameserv_async.h40
-rw-r--r--ntp.h200
-rw-r--r--ntp_auth.c386
-rw-r--r--ntp_auth.h84
-rw-r--r--ntp_core.c3267
-rw-r--r--ntp_core.h140
-rw-r--r--ntp_ext.c192
-rw-r--r--ntp_ext.h43
-rw-r--r--ntp_io.c634
-rw-r--r--ntp_io.h73
-rw-r--r--ntp_io_linux.c815
-rw-r--r--ntp_io_linux.h45
-rw-r--r--ntp_signd.c341
-rw-r--r--ntp_signd.h42
-rw-r--r--ntp_sources.c1561
-rw-r--r--ntp_sources.h154
-rw-r--r--nts_ke.h81
-rw-r--r--nts_ke_client.c457
-rw-r--r--nts_ke_client.h56
-rw-r--r--nts_ke_server.c1036
-rw-r--r--nts_ke_server.h49
-rw-r--r--nts_ke_session.c929
-rw-r--r--nts_ke_session.h93
-rw-r--r--nts_ntp.h36
-rw-r--r--nts_ntp_auth.c187
-rw-r--r--nts_ntp_auth.h43
-rw-r--r--nts_ntp_client.c717
-rw-r--r--nts_ntp_client.h51
-rw-r--r--nts_ntp_server.c309
-rw-r--r--nts_ntp_server.h40
-rw-r--r--pktlength.c222
-rw-r--r--pktlength.h40
-rw-r--r--privops.c696
-rw-r--r--privops.h77
-rw-r--r--ptp.h69
-rw-r--r--quantiles.c209
-rw-r--r--quantiles.h41
-rw-r--r--refclock.c862
-rw-r--r--refclock.h86
-rw-r--r--refclock_phc.c232
-rw-r--r--refclock_pps.c159
-rw-r--r--refclock_shm.c134
-rw-r--r--refclock_sock.c176
-rw-r--r--reference.c1441
-rw-r--r--reference.h200
-rw-r--r--regress.c704
-rw-r--r--regress.h137
-rw-r--r--reports.h212
-rw-r--r--rtc.c242
-rw-r--r--rtc.h45
-rw-r--r--rtc_linux.c1072
-rw-r--r--rtc_linux.h45
-rw-r--r--samplefilt.c497
-rw-r--r--samplefilt.h51
-rw-r--r--sched.c866
-rw-r--r--sched.h93
-rw-r--r--siv.h74
-rw-r--r--siv_gnutls.c310
-rw-r--r--siv_nettle.c251
-rw-r--r--siv_nettle_int.c452
-rw-r--r--smooth.c370
-rw-r--r--smooth.h48
-rw-r--r--socket.c1803
-rw-r--r--socket.h156
-rw-r--r--sources.c1876
-rw-r--r--sources.h144
-rw-r--r--sourcestats.c1041
-rw-r--r--sourcestats.h141
-rw-r--r--srcparams.h93
-rw-r--r--stubs.c573
-rw-r--r--sys.c150
-rw-r--r--sys.h53
-rw-r--r--sys_generic.c449
-rw-r--r--sys_generic.h46
-rw-r--r--sys_linux.c1025
-rw-r--r--sys_linux.h52
-rw-r--r--sys_macosx.c516
-rw-r--r--sys_macosx.h40
-rw-r--r--sys_netbsd.c158
-rw-r--r--sys_netbsd.h39
-rw-r--r--sys_null.c140
-rw-r--r--sys_null.h34
-rw-r--r--sys_posix.c109
-rw-r--r--sys_posix.h36
-rw-r--r--sys_solaris.c95
-rw-r--r--sys_solaris.h38
-rw-r--r--sys_timex.c276
-rw-r--r--sys_timex.h48
-rw-r--r--sysincl.h69
-rw-r--r--tempcomp.c176
-rw-r--r--tempcomp.h29
-rwxr-xr-xtest/compilation/001-features36
-rwxr-xr-xtest/compilation/002-scanbuild16
-rwxr-xr-xtest/compilation/003-sanitizers104
-rw-r--r--test/kernel/Makefile7
-rw-r--r--test/kernel/adjtime.c185
-rw-r--r--test/kernel/ntpadjtime.c75
-rwxr-xr-xtest/simulation/001-defaults13
-rwxr-xr-xtest/simulation/002-largenetwork22
-rwxr-xr-xtest/simulation/003-largefreqoffset19
-rwxr-xr-xtest/simulation/004-largetimeoffset18
-rwxr-xr-xtest/simulation/005-externalstep46
-rwxr-xr-xtest/simulation/006-largejitter21
-rwxr-xr-xtest/simulation/007-largewander20
-rwxr-xr-xtest/simulation/008-ntpera50
-rwxr-xr-xtest/simulation/009-sourceselection40
-rwxr-xr-xtest/simulation/010-multrecv17
-rwxr-xr-xtest/simulation/011-asymjitter18
-rwxr-xr-xtest/simulation/012-daemonts15
-rwxr-xr-xtest/simulation/013-nameserv15
-rwxr-xr-xtest/simulation/101-poll56
-rwxr-xr-xtest/simulation/102-iburst23
-rwxr-xr-xtest/simulation/103-initstepslew63
-rwxr-xr-xtest/simulation/104-driftfile23
-rwxr-xr-xtest/simulation/105-ntpauth96
-rwxr-xr-xtest/simulation/106-refclock143
-rwxr-xr-xtest/simulation/107-allowdeny48
-rwxr-xr-xtest/simulation/108-peer54
-rwxr-xr-xtest/simulation/109-makestep41
-rwxr-xr-xtest/simulation/110-chronyc529
-rwxr-xr-xtest/simulation/111-knownclient17
-rwxr-xr-xtest/simulation/112-port57
-rwxr-xr-xtest/simulation/113-leapsecond61
-rwxr-xr-xtest/simulation/114-presend51
-rwxr-xr-xtest/simulation/115-cmdmontime24
-rwxr-xr-xtest/simulation/116-minsources24
-rwxr-xr-xtest/simulation/117-fallbackdrift24
-rwxr-xr-xtest/simulation/118-maxdelay42
-rwxr-xr-xtest/simulation/119-smoothtime82
-rwxr-xr-xtest/simulation/120-selectoptions89
-rwxr-xr-xtest/simulation/121-orphan26
-rwxr-xr-xtest/simulation/122-xleave91
-rwxr-xr-xtest/simulation/123-mindelay27
-rwxr-xr-xtest/simulation/124-tai45
-rwxr-xr-xtest/simulation/125-packetloss29
-rwxr-xr-xtest/simulation/126-burst45
-rwxr-xr-xtest/simulation/127-filter43
-rwxr-xr-xtest/simulation/128-nocontrol27
-rwxr-xr-xtest/simulation/129-reload109
-rwxr-xr-xtest/simulation/130-quit31
-rwxr-xr-xtest/simulation/131-maxchange20
-rwxr-xr-xtest/simulation/132-logchange21
-rwxr-xr-xtest/simulation/133-hwtimestamp89
-rwxr-xr-xtest/simulation/134-log35
-rwxr-xr-xtest/simulation/135-ratelimit18
-rwxr-xr-xtest/simulation/136-broadcast16
-rwxr-xr-xtest/simulation/137-pool49
-rwxr-xr-xtest/simulation/138-syncloop34
-rwxr-xr-xtest/simulation/139-nts316
-rwxr-xr-xtest/simulation/140-noclientlog21
-rwxr-xr-xtest/simulation/141-copy19
-rwxr-xr-xtest/simulation/142-ntpoverptp106
-rwxr-xr-xtest/simulation/143-manual70
-rwxr-xr-xtest/simulation/144-monoroot55
-rwxr-xr-xtest/simulation/145-rtc75
-rwxr-xr-xtest/simulation/146-offline73
-rwxr-xr-xtest/simulation/147-refresh59
-rwxr-xr-xtest/simulation/148-replacement56
-rwxr-xr-xtest/simulation/201-freqaccumulation35
-rwxr-xr-xtest/simulation/202-prefer21
-rw-r--r--test/simulation/README11
-rwxr-xr-xtest/simulation/run90
-rw-r--r--test/simulation/test.common539
-rwxr-xr-xtest/system/001-minimal13
-rwxr-xr-xtest/system/002-extended13
-rwxr-xr-xtest/system/003-memlock15
-rwxr-xr-xtest/system/004-priority15
-rwxr-xr-xtest/system/006-privdrop17
-rwxr-xr-xtest/system/007-cmdmon188
-rwxr-xr-xtest/system/008-confload83
-rwxr-xr-xtest/system/009-binddevice24
-rwxr-xr-xtest/system/010-nts66
-rwxr-xr-xtest/system/011-systemd140
-rwxr-xr-xtest/system/099-scfilter24
-rwxr-xr-xtest/system/100-clockupdate30
-rwxr-xr-xtest/system/101-rtc19
-rwxr-xr-xtest/system/102-hwtimestamp28
-rwxr-xr-xtest/system/103-refclock19
-rwxr-xr-xtest/system/104-systemdirs19
-rwxr-xr-xtest/system/199-scfilter24
-rwxr-xr-xtest/system/run64
-rw-r--r--test/system/test.common375
-rw-r--r--test/unit/Makefile.in48
-rw-r--r--test/unit/addrfilt.c83
-rw-r--r--test/unit/array.c97
-rw-r--r--test/unit/clientlog.c298
-rw-r--r--test/unit/cmac.c109
-rw-r--r--test/unit/hash.c131
-rw-r--r--test/unit/hwclock.c117
-rw-r--r--test/unit/keys.c173
-rw-r--r--test/unit/ntp_auth.c289
-rw-r--r--test/unit/ntp_core.c648
-rw-r--r--test/unit/ntp_core.keys8
-rw-r--r--test/unit/ntp_ext.c167
-rw-r--r--test/unit/ntp_sources.c378
-rw-r--r--test/unit/nts_ke.crt8
-rw-r--r--test/unit/nts_ke.key25
-rw-r--r--test/unit/nts_ke_client.c147
-rw-r--r--test/unit/nts_ke_server.c235
-rw-r--r--test/unit/nts_ke_session.c224
-rw-r--r--test/unit/nts_ntp_auth.c135
-rw-r--r--test/unit/nts_ntp_client.c302
-rw-r--r--test/unit/nts_ntp_server.c180
-rw-r--r--test/unit/quantiles.c68
-rw-r--r--test/unit/regress.c119
-rw-r--r--test/unit/samplefilt.c120
-rw-r--r--test/unit/siv.c423
-rw-r--r--test/unit/smooth.c63
-rw-r--r--test/unit/socket.c61
-rw-r--r--test/unit/sources.c289
-rw-r--r--test/unit/test.c182
-rw-r--r--test/unit/test.h52
-rw-r--r--test/unit/util.c801
-rw-r--r--util.c1650
-rw-r--r--util.h275
-rw-r--r--version.txt1
299 files changed, 86069 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of 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.
+
+ 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/FAQ b/FAQ
new file mode 100644
index 0000000..c96acfa
--- /dev/null
+++ b/FAQ
@@ -0,0 +1,1159 @@
+Frequently Asked Questions
+
+Table of Contents
+
+ o 1. chrony compared to other programs
+ ? 1.1. How does chrony compare to ntpd?
+ ? 1.2. Should I prefer chrony over timesyncd if I do not need to run a
+ server?
+ o 2. Configuration issues
+ ? 2.1. What is the minimum recommended configuration for an NTP client?
+ ? 2.2. How do I make an NTP server?
+ ? 2.3. Should all computers on a LAN be clients of an external server?
+ ? 2.4. Must I specify servers by IP address if DNS is not available on
+ chronyd start?
+ ? 2.5. How can I make chronyd more secure?
+ ? 2.6. How can I make the system clock more secure?
+ ? 2.7. How can I improve the accuracy of the system clock with NTP
+ sources?
+ ? 2.8. Does chronyd have an ntpdate mode?
+ ? 2.9. Can chronyd be configured to control the clock like ntpd?
+ ? 2.10. Can NTP server be separated from NTP client?
+ ? 2.11. How can chronyd be configured to minimise downtime during
+ restarts?
+ ? 2.12. Should be a leap smear enabled on NTP server?
+ ? 2.13. How should chronyd be configured with gpsd?
+ ? 2.14. Does chrony support PTP?
+ ? 2.15. How can I avoid using wrong PHC refclock?
+ ? 2.16. Why are client log records dropped before reaching
+ clientloglimit?
+ ? 2.17. What happened to the commandkey and generatecommandkey
+ directives?
+ o 3. Computer is not synchronising
+ ? 3.1. Behind a firewall?
+ ? 3.2. Are NTP servers specified with the offline option?
+ ? 3.3. Is name resolution working correctly?
+ ? 3.4. Is chronyd allowed to step the system clock?
+ ? 3.5. Using NTS?
+ ? 3.6. Using a Windows NTP server?
+ ? 3.7. An unreachable source is selected?
+ ? 3.8. Does selected source drop new measurements?
+ ? 3.9. Using a PPS reference clock?
+ o 4. Issues with chronyc
+ ? 4.1. I keep getting the error 506 Cannot talk to daemon
+ ? 4.2. I keep getting the error 501 Not authorised
+ ? 4.3. What is the reference ID reported by the tracking command?
+ ? 4.4. Is the chronyc / chronyd protocol documented anywhere?
+ o 5. Real-time clock issues
+ ? 5.1. What is the real-time clock (RTC)?
+ ? 5.2. Does hwclock have to be disabled?
+ ? 5.3. I just keep getting the 513 RTC driver not running message
+ ? 5.4. I get Could not open /dev/rtc, Device or resource busy in my
+ syslog file
+ ? 5.5. When I start chronyd, the log says Could not enable RTC interrupt
+ : Invalid argument (or it may say disable)
+ ? 5.6. What if my computer does not have an RTC or backup battery?
+ o 6. NTP-specific issues
+ ? 6.1. Can chronyd be driven from broadcast/multicast NTP servers?
+ ? 6.2. Can chronyd transmit broadcast NTP packets?
+ ? 6.3. Can chronyd keep the system clock a fixed offset away from real
+ time?
+ ? 6.4. What happens if the network connection is dropped without using
+ chronyc's offline command first?
+ ? 6.5. Why is an offset measured between two computers synchronised to
+ each another?
+ o 7. Operation
+ ? 7.1. What clocks does chronyd use?
+ o 8. Operating systems
+ ? 8.1. Does chrony support Windows?
+ ? 8.2. Are there any plans to support Windows?
+
+1. chrony compared to other programs
+
+1.1. How does chrony compare to ntpd?
+
+chrony and ntpd are two different implementations of the Network Time Protocol
+(NTP).
+
+chrony is a newer implementation, which was designed to work well in a wider
+range of conditions. It can usually synchronise the system clock faster and
+with better time accuracy. It has many features, but it does not implement some
+of the less useful NTP modes like broadcast client or multicast server/client.
+
+If your computer is connected to the Internet only for few minutes at a time,
+the network connection is often congested, you turn your computer off or
+suspend it frequently, the clock is not very stable (e.g. there are rapid
+changes in the temperature or it is a virtual machine), or you want to use NTP
+on an isolated network with no hardware reference clocks in sight, chrony will
+probably work better for you.
+
+For a more detailed comparison of features and performance, see the comparison
+page on the chrony website.
+
+1.2. Should I prefer chrony over timesyncd if I do not need to run a server?
+
+Generally, yes.
+
+systemd-timesyncd is a very simple NTP client included in the systemd suite. It
+lacks almost all features of chrony and other advanced client implementations
+listed on the comparison page. One of its main limitations is that it cannot
+poll multiple servers at the same time and detect servers having incorrect time
+(falsetickers in the NTP terminology). It should be used only with trusted
+reliable servers, ideally in local network.
+
+Using timesyncd with pool.ntp.org is problematic. The pool is very robust as a
+whole, but the individual servers run by volunteers cannot be relied on.
+Occasionally, servers drift away or make a step to distant past or future due
+to misconfiguration, problematic implementation, and other bugs (e.g. in
+firmware of a GPS receiver). The pool monitoring system detects such servers
+and quickly removes them from the pool DNS, but clients like timesyncd cannot
+recover from that. They follow the server as long as it claims to be
+synchronised. They need to be restarted in order to get a new address from the
+pool DNS.
+
+Note that the complexity of NTP and clock synchronisation is on the client
+side. The amount of code in chrony specific to NTP server is very small and it
+is disabled by default. If it was removed, it would not significantly reduce
+the amount of memory or storage needed.
+
+2. Configuration issues
+
+2.1. What is the minimum recommended configuration for an NTP client?
+
+First, the client needs to know which NTP servers it should ask for the current
+time. They are specified by the server or pool directive. The pool directive is
+used with names that resolve to multiple addresses of different servers. For
+reliable operation, the client should have at least three servers.
+
+The iburst option enables a burst of requests to speed up the initial
+synchronisation.
+
+To stabilise the initial synchronisation on the next start, the estimated drift
+of the system clock is saved to a file specified by the driftfile directive.
+
+If the system clock can be far from the true time after boot for any reason,
+chronyd should be allowed to correct it quickly by stepping instead of slewing,
+which would take a very long time. The makestep directive does that.
+
+In order to keep the real-time clock (RTC) close to the true time, so the
+system time is reasonably close to the true time when it is initialised on the
+next boot from the RTC, the rtcsync directive enables a mode in which the
+system time is periodically copied to the RTC. It is supported on Linux and
+macOS.
+
+If you wanted to use public NTP servers from the pool.ntp.org project, the
+minimal chrony.conf file could be:
+
+pool pool.ntp.org iburst
+driftfile /var/lib/chrony/drift
+makestep 1 3
+rtcsync
+
+2.2. How do I make an NTP server?
+
+By default, chronyd does not operate as an NTP server. You need to add an allow
+directive to the chrony.conf file in order for chronyd to open the server NTP
+port and respond to client requests.
+
+allow 192.168.1.0/24
+
+An allow directive with no specified subnet allows access from all IPv4 and
+IPv6 addresses.
+
+2.3. Should all computers on a LAN be clients of an external server?
+
+It depends on the requirements. Usually, the best configuration is to make one
+computer the server, with the others as clients of it. Add a local directive to
+the server's chrony.conf file. This configuration will be better because
+
+ o the load on the external connection is less
+
+ o the load on the external NTP server(s) is less
+
+ o if your external connection goes down, the computers on the LAN will
+ maintain a common time with each other.
+
+2.4. Must I specify servers by IP address if DNS is not available on chronyd
+start?
+
+No, chronyd will keep trying to resolve the names specified by the server,
+pool, and peer directives in an increasing interval until it succeeds. The
+online command can be issued from chronyc to force chronyd to try to resolve
+the names immediately.
+
+2.5. How can I make chronyd more secure?
+
+If you do not need to use chronyc, or you want to run chronyc only under the
+root or chrony user (which can access chronyd through a Unix domain socket),
+you can disable the IPv4 and IPv6 command sockets (by default listening on
+localhost) by adding cmdport 0 to the configuration file.
+
+You can specify an unprivileged user with the -u option, or the user directive
+in the chrony.conf file, to which chronyd will switch after start in order to
+drop root privileges. The configure script has a --with-user option, which sets
+the default user. On Linux, chronyd needs to be compiled with support for the
+libcap library. On other systems, chronyd forks into two processes. The child
+process retains root privileges, but can only perform a very limited range of
+privileged system calls on behalf of the parent.
+
+Also, if chronyd is compiled with support for the Linux secure computing
+(seccomp) facility, you can enable a system call filter with the -F option. It
+will significantly reduce the kernel attack surface and possibly prevent kernel
+exploits from the chronyd process if it is compromised. It is recommended to
+enable the filter only when it is known to work on the version of the system
+where chrony is installed as the filter needs to allow also system calls made
+from libraries that chronyd is using (e.g. libc) and different versions or
+implementations of the libraries might make different system calls. If the
+filter is missing some system call, chronyd could be killed even in normal
+operation.
+
+2.6. How can I make the system clock more secure?
+
+An NTP client synchronising the system clock to an NTP server is susceptible to
+various attacks, which can break applications and network protocols relying on
+accuracy of the clock (e.g. DNSSEC, Kerberos, TLS, WireGuard).
+
+Generally, a man-in-the-middle (MITM) attacker between the client and server
+can
+
+ o make fake responses, or modify real responses from the server, to create an
+ arbitrarily large time and frequency offset, make the server appear more
+ accurate, insert a leap second, etc.
+
+ o delay the requests and/or responses to create a limited time offset and
+ temporarily also a limited frequency offset
+
+ o drop the requests or responses to prevent updates of the clock with new
+ measurements
+
+ o redirect the requests to a different server
+
+The attacks can be combined for a greater effect. The attacker can delay
+packets to create a significant frequency offset first and then drop all
+subsequent packets to let the clock quickly drift away from the true time. The
+attacker might also be able to control the server's clock.
+
+Some attacks cannot be prevented. Monitoring is needed for detection, e.g. the
+reachability register in the sources report shows missing packets. The extent
+to which the attacker can control the client's clock depends on its
+configuration.
+
+Enable authentication to prevent chronyd from accepting modified, fake, or
+redirected packets. It can be enabled with a symmetric key specified by the key
+option, or Network Time Security (NTS) by the nts option (supported since
+chrony version 4.0). The server needs to support the selected authentication
+mechanism. Symmetric keys have to be configured on both client and server, and
+each client must have its own key (one per server).
+
+The maximum offset that the attacker can insert in an NTP measurement by
+delaying packets can be limited by the maxdelay option. The default value is 3
+seconds. The measured delay is reported as the peer delay in the ntpdata report
+and measurements log. Set the maxdelay option to a value larger than the
+maximum value that is normally observed. Note that the delay can increase
+significantly even when not under an attack, e.g. when the network is congested
+or the routing has changed.
+
+The maximum accepted change in time offset between clock updates can be limited
+by the maxchange directive. Larger changes in the offset will be ignored or
+cause chronyd to exit. Note that the attacker can get around this limit by
+splitting the offset into multiple smaller offsets and/or creating a large
+frequency offset. When this directive is used, chronyd will have to be
+restarted after a successful attack. It will not be able to recover on its own.
+It must not be restarted automatically (e.g. by the service manager).
+
+The impact of a large accepted time offset can be reduced by disabling clock
+steps, i.e. by not using the makestep and initstepslew directives. The offset
+will be slowly corrected by speeding up or slowing down the clock at a rate
+which can be limited by the maxslewrate directive. Disabling clock steps
+completely is practical only if the clock cannot gain a larger error on its
+own, e.g. when the computer is shut down or suspended, and the maxslewrate
+limit is large enough to correct an expected error in an acceptable time. The
+rtcfile directive with the -s option can be used to compensate for the RTC
+drift.
+
+A more practical approach is to enable makestep for a limited number of clock
+updates (the 2nd argument of the directive) and limit the offset change in all
+updates by the maxchange directive. The attacker will be able to make only a
+limited step and only if the attack starts in a short window after booting the
+computer, or when chronyd is restarted without the -R option.
+
+The frequency offset can be limited by the maxdrift directive. The measured
+frequency offset is reported in the drift file, tracking report, and tracking
+log. Set maxdrift to a value larger than the maximum absolute value that is
+normally observed. Note that the frequency of the clock can change due to aging
+of the crystal, differences in calibration of the clock source between reboots,
+migrated virtual machine, etc. A typical computer clock has a drift smaller
+than 100 parts per million (ppm), but much larger drifts are possible (e.g. in
+some virtual machines).
+
+Use only trusted servers, which you expect to be well configured and managed,
+using authentication for their own servers, etc. Use multiple servers, ideally
+in different locations. The attacker will have to deal with a majority of the
+servers in order to pass the source selection and update the clock with a large
+offset. Use the minsources directive to increase the required number of
+selectable sources to make the selection more robust.
+
+Do not specify servers as peers. The symmetric mode is less secure than the
+client/server mode. If not authenticated, it is vulnerable to off-path
+denial-of-service attacks, and even when it is authenticated, it is still
+susceptible to replay attacks.
+
+Mixing of authenticated and unauthenticated servers should generally be
+avoided. If mixing is necessary (e.g. for a more accurate and stable
+synchronisation to a closer server which does not support authentication), the
+authenticated servers should be configured as trusted and required to not allow
+the unauthenticated servers to override the authenticated servers in the source
+selection. Since chrony version 4.0, the selection options are enabled in such
+a case automatically. This behaviour can be disabled or modified by the
+authselectmode directive.
+
+An example of a client configuration limiting the impact of the attacks could
+be
+
+server ntp1.example.net iburst nts maxdelay 0.1
+server ntp2.example.net iburst nts maxdelay 0.2
+server ntp3.example.net iburst nts maxdelay 0.05
+server ntp4.example.net iburst nts maxdelay 0.1
+server ntp5.example.net iburst nts maxdelay 0.1
+minsources 3
+maxchange 100 0 0
+makestep 0.001 1
+maxdrift 100
+maxslewrate 100
+driftfile /var/lib/chrony/drift
+ntsdumpdir /var/lib/chrony
+rtcsync
+
+2.7. How can I improve the accuracy of the system clock with NTP sources?
+
+Select NTP servers that are well synchronised, stable and close to your
+network. It is better to use more than one server. Three or four is usually
+recommended as the minimum, so chronyd can detect servers that serve false time
+and combine measurements from multiple sources.
+
+If you have a network card with hardware timestamping supported on Linux, it
+can be enabled by the hwtimestamp directive. It should make local receive and
+transmit timestamps of NTP packets much more stable and accurate.
+
+The server directive has some useful options: minpoll, maxpoll, polltarget,
+maxdelay, maxdelayratio, maxdelaydevratio, xleave, filter.
+
+The first three options set the minimum and maximum allowed polling interval,
+and how should be the actual interval adjusted in the specified range. Their
+default values are 6 (64 seconds) for minpoll, 10 (1024 seconds) for maxpoll
+and 8 (samples) for polltarget. The default values should be used for general
+servers on the Internet. With your own NTP servers, or if you have permission
+to poll some servers more frequently, setting these options for shorter polling
+intervals might significantly improve the accuracy of the system clock.
+
+The optimal polling interval depends mainly on two factors, stability of the
+network latency and stability of the system clock (which mainly depends on the
+temperature sensitivity of the crystal oscillator and the maximum rate of the
+temperature change).
+
+Generally, if the sourcestats command usually reports a small number of samples
+retained for a source (e.g. fewer than 16), a shorter polling interval should
+be considered. If the number of samples is usually at the maximum of 64, a
+longer polling interval might work better.
+
+An example of the directive for an NTP server on the Internet that you are
+allowed to poll frequently could be
+
+server ntp.example.net minpoll 4 maxpoll 6 polltarget 16
+
+An example using shorter polling intervals with a server located in the same
+LAN could be
+
+server ntp.local minpoll 2 maxpoll 4 polltarget 30
+
+The maxdelay options are useful to ignore measurements with an unusually large
+delay (e.g. due to congestion in the network) and improve the stability of the
+synchronisation. The maxdelaydevratio option could be added to the example with
+local NTP server
+
+server ntp.local minpoll 2 maxpoll 4 polltarget 30 maxdelaydevratio 2
+
+If your server supports the interleaved mode (e.g. it is running chronyd), the
+xleave option should be added to the server directive to enable the server to
+provide the client with more accurate transmit timestamps (kernel or preferably
+hardware). For example:
+
+server ntp.local minpoll 2 maxpoll 4 xleave
+
+When combined with local hardware timestamping, good network switches, and even
+shorter polling intervals, a sub-microsecond accuracy and stability of a few
+tens of nanoseconds might be possible. For example:
+
+server ntp.local minpoll 0 maxpoll 0 xleave
+hwtimestamp eth0
+
+For best stability, the CPU should be running at a constant frequency (i.e.
+disabled power saving and performance boosting). Energy-Efficient Ethernet
+(EEE) should be disabled in the network. The switches should be configured to
+prioritize NTP packets, especially if the network is expected to be heavily
+loaded. The dscp directive can be used to set the Differentiated Services Code
+Point in transmitted NTP packets if needed.
+
+If it is acceptable for NTP clients in the network to send requests at a high
+rate, a sub-second polling interval can be specified. A median filter can be
+enabled in order to update the clock at a reduced rate with more stable
+measurements. For example:
+
+server ntp.local minpoll -6 maxpoll -6 filter 15 xleave
+hwtimestamp eth0 minpoll -6
+
+Since chrony version 4.3, the minimum minpoll is -7 and a filter using a
+long-term estimate of a delay quantile can be enabled by the maxdelayquant
+option to replace the default maxdelaydevratio filter, which is sensitive to
+outliers corrupting the minimum delay. For example:
+
+server ntp.local minpoll -7 maxpoll -7 filter 31 maxdelayquant 0.3 xleave
+
+Since version 4.2, chronyd supports an NTPv4 extension field containing an
+additional timestamp to enable frequency transfer and significantly improve
+stability of synchronisation. It can be enabled by the extfield F323 option.
+For example:
+
+server ntp.local minpoll 0 maxpoll 0 xleave extfield F323
+
+Since version 4.5, chronyd can apply corrections from PTP one-step end-to-end
+transparent clocks (e.g. network switches) to significantly improve accuracy of
+synchronisation in local networks. It requires the PTP transport to be enabled
+by the ptpport directive, HW timestamping, and the extfield F324 option. For
+example:
+
+server ntp.local minpoll -4 maxpoll -4 xleave extfield F323 extfield F324 port 319
+ptpport 319
+hwtimestamp eth0 minpoll -4
+
+2.8. Does chronyd have an ntpdate mode?
+
+Yes. With the -q option chronyd will set the system clock once and exit. With
+the -Q option it will print the measured offset without setting the clock. If
+you do not want to use a configuration file, NTP servers can be 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 to one (supported since chrony version 4.0), and a timeout can be
+specified with the -t option. The following command would take only up to about
+one second.
+
+# chronyd -q -t 1 'server pool.ntp.org iburst maxsamples 1'
+
+It is not recommended to run chronyd with the -q option periodically (e.g. from
+a cron job) as a replacement for the daemon mode, because it performs
+significantly worse (e.g. the clock is stepped and its frequency is not
+corrected). If you must run it this way and you are using a public NTP server,
+make sure chronyd does not always start around the first second of a minute,
+e.g. by adding a random sleep before the chronyd command. Public servers
+typically receive large bursts of requests around the first second as there is
+a large number of NTP clients started from cron with no delay.
+
+2.9. Can chronyd be configured to control the clock like ntpd?
+
+It is not possible to perfectly emulate ntpd, but there are some options that
+can configure chronyd to behave more like ntpd if there is a reason to prefer
+that.
+
+In the following example the minsamples directive slows down the response to
+changes in the frequency and offset of the clock. The maxslewrate and
+corrtimeratio directives reduce the maximum frequency error due to an offset
+correction and the maxdrift directive reduces the maximum assumed frequency
+error of the clock. The makestep directive enables a step threshold and the
+maxchange directive enables a panic threshold. The maxclockerror directive
+increases the minimum dispersion rate.
+
+minsamples 32
+maxslewrate 500
+corrtimeratio 100
+maxdrift 500
+makestep 0.128 -1
+maxchange 1000 1 1
+maxclockerror 15
+
+Note that increasing minsamples might cause the offsets in the tracking and
+sourcestats reports/logs to be significantly smaller than the actual offsets
+and be unsuitable for monitoring.
+
+2.10. Can NTP server be separated from NTP client?
+
+Yes, it is possible to run multiple instances of chronyd on a computer at the
+same time. One can operate primarily as an NTP client to synchronise the system
+clock and another as a server for other computers. If they use the same
+filesystem, they need to be configured with different pidfiles, Unix domain
+command sockets, and any other file or directory specified in the configuration
+file. If they run in the same network namespace, they need to use different NTP
+and command ports, or bind the ports to different addresses or interfaces.
+
+The server instance should be started with the -x option to prevent it from
+adjusting the system clock and interfering with the client instance. It can be
+configured as a client to synchronise its NTP clock to other servers, or the
+client instance running on the same computer. In the latter case, the copy
+option (added in chrony version 4.1) can be used to assume the reference ID and
+stratum of the client instance, which enables detection of synchronisation
+loops with its own clients.
+
+On Linux, starting with chrony version 4.0, it is possible to run multiple
+server instances sharing a port to better utilise multiple cores of the CPU.
+Note that for rate limiting and client/server interleaved mode to work well it
+is necessary that all packets received from the same address are handled by the
+same server instance.
+
+An example configuration of the client instance could be
+
+pool pool.ntp.org iburst
+allow 127.0.0.1
+port 11123
+driftfile /var/lib/chrony/drift
+makestep 1 3
+rtcsync
+
+and configuration of the first server instance could be
+
+server 127.0.0.1 port 11123 minpoll 0 maxpoll 0 copy
+allow
+cmdport 11323
+bindcmdaddress /var/run/chrony/chronyd-server1.sock
+pidfile /var/run/chronyd-server1.pid
+driftfile /var/lib/chrony/drift-server1
+
+2.11. How can chronyd be configured to minimise downtime during restarts?
+
+The dumpdir directive in chrony.conf provides chronyd a location to save a
+measurement history of the sources it uses when the service exits. The -r
+option then enables chronyd to load state from the dump files, reducing the
+synchronisation time after a restart.
+
+Similarly, the ntsdumpdir directive provides a location for chronyd to save NTS
+cookies received from the server to avoid making a NTS-KE request when chronyd
+is started. When operating as an NTS server, chronyd also saves cookies keys to
+this directory to allow clients to continue to use the old keys after a server
+restart for a more seamless experience.
+
+On Linux systems, systemd socket activation provides a mechanism to reuse
+server sockets across chronyd restarts, so that client requests will be
+buffered until the service is again able to handle the requests. This allows
+for zero-downtime service restarts, simplified dependency logic at boot, and
+on-demand service spawning (for instance, for separated server chronyd
+instances run with the -x flag).
+
+Socket activation is supported since chrony version 4.5. The service manager
+(systemd) creates sockets and passes file descriptors to them to the process
+via the LISTEN_FDS environment variable. Before opening new sockets, chronyd
+first checks for and attempts to reuse matching sockets passed from the service
+manager. For instance, if an IPv4 datagram socket bound on bindaddress and port
+is available, it will be used by the NTP server to accept incoming IPv4
+requests.
+
+An example systemd socket unit is below, where chronyd is configured with
+bindaddress 0.0.0.0, bindaddress ::, port 123, and ntsport 4460.
+
+[Unit]
+Description=chronyd server sockets
+
+[Socket]
+Service=chronyd.service
+# IPv4 NTP server
+ListenDatagram=0.0.0.0:123
+# IPv6 NTP server
+ListenDatagram=[::]:123
+# IPv4 NTS-KE server
+ListenStream=0.0.0.0:4460
+# IPv6 NTS-KE server
+ListenStream=[::]:4460
+BindIPv6Only=ipv6-only
+
+[Install]
+WantedBy=sockets.target
+
+2.12. Should be a leap smear enabled on NTP server?
+
+With the smoothtime and leapsecmode directives it is possible to enable a
+server leap smear in order to hide leap seconds from clients and force them to
+follow a slow server's adjustment instead.
+
+This feature should be used only in local networks and only when necessary,
+e.g. when the clients cannot be configured to handle the leap seconds as
+needed, or their number is so large that configuring them all would be
+impractical. The clients should use only one leap-smearing server, or multiple
+identically configured leap-smearing servers. Note that some clients can get
+leap seconds from other sources (e.g. with the leapsectz directive in chrony)
+and they will not work correctly with a leap smearing server.
+
+2.13. How should chronyd be configured with gpsd?
+
+A GPS or other GNSS receiver can be used as a reference clock with gpsd. It can
+work as one or two separate time sources for each connected receiver. The first
+time source is based on timestamping of messages sent by the receiver.
+Typically, it is accurate to milliseconds. The other source is much more
+accurate. It is timestamping a pulse-per-second (PPS) signal, usually connected
+to a serial port (e.g. DCD pin) or GPIO pin.
+
+If the PPS signal is connected to the serial port which is receiving messages
+from the GPS/GNSS receiver, gpsd should detect and use it automatically. If it
+is connected to a GPIO pin, or another serial port, the PPS device needs to be
+specified on the command line as an additional data source. On Linux, the
+ldattach utility can be used to create a PPS device for a serial device.
+
+The PPS-based time source provided by gpsd is available as a SHM 1 refclock, or
+other odd number if gpsd is configured with multiple receivers, and also as
+SOCK /var/run/chrony.DEV.sock where DEV is the name of the serial device (e.g.
+ttyS0).
+
+The message-based time source is available as a SHM 0 refclock (or other even
+number) and since gpsd version 3.25 also as SOCK /var/run/chrony.clk.DEV.sock
+where DEV is the name of the serial device.
+
+The SOCK refclocks should be preferred over SHM for better security (the shared
+memory segment needs to be created by chronyd or gpsd with an expected owner
+and permissions before an untrusted application or user has a chance to create
+its own in order to feed chronyd with false measurements). gpsd needs to be
+started after chronyd in order to connect to the socket.
+
+With chronyd and gpsd both supporting PPS, there are two different recommended
+configurations:
+
+# First option
+refclock SOCK /var/run/chrony.ttyS0.sock refid GPS
+
+# Second option
+refclock PPS /dev/pps0 lock NMEA refid GPS
+refclock SOCK /var/run/chrony.clk.ttyS0.sock offset 0.5 delay 0.1 refid NMEA noselect
+
+They both have some advantages:
+
+ o SOCK can be more accurate than PPS if gpsd corrects for the sawtooth error
+ provided by the receiver in serial data
+
+ o PPS can be used with higher PPS rates (specified by the rate option), but
+ it requires a second refclock or another time source to pair pulses with
+ seconds, and the SOCK offset needs to be specified correctly to compensate
+ for the message delay, while gpsd can apply HW-specific information
+
+If the PPS signal is not available, or cannot be used for some reason, the only
+option is the message-based timing
+
+refclock SOCK /var/run/chrony.clk.ttyS0.sock offset 0.5 delay 0.1 refid GPS
+
+or the SHM equivalent if using gpsd version before 3.25
+
+refclock SHM 0 offset 0.5 delay 0.1 refid GPS
+
+2.14. Does chrony support PTP?
+
+No, the Precision Time Protocol (PTP) is not supported as a protocol for
+synchronisation of clocks and there are no plans to support it. It is a complex
+protocol, which shares some issues with the NTP broadcast mode. One of the main
+differences between NTP and PTP is that PTP was designed to be easily supported
+in hardware (e.g. network switches and routers) in order to make more stable
+and accurate measurements. PTP relies on the hardware support. NTP does not
+rely on any support in the hardware, but if it had the same support as PTP, it
+could perform equally well.
+
+On Linux, chrony supports hardware clocks that some NICs have for PTP. They are
+called PTP hardware clocks (PHC). They can be used as reference clocks
+(specified by the refclock directive) and for hardware timestamping of NTP
+packets (enabled by the hwtimestamp directive) if the NIC can timestamp other
+packets than PTP, which is usually the case at least for transmitted packets.
+The ethtool -T command can be used to verify the timestamping support.
+
+As an experimental feature added in version 4.2, chrony can use PTP as a
+transport for NTP messages (NTP over PTP) to enable hardware timestamping on
+hardware which can timestamp PTP packets only. It can be enabled by the ptpport
+directive. Since version 4.5, chrony can also apply corrections provided by PTP
+one-step end-to-end transparent clocks to reach the accuracy of ordinary PTP
+clocks. The application of PTP corrections can be enabled by the extfield F324
+option.
+
+2.15. How can I avoid using wrong PHC refclock?
+
+If your system has multiple PHC devices, normally named by udev as /dev/ptp0, /
+dev/ptp1, and so on, their order can change randomly across reboots depending
+on the order of initialisation of their drivers. If a PHC refclock is specified
+by this name, chronyd could be using a wrong refclock after reboot. To prevent
+that, you can configure udev to create a stable symlink for chronyd with a rule
+like this (e.g. written to /etc/udev/rules.d/80-phc.rules):
+
+KERNEL=="ptp[0-9]*", DEVPATH=="/devices/pci0000:00/0000:00:01.2/0000:02:00.0/ptp/*", SYMLINK+="ptp-i350-1"
+
+You can get the full DEVPATH of an existing PHC device with the udevadm info
+command. You will need to execute the udevadm trigger command, or reboot the
+system, for these changes to take effect.
+
+2.16. Why are client log records dropped before reaching clientloglimit?
+
+The number of dropped client log records reported by the serverstats command
+can be increasing before the number of clients reported by the clients command
+reaches the maximum value corresponding to the memory limit set by the
+clientloglimit directive.
+
+This is due to the design of the data structure keeping the client records. It
+is a hash table which can store only up to 16 colliding addresses per slot. If
+a slot has more collisions and the table already has the maximum size, the
+oldest record will be dropped and replaced by the new client.
+
+Note that the size of the table is always a power of two and it can only grow.
+The limit set by the clientloglimit directive takes into account that two
+copies of the table exist when it is being resized. This means the actual
+memory usage reported by top and other utilities can be significantly smaller
+than the limit even when the maximum number of records is used.
+
+The absolute maximum number of client records kept at the same time is
+16777216.
+
+2.17. What happened to the commandkey and generatecommandkey directives?
+
+They were removed in version 2.2. Authentication is no longer supported in the
+command protocol. Commands that required authentication are now allowed only
+through a Unix domain socket, which is accessible only by the root and chrony
+users. If you need to configure chronyd remotely or locally without the root
+password, please consider using ssh and/or sudo to run chronyc under the root
+or chrony user on the host where chronyd is running.
+
+3. Computer is not synchronising
+
+This is the most common problem. There are a number of reasons, see the
+following questions.
+
+3.1. Behind a firewall?
+
+Check the Reach value printed by the chronyc's sources command. If it is zero,
+it means chronyd did not get any valid responses from the NTP server you are
+trying to use. If there is a firewall between you and the server, the requests
+sent to the UDP port 123 of the server or responses sent back from the port
+might be blocked. Try using a tool like wireshark or tcpdump to see if you are
+getting any responses from the server.
+
+When chronyd is receiving responses from the servers, the output of the sources
+command issued few minutes after chronyd start might look like this:
+
+MS Name/IP address Stratum Poll Reach LastRx Last sample
+===============================================================================
+^* ntp1.example.net 2 6 377 34 +484us[ -157us] +/- 30ms
+^- ntp2.example.net 2 6 377 34 +33ms[ +32ms] +/- 47ms
+^+ ntp3.example.net 3 6 377 35 -1397us[-2033us] +/- 60ms
+
+3.2. Are NTP servers specified with the offline option?
+
+Check that the chronyc's online and offline commands are used appropriately
+(e.g. in the system networking scripts). The activity command prints the number
+of sources that are currently online and offline. For example:
+
+200 OK
+3 sources online
+0 sources offline
+0 sources doing burst (return to online)
+0 sources doing burst (return to offline)
+0 sources with unknown address
+
+3.3. Is name resolution working correctly?
+
+NTP servers specified by their hostname (instead of an IP address) have to have
+their names resolved before chronyd can send any requests to them. If the
+activity command prints a non-zero number of sources with unknown address,
+there is an issue with the resolution. Typically, a DNS server is specified in
+/etc/resolv.conf. Make sure it is working correctly.
+
+Since chrony version 4.0, you can run chronyc -N sources -a command to print
+all sources, even those that do not have a known address yet, with their names
+as they were specified in the configuration. This can be useful to verify that
+the names specified in the configuration are used as expected.
+
+3.4. Is chronyd allowed to step the system clock?
+
+By default, chronyd adjusts the clock gradually by slowing it down or speeding
+it up. If the clock is too far from the true time, it will take a long time to
+correct the error. The System time value printed by the chronyc's tracking
+command is the remaining correction that needs to be applied to the system
+clock.
+
+The makestep directive can be used to allow chronyd to step the clock. For
+example, if chrony.conf had
+
+makestep 1 3
+
+the clock would be stepped in the first three updates if its offset was larger
+than one second. Normally, it is recommended to allow the step only in the
+first few updates, but in some cases (e.g. a computer without an RTC or virtual
+machine which can be suspended and resumed with an incorrect time) it might be
+necessary to allow the step on any clock update. The example above would change
+to
+
+makestep 1 -1
+
+3.5. Using NTS?
+
+The Network Time Security (NTS) mechanism uses Transport Layer Security (TLS)
+to establish the keys needed for authentication of NTP packets.
+
+Run the authdata command to check whether the key establishment was successful:
+
+# chronyc -N authdata
+Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+ntp1.example.net NTS 1 15 256 33m 0 0 8 100
+ntp2.example.net NTS 1 15 256 33m 0 0 8 100
+ntp3.example.net NTS 1 15 256 33m 0 0 8 100
+
+The KeyID, Type, and KLen columns should have non-zero values. If they are
+zero, check the system log for error messages from chronyd. One possible cause
+of failure is a firewall blocking the client's connection to the server's TCP
+port 4460.
+
+Another possible cause of failure is a certificate that is failing to verify
+because the client's clock is wrong. This is a chicken-and-egg problem with
+NTS. You might need to manually correct the date, or temporarily disable NTS,
+in order to get NTS working. If your computer has an RTC and it is backed up by
+a good battery, this operation should be needed only once, assuming the RTC
+will be set periodically with the rtcsync directive, or compensated with the
+rtcfile directive and the -s option.
+
+If the computer does not have an RTC or battery, you can use the -s option
+without rtcfile directive to restore time of the last shutdown or reboot from
+the drift file. The clock will start behind the true time, but if the computer
+was not shut down for too long and the server's certificate was not renewed too
+close to its expiration, it should be sufficient for the time checks to
+succeed.
+
+If you run your own server, you can use a self-signed certificate covering all
+dates where the client can start (e.g. years 1970-2100). The certificate needs
+to be installed on the client and specified with the ntstrustedcerts directive.
+The server can have multiple names and certificates. To avoid trusting a
+certificate for too long, a new certificate can be added to the server
+periodically (e.g. once per year) and the client can have the server name and
+trusted certificate updated automatically (e.g. using a package repository, or
+a cron script downloading the files directly from the server over HTTPS). A
+client that was shut down for years will still be able to synchronise its clock
+and perform the update as long as the server keeps the old certificate.
+
+As a last resort, you can disable the time checks by the nocerttimecheck
+directive. This has some important security implications. To reduce the
+security risk, you can use the nosystemcert and ntstrustedcerts directives to
+disable the system's default trusted certificate authorities and trust only a
+minimal set of selected authorities needed to validate the certificates of used
+NTP servers.
+
+3.6. Using a Windows NTP server?
+
+A common issue with Windows NTP servers is that they report a very large root
+dispersion (e.g. three seconds or more), which causes chronyd to ignore the
+server for being too inaccurate. The sources command might show a valid
+measurement, but the server is not selected for synchronisation. You can check
+the root dispersion of the server with the chronyc's ntpdata command.
+
+The maxdistance value needs to be increased in chrony.conf to enable
+synchronisation to such a server. For example:
+
+maxdistance 16.0
+
+3.7. An unreachable source is selected?
+
+When chronyd is configured with multiple time sources, it tries to select the
+most accurate and stable sources for synchronisation of the system clock. They
+are marked with the * or + symbol in the report printed by the sources command.
+
+When the best source (marked with the * symbol) becomes unreachable (e.g. NTP
+server stops responding), chronyd will not immediately switch to the second
+best source in an attempt to minimise the error of the clock. It will let the
+clock run free for as long as its estimated error (in terms of root distance)
+based on previous measurements is smaller than the estimated error of the
+second source, and there is still an interval which contains some measurements
+from both sources.
+
+If the first source was significantly better than the second source, it can
+take many hours before the second source is selected, depending on its polling
+interval. You can force a faster reselection by increasing the clock error rate
+(maxclockerror directive), shortening the polling interval (maxpoll option), or
+reducing the number of samples (maxsamples option).
+
+3.8. Does selected source drop new measurements?
+
+chronyd can drop a large number of successive NTP measurements if they are not
+passing some of the NTP tests. The sources command can report for a selected
+source the fully-reachable value of 377 in the Reach column and at the same
+time a LastRx value that is much larger than the current polling interval. If
+the source is online, this indicates that a number of measurements was dropped.
+You can use the ntpdata command to check the NTP tests for the last
+measurement. Usually, it is the test C which fails.
+
+This can be an issue when there is a long-lasting increase in the measured
+delay, e.g. due to a routing change in the network. Unfortunately, chronyd does
+not know for how long it should wait for the delay to come back to the original
+values, or whether it is a permanent increase and it should start from scratch.
+
+The test C is an adaptive filter. It can take many hours before it accepts a
+measurement with the larger delay, and even much longer before it drops all
+measurements with smaller delay, which determine an expected delay used by the
+test. You can use the reset sources command to drop all measurements
+immediately (available in chrony 4.0 and later). If this issue happens
+frequently, you can effectively disable the test by setting the
+maxdelaydevratio option to a very large value (e.g. 1000000), or speed up the
+recovery by increasing the clock error rate with the maxclockerror directive.
+
+3.9. Using a PPS reference clock?
+
+A pulse-per-second (PPS) reference clock requires a non-PPS time source to
+determine which second of UTC corresponds to each pulse. If it is another
+reference clock specified with the lock option in the refclock directive, the
+offset between the two reference clocks must be smaller than 0.4 seconds (0.2
+seconds with chrony versions before 4.1) in order for the PPS reference clock
+to work. With NMEA reference clocks it is common to have a larger offset. It
+needs to be corrected with the offset option.
+
+One approach to find out a good value of the offset option is to configure the
+reference clocks with the noselect option and compare them to an NTP server.
+For example, if the sourcestats command showed
+
+Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
+==============================================================================
+PPS0 0 0 0 +0.000 2000.000 +0ns 4000ms
+NMEA 58 30 231 -96.494 38.406 +504ms 6080us
+ntp1.example.net 7 3 200 -2.991 16.141 -107us 492us
+
+the offset of the NMEA source would need to be increased by about 0.504
+seconds. It does not have to be very accurate. As long as the offset of the
+NMEA reference clock stays below the limit, the PPS reference clock should be
+able to determine the seconds corresponding to the pulses and allow the samples
+to be used for synchronisation.
+
+4. Issues with chronyc
+
+4.1. I keep getting the error 506 Cannot talk to daemon
+
+When accessing chronyd remotely, make sure that the chrony.conf file (on the
+computer where chronyd is running) has a cmdallow entry for the computer you
+are running chronyc on and an appropriate bindcmdaddress directive. This is not
+necessary for localhost.
+
+Perhaps chronyd is not running. Try using the ps command (e.g. on Linux, ps
+-auxw) to see if it is running. Or try netstat -a and see if the UDP port 323
+is listening. If chronyd is not running, you might have a problem with the way
+you are trying to start it (e.g. at boot time).
+
+Perhaps you have a firewall set up in a way that blocks packets on the UDP port
+323. You need to amend the firewall configuration in this case.
+
+4.2. I keep getting the error 501 Not authorised
+
+This error indicates that chronyc sent the command to chronyd using a UDP
+socket instead of the Unix domain socket (e.g. /var/run/chrony/chronyd.sock),
+which is required for some commands. For security reasons, only the root and
+chrony users are allowed to access the socket.
+
+It is also possible that the socket does not exist. chronyd will not create the
+socket if the directory has a wrong owner or permissions. In this case there
+should be an error message from chronyd in the system log.
+
+4.3. What is the reference ID reported by the tracking command?
+
+The reference ID is a 32-bit value used in NTP to prevent synchronisation
+loops.
+
+In chrony versions before 3.0 it was printed in the quad-dotted notation, even
+if the reference source did not actually have an IPv4 address. For IPv4
+addresses, the reference ID is equal to the address, but for IPv6 addresses it
+is the first 32 bits of the MD5 sum of the address. For reference clocks, the
+reference ID is the value specified with the refid option in the refclock
+directive.
+
+Since version 3.0, the reference ID is printed as a hexadecimal number to avoid
+confusion with IPv4 addresses.
+
+If you need to get the IP address of the current reference source, use the -n
+option to disable resolving of IP addresses and read the second field (printed
+in parentheses) on the Reference ID line.
+
+4.4. Is the chronyc / chronyd protocol documented anywhere?
+
+Only by the source code. See cmdmon.c (chronyd side) and client.c (chronyc
+side).
+
+Note that this protocol is not compatible with the mode 6 or mode 7 protocol
+supported by ntpd, i.e. the ntpq or ntpdc utility cannot be used to monitor
+chronyd, and chronyc cannot be used to monitor ntpd.
+
+5. Real-time clock issues
+
+5.1. What is the real-time clock (RTC)?
+
+This is the clock which keeps the time even when your computer is turned off.
+It is used to initialise the system clock on boot. It normally does not drift
+more than few seconds per day.
+
+There are two approaches how chronyd can work with it. One is to use the
+rtcsync directive, which tells chronyd to enable a kernel mode which sets the
+RTC from the system clock every 11 minutes. chronyd itself will not touch the
+RTC. If the computer is not turned off for a long time, the RTC should still be
+close to the true time when the system clock will be initialised from it on the
+next boot.
+
+The other option is to use the rtcfile directive, which tells chronyd to
+monitor the rate at which the RTC gains or loses time. When chronyd is started
+with the -s option on the next boot, it will set the system time from the RTC
+and also compensate for the drift it has measured previously. The rtcautotrim
+directive can be used to keep the RTC close to the true time, but it is not
+strictly necessary if its only purpose is to set the system clock when chronyd
+is started on boot. See the documentation for details.
+
+5.2. Does hwclock have to be disabled?
+
+The hwclock program is run by default in the boot and/or shutdown scripts in
+some Linux installations. With the kernel RTC synchronisation (rtcsync
+directive), the RTC will be set also every 11 minutes as long as the system
+clock is synchronised. If you want to use chronyd's RTC monitoring (rtcfile
+directive), it is important to disable hwclock in the shutdown procedure. If
+you do not do that, it will overwrite the RTC with a new value, unknown to
+chronyd. At the next reboot, chronyd started with the -s option will compensate
+this (wrong) time with its estimate of how far the RTC has drifted whilst the
+power was off, giving a meaningless initial system time.
+
+There is no need to remove hwclock from the boot process, as long as chronyd is
+started after it has run.
+
+5.3. I just keep getting the 513 RTC driver not running message
+
+For the real-time clock support to work, you need the following three things
+
+ o an RTC in your computer
+
+ o a Linux kernel with enabled RTC support
+
+ o an rtcfile directive in your chrony.conf file
+
+5.4. I get Could not open /dev/rtc, Device or resource busy in my syslog file
+
+Some other program running on the system might be using the device.
+
+5.5. When I start chronyd, the log says Could not enable RTC interrupt :
+Invalid argument (or it may say disable)
+
+Your real-time clock hardware might not support the required ioctl requests:
+
+ o RTC_UIE_ON
+
+ o RTC_UIE_OFF
+
+A possible solution could be to build the Linux kernel with support for
+software emulation instead; try enabling the following configuration option
+when building the Linux kernel:
+
+ o CONFIG_RTC_INTF_DEV_UIE_EMUL
+
+5.6. What if my computer does not have an RTC or backup battery?
+
+In this case you can still use the -s option to set the system clock to the
+last modification time of the drift file, which should correspond to the system
+time when chronyd was previously stopped. The initial system time will be
+increasing across reboots and applications started after chronyd will not
+observe backward steps.
+
+6. NTP-specific issues
+
+6.1. Can chronyd be driven from broadcast/multicast NTP servers?
+
+No, the broadcast/multicast client mode is not supported and there is currently
+no plan to implement it. While this mode can simplify configuration of clients
+in large networks, it is inherently less accurate and less secure (even with
+authentication) than the ordinary client/server mode.
+
+When configuring a large number of clients in a network, it is recommended to
+use the pool directive with a DNS name which resolves to addresses of multiple
+NTP servers. The clients will automatically replace the servers when they
+become unreachable, or otherwise unsuitable for synchronisation, with new
+servers from the pool.
+
+Even with very modest hardware, an NTP server can serve time to hundreds of
+thousands of clients using the ordinary client/server mode.
+
+6.2. Can chronyd transmit broadcast NTP packets?
+
+Yes, the broadcast directive can be used to enable the broadcast server mode to
+serve time to clients in the network which support the broadcast client mode
+(it is not supported in chronyd). Note that this mode should generally be
+avoided. See the previous question.
+
+6.3. Can chronyd keep the system clock a fixed offset away from real time?
+
+Yes. Starting from version 3.0, an offset can be specified by the offset option
+for all time sources in the chrony.conf file.
+
+6.4. What happens if the network connection is dropped without using chronyc's
+offline command first?
+
+chronyd will keep trying to access the sources that it thinks are online, and
+it will take longer before new measurements are actually made and the clock is
+corrected when the network is connected again. If the sources were set to
+offline, chronyd would make new measurements immediately after issuing the
+online command.
+
+Unless the network connection lasts only few minutes (less than the maximum
+polling interval), the delay is usually not a problem, and it might be
+acceptable to keep all sources online all the time.
+
+6.5. Why is an offset measured between two computers synchronised to each
+another?
+
+When two computers are synchronised to each other using the client/server or
+symmetric NTP mode, there is an expectation that NTP measurements between the
+two computers made on both ends show an average offset close to zero.
+
+With chronyd that can be expected only when the interleaved mode is enabled by
+the xleave option. Otherwise, chronyd will use different transmit timestamps
+(e.g. daemon timestamp vs kernel timestamp) for serving time and
+synchronisation of its own clock, which will cause the other computer to
+measure a significant offset.
+
+7. Operation
+
+7.1. What clocks does chronyd use?
+
+There are several different clocks used by chronyd:
+
+ o System clock: software clock maintained by the kernel. It is the main clock
+ used by applications running on the computer. It is synchronised by chronyd
+ to its NTP clock, unless started with the -x option.
+
+ o NTP clock: software clock (virtual) based on the system clock and internal
+ to chronyd. It keeps the best estimate of the true time according to the
+ configured time sources, which is served to NTP clients unless time
+ smoothing is enabled by the smoothtime directive. The System time value in
+ the tracking report is the current offset between the system and NTP clock.
+
+ o Real-time clock (RTC): hardware clock keeping time even when the computer
+ is turned off. It is used by the kernel to initialise the system clock on
+ boot and also by chronyd to compensate for its measured drift if configured
+ with the rtcfile directive and started with the -s option. The clock can be
+ kept accurate only by stepping enabled by the rtcsync or rtcautotrim
+ directive.
+
+ o Reference clock: hardware clock used as a time source. It is specified by
+ the refclock directive.
+
+ o NIC clock (also known as PTP hardware clock): hardware clock timestamping
+ packets received and transmitted by a network device specified by the
+ hwtimestamp directive. The clock is expected to be running free. It is not
+ synchronised by chronyd. Its offset is tracked relative to the NTP clock in
+ order to convert the hardware timestamps.
+
+8. Operating systems
+
+8.1. Does chrony support Windows?
+
+No. The chronyc program (the command-line client used for configuring chronyd
+while it is running) has been successfully built and run under Cygwin in the
+past. chronyd is not portable, because part of it is very system-dependent. It
+needs adapting to work with Windows' equivalent of the adjtimex() call, and it
+needs to be made to work as a service.
+
+8.2. Are there any plans to support Windows?
+
+We have no plans to do this. Anyone is welcome to pick this work up and
+contribute it back to the project.
+
+Last updated 2023-12-05 14:22:10 +0100
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..9ca6e22
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,165 @@
+Installation
+
+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.
+
+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:
+
+ o pkg-config: detection of development libraries
+
+ o Nettle, GnuTLS, NSS, or LibTomCrypt: secure hash functions (SECHASH)
+
+ o libcap: dropping root privileges on Linux (DROPROOT)
+
+ o libseccomp: system call filter on Linux (SCFILTER)
+
+ o GnuTLS and Nettle: Network Time Security (NTS)
+
+ o Editline: line editing in chronyc (READLINE)
+
+ o timepps.h header: PPS reference clock
+
+ o Asciidoctor: documentation in HTML format
+
+ o Bash: test suite
+
+The following programs are needed when building chrony from the git repository
+instead of a released tar file:
+
+ o Asciidoctor: manual pages
+
+ o Bison: parser for chronyc settime command
+
+After unpacking the source code, change directory into it, and type
+
+./configure
+
+This is a shell script that automatically determines the system type. There is
+an optional parameter --prefix, which indicates the directory tree where the
+software should be installed. For example,
+
+./configure --prefix=/opt/free
+
+will install the chronyd daemon into /opt/free/sbin and the chronyc control
+program into /opt/free/bin. The default value for the prefix is /usr/local.
+
+The configure script assumes you want to use gcc as your compiler. If you want
+to use a different compiler, you can configure this way:
+
+CC=cc ./configure --prefix=/opt/free
+
+for Bourne-family shells, or
+
+setenv CC cc
+setenv CFLAGS -O
+./configure --prefix=/opt/free
+
+for C-family shells.
+
+If the software cannot (yet) be built on your system, an error message will be
+shown. Otherwise, Makefile will be generated.
+
+On Linux, if development files for the libcap library are available, chronyd
+will be built with support for dropping root privileges. On other systems no
+extra library is needed. The default user which chronyd should run as can be
+specified with the --with-user option of the configure script.
+
+If development files for the POSIX threads library are available, chronyd will
+be built with support for asynchronous resolving of hostnames specified in the
+server, peer, and pool directives. This allows chronyd operating as a server to
+respond to client requests when resolving a hostname. If you don't want to
+enable the support, specify the --disable-asyncdns flag to configure.
+
+If development files for the Nettle, NSS, or libtomcrypt library are available,
+chronyd will be built with support for other cryptographic hash functions than
+MD5, which can be used for NTP authentication with a symmetric key. If you
+don't want to enable the support, specify the --disable-sechash flag to
+configure.
+
+If development files for the editline library are available, chronyc will be
+built with line editing support. If you don't want this, specify the
+--disable-readline flag to configure.
+
+If a timepps.h header is available (e.g. from the LinuxPPS project), chronyd
+will be built with PPS API reference clock driver. If the header is installed
+in a location that isn't normally searched by the compiler, you can add it to
+the searched locations by setting the CPPFLAGS variable to -I/path/to/timepps.
+
+The --help option can be specified to configure to print all options supported
+by the script.
+
+Now type
+
+make
+
+to build the programs.
+
+If you want to build the manual in HTML, type
+
+make docs
+
+Once the programs have been successfully compiled, they need to be installed in
+their target locations. This step normally needs to be performed by the
+superuser, and requires the following command to be entered.
+
+make install
+
+This will install the binaries and man pages.
+
+To install the HTML version of the manual, enter the command
+
+make install-docs
+
+Now that the software is successfully installed, the next step is to set up a
+configuration file. The default location of the file is /etc/chrony.conf.
+Several examples of configuration with comments are included in the examples
+directory. Suppose you want to use public NTP servers from the pool.ntp.org
+project as your time reference. A minimal useful configuration file could be
+
+pool pool.ntp.org iburst
+makestep 1.0 3
+rtcsync
+
+Then, chronyd can be run. For security reasons, it's recommended to create an
+unprivileged user for chronyd and specify it with the -u command-line option or
+the user directive in the configuration file, or set the default user with the
+--with-user configure option before building.
+
+Support for system call filtering
+
+chronyd can be built with support for the Linux secure computing (seccomp)
+facility. This requires development files for the libseccomp library and the
+--enable-scfilter option specified to configure. The -F option of chronyd will
+enable a system call filter, which should significantly reduce the kernel
+attack surface and possibly prevent kernel exploits from chronyd if it is
+compromised.
+
+Extra options for package builders
+
+The configure and make procedures have some extra options that may be useful if
+you are building a distribution package for chrony.
+
+The --mandir=DIR option to configure specifies an installation directory for
+the man pages. This overrides the man subdirectory of the argument to the
+--prefix option.
+
+./configure --prefix=/usr --mandir=/usr/share/man
+
+to set both options together.
+
+The final option is the DESTDIR option to the make command. For example, you
+could use the commands
+
+./configure --prefix=/usr --mandir=/usr/share/man
+make all docs
+make install DESTDIR=./tmp
+cd tmp
+tar cvf - . | gzip -9 > chrony.tar.gz
+
+to build a package. When untarred within the root directory, this will install
+the files to the intended final locations.
+
+Last updated 2023-12-05 14:22:10 +0100
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..101e0c6
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,143 @@
+##################################################
+#
+# chronyd/chronyc - Programs for keeping computer clocks accurate.
+#
+# Copyright (C) Richard P. Curnow 1997-2003
+#
+# 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.
+#
+# =======================================================================
+#
+# Makefile template
+
+SYSCONFDIR = @SYSCONFDIR@
+BINDIR = @BINDIR@
+SBINDIR = @SBINDIR@
+LOCALSTATEDIR = @LOCALSTATEDIR@
+CHRONYVARDIR = @CHRONYVARDIR@
+DESTDIR =
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+
+GETDATE_CFLAGS = @GETDATE_CFLAGS@
+
+EXTRA_OBJS = @EXTRA_OBJS@
+
+OBJS = array.o cmdparse.o conf.o local.o logging.o main.o memory.o quantiles.o \
+ 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_CLI_OBJS = @EXTRA_CLI_OBJS@
+
+CLI_OBJS = array.o client.o cmdparse.o getdate.o memory.o nameserv.o \
+ pktlength.o socket.o util.o $(EXTRA_CLI_OBJS)
+
+ALL_OBJS = $(OBJS) $(CLI_OBJS)
+
+LIBS = @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)
+ $(CC) $(CFLAGS) -o chronyd $(OBJS) $(LDFLAGS) $(LIBS) $(EXTRA_LIBS)
+
+chronyc : $(CLI_OBJS)
+ $(CC) $(CFLAGS) -o chronyc $(CLI_OBJS) $(LDFLAGS) $(LIBS) $(EXTRA_CLI_LIBS)
+
+getdate.o: CFLAGS += $(GETDATE_CFLAGS)
+
+distclean : clean
+ $(MAKE) -C doc distclean
+ $(MAKE) -C test/unit distclean
+ -rm -f .DS_Store
+ -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
+ -rm -rf *.dSYM
+
+getdate.c : getdate.y
+ bison -o getdate.c getdate.y
+
+# This can be used to force regeneration of getdate.c
+getdate :
+ bison -o getdate.c getdate.y
+
+# For install, don't use the install command, because its switches
+# seem to vary between systems.
+
+install: chronyd chronyc
+ [ -d $(DESTDIR)$(SYSCONFDIR) ] || mkdir -p $(DESTDIR)$(SYSCONFDIR)
+ [ -d $(DESTDIR)$(SBINDIR) ] || mkdir -p $(DESTDIR)$(SBINDIR)
+ [ -d $(DESTDIR)$(BINDIR) ] || mkdir -p $(DESTDIR)$(BINDIR)
+ [ -d $(DESTDIR)$(CHRONYVARDIR) ] || mkdir -p $(DESTDIR)$(CHRONYVARDIR)
+ if [ -f $(DESTDIR)$(SBINDIR)/chronyd ]; then rm -f $(DESTDIR)$(SBINDIR)/chronyd ; fi
+ if [ -f $(DESTDIR)$(BINDIR)/chronyc ]; then rm -f $(DESTDIR)$(BINDIR)/chronyc ; fi
+ cp chronyd $(DESTDIR)$(SBINDIR)/chronyd
+ chmod 755 $(DESTDIR)$(SBINDIR)/chronyd
+ cp chronyc $(DESTDIR)$(BINDIR)/chronyc
+ chmod 755 $(DESTDIR)$(BINDIR)/chronyc
+ $(MAKE) -C doc install
+
+docs :
+ $(MAKE) -C doc docs
+
+install-docs :
+ $(MAKE) -C doc install-docs
+
+%.o : %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
+
+%.s : %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -S $<
+
+quickcheck : chronyd chronyc
+ $(MAKE) -C test/unit check
+ cd test/simulation && ./run
+ cd test/system && ./run
+
+check : chronyd chronyc
+ $(MAKE) -C test/unit check
+ cd test/simulation && ./run -i 20 -m 2
+ cd test/system && ./run
+
+print-chronyd-objects :
+ @echo $(OBJS)
+
+Makefile : Makefile.in configure
+ @echo
+ @echo Makefile needs to be regenerated, run ./configure
+ @echo
+ @exit 1
+
+.deps:
+ @mkdir .deps
+
+.deps/%.d: %.c | .deps
+ @$(CC) -MM $(CPPFLAGS) -MT '$(<:%.c=%.o) $@' $< -o $@
+
+ifndef NODEPS
+-include $(ALL_OBJS:%.o=.deps/%.d)
+endif
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..93b21ed
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,1017 @@
+New in version 4.5
+==================
+
+Enhancements
+------------
+* Add support for AES-GCM-SIV in GnuTLS
+* Add support for corrections from PTP transparent clocks
+* Add support for systemd socket activation
+
+Bug fixes
+---------
+* Fix presend in interleaved mode
+* Fix reloading of modified sources from sourcedir
+
+New in version 4.4
+==================
+
+Enhancements
+------------
+* Add support for AES-GCM-SIV with Nettle >= 3.9 to shorten NTS
+ cookies to avoid some length-specific blocking of NTP on Internet
+* Add support for multiple refclocks using extpps option on one PHC
+* Add maxpoll option to hwtimestamp directive to improve PHC tracking
+ with low packet rates
+* Add hwtstimeout directive to configure timeout for late timestamps
+* Handle late hardware transmit timestamps of NTP requests on all sockets
+* Handle mismatched 32/64-bit time_t in SOCK refclock samples
+* Improve source replacement
+* Log important changes made by command requests (chronyc)
+* Refresh address of NTP sources periodically
+* Request nanosecond kernel RX timestamping on FreeBSD
+* Set DSCP for IPv6 packets
+* Shorten NTS-KE retry interval when network is down
+* Update seccomp filter for musl
+* Warn if loading keys from file with unexpected permissions
+* Warn if source selection fails or falseticker is detected
+* Add selectopts command to modify source-specific selection options
+* Add timestamp sources to serverstats report and make its fields 64-bit
+* Add -e option to chronyc to indicate end of response
+
+New in version 4.3
+==================
+
+Enhancements
+------------
+* Add local option to refclock directive to stabilise system clock
+ with more stable free-running clock (e.g. TCXO, OCXO)
+* Add maxdelayquant option to server/pool/peer directive to replace
+ maxdelaydevratio filter with long-term quantile-based filtering
+* Add selection option to log directive
+* Allow external PPS in PHC refclock without configurable pin
+* Don't accept first interleaved response to minimise error in delay
+* Don't use arc4random on Linux to avoid server performance loss
+* Improve filter option to better handle missing NTP samples
+* Improve stability with hardware timestamping and PHC refclock
+* Update seccomp filter
+
+Bug fixes
+---------
+* Fix waitsync command to reconnect when not getting response
+
+New in version 4.2
+==================
+
+Enhancements
+------------
+* Add support for NTPv4 extension field improving synchronisation
+ stability and resolution of root delay and dispersion (experimental)
+* Add support for NTP over PTP (experimental)
+* Add support for AES-CMAC and hash functions in GnuTLS
+* Improve server interleaved mode to be more reliable and support
+ multiple clients behind NAT
+* Update seccomp filter
+* Add statistics about interleaved mode to serverstats report
+
+Bug fixes
+---------
+* Fix RTC support with 64-bit time_t on 32-bit Linux
+* Fix seccomp filter to work correctly with bind*device directives
+* Suppress kernel adjustments of system clock (dosynctodr) on illumos
+
+Other changes
+-------------
+* Switch Solaris support to illumos
+
+New in version 4.1
+==================
+
+Enhancements
+------------
+* Add support for NTS servers specified by IP address (matching
+ Subject Alternative Name in server certificate)
+* Add source-specific configuration of trusted certificates
+* Allow multiple files and directories with trusted certificates
+* Allow multiple pairs of server keys and certificates
+* Add copy option to server/pool directive
+* Increase PPS lock limit to 40% of pulse interval
+* Perform source selection immediately after loading dump files
+* Reload dump files for addresses negotiated by NTS-KE server
+* Update seccomp filter and add less restrictive level
+* Restart ongoing name resolution on online command
+
+Bug fixes
+---------
+* Fix responding to IPv4 command requests on FreeBSD
+* Fix dump files to not include uncorrected offset
+* Fix initstepslew to accept time from own NTP clients
+* Reset NTP address and port when no longer negotiated by NTS-KE server
+
+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 authselectmode directive to control selection of unauthenticated sources
+* Add binddevice, bindacqdevice, bindcmddevice directives
+* Add confdir directive to better support fragmented configuration
+* Add sourcedir directive and "reload sources" command to support dynamic
+ NTP sources specified in files
+* Add clockprecision directive
+* Add dscp directive to set Differentiated Services Code Point (DSCP)
+* Add -L option to limit log messages by severity
+* Add -p option to print whole configuration with included files
+* Add -U option to allow start under non-root user
+* Allow maxsamples to be set to 1 for faster update with -q/-Q option
+* Avoid replacing NTP sources with sources that have unreachable address
+* Improve pools to repeat name resolution to get "maxsources" sources
+* Improve source selection with trusted sources
+* Improve NTP loop test to prevent synchronisation to itself
+* Repeat iburst when NTP source is switched from offline state to online
+* Update clock synchronisation status and leap status more frequently
+* Update seccomp filter
+* Add "add pool" command
+* Add "reset sources" command to drop all measurements
+* Add authdata command to print details about NTP authentication
+* Add selectdata command to print details about source selection
+* Add -N option and sourcename command to print original names of sources
+* Add -a option to some commands to print also unresolved sources
+* Add -k, -p, -r options to clients command to select, limit, reset data
+
+Bug fixes
+---------
+* Don't set interface for NTP responses to allow asymmetric routing
+* 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)
+* Drop support for long (non-standard) MACs in NTPv4 packets (chrony 2.x
+ clients using non-MD5/SHA1 keys need to use option "version 3")
+* Drop support for line editing with GNU Readline
+
+New in version 3.5.1
+====================
+
+Security fixes
+--------------
+* Create new file when writing pidfile (CVE-2020-14367)
+
+New in version 3.5
+==================
+
+Enhancements
+------------
+* Add support for more accurate reading of PHC on Linux 5.0
+* Add support for hardware timestamping on interfaces with read-only
+ timestamping configuration
+* Add support for memory locking and real-time priority on FreeBSD,
+ NetBSD, Solaris
+* Update seccomp filter to work on more architectures
+* Validate refclock driver options
+
+Bug fixes
+---------
+* Fix bindaddress directive on FreeBSD
+* Fix transposition of hardware RX timestamp on Linux 4.13 and later
+* Fix building on non-glibc systems
+
+New in version 3.4
+==================
+
+Enhancements
+------------
+* Add filter option to server/pool/peer directive
+* Add minsamples and maxsamples options to hwtimestamp directive
+* Add support for faster frequency adjustments in Linux 4.19
+* Change default pidfile to /var/run/chrony/chronyd.pid to allow
+ chronyd without root privileges to remove it on exit
+* Disable sub-second polling intervals for distant NTP sources
+* Extend range of supported sub-second polling intervals
+* Get/set IPv4 destination/source address of NTP packets on FreeBSD
+* Make burst options and command useful with short polling intervals
+* Modify auto_offline option to activate when sending request failed
+* Respond from interface that received NTP request if possible
+* Add onoffline command to switch between online and offline state
+ according to current system network configuration
+* Improve example NetworkManager dispatcher script
+
+Bug fixes
+---------
+* Avoid waiting in Linux getrandom system call
+* Fix PPS support on FreeBSD and NetBSD
+
+New in version 3.3
+==================
+
+Enhancements
+------------
+* Add burst option to server/pool directive
+* Add stratum and tai options to refclock directive
+* Add support for Nettle crypto library
+* Add workaround for missing kernel receive timestamps on Linux
+* Wait for late hardware transmit timestamps
+* Improve source selection with unreachable sources
+* Improve protection against replay attacks on symmetric mode
+* Allow PHC refclock to use socket in /var/run/chrony
+* Add shutdown command to stop chronyd
+* Simplify format of response to manual list command
+* Improve handling of unknown responses in chronyc
+
+Bug fixes
+---------
+* Respond to NTPv1 client requests with zero mode
+* Fix -x option to not require CAP_SYS_TIME under non-root user
+* Fix acquisitionport directive to work with privilege separation
+* Fix handling of socket errors on Linux to avoid high CPU usage
+* Fix chronyc to not get stuck in infinite loop after clock step
+
+New in version 3.2
+==================
+
+Enhancements
+------------
+* Improve stability with NTP sources and reference clocks
+* Improve stability with hardware timestamping
+* Improve support for NTP interleaved modes
+* Control frequency of system clock on macOS 10.13 and later
+* Set TAI-UTC offset of system clock with leapsectz directive
+* Minimise data in client requests to improve privacy
+* Allow transmit-only hardware timestamping
+* Add support for new timestamping options introduced in Linux 4.13
+* Add root delay, root dispersion and maximum error to tracking log
+* Add mindelay and asymmetry options to server/peer/pool directive
+* Add extpps option to PHC refclock to timestamp external PPS signal
+* Add pps option to refclock directive to treat any refclock as PPS
+* Add width option to refclock directive to filter wrong pulse edges
+* Add rxfilter option to hwtimestamp directive
+* Add -x option to disable control of system clock
+* Add -l option to log to specified file instead of syslog
+* Allow multiple command-line options to be specified together
+* Allow starting without root privileges with -Q option
+* Update seccomp filter for new glibc versions
+* Dump history on exit by default with dumpdir directive
+* Use hardening compiler options by default
+
+Bug fixes
+---------
+* Don't drop PHC samples with low-resolution system clock
+* Ignore outliers in PHC tracking, RTC tracking, manual input
+* Increase polling interval when peer is not responding
+* Exit with error message when include directive fails
+* Don't allow slash after hostname in allow/deny directive/command
+* Try to connect to all addresses in chronyc before giving up
+
+New in version 3.1
+==================
+
+Enhancements
+------------
+* Add support for precise cross timestamping of PHC on Linux
+* Add minpoll, precision, nocrossts options to hwtimestamp directive
+* Add rawmeasurements option to log directive and modify measurements
+ option to log only valid measurements from synchronised sources
+* Allow sub-second polling interval with NTP sources
+
+Bug fixes
+---------
+* Fix time smoothing in interleaved mode
+
+New in version 3.0
+==================
+
+Enhancements
+------------
+* Add support for software and hardware timestamping on Linux
+* Add support for client/server and symmetric interleaved modes
+* Add support for MS-SNTP authentication in Samba
+* Add support for truncated MACs in NTPv4 packets
+* Estimate and correct for asymmetric network jitter
+* Increase default minsamples and polltarget to improve stability
+ with very low jitter
+* Add maxjitter directive to limit source selection by jitter
+* Add offset option to server/pool/peer directive
+* Add maxlockage option to refclock directive
+* Add -t option to chronyd to exit after specified time
+* Add partial protection against replay attacks on symmetric mode
+* Don't reset polling interval when switching sources to online state
+* Allow rate limiting with very short intervals
+* Improve maximum server throughput on Linux and NetBSD
+* Remove dump files after start
+* Add tab-completion to chronyc with libedit/readline
+* Add ntpdata command to print details about NTP measurements
+* Allow all source options to be set in add server/peer command
+* Indicate truncated addresses/hostnames in chronyc output
+* Print reference IDs as hexadecimal numbers to avoid confusion with
+ IPv4 addresses
+
+Bug fixes
+---------
+* Fix crash with disabled asynchronous name resolving
+
+New in version 2.4.1
+====================
+
+Bug fixes
+---------
+* Fix processing of kernel timestamps on non-Linux systems
+* Fix crash with smoothtime directive
+* Fix validation of refclock sample times
+* Fix parsing of refclock directive
+
+New in version 2.4
+==================
+
+Enhancements
+------------
+* Add orphan option to local directive for orphan mode compatible with ntpd
+* Add distance option to local directive to set activation threshold
+ (1 second by default)
+* Add maxdrift directive to set maximum allowed drift of system clock
+* Try to replace NTP sources exceeding maximum distance
+* Randomise source replacement to avoid getting stuck with bad sources
+* Randomise selection of sources from pools on start
+* Ignore reference timestamp as ntpd doesn't always set it correctly
+* Modify tracking report to use same values as seen by NTP clients
+* Add -c option to chronyc to write reports in CSV format
+* Provide detailed manual pages
+
+Bug fixes
+---------
+* Fix SOCK refclock to work correctly when not specified as last refclock
+* Fix initstepslew and -q/-Q options to accept time from own NTP clients
+* Fix authentication with keys using 512-bit hash functions
+* Fix crash on exit when multiple signals are received
+* Fix conversion of very small floating-point numbers in command packets
+
+Removed features
+----------------
+* Drop documentation in Texinfo format
+
+New in version 2.3
+==================
+
+Enhancements
+------------
+* Add support for NTP and command response rate limiting
+* Add support for dropping root privileges on Mac OS X, FreeBSD, Solaris
+* Add require and trust options for source selection
+* Enable logchange by default (1 second threshold)
+* Set RTC on Mac OS X with rtcsync directive
+* Allow binding to NTP port after dropping root privileges on NetBSD
+* Drop CAP_NET_BIND_SERVICE capability on Linux when NTP port is disabled
+* Resolve names in separate process when seccomp filter is enabled
+* Replace old records in client log when memory limit is reached
+* Don't reveal local time and synchronisation state in client packets
+* Don't keep client sockets open for longer than necessary
+* Ignore poll in KoD RATE packets as ntpd doesn't always set it correctly
+* Warn when using keys shorter than 80 bits
+* Add keygen command to generate random keys easily
+* Add serverstats command to report NTP and command packet statistics
+
+Bug fixes
+---------
+* Fix clock correction after making step on Mac OS X
+* Fix building on Solaris
+
+New in version 2.2.1
+====================
+
+Security fixes
+--------------
+* Restrict authentication of NTP server/peer to specified key (CVE-2016-1567)
+
+New in version 2.2
+==================
+
+Enhancements
+------------
+* Add support for configuration and monitoring over Unix domain socket
+ (accessible by root or chrony user when root privileges are dropped)
+* Add support for system call filtering with seccomp on Linux (experimental)
+* Add support for dropping root privileges on NetBSD
+* Control frequency of system clock on FreeBSD, NetBSD, Solaris
+* Add system leap second handling mode on FreeBSD, NetBSD, Solaris
+* Add dynamic drift removal on Mac OS X
+* Add support for setting real-time priority on Mac OS X
+* Add maxdistance directive to limit source selection by root distance
+ (3 seconds by default)
+* Add refresh command to get new addresses of NTP sources
+* Allow wildcard patterns in include directive
+* Restore time from driftfile with -s option if later than RTC time
+* Add configure option to set default hwclockfile
+* Add -d option to chronyc to enable debug messages
+* Allow multiple addresses to be specified for chronyc with -h option
+ and reconnect when no valid reply is received
+* Make check interval in waitsync command configurable
+
+Bug fixes
+---------
+* Fix building on NetBSD, Solaris
+* Restore time from driftfile with -s option if reading RTC failed
+
+Removed features
+----------------
+* Drop support for authentication with command key (run-time configuration
+ is now allowed only for local users that can access the Unix domain socket)
+
+New in version 2.1.1
+====================
+
+Bug fixes
+---------
+* Fix clock stepping by integer number of seconds on Linux
+
+New in version 2.1
+==================
+
+Enhancements
+------------
+* Add support for Mac OS X
+* Try to replace unreachable and falseticker servers/peers specified
+ by name like pool sources
+* Add leaponly option to smoothtime directive to allow synchronised
+ leap smear between multiple servers
+* Use specific reference ID when smoothing served time
+* Add smoothing command to report time smoothing status
+* Add smoothtime command to activate or reset time smoothing
+
+Bug fixes
+---------
+* Fix crash in source selection with preferred sources
+* Fix resetting of time smoothing
+* Include packet precision in peer dispersion
+* Fix crash in chronyc on invalid command syntax
+
+New in version 2.0
+==================
+
+Enhancements
+------------
+* Update to NTP version 4 (RFC 5905)
+* Add pool directive to specify pool of NTP servers
+* Add leapsecmode directive to select how to correct clock for leap second
+* Add smoothtime directive to smooth served time and enable leap smear
+* Add minsources directive to set required number of selectable sources
+* Add minsamples and maxsamples options for all sources
+* Add tempcomp configuration with list of points
+* Allow unlimited number of NTP sources, refclocks and keys
+* Allow unreachable sources to remain selected
+* Improve source selection
+* Handle offline sources as unreachable
+* Open NTP server port only when necessary (client access is allowed by
+ allow directive/command or peer/broadcast is configured)
+* Change default bindcmdaddress to loopback address
+* Change default maxdelay to 3 seconds
+* Change default stratumweight to 0.001
+* Update adjtimex synchronisation status
+* Use system headers for adjtimex
+* Check for memory allocation errors
+* Reduce memory usage
+* Add configure options to compile without NTP, cmdmon, refclock support
+* Extend makestep command to set automatic clock stepping
+
+Bug fixes
+---------
+* Add sanity checks for time and frequency offset
+* Don't report synchronised status during leap second
+* Don't combine reference clocks with close NTP sources
+* Fix accepting requests from configured sources
+* Fix initial fallback drift setting
+
+New in version 1.31.1
+=====================
+
+Security fixes
+--------------
+* Protect authenticated symmetric NTP associations against DoS attacks
+ (CVE-2015-1853)
+* Fix access configuration with subnet size indivisible by 4 (CVE-2015-1821)
+* Fix initialization of reply slots for authenticated commands (CVE-2015-1822)
+
+New in version 1.31
+===================
+
+Enhancements
+------------
+* Support operation in other NTP eras (next era begins in 2036),
+ NTP time is mapped to [-50, +86] years around build date by default
+* Restore time from driftfile with -s when RTC is missing/unsupported
+* Close connected client sockets when not waiting for reply
+* Use one client socket with random port when acquisitionport is 0
+* Use NTP packets instead of UDP echo for presend
+* Don't adjust polling interval when sending fails
+* Allow binding to addresses that don't exist yet
+* Ignore measurements around leap second
+* Improve detection of unexpected time jumps
+* Include example of logrotate configuration, systemd services and
+ NetworkManager dispatcher script
+
+Bug fixes
+---------
+* Reconnect client sockets for each request to follow changes
+ in network configuration automatically
+* Restart timer when polling interval is changed on reset
+
+New in version 1.30
+===================
+
+Enhancements
+------------
+* Add asynchronous name resolving with POSIX threads
+* Add PTP hardware clock (PHC) refclock driver
+* Add new generic clock driver to slew by adjusting frequency only
+ (without kernel PLL or adjtime) and use it on Linux
+* Add rtcautotrim directive to trim RTC automatically
+* Add hwclockfile directive to share RTC LOCAL/UTC setting with hwclock
+* Add maxslewrate directive to set maximum allowed slew rate
+* Add maxdispersion option for refclocks
+* Add -q/-Q options to set clock/print offset once and exit
+* Allow directives to be specified on chronyd command line
+* Replace frequency scaling in Linux driver with retaining of tick
+* Try to detect unexpected forward time jumps and reset state
+* Exit with non-zero code when maxchange limit is reached
+* Improve makestep to not start and stop slew unnecessarily
+* Change default corrtimeratio to 3.0 to improve frequency accuracy
+* Announce leap second only on last day of June and December
+* Use separate connected client sockets for each NTP server
+* Remove separate NTP implementation used for initstepslew
+* Limit maximum minpoll set by KoD RATE to default maxpoll
+* Don't send NTP requests with unknown key
+* Print warning when source is added with unknown key
+* Take leap second in PPS refclock from locked source
+* Make reading of RTC for initial trim more reliable
+* Don't create cmdmon sockets when cmdport is 0
+* Add configure option to set default user to drop root privileges
+* Add configure option to compile with debug messages
+* Print debug messages when -d is used more than once
+* Change format of messages written to terminal with -d
+* Write fatal messages also to stderr with -n
+* Use IP_RECVERR socket option in chronyc to not wait unnecessarily
+* Shorten default chronyc timeout for localhost
+* Change default hostname in chronyc from localhost to 127.0.0.1
+* Print error message on invalid syntax with all chronyc commands
+* Include simulation test suite using clknetsim
+
+Bug fixes
+---------
+* Fix crash when selecting with multiple preferred sources
+* Fix frequency calculation with large frequency offsets
+* Fix code writing drift and RTC files to compile correctly
+* Fix -4/-6 options in chronyc to not reset hostname set by -h
+* Fix refclock sample validation with sub-second polling interval
+* Set stratum correctly with non-PPS SOCK refclock and local stratum
+* Modify dispersion accounting in refclocks to prevent PPS getting
+ stuck with large dispersion and not accepting new samples
+
+New in version 1.29.1
+=====================
+
+Security fixes
+--------------
+* Modify chronyc protocol to prevent amplification attacks (CVE-2014-0021)
+ (incompatible with previous protocol version, chronyc supports both)
+
+New in version 1.29
+===================
+
+Security fixes
+--------------
+* Fix crash when processing crafted commands (CVE-2012-4502)
+ (possible with IP addresses allowed by cmdallow and localhost)
+* Don't send uninitialized data in SUBNETS_ACCESSED and CLIENT_ACCESSES
+ replies (CVE-2012-4503) (not used by chronyc)
+
+Other changes
+-------------
+* Drop support for SUBNETS_ACCESSED and CLIENT_ACCESSES commands
+
+New in version 1.28
+===================
+
+* Combine sources to improve accuracy
+* Make config and command parser strict
+* Add -a option to chronyc to authenticate automatically
+* Add -R option to ignore initstepslew and makestep directives
+* Add generatecommandkey, minsamples, maxsamples and user directives
+* Improve compatibility with NTPv1 and NTPv2 clients
+* Create sockets only in selected family with -4/-6 option
+* Treat address bind errors as non-fatal
+* Extend tracking log
+* Accept float values as initstepslew threshold
+* Allow hostnames in offline, online and burst commands
+* Fix and improve peer polling
+* Fix crash in config parsing with too many servers
+* Fix crash with duplicated initstepslew address
+* Fix delta calculation with extreme frequency offsets
+* Set local stratum correctly
+* Remove unnecessary adjtimex calls
+* Set paths in documentation by configure
+* Update chrony.spec
+
+New in version 1.27
+===================
+
+* Support for stronger keys via NSS or libtomcrypt library
+* Support reading leap second data from tz database
+* Support for precise clock stepping on Linux
+* Support for nanoseconds in SHM refclock
+* Make offset corrections smoother on Linux
+* Make transmit timestamps random below clock precision
+* Add corrtimeratio and maxchange directives
+* Extend tracking, sources and activity reports
+* Wait in foreground process until daemon is fully initialized
+* Fix crash with slow name resolving
+* Fix iburst with jittery sources
+* Fix offset stored in rtc data right after trimrtc
+* Fix crash and hang with RTC or manual samples
+* Don't use readonly adjtime on Linux kernels before 2.6.28
+* Changed chronyc protocol, incompatible with older versions
+
+New in version 1.26
+===================
+
+* Add compatibility with Linux 3.0 and later
+* Use proper source address in NTP replies on multihomed IPv6 hosts
+* Accept NTP packets with versions 4, 3 and 2
+* Cope with unexpected backward time jumps
+* Don't reset kernel frequency on start without drift file
+* Retry on permanent DNS error by default
+* Add waitsync command
+
+New in version 1.25
+===================
+
+* Improve accuracy with NTP sources
+* Improve accuracy with reference clocks
+* Improve polling interval adjustment
+* Improve stability with temporary asymmetric delays
+* Improve source selection
+* Improve initial synchronisation
+* Add delayed server name resolving
+* Add temperature compensation
+* Add nanosecond slewing to Linux driver
+* Add fallback drifts
+* Add iburst, minstratum, maxdelaydevratio, polltarget,
+ prefer, noselect options
+* Add rtcsync directive to enable Linux 11-minute mode
+* Add reselectdist, stratumweight, logbanner, maxclockerror,
+ include directives
+* Add -n option to not detach daemon from terminal
+* Fix pidfile directive
+* Fix name resolving with disabled IPv6 support
+* Fix reloading sample histories with reference clocks
+* Fix crash with auto_offline option
+* Fix online command on auto_offline sources
+* Fix file descriptor leaks
+* Increase burst polling interval and stop on KoD RATE
+* Set maxupdateskew to 1000 ppm by default
+* Require password for clients command
+* Update drift file at most once per hour
+* Use system headers for Linux RTC support
+* Reduce default chronyc timeout and make it configurable
+* Avoid large values in chronyc sources and sourcestats output
+* Add reselect command to force reselecting best source
+* Add -m option to allow multiple commands on command line
+
+New in version 1.24
+===================
+
+Security fixes
+--------------
+* Don't reply to invalid cmdmon packets (CVE-2010-0292)
+* Limit client log memory size (CVE-2010-0293)
+* Limit rate of syslog messages (CVE-2010-0294)
+
+Bug fixes/Enhancements
+----------------------
+* Support for reference clocks (SHM, SOCK, PPS drivers)
+* IPv6 support
+* Linux capabilities support (to drop root privileges)
+* Memory locking support on Linux
+* Real-time scheduler support on Linux
+* Leap second support on Linux
+* Support for editline library
+* Support for new Linux readonly adjtime
+* NTP client support for KoD RATE
+* Read kernel timestamps for received NTP packets
+* Reply to NTP requests with correct address on multihomed hosts
+* Retry name resolving after temporary failure
+* Fix makestep command, make it available on all systems
+* Add makestep directive for automatic clock stepping
+* Don't require _bigadj kernel symbol on NetBSD
+* Avoid blocking read in Linux RTC driver
+* Support for Linux on S/390 and PowerPC
+* Fix various bugs on 64-bit systems
+* Fix valgrind errors and compiler warnings
+* Improve configure to support common options and variables
+* Improve status checking and printing in chronyc
+* Return non-zero exit code on errors in chronyc
+* Reduce request timeout in chronyc
+* Print estimated offset in sourcestats
+* Changed chronyc protocol, incompatible with older versions
+
+New in version 1.23
+===================
+
+* Support for MIPS, x86_64, sparc, alpha, arm, FreeBSD
+* Fix serious sign-extension error in handling IP addresses
+* RTC support can be excluded at compile time
+* Make sources gcc-4 compatible
+* Fix various compiler warnings
+* Handle fluctuations in peer distance better.
+* Fixed handling of stratum zero.
+* Fix various problems for 64-bit systems
+* Flush chronyc output streams after each command, to allow it to be driven
+ through pipes
+* Manpage improvements
+
+Version 1.22
+============
+
+This release number was claimed by a release that Mandriva made to patch
+important bugs in 1.21. The official numbering has jumped to 1.23 as a
+consequence.
+
+New in version 1.21
+===================
+
+* Don't include Linux kernel header files any longer : allows chrony to compile
+ on recent distros.
+* Stop trying to use RTC if continuous streams of error messages would occur
+ (Linux with HPET).
+
+New in version 1.20
+===================
+
+* Many small tidy-ups and security improvements
+* Improve documentation (RTC support in post 2.0 kernels)
+* Remove trailing \n from syslog messages
+* Syslog messages now include IP and port number when packet cannot be sent.
+* Added the "acquisitionport" directive. (Kalle Olavi Niemitalo)
+* Use uname(2) instead of /proc/version to get kernel version.
+* Merge support for Linux on Alpha
+* Merge support for 64bit architectures
+* Don't link -lm if it's not needed
+* Fix Solaris build (broken by 64bit change)
+* Add detection of Linux 2.5
+* Allow arbitrary value of HZ in Linux kernel
+* Fix for chrony.spec on SuSE (Paul Elliot)
+* Fix handling of initstepslew if no servers are listed (John Hasler)
+* Fix install rule in Makefile if chronyd is in use (Juliusz Chroboczek)
+* Replace sprintf by snprintf to remove risk of buffer overrun (John Hasler)
+* Add --help to configure script
+
+New in version 1.19
+===================
+
+* Auto-detect kernel's timer interrupt rate (so-called 'HZ') when chronyd
+ starts instead of relying on compiled-in value.
+* Fix 2 bugs in function that creates the directory for the log and dump files.
+* Amended webpage URL and contact details.
+* Generate more informative syslog messages before exiting on failed
+ assertions.
+* Fix bugs in clamping code for the tick value used when slewing a large
+ offset.
+* Don't chown files to root during install (should be pointless, and makes RPM
+ building awkward as ordinary user.)
+* Include chrony.spec file for building RPMs
+
+New in version 1.18
+===================
+* Amend homepage and mailing list information to chrony.sunsite.dk
+* Delete pidfile on exit from chronyd.
+* Improvements to readline interface to chronyc
+* Only generate syslog message when synchronisation is initially lost (instead
+ of on every failed synchronisation attempt)
+* Use double fork approach when initialising daemon.
+* More things in contrib directory.
+* New options to help package builders: --infodir/--mandir for configure, and
+ DESTDIR=xxx for make. (See section 2.2 of chrony.txt for details).
+* Changed the wording of the messages generated by mailonchange and logchange
+ directives.
+
+New in version 1.17
+===================
+* Port to NetBSD
+* Configuration supports Linux on PPC
+* Fix compilation warnings
+* Several documentation improvements
+* Bundled manpages (taken from the 'missing manpages project')
+* Cope with lack of bzero function for Solaris 2.3 systems
+* Store chronyd's pid in a file (default /var/run/chronyd.pid) and check if
+ chronyd may already be running when starting up. New pidfile directive in
+ configuration file.
+* Any size subnet is now allowed in allow and deny commands. (Example:
+ 6.7.8/20 or 6.7.8.x/20 (any x) mean a 20 bit subnet).
+* The environment variables CC and CFLAGS passed to configure can now be used
+ to select the compiler and optimisation/debug options to use
+* Write syslog messages when chronyd loses synchronisation.
+* Print GPL text when chronyc is run.
+* Add NTP broadcast server capability (new broadcast directive).
+* Add 'auto_offline' option to server/peer (conf file) or add server/peer (via
+ chronyc).
+* Add 'activity' command to chronyc, to report how many servers/peers are
+ currently online/offline.
+* Fix long-standing bug with how the system time quantum was calculated.
+* Include support for systems with HZ!=100 (HZ is the timer interrupt
+ frequency).
+* Include example chrony.conf and chrony.keys files (examples subdirectory).
+* Include support for readline in chronyc.
+
+New in version 1.16.1
+=====================
+* Fix compilation problem on Linux 2.4.13 (spinlock.h / spinlock_t)
+
+New in version 1.16
+===================
+* More informative captions for 'sources' and 'sourcestats' commands in chronyc
+ (use 'sources -v' and 'sourcestats -v' to get them).
+* Correct behaviour for Solaris versions>=2.6 (dosynctodr not required on these
+ versions.)
+* Remove some compiler warnings (Solaris)
+* If last line of keys file doesn't have end-of-line, don't truncate final
+ character of that key.
+* Change timestamp format used in logfiles to make it fully numeric (to aid
+ importing data into spreadsheets etc)
+* Minor documentation updates and improvements.
+
+New in version 1.15
+===================
+* Add contributed change to 'configure' to support Solaris 2.8 on x86
+* Workaround for assertion failure that arises if two received packets occur
+ close together. (Still need to find out why this happens at all.)
+* Hopefully fix problem where fast slewing was incompatible with machines
+ that have a large background drift rate (=> tick value went out of range
+ for adjtimex() on Linux.)
+* Fix rtc_linux.c compile problems with 2.4.x kernel include files.
+* Include support for RTC device not being at /dev/rtc (new rtcdevice directive
+ in configuration file).
+* Include support for restricting network interfaces for commands (new
+ bindcmdaddress directive in configuration file)
+* Fix potential linking fault in pktlength.c (use of CROAK macro replaced by
+ normal assert).
+* Add some material on bug reporting + contributing to the chrony.texi file
+* Made the chrony.texi file "Vim6-friendly" (removed xrefs on @node lines,
+ added folding markers to chapters + sections.)
+* Switched over to GPL for the licence
+
+New in version 1.14
+===================
+* Fix compilation for certain other Linux distributions (including Mandrake
+ 7.1)
+
+New in version 1.13
+===================
+* Fixed compilation problems on Redhat/SuSE installations with recent 2.2.x
+ kernels.
+* Minor tidy-ups and documentation enhancements.
+* Add support for Linux 2.4 kernels
+
+New in version 1.12
+===================
+
+* Trial fix for long-standing bug in Linux RTC estimator when system time is
+ slewed.
+* Fix bug in chronyc if -h is specified without a hostname
+* Fixes to logging various error conditions when operating in daemon mode.
+* More stuff under contrib/
+* Changes to README file (e.g. about the new chrony-users mailing list)
+
+New in version 1.11a
+====================
+
+* Minor changes to contact details
+* Minor changes to installation details (chrony subdirectory under doc/)
+
+New in version 1.11
+===================
+
+* Improve robustness of installation procedure
+* Tidy up documenation and contact details
+* Distribute manual as .txt rather than as .ps
+* Add -n option to chronyc to work with numeric IP addresses rather than
+ names.
+* Add material in contrib subdirectory
+* Improve robustness of handling drift file and RTC coefficients file
+* Improve robustness of regression algorithm
+
+New in version 1.1
+==================
+
+Bug fixes
+---------
+
+* Made linear regression more resistant to rounding errors (old one
+ occasionally generated negative variances which made everything go
+ haywire). Trap infinite or 'not-a-number' values being used to
+ alter system clock to increase robustness further.
+
+Other changes/Enhancements
+--------------------------
+
+* Support for Linux 2.1 and 2.2 kernels
+
+* New command 'makestep' in chronyc to immediately jump the system
+ time to match the NTP estimated time (Linux only) - a response to
+ systems booting an hour wrong after summertime/wintertime changes,
+ due to RTCs running on local time. Needs extending to Sun driver
+ files too.
+
+* New directives 'logchange' and 'mailonchange' to log to syslog or
+ email to a specific address respectively if chronyd detects a clock
+ offset exceeding a defined threshold.
+
+* Added capability to log all client/peer NTP accesses and command
+ accesses (can be turned off with conf file directive 'noclientlog').
+ Added 'clients' command to chronyc to display this data.
+
+* Improved manual mode to use robust regression rather than 2 point
+ fit.
+
+* Added 'manual list' and 'manual delete' commands to chronyc to
+ allow display of entered timestamps and discretionary deletion of
+ outliers.
+
+* If host goes unsynchronised the dummy IP address 0.0.0.0 is detected
+ to avoid attempting a reverse name lookup (to stop dial on demand IP
+ links from being started)
+
+* Changed chronyc/chronyd protocol so messages are now all variable
+ length. Saves on network bandwidth particularly for large replies
+ from chronyd to chronyc (to support the clients command).
+
+* Added bindaddress directive to configuration file, to give
+ additional control over limiting which hosts can access the local
+ server.
+
+* Groundwork done for a port to Windows NT to compile with Cygwin
+ toolkit. chronyc works (to monitor another host). sys_winnt.c
+ needs finishing to use NT clock control API. Program structure
+ needs adapting to use Windows NT service functions, so it can be
+ started at boot time. Hopefully a Windows NT / Cygwin guru with
+ some spare time can take this port over :-)
+
+New in version 1.02
+===================
+
+Bug fixes
+---------
+
+* Fix error messages in chronyc if daemon is not reachable.
+
+* Fix config file problem for 'allow all' and 'deny all' without a
+ trailing machine address.
+
+* Remove fatal failed assertion if command socket cannot be read from
+ in daemon.
+
+* Rewrote timezone handling for Linux real time clock, following
+ various reported problems related to daylight saving.
+
+Other changes/Enhancements
+--------------------------
+
+* Configure script recognizes BSD/386 and uses SunOS 4.1 driver for
+ it.
+
+* Log files now print date as day-month-year rather than as a day
+ number. Milliseconds removed from timestamps of logged data.
+ Banners included in file to give meanings of columns.
+
+* Only do 1 initial step (followed by a trimming slew) when
+ initialising from RTC on Linux (previously did 2 steps).
+
+New in version 1.01
+===================
+
+Bug fixes
+---------
+
+* Handle timezone of RTC correctly with respect to daylight saving
+ time
+
+* Syntax check the chronyc 'local' command properly
+
+* Fixed assertion failed fault in median finder (used by RTC
+ regression fitting)
+
+Other changes/Enhancements
+--------------------------
+
+* Log selection of new NTP reference source to syslog.
+
+* Don't zero-pad IP address fields
+
+* Add new command to chronyc to allow logfiles to be cycled.
+
+* Extend allow/deny directive syntax in configuration file to so
+ directive can apply to all hosts on the Internet.
+
+* Tidy up printout of timestamps to make it clear they are in UTC
+
+* Make 'configure' check the processor type as well as the operating
+ system.
diff --git a/README b/README
new file mode 100644
index 0000000..1eeac1b
--- /dev/null
+++ b/README
@@ -0,0 +1,141 @@
+This is the README for chrony.
+
+What is chrony?
+===============
+
+chrony is a versatile implementation of the Network Time Protocol (NTP).
+It can synchronise the system clock with NTP servers, reference clocks
+(e.g. GPS receiver), and manual input using wristwatch and keyboard.
+It can also operate as an NTPv4 (RFC 5905) server and peer to provide
+a time service to other computers in the network.
+
+It is designed to perform well in a wide range of conditions, including
+intermittent network connections, heavily congested networks, changing
+temperatures (ordinary computer clocks are sensitive to temperature),
+and systems that do not run continuosly, or run on a virtual machine.
+
+Typical accuracy between two machines synchronised over the Internet is
+within a few milliseconds; on a LAN, accuracy is typically in tens of
+microseconds. With hardware timestamping, or a hardware reference clock,
+sub-microsecond accuracy may be possible.
+
+Two programs are included in chrony, chronyd is a daemon that can be
+started at boot time and chronyc is a command-line interface program
+which can be used to monitor chronyd's performance and to change various
+operating parameters whilst it is running.
+
+What will chrony run on?
+========================
+
+The software is known to work on Linux, FreeBSD, NetBSD, macOS and
+illumos. Closely related systems may work too. Any other system will
+likely require a porting exercise.
+
+How do I set it up?
+===================
+
+The file INSTALL gives instructions. On supported systems the
+compilation process should be automatic. You will need a C compiler,
+e.g. gcc or clang.
+
+What documentation is there?
+============================
+
+The distribution includes manual pages and a document containing
+Frequently Asked Questions (FAQ).
+
+The documentation is also available on the chrony web pages, accessible
+through the URL
+
+ https://chrony-project.org/
+
+License
+=======
+
+chrony is distributed under the GNU General Public License version 2.
+
+Authors
+=======
+
+Richard P. Curnow <rc@rc0.org.uk>
+Miroslav Lichvar <mlichvar@redhat.com>
+
+Acknowledgements
+================
+
+In writing the chronyd program, extensive use has been made of the NTPv3 (RFC
+1305) and NTPv4 (RFC 5905) specification. The source code of the xntpd/ntpd
+implementation written by Dennis Fergusson, Lars Mathiesen, David Mills, and
+others has been used to check the details of the protocol.
+
+The following people have provided patches and other major contributions
+to chrony:
+
+Lonnie Abelbeck <lonnie@abelbeck.com>
+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>
+Jonathan Cameron <jic23@cam.ac.uk>
+Bryan Christianson <bryan@whatroute.net>
+Juliusz Chroboczek <jch@pps.jussieu.fr>
+Dan Drown <dan-ntp@drown.org>
+Kamil Dudka <kdudka@redhat.com>
+Christian Ehrhardt <christian.ehrhardt@canonical.com>
+Paul Elliott <pelliott@io.com>
+Robert Fairley <rfairley@redhat.com>
+Stefan R. Filipek <srfilipek@gmail.com>
+Mike Fleetwood <mike@rockover.demon.co.uk>
+Alexander Gretencord <arutha@gmx.de>
+Andrew Griffiths <agriffit@redhat.com>
+Walter Haidinger <walter.haidinger@gmx.at>
+Juergen Hannken-Illjes <hannken@eis.cs.tu-bs.de>
+John Hasler <john@dhh.gt.org>
+Tjalling Hattink <t.hattink@fugro.nl>
+Liam Hatton <me@liamhatton.com>
+Holger Hoffstätte <holger@applied-asynchrony.com>
+Jachym Holecek <jakym@volny.cz>
+HÃ¥kan Johansson <f96hajo@chalmers.se>
+Jim Knoble <jmknoble@pobox.com>
+Antti Jrvinen <costello@iki.fi>
+Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
+Eric Lammerts <eric@lammerts.org>
+Stefan Lucke <stefan@lucke.in-berlin.de>
+Victor Lum <viclum@vanu.com>
+Kevin Lyda <kevin@ie.suberic.net>
+Paul Menzel <paulepanter@users.sourceforge.net>
+Vladimir Michl <vladimir.michl@seznam.cz>
+Victor Moroz <vim@prv.adlum.ru>
+Kalle Olavi Niemitalo <tosi@stekt.oulu.fi>
+Frank Otto <sandwichmacher@web.de>
+Denny Page <dennypage@me.com>
+Rupesh Patel <rupatel@redhat.com>
+Chris Perl <cperl@janestreet.com>
+Gautier PHILIPPON <gautier.philippon@ensimag.grenoble-inp.fr>
+Andreas Piesk <apiesk@virbus.de>
+Mike Ryan <msr@hsilop.net>
+Baruch Siach <baruch@tkos.co.il>
+Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+Foster Snowhill <forst@forstwoof.ru>
+Andreas Steinmetz <ast@domdv.de>
+NAKAMURA Takumi <takumi@ps.sakura.ne.jp>
+Timo Teras <timo.teras@iki.fi>
+Bill Unruh <unruh@physics.ubc.ca>
+Luke Valenta <lvalenta@cloudflare.com>
+Stephen Wadeley <swadeley@redhat.com>
+Bernhard Weiss <lisnablagh@web.de>
+Wolfgang Weisselberg <weissel@netcologne.de>
+Bernhard M. Wiedemann <bwiedemann@suse.de>
+Joachim Wiedorn <ad_debian@joonet.de>
+Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+Ulrich Windl <ulrich.windl@rz.uni-regensburg.de>
+Michael Witten <mfwitten@gmail.com>
+Doug Woodward <dougw@whistler.com>
+Thomas Zajic <zlatko@zlatko.fdns.net>
+
+Many other people have contributed bug reports and suggestions. We are sorry
+we cannot identify all of you individually.
diff --git a/addressing.h b/addressing.h
new file mode 100644
index 0000000..3e311fa
--- /dev/null
+++ b/addressing.h
@@ -0,0 +1,67 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Types used for addressing sources etc
+ */
+
+#ifndef GOT_ADDRESSING_H
+#define GOT_ADDRESSING_H
+
+#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;
+} IPAddr;
+
+typedef struct {
+ IPAddr ip_addr;
+ uint16_t port;
+} IPSockAddr;
+
+typedef IPSockAddr NTP_Remote_Address;
+
+#define INVALID_IF_INDEX -1
+
+typedef struct {
+ IPAddr ip_addr;
+ int if_index;
+ int sock_fd;
+} NTP_Local_Address;
+
+#endif /* GOT_ADDRESSING_H */
+
diff --git a/addrfilt.c b/addrfilt.c
new file mode 100644
index 0000000..6208b46
--- /dev/null
+++ b/addrfilt.c
@@ -0,0 +1,405 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997,1998,1999,2000,2001,2002,2005
+ * Copyright (C) Miroslav Lichvar 2009, 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ This module provides a set of routines for checking IP addresses
+ against a set of rules and deciding whether they are allowed or
+ disallowed.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "addrfilt.h"
+#include "memory.h"
+
+/* Define the number of bits which are stripped off per level of
+ indirection in the tables */
+#define NBITS 4
+
+/* Define the table size */
+#define TABLE_SIZE (1UL<<NBITS)
+
+typedef enum {DENY, ALLOW, AS_PARENT} State;
+
+typedef struct _TableNode {
+ State state;
+ struct _TableNode *extended;
+} TableNode;
+
+struct ADF_AuthTableInst {
+ TableNode base4; /* IPv4 node */
+ TableNode base6; /* IPv6 node */
+};
+
+/* ================================================== */
+
+static void
+split_ip6(IPAddr *ip, uint32_t *dst)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ dst[i] = (uint32_t)ip->addr.in6[i * 4 + 0] << 24 |
+ ip->addr.in6[i * 4 + 1] << 16 |
+ ip->addr.in6[i * 4 + 2] << 8 |
+ ip->addr.in6[i * 4 + 3];
+}
+
+/* ================================================== */
+
+inline static uint32_t
+get_subnet(uint32_t *addr, unsigned int where)
+{
+ int off;
+
+ off = where / 32;
+ where %= 32;
+
+ return (addr[off] >> (32 - NBITS - where)) & ((1UL << NBITS) - 1);
+}
+
+/* ================================================== */
+
+ADF_AuthTable
+ADF_CreateTable(void)
+{
+ ADF_AuthTable result;
+ result = MallocNew(struct ADF_AuthTableInst);
+
+ /* Default is that nothing is allowed */
+ result->base4.state = DENY;
+ result->base4.extended = NULL;
+ result->base6.state = DENY;
+ result->base6.extended = NULL;
+
+ return result;
+}
+
+/* ================================================== */
+/* This function deletes all definitions of child nodes, in effect
+ pruning a whole subnet definition back to a single parent
+ record. */
+static void
+close_node(TableNode *node)
+{
+ int i;
+ TableNode *child_node;
+
+ if (node->extended != NULL) {
+ for (i=0; i<TABLE_SIZE; i++) {
+ child_node = &(node->extended[i]);
+ close_node(child_node);
+ }
+ Free(node->extended);
+ node->extended = NULL;
+ }
+}
+
+
+/* ================================================== */
+/* Allocate the extension field in a node, and set all the children's
+ states to default to that of the node being extended */
+
+static void
+open_node(TableNode *node)
+{
+ int i;
+ TableNode *child_node;
+
+ if (node->extended == NULL) {
+
+ node->extended = MallocArray(struct _TableNode, TABLE_SIZE);
+
+ for (i=0; i<TABLE_SIZE; i++) {
+ child_node = &(node->extended[i]);
+ child_node->state = AS_PARENT;
+ child_node->extended = NULL;
+ }
+ }
+}
+
+/* ================================================== */
+
+static ADF_Status
+set_subnet(TableNode *start_node,
+ uint32_t *ip,
+ int ip_len,
+ int subnet_bits,
+ State new_state,
+ int delete_children)
+{
+ int bits_to_go, bits_consumed;
+ uint32_t subnet;
+ TableNode *node;
+
+ bits_consumed = 0;
+ bits_to_go = subnet_bits;
+ node = start_node;
+
+ if ((subnet_bits < 0) ||
+ (subnet_bits > 32 * ip_len)) {
+
+ return ADF_BADSUBNET;
+
+ } else {
+
+ if ((bits_to_go & (NBITS-1)) == 0) {
+
+ while (bits_to_go > 0) {
+ subnet = get_subnet(ip, bits_consumed);
+ if (!(node->extended)) {
+ open_node(node);
+ }
+ node = &(node->extended[subnet]);
+ bits_to_go -= NBITS;
+ bits_consumed += NBITS;
+ }
+
+ if (delete_children) {
+ close_node(node);
+ }
+ node->state = new_state;
+
+ } else { /* Have to set multiple entries */
+ int N, i, j;
+ TableNode *this_node;
+
+ while (bits_to_go >= NBITS) {
+ subnet = get_subnet(ip, bits_consumed);
+ if (!(node->extended)) {
+ open_node(node);
+ }
+ node = &(node->extended[subnet]);
+ bits_to_go -= NBITS;
+ bits_consumed += NBITS;
+ }
+
+ /* How many subnet entries to set : 1->8, 2->4, 3->2 */
+ N = 1 << (NBITS-bits_to_go);
+
+ subnet = get_subnet(ip, bits_consumed) & ~(N - 1);
+ assert(subnet + N <= TABLE_SIZE);
+
+ if (!(node->extended)) {
+ open_node(node);
+ }
+
+ for (i=subnet, j=0; j<N; i++, j++) {
+ this_node = &(node->extended[i]);
+ if (delete_children) {
+ close_node(this_node);
+ }
+ this_node->state = new_state;
+ }
+ }
+
+ return ADF_SUCCESS;
+ }
+
+}
+
+/* ================================================== */
+
+static ADF_Status
+set_subnet_(ADF_AuthTable table,
+ IPAddr *ip_addr,
+ int subnet_bits,
+ State new_state,
+ int delete_children)
+{
+ uint32_t ip6[4];
+
+ switch (ip_addr->family) {
+ case IPADDR_INET4:
+ return set_subnet(&table->base4, &ip_addr->addr.in4, 1, subnet_bits, new_state, delete_children);
+ case IPADDR_INET6:
+ split_ip6(ip_addr, ip6);
+ return set_subnet(&table->base6, ip6, 4, subnet_bits, new_state, delete_children);
+ case IPADDR_UNSPEC:
+ /* Apply to both, subnet_bits has to be 0 */
+ if (subnet_bits != 0)
+ return ADF_BADSUBNET;
+ memset(ip6, 0, sizeof (ip6));
+ if (set_subnet(&table->base4, ip6, 1, 0, new_state, delete_children) == ADF_SUCCESS &&
+ set_subnet(&table->base6, ip6, 4, 0, new_state, delete_children) == ADF_SUCCESS)
+ return ADF_SUCCESS;
+ break;
+ default:
+ break;
+ }
+
+ return ADF_BADSUBNET;
+}
+
+ADF_Status
+ADF_Allow(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits)
+{
+ return set_subnet_(table, ip, subnet_bits, ALLOW, 0);
+}
+
+/* ================================================== */
+
+
+ADF_Status
+ADF_AllowAll(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits)
+{
+ return set_subnet_(table, ip, subnet_bits, ALLOW, 1);
+}
+
+/* ================================================== */
+
+ADF_Status
+ADF_Deny(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits)
+{
+ return set_subnet_(table, ip, subnet_bits, DENY, 0);
+}
+
+/* ================================================== */
+
+ADF_Status
+ADF_DenyAll(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits)
+{
+ return set_subnet_(table, ip, subnet_bits, DENY, 1);
+}
+
+/* ================================================== */
+
+void
+ADF_DestroyTable(ADF_AuthTable table)
+{
+ close_node(&table->base4);
+ close_node(&table->base6);
+ Free(table);
+}
+
+/* ================================================== */
+
+static int
+check_ip_in_node(TableNode *start_node, uint32_t *ip)
+{
+ uint32_t subnet;
+ int bits_consumed = 0;
+ int result = 0;
+ int finished = 0;
+ TableNode *node;
+ State state=DENY;
+
+ node = start_node;
+
+ do {
+ if (node->state != AS_PARENT) {
+ state = node->state;
+ }
+ if (node->extended) {
+ subnet = get_subnet(ip, bits_consumed);
+ node = &(node->extended[subnet]);
+ bits_consumed += NBITS;
+ } else {
+ /* Make decision on this node */
+ finished = 1;
+ }
+ } while (!finished);
+
+ switch (state) {
+ case ALLOW:
+ result = 1;
+ break;
+ case DENY:
+ result = 0;
+ break;
+ case AS_PARENT:
+ assert(0);
+ break;
+ }
+
+ return result;
+}
+
+
+/* ================================================== */
+
+int
+ADF_IsAllowed(ADF_AuthTable table,
+ IPAddr *ip_addr)
+{
+ uint32_t ip6[4];
+
+ switch (ip_addr->family) {
+ case IPADDR_INET4:
+ return check_ip_in_node(&table->base4, &ip_addr->addr.in4);
+ case IPADDR_INET6:
+ split_ip6(ip_addr, ip6);
+ return check_ip_in_node(&table->base6, ip6);
+ default:
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+static int
+is_any_allowed(TableNode *node, State parent)
+{
+ State state;
+ int i;
+
+ state = node->state != AS_PARENT ? node->state : parent;
+ assert(state != AS_PARENT);
+
+ if (node->extended) {
+ for (i = 0; i < TABLE_SIZE; i++) {
+ if (is_any_allowed(&node->extended[i], state))
+ return 1;
+ }
+ } else if (state == ALLOW) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+ADF_IsAnyAllowed(ADF_AuthTable table, int family)
+{
+ switch (family) {
+ case IPADDR_INET4:
+ return is_any_allowed(&table->base4, AS_PARENT);
+ case IPADDR_INET6:
+ return is_any_allowed(&table->base6, AS_PARENT);
+ default:
+ return 0;
+ }
+}
diff --git a/addrfilt.h b/addrfilt.h
new file mode 100644
index 0000000..b8c131f
--- /dev/null
+++ b/addrfilt.h
@@ -0,0 +1,80 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Module for providing an authorisation filter on IP addresses
+ */
+
+#ifndef GOT_ADDRFILT_H
+#define GOT_ADDRFILT_H
+
+#include "addressing.h"
+
+typedef struct ADF_AuthTableInst *ADF_AuthTable;
+
+typedef enum {
+ ADF_SUCCESS,
+ ADF_BADSUBNET
+} ADF_Status;
+
+
+/* Create a new table. The default rule is deny for everything */
+extern ADF_AuthTable ADF_CreateTable(void);
+
+/* Allow anything in the supplied subnet, EXCEPT for any more specific
+ subnets that are already defined */
+extern ADF_Status ADF_Allow(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits);
+
+/* Allow anything in the supplied subnet, overwriting existing
+ definitions for any more specific subnets */
+extern ADF_Status ADF_AllowAll(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits);
+
+/* Deny anything in the supplied subnet, EXCEPT for any more specific
+ subnets that are already defined */
+extern ADF_Status ADF_Deny(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits);
+
+/* Deny anything in the supplied subnet, overwriting existing
+ definitions for any more specific subnets */
+extern ADF_Status ADF_DenyAll(ADF_AuthTable table,
+ IPAddr *ip,
+ int subnet_bits);
+
+/* Clear up the table */
+extern void ADF_DestroyTable(ADF_AuthTable table);
+
+/* Check whether a given IP address is allowed by the rules in
+ the table */
+extern int ADF_IsAllowed(ADF_AuthTable table,
+ IPAddr *ip);
+
+/* Check if at least one address from a given family is allowed by
+ the rules in the table */
+extern int ADF_IsAnyAllowed(ADF_AuthTable table,
+ int family);
+
+#endif /* GOT_ADDRFILT_H */
diff --git a/array.c b/array.c
new file mode 100644
index 0000000..b31ba56
--- /dev/null
+++ b/array.c
@@ -0,0 +1,145 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 implementing an array with automatic memory allocation.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "memory.h"
+
+struct ARR_Instance_Record {
+ void *data;
+ unsigned int elem_size;
+ unsigned int used;
+ unsigned int allocated;
+};
+
+ARR_Instance
+ARR_CreateInstance(unsigned int elem_size)
+{
+ ARR_Instance array;
+
+ assert(elem_size > 0);
+
+ array = MallocNew(struct ARR_Instance_Record);
+
+ array->data = NULL;
+ array->elem_size = elem_size;
+ array->used = 0;
+ array->allocated = 0;
+
+ return array;
+}
+
+void
+ARR_DestroyInstance(ARR_Instance array)
+{
+ Free(array->data);
+ Free(array);
+}
+
+static void
+realloc_array(ARR_Instance array, unsigned int min_size)
+{
+ assert(min_size <= 2 * min_size);
+ if (array->allocated >= min_size && array->allocated <= 2 * min_size)
+ return;
+
+ if (array->allocated < min_size) {
+ while (array->allocated < min_size)
+ array->allocated = array->allocated ? 2 * array->allocated : 1;
+ } else {
+ array->allocated = min_size;
+ }
+
+ array->data = Realloc2(array->data, array->allocated, array->elem_size);
+}
+
+void *
+ARR_GetNewElement(ARR_Instance array)
+{
+ array->used++;
+ realloc_array(array, array->used);
+ return ARR_GetElement(array, array->used - 1);
+}
+
+void *
+ARR_GetElement(ARR_Instance array, unsigned int index)
+{
+ assert(index < array->used);
+ return (void *)((char *)array->data + (size_t)index * array->elem_size);
+}
+
+void *
+ARR_GetElements(ARR_Instance array)
+{
+ /* Return a non-NULL pointer when the array has zero size */
+ if (!array->data) {
+ assert(!array->used);
+ return array;
+ }
+
+ return array->data;
+}
+
+void
+ARR_AppendElement(ARR_Instance array, void *element)
+{
+ void *e;
+
+ e = ARR_GetNewElement(array);
+ memcpy(e, element, array->elem_size);
+}
+
+void
+ARR_RemoveElement(ARR_Instance array, unsigned int index)
+{
+ void *e, *l;
+
+ e = ARR_GetElement(array, index);
+ l = ARR_GetElement(array, array->used - 1);
+
+ if (e < l)
+ memmove(e, (char *)e + array->elem_size, (char *)l - (char *)e);
+ array->used--;
+
+ realloc_array(array, array->used);
+}
+
+void
+ARR_SetSize(ARR_Instance array, unsigned int size)
+{
+ realloc_array(array, size);
+ array->used = size;
+}
+
+unsigned int
+ARR_GetSize(ARR_Instance array)
+{
+ return array->used;
+}
diff --git a/array.h b/array.h
new file mode 100644
index 0000000..f4fbddb
--- /dev/null
+++ b/array.h
@@ -0,0 +1,59 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 array functions.
+ */
+
+#ifndef GOT_ARRAY_H
+#define GOT_ARRAY_H
+
+typedef struct ARR_Instance_Record *ARR_Instance;
+
+/* Create a new array with given element size */
+extern ARR_Instance ARR_CreateInstance(unsigned int elem_size);
+
+/* Destroy the array */
+extern void ARR_DestroyInstance(ARR_Instance array);
+
+/* Return pointer to a new element added to the end of the array */
+extern void *ARR_GetNewElement(ARR_Instance array);
+
+/* Return element with given index */
+extern void *ARR_GetElement(ARR_Instance array, unsigned int index);
+
+/* Return pointer to the internal array of elements */
+extern void *ARR_GetElements(ARR_Instance array);
+
+/* Add a new element to the end of the array */
+extern void ARR_AppendElement(ARR_Instance array, void *element);
+
+/* Remove element with given index */
+extern void ARR_RemoveElement(ARR_Instance array, unsigned int index);
+
+/* Set the size of the array */
+extern void ARR_SetSize(ARR_Instance array, unsigned int size);
+
+/* Return current size of the array */
+extern unsigned int ARR_GetSize(ARR_Instance array);
+
+#endif
diff --git a/candm.h b/candm.h
new file mode 100644
index 0000000..033cdb9
--- /dev/null
+++ b/candm.h
@@ -0,0 +1,850 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Definitions for the network protocol used for command and monitoring
+ of the timeserver.
+
+ */
+
+#ifndef GOT_CANDM_H
+#define GOT_CANDM_H
+
+#include "sysincl.h"
+#include "addressing.h"
+
+/* This is the default port to use for CANDM, if no alternative is
+ defined */
+#define DEFAULT_CANDM_PORT 323
+
+/* Request codes */
+#define REQ_NULL 0
+#define REQ_ONLINE 1
+#define REQ_OFFLINE 2
+#define REQ_BURST 3
+#define REQ_MODIFY_MINPOLL 4
+#define REQ_MODIFY_MAXPOLL 5
+#define REQ_DUMP 6
+#define REQ_MODIFY_MAXDELAY 7
+#define REQ_MODIFY_MAXDELAYRATIO 8
+#define REQ_MODIFY_MAXUPDATESKEW 9
+#define REQ_LOGON 10
+#define REQ_SETTIME 11
+#define REQ_LOCAL 12
+#define REQ_MANUAL 13
+#define REQ_N_SOURCES 14
+#define REQ_SOURCE_DATA 15
+#define REQ_REKEY 16
+#define REQ_ALLOW 17
+#define REQ_ALLOWALL 18
+#define REQ_DENY 19
+#define REQ_DENYALL 20
+#define REQ_CMDALLOW 21
+#define REQ_CMDALLOWALL 22
+#define REQ_CMDDENY 23
+#define REQ_CMDDENYALL 24
+#define REQ_ACCHECK 25
+#define REQ_CMDACCHECK 26
+#define REQ_ADD_SERVER 27
+#define REQ_ADD_PEER 28
+#define REQ_DEL_SOURCE 29
+#define REQ_WRITERTC 30
+#define REQ_DFREQ 31
+#define REQ_DOFFSET 32
+#define REQ_TRACKING 33
+#define REQ_SOURCESTATS 34
+#define REQ_RTCREPORT 35
+#define REQ_TRIMRTC 36
+#define REQ_CYCLELOGS 37
+#define REQ_SUBNETS_ACCESSED 38
+#define REQ_CLIENT_ACCESSES 39
+#define REQ_CLIENT_ACCESSES_BY_INDEX 40
+#define REQ_MANUAL_LIST 41
+#define REQ_MANUAL_DELETE 42
+#define REQ_MAKESTEP 43
+#define REQ_ACTIVITY 44
+#define REQ_MODIFY_MINSTRATUM 45
+#define REQ_MODIFY_POLLTARGET 46
+#define REQ_MODIFY_MAXDELAYDEVRATIO 47
+#define REQ_RESELECT 48
+#define REQ_RESELECTDISTANCE 49
+#define REQ_MODIFY_MAKESTEP 50
+#define REQ_SMOOTHING 51
+#define REQ_SMOOTHTIME 52
+#define REQ_REFRESH 53
+#define REQ_SERVER_STATS 54
+#define REQ_CLIENT_ACCESSES_BY_INDEX2 55
+#define REQ_LOCAL2 56
+#define REQ_NTP_DATA 57
+#define REQ_ADD_SERVER2 58
+#define REQ_ADD_PEER2 59
+#define REQ_ADD_SERVER3 60
+#define REQ_ADD_PEER3 61
+#define REQ_SHUTDOWN 62
+#define REQ_ONOFFLINE 63
+#define REQ_ADD_SOURCE 64
+#define REQ_NTP_SOURCE_NAME 65
+#define REQ_RESET_SOURCES 66
+#define REQ_AUTH_DATA 67
+#define REQ_CLIENT_ACCESSES_BY_INDEX3 68
+#define REQ_SELECT_DATA 69
+#define REQ_RELOAD_SOURCES 70
+#define REQ_DOFFSET2 71
+#define REQ_MODIFY_SELECTOPTS 72
+#define N_REQUEST_TYPES 73
+
+/* Structure used to exchange timespecs independent of time_t size */
+typedef struct {
+ uint32_t tv_sec_high;
+ uint32_t tv_sec_low;
+ uint32_t tv_nsec;
+} Timespec;
+
+/* This is used in tv_sec_high for 32-bit timestamps */
+#define TV_NOHIGHSEC 0x7fffffff
+
+/* Structure for 64-bit integers (not requiring 64-bit alignment) */
+typedef struct {
+ uint32_t high;
+ uint32_t low;
+} Integer64;
+
+/* 32-bit floating-point format consisting of 7-bit signed exponent
+ and 25-bit signed coefficient without hidden bit.
+ The result is calculated as: 2^(exp - 25) * coef */
+typedef struct {
+ int32_t f;
+} Float;
+
+/* The EOR (end of record) fields are used by the offsetof operator in
+ pktlength.c, to get the number of bytes that ought to be
+ transmitted for each packet type. */
+
+typedef struct {
+ int32_t EOR;
+} REQ_Null;
+
+typedef struct {
+ IPAddr mask;
+ IPAddr address;
+ int32_t EOR;
+} REQ_Online;
+
+typedef struct {
+ IPAddr mask;
+ IPAddr address;
+ int32_t EOR;
+} REQ_Offline;
+
+typedef struct {
+ IPAddr mask;
+ IPAddr address;
+ int32_t n_good_samples;
+ int32_t n_total_samples;
+ int32_t EOR;
+} REQ_Burst;
+
+typedef struct {
+ IPAddr address;
+ int32_t new_minpoll;
+ int32_t EOR;
+} REQ_Modify_Minpoll;
+
+typedef struct {
+ IPAddr address;
+ int32_t new_maxpoll;
+ int32_t EOR;
+} REQ_Modify_Maxpoll;
+
+typedef struct {
+ int32_t pad;
+ int32_t EOR;
+} REQ_Dump;
+
+typedef struct {
+ IPAddr address;
+ Float new_max_delay;
+ int32_t EOR;
+} REQ_Modify_Maxdelay;
+
+typedef struct {
+ IPAddr address;
+ Float new_max_delay_ratio;
+ int32_t EOR;
+} REQ_Modify_Maxdelayratio;
+
+typedef struct {
+ IPAddr address;
+ Float new_max_delay_dev_ratio;
+ int32_t EOR;
+} REQ_Modify_Maxdelaydevratio;
+
+typedef struct {
+ IPAddr address;
+ int32_t new_min_stratum;
+ int32_t EOR;
+} REQ_Modify_Minstratum;
+
+typedef struct {
+ IPAddr address;
+ int32_t new_poll_target;
+ int32_t EOR;
+} REQ_Modify_Polltarget;
+
+typedef struct {
+ Float new_max_update_skew;
+ int32_t EOR;
+} REQ_Modify_Maxupdateskew;
+
+typedef struct {
+ int32_t limit;
+ Float threshold;
+ int32_t EOR;
+} REQ_Modify_Makestep;
+
+typedef struct {
+ Timespec ts;
+ int32_t EOR;
+} REQ_Logon;
+
+typedef struct {
+ Timespec ts;
+ int32_t EOR;
+} REQ_Settime;
+
+typedef struct {
+ int32_t on_off;
+ int32_t stratum;
+ Float distance;
+ int32_t orphan;
+ int32_t EOR;
+} REQ_Local;
+
+typedef struct {
+ int32_t option;
+ int32_t EOR;
+} REQ_Manual;
+
+typedef struct {
+ int32_t index;
+ int32_t EOR;
+} REQ_Source_Data;
+
+typedef struct {
+ IPAddr ip;
+ int32_t subnet_bits;
+ int32_t EOR;
+} REQ_Allow_Deny;
+
+typedef struct {
+ IPAddr ip;
+ 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
+#define REQ_ADDSRC_IBURST 0x4
+#define REQ_ADDSRC_PREFER 0x8
+#define REQ_ADDSRC_NOSELECT 0x10
+#define REQ_ADDSRC_TRUST 0x20
+#define REQ_ADDSRC_REQUIRE 0x40
+#define REQ_ADDSRC_INTERLEAVED 0x80
+#define REQ_ADDSRC_BURST 0x100
+#define REQ_ADDSRC_NTS 0x200
+#define REQ_ADDSRC_COPY 0x400
+#define REQ_ADDSRC_EF_EXP_MONO_ROOT 0x800
+#define REQ_ADDSRC_EF_EXP_NET_CORRECTION 0x1000
+
+typedef struct {
+ uint32_t type;
+ uint8_t name[256];
+ uint32_t port;
+ int32_t minpoll;
+ int32_t maxpoll;
+ int32_t presend_minpoll;
+ uint32_t min_stratum;
+ uint32_t poll_target;
+ uint32_t version;
+ uint32_t max_sources;
+ 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;
+ Float min_delay;
+ Float asymmetry;
+ Float offset;
+ uint32_t flags;
+ int32_t filter_length;
+ uint32_t cert_set;
+ Float max_delay_quant;
+ uint32_t reserved[1];
+ int32_t EOR;
+} REQ_NTP_Source;
+
+typedef struct {
+ IPAddr ip_addr;
+ int32_t EOR;
+} REQ_Del_Source;
+
+typedef struct {
+ Float dfreq;
+ int32_t EOR;
+} REQ_Dfreq;
+
+typedef struct {
+ Float doffset;
+ int32_t EOR;
+} REQ_Doffset;
+
+typedef struct {
+ uint32_t index;
+ int32_t EOR;
+} REQ_Sourcestats;
+
+/* This is based on the response size rather than the
+ request size */
+#define MAX_CLIENT_ACCESSES 8
+
+typedef struct {
+ uint32_t first_index;
+ uint32_t n_clients;
+ uint32_t min_hits;
+ uint32_t reset;
+ int32_t EOR;
+} REQ_ClientAccessesByIndex;
+
+typedef struct {
+ int32_t index;
+ int32_t EOR;
+} REQ_ManualDelete;
+
+typedef struct {
+ Float distance;
+ int32_t EOR;
+} REQ_ReselectDistance;
+
+#define REQ_SMOOTHTIME_RESET 0
+#define REQ_SMOOTHTIME_ACTIVATE 1
+
+typedef struct {
+ int32_t option;
+ int32_t EOR;
+} REQ_SmoothTime;
+
+typedef struct {
+ IPAddr ip_addr;
+ int32_t EOR;
+} REQ_NTPData;
+
+typedef struct {
+ IPAddr ip_addr;
+ int32_t EOR;
+} REQ_NTPSourceName;
+
+typedef struct {
+ IPAddr ip_addr;
+ int32_t EOR;
+} REQ_AuthData;
+
+typedef struct {
+ uint32_t index;
+ int32_t EOR;
+} REQ_SelectData;
+
+/* Mask and options reuse the REQ_ADDSRC flags */
+typedef struct {
+ IPAddr address;
+ uint32_t ref_id;
+ uint32_t mask;
+ uint32_t options;
+ int32_t EOR;
+} REQ_Modify_SelectOpts;
+
+/* ================================================== */
+
+#define PKT_TYPE_CMD_REQUEST 1
+#define PKT_TYPE_CMD_REPLY 2
+
+/* This version number needs to be incremented whenever the packet
+ size and/or the format of any of the existing messages is changed.
+ Other changes, e.g. new command types, should be handled cleanly by
+ client.c and cmdmon.c anyway, so the version can stay the same.
+
+ Version 1 : original version with fixed size packets
+
+ Version 2 : both command and reply packet sizes made capable of
+ being variable length.
+
+ Version 3 : NTP_Source message lengthened (auto_offline)
+
+ Version 4 : IPv6 addressing added, 64-bit time values, sourcestats
+ and tracking reports extended, added flags to NTP source request,
+ trimmed source report, replaced fixed-point format with floating-point
+ and used also instead of integer microseconds, new commands: modify stratum,
+ modify polltarget, modify maxdelaydevratio, reselect, reselectdistance
+
+ Version 5 : auth data moved to the end of the packet to allow hashes with
+ different sizes, extended sources, tracking and activity reports, dropped
+ subnets accessed and client accesses
+
+ Version 6 : added padding to requests to prevent amplification attack,
+ changed maximum number of samples in manual list to 16, new commands: modify
+ makestep, smoothing, smoothtime
+
+ Support for authentication was removed later in version 6 of the protocol
+ and commands that required authentication are allowed only locally over Unix
+ domain socket.
+
+ Version 6 (no authentication) : changed format of client accesses by index
+ (two times), delta offset, and manual timestamp, added new fields and
+ flags to NTP source request and report, made length of manual list constant,
+ added new commands: authdata, ntpdata, onoffline, refresh, reset,
+ selectdata, serverstats, shutdown, sourcename
+ */
+
+#define PROTO_VERSION_NUMBER 6
+
+/* The oldest protocol versions that are compatible enough with the current
+ version to report a version mismatch for the server and the client */
+#define PROTO_VERSION_MISMATCH_COMPAT_SERVER 5
+#define PROTO_VERSION_MISMATCH_COMPAT_CLIENT 4
+
+/* The first protocol version using padding in requests */
+#define PROTO_VERSION_PADDING 6
+
+/* The maximum length of padding in request packet, currently
+ defined by CLIENT_ACCESSES_BY_INDEX3 */
+#define MAX_PADDING_LENGTH 484
+
+/* ================================================== */
+
+typedef struct {
+ uint8_t version; /* Protocol version */
+ uint8_t pkt_type; /* What sort of packet this is */
+ uint8_t res1;
+ uint8_t res2;
+ uint16_t command; /* Which command is being issued */
+ uint16_t attempt; /* How many resends the client has done
+ (count up from zero for same sequence
+ number) */
+ uint32_t sequence; /* Client's sequence number */
+ uint32_t pad1;
+ uint32_t pad2;
+
+ union {
+ REQ_Null null;
+ REQ_Online online;
+ REQ_Offline offline;
+ REQ_Burst burst;
+ REQ_Modify_Minpoll modify_minpoll;
+ REQ_Modify_Maxpoll modify_maxpoll;
+ REQ_Dump dump;
+ REQ_Modify_Maxdelay modify_maxdelay;
+ REQ_Modify_Maxdelayratio modify_maxdelayratio;
+ REQ_Modify_Maxdelaydevratio modify_maxdelaydevratio;
+ REQ_Modify_Minstratum modify_minstratum;
+ REQ_Modify_Polltarget modify_polltarget;
+ REQ_Modify_Maxupdateskew modify_maxupdateskew;
+ REQ_Modify_Makestep modify_makestep;
+ REQ_Logon logon;
+ REQ_Settime settime;
+ REQ_Local local;
+ REQ_Manual manual;
+ REQ_Source_Data source_data;
+ REQ_Allow_Deny allow_deny;
+ REQ_Ac_Check ac_check;
+ REQ_NTP_Source ntp_source;
+ REQ_Del_Source del_source;
+ REQ_Dfreq dfreq;
+ REQ_Doffset doffset;
+ REQ_Sourcestats sourcestats;
+ REQ_ClientAccessesByIndex client_accesses_by_index;
+ REQ_ManualDelete manual_delete;
+ REQ_ReselectDistance reselect_distance;
+ REQ_SmoothTime smoothtime;
+ REQ_NTPData ntp_data;
+ REQ_NTPSourceName ntp_source_name;
+ REQ_AuthData auth_data;
+ REQ_SelectData select_data;
+ REQ_Modify_SelectOpts modify_select_opts;
+ } data; /* Command specific parameters */
+
+ /* Padding used to prevent traffic amplification. It only defines the
+ maximum size of the packet, there is no hole after the data field. */
+ uint8_t padding[MAX_PADDING_LENGTH];
+
+} CMD_Request;
+
+/* ================================================== */
+/* Authority codes for command types */
+
+#define PERMIT_OPEN 0
+#define PERMIT_LOCAL 1
+#define PERMIT_AUTH 2
+
+/* ================================================== */
+
+/* Reply codes */
+#define RPY_NULL 1
+#define RPY_N_SOURCES 2
+#define RPY_SOURCE_DATA 3
+#define RPY_MANUAL_TIMESTAMP 4
+#define RPY_TRACKING 5
+#define RPY_SOURCESTATS 6
+#define RPY_RTC 7
+#define RPY_SUBNETS_ACCESSED 8
+#define RPY_CLIENT_ACCESSES 9
+#define RPY_CLIENT_ACCESSES_BY_INDEX 10
+#define RPY_MANUAL_LIST 11
+#define RPY_ACTIVITY 12
+#define RPY_SMOOTHING 13
+#define RPY_SERVER_STATS 14
+#define RPY_CLIENT_ACCESSES_BY_INDEX2 15
+#define RPY_NTP_DATA 16
+#define RPY_MANUAL_TIMESTAMP2 17
+#define RPY_MANUAL_LIST2 18
+#define RPY_NTP_SOURCE_NAME 19
+#define RPY_AUTH_DATA 20
+#define RPY_CLIENT_ACCESSES_BY_INDEX3 21
+#define RPY_SERVER_STATS2 22
+#define RPY_SELECT_DATA 23
+#define RPY_SERVER_STATS3 24
+#define RPY_SERVER_STATS4 25
+#define N_REPLY_TYPES 26
+
+/* Status codes */
+#define STT_SUCCESS 0
+#define STT_FAILED 1
+#define STT_UNAUTH 2
+#define STT_INVALID 3
+#define STT_NOSUCHSOURCE 4
+#define STT_INVALIDTS 5
+#define STT_NOTENABLED 6
+#define STT_BADSUBNET 7
+#define STT_ACCESSALLOWED 8
+#define STT_ACCESSDENIED 9
+#define STT_NOHOSTACCESS 10 /* Deprecated */
+#define STT_SOURCEALREADYKNOWN 11
+#define STT_TOOMANYSOURCES 12
+#define STT_NORTC 13
+#define STT_BADRTCFILE 14
+#define STT_INACTIVE 15
+#define STT_BADSAMPLE 16
+#define STT_INVALIDAF 17
+#define STT_BADPKTVERSION 18
+#define STT_BADPKTLENGTH 19
+#define STT_INVALIDNAME 21
+
+typedef struct {
+ int32_t EOR;
+} RPY_Null;
+
+typedef struct {
+ uint32_t n_sources;
+ int32_t EOR;
+} RPY_N_Sources;
+
+#define RPY_SD_MD_CLIENT 0
+#define RPY_SD_MD_PEER 1
+#define RPY_SD_MD_REF 2
+
+#define RPY_SD_ST_SELECTED 0
+#define RPY_SD_ST_NONSELECTABLE 1
+#define RPY_SD_ST_FALSETICKER 2
+#define RPY_SD_ST_JITTERY 3
+#define RPY_SD_ST_UNSELECTED 4
+#define RPY_SD_ST_SELECTABLE 5
+
+typedef struct {
+ IPAddr ip_addr;
+ int16_t poll;
+ uint16_t stratum;
+ uint16_t state;
+ uint16_t mode;
+ uint16_t flags;
+ uint16_t reachability;
+ uint32_t since_sample;
+ Float orig_latest_meas;
+ Float latest_meas;
+ Float latest_meas_err;
+ int32_t EOR;
+} RPY_Source_Data;
+
+typedef struct {
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ uint16_t stratum;
+ uint16_t leap_status;
+ Timespec ref_time;
+ Float current_correction;
+ Float last_offset;
+ Float rms_offset;
+ Float freq_ppm;
+ Float resid_freq_ppm;
+ Float skew_ppm;
+ Float root_delay;
+ Float root_dispersion;
+ Float last_update_interval;
+ int32_t EOR;
+} RPY_Tracking;
+
+typedef struct {
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ uint32_t n_samples;
+ uint32_t n_runs;
+ uint32_t span_seconds;
+ Float sd;
+ Float resid_freq_ppm;
+ Float skew_ppm;
+ Float est_offset;
+ Float est_offset_err;
+ int32_t EOR;
+} RPY_Sourcestats;
+
+typedef struct {
+ Timespec ref_time;
+ uint16_t n_samples;
+ uint16_t n_runs;
+ uint32_t span_seconds;
+ Float rtc_seconds_fast;
+ Float rtc_gain_rate_ppm;
+ int32_t EOR;
+} RPY_Rtc;
+
+typedef struct {
+ Float offset;
+ Float dfreq_ppm;
+ Float new_afreq_ppm;
+ int32_t EOR;
+} RPY_ManualTimestamp;
+
+typedef struct {
+ IPAddr ip;
+ uint32_t ntp_hits;
+ uint32_t nke_hits;
+ uint32_t cmd_hits;
+ uint32_t ntp_drops;
+ uint32_t nke_drops;
+ uint32_t cmd_drops;
+ int8_t ntp_interval;
+ int8_t nke_interval;
+ int8_t cmd_interval;
+ int8_t ntp_timeout_interval;
+ uint32_t last_ntp_hit_ago;
+ uint32_t last_nke_hit_ago;
+ uint32_t last_cmd_hit_ago;
+} RPY_ClientAccesses_Client;
+
+typedef struct {
+ uint32_t n_indices; /* how many indices there are in the server's table */
+ uint32_t next_index; /* the index 1 beyond those processed on this call */
+ uint32_t n_clients; /* the number of valid entries in the following array */
+ RPY_ClientAccesses_Client clients[MAX_CLIENT_ACCESSES];
+ int32_t EOR;
+} RPY_ClientAccessesByIndex;
+
+typedef struct {
+ Integer64 ntp_hits;
+ Integer64 nke_hits;
+ Integer64 cmd_hits;
+ Integer64 ntp_drops;
+ Integer64 nke_drops;
+ Integer64 cmd_drops;
+ Integer64 log_drops;
+ Integer64 ntp_auth_hits;
+ Integer64 ntp_interleaved_hits;
+ Integer64 ntp_timestamps;
+ Integer64 ntp_span_seconds;
+ Integer64 ntp_daemon_rx_timestamps;
+ Integer64 ntp_daemon_tx_timestamps;
+ Integer64 ntp_kernel_rx_timestamps;
+ Integer64 ntp_kernel_tx_timestamps;
+ Integer64 ntp_hw_rx_timestamps;
+ Integer64 ntp_hw_tx_timestamps;
+ Integer64 reserved[4];
+ int32_t EOR;
+} RPY_ServerStats;
+
+#define MAX_MANUAL_LIST_SAMPLES 16
+
+typedef struct {
+ Timespec when;
+ Float slewed_offset;
+ Float orig_offset;
+ Float residual;
+} RPY_ManualListSample;
+
+typedef struct {
+ uint32_t n_samples;
+ RPY_ManualListSample samples[MAX_MANUAL_LIST_SAMPLES];
+ int32_t EOR;
+} RPY_ManualList;
+
+typedef struct {
+ int32_t online;
+ int32_t offline;
+ int32_t burst_online;
+ int32_t burst_offline;
+ int32_t unresolved;
+ int32_t EOR;
+} RPY_Activity;
+
+#define RPY_SMT_FLAG_ACTIVE 0x1
+#define RPY_SMT_FLAG_LEAPONLY 0x2
+
+typedef struct {
+ uint32_t flags;
+ Float offset;
+ Float freq_ppm;
+ Float wander_ppm;
+ Float last_update_ago;
+ Float remaining_time;
+ int32_t EOR;
+} RPY_Smoothing;
+
+#define RPY_NTP_FLAGS_TESTS 0x3ff
+#define RPY_NTP_FLAG_INTERLEAVED 0x4000
+#define RPY_NTP_FLAG_AUTHENTICATED 0x8000
+
+typedef struct {
+ IPAddr remote_addr;
+ IPAddr local_addr;
+ uint16_t remote_port;
+ uint8_t leap;
+ uint8_t version;
+ uint8_t mode;
+ uint8_t stratum;
+ int8_t poll;
+ int8_t precision;
+ Float root_delay;
+ Float root_dispersion;
+ uint32_t ref_id;
+ Timespec ref_time;
+ Float offset;
+ Float peer_delay;
+ Float peer_dispersion;
+ Float response_time;
+ Float jitter_asymmetry;
+ uint16_t flags;
+ uint8_t tx_tss_char;
+ uint8_t rx_tss_char;
+ uint32_t total_tx_count;
+ uint32_t total_rx_count;
+ uint32_t total_valid_count;
+ uint32_t total_good_count;
+ uint32_t reserved[3];
+ int32_t EOR;
+} RPY_NTPData;
+
+typedef struct {
+ uint8_t name[256];
+ int32_t EOR;
+} RPY_NTPSourceName;
+
+#define RPY_AD_MD_NONE 0
+#define RPY_AD_MD_SYMMETRIC 1
+#define RPY_AD_MD_NTS 2
+
+typedef struct {
+ uint16_t mode;
+ uint16_t key_type;
+ uint32_t key_id;
+ uint16_t key_length;
+ uint16_t ke_attempts;
+ uint32_t last_ke_ago;
+ uint16_t cookies;
+ uint16_t cookie_length;
+ uint16_t nak;
+ uint16_t pad;
+ int32_t EOR;
+} RPY_AuthData;
+
+#define RPY_SD_OPTION_NOSELECT 0x1
+#define RPY_SD_OPTION_PREFER 0x2
+#define RPY_SD_OPTION_TRUST 0x4
+#define RPY_SD_OPTION_REQUIRE 0x8
+
+typedef struct {
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ uint8_t state_char;
+ uint8_t authentication;
+ uint8_t leap;
+ uint8_t pad;
+ uint16_t conf_options;
+ uint16_t eff_options;
+ uint32_t last_sample_ago;
+ Float score;
+ Float lo_limit;
+ Float hi_limit;
+ int32_t EOR;
+} RPY_SelectData;
+
+typedef struct {
+ uint8_t version;
+ uint8_t pkt_type;
+ uint8_t res1;
+ uint8_t res2;
+ uint16_t command; /* Which command is being replied to */
+ uint16_t reply; /* Which format of reply this is */
+ uint16_t status; /* Status of command processing */
+ uint16_t pad1; /* Padding for compatibility and 4 byte alignment */
+ uint16_t pad2;
+ uint16_t pad3;
+ uint32_t sequence; /* Echo of client's sequence number */
+ uint32_t pad4;
+ uint32_t pad5;
+
+ union {
+ RPY_Null null;
+ RPY_N_Sources n_sources;
+ RPY_Source_Data source_data;
+ RPY_ManualTimestamp manual_timestamp;
+ RPY_Tracking tracking;
+ RPY_Sourcestats sourcestats;
+ RPY_Rtc rtc;
+ RPY_ClientAccessesByIndex client_accesses_by_index;
+ RPY_ServerStats server_stats;
+ RPY_ManualList manual_list;
+ RPY_Activity activity;
+ RPY_Smoothing smoothing;
+ RPY_NTPData ntp_data;
+ RPY_NTPSourceName ntp_source_name;
+ RPY_AuthData auth_data;
+ RPY_SelectData select_data;
+ } data; /* Reply specific parameters */
+
+} CMD_Reply;
+
+/* ================================================== */
+
+#endif /* GOT_CANDM_H */
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..7cfefba
--- /dev/null
+++ b/client.c
@@ -0,0 +1,3538 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Lonnie Abelbeck 2016, 2018
+ * Copyright (C) Miroslav Lichvar 2009-2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Command line client for configuring the daemon and obtaining status
+ from it whilst running.
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#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
+#include <editline/readline.h>
+#endif
+
+/* ================================================== */
+
+struct Address {
+ SCK_AddressType type;
+ union {
+ IPSockAddr ip;
+ char *path;
+ } addr;
+};
+
+static ARR_Instance server_addresses;
+
+static int sock_fd = -1;
+
+static volatile int quit = 0;
+
+static int on_terminal = 0;
+
+static int no_dns = 0;
+
+static int source_names = 0;
+
+static int csv_mode = 0;
+
+static int end_dot = 0;
+
+/* ================================================== */
+/* Log a message. This is a minimalistic replacement of the logging.c
+ implementation to avoid linking with it and other modules. */
+
+LOG_Severity log_min_severity = LOGS_INFO;
+
+void LOG_Message(LOG_Severity severity,
+#if DEBUG > 0
+ int line_number, const char *filename, const char *function_name,
+#endif
+ const char *format, ...)
+{
+ va_list ap;
+
+ if (severity < log_min_severity)
+ return;
+
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ putc('\n', stderr);
+ va_end(ap);
+}
+
+/* ================================================== */
+/* Read a single line of commands from standard input */
+
+#ifdef FEAT_READLINE
+static char **command_name_completion(const char *text, int start, int end);
+#endif
+
+static char *
+read_line(void)
+{
+ static char line[2048];
+ static const char *prompt = "chronyc> ";
+
+ if (on_terminal) {
+#ifdef FEAT_READLINE
+ char *cmd;
+
+ rl_attempted_completion_function = command_name_completion;
+ rl_basic_word_break_characters = " \t\n\r";
+
+ /* save line only if not empty */
+ cmd = readline(prompt);
+ if( cmd == NULL ) return( NULL );
+
+ /* user pressed return */
+ if( *cmd != '\0' ) {
+ strncpy(line, cmd, sizeof(line) - 1);
+ line[sizeof(line) - 1] = '\0';
+ add_history(cmd);
+ /* free the buffer allocated by readline */
+ Free(cmd);
+ } else {
+ /* simulate the user has entered an empty line */
+ *line = '\0';
+ }
+ return( line );
+#else
+ printf("%s", prompt);
+ fflush(stdout);
+#endif
+ }
+ if (fgets(line, sizeof(line), stdin)) {
+ return line;
+ } else {
+ return NULL;
+ }
+
+}
+
+/* ================================================== */
+
+static ARR_Instance
+get_addresses(const char *hostnames, int port)
+{
+ struct Address *addr;
+ ARR_Instance addrs;
+ char *hostname, *s1, *s2;
+ IPAddr ip_addrs[DNS_MAX_ADDRESSES];
+ int i;
+
+ addrs = ARR_CreateInstance(sizeof (*addr));
+ s1 = Strdup(hostnames);
+
+ /* Parse the comma-separated list of hostnames */
+ for (hostname = s1; hostname && *hostname; hostname = s2) {
+ s2 = strchr(hostname, ',');
+ if (s2)
+ *s2++ = '\0';
+
+ /* hostname starting with / is considered a path of Unix domain socket */
+ if (hostname[0] == '/') {
+ 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);
+ continue;
+ }
+
+ for (i = 0; i < DNS_MAX_ADDRESSES && ip_addrs[i].family != IPADDR_UNSPEC; i++) {
+ 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]));
+ }
+ }
+ }
+
+ Free(s1);
+ 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
+open_socket(struct Address *addr)
+{
+ char *dir, *local_addr;
+ size_t local_addr_len;
+
+ switch (addr->type) {
+ case SCK_ADDR_IP:
+ sock_fd = SCK_OpenUdpSocket(&addr->addr.ip, NULL, NULL, 0);
+ break;
+ 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;
+ default:
+ assert(0);
+ }
+
+ if (sock_fd < 0)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+close_io(void)
+{
+ if (sock_fd < 0)
+ return;
+
+ SCK_RemoveSocket(sock_fd);
+ SCK_CloseSocket(sock_fd);
+ sock_fd = -1;
+}
+
+/* ================================================== */
+
+static int
+open_io(void)
+{
+ static unsigned int address_index = 0;
+ struct Address *addr;
+
+ /* If a socket is already opened, close it and try the next address */
+ if (sock_fd >= 0) {
+ close_io();
+ address_index++;
+ }
+
+ /* Find an address for which a socket can be opened and connected */
+ for (; address_index < ARR_GetSize(server_addresses); address_index++) {
+ addr = ARR_GetElement(server_addresses, address_index);
+
+ if (open_socket(addr))
+ return 1;
+
+ close_io();
+ }
+
+ /* Start from the first address if called again */
+ address_index = 0;
+
+ return 0;
+}
+
+/* ================================================== */
+
+static void
+bits_to_mask(int bits, int family, IPAddr *mask)
+{
+ int i;
+
+ mask->family = family;
+ switch (family) {
+ case IPADDR_INET4:
+ if (bits > 32 || bits < 0)
+ bits = 32;
+ if (bits > 0) {
+ mask->addr.in4 = -1;
+ mask->addr.in4 <<= 32 - bits;
+ } else {
+ mask->addr.in4 = 0;
+ }
+ break;
+ case IPADDR_INET6:
+ if (bits > 128 || bits < 0)
+ bits = 128;
+ for (i = 0; i < bits / 8; i++)
+ mask->addr.in6[i] = 0xff;
+ if (i < 16)
+ mask->addr.in6[i++] = (0xff << (8 - bits % 8)) & 0xff;
+ for (; i < 16; i++)
+ mask->addr.in6[i] = 0x0;
+ break;
+ case IPADDR_ID:
+ mask->family = IPADDR_UNSPEC;
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+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)
+{
+ unsigned int bits;
+ char *p, *q;
+
+ p = line;
+ if (!*p) {
+ mask->family = address->family = IPADDR_UNSPEC;
+ return 1;
+ } else {
+ q = strchr(p, '/');
+ if (q) {
+ *q++ = 0;
+ if (UTI_StringToIP(p, mask)) {
+ p = q;
+ if (UTI_StringToIP(p, address)) {
+ if (address->family == mask->family)
+ return 1;
+ } else if (sscanf(p, "%u", &bits) == 1) {
+ *address = *mask;
+ bits_to_mask(bits, address->family, mask);
+ return 1;
+ }
+ }
+ } else {
+ if (parse_source_address(p, address)) {
+ bits_to_mask(-1, address->family, mask);
+ return 1;
+ } else {
+ LOG(LOGS_ERR, "Could not get address for hostname");
+ return 0;
+ }
+ }
+ }
+
+ LOG(LOGS_ERR, "Invalid syntax for mask/address");
+ return 0;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_offline(CMD_Request *msg, char *line)
+{
+ IPAddr mask, address;
+ int ok;
+
+ if (read_mask_address(line, &mask, &address)) {
+ UTI_IPHostToNetwork(&mask, &msg->data.offline.mask);
+ UTI_IPHostToNetwork(&address, &msg->data.offline.address);
+ msg->command = htons(REQ_OFFLINE);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+
+static int
+process_cmd_online(CMD_Request *msg, char *line)
+{
+ IPAddr mask, address;
+ int ok;
+
+ if (read_mask_address(line, &mask, &address)) {
+ UTI_IPHostToNetwork(&mask, &msg->data.online.mask);
+ UTI_IPHostToNetwork(&address, &msg->data.online.address);
+ msg->command = htons(REQ_ONLINE);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_onoffline(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_ONOFFLINE);
+}
+
+/* ================================================== */
+
+static int
+read_address_integer(char *line, IPAddr *address, int *value)
+{
+ char *hostname;
+ int ok = 0;
+
+ hostname = line;
+ line = CPS_SplitWord(line);
+
+ if (sscanf(line, "%d", value) != 1) {
+ LOG(LOGS_ERR, "Invalid syntax for address value");
+ ok = 0;
+ } else {
+ if (!parse_source_address(hostname, address)) {
+ LOG(LOGS_ERR, "Could not get address for hostname");
+ ok = 0;
+ } else {
+ ok = 1;
+ }
+ }
+
+ return ok;
+
+}
+
+
+/* ================================================== */
+
+static int
+read_address_double(char *line, IPAddr *address, double *value)
+{
+ char *hostname;
+ int ok = 0;
+
+ hostname = line;
+ line = CPS_SplitWord(line);
+
+ if (sscanf(line, "%lf", value) != 1) {
+ LOG(LOGS_ERR, "Invalid syntax for address value");
+ ok = 0;
+ } else {
+ if (!parse_source_address(hostname, address)) {
+ LOG(LOGS_ERR, "Could not get address for hostname");
+ ok = 0;
+ } else {
+ ok = 1;
+ }
+ }
+
+ return ok;
+
+}
+
+
+/* ================================================== */
+
+static int
+process_cmd_minpoll(CMD_Request *msg, char *line)
+{
+ IPAddr address;
+ int minpoll;
+ int ok;
+
+ if (read_address_integer(line, &address, &minpoll)) {
+ UTI_IPHostToNetwork(&address, &msg->data.modify_minpoll.address);
+ msg->data.modify_minpoll.new_minpoll = htonl(minpoll);
+ msg->command = htons(REQ_MODIFY_MINPOLL);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxpoll(CMD_Request *msg, char *line)
+{
+ IPAddr address;
+ int maxpoll;
+ int ok;
+
+ if (read_address_integer(line, &address, &maxpoll)) {
+ UTI_IPHostToNetwork(&address, &msg->data.modify_maxpoll.address);
+ msg->data.modify_maxpoll.new_maxpoll = htonl(maxpoll);
+ msg->command = htons(REQ_MODIFY_MAXPOLL);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxdelay(CMD_Request *msg, char *line)
+{
+ IPAddr address;
+ double max_delay;
+ int ok;
+
+ if (read_address_double(line, &address, &max_delay)) {
+ UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelay.address);
+ msg->data.modify_maxdelay.new_max_delay = UTI_FloatHostToNetwork(max_delay);
+ msg->command = htons(REQ_MODIFY_MAXDELAY);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxdelaydevratio(CMD_Request *msg, char *line)
+{
+ IPAddr address;
+ double max_delay_dev_ratio;
+ int ok;
+
+ if (read_address_double(line, &address, &max_delay_dev_ratio)) {
+ UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelaydevratio.address);
+ msg->data.modify_maxdelayratio.new_max_delay_ratio = UTI_FloatHostToNetwork(max_delay_dev_ratio);
+ msg->command = htons(REQ_MODIFY_MAXDELAYDEVRATIO);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxdelayratio(CMD_Request *msg, char *line)
+{
+ IPAddr address;
+ double max_delay_ratio;
+ int ok;
+
+ if (read_address_double(line, &address, &max_delay_ratio)) {
+ UTI_IPHostToNetwork(&address, &msg->data.modify_maxdelayratio.address);
+ msg->data.modify_maxdelayratio.new_max_delay_ratio = UTI_FloatHostToNetwork(max_delay_ratio);
+ msg->command = htons(REQ_MODIFY_MAXDELAYRATIO);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_minstratum(CMD_Request *msg, char *line)
+{
+ IPAddr address;
+ int min_stratum;
+ int ok;
+
+ if (read_address_integer(line, &address, &min_stratum)) {
+ UTI_IPHostToNetwork(&address, &msg->data.modify_minstratum.address);
+ msg->data.modify_minstratum.new_min_stratum = htonl(min_stratum);
+ msg->command = htons(REQ_MODIFY_MINSTRATUM);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_polltarget(CMD_Request *msg, char *line)
+{
+ IPAddr address;
+ int poll_target;
+ int ok;
+
+ if (read_address_integer(line, &address, &poll_target)) {
+ UTI_IPHostToNetwork(&address, &msg->data.modify_polltarget.address);
+ msg->data.modify_polltarget.new_poll_target = htonl(poll_target);
+ msg->command = htons(REQ_MODIFY_POLLTARGET);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static int
+process_cmd_maxupdateskew(CMD_Request *msg, char *line)
+{
+ int ok;
+ double new_max_update_skew;
+
+ if (sscanf(line, "%lf", &new_max_update_skew) == 1) {
+ msg->data.modify_maxupdateskew.new_max_update_skew = UTI_FloatHostToNetwork(new_max_update_skew);
+ msg->command = htons(REQ_MODIFY_MAXUPDATESKEW);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static void
+process_cmd_dump(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_DUMP);
+ msg->data.dump.pad = htonl(0);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_writertc(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_WRITERTC);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_trimrtc(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_TRIMRTC);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_cyclelogs(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_CYCLELOGS);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_burst(CMD_Request *msg, char *line)
+{
+ int n_good_samples, n_total_samples;
+ char *s1, *s2;
+ IPAddr address, mask;
+
+ s1 = line;
+ s2 = CPS_SplitWord(s1);
+ CPS_SplitWord(s2);
+
+ if (sscanf(s1, "%d/%d", &n_good_samples, &n_total_samples) != 2) {
+ LOG(LOGS_ERR, "Invalid syntax for burst command");
+ return 0;
+ }
+
+ mask.family = address.family = IPADDR_UNSPEC;
+ if (*s2 && !read_mask_address(s2, &mask, &address)) {
+ return 0;
+ }
+
+ msg->command = htons(REQ_BURST);
+ msg->data.burst.n_good_samples = ntohl(n_good_samples);
+ msg->data.burst.n_total_samples = ntohl(n_total_samples);
+
+ UTI_IPHostToNetwork(&mask, &msg->data.burst.mask);
+ UTI_IPHostToNetwork(&address, &msg->data.burst.address);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_local(CMD_Request *msg, char *line)
+{
+ int on_off, stratum = 0, orphan = 0;
+ double distance = 0.0;
+
+ if (!strcmp(line, "off")) {
+ on_off = 0;
+ } else if (CPS_ParseLocal(line, &stratum, &orphan, &distance)) {
+ on_off = 1;
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for local command");
+ return 0;
+ }
+
+ msg->command = htons(REQ_LOCAL2);
+ msg->data.local.on_off = htonl(on_off);
+ msg->data.local.stratum = htonl(stratum);
+ msg->data.local.distance = UTI_FloatHostToNetwork(distance);
+ msg->data.local.orphan = htonl(orphan);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_manual(CMD_Request *msg, const char *line)
+{
+ const char *p;
+
+ p = line;
+
+ if (!strcmp(p, "off")) {
+ msg->data.manual.option = htonl(0);
+ } else if (!strcmp(p, "on")) {
+ msg->data.manual.option = htonl(1);
+ } else if (!strcmp(p, "reset")) {
+ msg->data.manual.option = htonl(2);
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for manual command");
+ return 0;
+ }
+ msg->command = htons(REQ_MANUAL);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_allowdeny(CMD_Request *msg, char *line, int cmd, int allcmd)
+{
+ int all, subnet_bits;
+ IPAddr ip;
+
+ if (!CPS_ParseAllowDeny(line, &all, &ip, &subnet_bits)) {
+ LOG(LOGS_ERR, "Could not read address");
+ return 0;
+ }
+
+ msg->command = htons(all ? allcmd : cmd);
+ UTI_IPHostToNetwork(&ip, &msg->data.allow_deny.ip);
+ msg->data.allow_deny.subnet_bits = htonl(subnet_bits);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_accheck(CMD_Request *msg, char *line)
+{
+ IPAddr ip;
+ msg->command = htons(REQ_ACCHECK);
+ if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) {
+ UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip);
+ return 1;
+ } else {
+ LOG(LOGS_ERR, "Could not read address");
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+static int
+process_cmd_cmdaccheck(CMD_Request *msg, char *line)
+{
+ IPAddr ip;
+ msg->command = htons(REQ_CMDACCHECK);
+ if (DNS_Name2IPAddress(line, &ip, 1) == DNS_Success) {
+ UTI_IPHostToNetwork(&ip, &msg->data.ac_check.ip);
+ return 1;
+ } else {
+ LOG(LOGS_ERR, "Could not read address");
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+static int
+process_cmd_dfreq(CMD_Request *msg, char *line)
+{
+ double dfreq;
+
+ msg->command = htons(REQ_DFREQ);
+
+ if (sscanf(line, "%lf", &dfreq) != 1) {
+ LOG(LOGS_ERR, "Invalid value");
+ return 0;
+ }
+
+ msg->data.dfreq.dfreq = UTI_FloatHostToNetwork(dfreq);
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_doffset(CMD_Request *msg, char *line)
+{
+ double doffset;
+
+ msg->command = htons(REQ_DOFFSET2);
+
+ if (sscanf(line, "%lf", &doffset) != 1) {
+ LOG(LOGS_ERR, "Invalid value");
+ return 0;
+ }
+
+ msg->data.doffset.doffset = UTI_FloatHostToNetwork(doffset);
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+convert_addsrc_sel_options(int options)
+{
+ return (options & SRC_SELECT_PREFER ? REQ_ADDSRC_PREFER : 0) |
+ (options & SRC_SELECT_NOSELECT ? REQ_ADDSRC_NOSELECT : 0) |
+ (options & SRC_SELECT_TRUST ? REQ_ADDSRC_TRUST : 0) |
+ (options & SRC_SELECT_REQUIRE ? REQ_ADDSRC_REQUIRE : 0);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_add_source(CMD_Request *msg, char *line)
+{
+ CPS_NTP_Source data;
+ IPAddr ip_addr;
+ int result = 0, status, type;
+ const char *opt_name, *word;
+
+ msg->command = htons(REQ_ADD_SOURCE);
+
+ word = line;
+ line = CPS_SplitWord(line);
+
+ if (!strcasecmp(word, "server")) {
+ type = REQ_ADDSRC_SERVER;
+ } else if (!strcasecmp(word, "peer")) {
+ type = REQ_ADDSRC_PEER;
+ } else if (!strcasecmp(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:
+ /* 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;
+ }
+
+ opt_name = NULL;
+ if (opt_name) {
+ LOG(LOGS_ERR, "%s can't be set in chronyc", opt_name);
+ 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(data.port);
+ 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);
+ msg->data.ntp_source.min_stratum = htonl(data.params.min_stratum);
+ msg->data.ntp_source.poll_target = htonl(data.params.poll_target);
+ msg->data.ntp_source.version = htonl(data.params.version);
+ msg->data.ntp_source.max_sources = htonl(data.params.max_sources);
+ 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 =
+ UTI_FloatHostToNetwork(data.params.max_delay_dev_ratio);
+ msg->data.ntp_source.min_delay = UTI_FloatHostToNetwork(data.params.min_delay);
+ msg->data.ntp_source.asymmetry = UTI_FloatHostToNetwork(data.params.asymmetry);
+ msg->data.ntp_source.offset = UTI_FloatHostToNetwork(data.params.offset);
+ msg->data.ntp_source.flags = htonl(
+ (data.params.connectivity == SRC_ONLINE ? REQ_ADDSRC_ONLINE : 0) |
+ (data.params.auto_offline ? REQ_ADDSRC_AUTOOFFLINE : 0) |
+ (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.copy ? REQ_ADDSRC_COPY : 0) |
+ (data.params.ext_fields & NTP_EF_FLAG_EXP_MONO_ROOT ?
+ REQ_ADDSRC_EF_EXP_MONO_ROOT : 0) |
+ (data.params.ext_fields & NTP_EF_FLAG_EXP_NET_CORRECTION ?
+ REQ_ADDSRC_EF_EXP_NET_CORRECTION : 0) |
+ convert_addsrc_sel_options(data.params.sel_options));
+ msg->data.ntp_source.filter_length = htonl(data.params.filter_length);
+ msg->data.ntp_source.cert_set = htonl(data.params.cert_set);
+ msg->data.ntp_source.max_delay_quant =
+ UTI_FloatHostToNetwork(data.params.max_delay_quant);
+ memset(msg->data.ntp_source.reserved, 0, sizeof (msg->data.ntp_source.reserved));
+
+ result = 1;
+
+ break;
+ }
+
+ return result;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_delete(CMD_Request *msg, char *line)
+{
+ char *hostname;
+ int ok = 0;
+ IPAddr address;
+
+ msg->command = htons(REQ_DEL_SOURCE);
+ hostname = line;
+ CPS_SplitWord(line);
+
+ if (!*hostname) {
+ LOG(LOGS_ERR, "Invalid syntax for address");
+ ok = 0;
+ } else {
+ if (!parse_source_address(hostname, &address)) {
+ LOG(LOGS_ERR, "Could not get address for hostname");
+ ok = 0;
+ } else {
+ UTI_IPHostToNetwork(&address, &msg->data.del_source.ip_addr);
+ ok = 1;
+ }
+ }
+
+ return ok;
+
+}
+
+/* ================================================== */
+
+static void
+give_help(void)
+{
+ int line, len;
+ const char *s, cols[] =
+ "System clock:\0\0"
+ "tracking\0Display system time information\0"
+ "makestep\0Correct clock by stepping immediately\0"
+ "makestep <threshold> <updates>\0Configure automatic clock stepping\0"
+ "maxupdateskew <skew>\0Modify maximum valid skew to update frequency\0"
+ "waitsync [<max-tries> [<max-correction> [<max-skew> [<interval>]]]]\0"
+ "Wait until synchronised in specified limits\0"
+ "\0\0"
+ "Time sources:\0\0"
+ "sources [-a] [-v]\0Display information about current sources\0"
+ "sourcestats [-a] [-v]\0Display statistics about collected measurements\0"
+ "selectdata [-a] [-v]\0Display information about source selection\0"
+ "selectopts <address|refid> <+|-options>\0Modify selection options\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"
+ "authdata [-a] [-v]\0Display information about authentication\0"
+ "ntpdata [<address>]\0Display information about last valid measurement\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"
+ "maxdelayratio <address> <ratio>\0Modify maximum valid delay/minimum ratio\0"
+ "maxdelaydevratio <address> <ratio>\0Modify maximum valid delay/deviation ratio\0"
+ "minpoll <address> <poll>\0Modify minimum polling interval\0"
+ "maxpoll <address> <poll>\0Modify maximum polling interval\0"
+ "minstratum <address> <stratum>\0Modify minimum stratum\0"
+ "offline [[<mask>/]<address>]\0Set sources in subnet to offline status\0"
+ "online [[<mask>/]<address>]\0Set sources in subnet to online status\0"
+ "onoffline\0Set all sources to online or offline status\0"
+ "\0according to network configuration\0"
+ "polltarget <address> <target>\0Modify poll target\0"
+ "refresh\0Refresh IP addresses\0"
+ "reload sources\0Re-read *.sources files\0"
+ "sourcename <address>\0Display original name\0"
+ "\0\0"
+ "Manual time input:\0\0"
+ "manual off|on|reset\0Disable/enable/reset settime command\0"
+ "manual list\0Show previous settime entries\0"
+ "manual delete <index>\0Delete previous settime entry\0"
+ "settime <time>\0Set daemon time\0"
+ "\0(e.g. Sep 25, 2015 16:30:05 or 16:30:05)\0"
+ "\0\0NTP access:\0\0"
+ "accheck <address>\0Check whether address is allowed\0"
+ "clients [-p <packets>] [-k] [-r]\0Report on clients that accessed the server\0"
+ "serverstats\0Display statistics of the server\0"
+ "allow [<subnet>]\0Allow access to subnet as a default\0"
+ "allow all [<subnet>]\0Allow access to subnet and all children\0"
+ "deny [<subnet>]\0Deny access to subnet as a default\0"
+ "deny all [<subnet>]\0Deny access to subnet and all children\0"
+ "local [options]\0Serve time even when not synchronised\0"
+ "local off\0Don't serve time when not synchronised\0"
+ "smoothtime reset|activate\0Reset/activate time smoothing\0"
+ "smoothing\0Display current time smoothing state\0"
+ "\0\0"
+ "Monitoring access:\0\0"
+ "cmdaccheck <address>\0Check whether address is allowed\0"
+ "cmdallow [<subnet>]\0Allow access to subnet as a default\0"
+ "cmdallow all [<subnet>]\0Allow access to subnet and all children\0"
+ "cmddeny [<subnet>]\0Deny access to subnet as a default\0"
+ "cmddeny all [<subnet>]\0Deny access to subnet and all children\0"
+ "\0\0"
+ "Real-time clock:\0\0"
+ "rtcdata\0Print current RTC performance parameters\0"
+ "trimrtc\0Correct RTC relative to system clock\0"
+ "writertc\0Save RTC performance parameters to file\0"
+ "\0\0"
+ "Other daemon commands:\0\0"
+ "cyclelogs\0Close and re-open log files\0"
+ "dump\0Dump measurements and NTS keys/cookies\0"
+ "rekey\0Re-read keys\0"
+ "reset sources\0Drop all measurements\0"
+ "shutdown\0Stop daemon\0"
+ "\0\0"
+ "Client commands:\0\0"
+ "dns -n|+n\0Disable/enable resolving IP addresses to hostnames\0"
+ "dns -4|-6|-46\0Resolve hostnames only to IPv4/IPv6/both addresses\0"
+ "timeout <milliseconds>\0Set initial response timeout\0"
+ "retries <retries>\0Set maximum number of retries\0"
+ "keygen [<id> [<type> [<bits>]]]\0Generate key for key file\0"
+ "exit|quit\0Leave the program\0"
+ "help\0Generate this help\0"
+ "\0";
+
+ /* Indent the second column */
+ for (s = cols, line = 0; s < cols + sizeof (cols); s += len + 1, line++) {
+ len = strlen(s);
+ printf(line % 2 == 0 ? (len >= 28 ? "%s\n%28s" : "%-28s%s") : "%s%s\n",
+ s, "");
+ }
+}
+
+/* ================================================== */
+/* Tab-completion when editline is available */
+
+#ifdef FEAT_READLINE
+
+enum {
+ TAB_COMPLETE_BASE_CMDS,
+ TAB_COMPLETE_ADD_OPTS,
+ TAB_COMPLETE_MANUAL_OPTS,
+ TAB_COMPLETE_RELOAD_OPTS,
+ TAB_COMPLETE_RESET_OPTS,
+ TAB_COMPLETE_SOURCES_OPTS,
+ TAB_COMPLETE_SOURCESTATS_OPTS,
+ TAB_COMPLETE_AUTHDATA_OPTS,
+ TAB_COMPLETE_SELECTDATA_OPTS,
+ TAB_COMPLETE_MAX_INDEX
+};
+
+static int tab_complete_index;
+
+static char *
+command_name_generator(const char *text, int state)
+{
+ const char *name, **names[TAB_COMPLETE_MAX_INDEX];
+ const char *base_commands[] = {
+ "accheck", "activity", "add", "allow", "authdata", "burst",
+ "clients", "cmdaccheck", "cmdallow", "cmddeny", "cyclelogs", "delete",
+ "deny", "dns", "dump", "exit", "help", "keygen", "local", "makestep",
+ "manual", "maxdelay", "maxdelaydevratio", "maxdelayratio", "maxpoll",
+ "maxupdateskew", "minpoll", "minstratum", "ntpdata", "offline", "online", "onoffline",
+ "polltarget", "quit", "refresh", "rekey", "reload", "reselect", "reselectdist", "reset",
+ "retries", "rtcdata", "selectdata", "selectopts", "serverstats", "settime",
+ "shutdown", "smoothing", "smoothtime", "sourcename", "sources", "sourcestats",
+ "timeout", "tracking", "trimrtc", "waitsync", "writertc",
+ NULL
+ };
+ const char *add_options[] = { "peer", "pool", "server", NULL };
+ const char *manual_options[] = { "on", "off", "delete", "list", "reset", NULL };
+ const char *reset_options[] = { "sources", NULL };
+ const char *reload_options[] = { "sources", NULL };
+ const char *common_source_options[] = { "-a", "-v", NULL };
+ static int list_index, len;
+
+ names[TAB_COMPLETE_BASE_CMDS] = base_commands;
+ names[TAB_COMPLETE_ADD_OPTS] = add_options;
+ names[TAB_COMPLETE_MANUAL_OPTS] = manual_options;
+ names[TAB_COMPLETE_RELOAD_OPTS] = reload_options;
+ names[TAB_COMPLETE_RESET_OPTS] = reset_options;
+ names[TAB_COMPLETE_AUTHDATA_OPTS] = common_source_options;
+ names[TAB_COMPLETE_SELECTDATA_OPTS] = common_source_options;
+ names[TAB_COMPLETE_SOURCES_OPTS] = common_source_options;
+ names[TAB_COMPLETE_SOURCESTATS_OPTS] = common_source_options;
+
+ if (!state) {
+ list_index = 0;
+ len = strlen(text);
+ }
+
+ while ((name = names[tab_complete_index][list_index++])) {
+ if (strncmp(name, text, len) == 0) {
+ return Strdup(name);
+ }
+ }
+
+ return NULL;
+}
+
+/* ================================================== */
+
+static char **
+command_name_completion(const char *text, int start, int end)
+{
+ char first[32];
+
+ snprintf(first, MIN(sizeof (first), start + 1), "%s", rl_line_buffer);
+ rl_attempted_completion_over = 1;
+
+ if (!strcmp(first, "add ")) {
+ tab_complete_index = TAB_COMPLETE_ADD_OPTS;
+ } else if (!strcmp(first, "authdata ")) {
+ tab_complete_index = TAB_COMPLETE_AUTHDATA_OPTS;
+ } else if (!strcmp(first, "manual ")) {
+ tab_complete_index = TAB_COMPLETE_MANUAL_OPTS;
+ } else if (!strcmp(first, "reload ")) {
+ tab_complete_index = TAB_COMPLETE_RELOAD_OPTS;
+ } else if (!strcmp(first, "reset ")) {
+ tab_complete_index = TAB_COMPLETE_RESET_OPTS;
+ } else if (!strcmp(first, "selectdata ")) {
+ tab_complete_index = TAB_COMPLETE_SELECTDATA_OPTS;
+ } else if (!strcmp(first, "sources ")) {
+ tab_complete_index = TAB_COMPLETE_SOURCES_OPTS;
+ } else if (!strcmp(first, "sourcestats ")) {
+ tab_complete_index = TAB_COMPLETE_SOURCESTATS_OPTS;
+ } else if (first[0] == '\0') {
+ tab_complete_index = TAB_COMPLETE_BASE_CMDS;
+ } else {
+ return NULL;
+ }
+
+ return rl_completion_matches(text, command_name_generator);
+}
+#endif
+
+/* ================================================== */
+
+static int max_retries = 2;
+static int initial_timeout = 1000;
+static int proto_version = PROTO_VERSION_NUMBER;
+
+/* This is the core protocol module. Complete particular fields in
+ the outgoing packet, send it, wait for a response, handle retries,
+ etc. Returns a Boolean indicating whether the protocol was
+ successful or not.*/
+
+static int
+submit_request(CMD_Request *request, CMD_Reply *reply)
+{
+ int select_status;
+ int recv_status;
+ int read_length;
+ int command_length;
+ int padding_length;
+ struct timespec ts_now, ts_start;
+ struct timeval tv;
+ int n_attempts, new_attempt;
+ double timeout;
+ fd_set rdfd;
+
+ request->pkt_type = PKT_TYPE_CMD_REQUEST;
+ request->res1 = 0;
+ request->res2 = 0;
+ request->pad1 = 0;
+ request->pad2 = 0;
+
+ n_attempts = 0;
+ new_attempt = 1;
+
+ do {
+ if (gettimeofday(&tv, NULL))
+ return 0;
+
+ if (new_attempt) {
+ new_attempt = 0;
+
+ if (n_attempts > max_retries)
+ return 0;
+
+ UTI_TimevalToTimespec(&tv, &ts_start);
+
+ UTI_GetRandomBytes(&request->sequence, sizeof (request->sequence));
+ request->attempt = htons(n_attempts);
+ request->version = proto_version;
+ command_length = PKL_CommandLength(request);
+ padding_length = PKL_CommandPaddingLength(request);
+ assert(command_length > 0 && command_length > padding_length);
+
+ n_attempts++;
+
+ /* Zero the padding to not send any uninitialized data */
+ memset(((char *)request) + command_length - padding_length, 0, padding_length);
+
+ if (sock_fd < 0) {
+ DEBUG_LOG("No socket to send request");
+ return 0;
+ }
+
+ if (SCK_Send(sock_fd, (void *)request, command_length, 0) < 0)
+ return 0;
+ }
+
+ UTI_TimevalToTimespec(&tv, &ts_now);
+
+ /* Check if the clock wasn't stepped back */
+ if (UTI_CompareTimespecs(&ts_now, &ts_start) < 0)
+ ts_start = ts_now;
+
+ timeout = initial_timeout / 1000.0 * (1U << (n_attempts - 1)) -
+ UTI_DiffTimespecsToDouble(&ts_now, &ts_start);
+ DEBUG_LOG("Timeout %f seconds", timeout);
+
+ /* Avoid calling select() with an invalid timeout */
+ if (timeout <= 0.0) {
+ new_attempt = 1;
+ continue;
+ }
+
+ UTI_DoubleToTimeval(timeout, &tv);
+
+ FD_ZERO(&rdfd);
+ FD_SET(sock_fd, &rdfd);
+
+ if (quit)
+ return 0;
+
+ select_status = select(sock_fd + 1, &rdfd, NULL, NULL, &tv);
+
+ if (select_status < 0) {
+ DEBUG_LOG("select failed : %s", strerror(errno));
+ return 0;
+ } else if (select_status == 0) {
+ /* Timeout must have elapsed, try a resend? */
+ new_attempt = 1;
+ } else {
+ recv_status = SCK_Receive(sock_fd, reply, sizeof (*reply), 0);
+
+ if (recv_status < 0) {
+ new_attempt = 1;
+ } else {
+ read_length = recv_status;
+
+ /* Check if the header is valid */
+ if (read_length < offsetof(CMD_Reply, data) ||
+ (reply->version != proto_version &&
+ !(reply->version >= PROTO_VERSION_MISMATCH_COMPAT_CLIENT &&
+ ntohs(reply->status) == STT_BADPKTVERSION)) ||
+ reply->pkt_type != PKT_TYPE_CMD_REPLY ||
+ reply->res1 != 0 ||
+ reply->res2 != 0 ||
+ reply->command != request->command ||
+ reply->sequence != request->sequence) {
+ DEBUG_LOG("Invalid reply");
+ continue;
+ }
+
+#if PROTO_VERSION_NUMBER == 6
+ /* Protocol version 5 is similar to 6 except there is no padding.
+ If a version 5 reply with STT_BADPKTVERSION is received,
+ switch our version and try again. */
+ if (proto_version == PROTO_VERSION_NUMBER &&
+ reply->version == PROTO_VERSION_NUMBER - 1) {
+ proto_version = PROTO_VERSION_NUMBER - 1;
+ n_attempts--;
+ new_attempt = 1;
+ continue;
+ }
+#else
+#error unknown compatibility with PROTO_VERSION - 1
+#endif
+
+ /* Check that the packet contains all data it is supposed to have.
+ Unknown responses will always pass this test as their expected
+ length is zero. */
+ if (read_length < PKL_ReplyLength(reply)) {
+ DEBUG_LOG("Reply too short");
+ new_attempt = 1;
+ continue;
+ }
+
+ /* Good packet received, print out results */
+ DEBUG_LOG("Reply cmd=%d reply=%d stat=%d",
+ ntohs(reply->command), ntohs(reply->reply), ntohs(reply->status));
+ break;
+ }
+ }
+ } while (1);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+request_reply(CMD_Request *request, CMD_Reply *reply, int requested_reply, int verbose)
+{
+ int status;
+
+ while (!submit_request(request, reply)) {
+ /* Try connecting to other addresses before giving up */
+ if (open_io())
+ continue;
+ printf("506 Cannot talk to daemon\n");
+ return 0;
+ }
+
+ status = ntohs(reply->status);
+
+ if (verbose || status != STT_SUCCESS) {
+ switch (status) {
+ case STT_SUCCESS:
+ printf("200 OK");
+ break;
+ case STT_ACCESSALLOWED:
+ printf("208 Access allowed");
+ break;
+ case STT_ACCESSDENIED:
+ printf("209 Access denied");
+ break;
+ case STT_FAILED:
+ printf("500 Failure");
+ break;
+ case STT_UNAUTH:
+ printf("501 Not authorised");
+ break;
+ case STT_INVALID:
+ printf("502 Invalid command");
+ break;
+ case STT_NOSUCHSOURCE:
+ printf("503 No such source");
+ break;
+ case STT_INVALIDTS:
+ printf("504 Duplicate or stale logon detected");
+ break;
+ case STT_NOTENABLED:
+ printf("505 Facility not enabled in daemon");
+ break;
+ case STT_BADSUBNET:
+ printf("507 Bad subnet");
+ break;
+ case STT_NOHOSTACCESS:
+ printf("510 No command access from this host");
+ break;
+ case STT_SOURCEALREADYKNOWN:
+ printf("511 Source already present");
+ break;
+ case STT_TOOMANYSOURCES:
+ printf("512 Too many sources present");
+ break;
+ case STT_NORTC:
+ printf("513 RTC driver not running");
+ break;
+ case STT_BADRTCFILE:
+ printf("514 Can't write RTC parameters");
+ break;
+ case STT_INVALIDAF:
+ printf("515 Invalid address family");
+ break;
+ case STT_BADSAMPLE:
+ printf("516 Sample index out of range");
+ break;
+ case STT_BADPKTVERSION:
+ printf("517 Protocol version mismatch");
+ break;
+ case STT_BADPKTLENGTH:
+ printf("518 Packet length mismatch");
+ break;
+ 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");
+ }
+ printf("\n");
+ }
+
+ if (status != STT_SUCCESS &&
+ status != STT_ACCESSALLOWED && status != STT_ACCESSDENIED) {
+ return 0;
+ }
+
+ if (ntohs(reply->reply) != requested_reply) {
+ printf("508 Bad reply from daemon\n");
+ return 0;
+ }
+
+ /* Make sure an unknown response was not requested */
+ assert(PKL_ReplyLength(reply));
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+print_seconds(uint32_t s)
+{
+ uint32_t d;
+
+ if (s == (uint32_t)-1) {
+ printf(" -");
+ } else if (s < 1200) {
+ printf("%4"PRIu32, s);
+ } else if (s < 36000) {
+ printf("%3"PRIu32"m", s / 60);
+ } else if (s < 345600) {
+ printf("%3"PRIu32"h", s / 3600);
+ } else {
+ d = s / 86400;
+ if (d > 999) {
+ printf("%3"PRIu32"y", d / 365);
+ } else {
+ printf("%3"PRIu32"d", d);
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+print_nanoseconds(double s)
+{
+ s = fabs(s);
+
+ if (s < 9999.5e-9) {
+ printf("%4.0fns", s * 1e9);
+ } else if (s < 9999.5e-6) {
+ printf("%4.0fus", s * 1e6);
+ } else if (s < 9999.5e-3) {
+ printf("%4.0fms", s * 1e3);
+ } else if (s < 999.5) {
+ printf("%5.1fs", s);
+ } else if (s < 99999.5) {
+ printf("%5.0fs", s);
+ } else if (s < 99999.5 * 60) {
+ printf("%5.0fm", s / 60);
+ } else if (s < 99999.5 * 3600) {
+ printf("%5.0fh", s / 3600);
+ } else if (s < 99999.5 * 3600 * 24) {
+ printf("%5.0fd", s / (3600 * 24));
+ } else {
+ printf("%5.0fy", s / (3600 * 24 * 365));
+ }
+}
+
+/* ================================================== */
+
+static void
+print_signed_nanoseconds(double s)
+{
+ double x;
+
+ x = fabs(s);
+
+ if (x < 9999.5e-9) {
+ printf("%+5.0fns", s * 1e9);
+ } else if (x < 9999.5e-6) {
+ printf("%+5.0fus", s * 1e6);
+ } else if (x < 9999.5e-3) {
+ printf("%+5.0fms", s * 1e3);
+ } else if (x < 999.5) {
+ printf("%+6.1fs", s);
+ } else if (x < 99999.5) {
+ printf("%+6.0fs", s);
+ } else if (x < 99999.5 * 60) {
+ printf("%+6.0fm", s / 60);
+ } else if (x < 99999.5 * 3600) {
+ printf("%+6.0fh", s / 3600);
+ } else if (x < 99999.5 * 3600 * 24) {
+ printf("%+6.0fd", s / (3600 * 24));
+ } else {
+ printf("%+6.0fy", s / (3600 * 24 * 365));
+ }
+}
+
+/* ================================================== */
+
+static void
+print_freq_ppm(double f)
+{
+ if (fabs(f) < 99999.5) {
+ printf("%10.3f", f);
+ } else {
+ printf("%10.0f", f);
+ }
+}
+
+/* ================================================== */
+
+static void
+print_signed_freq_ppm(double f)
+{
+ if (fabs(f) < 99999.5) {
+ printf("%+10.3f", f);
+ } else {
+ printf("%+10.0f", f);
+ }
+}
+
+/* ================================================== */
+
+static void
+print_clientlog_interval(int rate)
+{
+ if (rate >= 127) {
+ printf(" -");
+ } else {
+ printf("%2d", rate);
+ }
+}
+
+/* ================================================== */
+
+static void
+print_header(const char *header)
+{
+ int len;
+
+ if (csv_mode)
+ return;
+
+ printf("%s\n", header);
+
+ len = strlen(header);
+ while (len--)
+ printf("=");
+ printf("\n");
+}
+
+/* ================================================== */
+
+#define REPORT_END 0x1234
+
+/* Print a report. The syntax of the format is similar to printf(), but not all
+ specifiers are supported and some are different! */
+
+static void
+print_report(const char *format, ...)
+{
+ char buf[256];
+ va_list ap;
+ int i, field, sign, width, prec, spec;
+ const char *string;
+ unsigned int uinteger;
+ uint64_t uinteger64;
+ uint32_t uinteger32;
+ int integer;
+ struct timespec *ts;
+ struct tm *tm;
+ double dbl;
+
+ va_start(ap, format);
+
+ for (field = 0; ; field++) {
+ /* Search for text between format specifiers and print it
+ if not in the CSV mode */
+ for (i = 0; i < sizeof (buf) && format[i] != '%' && format[i] != '\0'; i++)
+ buf[i] = format[i];
+
+ if (i >= sizeof (buf))
+ break;
+
+ buf[i] = '\0';
+
+ if (!csv_mode)
+ printf("%s", buf);
+
+ if (format[i] == '\0' || format[i + 1] == '\0')
+ break;
+
+ format += i + 1;
+
+ sign = 0;
+ width = 0;
+ prec = 5;
+
+ if (*format == '+' || *format == '-') {
+ sign = 1;
+ format++;
+ }
+
+ if (isdigit((unsigned char)*format)) {
+ width = atoi(format);
+ while (isdigit((unsigned char)*format))
+ format++;
+ }
+
+ if (*format == '.') {
+ format++;
+ prec = atoi(format);
+ while (isdigit((unsigned char)*format))
+ format++;
+ }
+
+ spec = *format;
+ format++;
+
+ /* Disable human-readable formatting in the CSV mode */
+ if (csv_mode) {
+ sign = width = 0;
+
+ if (field > 0)
+ printf(",");
+
+ switch (spec) {
+ case 'C':
+ spec = 'd';
+ break;
+ case 'F':
+ case 'P':
+ prec = 3;
+ spec = 'f';
+ break;
+ case 'O':
+ case 'S':
+ prec = 9;
+ spec = 'f';
+ break;
+ case 'I':
+ spec = 'U';
+ break;
+ case 'T':
+ spec = 'V';
+ break;
+ }
+ }
+
+ switch (spec) {
+ case 'B': /* boolean */
+ integer = va_arg(ap, int);
+ printf("%s", integer ? "Yes" : "No");
+ break;
+ case 'C': /* clientlog interval */
+ integer = va_arg(ap, int);
+ print_clientlog_interval(integer);
+ break;
+ case 'F': /* absolute frequency in ppm with fast/slow keyword */
+ case 'O': /* absolute offset in seconds with fast/slow keyword */
+ dbl = va_arg(ap, double);
+ printf("%*.*f %s %s", width, prec, fabs(dbl),
+ spec == 'O' ? "seconds" : "ppm",
+ (dbl > 0.0) ^ (spec != 'O') ? "slow" : "fast");
+ break;
+ case 'I': /* uint32_t interval with unit */
+ uinteger32 = va_arg(ap, uint32_t);
+ print_seconds(uinteger32);
+ break;
+ case 'L': /* leap status */
+ integer = va_arg(ap, int);
+ switch (integer) {
+ case LEAP_Normal:
+ string = width != 1 ? "Normal" : "N";
+ break;
+ case LEAP_InsertSecond:
+ string = width != 1 ? "Insert second" : "+";
+ break;
+ case LEAP_DeleteSecond:
+ string = width != 1 ? "Delete second" : "-";
+ break;
+ case LEAP_Unsynchronised:
+ string = width != 1 ? "Not synchronised" : "?";
+ break;
+ default:
+ string = width != 1 ? "Invalid" : "?";
+ break;
+ }
+ printf("%s", string);
+ break;
+ case 'M': /* NTP mode */
+ integer = va_arg(ap, int);
+ switch (integer) {
+ case MODE_ACTIVE:
+ string = "Symmetric active";
+ break;
+ case MODE_PASSIVE:
+ string = "Symmetric passive";
+ break;
+ case MODE_SERVER:
+ string = "Server";
+ break;
+ default:
+ string = "Invalid";
+ break;
+ }
+ printf("%s", string);
+ break;
+ case 'N': /* Timestamp source */
+ integer = va_arg(ap, int);
+ switch (integer) {
+ case 'D':
+ string = "Daemon";
+ break;
+ case 'K':
+ string = "Kernel";
+ break;
+ case 'H':
+ string = "Hardware";
+ break;
+ default:
+ string = "Invalid";
+ break;
+ }
+ printf("%s", string);
+ break;
+ case 'P': /* frequency in ppm */
+ dbl = va_arg(ap, double);
+ if (sign)
+ print_signed_freq_ppm(dbl);
+ else
+ print_freq_ppm(dbl);
+ break;
+ case 'R': /* reference ID in hexdecimal */
+ uinteger32 = va_arg(ap, uint32_t);
+ printf("%08"PRIX32, uinteger32);
+ break;
+ case 'S': /* offset with unit */
+ dbl = va_arg(ap, double);
+ if (sign)
+ print_signed_nanoseconds(dbl);
+ else
+ print_nanoseconds(dbl);
+ break;
+ case 'T': /* timespec as date and time in UTC */
+ ts = va_arg(ap, struct timespec *);
+ tm = gmtime(&ts->tv_sec);
+ if (!tm)
+ break;
+ strftime(buf, sizeof (buf), "%a %b %d %T %Y", tm);
+ printf("%s", buf);
+ break;
+ case 'U': /* uint32_t in decimal */
+ uinteger32 = va_arg(ap, uint32_t);
+ printf("%*"PRIu32, width, uinteger32);
+ break;
+ case 'V': /* timespec as seconds since epoch */
+ ts = va_arg(ap, struct timespec *);
+ printf("%s", UTI_TimespecToString(ts));
+ break;
+ case 'Q': /* uint64_t in decimal */
+ uinteger64 = va_arg(ap, uint64_t);
+ printf("%*"PRIu64, width, uinteger64);
+ break;
+ case 'b': /* unsigned int in binary */
+ uinteger = va_arg(ap, unsigned int);
+ for (i = prec - 1; i >= 0; i--)
+ printf("%c", uinteger & 1U << i ? '1' : '0');
+ break;
+
+ /* Classic printf specifiers */
+ case 'c': /* character */
+ integer = va_arg(ap, int);
+ printf("%c", integer);
+ break;
+ case 'd': /* signed int in decimal */
+ integer = va_arg(ap, int);
+ printf("%*d", width, integer);
+ break;
+ case 'f': /* double */
+ dbl = va_arg(ap, double);
+ printf(sign ? "%+*.*f" : "%*.*f", width, prec, dbl);
+ break;
+ case 'o': /* unsigned int in octal */
+ uinteger = va_arg(ap, unsigned int);
+ printf("%*o", width, uinteger);
+ break;
+ case 's': /* string */
+ string = va_arg(ap, const char *);
+ if (sign)
+ printf("%-*s", width, string);
+ else
+ printf("%*s", width, string);
+ break;
+ case 'u': /* unsigned int in decimal */
+ uinteger = va_arg(ap, unsigned int);
+ printf("%*u", width, uinteger);
+ break;
+ }
+ }
+
+ /* Require terminating argument to catch bad type conversions */
+ if (va_arg(ap, int) != REPORT_END)
+ assert(0);
+
+ va_end(ap);
+
+ if (csv_mode)
+ printf("\n");
+}
+
+/* ================================================== */
+
+static void
+print_info_field(const char *format, ...)
+{
+ va_list ap;
+
+ if (csv_mode)
+ return;
+
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+}
+
+/* ================================================== */
+
+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", (char *)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((unsigned char)buf[i]))
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+format_name(char *buf, int size, int trunc_dns, int ref, uint32_t ref_id,
+ 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 {
+ DNS_IPAddress2Name(ip_addr, buf, size);
+ if (trunc_dns > 0 && strlen(buf) > trunc_dns) {
+ buf[trunc_dns - 1] = '>';
+ buf[trunc_dns] = '\0';
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_sources_options(char *line, int *all, int *verbose)
+{
+ 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 (!parse_source_address(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;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_sources(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t i, mode, n_sources;
+ char name[256], mode_ch, state_ch;
+ int all, verbose, ref;
+
+ 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);
+
+ if (verbose) {
+ printf("\n");
+ printf(" .-- Source mode '^' = server, '=' = peer, '#' = local clock.\n");
+ printf(" / .- Source state '*' = current best, '+' = combined, '-' = not combined,\n");
+ printf("| / 'x' = may be in error, '~' = too variable, '?' = unusable.\n");
+ printf("|| .- xxxx [ yyyy ] +/- zzzz\n");
+ printf("|| Reachability register (octal) -. | xxxx = adjusted offset,\n");
+ printf("|| Log2(Polling interval) --. | | yyyy = measured offset,\n");
+ printf("|| \\ | | zzzz = estimated error.\n");
+ printf("|| | | \\\n");
+ }
+
+ print_header("MS Name/IP address Stratum Poll Reach LastRx Last sample ");
+
+ /* "MS NNNNNNNNNNNNNNNNNNNNNNNNNNN SS PP RRR RRRR SSSSSSS[SSSSSSS] +/- SSSSSS" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SOURCE_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
+ return 0;
+
+ 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;
+
+ ref = mode == RPY_SD_MD_REF && ip_addr.family == IPADDR_INET4;
+ format_name(name, sizeof (name), 25, ref, ref ? ip_addr.addr.in4 : 0, 1, &ip_addr);
+
+ switch (mode) {
+ case RPY_SD_MD_CLIENT:
+ mode_ch = '^';
+ break;
+ case RPY_SD_MD_PEER:
+ mode_ch = '=';
+ break;
+ case RPY_SD_MD_REF:
+ mode_ch = '#';
+ break;
+ default:
+ mode_ch = ' ';
+ }
+
+ switch (ntohs(reply.data.source_data.state)) {
+ case RPY_SD_ST_SELECTED:
+ state_ch = '*';
+ break;
+ case RPY_SD_ST_NONSELECTABLE:
+ state_ch = '?';
+ break;
+ case RPY_SD_ST_FALSETICKER:
+ state_ch = 'x';
+ break;
+ case RPY_SD_ST_JITTERY:
+ state_ch = '~';
+ break;
+ case RPY_SD_ST_UNSELECTED:
+ state_ch = '+';
+ break;
+ case RPY_SD_ST_SELECTABLE:
+ state_ch = '-';
+ break;
+ default:
+ state_ch = ' ';
+ }
+
+ switch (ntohs(reply.data.source_data.flags)) {
+ default:
+ break;
+ }
+
+ print_report("%c%c %-27s %2d %2d %3o %I %+S[%+S] +/- %S\n",
+ mode_ch, state_ch, name,
+ ntohs(reply.data.source_data.stratum),
+ (int16_t)ntohs(reply.data.source_data.poll),
+ ntohs(reply.data.source_data.reachability),
+ ntohl(reply.data.source_data.since_sample),
+ UTI_FloatNetworkToHost(reply.data.source_data.latest_meas),
+ UTI_FloatNetworkToHost(reply.data.source_data.orig_latest_meas),
+ UTI_FloatNetworkToHost(reply.data.source_data.latest_meas_err),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_sourcestats(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t i, n_sources;
+ int all, verbose;
+ char name[256];
+ IPAddr ip_addr;
+
+ 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);
+
+ if (verbose) {
+ printf(" .- Number of sample points in measurement set.\n");
+ printf(" / .- Number of residual runs with same sign.\n");
+ printf(" | / .- Length of measurement set (time).\n");
+ printf(" | | / .- Est. clock freq error (ppm).\n");
+ printf(" | | | / .- Est. error in freq.\n");
+ printf(" | | | | / .- Est. offset.\n");
+ printf(" | | | | | | On the -.\n");
+ printf(" | | | | | | samples. \\\n");
+ printf(" | | | | | | |\n");
+ }
+
+ print_header("Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev");
+
+ /* "NNNNNNNNNNNNNNNNNNNNNNNNN NP NR SSSS FFFFFFFFFF SSSSSSSSSS SSSSSSS SSSSSS" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SOURCESTATS);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCESTATS, 0))
+ 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), 1, &ip_addr);
+
+ print_report("%-25s %3U %3U %I %+P %P %+S %S\n",
+ name,
+ ntohl(reply.data.sourcestats.n_samples),
+ ntohl(reply.data.sourcestats.n_runs),
+ ntohl(reply.data.sourcestats.span_seconds),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.resid_freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.skew_ppm),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.est_offset),
+ UTI_FloatNetworkToHost(reply.data.sourcestats.sd),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_tracking(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t ref_id;
+ char name[256];
+ struct timespec ref_time;
+
+ request.command = htons(REQ_TRACKING);
+ if (!request_reply(&request, &reply, RPY_TRACKING, 0))
+ return 0;
+
+ ref_id = ntohl(reply.data.tracking.ref_id);
+
+ UTI_IPNetworkToHost(&reply.data.tracking.ip_addr, &ip_addr);
+ format_name(name, sizeof (name), sizeof (name),
+ ip_addr.family == IPADDR_UNSPEC, ref_id, 1, &ip_addr);
+
+ UTI_TimespecNetworkToHost(&reply.data.tracking.ref_time, &ref_time);
+
+ print_report("Reference ID : %R (%s)\n"
+ "Stratum : %u\n"
+ "Ref time (UTC) : %T\n"
+ "System time : %.9O of NTP time\n"
+ "Last offset : %+.9f seconds\n"
+ "RMS offset : %.9f seconds\n"
+ "Frequency : %.3F\n"
+ "Residual freq : %+.3f ppm\n"
+ "Skew : %.3f ppm\n"
+ "Root delay : %.9f seconds\n"
+ "Root dispersion : %.9f seconds\n"
+ "Update interval : %.1f seconds\n"
+ "Leap status : %L\n",
+ ref_id, name,
+ ntohs(reply.data.tracking.stratum),
+ &ref_time,
+ UTI_FloatNetworkToHost(reply.data.tracking.current_correction),
+ UTI_FloatNetworkToHost(reply.data.tracking.last_offset),
+ UTI_FloatNetworkToHost(reply.data.tracking.rms_offset),
+ UTI_FloatNetworkToHost(reply.data.tracking.freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.tracking.resid_freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.tracking.skew_ppm),
+ UTI_FloatNetworkToHost(reply.data.tracking.root_delay),
+ UTI_FloatNetworkToHost(reply.data.tracking.root_dispersion),
+ UTI_FloatNetworkToHost(reply.data.tracking.last_update_interval),
+ ntohs(reply.data.tracking.leap_status), REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_authdata(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t i, source_mode, n_sources;
+ int all, verbose;
+ const char *mode_str;
+ char name[256];
+
+ 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);
+
+ if (verbose) {
+ printf( " .- Auth. mechanism (NTS, SK - symmetric key)\n");
+ printf( " | Key length -. Cookie length (bytes) -.\n");
+ printf( " | (bits) | Num. of cookies --. |\n");
+ printf( " | | Key est. attempts | |\n");
+ printf( " | | | | |\n");
+ }
+
+ print_header("Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen");
+
+ /* "NNNNNNNNNNNNNNNNNNNNNNNNNNN MMMM KKKKK AAAA LLLL LLLL AAAA NNNN CCCC LLLL" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SOURCE_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
+ return 0;
+
+ source_mode = ntohs(reply.data.source_data.mode);
+ if (source_mode != RPY_SD_MD_CLIENT && source_mode != RPY_SD_MD_PEER)
+ continue;
+
+ UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &ip_addr);
+ if (!all && ip_addr.family == IPADDR_ID)
+ continue;
+
+ request.command = htons(REQ_AUTH_DATA);
+ request.data.auth_data.ip_addr = reply.data.source_data.ip_addr;
+ if (!request_reply(&request, &reply, RPY_AUTH_DATA, 0))
+ return 0;
+
+ format_name(name, sizeof (name), 25, 0, 0, 1, &ip_addr);
+
+ switch (ntohs(reply.data.auth_data.mode)) {
+ case RPY_AD_MD_NONE:
+ mode_str = "-";
+ break;
+ case RPY_AD_MD_SYMMETRIC:
+ mode_str = "SK";
+ break;
+ case RPY_AD_MD_NTS:
+ mode_str = "NTS";
+ break;
+ default:
+ mode_str = "?";
+ break;
+ }
+
+ print_report("%-27s %4s %5U %4d %4d %I %4d %4d %4d %4d\n",
+ name, mode_str,
+ ntohl(reply.data.auth_data.key_id),
+ ntohs(reply.data.auth_data.key_type),
+ ntohs(reply.data.auth_data.key_length),
+ ntohl(reply.data.auth_data.last_ke_ago),
+ ntohs(reply.data.auth_data.ke_attempts),
+ ntohs(reply.data.auth_data.nak),
+ ntohs(reply.data.auth_data.cookies),
+ ntohs(reply.data.auth_data.cookie_length),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_ntpdata(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr remote_addr, local_addr;
+ struct timespec ref_time;
+ uint32_t i, n_sources;
+ uint16_t mode;
+ int specified_addr;
+
+ if (*line) {
+ specified_addr = 1;
+ n_sources = 1;
+ } else {
+ specified_addr = 0;
+ 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);
+ }
+
+ for (i = 0; i < n_sources; i++) {
+ if (specified_addr) {
+ if (!parse_source_address(line, &remote_addr)) {
+ LOG(LOGS_ERR, "Could not get address for hostname");
+ return 0;
+ }
+ } else {
+ request.command = htons(REQ_SOURCE_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SOURCE_DATA, 0))
+ return 0;
+
+ mode = ntohs(reply.data.source_data.mode);
+ if (mode != RPY_SD_MD_CLIENT && mode != RPY_SD_MD_PEER)
+ continue;
+
+ UTI_IPNetworkToHost(&reply.data.source_data.ip_addr, &remote_addr);
+ if (!UTI_IsIPReal(&remote_addr))
+ continue;
+ }
+
+ request.command = htons(REQ_NTP_DATA);
+ UTI_IPHostToNetwork(&remote_addr, &request.data.ntp_data.ip_addr);
+ if (!request_reply(&request, &reply, RPY_NTP_DATA, 0))
+ return 0;
+
+ UTI_IPNetworkToHost(&reply.data.ntp_data.remote_addr, &remote_addr);
+ UTI_IPNetworkToHost(&reply.data.ntp_data.local_addr, &local_addr);
+ UTI_TimespecNetworkToHost(&reply.data.ntp_data.ref_time, &ref_time);
+
+ if (!specified_addr && !csv_mode)
+ printf("\n");
+
+ print_report("Remote address : %s (%R)\n"
+ "Remote port : %u\n"
+ "Local address : %s (%R)\n"
+ "Leap status : %L\n"
+ "Version : %u\n"
+ "Mode : %M\n"
+ "Stratum : %u\n"
+ "Poll interval : %d (%.0f seconds)\n"
+ "Precision : %d (%.9f seconds)\n"
+ "Root delay : %.6f seconds\n"
+ "Root dispersion : %.6f seconds\n"
+ "Reference ID : %R (%s)\n"
+ "Reference time : %T\n"
+ "Offset : %+.9f seconds\n"
+ "Peer delay : %.9f seconds\n"
+ "Peer dispersion : %.9f seconds\n"
+ "Response time : %.9f seconds\n"
+ "Jitter asymmetry: %+.2f\n"
+ "NTP tests : %.3b %.3b %.4b\n"
+ "Interleaved : %B\n"
+ "Authenticated : %B\n"
+ "TX timestamping : %N\n"
+ "RX timestamping : %N\n"
+ "Total TX : %U\n"
+ "Total RX : %U\n"
+ "Total valid RX : %U\n"
+ "Total good RX : %U\n",
+ UTI_IPToString(&remote_addr), UTI_IPToRefid(&remote_addr),
+ ntohs(reply.data.ntp_data.remote_port),
+ UTI_IPToString(&local_addr), UTI_IPToRefid(&local_addr),
+ reply.data.ntp_data.leap, reply.data.ntp_data.version,
+ reply.data.ntp_data.mode, reply.data.ntp_data.stratum,
+ reply.data.ntp_data.poll, UTI_Log2ToDouble(reply.data.ntp_data.poll),
+ reply.data.ntp_data.precision, UTI_Log2ToDouble(reply.data.ntp_data.precision),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.root_delay),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.root_dispersion),
+ ntohl(reply.data.ntp_data.ref_id), reply.data.ntp_data.stratum <= 1 ?
+ UTI_RefidToString(ntohl(reply.data.ntp_data.ref_id)) : "",
+ &ref_time,
+ UTI_FloatNetworkToHost(reply.data.ntp_data.offset),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.peer_delay),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.peer_dispersion),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.response_time),
+ UTI_FloatNetworkToHost(reply.data.ntp_data.jitter_asymmetry),
+ ntohs(reply.data.ntp_data.flags) >> 7,
+ ntohs(reply.data.ntp_data.flags) >> 4,
+ ntohs(reply.data.ntp_data.flags),
+ ntohs(reply.data.ntp_data.flags) & RPY_NTP_FLAG_INTERLEAVED,
+ ntohs(reply.data.ntp_data.flags) & RPY_NTP_FLAG_AUTHENTICATED,
+ reply.data.ntp_data.tx_tss_char, reply.data.ntp_data.rx_tss_char,
+ ntohl(reply.data.ntp_data.total_tx_count),
+ ntohl(reply.data.ntp_data.total_rx_count),
+ ntohl(reply.data.ntp_data.total_valid_count),
+ ntohl(reply.data.ntp_data.total_good_count),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_selectdata(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t i, n_sources;
+ int all, verbose, conf_options, eff_options;
+ char name[256];
+ IPAddr ip_addr;
+
+ 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);
+
+ if (verbose) {
+ printf( " . State: N - noselect, s - unsynchronised, M - missing samples,\n");
+ printf( " / d/D - large distance, ~ - jittery, w/W - waits for others,\n");
+ printf( "| S - stale, O - orphan, T - not trusted, P - not preferred,\n");
+ printf( "| U - waits for update,, x - falseticker, + - combined, * - best.\n");
+ printf( "| Effective options ---------. (N - noselect, P - prefer\n");
+ printf( "| Configured options ----. \\ T - trust, R - require)\n");
+ printf( "| Auth. enabled (Y/N) -. \\ \\ Offset interval --.\n");
+ printf( "| | | | |\n");
+ }
+
+ print_header("S Name/IP Address Auth COpts EOpts Last Score Interval Leap");
+
+ /* "S NNNNNNNNNNNNNNNNNNNNNNNNN A OOOO- OOOO- LLLL SSSSS IIIIIII IIIIIII L" */
+
+ for (i = 0; i < n_sources; i++) {
+ request.command = htons(REQ_SELECT_DATA);
+ request.data.source_data.index = htonl(i);
+ if (!request_reply(&request, &reply, RPY_SELECT_DATA, 0))
+ return 0;
+
+ UTI_IPNetworkToHost(&reply.data.select_data.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.select_data.ref_id), 1, &ip_addr);
+
+ conf_options = ntohs(reply.data.select_data.conf_options);
+ eff_options = ntohs(reply.data.select_data.eff_options);
+
+ print_report("%c %-25s %c %c%c%c%c%c %c%c%c%c%c %I %5.1f %+S %+S %1L\n",
+ reply.data.select_data.state_char,
+ name,
+ reply.data.select_data.authentication ? 'Y' : 'N',
+ conf_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
+ conf_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
+ conf_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
+ conf_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
+ '-',
+ eff_options & RPY_SD_OPTION_NOSELECT ? 'N' : '-',
+ eff_options & RPY_SD_OPTION_PREFER ? 'P' : '-',
+ eff_options & RPY_SD_OPTION_TRUST ? 'T' : '-',
+ eff_options & RPY_SD_OPTION_REQUIRE ? 'R' : '-',
+ '-',
+ ntohl(reply.data.select_data.last_sample_ago),
+ UTI_FloatNetworkToHost(reply.data.select_data.score),
+ UTI_FloatNetworkToHost(reply.data.select_data.lo_limit),
+ UTI_FloatNetworkToHost(reply.data.select_data.hi_limit),
+ reply.data.select_data.leap,
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_serverstats(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+
+ request.command = htons(REQ_SERVER_STATS);
+ if (!request_reply(&request, &reply, RPY_SERVER_STATS4, 0))
+ return 0;
+
+ print_report("NTP packets received : %Q\n"
+ "NTP packets dropped : %Q\n"
+ "Command packets received : %Q\n"
+ "Command packets dropped : %Q\n"
+ "Client log records dropped : %Q\n"
+ "NTS-KE connections accepted: %Q\n"
+ "NTS-KE connections dropped : %Q\n"
+ "Authenticated NTP packets : %Q\n"
+ "Interleaved NTP packets : %Q\n"
+ "NTP timestamps held : %Q\n"
+ "NTP timestamp span : %Q\n"
+ "NTP daemon RX timestamps : %Q\n"
+ "NTP daemon TX timestamps : %Q\n"
+ "NTP kernel RX timestamps : %Q\n"
+ "NTP kernel TX timestamps : %Q\n"
+ "NTP hardware RX timestamps : %Q\n"
+ "NTP hardware TX timestamps : %Q\n",
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.cmd_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.cmd_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.log_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.nke_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.nke_drops),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_auth_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_interleaved_hits),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_span_seconds),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_daemon_rx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_daemon_tx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_kernel_rx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_kernel_tx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hw_rx_timestamps),
+ UTI_Integer64NetworkToHost(reply.data.server_stats.ntp_hw_tx_timestamps),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_smoothing(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t flags;
+
+ request.command = htons(REQ_SMOOTHING);
+ if (!request_reply(&request, &reply, RPY_SMOOTHING, 0))
+ return 0;
+
+ flags = ntohl(reply.data.smoothing.flags);
+
+ print_report("Active : %B %s\n"
+ "Offset : %+.9f seconds\n"
+ "Frequency : %+.6f ppm\n"
+ "Wander : %+.6f ppm per second\n"
+ "Last update : %.1f seconds ago\n"
+ "Remaining time : %.1f seconds\n",
+ !!(flags & RPY_SMT_FLAG_ACTIVE),
+ flags & RPY_SMT_FLAG_LEAPONLY ? "(leap second only)" : "",
+ UTI_FloatNetworkToHost(reply.data.smoothing.offset),
+ UTI_FloatNetworkToHost(reply.data.smoothing.freq_ppm),
+ UTI_FloatNetworkToHost(reply.data.smoothing.wander_ppm),
+ UTI_FloatNetworkToHost(reply.data.smoothing.last_update_ago),
+ UTI_FloatNetworkToHost(reply.data.smoothing.remaining_time),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_smoothtime(CMD_Request *msg, const char *line)
+{
+ if (!strcmp(line, "reset")) {
+ msg->data.smoothtime.option = htonl(REQ_SMOOTHTIME_RESET);
+ } else if (!strcmp(line, "activate")) {
+ msg->data.smoothtime.option = htonl(REQ_SMOOTHTIME_ACTIVATE);
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for smoothtime command");
+ return 0;
+ }
+
+ msg->command = htons(REQ_SMOOTHTIME);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_rtcreport(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ struct timespec ref_time;
+
+ request.command = htons(REQ_RTCREPORT);
+ if (!request_reply(&request, &reply, RPY_RTC, 0))
+ return 0;
+
+ UTI_TimespecNetworkToHost(&reply.data.rtc.ref_time, &ref_time);
+
+ print_report("RTC ref time (UTC) : %T\n"
+ "Number of samples : %u\n"
+ "Number of runs : %u\n"
+ "Sample span period : %I\n"
+ "RTC is fast by : %12.6f seconds\n"
+ "RTC gains time at : %9.3f ppm\n",
+ &ref_time,
+ ntohs(reply.data.rtc.n_samples),
+ ntohs(reply.data.rtc.n_runs),
+ ntohl(reply.data.rtc.span_seconds),
+ UTI_FloatNetworkToHost(reply.data.rtc.rtc_seconds_fast),
+ UTI_FloatNetworkToHost(reply.data.rtc.rtc_gain_rate_ppm),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_clients(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip;
+ uint32_t i, n_clients, next_index, n_indices, min_hits, reset;
+ RPY_ClientAccesses_Client *client;
+ char header[80], name[50], *opt, *arg;
+ int nke;
+
+ next_index = 0;
+ min_hits = 0;
+ reset = 0;
+ nke = 0;
+
+ while (*line) {
+ opt = line;
+ line = CPS_SplitWord(line);
+ if (strcmp(opt, "-k") == 0) {
+ nke = 1;
+ } else if (strcmp(opt, "-p") == 0) {
+ arg = line;
+ line = CPS_SplitWord(line);
+ if (sscanf(arg, "%"SCNu32, &min_hits) != 1) {
+ LOG(LOGS_ERR, "Invalid syntax for clients command");
+ return 0;
+ }
+ } else if (strcmp(opt, "-r") == 0) {
+ reset = 1;
+ }
+ }
+
+ snprintf(header, sizeof (header),
+ "Hostname NTP Drop Int IntL Last %6s Drop Int Last",
+ nke ? "NTS-KE" : "Cmd");
+ print_header(header);
+
+ while (1) {
+ request.command = htons(REQ_CLIENT_ACCESSES_BY_INDEX3);
+ request.data.client_accesses_by_index.first_index = htonl(next_index);
+ request.data.client_accesses_by_index.n_clients = htonl(MAX_CLIENT_ACCESSES);
+ request.data.client_accesses_by_index.min_hits = htonl(min_hits);
+ request.data.client_accesses_by_index.reset = htonl(reset);
+
+ if (!request_reply(&request, &reply, RPY_CLIENT_ACCESSES_BY_INDEX3, 0))
+ return 0;
+
+ n_clients = ntohl(reply.data.client_accesses_by_index.n_clients);
+ n_indices = ntohl(reply.data.client_accesses_by_index.n_indices);
+
+ for (i = 0; i < n_clients && i < MAX_CLIENT_ACCESSES; i++) {
+ client = &reply.data.client_accesses_by_index.clients[i];
+
+ UTI_IPNetworkToHost(&client->ip, &ip);
+
+ /* UNSPEC means the record could not be found in the daemon's tables.
+ We shouldn't ever generate this case, but ignore it if we do. */
+ if (ip.family == IPADDR_UNSPEC)
+ continue;
+
+ format_name(name, sizeof (name), 25, 0, 0, 0, &ip);
+
+ print_report("%-25s %6U %5U %C %C %I %6U %5U %C %I\n",
+ name,
+ ntohl(client->ntp_hits),
+ ntohl(client->ntp_drops),
+ client->ntp_interval,
+ client->ntp_timeout_interval,
+ ntohl(client->last_ntp_hit_ago),
+ ntohl(nke ? client->nke_hits : client->cmd_hits),
+ ntohl(nke ? client->nke_drops : client->cmd_drops),
+ nke ? client->nke_interval : client->cmd_interval,
+ ntohl(nke ? client->last_nke_hit_ago : client->last_cmd_hit_ago),
+ REPORT_END);
+ }
+
+ /* Set the next index to probe based on what the server tells us */
+ next_index = ntohl(reply.data.client_accesses_by_index.next_index);
+
+ if (next_index >= n_indices || n_clients < MAX_CLIENT_ACCESSES)
+ break;
+ }
+
+ return 1;
+}
+
+
+/* ================================================== */
+/* Process the manual list command */
+static int
+process_cmd_manual_list(const char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ uint32_t i, n_samples;
+ RPY_ManualListSample *sample;
+ struct timespec when;
+
+ request.command = htons(REQ_MANUAL_LIST);
+ if (!request_reply(&request, &reply, RPY_MANUAL_LIST2, 0))
+ return 0;
+
+ n_samples = ntohl(reply.data.manual_list.n_samples);
+ print_info_field("210 n_samples = %"PRIu32"\n", n_samples);
+
+ print_header("# Date Time(UTC) Slewed Original Residual");
+
+ for (i = 0; i < n_samples && i < MAX_MANUAL_LIST_SAMPLES; i++) {
+ sample = &reply.data.manual_list.samples[i];
+ UTI_TimespecNetworkToHost(&sample->when, &when);
+
+ print_report("%2d %s %10.2f %10.2f %10.2f\n",
+ i, UTI_TimeToLogForm(when.tv_sec),
+ UTI_FloatNetworkToHost(sample->slewed_offset),
+ UTI_FloatNetworkToHost(sample->orig_offset),
+ UTI_FloatNetworkToHost(sample->residual),
+ REPORT_END);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_manual_delete(CMD_Request *msg, const char *line)
+{
+ int index;
+
+ if (sscanf(line, "%d", &index) != 1) {
+ LOG(LOGS_ERR, "Bad syntax for manual delete command");
+ return 0;
+ }
+
+ msg->command = htons(REQ_MANUAL_DELETE);
+ msg->data.manual_delete.index = htonl(index);
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_settime(char *line)
+{
+ struct timespec ts;
+ time_t now, new_time;
+ CMD_Request request;
+ CMD_Reply reply;
+ double dfreq_ppm, new_afreq_ppm;
+ double offset;
+
+ now = time(NULL);
+ new_time = get_date(line, &now);
+
+ if (new_time == -1) {
+ printf("510 - Could not parse date string\n");
+ } else {
+ ts.tv_sec = new_time;
+ ts.tv_nsec = 0;
+ UTI_TimespecHostToNetwork(&ts, &request.data.settime.ts);
+ request.command = htons(REQ_SETTIME);
+ if (request_reply(&request, &reply, RPY_MANUAL_TIMESTAMP2, 1)) {
+ offset = UTI_FloatNetworkToHost(reply.data.manual_timestamp.offset);
+ dfreq_ppm = UTI_FloatNetworkToHost(reply.data.manual_timestamp.dfreq_ppm);
+ new_afreq_ppm = UTI_FloatNetworkToHost(reply.data.manual_timestamp.new_afreq_ppm);
+ printf("Clock was %.2f seconds fast. Frequency change = %.2fppm, new frequency = %.2fppm\n",
+ offset, dfreq_ppm, new_afreq_ppm);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* ================================================== */
+
+static void
+process_cmd_rekey(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_REKEY);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_makestep(CMD_Request *msg, char *line)
+{
+ int limit;
+ double threshold;
+
+ if (*line) {
+ if (sscanf(line, "%lf %d", &threshold, &limit) != 2) {
+ LOG(LOGS_ERR, "Bad syntax for makestep command");
+ return 0;
+ }
+ msg->command = htons(REQ_MODIFY_MAKESTEP);
+ msg->data.modify_makestep.limit = htonl(limit);
+ msg->data.modify_makestep.threshold = UTI_FloatHostToNetwork(threshold);
+ } else {
+ msg->command = htons(REQ_MAKESTEP);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_activity(const char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+
+ request.command = htons(REQ_ACTIVITY);
+ if (!request_reply(&request, &reply, RPY_ACTIVITY, 0))
+ return 0;
+
+ print_info_field("200 OK\n");
+
+ print_report("%U sources online\n"
+ "%U sources offline\n"
+ "%U sources doing burst (return to online)\n"
+ "%U sources doing burst (return to offline)\n"
+ "%U sources with unknown address\n",
+ ntohl(reply.data.activity.online),
+ ntohl(reply.data.activity.offline),
+ ntohl(reply.data.activity.burst_online),
+ ntohl(reply.data.activity.burst_offline),
+ ntohl(reply.data.activity.unresolved),
+ REPORT_END);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_reselectdist(CMD_Request *msg, char *line)
+{
+ double dist;
+ int ok;
+ msg->command = htons(REQ_RESELECTDISTANCE);
+ if (sscanf(line, "%lf", &dist) == 1) {
+ msg->data.reselect_distance.distance = UTI_FloatHostToNetwork(dist);
+ ok = 1;
+ } else {
+ ok = 0;
+ }
+ return ok;
+}
+
+/* ================================================== */
+
+static void
+process_cmd_reselect(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_RESELECT);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_refresh(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_REFRESH);
+}
+
+/* ================================================== */
+
+static void
+process_cmd_shutdown(CMD_Request *msg, char *line)
+{
+ msg->command = htons(REQ_SHUTDOWN);
+}
+
+/* ================================================== */
+
+static int
+process_cmd_reload(CMD_Request *msg, char *line)
+{
+ if (!strcmp(line, "sources")) {
+ msg->command = htons(REQ_RELOAD_SOURCES);
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for reload command");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_reset(CMD_Request *msg, char *line)
+{
+ if (!strcmp(line, "sources")) {
+ msg->command = htons(REQ_RESET_SOURCES);
+ } else {
+ LOG(LOGS_ERR, "Invalid syntax for reset command");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_selectopts(CMD_Request *msg, char *line)
+{
+ int mask, options, option;
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ char *src, *opt;
+
+ src = line;
+ line = CPS_SplitWord(line);
+ ref_id = 0;
+
+ /* Don't allow hostnames to avoid conflicts with reference IDs */
+ if (!UTI_StringToIdIP(src, &ip_addr) && !UTI_StringToIP(src, &ip_addr)) {
+ ip_addr.family = IPADDR_UNSPEC;
+ if (CPS_ParseRefid(src, &ref_id) == 0) {
+ LOG(LOGS_ERR, "Invalid syntax for selectopts command");
+ return 0;
+ }
+ }
+
+ mask = options = 0;
+
+ while (*line != '\0') {
+ opt = line;
+ line = CPS_SplitWord(line);
+
+ if ((opt[0] != '+' && opt[0] != '-') || (option = CPS_GetSelectOption(opt + 1)) == 0) {
+ LOG(LOGS_ERR, "Invalid syntax for selectopts command");
+ return 0;
+ }
+
+ mask |= option;
+ if (opt[0] == '+')
+ options |= option;
+ }
+
+ UTI_IPHostToNetwork(&ip_addr, &msg->data.modify_select_opts.address);
+ msg->data.modify_select_opts.ref_id = htonl(ref_id);
+ msg->data.modify_select_opts.mask = htonl(mask);
+ msg->data.modify_select_opts.options = htonl(convert_addsrc_sel_options(options));
+
+ msg->command = htons(REQ_MODIFY_SELECTOPTS);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_waitsync(char *line)
+{
+ CMD_Request request;
+ CMD_Reply reply;
+ IPAddr ip_addr;
+ uint32_t ref_id;
+ double correction, skew_ppm, max_correction, max_skew_ppm, interval;
+ int ret = 0, max_tries, i;
+ struct timeval timeout;
+
+ max_tries = 0;
+ max_correction = 0.0;
+ max_skew_ppm = 0.0;
+ interval = 10.0;
+
+ if (sscanf(line, "%d %lf %lf %lf", &max_tries, &max_correction, &max_skew_ppm, &interval))
+ ;
+
+ /* Don't allow shorter interval than 0.1 seconds */
+ if (interval < 0.1)
+ interval = 0.1;
+
+ request.command = htons(REQ_TRACKING);
+
+ for (i = 1; ; i++) {
+ if (request_reply(&request, &reply, RPY_TRACKING, 0)) {
+ ref_id = ntohl(reply.data.tracking.ref_id);
+ UTI_IPNetworkToHost(&reply.data.tracking.ip_addr, &ip_addr);
+
+ correction = UTI_FloatNetworkToHost(reply.data.tracking.current_correction);
+ correction = fabs(correction);
+ skew_ppm = UTI_FloatNetworkToHost(reply.data.tracking.skew_ppm);
+
+ print_report("try: %d, refid: %R, correction: %.9f, skew: %.3f\n",
+ i, ref_id, correction, skew_ppm, REPORT_END);
+
+ if ((ip_addr.family != IPADDR_UNSPEC ||
+ (ref_id != 0 && ref_id != 0x7f7f0101L /* LOCAL refid */)) &&
+ (max_correction == 0.0 || correction <= max_correction) &&
+ (max_skew_ppm == 0.0 || skew_ppm <= max_skew_ppm)) {
+ ret = 1;
+ }
+ }
+
+ if (!ret && (!max_tries || i < max_tries) && !quit) {
+ UTI_DoubleToTimeval(interval, &timeout);
+ if (select(0, NULL, NULL, NULL, &timeout))
+ break;
+ } else {
+ break;
+ }
+ }
+ return ret;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_dns(const char *line)
+{
+ if (!strcmp(line, "-46")) {
+ DNS_SetAddressFamily(IPADDR_UNSPEC);
+ } else if (!strcmp(line, "-4")) {
+ DNS_SetAddressFamily(IPADDR_INET4);
+ } else if (!strcmp(line, "-6")) {
+ DNS_SetAddressFamily(IPADDR_INET6);
+ } else if (!strcmp(line, "-n")) {
+ no_dns = 1;
+ } else if (!strcmp(line, "+n")) {
+ no_dns = 0;
+ } else {
+ LOG(LOGS_ERR, "Unrecognized dns command");
+ return 0;
+ }
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_timeout(const char *line)
+{
+ int timeout;
+
+ timeout = atoi(line);
+ if (timeout < 100) {
+ LOG(LOGS_ERR, "Timeout %d is too short", timeout);
+ return 0;
+ }
+ initial_timeout = timeout;
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_retries(const char *line)
+{
+ int retries;
+
+ retries = atoi(line);
+ if (retries < 0 || retries > 30) {
+ LOG(LOGS_ERR, "Invalid maximum number of retries");
+ return 0;
+ }
+ max_retries = retries;
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_cmd_keygen(char *line)
+{
+ unsigned int i, args, cmac_length, length, id = 1, bits = 160;
+ unsigned char key[512];
+ const char *type;
+ char *words[3];
+
+#ifdef FEAT_SECHASH
+ type = "SHA1";
+#else
+ type = "MD5";
+#endif
+
+ args = UTI_SplitString(line, words, 3);
+ if (args >= 2)
+ type = words[1];
+
+ if (args > 3 ||
+ (args >= 1 && sscanf(words[0], "%u", &id) != 1) ||
+ (args >= 3 && sscanf(words[2], "%u", &bits) != 1)) {
+ LOG(LOGS_ERR, "Invalid syntax for keygen command");
+ return 0;
+ }
+
+#ifdef HAVE_CMAC
+ cmac_length = CMC_GetKeyLength(UTI_CmacNameToAlgorithm(type));
+#else
+ cmac_length = 0;
+#endif
+
+ if (HSH_GetHashId(UTI_HashNameToAlgorithm(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, type);
+ for (i = 0; i < length; i++)
+ printf("%02hhX", key[i]);
+ printf("\n");
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_line(char *line)
+{
+ char *command;
+ int do_normal_submit;
+ int ret;
+ CMD_Request tx_message;
+ CMD_Reply rx_message;
+
+ ret = 0;
+
+ do_normal_submit = 1;
+
+ CPS_NormalizeLine(line);
+
+ if (!*line) {
+ fflush(stderr);
+ fflush(stdout);
+ return 1;
+ };
+
+ command = line;
+ line = CPS_SplitWord(line);
+
+ if (!strcmp(command, "accheck")) {
+ do_normal_submit = process_cmd_accheck(&tx_message, line);
+ } else if (!strcmp(command, "activity")) {
+ do_normal_submit = 0;
+ ret = process_cmd_activity(line);
+ } else if (!strcmp(command, "add")) {
+ do_normal_submit = process_cmd_add_source(&tx_message, line);
+ } else if (!strcmp(command, "allow")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_ALLOW, REQ_ALLOWALL);
+ } else if (!strcmp(command, "authdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_authdata(line);
+ } else if (!strcmp(command, "burst")) {
+ do_normal_submit = process_cmd_burst(&tx_message, line);
+ } else if (!strcmp(command, "clients")) {
+ ret = process_cmd_clients(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "cmdaccheck")) {
+ do_normal_submit = process_cmd_cmdaccheck(&tx_message, line);
+ } else if (!strcmp(command, "cmdallow")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_CMDALLOW, REQ_CMDALLOWALL);
+ } else if (!strcmp(command, "cmddeny")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_CMDDENY, REQ_CMDDENYALL);
+ } else if (!strcmp(command, "cyclelogs")) {
+ process_cmd_cyclelogs(&tx_message, line);
+ } else if (!strcmp(command, "delete")) {
+ do_normal_submit = process_cmd_delete(&tx_message, line);
+ } else if (!strcmp(command, "deny")) {
+ do_normal_submit = process_cmd_allowdeny(&tx_message, line, REQ_DENY, REQ_DENYALL);
+ } else if (!strcmp(command, "dfreq")) {
+ do_normal_submit = process_cmd_dfreq(&tx_message, line);
+ } else if (!strcmp(command, "dns")) {
+ ret = process_cmd_dns(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "doffset")) {
+ do_normal_submit = process_cmd_doffset(&tx_message, line);
+ } else if (!strcmp(command, "dump")) {
+ process_cmd_dump(&tx_message, line);
+ } else if (!strcmp(command, "exit")) {
+ do_normal_submit = 0;
+ quit = 1;
+ ret = 1;
+ } else if (!strcmp(command, "help")) {
+ do_normal_submit = 0;
+ give_help();
+ ret = 1;
+ } else if (!strcmp(command, "keygen")) {
+ ret = process_cmd_keygen(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "local")) {
+ do_normal_submit = process_cmd_local(&tx_message, line);
+ } else if (!strcmp(command, "makestep")) {
+ do_normal_submit = process_cmd_makestep(&tx_message, line);
+ } else if (!strcmp(command, "manual")) {
+ if (!strncmp(line, "list", 4)) {
+ do_normal_submit = 0;
+ ret = process_cmd_manual_list(CPS_SplitWord(line));
+ } else if (!strncmp(line, "delete", 6)) {
+ do_normal_submit = process_cmd_manual_delete(&tx_message, CPS_SplitWord(line));
+ } else {
+ do_normal_submit = process_cmd_manual(&tx_message, line);
+ }
+ } else if (!strcmp(command, "maxdelay")) {
+ do_normal_submit = process_cmd_maxdelay(&tx_message, line);
+ } else if (!strcmp(command, "maxdelaydevratio")) {
+ do_normal_submit = process_cmd_maxdelaydevratio(&tx_message, line);
+ } else if (!strcmp(command, "maxdelayratio")) {
+ do_normal_submit = process_cmd_maxdelayratio(&tx_message, line);
+ } else if (!strcmp(command, "maxpoll")) {
+ do_normal_submit = process_cmd_maxpoll(&tx_message, line);
+ } else if (!strcmp(command, "maxupdateskew")) {
+ do_normal_submit = process_cmd_maxupdateskew(&tx_message, line);
+ } else if (!strcmp(command, "minpoll")) {
+ do_normal_submit = process_cmd_minpoll(&tx_message, line);
+ } else if (!strcmp(command, "minstratum")) {
+ do_normal_submit = process_cmd_minstratum(&tx_message, line);
+ } else if (!strcmp(command, "ntpdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_ntpdata(line);
+ } else if (!strcmp(command, "offline")) {
+ do_normal_submit = process_cmd_offline(&tx_message, line);
+ } else if (!strcmp(command, "online")) {
+ do_normal_submit = process_cmd_online(&tx_message, line);
+ } else if (!strcmp(command, "onoffline")) {
+ process_cmd_onoffline(&tx_message, line);
+ } else if (!strcmp(command, "polltarget")) {
+ do_normal_submit = process_cmd_polltarget(&tx_message, line);
+ } else if (!strcmp(command, "quit")) {
+ do_normal_submit = 0;
+ quit = 1;
+ ret = 1;
+ } else if (!strcmp(command, "refresh")) {
+ process_cmd_refresh(&tx_message, line);
+ } else if (!strcmp(command, "rekey")) {
+ process_cmd_rekey(&tx_message, line);
+ } else if (!strcmp(command, "reload")) {
+ do_normal_submit = process_cmd_reload(&tx_message, line);
+ } else if (!strcmp(command, "reselect")) {
+ 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")) {
+ do_normal_submit = process_cmd_reset(&tx_message, line);
+ } else if (!strcmp(command, "retries")) {
+ ret = process_cmd_retries(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "rtcdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_rtcreport(line);
+ } else if (!strcmp(command, "selectdata")) {
+ do_normal_submit = 0;
+ ret = process_cmd_selectdata(line);
+ } else if (!strcmp(command, "selectopts")) {
+ do_normal_submit = process_cmd_selectopts(&tx_message, line);
+ } else if (!strcmp(command, "serverstats")) {
+ do_normal_submit = 0;
+ ret = process_cmd_serverstats(line);
+ } else if (!strcmp(command, "settime")) {
+ do_normal_submit = 0;
+ ret = process_cmd_settime(line);
+ } else if (!strcmp(command, "shutdown")) {
+ process_cmd_shutdown(&tx_message, line);
+ } else if (!strcmp(command, "smoothing")) {
+ do_normal_submit = 0;
+ 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);
+ } else if (!strcmp(command, "sourcestats")) {
+ do_normal_submit = 0;
+ ret = process_cmd_sourcestats(line);
+ } else if (!strcmp(command, "timeout")) {
+ ret = process_cmd_timeout(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "tracking")) {
+ ret = process_cmd_tracking(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "trimrtc")) {
+ process_cmd_trimrtc(&tx_message, line);
+ } else if (!strcmp(command, "waitsync")) {
+ ret = process_cmd_waitsync(line);
+ do_normal_submit = 0;
+ } else if (!strcmp(command, "writertc")) {
+ process_cmd_writertc(&tx_message, line);
+ } else if (!strcmp(command, "authhash") ||
+ !strcmp(command, "password")) {
+ /* Warn, but don't return error to not break scripts */
+ LOG(LOGS_WARN, "Authentication is no longer supported");
+ do_normal_submit = 0;
+ ret = 1;
+ } else {
+ LOG(LOGS_ERR, "Unrecognized command");
+ do_normal_submit = 0;
+ }
+
+ if (do_normal_submit) {
+ ret = request_reply(&tx_message, &rx_message, RPY_NULL, 1);
+ }
+
+ if (end_dot) {
+ printf(".\n");
+ }
+
+ fflush(stderr);
+
+ if (fflush(stdout) != 0 || ferror(stdout) != 0) {
+ LOG(LOGS_ERR, "Could not write to stdout");
+
+ /* Return error for commands that print data */
+ if (!do_normal_submit)
+ return 0;
+ }
+
+ return ret;
+}
+
+/* ================================================== */
+
+#define MAX_LINE_LENGTH 2048
+
+static int
+process_args(int argc, char **argv, int multi)
+{
+ char line[MAX_LINE_LENGTH];
+ int i, l, ret = 0;
+
+ for (i = l = 0; i < argc; i++) {
+ l += snprintf(line + l, sizeof (line) - l, "%s ", argv[i]);
+ if (l >= sizeof (line)) {
+ LOG(LOGS_ERR, "Command too long");
+ return 0;
+ }
+
+ if (!multi && i + 1 < argc)
+ continue;
+
+ ret = process_line(line);
+ if (!ret || quit)
+ break;
+
+ l = 0;
+ }
+
+ return ret;
+}
+
+/* ================================================== */
+
+static void
+signal_handler(int signum)
+{
+ quit = 1;
+}
+
+/* ================================================== */
+
+static void
+display_gpl(void)
+{
+ printf("chrony version %s\n"
+ "Copyright (C) 1997-2003, 2007, 2009-2023 Richard P. Curnow and others\n"
+ "chrony comes with ABSOLUTELY NO WARRANTY. This is free software, and\n"
+ "you are welcome to redistribute it under certain conditions. See the\n"
+ "GNU General Public License version 2 for details.\n\n",
+ CHRONY_VERSION);
+}
+
+/* ================================================== */
+
+static void
+print_help(const char *progname)
+{
+ printf("Usage: %s [OPTION]... [COMMAND]...\n\n"
+ "Options:\n"
+ " -4\t\tUse IPv4 addresses only\n"
+ " -6\t\tUse IPv6 addresses only\n"
+ " -n\t\tDon't resolve hostnames\n"
+ " -N\t\tPrint original source names\n"
+ " -c\t\tEnable CSV format\n"
+ " -e\t\tEnd responses with dot\n"
+#if DEBUG > 0
+ " -d\t\tEnable debug messages\n"
+#endif
+ " -m\t\tAccept multiple commands\n"
+ " -h HOST\tSpecify server (%s)\n"
+ " -p PORT\tSpecify UDP port (%d)\n"
+ " -v, --version\tPrint version and exit\n"
+ " --help\tPrint usage and exit\n",
+ progname, DEFAULT_COMMAND_SOCKET",127.0.0.1,::1", DEFAULT_CANDM_PORT);
+}
+
+/* ================================================== */
+
+static void
+print_version(void)
+{
+ printf("chronyc (chrony) version %s (%s)\n", CHRONY_VERSION, CHRONYC_FEATURES);
+}
+
+/* ================================================== */
+
+int
+main(int argc, char **argv)
+{
+ char *line;
+ const char *progname = argv[0];
+ const char *hostnames = NULL;
+ int opt, ret = 1, multi = 0, family = IPADDR_UNSPEC;
+ int port = DEFAULT_CANDM_PORT;
+
+ /* Parse long command-line options */
+ for (optind = 1; optind < argc; optind++) {
+ if (!strcmp("--help", argv[optind])) {
+ print_help(progname);
+ return 0;
+ } else if (!strcmp("--version", argv[optind])) {
+ print_version();
+ return 0;
+ }
+ }
+
+ optind = 1;
+
+ /* Parse short command-line options */
+ while ((opt = getopt(argc, argv, "+46acdef:h:mnNp:v")) != -1) {
+ switch (opt) {
+ case '4':
+ case '6':
+ family = opt == '4' ? IPADDR_INET4 : IPADDR_INET6;
+ break;
+ case 'a':
+ case 'f':
+ /* For compatibility only */
+ break;
+ case 'c':
+ csv_mode = 1;
+ break;
+ case 'd':
+#if DEBUG > 0
+ log_min_severity = LOGS_DEBUG;
+#endif
+ break;
+ case 'e':
+ end_dot = 1;
+ break;
+ case 'h':
+ hostnames = optarg;
+ break;
+ case 'm':
+ multi = 1;
+ break;
+ case 'n':
+ no_dns = 1;
+ break;
+ case 'N':
+ source_names = 1;
+ break;
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case 'v':
+ print_version();
+ return 0;
+ default:
+ print_help(progname);
+ return 1;
+ }
+ }
+
+ if (isatty(0) && isatty(1) && isatty(2)) {
+ on_terminal = 1;
+ }
+
+ if (on_terminal && optind == argc) {
+ display_gpl();
+ }
+
+ DNS_SetAddressFamily(family);
+
+ if (!hostnames) {
+ hostnames = DEFAULT_COMMAND_SOCKET",127.0.0.1,::1";
+ }
+
+ UTI_SetQuitSignalsHandler(signal_handler, 0);
+
+ SCK_Initialise(IPADDR_UNSPEC);
+ server_addresses = get_addresses(hostnames, port);
+
+ if (!open_io())
+ LOG_FATAL("Could not open connection to daemon");
+
+ if (optind < argc) {
+ ret = process_args(argc - optind, argv + optind, multi);
+ } else {
+ do {
+ line = read_line();
+ if (line && !quit) {
+ ret = process_line(line);
+ }else {
+ /* supply the final '\n' when user exits via ^D */
+ if( on_terminal ) printf("\n");
+ }
+ } while (line && !quit);
+ }
+
+ close_io();
+ free_addresses(server_addresses);
+ SCK_Finalise();
+
+ return !ret;
+}
+
+
diff --git a/clientlog.c b/clientlog.c
new file mode 100644
index 0000000..c408e8d
--- /dev/null
+++ b/clientlog.c
@@ -0,0 +1,1111 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009, 2015-2017, 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ This module keeps a count of the number of successful accesses by
+ clients, and the times of the last accesses.
+
+ This can be used for status reporting, and (in the case of a
+ server), if it needs to know which clients have made use of its data
+ recently.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "clientlog.h"
+#include "conf.h"
+#include "local.h"
+#include "memory.h"
+#include "ntp.h"
+#include "reports.h"
+#include "util.h"
+#include "logging.h"
+
+#define MAX_SERVICES 3
+
+typedef struct {
+ IPAddr ip_addr;
+ uint32_t last_hit[MAX_SERVICES];
+ uint32_t hits[MAX_SERVICES];
+ uint16_t drops[MAX_SERVICES];
+ uint16_t tokens[MAX_SERVICES];
+ int8_t rate[MAX_SERVICES];
+ int8_t ntp_timeout_rate;
+ uint8_t drop_flags;
+} Record;
+
+/* Hash table of records, there is a fixed number of records per slot */
+static ARR_Instance records;
+
+#define SLOT_BITS 4
+
+/* Number of records in one slot of the hash table */
+#define SLOT_SIZE (1U << SLOT_BITS)
+
+/* Minimum number of slots */
+#define MIN_SLOTS 1
+
+/* Maximum number of slots, this is a hard limit */
+#define MAX_SLOTS (1U << (24 - SLOT_BITS))
+
+/* Number of slots in the hash table */
+static unsigned int slots;
+
+/* Maximum number of slots given memory allocation limit */
+static unsigned int max_slots;
+
+/* Times of last hits are saved as 32-bit fixed point values */
+#define TS_FRAC 4
+#define INVALID_TS 0
+
+/* Static offset included in conversion to the fixed-point timestamps to
+ randomise their alignment */
+static uint32_t ts_offset;
+
+/* Request rates are saved in the record as 8-bit scaled log2 values */
+#define RATE_SCALE 4
+#define MIN_RATE (-14 * RATE_SCALE)
+#define INVALID_RATE -128
+
+/* Response rates are controlled by token buckets. The capacity and
+ number of tokens spent on response are determined from configured
+ minimum inverval between responses (in log2) and burst length. */
+
+#define MIN_LIMIT_INTERVAL (-15 - TS_FRAC)
+#define MAX_LIMIT_INTERVAL 12
+#define MIN_LIMIT_BURST 1
+#define MAX_LIMIT_BURST 255
+
+static uint16_t max_tokens[MAX_SERVICES];
+static uint16_t tokens_per_hit[MAX_SERVICES];
+
+/* Reduction of token rates to avoid overflow of 16-bit counters. Negative
+ shift is used for coarse limiting with intervals shorter than -TS_FRAC. */
+static int token_shift[MAX_SERVICES];
+
+/* Rates at which responses are randomly allowed (in log2) when the
+ buckets don't have enough tokens. This is necessary in order to
+ prevent an attacker sending requests with spoofed source address
+ from blocking responses to the address completely. */
+
+#define MIN_LEAK_RATE 1
+#define MAX_LEAK_RATE 4
+
+static int leak_rate[MAX_SERVICES];
+
+/* Limit intervals in log2 */
+static int limit_interval[MAX_SERVICES];
+
+/* Flag indicating whether facility is turned on or not */
+static int active;
+
+/* RX and TX timestamp saved for clients using interleaved mode */
+typedef struct {
+ uint64_t rx_ts;
+ uint8_t flags;
+ uint8_t tx_ts_source;
+ uint16_t slew_epoch;
+ int32_t tx_ts_offset;
+} NtpTimestamps;
+
+/* Flags for NTP timestamps */
+#define NTPTS_DISABLED 1
+#define NTPTS_VALID_TX 2
+
+/* RX->TX map using a circular buffer with ordered timestamps */
+typedef struct {
+ ARR_Instance timestamps;
+ uint32_t first;
+ uint32_t size;
+ uint32_t max_size;
+ uint32_t cached_index;
+ uint64_t cached_rx_ts;
+ uint16_t slew_epoch;
+ double slew_offset;
+} NtpTimestampMap;
+
+static NtpTimestampMap ntp_ts_map;
+
+/* Maximum interval of NTP timestamps in future after a backward step */
+#define NTPTS_FUTURE_LIMIT (1LL << 32) /* 1 second */
+
+/* Maximum number of timestamps moved in the array to insert a new timestamp */
+#define NTPTS_INSERT_LIMIT 64
+
+/* Maximum expected value of the timestamp source */
+#define MAX_NTP_TS NTP_TS_HARDWARE
+
+/* Global statistics */
+static uint64_t total_hits[MAX_SERVICES];
+static uint64_t total_drops[MAX_SERVICES];
+static uint64_t total_ntp_auth_hits;
+static uint64_t total_ntp_interleaved_hits;
+static uint64_t total_record_drops;
+static uint64_t total_ntp_rx_timestamps[MAX_NTP_TS + 1];
+static uint64_t total_ntp_tx_timestamps[MAX_NTP_TS + 1];
+
+#define NSEC_PER_SEC 1000000000U
+
+/* ================================================== */
+
+static int expand_hashtable(void);
+static void handle_slew(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything);
+
+/* ================================================== */
+
+static int
+compare_ts(uint32_t x, uint32_t y)
+{
+ if (x == y)
+ return 0;
+ if (y == INVALID_TS)
+ return 1;
+ return (int32_t)(x - y) > 0 ? 1 : -1;
+}
+
+/* ================================================== */
+
+static int
+compare_total_hits(Record *x, Record *y)
+{
+ uint32_t x_hits, y_hits;
+ int i;
+
+ for (i = 0, x_hits = y_hits = 0; i < MAX_SERVICES; i++) {
+ x_hits += x->hits[i];
+ y_hits += y->hits[i];
+ }
+
+ return x_hits > y_hits ? 1 : -1;
+}
+
+/* ================================================== */
+
+static Record *
+get_record(IPAddr *ip)
+{
+ uint32_t last_hit = 0, oldest_hit = 0;
+ Record *record, *oldest_record;
+ unsigned int first, i, j;
+
+ if (!active || (ip->family != IPADDR_INET4 && ip->family != IPADDR_INET6))
+ return NULL;
+
+ while (1) {
+ /* Get index of the first record in the slot */
+ first = UTI_IPToHash(ip) % slots * SLOT_SIZE;
+
+ for (i = 0, oldest_record = NULL; i < SLOT_SIZE; i++) {
+ record = ARR_GetElement(records, first + i);
+
+ if (!UTI_CompareIPs(ip, &record->ip_addr, NULL))
+ return record;
+
+ if (record->ip_addr.family == IPADDR_UNSPEC)
+ break;
+
+ for (j = 0; j < MAX_SERVICES; j++) {
+ if (j == 0 || compare_ts(last_hit, record->last_hit[j]) < 0)
+ last_hit = record->last_hit[j];
+ }
+
+ if (!oldest_record || compare_ts(oldest_hit, last_hit) > 0 ||
+ (oldest_hit == last_hit && compare_total_hits(oldest_record, record) > 0)) {
+ oldest_record = record;
+ oldest_hit = last_hit;
+ }
+ }
+
+ /* If the slot still has an empty record, use it */
+ if (record->ip_addr.family == IPADDR_UNSPEC)
+ break;
+
+ /* Resize the table if possible and try again as the new slot may
+ have some empty records */
+ if (expand_hashtable())
+ continue;
+
+ /* There is no other option, replace the oldest record */
+ record = oldest_record;
+ total_record_drops++;
+ break;
+ }
+
+ record->ip_addr = *ip;
+ for (i = 0; i < MAX_SERVICES; i++)
+ record->last_hit[i] = INVALID_TS;
+ for (i = 0; i < MAX_SERVICES; i++)
+ record->hits[i] = 0;
+ for (i = 0; i < MAX_SERVICES; i++)
+ record->drops[i] = 0;
+ for (i = 0; i < MAX_SERVICES; i++)
+ record->tokens[i] = max_tokens[i];
+ for (i = 0; i < MAX_SERVICES; i++)
+ record->rate[i] = INVALID_RATE;
+ record->ntp_timeout_rate = INVALID_RATE;
+ record->drop_flags = 0;
+
+ return record;
+}
+
+/* ================================================== */
+
+static int
+expand_hashtable(void)
+{
+ ARR_Instance old_records;
+ Record *old_record, *new_record;
+ unsigned int i;
+
+ old_records = records;
+
+ if (2 * slots > max_slots)
+ return 0;
+
+ records = ARR_CreateInstance(sizeof (Record));
+
+ slots = MAX(MIN_SLOTS, 2 * slots);
+ assert(slots <= max_slots);
+
+ ARR_SetSize(records, slots * SLOT_SIZE);
+
+ /* Mark all new records as empty */
+ for (i = 0; i < slots * SLOT_SIZE; i++) {
+ new_record = ARR_GetElement(records, i);
+ new_record->ip_addr.family = IPADDR_UNSPEC;
+ }
+
+ if (!old_records)
+ return 1;
+
+ /* Copy old records to the new hash table */
+ for (i = 0; i < ARR_GetSize(old_records); i++) {
+ old_record = ARR_GetElement(old_records, i);
+ if (old_record->ip_addr.family == IPADDR_UNSPEC)
+ continue;
+
+ new_record = get_record(&old_record->ip_addr);
+
+ assert(new_record);
+ *new_record = *old_record;
+ }
+
+ ARR_DestroyInstance(old_records);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+set_bucket_params(int interval, int burst, uint16_t *max_tokens,
+ uint16_t *tokens_per_packet, int *token_shift)
+{
+ interval = CLAMP(MIN_LIMIT_INTERVAL, interval, MAX_LIMIT_INTERVAL);
+ burst = CLAMP(MIN_LIMIT_BURST, burst, MAX_LIMIT_BURST);
+
+ if (interval >= -TS_FRAC) {
+ /* Find the smallest shift with which the maximum number fits in 16 bits */
+ for (*token_shift = 0; *token_shift < interval + TS_FRAC; (*token_shift)++) {
+ if (burst << (TS_FRAC + interval - *token_shift) < 1U << 16)
+ break;
+ }
+ } else {
+ /* Coarse rate limiting */
+ *token_shift = interval + TS_FRAC;
+ *tokens_per_packet = 1;
+ burst = MAX(1U << -*token_shift, burst);
+ }
+
+ *tokens_per_packet = 1U << (TS_FRAC + interval - *token_shift);
+ *max_tokens = *tokens_per_packet * burst;
+
+ DEBUG_LOG("Tokens max %d packet %d shift %d",
+ *max_tokens, *tokens_per_packet, *token_shift);
+}
+
+/* ================================================== */
+
+void
+CLG_Initialise(void)
+{
+ int i, interval, burst, lrate, slots2;
+
+ for (i = 0; i < MAX_SERVICES; i++) {
+ max_tokens[i] = 0;
+ tokens_per_hit[i] = 0;
+ token_shift[i] = 0;
+ leak_rate[i] = 0;
+ limit_interval[i] = MIN_LIMIT_INTERVAL;
+
+ switch (i) {
+ case CLG_NTP:
+ if (!CNF_GetNTPRateLimit(&interval, &burst, &lrate))
+ continue;
+ break;
+ case CLG_NTSKE:
+ if (!CNF_GetNtsRateLimit(&interval, &burst, &lrate))
+ continue;
+ break;
+ case CLG_CMDMON:
+ if (!CNF_GetCommandRateLimit(&interval, &burst, &lrate))
+ continue;
+ break;
+ default:
+ assert(0);
+ }
+
+ set_bucket_params(interval, burst, &max_tokens[i], &tokens_per_hit[i], &token_shift[i]);
+ leak_rate[i] = CLAMP(MIN_LEAK_RATE, lrate, MAX_LEAK_RATE);
+ limit_interval[i] = CLAMP(MIN_LIMIT_INTERVAL, interval, MAX_LIMIT_INTERVAL);
+ }
+
+ active = !CNF_GetNoClientLog();
+ if (!active) {
+ for (i = 0; i < MAX_SERVICES; i++) {
+ if (leak_rate[i] != 0)
+ LOG_FATAL("Rate limiting cannot be enabled with noclientlog");
+ }
+ return;
+ }
+
+ /* Calculate the maximum number of slots that can be allocated in the
+ configured memory limit. Take into account expanding of the hash
+ table where two copies exist at the same time. */
+ max_slots = CNF_GetClientLogLimit() /
+ ((sizeof (Record) + sizeof (NtpTimestamps)) * SLOT_SIZE * 3 / 2);
+ max_slots = CLAMP(MIN_SLOTS, max_slots, MAX_SLOTS);
+ for (slots2 = 0; 1U << (slots2 + 1) <= max_slots; slots2++)
+ ;
+
+ DEBUG_LOG("Max records %u", 1U << (slots2 + SLOT_BITS));
+
+ slots = 0;
+ records = NULL;
+
+ expand_hashtable();
+
+ UTI_GetRandomBytes(&ts_offset, sizeof (ts_offset));
+ ts_offset %= NSEC_PER_SEC / (1U << TS_FRAC);
+
+ ntp_ts_map.timestamps = NULL;
+ ntp_ts_map.first = 0;
+ ntp_ts_map.size = 0;
+ ntp_ts_map.max_size = 1U << (slots2 + SLOT_BITS);
+ ntp_ts_map.cached_index = 0;
+ ntp_ts_map.cached_rx_ts = 0ULL;
+ ntp_ts_map.slew_epoch = 0;
+ ntp_ts_map.slew_offset = 0.0;
+
+ LCL_AddParameterChangeHandler(handle_slew, NULL);
+}
+
+/* ================================================== */
+
+void
+CLG_Finalise(void)
+{
+ if (!active)
+ return;
+
+ ARR_DestroyInstance(records);
+ if (ntp_ts_map.timestamps)
+ ARR_DestroyInstance(ntp_ts_map.timestamps);
+
+ LCL_RemoveParameterChangeHandler(handle_slew, NULL);
+}
+
+/* ================================================== */
+
+static uint32_t
+get_ts_from_timespec(struct timespec *ts)
+{
+ uint32_t sec = ts->tv_sec, nsec = ts->tv_nsec;
+
+ nsec += ts_offset;
+ if (nsec >= NSEC_PER_SEC) {
+ nsec -= NSEC_PER_SEC;
+ sec++;
+ }
+
+ /* This is fast and accurate enough */
+ return sec << TS_FRAC | (140740U * (nsec >> 15)) >> (32 - TS_FRAC);
+}
+
+/* ================================================== */
+
+static void
+update_record(CLG_Service service, Record *record, struct timespec *now)
+{
+ uint32_t interval, now_ts, prev_hit, tokens;
+ int interval2, tshift, mtokens;
+ int8_t *rate;
+
+ now_ts = get_ts_from_timespec(now);
+
+ prev_hit = record->last_hit[service];
+ record->last_hit[service] = now_ts;
+ record->hits[service]++;
+
+ interval = now_ts - prev_hit;
+
+ if (prev_hit == INVALID_TS || (int32_t)interval < 0)
+ return;
+
+ tshift = token_shift[service];
+ mtokens = max_tokens[service];
+
+ if (tshift >= 0)
+ tokens = (now_ts >> tshift) - (prev_hit >> tshift);
+ else if (now_ts - prev_hit > mtokens)
+ tokens = mtokens;
+ else
+ tokens = (now_ts - prev_hit) << -tshift;
+ record->tokens[service] = MIN(record->tokens[service] + tokens, mtokens);
+
+ /* Convert the interval to scaled and rounded log2 */
+ if (interval) {
+ interval += interval >> 1;
+ for (interval2 = -RATE_SCALE * TS_FRAC; interval2 < -MIN_RATE;
+ interval2 += RATE_SCALE) {
+ if (interval <= 1)
+ break;
+ interval >>= 1;
+ }
+ } else {
+ interval2 = -RATE_SCALE * (TS_FRAC + 1);
+ }
+
+ /* For the NTP service, update one of the two rates depending on whether
+ the previous request of the client had a reply or it timed out */
+ rate = service == CLG_NTP && record->drop_flags & (1U << service) ?
+ &record->ntp_timeout_rate : &record->rate[service];
+
+ /* Update the rate in a rough approximation of exponential moving average */
+ if (*rate == INVALID_RATE) {
+ *rate = -interval2;
+ } else {
+ if (*rate < -interval2) {
+ (*rate)++;
+ } else if (*rate > -interval2) {
+ if (*rate > RATE_SCALE * 5 / 2 - interval2)
+ *rate = RATE_SCALE * 5 / 2 - interval2;
+ else
+ *rate = (*rate - interval2 - 1) / 2;
+ }
+ }
+}
+
+/* ================================================== */
+
+static int
+get_index(Record *record)
+{
+ return record - (Record *)ARR_GetElements(records);
+}
+
+/* ================================================== */
+
+int
+CLG_GetClientIndex(IPAddr *client)
+{
+ Record *record;
+
+ record = get_record(client);
+ if (record == NULL)
+ return -1;
+
+ return get_index(record);
+}
+
+/* ================================================== */
+
+static void
+check_service_number(CLG_Service service)
+{
+ assert(service >= 0 && service <= MAX_SERVICES);
+}
+
+/* ================================================== */
+
+int
+CLG_LogServiceAccess(CLG_Service service, IPAddr *client, struct timespec *now)
+{
+ Record *record;
+
+ check_service_number(service);
+
+ total_hits[service]++;
+
+ record = get_record(client);
+ if (record == NULL)
+ return -1;
+
+ update_record(service, record, now);
+
+ DEBUG_LOG("service %d hits %"PRIu32" rate %d trate %d tokens %d",
+ (int)service, record->hits[service], record->rate[service],
+ service == CLG_NTP ? record->ntp_timeout_rate : INVALID_RATE,
+ record->tokens[service]);
+
+ return get_index(record);
+}
+
+/* ================================================== */
+
+static int
+limit_response_random(int leak_rate)
+{
+ static uint32_t rnd;
+ static int bits_left = 0;
+ int r;
+
+ if (bits_left < leak_rate) {
+ UTI_GetRandomBytes(&rnd, sizeof (rnd));
+ bits_left = 8 * sizeof (rnd);
+ }
+
+ /* Return zero on average once per 2^leak_rate */
+ r = rnd % (1U << leak_rate) ? 1 : 0;
+ rnd >>= leak_rate;
+ bits_left -= leak_rate;
+
+ return r;
+}
+
+/* ================================================== */
+
+int
+CLG_LimitServiceRate(CLG_Service service, int index)
+{
+ Record *record;
+ int drop;
+
+ check_service_number(service);
+
+ if (tokens_per_hit[service] == 0)
+ return 0;
+
+ record = ARR_GetElement(records, index);
+ record->drop_flags &= ~(1U << service);
+
+ if (record->tokens[service] >= tokens_per_hit[service]) {
+ record->tokens[service] -= tokens_per_hit[service];
+ return 0;
+ }
+
+ drop = limit_response_random(leak_rate[service]);
+
+ /* Poorly implemented NTP clients can send requests at a higher rate
+ when they are not getting replies. If the request rate seems to be more
+ than twice as much as when replies are sent, give up on rate limiting to
+ reduce the amount of traffic. Invert the sense of the leak to respond to
+ most of the requests, but still keep the estimated rate updated. */
+ if (service == CLG_NTP && record->ntp_timeout_rate != INVALID_RATE &&
+ record->ntp_timeout_rate > record->rate[service] + RATE_SCALE)
+ drop = !drop;
+
+ if (!drop) {
+ record->tokens[service] = 0;
+ return 0;
+ }
+
+ record->drop_flags |= 1U << service;
+ record->drops[service]++;
+ total_drops[service]++;
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+CLG_UpdateNtpStats(int auth, NTP_Timestamp_Source rx_ts_src, NTP_Timestamp_Source tx_ts_src)
+{
+ if (auth)
+ total_ntp_auth_hits++;
+ if (rx_ts_src >= 0 && rx_ts_src <= MAX_NTP_TS)
+ total_ntp_rx_timestamps[rx_ts_src]++;
+ if (tx_ts_src >= 0 && tx_ts_src <= MAX_NTP_TS)
+ total_ntp_tx_timestamps[tx_ts_src]++;
+}
+
+/* ================================================== */
+
+int
+CLG_GetNtpMinPoll(void)
+{
+ return limit_interval[CLG_NTP];
+}
+
+/* ================================================== */
+
+static NtpTimestamps *
+get_ntp_tss(uint32_t index)
+{
+ return ARR_GetElement(ntp_ts_map.timestamps,
+ (ntp_ts_map.first + index) & (ntp_ts_map.max_size - 1));
+}
+
+/* ================================================== */
+
+static int
+find_ntp_rx_ts(uint64_t rx_ts, uint32_t *index)
+{
+ uint64_t rx_x, rx_lo, rx_hi, step;
+ uint32_t i, x, lo, hi;
+
+ if (ntp_ts_map.cached_rx_ts == rx_ts && rx_ts != 0ULL) {
+ *index = ntp_ts_map.cached_index;
+ return 1;
+ }
+
+ if (ntp_ts_map.size == 0) {
+ *index = 0;
+ return 0;
+ }
+
+ lo = 0;
+ hi = ntp_ts_map.size - 1;
+ rx_lo = get_ntp_tss(lo)->rx_ts;
+ rx_hi = get_ntp_tss(hi)->rx_ts;
+
+ /* Check for ts < lo before ts > hi to trim timestamps from "future" later
+ if both conditions are true to not break the order of the endpoints.
+ Compare timestamps by their difference to allow adjacent NTP eras. */
+ if ((int64_t)(rx_ts - rx_lo) < 0) {
+ *index = 0;
+ return 0;
+ } else if ((int64_t)(rx_ts - rx_hi) > 0) {
+ *index = ntp_ts_map.size;
+ return 0;
+ }
+
+ /* Perform a combined linear interpolation and binary search */
+
+ for (i = 0; ; i++) {
+ if (rx_ts == rx_hi) {
+ *index = ntp_ts_map.cached_index = hi;
+ ntp_ts_map.cached_rx_ts = rx_ts;
+ return 1;
+ } else if (rx_ts == rx_lo) {
+ *index = ntp_ts_map.cached_index = lo;
+ ntp_ts_map.cached_rx_ts = rx_ts;
+ return 1;
+ } else if (lo + 1 == hi) {
+ *index = hi;
+ return 0;
+ }
+
+ if (hi - lo > 3 && i % 2 == 0) {
+ step = (rx_hi - rx_lo) / (hi - lo);
+ if (step == 0)
+ step = 1;
+ x = lo + (rx_ts - rx_lo) / step;
+ } else {
+ x = lo + (hi - lo) / 2;
+ }
+
+ if (x <= lo)
+ x = lo + 1;
+ else if (x >= hi)
+ x = hi - 1;
+
+ rx_x = get_ntp_tss(x)->rx_ts;
+
+ if ((int64_t)(rx_x - rx_ts) <= 0) {
+ lo = x;
+ rx_lo = rx_x;
+ } else {
+ hi = x;
+ rx_hi = rx_x;
+ }
+ }
+}
+
+/* ================================================== */
+
+static uint64_t
+ntp64_to_int64(NTP_int64 *ts)
+{
+ return (uint64_t)ntohl(ts->hi) << 32 | ntohl(ts->lo);
+}
+
+/* ================================================== */
+
+static void
+int64_to_ntp64(uint64_t ts, NTP_int64 *ntp_ts)
+{
+ ntp_ts->hi = htonl(ts >> 32);
+ ntp_ts->lo = htonl(ts);
+}
+
+/* ================================================== */
+
+static uint32_t
+push_ntp_tss(uint32_t index)
+{
+ if (ntp_ts_map.size < ntp_ts_map.max_size) {
+ ntp_ts_map.size++;
+ } else {
+ ntp_ts_map.first = (ntp_ts_map.first + 1) % (ntp_ts_map.max_size);
+ if (index > 0)
+ index--;
+ }
+
+ return index;
+}
+
+/* ================================================== */
+
+static void
+set_ntp_tx(NtpTimestamps *tss, NTP_int64 *rx_ts, struct timespec *tx_ts,
+ NTP_Timestamp_Source tx_src)
+{
+ struct timespec ts;
+
+ if (!tx_ts) {
+ tss->flags &= ~NTPTS_VALID_TX;
+ return;
+ }
+
+ UTI_Ntp64ToTimespec(rx_ts, &ts);
+ UTI_DiffTimespecs(&ts, tx_ts, &ts);
+
+ if (ts.tv_sec < -2 || ts.tv_sec > 1) {
+ tss->flags &= ~NTPTS_VALID_TX;
+ return;
+ }
+
+ tss->tx_ts_offset = (int32_t)ts.tv_nsec + (int32_t)ts.tv_sec * (int32_t)NSEC_PER_SEC;
+ tss->flags |= NTPTS_VALID_TX;
+ tss->tx_ts_source = tx_src;
+}
+
+/* ================================================== */
+
+static void
+get_ntp_tx(NtpTimestamps *tss, struct timespec *tx_ts, NTP_Timestamp_Source *tx_src)
+{
+ int32_t offset = tss->tx_ts_offset;
+ NTP_int64 ntp_ts;
+
+ if (tss->flags & NTPTS_VALID_TX) {
+ int64_to_ntp64(tss->rx_ts, &ntp_ts);
+ UTI_Ntp64ToTimespec(&ntp_ts, tx_ts);
+ if (offset >= (int32_t)NSEC_PER_SEC) {
+ offset -= NSEC_PER_SEC;
+ tx_ts->tv_sec++;
+ }
+ tx_ts->tv_nsec += offset;
+ UTI_NormaliseTimespec(tx_ts);
+ } else {
+ UTI_ZeroTimespec(tx_ts);
+ }
+
+ *tx_src = tss->tx_ts_source;
+}
+
+/* ================================================== */
+
+void
+CLG_SaveNtpTimestamps(NTP_int64 *rx_ts, struct timespec *tx_ts, NTP_Timestamp_Source tx_src)
+{
+ NtpTimestamps *tss;
+ uint32_t i, index;
+ uint64_t rx;
+
+ if (!active)
+ return;
+
+ /* Allocate the array on first use */
+ if (!ntp_ts_map.timestamps) {
+ ntp_ts_map.timestamps = ARR_CreateInstance(sizeof (NtpTimestamps));
+ ARR_SetSize(ntp_ts_map.timestamps, ntp_ts_map.max_size);
+ }
+
+ rx = ntp64_to_int64(rx_ts);
+
+ if (rx == 0ULL)
+ return;
+
+ /* Disable the RX timestamp if it already exists to avoid responding
+ with a wrong TX timestamp */
+ if (find_ntp_rx_ts(rx, &index)) {
+ get_ntp_tss(index)->flags |= NTPTS_DISABLED;
+ return;
+ }
+
+ assert(index <= ntp_ts_map.size);
+
+ if (index == ntp_ts_map.size) {
+ /* Increase the size or drop the oldest timestamp to make room for
+ the new timestamp */
+ index = push_ntp_tss(index);
+ } else {
+ /* Trim timestamps in distant future after backward step */
+ while (index < ntp_ts_map.size &&
+ get_ntp_tss(ntp_ts_map.size - 1)->rx_ts - rx > NTPTS_FUTURE_LIMIT)
+ ntp_ts_map.size--;
+
+ /* Insert the timestamp if it is close to the latest timestamp.
+ Otherwise, replace the closest older or the oldest timestamp. */
+ if (index + NTPTS_INSERT_LIMIT >= ntp_ts_map.size) {
+ index = push_ntp_tss(index);
+ for (i = ntp_ts_map.size - 1; i > index; i--)
+ *get_ntp_tss(i) = *get_ntp_tss(i - 1);
+ } else {
+ if (index > 0)
+ index--;
+ }
+ }
+
+ ntp_ts_map.cached_index = index;
+ ntp_ts_map.cached_rx_ts = rx;
+
+ tss = get_ntp_tss(index);
+ tss->rx_ts = rx;
+ tss->flags = 0;
+ tss->slew_epoch = ntp_ts_map.slew_epoch;
+ set_ntp_tx(tss, rx_ts, tx_ts, tx_src);
+
+ DEBUG_LOG("Saved RX+TX index=%"PRIu32" first=%"PRIu32" size=%"PRIu32,
+ index, ntp_ts_map.first, ntp_ts_map.size);
+}
+
+/* ================================================== */
+
+static void
+handle_slew(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything)
+{
+ /* Drop all timestamps on unknown step */
+ if (change_type == LCL_ChangeUnknownStep) {
+ ntp_ts_map.size = 0;
+ ntp_ts_map.cached_rx_ts = 0ULL;
+ }
+
+ ntp_ts_map.slew_epoch++;
+ ntp_ts_map.slew_offset = doffset;
+}
+
+/* ================================================== */
+
+void
+CLG_UndoNtpTxTimestampSlew(NTP_int64 *rx_ts, struct timespec *tx_ts)
+{
+ uint32_t index;
+
+ if (!ntp_ts_map.timestamps)
+ return;
+
+ if (!find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index))
+ return;
+
+ /* If the RX timestamp was captured before the last correction of the clock,
+ remove the adjustment from the TX timestamp */
+ if ((uint16_t)(get_ntp_tss(index)->slew_epoch + 1U) == ntp_ts_map.slew_epoch)
+ UTI_AddDoubleToTimespec(tx_ts, ntp_ts_map.slew_offset, tx_ts);
+}
+
+/* ================================================== */
+
+void
+CLG_UpdateNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts,
+ NTP_Timestamp_Source tx_src)
+{
+ uint32_t index;
+
+ if (!ntp_ts_map.timestamps)
+ return;
+
+ if (!find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index))
+ return;
+
+ set_ntp_tx(get_ntp_tss(index), rx_ts, tx_ts, tx_src);
+}
+
+/* ================================================== */
+
+int
+CLG_GetNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts,
+ NTP_Timestamp_Source *tx_src)
+{
+ NtpTimestamps *tss;
+ uint32_t index;
+
+ if (!ntp_ts_map.timestamps)
+ return 0;
+
+ if (!find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index))
+ return 0;
+
+ tss = get_ntp_tss(index);
+
+ if (tss->flags & NTPTS_DISABLED)
+ return 0;
+
+ get_ntp_tx(tss, tx_ts, tx_src);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+CLG_DisableNtpTimestamps(NTP_int64 *rx_ts)
+{
+ uint32_t index;
+
+ if (!ntp_ts_map.timestamps)
+ return;
+
+ if (find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index))
+ get_ntp_tss(index)->flags |= NTPTS_DISABLED;
+
+ /* This assumes the function is called only to prevent multiple
+ interleaved responses to the same timestamp */
+ total_ntp_interleaved_hits++;
+}
+
+/* ================================================== */
+
+int
+CLG_GetNumberOfIndices(void)
+{
+ if (!active)
+ return -1;
+
+ return ARR_GetSize(records);
+}
+
+/* ================================================== */
+
+static int get_interval(int rate)
+{
+ if (rate == INVALID_RATE)
+ return 127;
+
+ rate += rate > 0 ? RATE_SCALE / 2 : -RATE_SCALE / 2;
+
+ return rate / -RATE_SCALE;
+}
+
+/* ================================================== */
+
+static uint32_t get_last_ago(uint32_t x, uint32_t y)
+{
+ if (y == INVALID_TS || (int32_t)(x - y) < 0)
+ return -1;
+
+ return (x - y) >> TS_FRAC;
+}
+
+/* ================================================== */
+
+int
+CLG_GetClientAccessReportByIndex(int index, int reset, uint32_t min_hits,
+ RPT_ClientAccessByIndex_Report *report, struct timespec *now)
+{
+ Record *record;
+ uint32_t now_ts;
+ int i, r;
+
+ if (!active || index < 0 || index >= ARR_GetSize(records))
+ return 0;
+
+ record = ARR_GetElement(records, index);
+
+ if (record->ip_addr.family == IPADDR_UNSPEC)
+ return 0;
+
+ if (min_hits == 0) {
+ r = 1;
+ } else {
+ for (i = r = 0; i < MAX_SERVICES; i++) {
+ if (record->hits[i] >= min_hits) {
+ r = 1;
+ break;
+ }
+ }
+ }
+
+ if (r) {
+ now_ts = get_ts_from_timespec(now);
+
+ report->ip_addr = record->ip_addr;
+ report->ntp_hits = record->hits[CLG_NTP];
+ report->nke_hits = record->hits[CLG_NTSKE];
+ report->cmd_hits = record->hits[CLG_CMDMON];
+ report->ntp_drops = record->drops[CLG_NTP];
+ report->nke_drops = record->drops[CLG_NTSKE];
+ report->cmd_drops = record->drops[CLG_CMDMON];
+ report->ntp_interval = get_interval(record->rate[CLG_NTP]);
+ report->nke_interval = get_interval(record->rate[CLG_NTSKE]);
+ report->cmd_interval = get_interval(record->rate[CLG_CMDMON]);
+ report->ntp_timeout_interval = get_interval(record->ntp_timeout_rate);
+ report->last_ntp_hit_ago = get_last_ago(now_ts, record->last_hit[CLG_NTP]);
+ report->last_nke_hit_ago = get_last_ago(now_ts, record->last_hit[CLG_NTSKE]);
+ report->last_cmd_hit_ago = get_last_ago(now_ts, record->last_hit[CLG_CMDMON]);
+ }
+
+ if (reset) {
+ for (i = 0; i < MAX_SERVICES; i++) {
+ record->hits[i] = 0;
+ record->drops[i] = 0;
+ }
+ }
+
+ return r;
+}
+
+/* ================================================== */
+
+void
+CLG_GetServerStatsReport(RPT_ServerStatsReport *report)
+{
+ report->ntp_hits = total_hits[CLG_NTP];
+ report->nke_hits = total_hits[CLG_NTSKE];
+ report->cmd_hits = total_hits[CLG_CMDMON];
+ report->ntp_drops = total_drops[CLG_NTP];
+ report->nke_drops = total_drops[CLG_NTSKE];
+ report->cmd_drops = total_drops[CLG_CMDMON];
+ report->log_drops = total_record_drops;
+ report->ntp_auth_hits = total_ntp_auth_hits;
+ report->ntp_interleaved_hits = total_ntp_interleaved_hits;
+ report->ntp_timestamps = ntp_ts_map.size;
+ report->ntp_span_seconds = ntp_ts_map.size > 1 ?
+ (get_ntp_tss(ntp_ts_map.size - 1)->rx_ts -
+ get_ntp_tss(0)->rx_ts) >> 32 : 0;
+ report->ntp_daemon_rx_timestamps = total_ntp_rx_timestamps[NTP_TS_DAEMON];
+ report->ntp_daemon_tx_timestamps = total_ntp_tx_timestamps[NTP_TS_DAEMON];
+ report->ntp_kernel_rx_timestamps = total_ntp_rx_timestamps[NTP_TS_KERNEL];
+ report->ntp_kernel_tx_timestamps = total_ntp_tx_timestamps[NTP_TS_KERNEL];
+ report->ntp_hw_rx_timestamps = total_ntp_rx_timestamps[NTP_TS_HARDWARE];
+ report->ntp_hw_tx_timestamps = total_ntp_tx_timestamps[NTP_TS_HARDWARE];
+}
diff --git a/clientlog.h b/clientlog.h
new file mode 100644
index 0000000..9ea0a3f
--- /dev/null
+++ b/clientlog.h
@@ -0,0 +1,67 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ This module contains facilities for logging access by clients.
+
+ */
+
+#ifndef GOT_CLIENTLOG_H
+#define GOT_CLIENTLOG_H
+
+#include "sysincl.h"
+#include "reports.h"
+
+typedef enum {
+ CLG_NTP = 0,
+ CLG_NTSKE,
+ CLG_CMDMON,
+} CLG_Service;
+
+extern void CLG_Initialise(void);
+extern void CLG_Finalise(void);
+extern int CLG_GetClientIndex(IPAddr *client);
+extern int CLG_LogServiceAccess(CLG_Service service, IPAddr *client, struct timespec *now);
+extern int CLG_LimitServiceRate(CLG_Service service, int index);
+extern void CLG_UpdateNtpStats(int auth, NTP_Timestamp_Source rx_ts_src,
+ NTP_Timestamp_Source tx_ts_src);
+extern int CLG_GetNtpMinPoll(void);
+
+/* Functions to save and retrieve timestamps for server interleaved mode */
+extern void CLG_SaveNtpTimestamps(NTP_int64 *rx_ts, struct timespec *tx_ts,
+ NTP_Timestamp_Source tx_src);
+extern void CLG_UndoNtpTxTimestampSlew(NTP_int64 *rx_ts, struct timespec *tx_ts);
+extern void CLG_UpdateNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts,
+ NTP_Timestamp_Source tx_src);
+extern int CLG_GetNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts,
+ NTP_Timestamp_Source *tx_src);
+extern void CLG_DisableNtpTimestamps(NTP_int64 *rx_ts);
+
+/* And some reporting functions, for use by chronyc. */
+
+extern int CLG_GetNumberOfIndices(void);
+extern int CLG_GetClientAccessReportByIndex(int index, int reset, uint32_t min_hits,
+ RPT_ClientAccessByIndex_Report *report,
+ struct timespec *now);
+extern void CLG_GetServerStatsReport(RPT_ServerStatsReport *report);
+
+#endif /* GOT_CLIENTLOG_H */
diff --git a/cmac.h b/cmac.h
new file mode 100644
index 0000000..935820d
--- /dev/null
+++ b/cmac.h
@@ -0,0 +1,48 @@
+/*
+ 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
+
+/* Avoid overlapping with the hash enumeration */
+typedef enum {
+ CMC_INVALID = 0,
+ CMC_AES128 = 13,
+ CMC_AES256 = 14,
+} CMC_Algorithm;
+
+typedef struct CMC_Instance_Record *CMC_Instance;
+
+extern int CMC_GetKeyLength(CMC_Algorithm algorithm);
+extern CMC_Instance CMC_CreateInstance(CMC_Algorithm algorithm, const unsigned char *key,
+ int length);
+extern int CMC_Hash(CMC_Instance inst, const void *in, int in_len,
+ unsigned char *out, int out_len);
+extern void CMC_DestroyInstance(CMC_Instance inst);
+
+#endif
+
diff --git a/cmac_gnutls.c b/cmac_gnutls.c
new file mode 100644
index 0000000..d1cd550
--- /dev/null
+++ b/cmac_gnutls.c
@@ -0,0 +1,189 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2021
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ CMAC using the GnuTLS library
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <gnutls/crypto.h>
+
+#include "cmac.h"
+#include "hash.h"
+#include "logging.h"
+#include "memory.h"
+
+struct CMC_Instance_Record {
+ gnutls_mac_algorithm_t algorithm;
+ gnutls_hmac_hd_t mac;
+};
+
+/* ================================================== */
+
+static int instance_counter = 0;
+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));
+
+ DEBUG_LOG("Initialised");
+ gnutls_initialised = 1;
+}
+
+/* ================================================== */
+
+static void
+deinit_gnutls(void)
+{
+ assert(gnutls_initialised);
+ gnutls_global_deinit();
+ gnutls_initialised = 0;
+ DEBUG_LOG("Deinitialised");
+}
+
+/* ================================================== */
+
+static gnutls_mac_algorithm_t
+get_mac_algorithm(CMC_Algorithm algorithm)
+{
+ switch (algorithm) {
+ case CMC_AES128:
+ return GNUTLS_MAC_AES_CMAC_128;
+ case CMC_AES256:
+ return GNUTLS_MAC_AES_CMAC_256;
+ default:
+ return GNUTLS_MAC_UNKNOWN;
+ }
+}
+
+/* ================================================== */
+
+int
+CMC_GetKeyLength(CMC_Algorithm algorithm)
+{
+ gnutls_mac_algorithm_t malgo = get_mac_algorithm(algorithm);
+ int len;
+
+ if (malgo == GNUTLS_MAC_UNKNOWN)
+ return 0;
+
+ len = gnutls_hmac_get_key_size(malgo);
+
+ if (len < 0)
+ return 0;
+
+ return len;
+}
+
+/* ================================================== */
+
+CMC_Instance
+CMC_CreateInstance(CMC_Algorithm algorithm, const unsigned char *key, int length)
+{
+ gnutls_hmac_hd_t handle;
+ CMC_Instance inst;
+
+ int r;
+
+ if (instance_counter == 0)
+ init_gnutls();
+
+ if (length <= 0 || length != CMC_GetKeyLength(algorithm))
+ goto error;
+
+ r = gnutls_hmac_init(&handle, get_mac_algorithm(algorithm), key, length);
+ if (r < 0) {
+ DEBUG_LOG("Could not initialise %s : %s", "mac", gnutls_strerror(r));
+ goto error;
+ }
+
+ inst = MallocNew(struct CMC_Instance_Record);
+ inst->algorithm = get_mac_algorithm(algorithm);
+ inst->mac = handle;
+
+ instance_counter++;
+
+ return inst;
+
+error:
+ if (instance_counter == 0)
+ deinit_gnutls();
+ return NULL;
+}
+
+/* ================================================== */
+
+int
+CMC_Hash(CMC_Instance inst, const void *in, int in_len, unsigned char *out, int out_len)
+{
+ unsigned char buf[MAX_HASH_LENGTH];
+ int hash_len;
+
+ if (in_len < 0 || out_len < 0)
+ return 0;
+
+ hash_len = gnutls_hmac_get_len(inst->algorithm);
+
+ if (out_len > hash_len)
+ out_len = hash_len;
+
+ if (hash_len > sizeof (buf))
+ return 0;
+
+ if (gnutls_hmac(inst->mac, in, in_len) < 0) {
+ /* Reset the state */
+ gnutls_hmac_output(inst->mac, buf);
+ return 0;
+ }
+
+ gnutls_hmac_output(inst->mac, buf);
+ memcpy(out, buf, out_len);
+
+ return out_len;
+}
+
+/* ================================================== */
+
+void
+CMC_DestroyInstance(CMC_Instance inst)
+{
+ gnutls_hmac_deinit(inst->mac, NULL);
+ Free(inst);
+
+ instance_counter--;
+ if (instance_counter == 0)
+ deinit_gnutls();
+}
diff --git a/cmac_nettle.c b/cmac_nettle.c
new file mode 100644
index 0000000..5b2c0d4
--- /dev/null
+++ b/cmac_nettle.c
@@ -0,0 +1,117 @@
+/*
+ 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;
+};
+
+/* ================================================== */
+
+int
+CMC_GetKeyLength(CMC_Algorithm algorithm)
+{
+ if (algorithm == CMC_AES128)
+ return AES128_KEY_SIZE;
+ else if (algorithm == CMC_AES256)
+ return AES256_KEY_SIZE;
+ return 0;
+}
+
+/* ================================================== */
+
+CMC_Instance
+CMC_CreateInstance(CMC_Algorithm algorithm, const unsigned char *key, int length)
+{
+ CMC_Instance inst;
+
+ if (length <= 0 || length != CMC_GetKeyLength(algorithm))
+ 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;
+}
+
+/* ================================================== */
+
+int
+CMC_Hash(CMC_Instance inst, const void *in, int in_len, unsigned char *out, int out_len)
+{
+ if (in_len < 0 || out_len < 0)
+ return 0;
+
+ 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);
+}
diff --git a/cmdmon.c b/cmdmon.c
new file mode 100644
index 0000000..9adc9d6
--- /dev/null
+++ b/cmdmon.c
@@ -0,0 +1,1871 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009-2016, 2018-2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Command and monitoring module in the main program
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "cmdmon.h"
+#include "candm.h"
+#include "sched.h"
+#include "util.h"
+#include "logging.h"
+#include "keys.h"
+#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"
+#include "rtc.h"
+#include "pktlength.h"
+#include "clientlog.h"
+#include "refclock.h"
+
+/* ================================================== */
+
+#define INVALID_SOCK_FD (-5)
+
+/* File descriptors for command and monitoring sockets */
+static int sock_fdu;
+static int sock_fd4;
+static int sock_fd6;
+
+/* Flag indicating the IPv4 socket is bound to an address */
+static int bound_sock_fd4;
+
+/* Flag indicating whether this module has been initialised or not */
+static int initialised = 0;
+
+/* ================================================== */
+/* Array of permission levels for command types */
+
+static const char permissions[] = {
+ PERMIT_OPEN, /* NULL */
+ PERMIT_AUTH, /* ONLINE */
+ PERMIT_AUTH, /* OFFLINE */
+ PERMIT_AUTH, /* BURST */
+ PERMIT_AUTH, /* MODIFY_MINPOLL */
+ PERMIT_AUTH, /* MODIFY_MAXPOLL */
+ PERMIT_AUTH, /* DUMP */
+ PERMIT_AUTH, /* MODIFY_MAXDELAY */
+ PERMIT_AUTH, /* MODIFY_MAXDELAYRATIO */
+ PERMIT_AUTH, /* MODIFY_MAXUPDATESKEW */
+ PERMIT_OPEN, /* LOGON */
+ PERMIT_AUTH, /* SETTIME */
+ PERMIT_AUTH, /* LOCAL */
+ PERMIT_AUTH, /* MANUAL */
+ PERMIT_OPEN, /* N_SOURCES */
+ PERMIT_OPEN, /* SOURCE_DATA */
+ PERMIT_AUTH, /* REKEY */
+ PERMIT_AUTH, /* ALLOW */
+ PERMIT_AUTH, /* ALLOWALL */
+ PERMIT_AUTH, /* DENY */
+ PERMIT_AUTH, /* DENYALL */
+ PERMIT_AUTH, /* CMDALLOW */
+ PERMIT_AUTH, /* CMDALLOWALL */
+ PERMIT_AUTH, /* CMDDENY */
+ PERMIT_AUTH, /* CMDDENYALL */
+ PERMIT_AUTH, /* ACCHECK */
+ PERMIT_AUTH, /* CMDACCHECK */
+ PERMIT_AUTH, /* ADD_SERVER */
+ PERMIT_AUTH, /* ADD_PEER */
+ PERMIT_AUTH, /* DEL_SOURCE */
+ PERMIT_AUTH, /* WRITERTC */
+ PERMIT_AUTH, /* DFREQ */
+ PERMIT_AUTH, /* DOFFSET */
+ PERMIT_OPEN, /* TRACKING */
+ PERMIT_OPEN, /* SOURCESTATS */
+ PERMIT_OPEN, /* RTCREPORT */
+ PERMIT_AUTH, /* TRIMRTC */
+ PERMIT_AUTH, /* CYCLELOGS */
+ PERMIT_AUTH, /* SUBNETS_ACCESSED */
+ PERMIT_AUTH, /* CLIENT_ACCESSES (by subnet) */
+ PERMIT_AUTH, /* CLIENT_ACCESSES_BY_INDEX */
+ PERMIT_OPEN, /* MANUAL_LIST */
+ PERMIT_AUTH, /* MANUAL_DELETE */
+ PERMIT_AUTH, /* MAKESTEP */
+ PERMIT_OPEN, /* ACTIVITY */
+ PERMIT_AUTH, /* MODIFY_MINSTRATUM */
+ PERMIT_AUTH, /* MODIFY_POLLTARGET */
+ PERMIT_AUTH, /* MODIFY_MAXDELAYDEVRATIO */
+ PERMIT_AUTH, /* RESELECT */
+ PERMIT_AUTH, /* RESELECTDISTANCE */
+ PERMIT_AUTH, /* MODIFY_MAKESTEP */
+ PERMIT_OPEN, /* SMOOTHING */
+ PERMIT_AUTH, /* SMOOTHTIME */
+ PERMIT_AUTH, /* REFRESH */
+ PERMIT_AUTH, /* SERVER_STATS */
+ PERMIT_AUTH, /* CLIENT_ACCESSES_BY_INDEX2 */
+ PERMIT_AUTH, /* LOCAL2 */
+ PERMIT_AUTH, /* NTP_DATA */
+ PERMIT_AUTH, /* ADD_SERVER2 */
+ PERMIT_AUTH, /* ADD_PEER2 */
+ PERMIT_AUTH, /* ADD_SERVER3 */
+ PERMIT_AUTH, /* ADD_PEER3 */
+ PERMIT_AUTH, /* SHUTDOWN */
+ PERMIT_AUTH, /* ONOFFLINE */
+ PERMIT_AUTH, /* ADD_SOURCE */
+ PERMIT_OPEN, /* NTP_SOURCE_NAME */
+ PERMIT_AUTH, /* RESET_SOURCES */
+ PERMIT_AUTH, /* AUTH_DATA */
+ PERMIT_AUTH, /* CLIENT_ACCESSES_BY_INDEX3 */
+ PERMIT_AUTH, /* SELECT_DATA */
+ PERMIT_AUTH, /* RELOAD_SOURCES */
+ PERMIT_AUTH, /* DOFFSET2 */
+ PERMIT_AUTH, /* MODIFY_SELECTOPTS */
+};
+
+/* ================================================== */
+
+/* This authorisation table is used for checking whether particular
+ machines are allowed to make command and monitoring requests. */
+static ADF_AuthTable access_auth_table;
+
+/* ================================================== */
+/* Forward prototypes */
+static void read_from_cmd_socket(int sock_fd, int event, void *anything);
+
+/* ================================================== */
+
+static int
+open_socket(int family)
+{
+ const char *local_path, *iface;
+ IPSockAddr local_addr;
+ int sock_fd, port;
+
+ switch (family) {
+ case IPADDR_INET4:
+ case IPADDR_INET6:
+ port = CNF_GetCommandPort();
+ if (port == 0 || !SCK_IsIpFamilyEnabled(family))
+ return INVALID_SOCK_FD;
+
+ CNF_GetBindCommandAddress(family, &local_addr.ip_addr);
+ local_addr.port = port;
+ iface = CNF_GetBindCommandInterface();
+
+ sock_fd = SCK_OpenUdpSocket(NULL, &local_addr, iface, 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 (family == IPADDR_INET4)
+ bound_sock_fd4 = local_addr.ip_addr.addr.in4 != INADDR_ANY;
+
+ break;
+ case IPADDR_UNSPEC:
+ local_path = CNF_GetBindCommandPath();
+
+ 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;
+ }
+
+ break;
+ default:
+ assert(0);
+ }
+
+ /* Register handler for read events on the socket */
+ SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_from_cmd_socket, NULL);
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+static void
+do_size_checks(void)
+{
+ int i, request_length, padding_length, reply_length;
+ CMD_Request request;
+ CMD_Reply reply;
+
+ assert(offsetof(CMD_Request, data) == 20);
+ assert(offsetof(CMD_Reply, data) == 28);
+
+ for (i = 0; i < N_REQUEST_TYPES; i++) {
+ request.version = PROTO_VERSION_NUMBER;
+ request.command = htons(i);
+ request_length = PKL_CommandLength(&request);
+ padding_length = PKL_CommandPaddingLength(&request);
+ if (padding_length > MAX_PADDING_LENGTH || padding_length > request_length ||
+ request_length > sizeof (CMD_Request) ||
+ (request_length && request_length < offsetof(CMD_Request, data)))
+ assert(0);
+ }
+
+ for (i = 1; i < N_REPLY_TYPES; i++) {
+ reply.reply = htons(i);
+ reply.status = STT_SUCCESS;
+ reply_length = PKL_ReplyLength(&reply);
+ if ((reply_length && reply_length < offsetof(CMD_Reply, data)) ||
+ reply_length > sizeof (CMD_Reply))
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+void
+CAM_Initialise(void)
+{
+ assert(!initialised);
+ assert(sizeof (permissions) / sizeof (permissions[0]) == N_REQUEST_TYPES);
+ do_size_checks();
+
+ initialised = 1;
+
+ bound_sock_fd4 = 0;
+
+ sock_fdu = INVALID_SOCK_FD;
+ sock_fd4 = open_socket(IPADDR_INET4);
+ sock_fd6 = open_socket(IPADDR_INET6);
+
+ access_auth_table = ADF_CreateTable();
+}
+
+/* ================================================== */
+
+void
+CAM_Finalise(void)
+{
+ if (sock_fdu != INVALID_SOCK_FD) {
+ SCH_RemoveFileHandler(sock_fdu);
+ SCK_RemoveSocket(sock_fdu);
+ SCK_CloseSocket(sock_fdu);
+ sock_fdu = INVALID_SOCK_FD;
+ }
+
+ if (sock_fd4 != INVALID_SOCK_FD) {
+ SCH_RemoveFileHandler(sock_fd4);
+ SCK_CloseSocket(sock_fd4);
+ sock_fd4 = INVALID_SOCK_FD;
+ }
+
+ if (sock_fd6 != INVALID_SOCK_FD) {
+ SCH_RemoveFileHandler(sock_fd6);
+ SCK_CloseSocket(sock_fd6);
+ sock_fd6 = INVALID_SOCK_FD;
+ }
+
+ ADF_DestroyTable(access_auth_table);
+
+ initialised = 0;
+}
+
+/* ================================================== */
+
+void
+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())
+ sock_fdu = open_socket(IPADDR_UNSPEC);
+}
+
+/* ================================================== */
+
+static void
+transmit_reply(int sock_fd, int request_length, SCK_Message *message)
+{
+ message->length = PKL_ReplyLength((CMD_Reply *)message->data);
+
+ if (request_length < message->length) {
+ DEBUG_LOG("Response longer than request req_len=%d res_len=%d",
+ request_length, message->length);
+ return;
+ }
+
+ /* Don't require responses to non-link-local addresses to use the same
+ interface */
+ if (message->addr_type == SCK_ADDR_IP &&
+ !SCK_IsLinkLocalIPAddress(&message->remote_addr.ip.ip_addr))
+ message->if_index = INVALID_IF_INDEX;
+
+#if !defined(HAVE_IN_PKTINFO) && defined(IP_SENDSRCADDR)
+ /* On FreeBSD a local IPv4 address cannot be specified on bound socket */
+ if (message->addr_type == SCK_ADDR_IP && message->local_addr.ip.family == IPADDR_INET4 &&
+ (sock_fd != sock_fd4 || bound_sock_fd4))
+ message->local_addr.ip.family = IPADDR_UNSPEC;
+#endif
+
+ if (!SCK_SendMessage(sock_fd, message, 0))
+ return;
+}
+
+/* ================================================== */
+
+static void
+handle_dump(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ SRC_DumpSources();
+ NSR_DumpAuthData();
+ NKS_DumpKeys();
+}
+
+/* ================================================== */
+
+static void
+handle_online(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address, mask;
+
+ UTI_IPNetworkToHost(&rx_message->data.online.mask, &mask);
+ UTI_IPNetworkToHost(&rx_message->data.online.address, &address);
+ if (!NSR_SetConnectivity(&mask, &address, SRC_ONLINE))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_offline(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address, mask;
+
+ UTI_IPNetworkToHost(&rx_message->data.offline.mask, &mask);
+ UTI_IPNetworkToHost(&rx_message->data.offline.address, &address);
+ if (!NSR_SetConnectivity(&mask, &address, SRC_OFFLINE))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_onoffline(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address, mask;
+
+ address.family = mask.family = IPADDR_UNSPEC;
+ if (!NSR_SetConnectivity(&mask, &address, SRC_MAYBE_ONLINE))
+ ;
+}
+
+/* ================================================== */
+
+static void
+handle_burst(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address, mask;
+
+ UTI_IPNetworkToHost(&rx_message->data.burst.mask, &mask);
+ UTI_IPNetworkToHost(&rx_message->data.burst.address, &address);
+ if (!NSR_InitiateSampleBurst(ntohl(rx_message->data.burst.n_good_samples),
+ ntohl(rx_message->data.burst.n_total_samples),
+ &mask, &address))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_minpoll(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_minpoll.address, &address);
+ if (!NSR_ModifyMinpoll(&address,
+ ntohl(rx_message->data.modify_minpoll.new_minpoll)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxpoll(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_minpoll.address, &address);
+ if (!NSR_ModifyMaxpoll(&address,
+ ntohl(rx_message->data.modify_minpoll.new_minpoll)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxdelay(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_maxdelay.address, &address);
+ if (!NSR_ModifyMaxdelay(&address,
+ UTI_FloatNetworkToHost(rx_message->data.modify_maxdelay.new_max_delay)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxdelayratio(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_maxdelayratio.address, &address);
+ if (!NSR_ModifyMaxdelayratio(&address,
+ UTI_FloatNetworkToHost(rx_message->data.modify_maxdelayratio.new_max_delay_ratio)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxdelaydevratio(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_maxdelaydevratio.address, &address);
+ if (!NSR_ModifyMaxdelaydevratio(&address,
+ UTI_FloatNetworkToHost(rx_message->data.modify_maxdelaydevratio.new_max_delay_dev_ratio)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_minstratum(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_minpoll.address, &address);
+ if (!NSR_ModifyMinstratum(&address,
+ ntohl(rx_message->data.modify_minstratum.new_min_stratum)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_polltarget(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr address;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_polltarget.address, &address);
+ if (!NSR_ModifyPolltarget(&address,
+ ntohl(rx_message->data.modify_polltarget.new_poll_target)))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_maxupdateskew(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ REF_ModifyMaxupdateskew(UTI_FloatNetworkToHost(rx_message->data.modify_maxupdateskew.new_max_update_skew));
+}
+
+/* ================================================== */
+
+static void
+handle_modify_makestep(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ REF_ModifyMakestep(ntohl(rx_message->data.modify_makestep.limit),
+ UTI_FloatNetworkToHost(rx_message->data.modify_makestep.threshold));
+}
+
+/* ================================================== */
+
+static void
+handle_settime(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ struct timespec ts;
+ double offset, dfreq_ppm, new_afreq_ppm;
+ UTI_TimespecNetworkToHost(&rx_message->data.settime.ts, &ts);
+ if (!MNL_IsEnabled()) {
+ tx_message->status = htons(STT_NOTENABLED);
+ } else if (MNL_AcceptTimestamp(&ts, &offset, &dfreq_ppm, &new_afreq_ppm)) {
+ tx_message->reply = htons(RPY_MANUAL_TIMESTAMP2);
+ tx_message->data.manual_timestamp.offset = UTI_FloatHostToNetwork(offset);
+ tx_message->data.manual_timestamp.dfreq_ppm = UTI_FloatHostToNetwork(dfreq_ppm);
+ tx_message->data.manual_timestamp.new_afreq_ppm = UTI_FloatHostToNetwork(new_afreq_ppm);
+ } else {
+ tx_message->status = htons(STT_FAILED);
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_local(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ if (ntohl(rx_message->data.local.on_off)) {
+ REF_EnableLocal(ntohl(rx_message->data.local.stratum),
+ UTI_FloatNetworkToHost(rx_message->data.local.distance),
+ ntohl(rx_message->data.local.orphan));
+ } else {
+ REF_DisableLocal();
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_manual(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ int option;
+ option = ntohl(rx_message->data.manual.option);
+ switch (option) {
+ case 0:
+ MNL_Disable();
+ break;
+ case 1:
+ MNL_Enable();
+ break;
+ case 2:
+ MNL_Reset();
+ break;
+ default:
+ tx_message->status = htons(STT_INVALID);
+ break;
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_n_sources(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ int n_sources;
+ n_sources = SRC_ReadNumberOfSources();
+ tx_message->reply = htons(RPY_N_SOURCES);
+ tx_message->data.n_sources.n_sources = htonl(n_sources);
+}
+
+/* ================================================== */
+
+static void
+handle_source_data(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_SourceReport report;
+ struct timespec now_corr;
+
+ /* Get data */
+ SCH_GetLastEventTime(&now_corr, NULL, NULL);
+ if (SRC_ReportSource(ntohl(rx_message->data.source_data.index), &report, &now_corr)) {
+ switch (SRC_GetType(ntohl(rx_message->data.source_data.index))) {
+ case SRC_NTP:
+ NSR_ReportSource(&report, &now_corr);
+ break;
+ case SRC_REFCLOCK:
+ RCL_ReportSource(&report, &now_corr);
+ break;
+ }
+
+ tx_message->reply = htons(RPY_SOURCE_DATA);
+
+ UTI_IPHostToNetwork(&report.ip_addr, &tx_message->data.source_data.ip_addr);
+ tx_message->data.source_data.stratum = htons(report.stratum);
+ tx_message->data.source_data.poll = htons(report.poll);
+ switch (report.state) {
+ case RPT_NONSELECTABLE:
+ tx_message->data.source_data.state = htons(RPY_SD_ST_NONSELECTABLE);
+ break;
+ case RPT_FALSETICKER:
+ tx_message->data.source_data.state = htons(RPY_SD_ST_FALSETICKER);
+ break;
+ case RPT_JITTERY:
+ tx_message->data.source_data.state = htons(RPY_SD_ST_JITTERY);
+ break;
+ case RPT_SELECTABLE:
+ tx_message->data.source_data.state = htons(RPY_SD_ST_SELECTABLE);
+ break;
+ case RPT_UNSELECTED:
+ tx_message->data.source_data.state = htons(RPY_SD_ST_UNSELECTED);
+ break;
+ case RPT_SELECTED:
+ tx_message->data.source_data.state = htons(RPY_SD_ST_SELECTED);
+ break;
+ }
+ switch (report.mode) {
+ case RPT_NTP_CLIENT:
+ tx_message->data.source_data.mode = htons(RPY_SD_MD_CLIENT);
+ break;
+ case RPT_NTP_PEER:
+ tx_message->data.source_data.mode = htons(RPY_SD_MD_PEER);
+ break;
+ case RPT_LOCAL_REFERENCE:
+ tx_message->data.source_data.mode = htons(RPY_SD_MD_REF);
+ break;
+ }
+ tx_message->data.source_data.flags = htons(0);
+ tx_message->data.source_data.reachability = htons(report.reachability);
+ tx_message->data.source_data.since_sample = htonl(report.latest_meas_ago);
+ tx_message->data.source_data.orig_latest_meas = UTI_FloatHostToNetwork(report.orig_latest_meas);
+ tx_message->data.source_data.latest_meas = UTI_FloatHostToNetwork(report.latest_meas);
+ tx_message->data.source_data.latest_meas_err = UTI_FloatHostToNetwork(report.latest_meas_err);
+ } else {
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_rekey(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ KEY_Reload();
+ NKS_ReloadKeys();
+}
+
+/* ================================================== */
+
+static void
+handle_allowdeny(CMD_Request *rx_message, CMD_Reply *tx_message, int allow, int all)
+{
+ IPAddr ip;
+ int subnet_bits;
+
+ UTI_IPNetworkToHost(&rx_message->data.allow_deny.ip, &ip);
+ subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+ if (!NCR_AddAccessRestriction(&ip, subnet_bits, allow, all))
+ tx_message->status = htons(STT_BADSUBNET);
+}
+
+/* ================================================== */
+
+static void
+handle_cmdallowdeny(CMD_Request *rx_message, CMD_Reply *tx_message, int allow, int all)
+{
+ IPAddr ip;
+ int subnet_bits;
+
+ UTI_IPNetworkToHost(&rx_message->data.allow_deny.ip, &ip);
+ subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits);
+ if (!CAM_AddAccessRestriction(&ip, subnet_bits, allow, all))
+ tx_message->status = htons(STT_BADSUBNET);
+}
+
+/* ================================================== */
+
+static void
+handle_accheck(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr ip;
+ UTI_IPNetworkToHost(&rx_message->data.ac_check.ip, &ip);
+ if (NCR_CheckAccessRestriction(&ip)) {
+ tx_message->status = htons(STT_ACCESSALLOWED);
+ } else {
+ tx_message->status = htons(STT_ACCESSDENIED);
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_cmdaccheck(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ IPAddr ip;
+ UTI_IPNetworkToHost(&rx_message->data.ac_check.ip, &ip);
+ if (CAM_CheckAccessRestriction(&ip)) {
+ tx_message->status = htons(STT_ACCESSALLOWED);
+ } else {
+ tx_message->status = htons(STT_ACCESSDENIED);
+ }
+}
+
+/* ================================================== */
+
+static int
+convert_addsrc_select_options(int flags)
+{
+ return (flags & REQ_ADDSRC_PREFER ? SRC_SELECT_PREFER : 0) |
+ (flags & REQ_ADDSRC_NOSELECT ? SRC_SELECT_NOSELECT : 0) |
+ (flags & REQ_ADDSRC_TRUST ? SRC_SELECT_TRUST : 0) |
+ (flags & REQ_ADDSRC_REQUIRE ? SRC_SELECT_REQUIRE : 0);
+}
+
+/* ================================================== */
+
+static void
+handle_add_source(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ NTP_Source_Type type;
+ SourceParameters params;
+ NSR_Status status;
+ char *name;
+ int pool, 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 = 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);
+ params.min_stratum = ntohl(rx_message->data.ntp_source.min_stratum);
+ params.poll_target = ntohl(rx_message->data.ntp_source.poll_target);
+ params.version = ntohl(rx_message->data.ntp_source.version);
+ params.max_sources = ntohl(rx_message->data.ntp_source.max_sources);
+ params.min_samples = ntohl(rx_message->data.ntp_source.min_samples);
+ 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.cert_set = ntohl(rx_message->data.ntp_source.cert_set);
+ 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);
+ params.max_delay_dev_ratio =
+ UTI_FloatNetworkToHost(rx_message->data.ntp_source.max_delay_dev_ratio);
+ params.max_delay_quant =
+ UTI_FloatNetworkToHost(rx_message->data.ntp_source.max_delay_quant);
+ params.min_delay = UTI_FloatNetworkToHost(rx_message->data.ntp_source.min_delay);
+ params.asymmetry = UTI_FloatNetworkToHost(rx_message->data.ntp_source.asymmetry);
+ params.offset = UTI_FloatNetworkToHost(rx_message->data.ntp_source.offset);
+
+ params.connectivity = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_ONLINE ?
+ SRC_ONLINE : SRC_OFFLINE;
+ params.auto_offline = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_AUTOOFFLINE ? 1 : 0;
+ params.iburst = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_IBURST ? 1 : 0;
+ params.interleaved = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_INTERLEAVED ? 1 : 0;
+ params.burst = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_BURST ? 1 : 0;
+ params.nts = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_NTS ? 1 : 0;
+ params.copy = ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_COPY ? 1 : 0;
+ params.ext_fields = (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_EF_EXP_MONO_ROOT ?
+ NTP_EF_FLAG_EXP_MONO_ROOT : 0) |
+ (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_EF_EXP_NET_CORRECTION ?
+ NTP_EF_FLAG_EXP_NET_CORRECTION : 0);
+ params.sel_options = convert_addsrc_select_options(ntohl(rx_message->data.ntp_source.flags));
+
+ status = NSR_AddSourceByName(name, port, pool, type, &params, NULL);
+ 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_InvalidName:
+ tx_message->status = htons(STT_INVALIDNAME);
+ break;
+ case NSR_InvalidAF:
+ case NSR_NoSuchSource:
+ assert(0);
+ break;
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_del_source(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ NSR_Status status;
+ IPAddr ip_addr;
+
+ UTI_IPNetworkToHost(&rx_message->data.del_source.ip_addr, &ip_addr);
+
+ status = NSR_RemoveSource(&ip_addr);
+ switch (status) {
+ case NSR_Success:
+ break;
+ case NSR_NoSuchSource:
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+ break;
+ case NSR_TooManySources:
+ case NSR_AlreadyInUse:
+ case NSR_InvalidAF:
+ case NSR_InvalidName:
+ case NSR_UnresolvedName:
+ assert(0);
+ break;
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_writertc(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ switch (RTC_WriteParameters()) {
+ case RTC_ST_OK:
+ break;
+ case RTC_ST_NODRV:
+ tx_message->status = htons(STT_NORTC);
+ break;
+ case RTC_ST_BADFILE:
+ tx_message->status = htons(STT_BADRTCFILE);
+ break;
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_dfreq(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ double dfreq;
+ dfreq = UTI_FloatNetworkToHost(rx_message->data.dfreq.dfreq);
+ LCL_AccumulateDeltaFrequency(dfreq * 1.0e-6);
+ LOG(LOGS_INFO, "Accumulated delta freq of %.3fppm", dfreq);
+}
+
+/* ================================================== */
+
+static void
+handle_doffset(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ double doffset;
+
+ doffset = UTI_FloatNetworkToHost(rx_message->data.doffset.doffset);
+ if (!LCL_AccumulateOffset(doffset, 0.0)) {
+ tx_message->status = htons(STT_FAILED);
+ } else {
+ LOG(LOGS_INFO, "Accumulated delta offset of %.6f seconds", doffset);
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_tracking(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_TrackingReport rpt;
+
+ REF_GetTrackingReport(&rpt);
+ tx_message->reply = htons(RPY_TRACKING);
+ tx_message->data.tracking.ref_id = htonl(rpt.ref_id);
+ UTI_IPHostToNetwork(&rpt.ip_addr, &tx_message->data.tracking.ip_addr);
+ tx_message->data.tracking.stratum = htons(rpt.stratum);
+ tx_message->data.tracking.leap_status = htons(rpt.leap_status);
+ UTI_TimespecHostToNetwork(&rpt.ref_time, &tx_message->data.tracking.ref_time);
+ tx_message->data.tracking.current_correction = UTI_FloatHostToNetwork(rpt.current_correction);
+ tx_message->data.tracking.last_offset = UTI_FloatHostToNetwork(rpt.last_offset);
+ tx_message->data.tracking.rms_offset = UTI_FloatHostToNetwork(rpt.rms_offset);
+ tx_message->data.tracking.freq_ppm = UTI_FloatHostToNetwork(rpt.freq_ppm);
+ tx_message->data.tracking.resid_freq_ppm = UTI_FloatHostToNetwork(rpt.resid_freq_ppm);
+ tx_message->data.tracking.skew_ppm = UTI_FloatHostToNetwork(rpt.skew_ppm);
+ tx_message->data.tracking.root_delay = UTI_FloatHostToNetwork(rpt.root_delay);
+ tx_message->data.tracking.root_dispersion = UTI_FloatHostToNetwork(rpt.root_dispersion);
+ tx_message->data.tracking.last_update_interval = UTI_FloatHostToNetwork(rpt.last_update_interval);
+}
+
+/* ================================================== */
+
+static void
+handle_smoothing(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_SmoothingReport report;
+ struct timespec now;
+
+ SCH_GetLastEventTime(&now, NULL, NULL);
+
+ if (!SMT_GetSmoothingReport(&report, &now)) {
+ tx_message->status = htons(STT_NOTENABLED);
+ return;
+ }
+
+ tx_message->reply = htons(RPY_SMOOTHING);
+ tx_message->data.smoothing.flags = htonl((report.active ? RPY_SMT_FLAG_ACTIVE : 0) |
+ (report.leap_only ? RPY_SMT_FLAG_LEAPONLY : 0));
+ tx_message->data.smoothing.offset = UTI_FloatHostToNetwork(report.offset);
+ tx_message->data.smoothing.freq_ppm = UTI_FloatHostToNetwork(report.freq_ppm);
+ tx_message->data.smoothing.wander_ppm = UTI_FloatHostToNetwork(report.wander_ppm);
+ tx_message->data.smoothing.last_update_ago = UTI_FloatHostToNetwork(report.last_update_ago);
+ tx_message->data.smoothing.remaining_time = UTI_FloatHostToNetwork(report.remaining_time);
+}
+
+/* ================================================== */
+
+static void
+handle_smoothtime(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ struct timespec now;
+ int option;
+
+ if (!SMT_IsEnabled()) {
+ tx_message->status = htons(STT_NOTENABLED);
+ return;
+ }
+
+ option = ntohl(rx_message->data.smoothtime.option);
+ SCH_GetLastEventTime(&now, NULL, NULL);
+
+ switch (option) {
+ case REQ_SMOOTHTIME_RESET:
+ SMT_Reset(&now);
+ break;
+ case REQ_SMOOTHTIME_ACTIVATE:
+ SMT_Activate(&now);
+ break;
+ default:
+ tx_message->status = htons(STT_INVALID);
+ break;
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_sourcestats(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ int status;
+ RPT_SourcestatsReport report;
+ struct timespec now_corr;
+
+ SCH_GetLastEventTime(&now_corr, NULL, NULL);
+ status = SRC_ReportSourcestats(ntohl(rx_message->data.sourcestats.index),
+ &report, &now_corr);
+
+ if (status) {
+ tx_message->reply = htons(RPY_SOURCESTATS);
+ tx_message->data.sourcestats.ref_id = htonl(report.ref_id);
+ UTI_IPHostToNetwork(&report.ip_addr, &tx_message->data.sourcestats.ip_addr);
+ tx_message->data.sourcestats.n_samples = htonl(report.n_samples);
+ tx_message->data.sourcestats.n_runs = htonl(report.n_runs);
+ tx_message->data.sourcestats.span_seconds = htonl(report.span_seconds);
+ tx_message->data.sourcestats.resid_freq_ppm = UTI_FloatHostToNetwork(report.resid_freq_ppm);
+ tx_message->data.sourcestats.skew_ppm = UTI_FloatHostToNetwork(report.skew_ppm);
+ tx_message->data.sourcestats.sd = UTI_FloatHostToNetwork(report.sd);
+ tx_message->data.sourcestats.est_offset = UTI_FloatHostToNetwork(report.est_offset);
+ tx_message->data.sourcestats.est_offset_err = UTI_FloatHostToNetwork(report.est_offset_err);
+ } else {
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_rtcreport(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ int status;
+ RPT_RTC_Report report;
+ status = RTC_GetReport(&report);
+ if (status) {
+ tx_message->reply = htons(RPY_RTC);
+ UTI_TimespecHostToNetwork(&report.ref_time, &tx_message->data.rtc.ref_time);
+ tx_message->data.rtc.n_samples = htons(report.n_samples);
+ tx_message->data.rtc.n_runs = htons(report.n_runs);
+ tx_message->data.rtc.span_seconds = htonl(report.span_seconds);
+ tx_message->data.rtc.rtc_seconds_fast = UTI_FloatHostToNetwork(report.rtc_seconds_fast);
+ tx_message->data.rtc.rtc_gain_rate_ppm = UTI_FloatHostToNetwork(report.rtc_gain_rate_ppm);
+ } else {
+ tx_message->status = htons(STT_NORTC);
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_trimrtc(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ if (!RTC_Trim())
+ tx_message->status = htons(STT_NORTC);
+}
+
+/* ================================================== */
+
+static void
+handle_cyclelogs(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ LOG_CycleLogFiles();
+}
+
+/* ================================================== */
+
+static void
+handle_client_accesses_by_index(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_ClientAccessByIndex_Report report;
+ RPY_ClientAccesses_Client *client;
+ int n_indices;
+ uint32_t i, j, req_first_index, req_n_clients, req_min_hits, req_reset;
+ struct timespec now;
+
+ SCH_GetLastEventTime(&now, NULL, NULL);
+
+ req_first_index = ntohl(rx_message->data.client_accesses_by_index.first_index);
+ req_n_clients = ntohl(rx_message->data.client_accesses_by_index.n_clients);
+ if (req_n_clients > MAX_CLIENT_ACCESSES)
+ req_n_clients = MAX_CLIENT_ACCESSES;
+ req_min_hits = ntohl(rx_message->data.client_accesses_by_index.min_hits);
+ req_reset = ntohl(rx_message->data.client_accesses_by_index.reset);
+
+ n_indices = CLG_GetNumberOfIndices();
+ if (n_indices < 0) {
+ tx_message->status = htons(STT_INACTIVE);
+ return;
+ }
+
+ tx_message->reply = htons(RPY_CLIENT_ACCESSES_BY_INDEX3);
+ tx_message->data.client_accesses_by_index.n_indices = htonl(n_indices);
+
+ for (i = req_first_index, j = 0; i < (uint32_t)n_indices && j < req_n_clients; i++) {
+ if (!CLG_GetClientAccessReportByIndex(i, req_reset, req_min_hits, &report, &now))
+ continue;
+
+ client = &tx_message->data.client_accesses_by_index.clients[j++];
+
+ UTI_IPHostToNetwork(&report.ip_addr, &client->ip);
+ client->ntp_hits = htonl(report.ntp_hits);
+ client->nke_hits = htonl(report.nke_hits);
+ client->cmd_hits = htonl(report.cmd_hits);
+ client->ntp_drops = htonl(report.ntp_drops);
+ client->nke_drops = htonl(report.nke_drops);
+ client->cmd_drops = htonl(report.cmd_drops);
+ client->ntp_interval = report.ntp_interval;
+ client->nke_interval = report.nke_interval;
+ client->cmd_interval = report.cmd_interval;
+ client->ntp_timeout_interval = report.ntp_timeout_interval;
+ client->last_ntp_hit_ago = htonl(report.last_ntp_hit_ago);
+ client->last_nke_hit_ago = htonl(report.last_nke_hit_ago);
+ client->last_cmd_hit_ago = htonl(report.last_cmd_hit_ago);
+ }
+
+ tx_message->data.client_accesses_by_index.next_index = htonl(i);
+ tx_message->data.client_accesses_by_index.n_clients = htonl(j);
+}
+
+/* ================================================== */
+
+static void
+handle_manual_list(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ int n_samples;
+ int i;
+ RPY_ManualListSample *sample;
+ RPT_ManualSamplesReport report[MAX_MANUAL_LIST_SAMPLES];
+
+ tx_message->reply = htons(RPY_MANUAL_LIST2);
+
+ MNL_ReportSamples(report, MAX_MANUAL_LIST_SAMPLES, &n_samples);
+ tx_message->data.manual_list.n_samples = htonl(n_samples);
+
+ for (i=0; i<n_samples; i++) {
+ sample = &tx_message->data.manual_list.samples[i];
+ UTI_TimespecHostToNetwork(&report[i].when, &sample->when);
+ sample->slewed_offset = UTI_FloatHostToNetwork(report[i].slewed_offset);
+ sample->orig_offset = UTI_FloatHostToNetwork(report[i].orig_offset);
+ sample->residual = UTI_FloatHostToNetwork(report[i].residual);
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_manual_delete(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ int index;
+
+ index = ntohl(rx_message->data.manual_delete.index);
+ if (!MNL_DeleteSample(index))
+ tx_message->status = htons(STT_BADSAMPLE);
+}
+
+/* ================================================== */
+
+static void
+handle_make_step(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ if (!LCL_MakeStep())
+ tx_message->status = htons(STT_FAILED);
+}
+
+/* ================================================== */
+
+static void
+handle_activity(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_ActivityReport report;
+ NSR_GetActivityReport(&report);
+ tx_message->data.activity.online = htonl(report.online);
+ tx_message->data.activity.offline = htonl(report.offline);
+ tx_message->data.activity.burst_online = htonl(report.burst_online);
+ tx_message->data.activity.burst_offline = htonl(report.burst_offline);
+ tx_message->data.activity.unresolved = htonl(report.unresolved);
+ tx_message->reply = htons(RPY_ACTIVITY);
+}
+
+/* ================================================== */
+
+static void
+handle_reselect_distance(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ double dist;
+ dist = UTI_FloatNetworkToHost(rx_message->data.reselect_distance.distance);
+ SRC_SetReselectDistance(dist);
+}
+
+/* ================================================== */
+
+static void
+handle_reselect(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ SRC_ReselectSource();
+}
+
+/* ================================================== */
+
+static void
+handle_refresh(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ NSR_RefreshAddresses();
+}
+
+/* ================================================== */
+
+static void
+handle_server_stats(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_ServerStatsReport report;
+
+ CLG_GetServerStatsReport(&report);
+ tx_message->reply = htons(RPY_SERVER_STATS4);
+ tx_message->data.server_stats.ntp_hits = UTI_Integer64HostToNetwork(report.ntp_hits);
+ tx_message->data.server_stats.nke_hits = UTI_Integer64HostToNetwork(report.nke_hits);
+ tx_message->data.server_stats.cmd_hits = UTI_Integer64HostToNetwork(report.cmd_hits);
+ tx_message->data.server_stats.ntp_drops = UTI_Integer64HostToNetwork(report.ntp_drops);
+ tx_message->data.server_stats.nke_drops = UTI_Integer64HostToNetwork(report.nke_drops);
+ tx_message->data.server_stats.cmd_drops = UTI_Integer64HostToNetwork(report.cmd_drops);
+ tx_message->data.server_stats.log_drops = UTI_Integer64HostToNetwork(report.log_drops);
+ tx_message->data.server_stats.ntp_auth_hits =
+ UTI_Integer64HostToNetwork(report.ntp_auth_hits);
+ tx_message->data.server_stats.ntp_interleaved_hits =
+ UTI_Integer64HostToNetwork(report.ntp_interleaved_hits);
+ tx_message->data.server_stats.ntp_timestamps =
+ UTI_Integer64HostToNetwork(report.ntp_timestamps);
+ tx_message->data.server_stats.ntp_span_seconds =
+ UTI_Integer64HostToNetwork(report.ntp_span_seconds);
+ tx_message->data.server_stats.ntp_daemon_rx_timestamps =
+ UTI_Integer64HostToNetwork(report.ntp_daemon_rx_timestamps);
+ tx_message->data.server_stats.ntp_daemon_tx_timestamps =
+ UTI_Integer64HostToNetwork(report.ntp_daemon_tx_timestamps);
+ tx_message->data.server_stats.ntp_kernel_rx_timestamps =
+ UTI_Integer64HostToNetwork(report.ntp_kernel_rx_timestamps);
+ tx_message->data.server_stats.ntp_kernel_tx_timestamps =
+ UTI_Integer64HostToNetwork(report.ntp_kernel_tx_timestamps);
+ tx_message->data.server_stats.ntp_hw_rx_timestamps =
+ UTI_Integer64HostToNetwork(report.ntp_hw_rx_timestamps);
+ tx_message->data.server_stats.ntp_hw_tx_timestamps =
+ UTI_Integer64HostToNetwork(report.ntp_hw_tx_timestamps);
+ memset(tx_message->data.server_stats.reserved, 0xff,
+ sizeof (tx_message->data.server_stats.reserved));
+}
+
+/* ================================================== */
+
+static void
+handle_ntp_data(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_NTPReport report;
+
+ UTI_IPNetworkToHost(&rx_message->data.ntp_data.ip_addr, &report.remote_addr);
+
+ if (!NSR_GetNTPReport(&report)) {
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+ return;
+ }
+
+ tx_message->reply = htons(RPY_NTP_DATA);
+ UTI_IPHostToNetwork(&report.remote_addr, &tx_message->data.ntp_data.remote_addr);
+ UTI_IPHostToNetwork(&report.local_addr, &tx_message->data.ntp_data.local_addr);
+ tx_message->data.ntp_data.remote_port = htons(report.remote_port);
+ tx_message->data.ntp_data.leap = report.leap;
+ tx_message->data.ntp_data.version = report.version;
+ tx_message->data.ntp_data.mode = report.mode;
+ tx_message->data.ntp_data.stratum = report.stratum;
+ tx_message->data.ntp_data.poll = report.poll;
+ tx_message->data.ntp_data.precision = report.precision;
+ tx_message->data.ntp_data.root_delay = UTI_FloatHostToNetwork(report.root_delay);
+ tx_message->data.ntp_data.root_dispersion = UTI_FloatHostToNetwork(report.root_dispersion);
+ tx_message->data.ntp_data.ref_id = htonl(report.ref_id);
+ UTI_TimespecHostToNetwork(&report.ref_time, &tx_message->data.ntp_data.ref_time);
+ tx_message->data.ntp_data.offset = UTI_FloatHostToNetwork(report.offset);
+ tx_message->data.ntp_data.peer_delay = UTI_FloatHostToNetwork(report.peer_delay);
+ tx_message->data.ntp_data.peer_dispersion = UTI_FloatHostToNetwork(report.peer_dispersion);
+ tx_message->data.ntp_data.response_time = UTI_FloatHostToNetwork(report.response_time);
+ tx_message->data.ntp_data.jitter_asymmetry = UTI_FloatHostToNetwork(report.jitter_asymmetry);
+ tx_message->data.ntp_data.flags = htons((report.tests & RPY_NTP_FLAGS_TESTS) |
+ (report.interleaved ? RPY_NTP_FLAG_INTERLEAVED : 0) |
+ (report.authenticated ? RPY_NTP_FLAG_AUTHENTICATED : 0));
+ tx_message->data.ntp_data.tx_tss_char = report.tx_tss_char;
+ tx_message->data.ntp_data.rx_tss_char = report.rx_tss_char;
+ tx_message->data.ntp_data.total_tx_count = htonl(report.total_tx_count);
+ tx_message->data.ntp_data.total_rx_count = htonl(report.total_rx_count);
+ tx_message->data.ntp_data.total_valid_count = htonl(report.total_valid_count);
+ tx_message->data.ntp_data.total_good_count = htonl(report.total_good_count);
+ memset(tx_message->data.ntp_data.reserved, 0xff, sizeof (tx_message->data.ntp_data.reserved));
+}
+
+/* ================================================== */
+
+static void
+handle_shutdown(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ LOG(LOGS_INFO, "Received shutdown command");
+ 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_source_name.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_reload_sources(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ CNF_ReloadSources();
+}
+
+/* ================================================== */
+
+static void
+handle_reset_sources(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);
+}
+
+/* ================================================== */
+
+static void
+handle_auth_data(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_AuthReport report;
+ IPAddr ip_addr;
+
+ UTI_IPNetworkToHost(&rx_message->data.auth_data.ip_addr, &ip_addr);
+
+ if (!NSR_GetAuthReport(&ip_addr, &report)) {
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+ return;
+ }
+
+ tx_message->reply = htons(RPY_AUTH_DATA);
+
+ switch (report.mode) {
+ case NTP_AUTH_NONE:
+ tx_message->data.auth_data.mode = htons(RPY_AD_MD_NONE);
+ break;
+ case NTP_AUTH_SYMMETRIC:
+ tx_message->data.auth_data.mode = htons(RPY_AD_MD_SYMMETRIC);
+ break;
+ case NTP_AUTH_NTS:
+ tx_message->data.auth_data.mode = htons(RPY_AD_MD_NTS);
+ break;
+ default:
+ break;
+ }
+
+ tx_message->data.auth_data.key_type = htons(report.key_type);
+ tx_message->data.auth_data.key_id = htonl(report.key_id);
+ tx_message->data.auth_data.key_length = htons(report.key_length);
+ tx_message->data.auth_data.ke_attempts = htons(report.ke_attempts);
+ tx_message->data.auth_data.last_ke_ago = htonl(report.last_ke_ago);
+ tx_message->data.auth_data.cookies = htons(report.cookies);
+ tx_message->data.auth_data.cookie_length = htons(report.cookie_length);
+ tx_message->data.auth_data.nak = htons(report.nak);
+}
+
+/* ================================================== */
+
+static uint16_t
+convert_sd_sel_options(int options)
+{
+ return (options & SRC_SELECT_PREFER ? RPY_SD_OPTION_PREFER : 0) |
+ (options & SRC_SELECT_NOSELECT ? RPY_SD_OPTION_NOSELECT : 0) |
+ (options & SRC_SELECT_TRUST ? RPY_SD_OPTION_TRUST : 0) |
+ (options & SRC_SELECT_REQUIRE ? RPY_SD_OPTION_REQUIRE : 0);
+}
+
+/* ================================================== */
+
+static void
+handle_select_data(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ RPT_SelectReport report;
+
+ if (!SRC_GetSelectReport(ntohl(rx_message->data.select_data.index), &report)) {
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+ return;
+ }
+
+ tx_message->reply = htons(RPY_SELECT_DATA);
+
+ tx_message->data.select_data.ref_id = htonl(report.ref_id);
+ UTI_IPHostToNetwork(&report.ip_addr, &tx_message->data.select_data.ip_addr);
+ tx_message->data.select_data.state_char = report.state_char;
+ tx_message->data.select_data.authentication = report.authentication;
+ tx_message->data.select_data.leap = report.leap;
+ tx_message->data.select_data.conf_options = htons(convert_sd_sel_options(report.conf_options));
+ tx_message->data.select_data.eff_options = htons(convert_sd_sel_options(report.eff_options));
+ tx_message->data.select_data.last_sample_ago = htonl(report.last_sample_ago);
+ tx_message->data.select_data.score = UTI_FloatHostToNetwork(report.score);
+ tx_message->data.select_data.hi_limit = UTI_FloatHostToNetwork(report.hi_limit);
+ tx_message->data.select_data.lo_limit = UTI_FloatHostToNetwork(report.lo_limit);
+}
+
+/* ================================================== */
+
+static void
+handle_modify_selectopts(CMD_Request *rx_message, CMD_Reply *tx_message)
+{
+ int mask, options;
+ uint32_t ref_id;
+ IPAddr ip_addr;
+
+ UTI_IPNetworkToHost(&rx_message->data.modify_select_opts.address, &ip_addr);
+ ref_id = ntohl(rx_message->data.modify_select_opts.ref_id);
+ mask = ntohl(rx_message->data.modify_select_opts.mask);
+ options = convert_addsrc_select_options(ntohl(rx_message->data.modify_select_opts.options));
+
+ if (!SRC_ModifySelectOptions(&ip_addr, ref_id, options, mask))
+ tx_message->status = htons(STT_NOSUCHSOURCE);
+}
+
+/* ================================================== */
+/* 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;
+ IPAddr loopback_addr, remote_ip;
+ int read_length, expected_length;
+ int localhost, allowed, log_index;
+ uint16_t rx_command;
+ struct timespec now, cooked_now;
+
+ sck_message = SCK_ReceiveMessage(sock_fd, 0);
+ if (!sck_message)
+ return;
+
+ read_length = sck_message->length;
+
+ /* Get current time cheaply */
+ SCH_GetLastEventTime(&cooked_now, NULL, &now);
+
+ /* 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;
+
+ 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:
+ DEBUG_LOG("Unexpected address type");
+ return;
+ }
+
+ if (read_length < offsetof(CMD_Request, data) ||
+ read_length < offsetof(CMD_Reply, data) ||
+ 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_LogServiceAccess(CLG_CMDMON, &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_LimitServiceRate(CLG_CMDMON, 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;
+ tx_message.command = rx_message.command;
+ tx_message.reply = htons(RPY_NULL);
+ tx_message.status = htons(STT_SUCCESS);
+ tx_message.sequence = rx_message.sequence;
+
+ if (rx_message.version != PROTO_VERSION_NUMBER) {
+ DEBUG_LOG("Command packet has invalid version (%d != %d)",
+ rx_message.version, PROTO_VERSION_NUMBER);
+
+ if (rx_message.version >= PROTO_VERSION_MISMATCH_COMPAT_SERVER) {
+ tx_message.status = htons(STT_BADPKTVERSION);
+ transmit_reply(sock_fd, read_length, sck_message);
+ }
+ return;
+ }
+
+ if (rx_command >= N_REQUEST_TYPES ||
+ expected_length < (int)offsetof(CMD_Request, data)) {
+ DEBUG_LOG("Command packet has invalid command %d", rx_command);
+
+ tx_message.status = htons(STT_INVALID);
+ transmit_reply(sock_fd, read_length, sck_message);
+ return;
+ }
+
+ if (read_length < expected_length) {
+ DEBUG_LOG("Command packet is too short (%d < %d)", read_length,
+ expected_length);
+
+ tx_message.status = htons(STT_BADPKTLENGTH);
+ transmit_reply(sock_fd, read_length, sck_message);
+ return;
+ }
+
+ /* OK, we have a valid message. Now dispatch on message type and process it. */
+
+ if (rx_command >= N_REQUEST_TYPES) {
+ /* This should be already handled */
+ assert(0);
+ } else {
+ /* 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 (remote_ip.family == IPADDR_UNSPEC) {
+ assert(sock_fd == sock_fdu);
+ allowed = 1;
+ } else {
+ switch (permissions[rx_command]) {
+ case PERMIT_AUTH:
+ allowed = 0;
+ break;
+ case PERMIT_LOCAL:
+ allowed = localhost;
+ break;
+ case PERMIT_OPEN:
+ allowed = 1;
+ break;
+ default:
+ assert(0);
+ allowed = 0;
+ }
+ }
+
+ if (allowed) {
+ LOG_SetContext(LOGC_Command);
+
+ switch(rx_command) {
+ case REQ_NULL:
+ /* Do nothing */
+ break;
+
+ case REQ_DUMP:
+ handle_dump(&rx_message, &tx_message);
+ break;
+
+ case REQ_ONLINE:
+ handle_online(&rx_message, &tx_message);
+ break;
+
+ case REQ_OFFLINE:
+ handle_offline(&rx_message, &tx_message);
+ break;
+
+ case REQ_BURST:
+ handle_burst(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MINPOLL:
+ handle_modify_minpoll(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MAXPOLL:
+ handle_modify_maxpoll(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MAXDELAY:
+ handle_modify_maxdelay(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MAXDELAYRATIO:
+ handle_modify_maxdelayratio(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MAXDELAYDEVRATIO:
+ handle_modify_maxdelaydevratio(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MAXUPDATESKEW:
+ handle_modify_maxupdateskew(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MAKESTEP:
+ handle_modify_makestep(&rx_message, &tx_message);
+ break;
+
+ case REQ_LOGON:
+ /* Authentication is no longer supported, log-on always fails */
+ tx_message.status = htons(STT_FAILED);
+ break;
+
+ case REQ_SETTIME:
+ handle_settime(&rx_message, &tx_message);
+ break;
+
+ case REQ_LOCAL2:
+ handle_local(&rx_message, &tx_message);
+ break;
+
+ case REQ_MANUAL:
+ handle_manual(&rx_message, &tx_message);
+ break;
+
+ case REQ_N_SOURCES:
+ handle_n_sources(&rx_message, &tx_message);
+ break;
+
+ case REQ_SOURCE_DATA:
+ handle_source_data(&rx_message, &tx_message);
+ break;
+
+ case REQ_REKEY:
+ handle_rekey(&rx_message, &tx_message);
+ break;
+
+ case REQ_ALLOW:
+ handle_allowdeny(&rx_message, &tx_message, 1, 0);
+ break;
+
+ case REQ_ALLOWALL:
+ handle_allowdeny(&rx_message, &tx_message, 1, 1);
+ break;
+
+ case REQ_DENY:
+ handle_allowdeny(&rx_message, &tx_message, 0, 0);
+ break;
+
+ case REQ_DENYALL:
+ handle_allowdeny(&rx_message, &tx_message, 0, 1);
+ break;
+
+ case REQ_CMDALLOW:
+ handle_cmdallowdeny(&rx_message, &tx_message, 1, 0);
+ break;
+
+ case REQ_CMDALLOWALL:
+ handle_cmdallowdeny(&rx_message, &tx_message, 1, 1);
+ break;
+
+ case REQ_CMDDENY:
+ handle_cmdallowdeny(&rx_message, &tx_message, 0, 0);
+ break;
+
+ case REQ_CMDDENYALL:
+ handle_cmdallowdeny(&rx_message, &tx_message, 0, 1);
+ break;
+
+ case REQ_ACCHECK:
+ handle_accheck(&rx_message, &tx_message);
+ break;
+
+ case REQ_CMDACCHECK:
+ handle_cmdaccheck(&rx_message, &tx_message);
+ break;
+
+ case REQ_ADD_SOURCE:
+ handle_add_source(&rx_message, &tx_message);
+ break;
+
+ case REQ_DEL_SOURCE:
+ handle_del_source(&rx_message, &tx_message);
+ break;
+
+ case REQ_WRITERTC:
+ handle_writertc(&rx_message, &tx_message);
+ break;
+
+ case REQ_DFREQ:
+ handle_dfreq(&rx_message, &tx_message);
+ break;
+
+ case REQ_DOFFSET2:
+ handle_doffset(&rx_message, &tx_message);
+ break;
+
+ case REQ_TRACKING:
+ handle_tracking(&rx_message, &tx_message);
+ break;
+
+ case REQ_SMOOTHING:
+ handle_smoothing(&rx_message, &tx_message);
+ break;
+
+ case REQ_SMOOTHTIME:
+ handle_smoothtime(&rx_message, &tx_message);
+ break;
+
+ case REQ_SOURCESTATS:
+ handle_sourcestats(&rx_message, &tx_message);
+ break;
+
+ case REQ_RTCREPORT:
+ handle_rtcreport(&rx_message, &tx_message);
+ break;
+
+ case REQ_TRIMRTC:
+ handle_trimrtc(&rx_message, &tx_message);
+ break;
+
+ case REQ_CYCLELOGS:
+ handle_cyclelogs(&rx_message, &tx_message);
+ break;
+
+ case REQ_CLIENT_ACCESSES_BY_INDEX3:
+ handle_client_accesses_by_index(&rx_message, &tx_message);
+ break;
+
+ case REQ_MANUAL_LIST:
+ handle_manual_list(&rx_message, &tx_message);
+ break;
+
+ case REQ_MANUAL_DELETE:
+ handle_manual_delete(&rx_message, &tx_message);
+ break;
+
+ case REQ_MAKESTEP:
+ handle_make_step(&rx_message, &tx_message);
+ break;
+
+ case REQ_ACTIVITY:
+ handle_activity(&rx_message, &tx_message);
+ break;
+
+ case REQ_RESELECTDISTANCE:
+ handle_reselect_distance(&rx_message, &tx_message);
+ break;
+
+ case REQ_RESELECT:
+ handle_reselect(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_MINSTRATUM:
+ handle_modify_minstratum(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_POLLTARGET:
+ handle_modify_polltarget(&rx_message, &tx_message);
+ break;
+
+ case REQ_REFRESH:
+ handle_refresh(&rx_message, &tx_message);
+ break;
+
+ case REQ_SERVER_STATS:
+ handle_server_stats(&rx_message, &tx_message);
+ break;
+
+ case REQ_NTP_DATA:
+ handle_ntp_data(&rx_message, &tx_message);
+ break;
+
+ case REQ_SHUTDOWN:
+ handle_shutdown(&rx_message, &tx_message);
+ break;
+
+ case REQ_ONOFFLINE:
+ handle_onoffline(&rx_message, &tx_message);
+ break;
+
+ case REQ_NTP_SOURCE_NAME:
+ handle_ntp_source_name(&rx_message, &tx_message);
+ break;
+
+ case REQ_RESET_SOURCES:
+ handle_reset_sources(&rx_message, &tx_message);
+ break;
+
+ case REQ_AUTH_DATA:
+ handle_auth_data(&rx_message, &tx_message);
+ break;
+
+ case REQ_SELECT_DATA:
+ handle_select_data(&rx_message, &tx_message);
+ break;
+
+ case REQ_RELOAD_SOURCES:
+ handle_reload_sources(&rx_message, &tx_message);
+ break;
+
+ case REQ_MODIFY_SELECTOPTS:
+ handle_modify_selectopts(&rx_message, &tx_message);
+ break;
+
+ default:
+ DEBUG_LOG("Unhandled command %d", rx_command);
+ tx_message.status = htons(STT_FAILED);
+ break;
+ }
+
+ LOG_UnsetContext(LOGC_Command);
+ } else {
+ tx_message.status = htons(STT_UNAUTH);
+ }
+ }
+
+ /* Transmit the response */
+ transmit_reply(sock_fd, read_length, sck_message);
+}
+
+/* ================================================== */
+
+int
+CAM_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all)
+ {
+ ADF_Status status;
+
+ if (allow) {
+ if (all) {
+ status = ADF_AllowAll(access_auth_table, ip_addr, subnet_bits);
+ } else {
+ status = ADF_Allow(access_auth_table, ip_addr, subnet_bits);
+ }
+ } else {
+ if (all) {
+ status = ADF_DenyAll(access_auth_table, ip_addr, subnet_bits);
+ } else {
+ status = ADF_Deny(access_auth_table, ip_addr, subnet_bits);
+ }
+ }
+
+ if (status == ADF_BADSUBNET) {
+ return 0;
+ } else if (status == ADF_SUCCESS) {
+ LOG(LOG_GetContextSeverity(LOGC_Command), "%s%s %s access from %s",
+ allow ? "Allowed" : "Denied", all ? " all" : "", "command",
+ UTI_IPSubnetToString(ip_addr, subnet_bits));
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+int
+CAM_CheckAccessRestriction(IPAddr *ip_addr)
+{
+ return ADF_IsAllowed(access_auth_table, ip_addr);
+}
+
+
+/* ================================================== */
+/* ================================================== */
diff --git a/cmdmon.h b/cmdmon.h
new file mode 100644
index 0000000..86356b9
--- /dev/null
+++ b/cmdmon.h
@@ -0,0 +1,40 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 control and monitoring module in the software
+ */
+
+#ifndef GOT_CMDMON_H
+#define GOT_CMDMON_H
+
+#include "addressing.h"
+
+extern void CAM_Initialise(void);
+
+extern void CAM_Finalise(void);
+
+extern void CAM_OpenUnixSocket(void);
+extern int CAM_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all);
+extern int CAM_CheckAccessRestriction(IPAddr *ip_addr);
+
+#endif /* GOT_CMDMON_H */
diff --git a/cmdparse.c b/cmdparse.c
new file mode 100644
index 0000000..ac5ace2
--- /dev/null
+++ b/cmdparse.c
@@ -0,0 +1,428 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2013-2014, 2016, 2021
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Module for parsing various forms of directive and command lines that
+ are common to the configuration file and to the command client.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "cmdparse.h"
+#include "memory.h"
+#include "nameserv.h"
+#include "ntp.h"
+#include "util.h"
+
+/* ================================================== */
+
+int
+CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src)
+{
+ char *hostname, *cmd;
+ uint32_t ef_type;
+ int n, sel_option;
+
+ src->port = SRC_DEFAULT_PORT;
+ src->params.minpoll = SRC_DEFAULT_MINPOLL;
+ src->params.maxpoll = SRC_DEFAULT_MAXPOLL;
+ src->params.connectivity = SRC_ONLINE;
+ src->params.auto_offline = 0;
+ src->params.presend_minpoll = SRC_DEFAULT_PRESEND_MINPOLL;
+ src->params.burst = 0;
+ src->params.iburst = 0;
+ src->params.min_stratum = SRC_DEFAULT_MINSTRATUM;
+ src->params.poll_target = SRC_DEFAULT_POLLTARGET;
+ src->params.version = 0;
+ src->params.max_sources = SRC_DEFAULT_MAXSOURCES;
+ src->params.min_samples = SRC_DEFAULT_MINSAMPLES;
+ src->params.max_samples = SRC_DEFAULT_MAXSAMPLES;
+ 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.copy = 0;
+ src->params.ext_fields = 0;
+ src->params.authkey = INACTIVE_AUTHKEY;
+ src->params.cert_set = SRC_DEFAULT_CERTSET;
+ src->params.max_delay = SRC_DEFAULT_MAXDELAY;
+ src->params.max_delay_ratio = SRC_DEFAULT_MAXDELAYRATIO;
+ src->params.max_delay_dev_ratio = SRC_DEFAULT_MAXDELAYDEVRATIO;
+ src->params.max_delay_quant = 0.0;
+ src->params.min_delay = 0.0;
+ src->params.asymmetry = SRC_DEFAULT_ASYMMETRY;
+ src->params.offset = 0.0;
+
+ hostname = line;
+ line = CPS_SplitWord(line);
+
+ if (!*hostname)
+ return 0;
+
+ src->name = hostname;
+
+ /* Parse options */
+ for (; *line; line += n) {
+ cmd = line;
+ line = CPS_SplitWord(line);
+ n = 0;
+
+ if (!strcasecmp(cmd, "auto_offline")) {
+ src->params.auto_offline = 1;
+ } else if (!strcasecmp(cmd, "burst")) {
+ src->params.burst = 1;
+ } else if (!strcasecmp(cmd, "copy")) {
+ src->params.copy = 1;
+ } else if (!strcasecmp(cmd, "iburst")) {
+ src->params.iburst = 1;
+ } else if (!strcasecmp(cmd, "offline")) {
+ src->params.connectivity = SRC_OFFLINE;
+ } else if (!strcasecmp(cmd, "certset")) {
+ if (sscanf(line, "%"SCNu32"%n", &src->params.cert_set, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "key")) {
+ if (sscanf(line, "%"SCNu32"%n", &src->params.authkey, &n) != 1 ||
+ src->params.authkey == INACTIVE_AUTHKEY)
+ return 0;
+ } else if (!strcasecmp(cmd, "asymmetry")) {
+ if (sscanf(line, "%lf%n", &src->params.asymmetry, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "extfield")) {
+ if (sscanf(line, "%"SCNx32"%n", &ef_type, &n) != 1)
+ return 0;
+ switch (ef_type) {
+ case NTP_EF_EXP_MONO_ROOT:
+ src->params.ext_fields |= NTP_EF_FLAG_EXP_MONO_ROOT;
+ break;
+ case NTP_EF_EXP_NET_CORRECTION:
+ src->params.ext_fields |= NTP_EF_FLAG_EXP_NET_CORRECTION;
+ break;
+ default:
+ return 0;
+ }
+ } else if (!strcasecmp(cmd, "filter")) {
+ if (sscanf(line, "%d%n", &src->params.filter_length, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "maxdelay")) {
+ if (sscanf(line, "%lf%n", &src->params.max_delay, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "maxdelayratio")) {
+ if (sscanf(line, "%lf%n", &src->params.max_delay_ratio, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "maxdelaydevratio")) {
+ if (sscanf(line, "%lf%n", &src->params.max_delay_dev_ratio, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "maxdelayquant")) {
+ if (sscanf(line, "%lf%n", &src->params.max_delay_quant, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "maxpoll")) {
+ if (sscanf(line, "%d%n", &src->params.maxpoll, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "maxsamples")) {
+ if (sscanf(line, "%d%n", &src->params.max_samples, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "maxsources")) {
+ if (sscanf(line, "%d%n", &src->params.max_sources, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "mindelay")) {
+ if (sscanf(line, "%lf%n", &src->params.min_delay, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "minpoll")) {
+ if (sscanf(line, "%d%n", &src->params.minpoll, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "minsamples")) {
+ if (sscanf(line, "%d%n", &src->params.min_samples, &n) != 1)
+ return 0;
+ } 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;
+ } else if (!strcasecmp(cmd, "port")) {
+ if (sscanf(line, "%d%n", &src->port, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "polltarget")) {
+ if (sscanf(line, "%d%n", &src->params.poll_target, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "presend")) {
+ if (sscanf(line, "%d%n", &src->params.presend_minpoll, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "version")) {
+ if (sscanf(line, "%d%n", &src->params.version, &n) != 1)
+ return 0;
+ } else if (!strcasecmp(cmd, "xleave")) {
+ src->params.interleaved = 1;
+ } else if ((sel_option = CPS_GetSelectOption(cmd)) != 0) {
+ src->params.sel_options |= sel_option;
+ } else {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+CPS_GetSelectOption(char *option)
+{
+ if (!strcasecmp(option, "noselect")) {
+ return SRC_SELECT_NOSELECT;
+ } else if (!strcasecmp(option, "prefer")) {
+ return SRC_SELECT_PREFER;
+ } else if (!strcasecmp(option, "require")) {
+ return SRC_SELECT_REQUIRE;
+ } else if (!strcasecmp(option, "trust")) {
+ return SRC_SELECT_TRUST;
+ }
+ return 0;
+}
+
+/* ================================================== */
+
+int
+CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits)
+{
+ char *p, *net, *slash;
+ uint32_t a, b, c;
+ int bits, len, n;
+
+ p = CPS_SplitWord(line);
+
+ if (strcmp(line, "all") == 0) {
+ *all = 1;
+ net = p;
+ p = CPS_SplitWord(p);
+ } else {
+ *all = 0;
+ net = line;
+ }
+
+ /* Make sure there are no other arguments */
+ if (*p)
+ return 0;
+
+ /* No specified address or network means all IPv4 and IPv6 addresses */
+ if (!*net) {
+ ip->family = IPADDR_UNSPEC;
+ *subnet_bits = 0;
+ return 1;
+ }
+
+ slash = strchr(net, '/');
+ if (slash) {
+ if (sscanf(slash + 1, "%d%n", &bits, &len) != 1 || slash[len + 1] || bits < 0)
+ return 0;
+ *slash = '\0';
+ } else {
+ bits = -1;
+ }
+
+ if (UTI_StringToIP(net, ip)) {
+ if (bits >= 0)
+ *subnet_bits = bits;
+ else
+ *subnet_bits = ip->family == IPADDR_INET6 ? 128 : 32;
+ return 1;
+ }
+
+ /* Check for a shortened IPv4 network notation using only 1, 2, or 3 decimal
+ numbers. This is different than the numbers-and-dots notation accepted
+ by inet_aton()! */
+
+ a = b = c = 0;
+ n = sscanf(net, "%"PRIu32"%n.%"PRIu32"%n.%"PRIu32"%n", &a, &len, &b, &len, &c, &len);
+
+ if (n > 0 && !net[len]) {
+ if (a > 255 || b > 255 || c > 255)
+ return 0;
+
+ ip->family = IPADDR_INET4;
+ ip->addr.in4 = (a << 24) | (b << 16) | (c << 8);
+
+ if (bits >= 0)
+ *subnet_bits = bits;
+ else
+ *subnet_bits = n * 8;
+
+ return 1;
+ }
+
+ /* The last possibility is a hostname */
+ if (bits < 0 && DNS_Name2IPAddress(net, ip, 1) == DNS_Success) {
+ *subnet_bits = ip->family == IPADDR_INET6 ? 128 : 32;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance)
+{
+ int n;
+ char *cmd;
+
+ *stratum = 10;
+ *distance = 1.0;
+ *orphan = 0;
+
+ while (*line) {
+ cmd = line;
+ line = CPS_SplitWord(line);
+
+ if (!strcasecmp(cmd, "stratum")) {
+ if (sscanf(line, "%d%n", stratum, &n) != 1 ||
+ *stratum >= NTP_MAX_STRATUM || *stratum <= 0)
+ return 0;
+ } else if (!strcasecmp(cmd, "orphan")) {
+ *orphan = 1;
+ n = 0;
+ } else if (!strcasecmp(cmd, "distance")) {
+ if (sscanf(line, "%lf%n", distance, &n) != 1)
+ return 0;
+ } else {
+ return 0;
+ }
+
+ line += n;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+CPS_NormalizeLine(char *line)
+{
+ char *p, *q;
+ int space = 1, first = 1;
+
+ /* Remove white-space at beginning and replace white-spaces with space char */
+ for (p = q = line; *p; p++) {
+ if (isspace((unsigned char)*p)) {
+ if (!space)
+ *q++ = ' ';
+ space = 1;
+ continue;
+ }
+
+ /* Discard comment lines */
+ if (first && strchr("!;#%", *p))
+ break;
+
+ *q++ = *p;
+ space = first = 0;
+ }
+
+ /* Strip trailing space */
+ if (q > line && q[-1] == ' ')
+ q--;
+
+ *q = '\0';
+}
+
+/* ================================================== */
+
+char *
+CPS_SplitWord(char *line)
+{
+ char *p = line, *q = line;
+
+ /* Skip white-space before the word */
+ while (*q && isspace((unsigned char)*q))
+ q++;
+
+ /* Move the word to the beginning */
+ while (*q && !isspace((unsigned char)*q))
+ *p++ = *q++;
+
+ /* Find the next word */
+ while (*q && isspace((unsigned char)*q))
+ q++;
+
+ *p = '\0';
+
+ /* Return pointer to the next word or NUL */
+ return q;
+}
+
+/* ================================================== */
+
+int
+CPS_ParseKey(char *line, uint32_t *id, const char **type, char **key)
+{
+ char *s1, *s2, *s3, *s4;
+
+ s1 = line;
+ s2 = CPS_SplitWord(s1);
+ s3 = CPS_SplitWord(s2);
+ s4 = CPS_SplitWord(s3);
+
+ /* Require two or three words */
+ if (!*s2 || *s4)
+ return 0;
+
+ if (sscanf(s1, "%"SCNu32, id) != 1)
+ return 0;
+
+ if (*s3) {
+ *type = s2;
+ *key = s3;
+ } else {
+ *type = "MD5";
+ *key = s2;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+CPS_ParseRefid(char *line, uint32_t *ref_id)
+{
+ int i;
+
+ for (i = *ref_id = 0; line[i] && !isspace((unsigned char)line[i]); i++) {
+ if (i >= 4)
+ return 0;
+ *ref_id |= (uint32_t)line[i] << (24 - i * 8);
+ }
+
+ return i;
+}
diff --git a/cmdparse.h b/cmdparse.h
new file mode 100644
index 0000000..7a87979
--- /dev/null
+++ b/cmdparse.h
@@ -0,0 +1,63 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 command parser
+ */
+
+#ifndef GOT_CMDPARSE_H
+#define GOT_CMDPARSE_H
+
+#include "srcparams.h"
+#include "addressing.h"
+
+typedef struct {
+ char *name;
+ int port;
+ SourceParameters params;
+} CPS_NTP_Source;
+
+/* Parse a command to add an NTP server or peer */
+extern int CPS_ParseNTPSourceAdd(char *line, CPS_NTP_Source *src);
+
+/* Get an NTP/refclock select option */
+extern int CPS_GetSelectOption(char *option);
+
+/* Parse a command to allow/deny access */
+extern int CPS_ParseAllowDeny(char *line, int *all, IPAddr *ip, int *subnet_bits);
+
+/* Parse a command to enable local reference */
+extern int CPS_ParseLocal(char *line, int *stratum, int *orphan, double *distance);
+
+/* Remove extra white-space and comments */
+extern void CPS_NormalizeLine(char *line);
+
+/* Terminate first word and return pointer to the next word */
+extern char *CPS_SplitWord(char *line);
+
+/* Parse a key from keyfile */
+extern int CPS_ParseKey(char *line, uint32_t *id, const char **type, char **key);
+
+/* Parse a refclock reference ID (returns number of characters) */
+extern int CPS_ParseRefid(char *line, uint32_t *ref_id);
+
+#endif /* GOT_CMDPARSE_H */
diff --git a/conf.c b/conf.c
new file mode 100644
index 0000000..fa74459
--- /dev/null
+++ b/conf.c
@@ -0,0 +1,2647 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009-2017, 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Module that reads and processes the configuration file.
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "conf.h"
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "nts_ke.h"
+#include "refclock.h"
+#include "cmdmon.h"
+#include "socket.h"
+#include "srcparams.h"
+#include "logging.h"
+#include "nameserv.h"
+#include "memory.h"
+#include "cmdparse.h"
+#include "util.h"
+
+/* ================================================== */
+
+#define MAX_LINE_LENGTH 2048
+#define MAX_CONF_DIRS 10
+#define MAX_INCLUDE_LEVEL 10
+
+/* ================================================== */
+/* Forward prototypes */
+
+static int parse_string(char *line, char **result);
+static int parse_int(char *line, int *result);
+static int parse_double(char *line, double *result);
+static int parse_null(char *line);
+
+static void parse_allow_deny(char *line, ARR_Instance restrictions, int allow);
+static void parse_authselectmode(char *);
+static void parse_bindacqaddress(char *);
+static void parse_bindaddress(char *);
+static void parse_bindcmdaddress(char *);
+static void parse_broadcast(char *);
+static void parse_clientloglimit(char *);
+static void parse_confdir(char *);
+static void parse_fallbackdrift(char *);
+static void parse_hwtimestamp(char *);
+static void parse_include(char *);
+static void parse_initstepslew(char *);
+static void parse_leapsecmode(char *);
+static void parse_local(char *);
+static void parse_log(char *);
+static void parse_mailonchange(char *);
+static void parse_makestep(char *);
+static void parse_maxchange(char *);
+static void parse_ntsserver(char *, ARR_Instance files);
+static void parse_ntstrustedcerts(char *);
+static void parse_ratelimit(char *line, int *enabled, int *interval,
+ int *burst, int *leak);
+static void parse_refclock(char *);
+static void parse_smoothtime(char *);
+static void parse_source(char *line, char *type, int fatal);
+static void parse_sourcedir(char *);
+static void parse_tempcomp(char *);
+
+/* ================================================== */
+/* Configuration variables */
+
+static int print_config = 0;
+static int restarted = 0;
+static char *rtc_device;
+static int acquisition_port = -1;
+static int ntp_port = NTP_PORT;
+static char *keys_file = NULL;
+static char *drift_file = NULL;
+static char *rtc_file = NULL;
+static double max_update_skew = 1000.0;
+static double correction_time_ratio = 3.0;
+static double max_clock_error = 1.0; /* in ppm */
+static double max_drift = 500000.0; /* in ppm */
+static double max_slew_rate = 1e6 / 12.0; /* in ppm */
+static double clock_precision = 0.0; /* in seconds */
+
+static SRC_AuthSelectMode authselect_mode = SRC_AUTHSELECT_MIX;
+static double max_distance = 3.0;
+static double max_jitter = 1.0;
+static double reselect_distance = 1e-4;
+static double stratum_weight = 1e-3;
+static double combine_limit = 3.0;
+
+static int cmd_port = DEFAULT_CANDM_PORT;
+
+static int raw_measurements = 0;
+static int do_log_measurements = 0;
+static int do_log_selection = 0;
+static int do_log_statistics = 0;
+static int do_log_tracking = 0;
+static int do_log_rtc = 0;
+static int do_log_refclocks = 0;
+static int do_log_tempcomp = 0;
+static int log_banner = 32;
+static char *logdir = NULL;
+static char *dumpdir = NULL;
+
+static int enable_local=0;
+static int local_stratum;
+static int local_orphan;
+static double local_distance;
+
+/* Threshold (in seconds) - if absolute value of initial error is less
+ than this, slew instead of stepping */
+static double init_slew_threshold;
+/* Array of IPAddr */
+static ARR_Instance init_sources;
+
+static int enable_manual=0;
+
+/* Flag set if the RTC runs UTC (default is it runs local time
+ incl. daylight saving). */
+static int rtc_on_utc = 0;
+
+/* Filename used to read the hwclock(8) LOCAL/UTC setting */
+static char *hwclock_file;
+
+/* Flag set if the RTC should be automatically synchronised by kernel */
+static int rtc_sync = 0;
+
+/* Limit and threshold for clock stepping */
+static int make_step_limit = 0;
+static double make_step_threshold = 0.0;
+
+/* Threshold for automatic RTC trimming */
+static double rtc_autotrim_threshold = 0.0;
+
+/* Minimum number of selectables sources required to update the clock */
+static int min_sources = 1;
+
+/* Number of updates before offset checking, number of ignored updates
+ before exiting and the maximum allowed offset */
+static int max_offset_delay = -1;
+static int max_offset_ignore;
+static double max_offset;
+
+/* Maximum and minimum number of samples per source */
+static int max_samples = 0; /* no limit */
+static int min_samples = 6;
+
+/* Threshold for a time adjustment to be logged to syslog */
+static double log_change_threshold = 1.0;
+
+static char *mail_user_on_change = NULL;
+static double mail_change_threshold = 0.0;
+
+/* Flag indicating that we don't want to log clients, e.g. to save
+ memory */
+static int no_client_log = 0;
+
+/* Limit memory allocated for the clients log */
+static unsigned long client_log_limit = 524288;
+
+/* Minimum and maximum fallback drift intervals */
+static int fb_drift_min = 0;
+static int fb_drift_max = 0;
+
+/* IP addresses for binding the NTP server sockets to. UNSPEC family means
+ INADDR_ANY will be used */
+static IPAddr bind_address4, bind_address6;
+
+/* IP addresses for binding the NTP client sockets to. UNSPEC family means
+ INADDR_ANY will be used */
+static IPAddr bind_acq_address4, bind_acq_address6;
+
+/* IP addresses for binding the command socket to. UNSPEC family means
+ the loopback address will be used */
+static IPAddr bind_cmd_address4, bind_cmd_address6;
+
+/* Interface names to bind the NTP server, NTP client, and command socket */
+static char *bind_ntp_iface = NULL;
+static char *bind_acq_iface = NULL;
+static char *bind_cmd_iface = NULL;
+
+/* Path to the Unix domain command socket. */
+static char *bind_cmd_path = NULL;
+
+/* Differentiated Services Code Point (DSCP) in transmitted NTP packets */
+static int ntp_dscp = 0;
+
+/* Path to Samba (ntp_signd) socket. */
+static char *ntp_signd_socket = NULL;
+
+/* Filename to use for storing pid of running chronyd, to prevent multiple
+ * chronyds being started. */
+static char *pidfile = NULL;
+
+/* Rate limiting parameters */
+static int ntp_ratelimit_enabled = 0;
+static int ntp_ratelimit_interval = 3;
+static int ntp_ratelimit_burst = 8;
+static int ntp_ratelimit_leak = 2;
+static int nts_ratelimit_enabled = 0;
+static int nts_ratelimit_interval = 6;
+static int nts_ratelimit_burst = 8;
+static int nts_ratelimit_leak = 2;
+static int cmd_ratelimit_enabled = 0;
+static int cmd_ratelimit_interval = -4;
+static int cmd_ratelimit_burst = 8;
+static int cmd_ratelimit_leak = 2;
+
+/* Smoothing constants */
+static double smooth_max_freq = 0.0; /* in ppm */
+static double smooth_max_wander = 0.0; /* in ppm/s */
+static int smooth_leap_only = 0;
+
+/* Temperature sensor, update interval and compensation coefficients */
+static char *tempcomp_sensor_file = NULL;
+static char *tempcomp_point_file = NULL;
+static double tempcomp_interval;
+static double tempcomp_T0, tempcomp_k0, tempcomp_k1, tempcomp_k2;
+
+static int sched_priority = 0;
+static int lock_memory = 0;
+
+/* Leap second handling mode */
+static REF_LeapMode leapsec_mode = REF_LeapModeSystem;
+
+/* Name of a system timezone containing leap seconds occuring at midnight */
+static char *leapsec_tz = NULL;
+
+/* Name of the user to which will be dropped root privileges. */
+static char *user;
+
+/* Address refresh interval */
+static int refresh = 1209600; /* 2 weeks */
+
+/* NTS server and client configuration */
+static char *nts_dump_dir = NULL;
+static char *nts_ntp_server = NULL;
+static ARR_Instance nts_server_cert_files; /* array of (char *) */
+static ARR_Instance nts_server_key_files; /* array of (char *) */
+static int nts_server_port = NKE_PORT;
+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 ARR_Instance nts_trusted_certs_paths; /* array of (char *) */
+static ARR_Instance nts_trusted_certs_ids; /* array of uint32_t */
+
+/* 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;
+
+/* Timeout for resuming reading from sockets waiting for HW TX timestamp */
+static double hwts_timeout = 0.001;
+
+/* PTP event port (disabled by default) */
+static int ptp_port = 0;
+
+typedef struct {
+ NTP_Source_Type type;
+ int pool;
+ CPS_NTP_Source params;
+} NTP_Source;
+
+/* Array of NTP_Source */
+static ARR_Instance ntp_sources;
+/* Array of (char *) */
+static ARR_Instance ntp_source_dirs;
+/* Array of uint32_t corresponding to ntp_sources (for sourcedirs reload) */
+static ARR_Instance ntp_source_ids;
+
+/* Array of RefclockParameters */
+static ARR_Instance refclock_sources;
+
+typedef struct _AllowDeny {
+ IPAddr ip;
+ int subnet_bits;
+ int all; /* 1 to override existing more specific defns */
+ int allow; /* 0 for deny, 1 for allow */
+} AllowDeny;
+
+/* Arrays of AllowDeny */
+static ARR_Instance ntp_restrictions;
+static ARR_Instance cmd_restrictions;
+
+typedef struct {
+ NTP_Remote_Address addr;
+ int interval;
+} NTP_Broadcast_Destination;
+
+/* Array of NTP_Broadcast_Destination */
+static ARR_Instance broadcasts;
+
+/* ================================================== */
+
+/* The line number in the configuration file being processed */
+static int line_number;
+static const char *processed_file;
+static const char *processed_command;
+
+static int include_level = 0;
+
+/* ================================================== */
+
+static void
+command_parse_error(void)
+{
+ LOG_FATAL("Could not parse %s directive at line %d%s%s",
+ processed_command, line_number, processed_file ? " in file " : "",
+ processed_file ? processed_file : "");
+}
+
+/* ================================================== */
+
+static void
+other_parse_error(const char *message)
+{
+ LOG_FATAL("%s at line %d%s%s",
+ message, line_number, processed_file ? " in file " : "",
+ processed_file ? processed_file : "");
+}
+
+/* ================================================== */
+
+static int
+get_number_of_args(char *line)
+{
+ int num = 0;
+
+ /* The line is normalized, between arguments is just one space */
+ if (*line == ' ')
+ line++;
+ if (*line)
+ num++;
+ for (; *line; line++) {
+ if (*line == ' ')
+ num++;
+ }
+
+ return num;
+}
+
+/* ================================================== */
+
+static void
+check_number_of_args(char *line, int num)
+{
+ num -= get_number_of_args(line);
+
+ if (num) {
+ LOG_FATAL("%s arguments for %s directive at line %d%s%s",
+ num > 0 ? "Missing" : "Too many",
+ processed_command, line_number, processed_file ? " in file " : "",
+ processed_file ? processed_file : "");
+ }
+}
+
+/* ================================================== */
+
+void
+CNF_Initialise(int r, int client_only)
+{
+ restarted = r;
+
+ hwts_interfaces = ARR_CreateInstance(sizeof (CNF_HwTsInterface));
+
+ init_sources = ARR_CreateInstance(sizeof (IPAddr));
+ ntp_sources = ARR_CreateInstance(sizeof (NTP_Source));
+ ntp_source_dirs = ARR_CreateInstance(sizeof (char *));
+ ntp_source_ids = ARR_CreateInstance(sizeof (uint32_t));
+ refclock_sources = ARR_CreateInstance(sizeof (RefclockParameters));
+ broadcasts = ARR_CreateInstance(sizeof (NTP_Broadcast_Destination));
+
+ ntp_restrictions = ARR_CreateInstance(sizeof (AllowDeny));
+ cmd_restrictions = ARR_CreateInstance(sizeof (AllowDeny));
+
+ nts_server_cert_files = ARR_CreateInstance(sizeof (char *));
+ nts_server_key_files = ARR_CreateInstance(sizeof (char *));
+ nts_trusted_certs_paths = ARR_CreateInstance(sizeof (char *));
+ nts_trusted_certs_ids = ARR_CreateInstance(sizeof (uint32_t));
+
+ rtc_device = Strdup(DEFAULT_RTC_DEVICE);
+ hwclock_file = Strdup(DEFAULT_HWCLOCK_FILE);
+ user = Strdup(DEFAULT_USER);
+
+ if (client_only) {
+ cmd_port = ntp_port = 0;
+ } else {
+ bind_cmd_path = Strdup(DEFAULT_COMMAND_SOCKET);
+ pidfile = Strdup(DEFAULT_PID_FILE);
+ }
+
+ SCK_GetAnyLocalIPAddress(IPADDR_INET4, &bind_address4);
+ SCK_GetAnyLocalIPAddress(IPADDR_INET6, &bind_address6);
+ SCK_GetAnyLocalIPAddress(IPADDR_INET4, &bind_acq_address4);
+ SCK_GetAnyLocalIPAddress(IPADDR_INET6, &bind_acq_address6);
+ SCK_GetLoopbackIPAddress(IPADDR_INET4, &bind_cmd_address4);
+ SCK_GetLoopbackIPAddress(IPADDR_INET6, &bind_cmd_address6);
+}
+
+/* ================================================== */
+
+void
+CNF_Finalise(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(hwts_interfaces); i++)
+ Free(((CNF_HwTsInterface *)ARR_GetElement(hwts_interfaces, i))->name);
+ ARR_DestroyInstance(hwts_interfaces);
+
+ for (i = 0; i < ARR_GetSize(ntp_sources); i++)
+ Free(((NTP_Source *)ARR_GetElement(ntp_sources, i))->params.name);
+ for (i = 0; i < ARR_GetSize(ntp_source_dirs); i++)
+ Free(*(char **)ARR_GetElement(ntp_source_dirs, i));
+ for (i = 0; i < ARR_GetSize(refclock_sources); i++) {
+ Free(((RefclockParameters *)ARR_GetElement(refclock_sources, i))->driver_name);
+ Free(((RefclockParameters *)ARR_GetElement(refclock_sources, i))->driver_parameter);
+ }
+ for (i = 0; i < ARR_GetSize(nts_server_cert_files); i++)
+ Free(*(char **)ARR_GetElement(nts_server_cert_files, i));
+ for (i = 0; i < ARR_GetSize(nts_server_key_files); i++)
+ Free(*(char **)ARR_GetElement(nts_server_key_files, i));
+ for (i = 0; i < ARR_GetSize(nts_trusted_certs_paths); i++)
+ Free(*(char **)ARR_GetElement(nts_trusted_certs_paths, i));
+
+ ARR_DestroyInstance(init_sources);
+ ARR_DestroyInstance(ntp_sources);
+ ARR_DestroyInstance(ntp_source_dirs);
+ ARR_DestroyInstance(ntp_source_ids);
+ ARR_DestroyInstance(refclock_sources);
+ ARR_DestroyInstance(broadcasts);
+
+ ARR_DestroyInstance(ntp_restrictions);
+ ARR_DestroyInstance(cmd_restrictions);
+
+ ARR_DestroyInstance(nts_server_cert_files);
+ ARR_DestroyInstance(nts_server_key_files);
+ ARR_DestroyInstance(nts_trusted_certs_paths);
+ ARR_DestroyInstance(nts_trusted_certs_ids);
+
+ Free(drift_file);
+ Free(dumpdir);
+ Free(hwclock_file);
+ Free(keys_file);
+ Free(leapsec_tz);
+ Free(logdir);
+ Free(bind_ntp_iface);
+ Free(bind_acq_iface);
+ Free(bind_cmd_iface);
+ Free(bind_cmd_path);
+ Free(ntp_signd_socket);
+ Free(pidfile);
+ Free(rtc_device);
+ Free(rtc_file);
+ Free(user);
+ Free(mail_user_on_change);
+ Free(tempcomp_sensor_file);
+ Free(tempcomp_point_file);
+ Free(nts_dump_dir);
+ Free(nts_ntp_server);
+}
+
+/* ================================================== */
+
+void
+CNF_EnablePrint(void)
+{
+ print_config = 1;
+}
+
+/* ================================================== */
+
+/* Read the configuration file */
+void
+CNF_ReadFile(const char *filename)
+{
+ FILE *in;
+ char line[MAX_LINE_LENGTH + 1];
+ int i;
+
+ include_level++;
+ if (include_level > MAX_INCLUDE_LEVEL)
+ LOG_FATAL("Maximum include level reached");
+
+ in = UTI_OpenFile(NULL, filename, NULL, 'R', 0);
+
+ for (i = 1; fgets(line, sizeof(line), in); i++) {
+ CNF_ParseLine(filename, i, line);
+ }
+
+ fclose(in);
+
+ include_level--;
+}
+
+/* ================================================== */
+
+/* Parse one configuration line */
+void
+CNF_ParseLine(const char *filename, int number, char *line)
+{
+ char *p, *command;
+
+ /* Set global variables used in error messages */
+ processed_file = filename;
+ line_number = number;
+
+ /* Detect truncated line */
+ if (strlen(line) >= MAX_LINE_LENGTH)
+ other_parse_error("String too long");
+
+ /* Remove extra white-space and comments */
+ CPS_NormalizeLine(line);
+
+ /* Skip blank lines */
+ if (!*line) {
+ processed_file = NULL;
+ return;
+ }
+
+ /* We have a real line, now try to match commands */
+ processed_command = command = line;
+ p = CPS_SplitWord(line);
+
+ if (print_config && strcasecmp(command, "include") && strcasecmp(command, "confdir"))
+ printf("%s%s%s\n", command, p[0] != '\0' ? " " : "", p);
+
+ if (!strcasecmp(command, "acquisitionport")) {
+ parse_int(p, &acquisition_port);
+ } else if (!strcasecmp(command, "allow")) {
+ parse_allow_deny(p, ntp_restrictions, 1);
+ } else if (!strcasecmp(command, "authselectmode")) {
+ parse_authselectmode(p);
+ } else if (!strcasecmp(command, "bindacqaddress")) {
+ parse_bindacqaddress(p);
+ } else if (!strcasecmp(command, "bindacqdevice")) {
+ parse_string(p, &bind_acq_iface);
+ } else if (!strcasecmp(command, "bindaddress")) {
+ parse_bindaddress(p);
+ } else if (!strcasecmp(command, "bindcmdaddress")) {
+ parse_bindcmdaddress(p);
+ } else if (!strcasecmp(command, "bindcmddevice")) {
+ parse_string(p, &bind_cmd_iface);
+ } else if (!strcasecmp(command, "binddevice")) {
+ parse_string(p, &bind_ntp_iface);
+ } else if (!strcasecmp(command, "broadcast")) {
+ parse_broadcast(p);
+ } else if (!strcasecmp(command, "clientloglimit")) {
+ parse_clientloglimit(p);
+ } else if (!strcasecmp(command, "clockprecision")) {
+ parse_double(p, &clock_precision);
+ } else if (!strcasecmp(command, "cmdallow")) {
+ parse_allow_deny(p, cmd_restrictions, 1);
+ } else if (!strcasecmp(command, "cmddeny")) {
+ parse_allow_deny(p, cmd_restrictions, 0);
+ } else if (!strcasecmp(command, "cmdport")) {
+ parse_int(p, &cmd_port);
+ } else if (!strcasecmp(command, "cmdratelimit")) {
+ parse_ratelimit(p, &cmd_ratelimit_enabled, &cmd_ratelimit_interval,
+ &cmd_ratelimit_burst, &cmd_ratelimit_leak);
+ } else if (!strcasecmp(command, "combinelimit")) {
+ parse_double(p, &combine_limit);
+ } else if (!strcasecmp(command, "confdir")) {
+ parse_confdir(p);
+ } else if (!strcasecmp(command, "corrtimeratio")) {
+ parse_double(p, &correction_time_ratio);
+ } else if (!strcasecmp(command, "deny")) {
+ parse_allow_deny(p, ntp_restrictions, 0);
+ } else if (!strcasecmp(command, "driftfile")) {
+ parse_string(p, &drift_file);
+ } else if (!strcasecmp(command, "dscp")) {
+ parse_int(p, &ntp_dscp);
+ } else if (!strcasecmp(command, "dumpdir")) {
+ parse_string(p, &dumpdir);
+ } else if (!strcasecmp(command, "dumponexit")) {
+ /* Silently ignored */
+ } else if (!strcasecmp(command, "fallbackdrift")) {
+ parse_fallbackdrift(p);
+ } else if (!strcasecmp(command, "hwclockfile")) {
+ parse_string(p, &hwclock_file);
+ } else if (!strcasecmp(command, "hwtimestamp")) {
+ parse_hwtimestamp(p);
+ } else if (!strcasecmp(command, "hwtstimeout")) {
+ parse_double(p, &hwts_timeout);
+ } else if (!strcasecmp(command, "include")) {
+ parse_include(p);
+ } else if (!strcasecmp(command, "initstepslew")) {
+ parse_initstepslew(p);
+ } else if (!strcasecmp(command, "keyfile")) {
+ parse_string(p, &keys_file);
+ } else if (!strcasecmp(command, "leapsecmode")) {
+ parse_leapsecmode(p);
+ } else if (!strcasecmp(command, "leapsectz")) {
+ parse_string(p, &leapsec_tz);
+ } else if (!strcasecmp(command, "local")) {
+ parse_local(p);
+ } else if (!strcasecmp(command, "lock_all")) {
+ lock_memory = parse_null(p);
+ } else if (!strcasecmp(command, "log")) {
+ parse_log(p);
+ } else if (!strcasecmp(command, "logbanner")) {
+ parse_int(p, &log_banner);
+ } else if (!strcasecmp(command, "logchange")) {
+ parse_double(p, &log_change_threshold);
+ } else if (!strcasecmp(command, "logdir")) {
+ parse_string(p, &logdir);
+ } else if (!strcasecmp(command, "mailonchange")) {
+ parse_mailonchange(p);
+ } else if (!strcasecmp(command, "makestep")) {
+ parse_makestep(p);
+ } else if (!strcasecmp(command, "manual")) {
+ enable_manual = parse_null(p);
+ } else if (!strcasecmp(command, "maxchange")) {
+ parse_maxchange(p);
+ } else if (!strcasecmp(command, "maxclockerror")) {
+ parse_double(p, &max_clock_error);
+ } else if (!strcasecmp(command, "maxdistance")) {
+ parse_double(p, &max_distance);
+ } else if (!strcasecmp(command, "maxdrift")) {
+ 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")) {
+ parse_double(p, &max_slew_rate);
+ } else if (!strcasecmp(command, "maxupdateskew")) {
+ parse_double(p, &max_update_skew);
+ } else if (!strcasecmp(command, "minsamples")) {
+ 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, "ntsratelimit")) {
+ parse_ratelimit(p, &nts_ratelimit_enabled, &nts_ratelimit_interval,
+ &nts_ratelimit_burst, &nts_ratelimit_leak);
+ } 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_ntsserver(p, nts_server_cert_files);
+ } else if (!strcasecmp(command, "ntsserverkey")) {
+ parse_ntsserver(p, nts_server_key_files);
+ } else if (!strcasecmp(command, "ntstrustedcerts")) {
+ parse_ntstrustedcerts(p);
+ } else if (!strcasecmp(command, "peer")) {
+ parse_source(p, command, 1);
+ } else if (!strcasecmp(command, "pidfile")) {
+ parse_string(p, &pidfile);
+ } else if (!strcasecmp(command, "pool")) {
+ parse_source(p, command, 1);
+ } else if (!strcasecmp(command, "port")) {
+ parse_int(p, &ntp_port);
+ } else if (!strcasecmp(command, "ptpport")) {
+ parse_int(p, &ptp_port);
+ } else if (!strcasecmp(command, "ratelimit")) {
+ parse_ratelimit(p, &ntp_ratelimit_enabled, &ntp_ratelimit_interval,
+ &ntp_ratelimit_burst, &ntp_ratelimit_leak);
+ } else if (!strcasecmp(command, "refclock")) {
+ parse_refclock(p);
+ } else if (!strcasecmp(command, "refresh")) {
+ parse_int(p, &refresh);
+ } else if (!strcasecmp(command, "reselectdist")) {
+ parse_double(p, &reselect_distance);
+ } else if (!strcasecmp(command, "rtcautotrim")) {
+ parse_double(p, &rtc_autotrim_threshold);
+ } else if (!strcasecmp(command, "rtcdevice")) {
+ parse_string(p, &rtc_device);
+ } else if (!strcasecmp(command, "rtcfile")) {
+ parse_string(p, &rtc_file);
+ } else if (!strcasecmp(command, "rtconutc")) {
+ rtc_on_utc = parse_null(p);
+ } else if (!strcasecmp(command, "rtcsync")) {
+ rtc_sync = parse_null(p);
+ } else if (!strcasecmp(command, "sched_priority")) {
+ parse_int(p, &sched_priority);
+ } else if (!strcasecmp(command, "server")) {
+ parse_source(p, command, 1);
+ } else if (!strcasecmp(command, "smoothtime")) {
+ parse_smoothtime(p);
+ } else if (!strcasecmp(command, "sourcedir")) {
+ parse_sourcedir(p);
+ } else if (!strcasecmp(command, "stratumweight")) {
+ parse_double(p, &stratum_weight);
+ } else if (!strcasecmp(command, "tempcomp")) {
+ parse_tempcomp(p);
+ } else if (!strcasecmp(command, "user")) {
+ parse_string(p, &user);
+ } else if (!strcasecmp(command, "commandkey") ||
+ !strcasecmp(command, "generatecommandkey") ||
+ !strcasecmp(command, "linux_freq_scale") ||
+ !strcasecmp(command, "linux_hz")) {
+ LOG(LOGS_WARN, "%s directive is no longer supported", command);
+ } else {
+ other_parse_error("Invalid directive");
+ }
+
+ processed_file = processed_command = NULL;
+}
+
+/* ================================================== */
+
+static int
+parse_string(char *line, char **result)
+{
+ check_number_of_args(line, 1);
+ Free(*result);
+ *result = Strdup(line);
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+parse_int(char *line, int *result)
+{
+ check_number_of_args(line, 1);
+ if (sscanf(line, "%d", result) != 1) {
+ command_parse_error();
+ return 0;
+ }
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+parse_double(char *line, double *result)
+{
+ check_number_of_args(line, 1);
+ if (sscanf(line, "%lf", result) != 1) {
+ command_parse_error();
+ return 0;
+ }
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+parse_null(char *line)
+{
+ check_number_of_args(line, 0);
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+parse_source(char *line, char *type, int fatal)
+{
+ NTP_Source source;
+
+ if (strcasecmp(type, "peer") == 0) {
+ source.type = NTP_PEER;
+ source.pool = 0;
+ } else if (strcasecmp(type, "pool") == 0) {
+ source.type = NTP_SERVER;
+ source.pool = 1;
+ } else if (strcasecmp(type, "server") == 0) {
+ source.type = NTP_SERVER;
+ source.pool = 0;
+ } else {
+ if (fatal)
+ command_parse_error();
+ return;
+ }
+
+ /* Avoid comparing uninitialized data in compare_sources() */
+ memset(&source.params, 0, sizeof (source.params));
+
+ if (!CPS_ParseNTPSourceAdd(line, &source.params)) {
+ if (fatal)
+ command_parse_error();
+ return;
+ }
+
+ source.params.name = Strdup(source.params.name);
+ ARR_AppendElement(ntp_sources, &source);
+}
+
+/* ================================================== */
+
+static void
+parse_sourcedir(char *line)
+{
+ char *s;
+
+ s = Strdup(line);
+ ARR_AppendElement(ntp_source_dirs, &s);
+}
+
+/* ================================================== */
+
+static void
+parse_ratelimit(char *line, int *enabled, int *interval, int *burst, int *leak)
+{
+ int n, val;
+ char *opt;
+
+ *enabled = 1;
+
+ while (*line) {
+ opt = line;
+ line = CPS_SplitWord(line);
+ if (sscanf(line, "%d%n", &val, &n) != 1) {
+ command_parse_error();
+ return;
+ }
+ line += n;
+ if (!strcasecmp(opt, "interval"))
+ *interval = val;
+ else if (!strcasecmp(opt, "burst"))
+ *burst = val;
+ else if (!strcasecmp(opt, "leak"))
+ *leak = val;
+ else
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_refclock(char *line)
+{
+ int n, poll, dpoll, filter_length, pps_rate, min_samples, max_samples, sel_options;
+ int local, max_lock_age, pps_forced, sel_option, stratum, tai;
+ uint32_t ref_id, lock_ref_id;
+ double offset, delay, precision, max_dispersion, pulse_width;
+ char *p, *cmd, *name, *param;
+ RefclockParameters *refclock;
+
+ poll = 4;
+ dpoll = 0;
+ filter_length = 64;
+ local = 0;
+ pps_forced = 0;
+ pps_rate = 0;
+ min_samples = SRC_DEFAULT_MINSAMPLES;
+ max_samples = SRC_DEFAULT_MAXSAMPLES;
+ sel_options = 0;
+ offset = 0.0;
+ delay = 1e-9;
+ precision = 0.0;
+ max_dispersion = 0.0;
+ pulse_width = 0.0;
+ ref_id = 0;
+ max_lock_age = 2;
+ lock_ref_id = 0;
+ stratum = 0;
+ tai = 0;
+
+ if (!*line) {
+ command_parse_error();
+ return;
+ }
+
+ p = line;
+ line = CPS_SplitWord(line);
+
+ if (!*line) {
+ command_parse_error();
+ return;
+ }
+
+ name = Strdup(p);
+
+ p = line;
+ line = CPS_SplitWord(line);
+ param = Strdup(p);
+
+ for (cmd = line; *cmd; line += n, cmd = line) {
+ line = CPS_SplitWord(line);
+
+ if (!strcasecmp(cmd, "refid")) {
+ if ((n = CPS_ParseRefid(line, &ref_id)) == 0)
+ break;
+ } else if (!strcasecmp(cmd, "lock")) {
+ if ((n = CPS_ParseRefid(line, &lock_ref_id)) == 0)
+ break;
+ } else if (!strcasecmp(cmd, "poll")) {
+ if (sscanf(line, "%d%n", &poll, &n) != 1) {
+ break;
+ }
+ } else if (!strcasecmp(cmd, "dpoll")) {
+ if (sscanf(line, "%d%n", &dpoll, &n) != 1) {
+ break;
+ }
+ } else if (!strcasecmp(cmd, "filter")) {
+ if (sscanf(line, "%d%n", &filter_length, &n) != 1) {
+ break;
+ }
+ } else if (!strcasecmp(cmd, "local")) {
+ n = 0;
+ local = 1;
+ } else if (!strcasecmp(cmd, "rate")) {
+ if (sscanf(line, "%d%n", &pps_rate, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "minsamples")) {
+ if (sscanf(line, "%d%n", &min_samples, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "maxlockage")) {
+ if (sscanf(line, "%d%n", &max_lock_age, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "maxsamples")) {
+ if (sscanf(line, "%d%n", &max_samples, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "offset")) {
+ if (sscanf(line, "%lf%n", &offset, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "delay")) {
+ if (sscanf(line, "%lf%n", &delay, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "pps")) {
+ n = 0;
+ pps_forced = 1;
+ } else if (!strcasecmp(cmd, "precision")) {
+ if (sscanf(line, "%lf%n", &precision, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "maxdispersion")) {
+ if (sscanf(line, "%lf%n", &max_dispersion, &n) != 1)
+ break;
+ } else if (!strcasecmp(cmd, "stratum")) {
+ if (sscanf(line, "%d%n", &stratum, &n) != 1 ||
+ stratum >= NTP_MAX_STRATUM || stratum < 0)
+ break;
+ } else if (!strcasecmp(cmd, "tai")) {
+ n = 0;
+ tai = 1;
+ } else if (!strcasecmp(cmd, "width")) {
+ if (sscanf(line, "%lf%n", &pulse_width, &n) != 1)
+ break;
+ } else if ((sel_option = CPS_GetSelectOption(cmd)) != 0) {
+ n = 0;
+ sel_options |= sel_option;
+ } else {
+ other_parse_error("Invalid refclock option");
+ return;
+ }
+ }
+
+ if (*cmd) {
+ command_parse_error();
+ return;
+ }
+
+ refclock = (RefclockParameters *)ARR_GetNewElement(refclock_sources);
+ refclock->driver_name = name;
+ refclock->driver_parameter = param;
+ refclock->driver_poll = dpoll;
+ refclock->poll = poll;
+ refclock->filter_length = filter_length;
+ refclock->local = local;
+ refclock->pps_forced = pps_forced;
+ refclock->pps_rate = pps_rate;
+ refclock->min_samples = min_samples;
+ refclock->max_samples = max_samples;
+ refclock->sel_options = sel_options;
+ refclock->stratum = stratum;
+ refclock->tai = tai;
+ refclock->offset = offset;
+ refclock->delay = delay;
+ refclock->precision = precision;
+ refclock->max_dispersion = max_dispersion;
+ refclock->pulse_width = pulse_width;
+ refclock->ref_id = ref_id;
+ refclock->max_lock_age = max_lock_age;
+ refclock->lock_ref_id = lock_ref_id;
+}
+
+/* ================================================== */
+
+static void
+parse_log(char *line)
+{
+ char *log_name;
+ do {
+ log_name = line;
+ line = CPS_SplitWord(line);
+ if (*log_name) {
+ if (!strcmp(log_name, "rawmeasurements")) {
+ do_log_measurements = 1;
+ raw_measurements = 1;
+ } else if (!strcmp(log_name, "measurements")) {
+ do_log_measurements = 1;
+ } else if (!strcmp(log_name, "selection")) {
+ do_log_selection = 1;
+ } else if (!strcmp(log_name, "statistics")) {
+ do_log_statistics = 1;
+ } else if (!strcmp(log_name, "tracking")) {
+ do_log_tracking = 1;
+ } else if (!strcmp(log_name, "rtc")) {
+ do_log_rtc = 1;
+ } else if (!strcmp(log_name, "refclocks")) {
+ do_log_refclocks = 1;
+ } else if (!strcmp(log_name, "tempcomp")) {
+ do_log_tempcomp = 1;
+ } else {
+ other_parse_error("Invalid log parameter");
+ break;
+ }
+ } else {
+ break;
+ }
+ } while (1);
+}
+
+/* ================================================== */
+
+static void
+parse_local(char *line)
+{
+ if (!CPS_ParseLocal(line, &local_stratum, &local_orphan, &local_distance))
+ command_parse_error();
+ enable_local = 1;
+}
+
+/* ================================================== */
+
+static void
+parse_initstepslew(char *line)
+{
+ char *p, *hostname;
+ IPAddr ip_addr;
+
+ /* Ignore the line if chronyd was started with -R. */
+ if (restarted) {
+ return;
+ }
+
+ ARR_SetSize(init_sources, 0);
+ p = CPS_SplitWord(line);
+
+ if (sscanf(line, "%lf", &init_slew_threshold) != 1) {
+ command_parse_error();
+ return;
+ }
+
+ while (*p) {
+ hostname = p;
+ p = CPS_SplitWord(p);
+ if (*hostname) {
+ if (DNS_Name2IPAddress(hostname, &ip_addr, 1) == DNS_Success) {
+ ARR_AppendElement(init_sources, &ip_addr);
+ } else {
+ LOG(LOGS_WARN, "Could not resolve address of initstepslew server %s", hostname);
+ }
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_leapsecmode(char *line)
+{
+ if (!strcasecmp(line, "system"))
+ leapsec_mode = REF_LeapModeSystem;
+ else if (!strcasecmp(line, "slew"))
+ leapsec_mode = REF_LeapModeSlew;
+ else if (!strcasecmp(line, "step"))
+ leapsec_mode = REF_LeapModeStep;
+ else if (!strcasecmp(line, "ignore"))
+ leapsec_mode = REF_LeapModeIgnore;
+ else
+ command_parse_error();
+}
+
+/* ================================================== */
+
+static void
+parse_clientloglimit(char *line)
+{
+ check_number_of_args(line, 1);
+ if (sscanf(line, "%lu", &client_log_limit) != 1) {
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_fallbackdrift(char *line)
+{
+ check_number_of_args(line, 2);
+ if (sscanf(line, "%d %d", &fb_drift_min, &fb_drift_max) != 2) {
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_makestep(char *line)
+{
+ check_number_of_args(line, 2);
+ if (sscanf(line, "%lf %d", &make_step_threshold, &make_step_limit) != 2) {
+ make_step_limit = 0;
+ command_parse_error();
+ }
+
+ /* Disable limited makestep if chronyd was started with -R. */
+ if (restarted && make_step_limit > 0) {
+ make_step_limit = 0;
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_maxchange(char *line)
+{
+ check_number_of_args(line, 3);
+ if (sscanf(line, "%lf %d %d", &max_offset, &max_offset_delay, &max_offset_ignore) != 3) {
+ max_offset_delay = -1;
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_mailonchange(char *line)
+{
+ char *address;
+ check_number_of_args(line, 2);
+ address = line;
+ line = CPS_SplitWord(line);
+ Free(mail_user_on_change);
+ if (sscanf(line, "%lf", &mail_change_threshold) == 1) {
+ mail_user_on_change = Strdup(address);
+ } else {
+ mail_user_on_change = NULL;
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_ntsserver(char *line, ARR_Instance files)
+{
+ char *file = NULL;
+
+ parse_string(line, &file);
+ ARR_AppendElement(files, &file);
+}
+
+/* ================================================== */
+
+static void
+parse_ntstrustedcerts(char *line)
+{
+ uint32_t id;
+ char *path;
+
+ if (get_number_of_args(line) == 2) {
+ path = CPS_SplitWord(line);
+ if (sscanf(line, "%"SCNu32, &id) != 1)
+ command_parse_error();
+ } else {
+ check_number_of_args(line, 1);
+ path = line;
+ id = 0;
+ }
+
+ path = Strdup(path);
+
+ ARR_AppendElement(nts_trusted_certs_paths, &path);
+ ARR_AppendElement(nts_trusted_certs_ids, &id);
+}
+
+/* ================================================== */
+
+static void
+parse_allow_deny(char *line, ARR_Instance restrictions, int allow)
+{
+ int all, subnet_bits;
+ AllowDeny *node;
+ IPAddr ip;
+
+ if (!CPS_ParseAllowDeny(line, &all, &ip, &subnet_bits))
+ command_parse_error();
+
+ node = ARR_GetNewElement(restrictions);
+ node->allow = allow;
+ node->all = all;
+ node->ip = ip;
+ node->subnet_bits = subnet_bits;
+}
+
+/* ================================================== */
+
+static void
+parse_authselectmode(char *line)
+{
+ if (!strcasecmp(line, "require"))
+ authselect_mode = SRC_AUTHSELECT_REQUIRE;
+ else if (!strcasecmp(line, "prefer"))
+ authselect_mode = SRC_AUTHSELECT_PREFER;
+ else if (!strcasecmp(line, "mix"))
+ authselect_mode = SRC_AUTHSELECT_MIX;
+ else if (!strcasecmp(line, "ignore"))
+ authselect_mode = SRC_AUTHSELECT_IGNORE;
+ else
+ command_parse_error();
+}
+
+/* ================================================== */
+
+static void
+parse_bindacqaddress(char *line)
+{
+ IPAddr ip;
+
+ check_number_of_args(line, 1);
+ if (UTI_StringToIP(line, &ip)) {
+ if (ip.family == IPADDR_INET4)
+ bind_acq_address4 = ip;
+ else if (ip.family == IPADDR_INET6)
+ bind_acq_address6 = ip;
+ } else {
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_bindaddress(char *line)
+{
+ IPAddr ip;
+
+ check_number_of_args(line, 1);
+ if (UTI_StringToIP(line, &ip)) {
+ if (ip.family == IPADDR_INET4)
+ bind_address4 = ip;
+ else if (ip.family == IPADDR_INET6)
+ bind_address6 = ip;
+ } else {
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_bindcmdaddress(char *line)
+{
+ IPAddr ip;
+
+ check_number_of_args(line, 1);
+
+ /* Address starting with / is for the Unix domain socket */
+ if (line[0] == '/') {
+ parse_string(line, &bind_cmd_path);
+ /* / disables the socket */
+ if (strcmp(bind_cmd_path, "/") == 0) {
+ Free(bind_cmd_path);
+ bind_cmd_path = NULL;
+ }
+ } else if (UTI_StringToIP(line, &ip)) {
+ if (ip.family == IPADDR_INET4)
+ bind_cmd_address4 = ip;
+ else if (ip.family == IPADDR_INET6)
+ bind_cmd_address6 = ip;
+ } else {
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+
+static void
+parse_broadcast(char *line)
+{
+ /* Syntax : broadcast <interval> <broadcast-IP-addr> [<port>] */
+ NTP_Broadcast_Destination *destination;
+ int port;
+ int interval;
+ char *p;
+ IPAddr ip;
+
+ p = line;
+ line = CPS_SplitWord(line);
+
+ if (sscanf(p, "%d", &interval) != 1) {
+ command_parse_error();
+ return;
+ }
+
+ p = line;
+ line = CPS_SplitWord(line);
+
+ if (!UTI_StringToIP(p, &ip)) {
+ command_parse_error();
+ return;
+ }
+
+ p = line;
+ line = CPS_SplitWord(line);
+
+ if (*p) {
+ if (sscanf(p, "%d", &port) != 1 || *line) {
+ command_parse_error();
+ return;
+ }
+ } else {
+ /* default port */
+ port = NTP_PORT;
+ }
+
+ destination = (NTP_Broadcast_Destination *)ARR_GetNewElement(broadcasts);
+ destination->addr.ip_addr = ip;
+ destination->addr.port = port;
+ destination->interval = interval;
+}
+
+/* ================================================== */
+
+static void
+parse_smoothtime(char *line)
+{
+ if (get_number_of_args(line) != 3)
+ check_number_of_args(line, 2);
+
+ if (sscanf(line, "%lf %lf", &smooth_max_freq, &smooth_max_wander) != 2) {
+ smooth_max_freq = 0.0;
+ command_parse_error();
+ }
+
+ line = CPS_SplitWord(CPS_SplitWord(line));
+ smooth_leap_only = 0;
+
+ if (*line) {
+ if (!strcasecmp(line, "leaponly"))
+ smooth_leap_only = 1;
+ else
+ command_parse_error();
+ }
+}
+
+/* ================================================== */
+static void
+parse_tempcomp(char *line)
+{
+ char *p;
+ int point_form;
+
+ point_form = get_number_of_args(line) == 3;
+
+ if (!point_form)
+ check_number_of_args(line, 6);
+
+ p = line;
+ line = CPS_SplitWord(line);
+
+ if (!*p) {
+ command_parse_error();
+ return;
+ }
+
+ Free(tempcomp_point_file);
+
+ if (point_form) {
+ if (sscanf(line, "%lf", &tempcomp_interval) != 1) {
+ command_parse_error();
+ return;
+ }
+ tempcomp_point_file = Strdup(CPS_SplitWord(line));
+ } else {
+ if (sscanf(line, "%lf %lf %lf %lf %lf", &tempcomp_interval,
+ &tempcomp_T0, &tempcomp_k0, &tempcomp_k1, &tempcomp_k2) != 5) {
+ command_parse_error();
+ return;
+ }
+ tempcomp_point_file = NULL;
+ }
+
+ Free(tempcomp_sensor_file);
+ tempcomp_sensor_file = Strdup(p);
+}
+
+/* ================================================== */
+
+static void
+parse_hwtimestamp(char *line)
+{
+ CNF_HwTsInterface *iface;
+ int n, maxpoll_set = 0;
+ char *p, filter[5];
+
+ if (!*line) {
+ command_parse_error();
+ return;
+ }
+
+ p = line;
+ line = CPS_SplitWord(line);
+
+ iface = ARR_GetNewElement(hwts_interfaces);
+ iface->name = Strdup(p);
+ iface->minpoll = 0;
+ iface->min_samples = 2;
+ iface->max_samples = 16;
+ iface->nocrossts = 0;
+ iface->rxfilter = CNF_HWTS_RXFILTER_ANY;
+ iface->precision = 100.0e-9;
+ iface->tx_comp = 0.0;
+ iface->rx_comp = 0.0;
+
+ for (p = line; *p; line += n, p = line) {
+ line = CPS_SplitWord(line);
+
+ if (!strcasecmp(p, "maxsamples")) {
+ if (sscanf(line, "%d%n", &iface->max_samples, &n) != 1)
+ break;
+ } else if (!strcasecmp(p, "minpoll")) {
+ if (sscanf(line, "%d%n", &iface->minpoll, &n) != 1)
+ break;
+ } else if (!strcasecmp(p, "maxpoll")) {
+ if (sscanf(line, "%d%n", &iface->maxpoll, &n) != 1)
+ break;
+ maxpoll_set = 1;
+ } else if (!strcasecmp(p, "minsamples")) {
+ if (sscanf(line, "%d%n", &iface->min_samples, &n) != 1)
+ break;
+ } else if (!strcasecmp(p, "precision")) {
+ if (sscanf(line, "%lf%n", &iface->precision, &n) != 1)
+ break;
+ } else if (!strcasecmp(p, "rxcomp")) {
+ if (sscanf(line, "%lf%n", &iface->rx_comp, &n) != 1)
+ break;
+ } else if (!strcasecmp(p, "txcomp")) {
+ if (sscanf(line, "%lf%n", &iface->tx_comp, &n) != 1)
+ break;
+ } else if (!strcasecmp(p, "rxfilter")) {
+ if (sscanf(line, "%4s%n", filter, &n) != 1)
+ break;
+ if (!strcasecmp(filter, "none"))
+ iface->rxfilter = CNF_HWTS_RXFILTER_NONE;
+ else if (!strcasecmp(filter, "ntp"))
+ iface->rxfilter = CNF_HWTS_RXFILTER_NTP;
+ else if (!strcasecmp(filter, "ptp"))
+ iface->rxfilter = CNF_HWTS_RXFILTER_PTP;
+ else if (!strcasecmp(filter, "all"))
+ iface->rxfilter = CNF_HWTS_RXFILTER_ALL;
+ else
+ break;
+ } else if (!strcasecmp(p, "nocrossts")) {
+ n = 0;
+ iface->nocrossts = 1;
+ } else {
+ break;
+ }
+ }
+
+ if (*p)
+ command_parse_error();
+
+ if (!maxpoll_set)
+ iface->maxpoll = iface->minpoll + 1;
+}
+
+/* ================================================== */
+
+static const char *
+get_basename(const char *path)
+{
+ const char *b = strrchr(path, '/');
+ return b ? b + 1 : path;
+}
+
+/* ================================================== */
+
+static int
+compare_basenames(const void *a, const void *b)
+{
+ return strcmp(get_basename(*(const char * const *)a),
+ get_basename(*(const char * const *)b));
+}
+
+/* ================================================== */
+
+static int
+search_dirs(char *line, const char *suffix, void (*file_handler)(const char *path))
+{
+ char *dirs[MAX_CONF_DIRS], buf[MAX_LINE_LENGTH], *path;
+ size_t i, j, k, locations, n_dirs;
+ glob_t gl;
+
+ n_dirs = UTI_SplitString(line, dirs, MAX_CONF_DIRS);
+ if (n_dirs < 1 || n_dirs > MAX_CONF_DIRS)
+ return 0;
+
+ /* Get the paths of all config files in the specified directories */
+ for (i = 0; i < n_dirs; i++) {
+ if (snprintf(buf, sizeof (buf), "%s/*%s", dirs[i], suffix) >= sizeof (buf))
+ assert(0);
+ if (glob(buf, GLOB_NOSORT | (i > 0 ? GLOB_APPEND : 0), NULL, &gl) != 0)
+ ;
+ }
+
+ if (gl.gl_pathc > 0) {
+ /* Sort the paths by filenames */
+ qsort(gl.gl_pathv, gl.gl_pathc, sizeof (gl.gl_pathv[0]), compare_basenames);
+
+ for (i = 0; i < gl.gl_pathc; i += locations) {
+ /* Count directories containing files with this name */
+ for (j = i + 1, locations = 1; j < gl.gl_pathc; j++, locations++) {
+ if (compare_basenames(&gl.gl_pathv[i], &gl.gl_pathv[j]) != 0)
+ break;
+ }
+
+ /* Read the first file of this name in the order of the directive */
+ for (j = 0; j < n_dirs; j++) {
+ for (k = 0; k < locations; k++) {
+ path = gl.gl_pathv[i + k];
+ if (strncmp(path, dirs[j], strlen(dirs[j])) == 0 &&
+ strlen(dirs[j]) + 1 + strlen(get_basename(path)) == strlen(path)) {
+ file_handler(path);
+ break;
+ }
+ }
+ if (k < locations)
+ break;
+ }
+ }
+ }
+
+ globfree(&gl);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+parse_confdir(char *line)
+{
+ if (!search_dirs(line, ".conf", CNF_ReadFile))
+ command_parse_error();
+}
+
+/* ================================================== */
+
+static void
+parse_include(char *line)
+{
+ glob_t gl;
+ size_t i;
+ int r;
+
+ check_number_of_args(line, 1);
+
+ if ((r = glob(line,
+#ifdef GLOB_NOMAGIC
+ GLOB_NOMAGIC |
+#endif
+ GLOB_ERR, NULL, &gl)) != 0) {
+ if (r != GLOB_NOMATCH)
+ LOG_FATAL("Could not search for files matching %s", line);
+
+ DEBUG_LOG("glob of %s failed", line);
+ return;
+ }
+
+ for (i = 0; i < gl.gl_pathc; i++)
+ CNF_ReadFile(gl.gl_pathv[i]);
+
+ globfree(&gl);
+}
+
+/* ================================================== */
+
+static void
+load_source_file(const char *filename)
+{
+ char line[MAX_LINE_LENGTH + 1];
+ FILE *f;
+
+ f = UTI_OpenFile(NULL, filename, NULL, 'r', 0);
+ if (!f)
+ return;
+
+ while (fgets(line, sizeof (line), f)) {
+ /* Require lines to be terminated */
+ if (line[0] == '\0' || line[strlen(line) - 1] != '\n')
+ break;
+
+ CPS_NormalizeLine(line);
+ if (line[0] == '\0')
+ continue;
+
+ parse_source(CPS_SplitWord(line), line, 0);
+ }
+
+ fclose(f);
+}
+
+/* ================================================== */
+
+static int
+compare_sources(const void *a, const void *b)
+{
+ const NTP_Source *sa = a, *sb = b;
+ int d;
+
+ if (!sa->params.name)
+ return -1;
+ if (!sb->params.name)
+ return 1;
+ if ((d = strcmp(sa->params.name, sb->params.name)) != 0)
+ return d;
+ if ((d = (int)sa->type - (int)sb->type) != 0)
+ return d;
+ if ((d = (int)sa->pool - (int)sb->pool) != 0)
+ return d;
+ if ((d = (int)sa->params.port - (int)sb->params.port) != 0)
+ return d;
+ return memcmp(&sa->params.params, &sb->params.params, sizeof (sa->params.params));
+}
+
+/* ================================================== */
+
+static void
+reload_source_dirs(void)
+{
+ NTP_Source *prev_sources, *new_sources, *source;
+ unsigned int i, j, prev_size, new_size, unresolved;
+ uint32_t *prev_ids, *new_ids;
+ char buf[MAX_LINE_LENGTH];
+ NSR_Status s;
+ int d, pass;
+
+ prev_size = ARR_GetSize(ntp_source_ids);
+ if (prev_size > 0 && ARR_GetSize(ntp_sources) != prev_size)
+ assert(0);
+
+ /* Save the current sources and their configuration IDs */
+ prev_ids = MallocArray(uint32_t, prev_size);
+ memcpy(prev_ids, ARR_GetElements(ntp_source_ids), prev_size * sizeof (prev_ids[0]));
+ prev_sources = MallocArray(NTP_Source, prev_size);
+ memcpy(prev_sources, ARR_GetElements(ntp_sources), prev_size * sizeof (prev_sources[0]));
+
+ /* Load the sources again */
+ ARR_SetSize(ntp_sources, 0);
+ for (i = 0; i < ARR_GetSize(ntp_source_dirs); i++) {
+ if (snprintf(buf, sizeof (buf), "%s",
+ *(char **)ARR_GetElement(ntp_source_dirs, i)) >= sizeof (buf))
+ assert(0);
+ search_dirs(buf, ".sources", load_source_file);
+ }
+
+ /* Add new and remove existing sources according to the new configuration.
+ Avoid removing and adding the same source again to keep its state. */
+
+ new_size = ARR_GetSize(ntp_sources);
+ new_sources = ARR_GetElements(ntp_sources);
+ ARR_SetSize(ntp_source_ids, new_size);
+ new_ids = ARR_GetElements(ntp_source_ids);
+ unresolved = 0;
+
+ LOG_SetContext(LOGC_SourceFile);
+
+ qsort(new_sources, new_size, sizeof (new_sources[0]), compare_sources);
+
+ for (pass = 0; pass < 2; pass++) {
+ for (i = j = 0; i < prev_size || j < new_size; i += d <= 0, j += d >= 0) {
+ if (i < prev_size && j < new_size)
+ d = compare_sources(&prev_sources[i], &new_sources[j]);
+ else
+ d = i < prev_size ? -1 : 1;
+
+ /* Remove missing sources before adding others to avoid conflicts */
+ if (pass == 0 && d < 0 && prev_sources[i].params.name[0] != '\0') {
+ NSR_RemoveSourcesById(prev_ids[i]);
+ }
+
+ /* Add new sources */
+ if (pass == 1 && d > 0) {
+ source = &new_sources[j];
+ s = NSR_AddSourceByName(source->params.name, source->params.port, source->pool,
+ source->type, &source->params.params, &new_ids[j]);
+
+ if (s == NSR_UnresolvedName) {
+ unresolved++;
+ } else if (s != NSR_Success) {
+ LOG(LOGS_ERR, "Could not add source %s : %s",
+ source->params.name, NSR_StatusToString(s));
+
+ /* Mark the source as not present */
+ source->params.name[0] = '\0';
+ }
+ }
+
+ /* Keep unchanged sources */
+ if (pass == 1 && d == 0)
+ new_ids[j] = prev_ids[i];
+ }
+ }
+
+ LOG_UnsetContext(LOGC_SourceFile);
+
+ for (i = 0; i < prev_size; i++)
+ Free(prev_sources[i].params.name);
+ Free(prev_sources);
+ Free(prev_ids);
+
+ if (unresolved > 0)
+ NSR_ResolveSources();
+}
+
+/* ================================================== */
+
+void
+CNF_CreateDirs(uid_t uid, gid_t gid)
+{
+ char *dir;
+
+ /* Create a directory for the Unix domain command socket */
+ if (bind_cmd_path) {
+ dir = UTI_PathToDir(bind_cmd_path);
+ UTI_CreateDirAndParents(dir, 0770, uid, gid);
+
+ /* Check the permissions and owner/group in case the directory already
+ existed. It MUST NOT be accessible by others as permissions on Unix
+ domain sockets are ignored on some systems (e.g. Solaris). */
+ if (!UTI_CheckDirPermissions(dir, 0770, uid, gid)) {
+ LOG(LOGS_WARN, "Disabled command socket %s", bind_cmd_path);
+ Free(bind_cmd_path);
+ bind_cmd_path = NULL;
+ }
+
+ Free(dir);
+ }
+
+ if (logdir)
+ UTI_CreateDirAndParents(logdir, 0750, uid, gid);
+ if (dumpdir)
+ UTI_CreateDirAndParents(dumpdir, 0750, uid, gid);
+ if (nts_dump_dir)
+ UTI_CreateDirAndParents(nts_dump_dir, 0750, uid, gid);
+}
+
+/* ================================================== */
+
+void
+CNF_CheckReadOnlyAccess(void)
+{
+ unsigned int i;
+
+ if (keys_file)
+ UTI_CheckReadOnlyAccess(keys_file);
+ for (i = 0; i < ARR_GetSize(nts_server_key_files); i++)
+ UTI_CheckReadOnlyAccess(*(char **)ARR_GetElement(nts_server_key_files, i));
+}
+
+/* ================================================== */
+
+void
+CNF_AddInitSources(void)
+{
+ CPS_NTP_Source cps_source;
+ NTP_Remote_Address ntp_addr;
+ char dummy_hostname[2] = "H";
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(init_sources); i++) {
+ /* Get the default NTP params */
+ CPS_ParseNTPSourceAdd(dummy_hostname, &cps_source);
+
+ /* Add the address as a server specified with the iburst option */
+ ntp_addr.ip_addr = *(IPAddr *)ARR_GetElement(init_sources, i);
+ ntp_addr.port = cps_source.port;
+ cps_source.params.iburst = 1;
+
+ if (NSR_AddSource(&ntp_addr, NTP_SERVER, &cps_source.params, NULL) != NSR_Success)
+ LOG(LOGS_ERR, "Could not add source %s", UTI_IPToString(&ntp_addr.ip_addr));
+ }
+
+ ARR_SetSize(init_sources, 0);
+}
+
+/* ================================================== */
+
+void
+CNF_AddSources(void)
+{
+ NTP_Source *source;
+ unsigned int i;
+ NSR_Status s;
+
+ for (i = 0; i < ARR_GetSize(ntp_sources); i++) {
+ source = (NTP_Source *)ARR_GetElement(ntp_sources, i);
+
+ s = NSR_AddSourceByName(source->params.name, source->params.port, source->pool,
+ source->type, &source->params.params, NULL);
+ if (s != NSR_Success && s != NSR_UnresolvedName)
+ LOG(LOGS_ERR, "Could not add source %s", source->params.name);
+
+ Free(source->params.name);
+ }
+
+ ARR_SetSize(ntp_sources, 0);
+
+ reload_source_dirs();
+}
+
+/* ================================================== */
+
+void
+CNF_AddRefclocks(void)
+{
+ RefclockParameters *refclock;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(refclock_sources); i++) {
+ refclock = ARR_GetElement(refclock_sources, i);
+ RCL_AddRefclock(refclock);
+ Free(refclock->driver_name);
+ Free(refclock->driver_parameter);
+ }
+
+ ARR_SetSize(refclock_sources, 0);
+}
+
+/* ================================================== */
+
+void
+CNF_AddBroadcasts(void)
+{
+ unsigned int i;
+ NTP_Broadcast_Destination *destination;
+
+ for (i = 0; i < ARR_GetSize(broadcasts); i++) {
+ destination = (NTP_Broadcast_Destination *)ARR_GetElement(broadcasts, i);
+ NCR_AddBroadcastDestination(&destination->addr, destination->interval);
+ }
+
+ ARR_SetSize(broadcasts, 0);
+}
+
+/* ================================================== */
+
+void
+CNF_ReloadSources(void)
+{
+ reload_source_dirs();
+}
+
+/* ================================================== */
+
+int
+CNF_GetNTPPort(void)
+{
+ return ntp_port;
+}
+
+/* ================================================== */
+
+int
+CNF_GetAcquisitionPort(void)
+{
+ return acquisition_port;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetDriftFile(void)
+{
+ return drift_file;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogBanner(void)
+{
+ return log_banner;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetLogDir(void)
+{
+ return logdir;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetDumpDir(void)
+{
+ return dumpdir;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogMeasurements(int *raw)
+{
+ *raw = raw_measurements;
+ return do_log_measurements;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogSelection(void)
+{
+ return do_log_selection;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogStatistics(void)
+{
+ return do_log_statistics;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogTracking(void)
+{
+ return do_log_tracking;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogRtc(void)
+{
+ return do_log_rtc;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogRefclocks(void)
+{
+ return do_log_refclocks;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLogTempComp(void)
+{
+ return do_log_tempcomp;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetKeysFile(void)
+{
+ return keys_file;
+}
+
+/* ================================================== */
+
+double
+CNF_GetRtcAutotrim(void)
+{
+ return rtc_autotrim_threshold;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetRtcFile(void)
+{
+ return rtc_file;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetRtcDevice(void)
+{
+ return rtc_device;
+}
+
+/* ================================================== */
+
+double
+CNF_GetMaxUpdateSkew(void)
+{
+ return max_update_skew;
+}
+
+/* ================================================== */
+
+double
+CNF_GetMaxDrift(void)
+{
+ return max_drift;
+}
+
+/* ================================================== */
+
+double
+CNF_GetMaxClockError(void)
+{
+ return max_clock_error;
+}
+
+/* ================================================== */
+
+double
+CNF_GetCorrectionTimeRatio(void)
+{
+ return correction_time_ratio;
+}
+
+/* ================================================== */
+
+SRC_AuthSelectMode
+CNF_GetAuthSelectMode(void)
+{
+ return authselect_mode;
+}
+
+/* ================================================== */
+
+double
+CNF_GetMaxSlewRate(void)
+{
+ return max_slew_rate;
+}
+
+/* ================================================== */
+
+double
+CNF_GetClockPrecision(void)
+{
+ return clock_precision;
+}
+
+/* ================================================== */
+
+double
+CNF_GetMaxDistance(void)
+{
+ return max_distance;
+}
+
+/* ================================================== */
+
+double
+CNF_GetMaxJitter(void)
+{
+ return max_jitter;
+}
+
+/* ================================================== */
+
+double
+CNF_GetReselectDistance(void)
+{
+ return reselect_distance;
+}
+
+/* ================================================== */
+
+double
+CNF_GetStratumWeight(void)
+{
+ return stratum_weight;
+}
+
+/* ================================================== */
+
+double
+CNF_GetCombineLimit(void)
+{
+ return combine_limit;
+}
+
+/* ================================================== */
+
+int
+CNF_GetManualEnabled(void)
+{
+ return enable_manual;
+}
+
+/* ================================================== */
+
+int
+CNF_GetCommandPort(void) {
+ return cmd_port;
+}
+
+/* ================================================== */
+
+int
+CNF_AllowLocalReference(int *stratum, int *orphan, double *distance)
+{
+ if (enable_local) {
+ *stratum = local_stratum;
+ *orphan = local_orphan;
+ *distance = local_distance;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+int
+CNF_GetRtcOnUtc(void)
+{
+ return rtc_on_utc;
+}
+
+/* ================================================== */
+
+int
+CNF_GetRtcSync(void)
+{
+ return rtc_sync;
+}
+
+/* ================================================== */
+
+void
+CNF_GetMakeStep(int *limit, double *threshold)
+{
+ *limit = make_step_limit;
+ *threshold = make_step_threshold;
+}
+
+/* ================================================== */
+
+void
+CNF_GetMaxChange(int *delay, int *ignore, double *offset)
+{
+ *delay = max_offset_delay;
+ *ignore = max_offset_ignore;
+ *offset = max_offset;
+}
+
+/* ================================================== */
+
+double
+CNF_GetLogChange(void)
+{
+ return log_change_threshold;
+}
+
+/* ================================================== */
+
+void
+CNF_GetMailOnChange(int *enabled, double *threshold, char **user)
+{
+ if (mail_user_on_change) {
+ *enabled = 1;
+ *threshold = mail_change_threshold;
+ *user = mail_user_on_change;
+ } else {
+ *enabled = 0;
+ *threshold = 0.0;
+ *user = NULL;
+ }
+}
+
+/* ================================================== */
+
+void
+CNF_SetupAccessRestrictions(void)
+{
+ AllowDeny *node;
+ int status;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(ntp_restrictions); i++) {
+ node = ARR_GetElement(ntp_restrictions, i);
+ status = NCR_AddAccessRestriction(&node->ip, node->subnet_bits, node->allow, node->all);
+ if (!status) {
+ LOG_FATAL("Bad subnet in %s/%d", UTI_IPToString(&node->ip), node->subnet_bits);
+ }
+ }
+
+ for (i = 0; i < ARR_GetSize(cmd_restrictions); i++) {
+ node = ARR_GetElement(cmd_restrictions, i);
+ status = CAM_AddAccessRestriction(&node->ip, node->subnet_bits, node->allow, node->all);
+ if (!status) {
+ LOG_FATAL("Bad subnet in %s/%d", UTI_IPToString(&node->ip), node->subnet_bits);
+ }
+ }
+
+ ARR_SetSize(ntp_restrictions, 0);
+ ARR_SetSize(cmd_restrictions, 0);
+}
+
+/* ================================================== */
+
+int
+CNF_GetNoClientLog(void)
+{
+ return no_client_log;
+}
+
+/* ================================================== */
+
+unsigned long
+CNF_GetClientLogLimit(void)
+{
+ return client_log_limit;
+}
+
+/* ================================================== */
+
+void
+CNF_GetFallbackDrifts(int *min, int *max)
+{
+ *min = fb_drift_min;
+ *max = fb_drift_max;
+}
+
+/* ================================================== */
+
+void
+CNF_GetBindAddress(int family, IPAddr *addr)
+{
+ if (family == IPADDR_INET4)
+ *addr = bind_address4;
+ else if (family == IPADDR_INET6)
+ *addr = bind_address6;
+ else
+ addr->family = IPADDR_UNSPEC;
+}
+
+/* ================================================== */
+
+void
+CNF_GetBindAcquisitionAddress(int family, IPAddr *addr)
+{
+ if (family == IPADDR_INET4)
+ *addr = bind_acq_address4;
+ else if (family == IPADDR_INET6)
+ *addr = bind_acq_address6;
+ else
+ addr->family = IPADDR_UNSPEC;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetBindNtpInterface(void)
+{
+ return bind_ntp_iface;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetBindAcquisitionInterface(void)
+{
+ return bind_acq_iface;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetBindCommandInterface(void)
+{
+ return bind_cmd_iface;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetBindCommandPath(void)
+{
+ return bind_cmd_path;
+}
+
+/* ================================================== */
+
+void
+CNF_GetBindCommandAddress(int family, IPAddr *addr)
+{
+ if (family == IPADDR_INET4)
+ *addr = bind_cmd_address4;
+ else if (family == IPADDR_INET6)
+ *addr = bind_cmd_address6;
+ else
+ addr->family = IPADDR_UNSPEC;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNtpDscp(void)
+{
+ return ntp_dscp;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetNtpSigndSocket(void)
+{
+ return ntp_signd_socket;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetPidFile(void)
+{
+ return pidfile;
+}
+
+/* ================================================== */
+
+REF_LeapMode
+CNF_GetLeapSecMode(void)
+{
+ return leapsec_mode;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetLeapSecTimezone(void)
+{
+ return leapsec_tz;
+}
+
+/* ================================================== */
+
+int
+CNF_GetSchedPriority(void)
+{
+ return sched_priority;
+}
+
+/* ================================================== */
+
+int
+CNF_GetLockMemory(void)
+{
+ return lock_memory;
+}
+
+/* ================================================== */
+
+int CNF_GetNTPRateLimit(int *interval, int *burst, int *leak)
+{
+ *interval = ntp_ratelimit_interval;
+ *burst = ntp_ratelimit_burst;
+ *leak = ntp_ratelimit_leak;
+ return ntp_ratelimit_enabled;
+}
+
+/* ================================================== */
+
+int CNF_GetNtsRateLimit(int *interval, int *burst, int *leak)
+{
+ *interval = nts_ratelimit_interval;
+ *burst = nts_ratelimit_burst;
+ *leak = nts_ratelimit_leak;
+ return nts_ratelimit_enabled;
+}
+
+/* ================================================== */
+
+int CNF_GetCommandRateLimit(int *interval, int *burst, int *leak)
+{
+ *interval = cmd_ratelimit_interval;
+ *burst = cmd_ratelimit_burst;
+ *leak = cmd_ratelimit_leak;
+ return cmd_ratelimit_enabled;
+}
+
+/* ================================================== */
+
+void
+CNF_GetSmooth(double *max_freq, double *max_wander, int *leap_only)
+{
+ *max_freq = smooth_max_freq;
+ *max_wander = smooth_max_wander;
+ *leap_only = smooth_leap_only;
+}
+
+/* ================================================== */
+
+void
+CNF_GetTempComp(char **file, double *interval, char **point_file, double *T0, double *k0, double *k1, double *k2)
+{
+ *file = tempcomp_sensor_file;
+ *point_file = tempcomp_point_file;
+ *interval = tempcomp_interval;
+ *T0 = tempcomp_T0;
+ *k0 = tempcomp_k0;
+ *k1 = tempcomp_k1;
+ *k2 = tempcomp_k2;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetUser(void)
+{
+ return user;
+}
+
+/* ================================================== */
+
+int
+CNF_GetMaxSamples(void)
+{
+ return max_samples;
+}
+
+/* ================================================== */
+
+int
+CNF_GetMinSamples(void)
+{
+ return min_samples;
+}
+
+/* ================================================== */
+
+int
+CNF_GetMinSources(void)
+{
+ return min_sources;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetHwclockFile(void)
+{
+ return hwclock_file;
+}
+
+/* ================================================== */
+
+int
+CNF_GetInitSources(void)
+{
+ return ARR_GetSize(init_sources);
+}
+
+/* ================================================== */
+
+double
+CNF_GetInitStepThreshold(void)
+{
+ return init_slew_threshold;
+}
+
+/* ================================================== */
+
+int
+CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface)
+{
+ if (index >= ARR_GetSize(hwts_interfaces))
+ return 0;
+
+ *iface = (CNF_HwTsInterface *)ARR_GetElement(hwts_interfaces, index);
+ return 1;
+}
+
+/* ================================================== */
+
+double
+CNF_GetHwTsTimeout(void)
+{
+ return hwts_timeout;
+}
+
+/* ================================================== */
+
+int
+CNF_GetPtpPort(void)
+{
+ return ptp_port;
+}
+
+/* ================================================== */
+
+int
+CNF_GetRefresh(void)
+{
+ return refresh;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetNtsDumpDir(void)
+{
+ return nts_dump_dir;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetNtsNtpServer(void)
+{
+ return nts_ntp_server;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***keys)
+{
+ *certs = ARR_GetElements(nts_server_cert_files);
+ *keys = ARR_GetElements(nts_server_key_files);
+
+ if (ARR_GetSize(nts_server_cert_files) != ARR_GetSize(nts_server_key_files))
+ LOG_FATAL("Uneven number of NTS certs and keys");
+
+ return ARR_GetSize(nts_server_cert_files);
+}
+
+/* ================================================== */
+
+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;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNtsTrustedCertsPaths(const char ***paths, uint32_t **ids)
+{
+ *paths = ARR_GetElements(nts_trusted_certs_paths);
+ *ids = ARR_GetElements(nts_trusted_certs_ids);
+
+ if (ARR_GetSize(nts_trusted_certs_paths) != ARR_GetSize(nts_trusted_certs_ids))
+ assert(0);
+
+ return ARR_GetSize(nts_trusted_certs_paths);
+}
+
+/* ================================================== */
+
+int
+CNF_GetNoSystemCert(void)
+{
+ return no_system_cert;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNoCertTimeCheck(void)
+{
+ return no_cert_time_check;
+}
diff --git a/conf.h b/conf.h
new file mode 100644
index 0000000..58ebdeb
--- /dev/null
+++ b/conf.h
@@ -0,0 +1,176 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2013-2014
+ *
+ * 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 configuration module
+ */
+
+#ifndef GOT_CONF_H
+#define GOT_CONF_H
+
+#include "addressing.h"
+#include "reference.h"
+#include "sources.h"
+
+extern void CNF_Initialise(int restarted, int client_only);
+extern void CNF_Finalise(void);
+
+extern void CNF_EnablePrint(void);
+
+extern char *CNF_GetRtcDevice(void);
+
+extern void CNF_ReadFile(const char *filename);
+extern void CNF_ParseLine(const char *filename, int number, char *line);
+
+extern void CNF_CreateDirs(uid_t uid, gid_t gid);
+
+extern void CNF_CheckReadOnlyAccess(void);
+
+extern void CNF_AddInitSources(void);
+extern void CNF_AddSources(void);
+extern void CNF_AddBroadcasts(void);
+extern void CNF_AddRefclocks(void);
+
+extern void CNF_ReloadSources(void);
+
+extern int CNF_GetAcquisitionPort(void);
+extern int CNF_GetNTPPort(void);
+extern char *CNF_GetDriftFile(void);
+extern char *CNF_GetLogDir(void);
+extern char *CNF_GetDumpDir(void);
+extern int CNF_GetLogBanner(void);
+extern int CNF_GetLogMeasurements(int *raw);
+extern int CNF_GetLogSelection(void);
+extern int CNF_GetLogStatistics(void);
+extern int CNF_GetLogTracking(void);
+extern int CNF_GetLogRtc(void);
+extern int CNF_GetLogRefclocks(void);
+extern int CNF_GetLogTempComp(void);
+extern char *CNF_GetKeysFile(void);
+extern char *CNF_GetRtcFile(void);
+extern int CNF_GetManualEnabled(void);
+extern int CNF_GetCommandPort(void);
+extern int CNF_GetRtcOnUtc(void);
+extern int CNF_GetRtcSync(void);
+extern void CNF_GetMakeStep(int *limit, double *threshold);
+extern void CNF_GetMaxChange(int *delay, int *ignore, double *offset);
+extern double CNF_GetLogChange(void);
+extern void CNF_GetMailOnChange(int *enabled, double *threshold, char **user);
+extern int CNF_GetNoClientLog(void);
+extern unsigned long CNF_GetClientLogLimit(void);
+extern void CNF_GetFallbackDrifts(int *min, int *max);
+extern void CNF_GetBindAddress(int family, IPAddr *addr);
+extern void CNF_GetBindAcquisitionAddress(int family, IPAddr *addr);
+extern void CNF_GetBindCommandAddress(int family, IPAddr *addr);
+extern char *CNF_GetBindNtpInterface(void);
+extern char *CNF_GetBindAcquisitionInterface(void);
+extern char *CNF_GetBindCommandInterface(void);
+extern char *CNF_GetBindCommandPath(void);
+extern int CNF_GetNtpDscp(void);
+extern char *CNF_GetNtpSigndSocket(void);
+extern char *CNF_GetPidFile(void);
+extern REF_LeapMode CNF_GetLeapSecMode(void);
+extern char *CNF_GetLeapSecTimezone(void);
+
+/* Value returned in ppm, as read from file */
+extern double CNF_GetMaxUpdateSkew(void);
+extern double CNF_GetMaxClockError(void);
+extern double CNF_GetMaxDrift(void);
+extern double CNF_GetCorrectionTimeRatio(void);
+extern double CNF_GetMaxSlewRate(void);
+extern double CNF_GetClockPrecision(void);
+
+extern SRC_AuthSelectMode CNF_GetAuthSelectMode(void);
+extern double CNF_GetMaxDistance(void);
+extern double CNF_GetMaxJitter(void);
+extern double CNF_GetReselectDistance(void);
+extern double CNF_GetStratumWeight(void);
+extern double CNF_GetCombineLimit(void);
+
+extern int CNF_AllowLocalReference(int *stratum, int *orphan, double *distance);
+
+extern void CNF_SetupAccessRestrictions(void);
+
+extern int CNF_GetSchedPriority(void);
+extern int CNF_GetLockMemory(void);
+
+extern int CNF_GetNTPRateLimit(int *interval, int *burst, int *leak);
+extern int CNF_GetNtsRateLimit(int *interval, int *burst, int *leak);
+extern int CNF_GetCommandRateLimit(int *interval, int *burst, int *leak);
+extern void CNF_GetSmooth(double *max_freq, double *max_wander, int *leap_only);
+extern void CNF_GetTempComp(char **file, double *interval, char **point_file, double *T0, double *k0, double *k1, double *k2);
+
+extern char *CNF_GetUser(void);
+
+extern int CNF_GetMaxSamples(void);
+extern int CNF_GetMinSamples(void);
+
+extern int CNF_GetMinSources(void);
+
+extern double CNF_GetRtcAutotrim(void);
+extern char *CNF_GetHwclockFile(void);
+
+extern int CNF_GetInitSources(void);
+extern double CNF_GetInitStepThreshold(void);
+
+typedef enum {
+ CNF_HWTS_RXFILTER_ANY,
+ CNF_HWTS_RXFILTER_NONE,
+ CNF_HWTS_RXFILTER_NTP,
+ CNF_HWTS_RXFILTER_PTP,
+ CNF_HWTS_RXFILTER_ALL,
+} CNF_HwTs_RxFilter;
+
+typedef struct {
+ char *name;
+ int minpoll;
+ int maxpoll;
+ int min_samples;
+ int max_samples;
+ int nocrossts;
+ CNF_HwTs_RxFilter rxfilter;
+ double precision;
+ double tx_comp;
+ double rx_comp;
+} CNF_HwTsInterface;
+
+extern int CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface);
+extern double CNF_GetHwTsTimeout(void);
+
+extern int CNF_GetPtpPort(void);
+
+extern int CNF_GetRefresh(void);
+
+extern char *CNF_GetNtsDumpDir(void);
+extern char *CNF_GetNtsNtpServer(void);
+extern int CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***keys);
+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 int CNF_GetNtsTrustedCertsPaths(const char ***paths, uint32_t **ids);
+extern int CNF_GetNoSystemCert(void);
+extern int CNF_GetNoCertTimeCheck(void);
+
+#endif /* GOT_CONF_H */
diff --git a/configure b/configure
new file mode 100755
index 0000000..eefe5de
--- /dev/null
+++ b/configure
@@ -0,0 +1,1161 @@
+#!/bin/sh
+# =======================================================================
+#
+# chronyd/chronyc - Programs for keeping computer clocks accurate.
+#
+# Copyright (C) Richard P. Curnow 1997-2003
+# Copyright (C) Bryan Christianson 2016
+# Copyright (C) Miroslav Lichvar 2009, 2012-2022
+# Copyright (C) Stefan R. Filipek 2019
+#
+# =======================================================================
+
+# This configure script determines the operating system type and version
+
+# ======================================================================
+# FUNCTIONS
+
+#{{{ test_code
+test_code () {
+ name=$1
+ headers=$2
+ cflags=$3
+ ldflags=$4
+ code=$5
+
+ printf "%s" "Checking for $name : "
+
+ (
+ echo "#include \"config.h\""
+ for h in $headers; do
+ echo "#include <$h>"
+ done
+ echo "int main(int argc, char **argv) {"
+ echo "$code"
+ echo "return 0; }"
+ ) > conftest.c
+
+ echo "conftest.c:" >> config.log
+ cat conftest.c >> config.log
+ echo $MYCC $MYCFLAGS $MYCPPFLAGS $cflags -o conftest conftest.c $ldflags \
+ $MYLDFLAGS >> config.log
+ $MYCC $MYCFLAGS $MYCPPFLAGS $cflags -o conftest conftest.c $ldflags \
+ $MYLDFLAGS >> config.log 2>&1
+
+ if [ $? -eq 0 ]
+ then
+ echo "Yes"
+ result=0
+ else
+ echo "No"
+ result=1
+ fi
+ rm -f conftest.c conftest
+ echo >> config.log
+ 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
+\`configure' configures this package to adapt to many kinds of systems.
+
+Usage: ./configure [OPTION]...
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [/usr/local]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`/usr/local/bin', \`/usr/local/lib' etc. You can specify
+an installation prefix other than \`/usr/local' using \`--prefix',
+for instance \`--prefix=$HOME'.
+
+For better control, use the options below.
+ --disable-readline Disable line editing support
+ --without-editline Don't use editline even if it is available
+ --disable-sechash Disable support for hashes other than MD5
+ --without-nettle Don't use nettle even if it is available
+ --without-gnutls Don't use gnutls even if it is available
+ --without-nss Don't use NSS even if it is available
+ --without-tomcrypt Don't use libtomcrypt even if it is available
+ --disable-nts Disable NTS support
+ --disable-cmdmon Disable command and monitoring support
+ --disable-ntp Disable NTP support
+ --disable-refclock Disable reference clock support
+ --disable-phc Disable PHC refclock driver
+ --disable-pps Disable PPS refclock driver
+ --disable-ipv6 Disable IPv6 support
+ --disable-rtc Don't include RTC even on Linux
+ --disable-privdrop Disable support for dropping root privileges
+ --without-libcap Don't use libcap even if it is available
+ --enable-scfilter Enable support for system call filtering
+ --without-seccomp Don't use seccomp even if it is available
+ --disable-asyncdns Disable asynchronous name resolving
+ --disable-forcednsretry Don't retry on permanent DNS error
+ --without-aes-gcm-siv Don't use AES-GCM-SIV for NTS even if it is available
+ --without-clock-gettime Don't use clock_gettime() even if it is available
+ --disable-timestamping Disable support for SW/HW timestamping
+ --enable-ntp-signd Enable support for MS-SNTP authentication in Samba
+ --with-ntp-era=SECONDS Specify earliest assumed NTP time in seconds
+ since 1970-01-01 [50*365 days ago]
+ --with-user=USER Specify default chronyd user [root]
+ --with-hwclockfile=PATH Specify default path to hwclock(8) adjtime file
+ --with-pidfile=PATH Specify default pidfile [/var/run/chrony/chronyd.pid]
+ --with-rtcdevice=PATH Specify default path to RTC device [/dev/rtc]
+ --with-sendmail=PATH Path to sendmail binary [/usr/lib/sendmail]
+ --enable-debug Enable debugging support
+
+Fine tuning of the installation directories:
+ --sysconfdir=DIR chrony.conf location [/etc]
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --datarootdir=DIR data root [PREFIX/share]
+ --mandir=DIR man documentation [DATAROOTDIR/man]
+ --docdir=DIR documentation root [DATAROOTDIR/doc/chrony]
+ --localstatedir=DIR modifiable single-machine data [/var]
+ --chronyrundir=DIR location for chrony sockets [LOCALSTATEDIR/run/chrony]
+ --chronyvardir=DIR location for chrony data [LOCALSTATEDIR/lib/chrony]
+
+Overriding system detection when cross-compiling:
+ --host-system=OS Specify system name (uname -s)
+ --host-release=REL Specify system release (uname -r)
+ --host-machine=CPU Specify machine (uname -m)
+
+Some influential environment variables:
+ CC C compiler command
+ CFLAGS C compiler flags
+ CPPFLAGS C preprocessor flags, e.g. -I<include dir> if you have
+ 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.
+
+EOF
+
+}
+#}}}
+#{{{
+add_def () {
+ if [ "x$2" = "x" ]; then
+ echo "#define $1 1" >> config.h
+ else
+ echo "#define $1 $2" >> config.h
+ fi
+}
+#}}}
+#{{{ get_features
+get_features () {
+ ff=1
+ for f; do
+ if [ "$ff" = "0" ]; then
+ printf " "
+ fi
+ if grep "define FEAT_$f" config.h > /dev/null; then
+ printf "%s" "+$f"
+ else
+ printf "%s" "-$f"
+ fi
+ ff=0
+ done
+}
+#}}}
+
+# ======================================================================
+
+
+
+OPERATINGSYSTEM=`uname -s`
+VERSION=`uname -r`
+MACHINE=`uname -m`
+
+LIBS=""
+EXTRA_LIBS=""
+EXTRA_CLI_LIBS=""
+EXTRA_OBJECTS=""
+EXTRA_CLI_OBJECTS=""
+
+feat_debug=0
+feat_cmdmon=1
+feat_ntp=1
+feat_refclock=1
+feat_readline=1
+try_editline=1
+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
+try_libcap=-1
+try_clockctl=0
+feat_scfilter=0
+try_seccomp=-1
+priv_ops=""
+feat_ipv6=1
+feat_phc=1
+try_phc=0
+feat_pps=1
+try_setsched=0
+try_lockmem=0
+feat_asyncdns=1
+feat_forcednsretry=1
+try_aes_gcm_siv=1
+try_clock_gettime=1
+try_arc4random=1
+try_recvmmsg=1
+feat_timestamping=1
+try_timestamping=0
+feat_ntp_signd=0
+ntp_era_split=""
+use_pthread=0
+default_user="root"
+default_hwclockfile=""
+default_pidfile="/var/run/chrony/chronyd.pid"
+default_rtcdevice="/dev/rtc"
+mail_program="/usr/lib/sendmail"
+
+for option
+do
+ case "$option" in
+ --enable-debug )
+ feat_debug=1
+ ;;
+ --disable-readline )
+ feat_readline=0
+ ;;
+ --without-editline )
+ try_editline=0
+ ;;
+ --prefix=* | --install_prefix=* )
+ SETPREFIX=`echo $option | sed -e 's/[^=]*=//;'`
+ ;;
+ --exec-prefix=* )
+ SETEPREFIX=`echo $option | sed -e 's/[^=]*=//;'`
+ ;;
+ --sysconfdir=* )
+ SETSYSCONFDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --bindir=* )
+ SETBINDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --sbindir=* )
+ SETSBINDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --datarootdir=* )
+ SETDATAROOTDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --mandir=* )
+ SETMANDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --docdir=* )
+ SETDOCDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --localstatedir=* )
+ SETLOCALSTATEDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --chronyrundir=* | --chronysockdir=* )
+ SETCHRONYRUNDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --chronyvardir=* )
+ SETCHRONYVARDIR=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --disable-cmdmon)
+ feat_cmdmon=0
+ ;;
+ --disable-ntp)
+ feat_ntp=0
+ ;;
+ --disable-refclock)
+ feat_refclock=0
+ ;;
+ --disable-rtc)
+ feat_rtc=0
+ ;;
+ --disable-ipv6)
+ feat_ipv6=0
+ ;;
+ --disable-phc)
+ feat_phc=0
+ ;;
+ --disable-pps)
+ feat_pps=0
+ ;;
+ --disable-privdrop)
+ feat_droproot=0
+ ;;
+ --without-libcap|--disable-linuxcaps)
+ try_libcap=0
+ ;;
+ --enable-scfilter)
+ feat_scfilter=1
+ ;;
+ --disable-scfilter)
+ feat_scfilter=0
+ ;;
+ --without-seccomp)
+ try_seccomp=0
+ ;;
+ --disable-asyncdns)
+ feat_asyncdns=0
+ ;;
+ --disable-forcednsretry)
+ feat_forcednsretry=0
+ ;;
+ --without-aes-gcm-siv)
+ try_aes_gcm_siv=0
+ ;;
+ --without-clock-gettime)
+ try_clock_gettime=0
+ ;;
+ --disable-timestamping)
+ feat_timestamping=0
+ ;;
+ --enable-ntp-signd)
+ feat_ntp_signd=1
+ ;;
+ --with-ntp-era=* )
+ ntp_era_split=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --with-user=* )
+ default_user=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --with-hwclockfile=* )
+ default_hwclockfile=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --with-pidfile=* )
+ default_pidfile=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --with-rtcdevice=* )
+ default_rtcdevice=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --with-sendmail=* )
+ mail_program=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --disable-sechash )
+ feat_sechash=0
+ ;;
+ --without-nettle )
+ try_nettle=0
+ ;;
+ --without-nss )
+ try_nss=0
+ ;;
+ --without-tomcrypt )
+ try_tomcrypt=0
+ ;;
+ --disable-nts )
+ feat_nts=0
+ ;;
+ --without-gnutls )
+ try_gnutls=0
+ ;;
+ --host-system=* )
+ OPERATINGSYSTEM=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --host-release=* )
+ VERSION=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --host-machine=* )
+ MACHINE=`echo $option | sed -e 's/^.*=//;'`
+ ;;
+ --help | -h )
+ usage
+ exit 0
+ ;;
+ * )
+ echo "Unrecognized option : " $option
+ esac
+done
+
+rm -f config.h config.log
+
+SYSTEM=${OPERATINGSYSTEM}-${MACHINE}
+
+case $OPERATINGSYSTEM in
+ Linux)
+ EXTRA_OBJECTS="sys_generic.o sys_linux.o sys_timex.o sys_posix.o"
+ [ $try_libcap != "0" ] && try_libcap=1
+ try_rtc=1
+ [ $try_seccomp != "0" ] && try_seccomp=1
+ try_timestamping=1
+ try_setsched=1
+ try_lockmem=1
+ try_phc=1
+ try_arc4random=0
+ add_def LINUX
+ echo "Configuring for " $SYSTEM
+ ;;
+ FreeBSD)
+ # recvmmsg() seems to be broken on FreeBSD 11.0 and it's just
+ # a wrapper around recvmsg()
+ try_recvmmsg=0
+ EXTRA_OBJECTS="sys_generic.o sys_netbsd.o sys_timex.o sys_posix.o"
+ try_setsched=1
+ try_lockmem=1
+ add_def FREEBSD
+ if [ $feat_droproot = "1" ]; then
+ add_def FEAT_PRIVDROP
+ priv_ops="ADJUSTTIME ADJUSTTIMEX SETTIME BINDSOCKET"
+ fi
+ echo "Configuring for $SYSTEM"
+ ;;
+ NetBSD)
+ EXTRA_OBJECTS="sys_generic.o sys_netbsd.o sys_timex.o sys_posix.o"
+ try_clockctl=1
+ try_setsched=1
+ try_lockmem=1
+ add_def NETBSD
+ echo "Configuring for $SYSTEM"
+ ;;
+ Darwin)
+ EXTRA_OBJECTS="sys_macosx.o"
+ LIBS="$LIBS -lresolv"
+ add_def MACOSX
+ if [ $feat_droproot = "1" ]; then
+ add_def FEAT_PRIVDROP
+ priv_ops="ADJUSTTIME SETTIME BINDSOCKET"
+ fi
+ major=`echo $VERSION | cut -d. -f1`
+ # ntp_adjtime is not available in macOS 10.12 (Darwin 16.x.x) and earlier
+ if [ $major -gt "16" ]; then
+ add_def HAVE_MACOS_SYS_TIMEX
+ EXTRA_OBJECTS="$EXTRA_OBJECTS sys_generic.o sys_netbsd.o sys_timex.o"
+ if [ $feat_droproot = "1" ]; then
+ priv_ops="$priv_ops ADJUSTTIMEX"
+ fi
+ fi
+ echo "Configuring for macOS (" $SYSTEM "macOS version" $VERSION ")"
+ ;;
+ SunOS)
+ EXTRA_OBJECTS="sys_generic.o sys_solaris.o sys_timex.o sys_posix.o"
+ LIBS="$LIBS -lsocket -lnsl -lkvm -lelf -lresolv"
+ try_setsched=1
+ try_lockmem=1
+ add_def SOLARIS
+ # These are needed to have msg_control in struct msghdr
+ add_def __EXTENSIONS__
+ add_def _XOPEN_SOURCE 1
+ add_def _XOPEN_SOURCE_EXTENDED 1
+ if [ $feat_droproot = "1" ]; then
+ add_def FEAT_PRIVDROP
+ priv_ops="ADJUSTTIMEX SETTIME BINDSOCKET"
+ fi
+ echo "Configuring for illumos (" $SYSTEM "SunOS version" $VERSION ")"
+ ;;
+ * )
+ echo "error: $SYSTEM is not supported (yet?)"
+ exit 1
+ ;;
+esac
+
+if [ $feat_debug = "1" ]; then
+ add_def FEAT_DEBUG
+fi
+add_def DEBUG $feat_debug
+
+if [ $feat_cmdmon = "1" ]; then
+ add_def FEAT_CMDMON
+ EXTRA_OBJECTS="$EXTRA_OBJECTS cmdmon.o manual.o pktlength.o"
+fi
+
+if [ $feat_ntp = "1" ]; then
+ add_def FEAT_NTP
+ 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"
+ fi
+else
+ feat_asyncdns=0
+ feat_timestamping=0
+fi
+
+if [ "$feat_cmdmon" = "1" ] || [ $feat_ntp = "1" ]; then
+ EXTRA_OBJECTS="$EXTRA_OBJECTS addrfilt.o clientlog.o keys.o nameserv.o"
+else
+ feat_ipv6=0
+fi
+
+if [ $feat_refclock = "1" ]; then
+ add_def FEAT_REFCLOCK
+ EXTRA_OBJECTS="$EXTRA_OBJECTS refclock.o refclock_phc.o refclock_pps.o refclock_shm.o refclock_sock.o"
+fi
+
+MYCC="$CC"
+MYCFLAGS="$CFLAGS"
+MYCPPFLAGS="$CPPFLAGS"
+MYLDFLAGS="$LDFLAGS"
+
+if [ "x$MYCC" = "x" ]; then
+ for cc in gcc clang cc ""; do
+ if [ "x$cc" = "x" ]; then
+ echo "error: no C compiler found"
+ exit 1
+ fi
+ MYCC=$cc
+ if test_code "$MYCC" '' '' '' ''; then
+ break
+ fi
+ done
+else
+ if ! test_code "$MYCC" '' '' '' ''; then
+ echo "error: C compiler $MYCC cannot create executables"
+ exit 1
+ fi
+fi
+
+if [ "x$MYCFLAGS" = "x" ]; then
+ MYCFLAGS="-O2 -g"
+
+ TESTCFLAGS="-D_FORTIFY_SOURCE=2 -fPIE"
+ TESTLDFLAGS="-pie -Wl,-z,relro,-z,now"
+ if test_code 'hardening compiler options' '' "$TESTCFLAGS" "$TESTLDFLAGS" ''; then
+ MYCFLAGS="$MYCFLAGS $TESTCFLAGS"
+ MYLDFLAGS="$MYLDFLAGS $TESTLDFLAGS"
+ fi
+ TESTCFLAGS="-fstack-protector-strong --param=ssp-buffer-size=4"
+ if test_code '-fstack-protector-strong' '' "$TESTCFLAGS" '' ''; then
+ MYCFLAGS="$MYCFLAGS $TESTCFLAGS"
+ else
+ TESTCFLAGS="-fstack-protector --param=ssp-buffer-size=4"
+ if test_code '-fstack-protector' '' "$TESTCFLAGS" '' ''; then
+ MYCFLAGS="$MYCFLAGS $TESTCFLAGS"
+ fi
+ fi
+fi
+
+TESTCFLAGS="-fwrapv"
+if test_code '-fwrapv' '' "$TESTCFLAGS" '' ''; then
+ GETDATE_CFLAGS="-fwrapv"
+else
+ GETDATE_CFLAGS=""
+fi
+
+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];'
+then
+ add_def HAVE_LONG_TIME_T 1
+
+ if [ "x$ntp_era_split" != "x" ]; then
+ split_seconds=$ntp_era_split
+ split_days=0
+ else
+ if [ "x$SOURCE_DATE_EPOCH" != "x" ]; then
+ split_seconds=$SOURCE_DATE_EPOCH
+ else
+ split_seconds=`date '+%s'`
+ fi
+ if [ "x$split_seconds" = "x" ]; then
+ echo "error: could not get current time, --with-ntp-era option is needed"
+ exit 1
+ fi
+ split_days=$((50 * 365))
+ fi
+
+ add_def NTP_ERA_SPLIT "(${split_seconds}LL - $split_days * 24 * 3600)"
+
+ date_format='+%Y-%m-%dT%H:%M:%SZ'
+
+ # Print the full NTP interval if a suitable date is found
+ if [ "x`date -u -d '1970-01-01 UTC 9 days ago 5 seconds 3 seconds' \
+ $date_format 2> /dev/null`" = "x1969-12-23T00:00:08Z" ]
+ then
+ time1="`date -u -d "1970-01-01 UTC $split_days days ago $split_seconds seconds" \
+ $date_format`"
+ time2="`date -u -d "1970-01-01 UTC $split_days days ago $split_seconds seconds 4294967296 seconds" \
+ $date_format`"
+ echo "NTP time mapped to $time1/$time2"
+ fi
+fi
+
+MATHCODE='return (int) pow(2.0, log(sqrt((double)argc)));'
+if ! test_code 'math' 'math.h' '' '' "$MATHCODE"; then
+ if test_code 'math in -lm' 'math.h' '' '-lm' "$MATHCODE"; then
+ LIBS="$LIBS -lm"
+ else
+ echo "error: could not compile/link a program which uses sqrt(), log(), pow()"
+ exit 1
+ fi
+fi
+
+if test_code 'struct in_pktinfo' 'sys/socket.h netinet/in.h' '' '' '
+ struct in_pktinfo ipi;
+ return sizeof (ipi.ipi_spec_dst.s_addr) + IP_PKTINFO;'
+then
+ add_def HAVE_IN_PKTINFO
+fi
+
+if [ $feat_ipv6 = "1" ] && \
+ 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;
+ n.sin6_scope_id = 0;
+ return !inet_ntop(AF_INET6, &n.sin6_addr.s6_addr, p, sizeof(p));'
+then
+ add_def FEAT_IPV6
+ if test_code 'struct in6_pktinfo' 'sys/socket.h netinet/in.h' '' '' '
+ return sizeof (struct in6_pktinfo) + IPV6_PKTINFO;'
+ then
+ add_def HAVE_IN6_PKTINFO
+ else
+ if test_code 'struct in6_pktinfo with _GNU_SOURCE' 'sys/socket.h netinet/in.h' \
+ '-D_GNU_SOURCE' '' 'return sizeof (struct in6_pktinfo) + IPV6_PKTINFO;'
+ then
+ add_def _GNU_SOURCE
+ add_def HAVE_IN6_PKTINFO
+ fi
+ fi
+fi
+
+if ! test_code 'O_NOFOLLOW flag' 'sys/types.h sys/stat.h fcntl.h' '' "$LIBS" \
+ 'return open("/dev/null", O_NOFOLLOW);'
+then
+ if test_code 'O_NOFOLLOW flag with _GNU_SOURCE' 'sys/types.h sys/stat.h fcntl.h' \
+ '-D_GNU_SOURCE' "$LIBS" \
+ 'return open("/dev/null", O_NOFOLLOW);'
+ then
+ add_def _GNU_SOURCE
+ else
+ echo "error: open() does not support O_NOFOLLOW flag"
+ exit 1
+ fi
+fi
+
+if [ $try_clock_gettime = "1" ]; then
+ if test_code 'clock_gettime()' 'time.h' '' '' \
+ 'clock_gettime(CLOCK_REALTIME, (void *)1);'
+ then
+ add_def HAVE_CLOCK_GETTIME
+ else
+ if test_code 'clock_gettime() in -lrt' 'time.h' '' '-lrt' \
+ 'clock_gettime(CLOCK_REALTIME, (void *)1);'
+ then
+ add_def HAVE_CLOCK_GETTIME
+ EXTRA_LIBS="$EXTRA_LIBS -lrt"
+ fi
+ fi
+fi
+
+if ! test_code 'getaddrinfo()' 'sys/types.h sys/socket.h netdb.h' '' "$LIBS" \
+ 'return getaddrinfo(0, 0, 0, 0);'
+then
+ echo "error: getaddrinfo() not found"
+ exit 1
+fi
+
+if [ $feat_asyncdns = "1" ] && \
+ 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
+ EXTRA_OBJECTS="$EXTRA_OBJECTS nameserv_async.o"
+ use_pthread=1
+fi
+
+if [ $try_arc4random = "1" ] && \
+ test_code 'arc4random_buf()' 'stdlib.h' '' '' \
+ 'arc4random_buf((void *)1, 1);'
+then
+ add_def HAVE_ARC4RANDOM
+else
+ if test_code 'getrandom()' 'stdlib.h sys/random.h' '' '' \
+ 'return getrandom((void *)1, 1, 0);'; then
+ add_def HAVE_GETRANDOM
+ fi
+fi
+
+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' '' "$LIBS" "$RECVMMSG_CODE"; then
+ add_def HAVE_RECVMMSG
+ else
+ if test_code 'recvmmsg() with _GNU_SOURCE' 'sys/socket.h' '-D_GNU_SOURCE' \
+ "$LIBS" "$RECVMMSG_CODE"
+ then
+ add_def _GNU_SOURCE
+ add_def HAVE_RECVMMSG
+ fi
+ fi
+fi
+
+if [ $feat_timestamping = "1" ] && [ $try_timestamping = "1" ] &&
+ test_code 'SW/HW timestamping' 'sys/types.h sys/socket.h linux/net_tstamp.h
+ linux/errqueue.h linux/ptp_clock.h' '' '' '
+ int val = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE | SOF_TIMESTAMPING_OPT_CMSG;
+ return sizeof (struct scm_timestamping) + SCM_TSTAMP_SND + PTP_SYS_OFFSET +
+ setsockopt(0, SOL_SOCKET, SO_SELECT_ERR_QUEUE + SO_TIMESTAMPING,
+ &val, sizeof (val));'
+then
+ add_def HAVE_LINUX_TIMESTAMPING
+ EXTRA_OBJECTS="$EXTRA_OBJECTS hwclock.o ntp_io_linux.o"
+
+ if test_code 'other timestamping options' \
+ 'sys/types.h sys/socket.h linux/net_tstamp.h' '' '' '
+ struct scm_ts_pktinfo pktinfo;
+ pktinfo.if_index = pktinfo.pkt_length = 0;
+ return pktinfo.if_index + pktinfo.pkt_length + HWTSTAMP_FILTER_NTP_ALL +
+ SCM_TIMESTAMPING_PKTINFO +
+ SOF_TIMESTAMPING_OPT_PKTINFO + SOF_TIMESTAMPING_OPT_TX_SWHW;'; then
+ add_def HAVE_LINUX_TIMESTAMPING_RXFILTER_NTP 1
+ add_def HAVE_LINUX_TIMESTAMPING_OPT_PKTINFO 1
+ add_def HAVE_LINUX_TIMESTAMPING_OPT_TX_SWHW 1
+ fi
+fi
+
+timepps_h=""
+if [ $feat_refclock = "1" ] && [ $feat_pps = "1" ]; then
+ if test_code '<sys/timepps.h>' 'inttypes.h time.h sys/timepps.h' '' '' ''; then
+ timepps_h="sys/timepps.h"
+ add_def HAVE_SYS_TIMEPPS_H
+ else
+ if test_code '<timepps.h>' 'inttypes.h time.h timepps.h' '' '' ''; then
+ timepps_h="timepps.h"
+ add_def HAVE_TIMEPPS_H
+ fi
+ fi
+fi
+
+if [ "x$timepps_h" != "x" ] && \
+ test_code 'PPSAPI' "inttypes.h string.h time.h $timepps_h" '' '' '
+ 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
+fi
+
+if [ $feat_droproot = "1" ] && [ $try_libcap = "1" ] && \
+ test_code \
+ libcap \
+ 'sys/types.h pwd.h sys/prctl.h sys/capability.h grp.h' \
+ '' '-lcap' \
+ 'prctl(PR_SET_KEEPCAPS, 1);cap_set_proc(cap_from_text("cap_sys_time=ep"));'
+then
+ add_def FEAT_PRIVDROP
+ EXTRA_LIBS="$EXTRA_LIBS -lcap"
+fi
+
+if [ $feat_droproot = "1" ] && [ $try_clockctl = "1" ] && \
+ test_code '<sys/clockctl.h>' 'sys/clockctl.h' '' '' ''
+then
+ add_def FEAT_PRIVDROP
+ priv_ops="BINDSOCKET"
+fi
+
+if [ $feat_scfilter = "1" ] && [ $try_seccomp = "1" ] && \
+ test_code seccomp 'seccomp.h' '' '-lseccomp' \
+ 'seccomp_init(SCMP_ACT_KILL);'
+then
+ add_def FEAT_SCFILTER
+ if [ $feat_ntp = "1" ]; then
+ # NAME2IPADDRESS shouldn't be enabled together with a privops operation
+ # used by the main thread as the helper process works on one request at
+ # a time and the async resolver would block the main thread
+ priv_ops="NAME2IPADDRESS RELOADDNS"
+ fi
+ EXTRA_LIBS="$EXTRA_LIBS -lseccomp"
+fi
+
+if [ "x$priv_ops" != "x" ]; then
+ EXTRA_OBJECTS="$EXTRA_OBJECTS privops.o"
+ add_def PRIVOPS_HELPER
+ for o in $priv_ops; do
+ add_def PRIVOPS_$o
+ done
+fi
+
+if [ $feat_rtc = "1" ] && [ $try_rtc = "1" ] && \
+ test_code '<linux/rtc.h>' 'sys/ioctl.h linux/rtc.h' '' '' \
+ 'ioctl(1, RTC_UIE_ON&RTC_UIE_OFF&RTC_RD_TIME&RTC_SET_TIME, 0&RTC_UF);'
+then
+ EXTRA_OBJECTS="$EXTRA_OBJECTS rtc_linux.o"
+ add_def FEAT_RTC
+fi
+
+if [ $feat_refclock = "1" ] && [ $feat_phc = "1" ] && [ $try_phc = "1" ] && \
+ grep '#define HAVE_CLOCK_GETTIME' config.h > /dev/null && \
+ test_code '<linux/ptp_clock.h>' 'sys/ioctl.h linux/ptp_clock.h' '' '' \
+ 'ioctl(1, PTP_CLOCK_GETCAPS + PTP_SYS_OFFSET, 0);'
+then
+ grep 'HAVE_LINUX_TIMESTAMPING' config.h > /dev/null ||
+ EXTRA_OBJECTS="$EXTRA_OBJECTS hwclock.o"
+ add_def FEAT_PHC
+fi
+
+if [ $try_setsched = "1" ] && \
+ test_code \
+ 'pthread_setschedparam()' \
+ 'pthread.h sched.h' '-pthread' '' '
+ struct sched_param sched;
+ sched_get_priority_max(SCHED_FIFO);
+ pthread_setschedparam(pthread_self(), SCHED_FIFO, &sched);'
+then
+ add_def HAVE_PTHREAD_SETSCHEDPARAM
+ use_pthread=1
+fi
+
+if [ $try_lockmem = "1" ] && \
+ test_code \
+ 'mlockall()' \
+ 'sys/mman.h' '' '' '
+ mlockall(MCL_CURRENT|MCL_FUTURE);'
+then
+ add_def HAVE_MLOCKALL
+fi
+if [ $try_lockmem = "1" ] && \
+ test_code \
+ '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
+fi
+
+if [ $feat_forcednsretry = "1" ]
+then
+ add_def FORCE_DNSRETRY
+fi
+
+READLINE_LINK=""
+if [ $feat_readline = "1" ]; then
+ if [ $try_editline = "1" ]; then
+ if test_code editline 'stdio.h editline/readline.h' '' '-ledit' \
+ 'add_history(readline("prompt"));'
+ then
+ add_def FEAT_READLINE
+ READLINE_LINK="-ledit"
+ fi
+ fi
+
+ EXTRA_CLI_LIBS="$EXTRA_CLI_LIBS $READLINE_LINK"
+fi
+
+HASH_OBJ="hash_intmd5.o"
+HASH_LINK=""
+
+if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_nettle = "1" ]; then
+ test_cflags="`pkg_config --cflags nettle`"
+ test_link="`pkg_config --libs nettle`"
+ if test_code 'nettle' 'nettle/nettle-meta.h nettle/sha2.h' \
+ "$test_cflags" "$test_link" \
+ 'return nettle_hashes[0]->context_size;'
+ then
+ HASH_OBJ="hash_nettle.o"
+ HASH_LINK="$test_link"
+ MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
+ add_def FEAT_SECHASH
+
+ if test_code 'CMAC in nettle' 'nettle/cmac.h' "$test_cflags" "$test_link" \
+ 'cmac128_update((void *)1, (void *)2, (void *)3, 1, (void *)4);'
+ then
+ add_def HAVE_CMAC
+ EXTRA_OBJECTS="$EXTRA_OBJECTS cmac_nettle.o"
+ EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS cmac_nettle.o"
+ fi
+ fi
+fi
+
+if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_gnutls = "1" ]; then
+ test_cflags="`pkg_config --cflags gnutls`"
+ test_link="`pkg_config --libs gnutls`"
+ if test_code 'gnutls' 'gnutls/crypto.h' \
+ "$test_cflags" "$test_link" '
+ return gnutls_hash((void *)1, (void *)2, 1);'
+ then
+ HASH_OBJ="hash_gnutls.o"
+ HASH_LINK="$test_link"
+ MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
+ add_def FEAT_SECHASH
+
+ if test_code 'CMAC in gnutls' 'gnutls/crypto.h' "$test_cflags" "$test_link" \
+ 'return gnutls_hmac_init((void *)1, GNUTLS_MAC_AES_CMAC_128, (void *)2, 0);'
+ then
+ add_def HAVE_CMAC
+ EXTRA_OBJECTS="$EXTRA_OBJECTS cmac_gnutls.o"
+ EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS cmac_gnutls.o"
+ fi
+ fi
+fi
+
+if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_nss = "1" ]; then
+ test_cflags="`pkg_config --cflags nss`"
+ test_link="`pkg_config --libs-only-L nss` -lfreebl3 -lnssutil3"
+ if test_code 'NSS' 'nss.h hasht.h nsslowhash.h' \
+ "$test_cflags" "$test_link" \
+ 'NSSLOWHASH_Begin(NSSLOWHASH_NewContext(NSSLOW_Init(), HASH_AlgSHA512));'
+ then
+ HASH_OBJ="hash_nss.o"
+ HASH_LINK="$test_link"
+ MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
+ add_def FEAT_SECHASH
+ fi
+fi
+
+if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_tomcrypt = "1" ]; then
+ if test_code 'tomcrypt' 'tomcrypt.h' '-I/usr/include/tomcrypt' '-ltomcrypt' \
+ 'hash_memory_multi(find_hash("md5"), (void *)1, (void *)2, (void *)3, 1, (void *)4, 1);'
+ then
+ HASH_OBJ="hash_tomcrypt.o"
+ HASH_LINK="-ltomcrypt"
+ 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" ]; then
+ if [ "$HASH_OBJ" = "hash_gnutls.o" ]; then
+ test_cflags=""
+ test_link=""
+ else
+ test_cflags="`pkg_config --cflags gnutls`"
+ test_link="`pkg_config --libs gnutls`"
+ fi
+ if test_code 'TLS1.3 in gnutls' 'gnutls/gnutls.h' \
+ "$test_cflags" "$test_link $LIBS" '
+ return gnutls_init((void *)1, 0) + GNUTLS_TLS1_3 +
+ gnutls_priority_init2((void *)1, "", NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND) +
+ gnutls_prf_rfc5705((void *)1, 0, "", 0, "", 16, (void *)2);'
+ then
+ if [ $try_nettle = "1" ] && test_code 'AES-SIV-CMAC in nettle' \
+ 'nettle/siv-cmac.h' "" "$LIBS" \
+ 'siv_cmac_aes128_set_key((void *)1, (void *)2);'
+ then
+ EXTRA_OBJECTS="$EXTRA_OBJECTS siv_nettle.o"
+ add_def HAVE_SIV
+ add_def HAVE_NETTLE_SIV_CMAC
+ if [ $try_aes_gcm_siv = "1" ] && test_code 'AES-GCM-SIV in nettle' \
+ 'nettle/siv-gcm.h' "" "$LIBS" \
+ 'siv_gcm_aes128_encrypt_message((void *)1, 0, NULL, 0, (void *)2, 16, (void *)3,
+ (void *)4);'
+ then
+ add_def HAVE_NETTLE_SIV_GCM
+ fi
+ else
+ if test_code 'AES-SIV-CMAC in gnutls' 'gnutls/crypto.h' \
+ "$test_cflags" "$test_link $LIBS" '
+ return gnutls_aead_cipher_init((void *)1, GNUTLS_CIPHER_AES_128_SIV, (void *)2);'
+ then
+ EXTRA_OBJECTS="$EXTRA_OBJECTS siv_gnutls.o"
+ add_def HAVE_SIV
+ if [ $try_aes_gcm_siv = "1" ] && test_code 'AES-GCM-SIV in gnutls' \
+ 'gnutls/crypto.h' "$test_cflags" "$test_link $LIBS" '
+ return gnutls_aead_cipher_init((void *)1, GNUTLS_CIPHER_AES_128_SIV_GCM,
+ (void *)2);'
+ then
+ add_def HAVE_GNUTLS_SIV_GCM
+ fi
+ if test_code 'gnutls_aead_cipher_set_key()' 'gnutls/crypto.h' \
+ "$test_cflags" "$test_link $LIBS" '
+ return gnutls_aead_cipher_set_key((void *)1, (void *)2);'
+ then
+ add_def HAVE_GNUTLS_AEAD_CIPHER_SET_KEY
+ fi
+ else
+ if test_code 'AES128 in nettle' 'nettle/aes.h' '' "$LIBS" \
+ 'aes128_set_encrypt_key((void *)1, (void *)2);'
+ then
+ EXTRA_OBJECTS="$EXTRA_OBJECTS siv_nettle.o"
+ add_def HAVE_SIV
+ fi
+ fi
+ fi
+
+ if grep '#define HAVE_SIV' config.h > /dev/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"
+ LIBS="$LIBS $test_link"
+ MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
+ add_def FEAT_NTS
+ fi
+ fi
+fi
+
+if [ $use_pthread = "1" ]; then
+ MYCFLAGS="$MYCFLAGS -pthread"
+fi
+
+SYSCONFDIR=/etc
+if [ "x$SETSYSCONFDIR" != "x" ]; then
+ SYSCONFDIR=$SETSYSCONFDIR
+fi
+
+PREFIX=/usr/local
+if [ "x$SETPREFIX" != "x" ]; then
+ PREFIX=$SETPREFIX
+fi
+
+EPREFIX=${PREFIX}
+if [ "x$SETEPREFIX" != "x" ]; then
+ EPREFIX=$SETEPREFIX
+fi
+
+BINDIR=${EPREFIX}/bin
+if [ "x$SETBINDIR" != "x" ]; then
+ BINDIR=$SETBINDIR
+fi
+
+SBINDIR=${EPREFIX}/sbin
+if [ "x$SETSBINDIR" != "x" ]; then
+ SBINDIR=$SETSBINDIR
+fi
+
+DATAROOTDIR=${PREFIX}/share
+if [ "x$SETDATAROOTDIR" != "x" ]; then
+ DATAROOTDIR=$SETDATAROOTDIR
+fi
+
+MANDIR=${DATAROOTDIR}/man
+if [ "x$SETMANDIR" != "x" ]; then
+ MANDIR=$SETMANDIR
+fi
+
+DOCDIR=${DATAROOTDIR}/doc/chrony
+if [ "x$SETDOCDIR" != "x" ]; then
+ DOCDIR=$SETDOCDIR
+fi
+
+LOCALSTATEDIR=/var
+if [ "x$SETLOCALSTATEDIR" != "x" ]; then
+ LOCALSTATEDIR=$SETLOCALSTATEDIR
+fi
+
+CHRONYRUNDIR=${LOCALSTATEDIR}/run/chrony
+if [ "x$SETCHRONYRUNDIR" != "x" ]; then
+ CHRONYRUNDIR=$SETCHRONYRUNDIR
+fi
+
+CHRONYVARDIR=${LOCALSTATEDIR}/lib/chrony
+if [ "x$SETCHRONYVARDIR" != "x" ]; then
+ CHRONYVARDIR=$SETCHRONYVARDIR
+fi
+
+add_def DEFAULT_CONF_FILE "\"$SYSCONFDIR/chrony.conf\""
+add_def DEFAULT_HWCLOCK_FILE "\"$default_hwclockfile\""
+add_def DEFAULT_PID_FILE "\"$default_pidfile\""
+add_def DEFAULT_RTC_DEVICE "\"$default_rtcdevice\""
+add_def DEFAULT_USER "\"$default_user\""
+add_def DEFAULT_COMMAND_SOCKET "\"$CHRONYRUNDIR/chronyd.sock\""
+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 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"
+
+if [ -f version.txt ]; then
+ CHRONY_VERSION="`cat version.txt`"
+else
+ CHRONY_VERSION="DEVELOPMENT"
+fi
+
+add_def CHRONY_VERSION "\"${CHRONY_VERSION}\""
+
+for f in Makefile doc/Makefile test/unit/Makefile
+do
+ echo Creating $f
+ sed -e "s%@EXTRA_OBJS@%${EXTRA_OBJECTS}%;\
+ s%@EXTRA_CLI_OBJS@%${EXTRA_CLI_OBJECTS}%;\
+ s%@CC@%${MYCC}%;\
+ s%@CFLAGS@%${MYCFLAGS}%;\
+ s%@CPPFLAGS@%${MYCPPFLAGS}%;\
+ s%@LDFLAGS@%${MYLDFLAGS}%;\
+ s%@GETDATE_CFLAGS@%${GETDATE_CFLAGS}%;\
+ s%@LIBS@%${LIBS}%;\
+ s%@EXTRA_LIBS@%${EXTRA_LIBS}%;\
+ s%@EXTRA_CLI_LIBS@%${EXTRA_CLI_LIBS}%;\
+ s%@SYSCONFDIR@%${SYSCONFDIR}%;\
+ s%@BINDIR@%${BINDIR}%;\
+ s%@SBINDIR@%${SBINDIR}%;\
+ s%@DOCDIR@%${DOCDIR}%;\
+ s%@MANDIR@%${MANDIR}%;\
+ s%@LOCALSTATEDIR@%${LOCALSTATEDIR}%;\
+ s%@CHRONYRUNDIR@%${CHRONYRUNDIR}%;\
+ s%@CHRONYVARDIR@%${CHRONYVARDIR}%;\
+ s%@DEFAULT_HWCLOCK_FILE@%${default_hwclockfile}%;\
+ s%@DEFAULT_PID_FILE@%${default_pidfile}%;\
+ s%@DEFAULT_RTC_DEVICE@%${default_rtcdevice}%;\
+ s%@DEFAULT_USER@%${default_user}%;\
+ s%@CHRONY_VERSION@%${CHRONY_VERSION}%;" \
+ < ${f}.in > $f
+done
+
+# =======================================================================
+# vim:et:sw=2:ht=2:sts=2:fdm=marker:cms=#%s
+
diff --git a/contrib/andrew_bishop_1 b/contrib/andrew_bishop_1
new file mode 100644
index 0000000..4c0b437
--- /dev/null
+++ b/contrib/andrew_bishop_1
@@ -0,0 +1,114 @@
+From amb@gedanken.demon.co.uk Tue Aug 17 22:14:00 1999
+Date: Fri, 6 Aug 1999 19:00:24 +0100
+From: Andrew M. Bishop <amb@gedanken.demon.co.uk>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: Re: Chrony and laptop configuration
+
+Hi,
+
+Attached is the apmd_proxy script from the apmd-3.0beta9 distribution.
+
+The changes that I would make are the following:
+
+Replace the update_clock function (line 122) with
+
+update_clock () {
+
+chronyd -f /etc/chrony.conf
+
+}
+
+Around line 171 (in the suspend actions section) I would kill chronyd.
+
+begin 644 apmd_proxy.gz
+M'XL("+L@JS<``V%P;61?<')O>'D`I5K[;]M&$OY9^BLV2JZQ6TF.`Q2XUDA1
+MQU8<]>('_&A[.!R,-;D2MR&Y+)?TXZ[WO]\W,\N'%-E)[X08CJ2=V7G/-T,_
+M?[9S8_,=GPR?*_S3119?%Z6[?U`3A=_+4F<JMK[059284BU<J?;/CE6L3>9R
+M$!R4VB[5L2X_WNDRKM161!]D/Z:)T9FVZ73I%]$TUUY/E^YV6[W>Q>$'M?O=
+M=]^!^E#?VEB]+=U=;M)4;<4W/Q;.5]-$E[=@-S5QO:V^4S_5N0DD(+I,K%<^
+M(0(?E;:H%-Y'.DU-K&X>5)68GHAJBU3:5G>)R?DK7^G*@(M;*)T_J,+=0:U,
+MYWII,I-7:E'G465!F&AP372^-/%4X5*CS+V.*F5N<<R#EZ[`IBKM<@D.Q)ID
+ML/F267=VC$UA\E@11W?'7Z@[8NWRA5W6I8G)[!7>9X5-C:IL9J:LYR^V2BP)
+M3>J*HJS`@Z],IG2<V=SZJM05?.(35Z>Q*NJ*M0(SJ!1[TK-4FA7RL(&-DN;H
+MC5&%*>'/#&:K"XC'EH%".O>6"42,GBJ=G>\@F_*%B>S"1DJ7RSIKK0*-25Q<
+M0.*RN<A0]`W9U$51799LU#F[KJ?@2Z]*XR&,MS<VM16\Z=3OM2G9K>""P(@1
+M:(;4:ASM37EK(Z.V;JU6.Y`TVH',VQRKF<-9FY.:FG0:DT+P!MA6^J-(J`O0
+M%*4E[<54HO@[T,/E69&:<?";1<R1!=2H9Q5?>_:P.&:D?JM]!?/B3L-ZMRYC
+M-1/$-%D0$BP=1,/OAD'F8A-";<6[X)(A9^ZL3X@LN$W!2V;2^2`X.;50J\Y3
+MIV.*Q;BTMZ:$U8D+$EG?<(B25+FI[ESYD60`1QW1W;^T:=**#(?4%",I[%..
+M0T`\8@HYJW(R=SH:4UQ+T%%4@Z_SC8DI-+M(+`T)B9A@P\\7+,)'4Z(J2"ZS
+M%B0-G:/LW)WNOJ(@2.R2ZM)6[<69),T(/$(&D+T6EM*OKK;'_5B.=(YKJ[K,
+M$1#*E"6817``AXT$6^,7XH/DR&-4%\G^,8P6VP@A!5NBM%1)*`%TGCXC5ZYI
+M^)N)*H[Z3^(J2$0^CN%-R&`78W6C81`E1?;LBG1M'$86O*7DP&?0&W;)$2FU
+MV!4L6\$10A6%-=7%$(IDS;?STXM-HK'Q\7KV3)V<7L[HMY)JBW^YJT*VM`+7
+M(B"41<`?FAL+0TK<^&>!U_GL\NK\1/V\_^%J]KUP5Z_06G*'^-9I\,#>$Z96
+ME.TZBDQ1<:FDURXXB-0]9:D*!LJMXZN+2_5^_^<9ZWMX/O]Y=BXA<WJNWL^/
+MWL_.MUG"]X:XEV:E?GN#DI-'QG,L=`I_OUX/<5]9J977I-][J-SQ&2GR?4)7
+MJ"<(N<'5%8=7C.:X1KU2<'K4X0.ZMB0=/+F^7UTV\T'IK1#,Z3J?$..P0WNB
+M(=GZ>OL34[#IOU"F<)ID4IN%XL!N3'-%;[Y8K<#\LQQZ0JQR"&6LN4(X7/05
+MD9*X*%WVA"`-FW#/9]A\3IJ>EYY@L^ZI55:"9U!:JHIZZB=>*DWA*%I5"J@2
+M3FWD(+!)/<%A_V"GN49H-O+AOO`DG]R0:E23Z6A=Q-2F/PF_P"[2A0[`X1%V
+M`KNF#<'6-U('P'`U`ZD8HS;"&^BNTJZYVZ%.H-.T%8)(OV'26X!=-%>JW1LZ
+MEZ\+$@"5%!`Y?(:?Z5H?$V$NYL=G'^;OYK-#=7!Z\FY^='6^?SD_/5$JX%_`
+M'X``!C0$-OL(D=H:Y(::5>D8JAG+W8ERTTA%D>ZVP-?NCMX#:EL2W?>A4/HP
+M)KFH#L>V(6L!=^5-NI"&<6$J=75YP)"JK%G_!U>CF:8N^DB5K&ED<HK@;>@J
+M)`]Q!A.\<[!':>,8``27$HH-\L)HN=HQ5;0#9XK_=ICY=`B.;Q8Z]::5Y.+J
+MXFQV<GA]>G*]SS+QUT&H%D+I6V?C34T'7$+;X8&!%<ET!!QN&/VZ/.=>24S`
+MG],`ZKRESKW0=5IUL,&S+QCMDO*-&T).T!S5T`]7A'Y#5F1]S@Z.#^;[8%,&
+M9AR0`G[A+;@+H)MCCE3*)=Z#3JS%5,`4:9[H6S0ZYB50'/RHI=^T90Y*D?--
+MF:*!^AJ@#;5%JXN#BSGA*1`"0%5D%<1PAQY&`3V,`N15AN`DM]$^K`UQ*[I0
+MPZ7+B^3!4['"C8'+GD)8PTYC05-<96A$`$C@'NH6"PZT!#E-<)\A'S&-JE1X
+MC!KH-V:_,H!EP,0XB`<=\)&:.1V*B:]G/\T.+LG\P1-=4-&0.GM[=70T/SGB
+M=U<Y\:=)L1FR@+9CB!D<?#R[?']Z"(CBVO^_YOH5FYMZN91PS^`]#)P^Z-)4
+MJ=2A,JD36*8T<DL<BM_2Y$CXE'K:&,`?I8>2IN(RU6@GT0J,_U&B!&[.N7RV
+M`ZP'5W((\*H3"`\NN%1196O5(;,)'F[%1*US-?E51NRN\$H):'6>J`]NZ5OP
+MG5+:=,/AZESL\O1!KJ?YN??%BZ^I&W1L7S=LP<1"K[+.UUC!BCM55NSTY`);
+MRN4H<>KEUVNOE^J''YXB&+UX!2%&?73V*`&WHPVOIV]X.5E[/272O8DV7/#Z
+M40*4336Y9\?L4\5");#4K*@0)ZK$)!2J&<(HK6,I9IZ6$_33#MQ->2*.E:[J
+ML`Y8-^;_\!H.A\SY&L+1\&ZVMM6_AP,IX//#&4_Y$LB^``@%``X#I".\GZ"1
+M\GLB.>215L(RE&#(.=CQM-5*XD)C4)[\KB87:O>O:B<VM_A0#P:@Q/LW;]!_
+MOU4816J@W.&`*N_>\#^->&'0UK!=(V&X[A$I_YQ`KUIY>C<_Y]'K>]4-:LT$
+MCR88<,7KZ6M.4I[(I;(CU6/>*J"O#@4D74L#;B0/R"FP@"4S6NEU"Q4^/1R@
+M4?Y#318;^ZWZYQXW@.&``G"Z\8Q\AT*=4D]]2(U\P&Q'+PX^G![\[?CT<#92
+M;]31\67'<4"MG#L?GU_8X8!^F.X%`8<WH9.OR/#NP_[1F]%H.#!4KGL?3>J1
+M,(`R]TJ,'W107WT5L,G$JQ=T7/WQATKN5C\C7Q"DX`D/U2;3L#_MEDQ<EX1@
+MYDWRD/'".,\ESCM$:F9I&Q?2I[==!(>%I2%S')8QJX&",HVX+B<"F:0K]!%%
+M#ZFTB*`9W\G&=S+,HL=UX.+Y<!B!':R_.T(0#8?DGO7Z$UX-?.*++B[W3P[?
+M_OV1L\SF/8>=BFH,M)G]%REC$(_6=9OB9JYAT?O@Q#,'M$RRYJ%I-B<NH"Y1
+M?LS@I;_]"HB'K>P6S*-*6@^T:S#&.`)QIZ3R*-P]^F,4)!IM#RDO]ION*2C:
+M+-E/:W@Q^"HLMMK9:O`)6OS$_"W4%;[4]!0M<9#/"Z)G7;NE#'MYM+Y1;#8A
+M"\IW>-W&*1<_%"CKJ+GOI]X%4\GRD9ID3N;4Z@.JVWUO)"'";@+9N$RC#LN+
+MM)7UV12434:NPNPF-R=:O7B-=P'.=,D-(I=?Z^A:$-T/7/CR.DW;$P-S#SBS
+M2^X\E\W.%N^@@MQA?FK5DRB(MT$92D4CV2[='V*N$X"YOY*2P%G=WS!5#T5`
+MH;%C7+ITJM"^DI`HG,VKL;JI&3,3>0B.!@?"-K(9N\$(PY@'K?)/AU8`^V&9
+M[:5DTU9^)6<"D`N'@QO#,HTBBR#=H+>YY"4M0>X`S@D+)S+,K:EFR@HECHC;
+M>>.)^0!?06J,B#0>"`7'!TGG&YC?.`LW1V91DX#>+2IJ-Y-N'2H#"NO;0FN6
+MCK:Q8>)@*4H!``CUE9.<"9$KY<%!+`L\,;4`\_/9Q=7Q##<=A/U>,S#T+#7B
+M%2=C9:Y<88V)O*:5=3`W!IF:I^22[]3MR$\&)&,ANR::U.M=1B"]1>..8)AE
+MY])Y--\,=NDE5M>LPDP30EA)$B&^'YE8$/4\?7<A/UAEQ`:G3.!.N?YM6]&Z
+M=.J/.AL"&#-)J8O$1EZ&RM@961++:,5YDG=G>-/7Q8\UM$)7OR)B"3DA>$*A
+ME/K-+N4-C6_6#H8>3#@,?655:YI6<N^H(M?9#7R\.PX/&L2XM:\Y'S`@FONJ
+M.8P[KBBE1,)Q2$.D".^8*M?="J@,+(#K6-%?V]O0R9FQ>.RY:GU6^Y*?HT;)
+M;=7YX+D:K'ZSNZ=\:DRA=NG+8&;'<R.9&K*%_@4`*,]PX%57TVZ&^EV`@D0T
+M^W7_^.P#@.+I!=H&AK.8NR0Y(/AR)^P-&Z,3E:Q"`^.D9<A=6)Z+4:R5A@_4
+MG:T"K5)GF,[YHEM=/M#BHTO,T)A'+(S+:1_`S7?P7(P`>*53>=C,)Q;H?WM[
+M3\*1%I9("C]U;"B=@S6>3J>K.U6**^#JLS%@`CU(D&T_XSV!Q5-""*(J8`&5
+ML;8ET%%IH:&K"1V%#(_OC,`P<D'1/N[^?`/H%B<K>Q[N(DTI;8!A;]]G^=9&
+MV"[L>;%M2ZE?7-D^4\+6*E:ON7,+#QT$+?VQFO1_ER1+T]_C-:D)O7Y)>B17
+M0L1^+E?"L2YEPJ'I>F`_&K3YE\<LX//YY9.GF,U!>*!.B<=/5A>V1-ODQT9^
+MRD?F?6#?+!`9287V2!]E%-8(C&:F'C7XG[`O,4-@*_4%2.R3V;>9K@:K0[N`
+MJ2^WQNG9TX<V6X/"W92$;_@))9TY##F1,3Y8.8JB,@X9<D//_=6HI\<H;"+9
+M(*Z@1-^@Z]Z>>EHC=?!^_^1H1MSAXLNKBT=/TDW23^@N'L*06S;,KB.^G+ZA
+M9+VS]`<VU(5VN'0U`\18Z1B@!,4\0UV(&_>CDE71EP'K#3JV";?N478I^50D
+MI"5+$%`FGU0ZC/P1"#Y?J7@]PB!EH&U"ELIPI4;H"B.5`L^F:DLV'&9!H!EV
+M5&:ZG-+>^=M7?Q%:P-"7%>'RC#;2\D<[%2/8VA.F[-_:/?T)%PLWLA_J]5VW
+>Z]AB9"M2/=L.+(S7D<0S_V\81H;_`M>*^#$A)0``
+`
+end
+
+--
+Andrew.
+----------------------------------------------------------------------
+Andrew M. Bishop amb@gedanken.demon.co.uk
+ http://www.gedanken.demon.co.uk/
+
diff --git a/contrib/andrew_bishop_2 b/contrib/andrew_bishop_2
new file mode 100644
index 0000000..d3ede74
--- /dev/null
+++ b/contrib/andrew_bishop_2
@@ -0,0 +1,95 @@
+From amb@gedanken.demon.co.uk Wed Sep 1 22:26:59 1999
+Date: Thu, 19 Aug 1999 17:30:14 +0100
+From: Andrew M. Bishop <amb@gedanken.demon.co.uk>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: [amb@gedanken.demon.co.uk: Chrony and laptop configuration]
+
+Hi,
+
+What you need to do is replace 10.0.0.0 with the network of the
+freeserve nameservers in the two scripts below.
+
+Other than that you can use it as is.
+
+------- Start of forwarded message -------
+From: "Andrew M. Bishop" <amb@gedanken.demon.co.uk>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: Chrony and laptop configuration
+Date: Sat, 31 Jul 1999 11:02:04 +0100
+
+Attached are the ip-up and ip-down files that I use for chrony.
+(Actually because of the way that debian works they are separate file
+in the /etc/ppp/ip-up.d directory that are run in a SysV init style).
+
+They rely on the presence of an 'ipparam demon' or 'ipparam freeserve'
+line in the PPP options file.
+
+-------------------- /etc/ppp/ip-up --------------------
+#!/bin/sh -f
+#
+# A script to start chrony
+#
+
+PPP_IPPARAM="$6"
+
+if [ $PPP_IPPARAM = "demon" ]; then
+
+ /usr/local/bin/chronyc << EOF
+password xxxxxxx
+online 255.255.255.0/158.152.1.0
+online 255.255.255.0/194.159.253.0
+EOF
+
+fi
+
+if [ $PPP_IPPARAM = "freeserve" ]; then
+
+ /usr/local/bin/chronyc << EOF
+password xxxxxxx
+online 255.255.255.0/10.0.0.0
+EOF
+
+fi
+-------------------- /etc/ppp/ip-up --------------------
+
+-------------------- /etc/ppp/ip-down --------------------
+#!/bin/sh -f
+#
+# A script to stop chrony
+#
+
+PPP_IPPARAM="$6"
+
+if [ $PPP_IPPARAM = "demon" ]; then
+
+ /usr/local/bin/chronyc << EOF
+password xxxxxxx
+offline 255.255.255.0/158.152.1.0
+offline 255.255.255.0/194.159.253.0
+EOF
+
+fi
+
+if [ $PPP_IPPARAM = "freeserve" ]; then
+
+ /usr/local/bin/chronyc << EOF
+password xxxxxxx
+offline 255.255.255.0/10.0.0.0
+EOF
+
+fi
+-------------------- /etc/ppp/ip-down --------------------
+
+--
+Andrew.
+----------------------------------------------------------------------
+Andrew M. Bishop amb@gedanken.demon.co.uk
+ http://www.gedanken.demon.co.uk/
+------- End of forwarded message -------
+
+--
+Andrew.
+----------------------------------------------------------------------
+Andrew M. Bishop amb@gedanken.demon.co.uk
+ http://www.gedanken.demon.co.uk/
+
diff --git a/contrib/bryan_christianson_1/README.txt b/contrib/bryan_christianson_1/README.txt
new file mode 100644
index 0000000..05bdd0c
--- /dev/null
+++ b/contrib/bryan_christianson_1/README.txt
@@ -0,0 +1,103 @@
+Notes for installing chrony on macOS
+Author: Bryan Christianson (bryan@whatroute.net)
+------------------------------------------------
+
+These files are for those admins/users who would prefer to install chrony
+from the source distribution and are intended as guidelines rather than
+being definitive. They can be edited with a plain text editor, such as
+vi, emacs or your favourite IDE (Xcode)
+
+It is assumed you are comfortable with installing software from the
+terminal command line and know how to use sudo to acquire root access.
+
+If you are not familiar with the macOS command line then
+please consider using ChronyControl from http://whatroute.net/chronycontrol.html
+
+ChronyControl provides a gui wrapper for installing these files and sets the
+necessary permissions on each file.
+
+
+Install the chrony software
+---------------------------
+
+You will need xcode and the commandline additions to build and install chrony.
+These can be obtained from Apple's website via the App Store.
+
+cd to the chrony directory
+./configure
+make
+sudo make install
+
+chrony is now installed in default locations (/usr/local/sbin/chronyd,
+/usr/local/bin/chronyc)
+
+Create a chrony.conf file - see the chrony website for details
+
+The support files here assume the following directives are specified in the
+chrony.conf file
+
+keyfile /etc/chrony.d/chrony.keys
+driftfile /var/db/chrony/chrony.drift
+bindcmdaddress /var/db/chrony/chronyd.sock
+logdir /var/log/chrony
+dumpdir /var/db/chrony
+
+Install this file as /etc/chrony.d/chrony.conf and create
+the directories specified in the above directives if they don't exist.
+You will need root permissions to create the directories.
+
+
+Running chronyd
+---------------
+At this point chronyd *could* be run as a daemon. Apple discourage running
+daemons and their preferred method uses the launchd facility. The
+support files here provide a launchd configuration file for chronyd and also
+a shell script and launchd configuration file to rotate the chronyd logs on a daily basis.
+
+
+Support files
+-------------
+Dates and sizes may differ
+-rw-r--r-- 1 yourname staff 2084 4 Aug 22:54 README.txt
+-rwxr-xr-x 1 yourname staff 676 4 Aug 21:18 chronylogrotate.sh
+-rw-r--r-- 1 yourname staff 543 18 Jul 20:10 org.chrony-project.chronyc.plist
+-rw-r--r-- 1 yourname staff 511 19 Jun 18:30 org.chrony-project.chronyd.plist
+
+If you have used chrony support directories other than those suggested, you
+will need to edit each file and make the appropriate changes.
+
+
+Installing the support files
+----------------------------
+
+1. chronylogrotate.sh
+This is a simple shell script that deletes old log files. Unfortunately because
+of the need to run chronyc, the standard macOS logrotation does not work with
+chrony logs.
+
+This script runs on a daily basis under control of launchd and should be
+installed in the /usr/local/bin directory
+
+sudo cp chronylogrotate.sh /usr/local/bin
+sudo chmod +x /usr/local/bin/chronylogrotate.sh
+sudo chown root:wheel /usr/local/bin/chronylogrotate.sh
+
+
+2. org.chrony-project.chronyc.plist
+This file is the launchd plist that runs logrotation each day. You may
+wish to edit this file to change the time of day at which the rotation
+will run, currently 04:05 am
+
+sudo cp org.chrony-project.chronyc.plist /Library/LaunchDaemons
+sudo chown root:wheel /Library/LaunchDaemons/org.chrony-project.chronyc.plist
+sudo chmod 0644 /Library/LaunchDaemons/org.chrony-project.chronyc.plist
+sudo launchctl load -w /Library/LaunchDaemons/org.chrony-project.chronyc.plist
+
+
+3. org.chrony-project.chronyd.plist
+This file is the launchd plist that runs chronyd when the Macintosh starts.
+
+sudo cp org.chrony-project.chronyd.plist /Library/LaunchDaemons
+sudo chown root:wheel /Library/LaunchDaemons/org.chrony-project.chronyd.plist
+sudo chmod 0644 /Library/LaunchDaemons/org.chrony-project.chronyd.plist
+sudo launchctl load -w /Library/LaunchDaemons/org.chrony-project.chronyd.plist
diff --git a/contrib/bryan_christianson_1/chronylogrotate.sh b/contrib/bryan_christianson_1/chronylogrotate.sh
new file mode 100755
index 0000000..f919544
--- /dev/null
+++ b/contrib/bryan_christianson_1/chronylogrotate.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+# chronyd/chronyc - Programs for keeping computer clocks accurate.
+#
+# **********************************************************************
+# * Copyright (C) Bryan Christianson 2015
+# *
+# * 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.
+# *
+# **********************************************************************
+
+LOGDIR=/var/log/chrony
+
+rotate () {
+ prefix=$1
+
+ rm -f $prefix.log.10
+
+ for (( count=9; count>= 0; count-- ))
+ do
+ next=$(( $count+1 ))
+ if [ -f $prefix.log.$count ]; then
+ mv $prefix.log.$count $prefix.log.$next
+ fi
+ done
+
+ if [ -f $prefix.log ]; then
+ mv $prefix.log $prefix.log.0
+ fi
+}
+
+if [ ! -e "$LOGDIR" ]; then
+ logger -s "missing directory: $LOGDIR"
+ exit 1
+fi
+
+cd $LOGDIR
+
+rotate measurements
+rotate statistics
+rotate tracking
+
+#
+# signal chronyd via chronyc
+/usr/local/bin/chronyc cyclelogs > /dev/null
+
+exit $? \ No newline at end of file
diff --git a/contrib/bryan_christianson_1/org.chrony-project.chronyc.plist b/contrib/bryan_christianson_1/org.chrony-project.chronyc.plist
new file mode 100644
index 0000000..94cee17
--- /dev/null
+++ b/contrib/bryan_christianson_1/org.chrony-project.chronyc.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>org.chrony-project.logrotate</string>
+ <key>KeepAlive</key>
+ <false/>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/bin/sh</string>
+ <string>/usr/local/bin/chronylogrotate.sh</string>
+ </array>
+ <key>StartCalendarInterval</key>
+ <dict>
+ <key>Minute</key>
+ <integer>5</integer>
+ <key>Hour</key>
+ <integer>4</integer>
+ </dict>
+</dict>
+</plist>
diff --git a/contrib/bryan_christianson_1/org.chrony-project.chronyd.plist b/contrib/bryan_christianson_1/org.chrony-project.chronyd.plist
new file mode 100644
index 0000000..e23fc06
--- /dev/null
+++ b/contrib/bryan_christianson_1/org.chrony-project.chronyd.plist
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>org.chrony-project.chronyd</string>
+ <key>Program</key>
+ <string>/usr/local/sbin/chronyd</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>chronyd</string>
+ <string>-n</string>
+ <string>-f</string>
+ <string>/private/etc/chrony.d/chrony.conf</string>
+ </array>
+ <key>KeepAlive</key>
+ <true/>
+</dict>
+</plist>
diff --git a/contrib/erik_bryer_1 b/contrib/erik_bryer_1
new file mode 100644
index 0000000..c551dfe
--- /dev/null
+++ b/contrib/erik_bryer_1
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# chrony Start time synchronization. This script
+# starts chronyd.
+#
+# Hacked by: Erik Bryer <ebryer@spots.ab.ca> using inet as a template
+#
+# chkconfig: 2345 02 82
+# description: chronyd helps keep the system time accurate by calculating \
+# and applying correction factors to compensate for the drift \
+# in the clock. chronyd can also correct the hardware clock \
+# (RTC) on some systems.
+# processname: chronyd
+# config: /etc/chrony.conf
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Set path to include chronyd in /usr/local/sbin
+PATH="$PATH:/usr/local/sbin"
+
+[ -f /usr/local/sbin/chronyd ] || exit 0
+
+[ -f /etc/chrony.conf ] || exit 0
+
+RETVAL=0
+
+# See how we were called.
+case "$1" in
+ start)
+ # Start daemons.
+ echo -n "Starting chronyd: "
+ daemon chronyd
+ RETVAL=$?
+ [ $RETVAL -eq 0 ] && touch /var/lock/subsys/chrony
+ echo
+ ;;
+ stop)
+ # Stop daemons.
+ echo -n "Shutting down chronyd: "
+# If not dead killproc automatically sleeps for 4.1 seconds then does
+# kill -9. "chrony.txt" prefers a 5 second delay, but this should be ok.
+ killproc chronyd -15
+ RETVAL=$?
+ [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/chrony
+ echo
+ ;;
+ status)
+ status chronyd
+ exit $?
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ *)
+ echo "Usage: named {start|stop|status|restart}"
+ exit 1
+esac
+
+exit $RETVAL
+
diff --git a/contrib/ken_gillett_1 b/contrib/ken_gillett_1
new file mode 100644
index 0000000..48b7999
--- /dev/null
+++ b/contrib/ken_gillett_1
@@ -0,0 +1,100 @@
+#!/bin/sh
+#
+# chronyd This shell script takes care of starting and stopping
+# chronyd (NTP daemon).
+#
+# chkconfig: 45 80 20
+# description: chronyd is the NTP daemon.
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Check that networking is up.
+[ ${NETWORKING} = "no" ] && exit 0
+
+PREDIR="/usr/local"
+CHRONYD=$PREDIR"/sbin/chronyd"
+CHRONYC=$PREDIR"/bin/chronyc"
+
+[ -x $CHRONYD -a -x $CHRONYC -a -f /etc/chrony.conf ] || exit 0
+
+dochrony() {
+ if [ -z "$(pidofproc chronyd)" ]; then
+ echo -e "\n\tchronyd not running\n\n"
+ exit 2
+ fi
+ KEY=`awk '$1 == "commandkey" {print $2; exit}' /etc/chrony.conf`
+ PASSWORD=`awk '$1 == '$KEY' {print $2; exit}' /etc/chrony/keys`
+
+ $CHRONYC <<- EOF
+ password $PASSWORD
+ $@
+ quit
+ EOF
+}
+
+# make the first parameter' lower case
+set - `echo $1 | awk '{print tolower($1)}';shift;echo "$@"`
+
+# Expand any shortcuts.
+case "$1" in
+ on|1)
+ set - "online"
+ ;;
+ off|0)
+ set - "offline"
+esac
+
+# See how we were called.
+case "$1" in
+ start)
+ # Start daemons.
+ echo -n "Starting chronyd: "
+ daemon $CHRONYD
+ if [ $? -eq 0 ]; then
+ echo $(pidofproc chronyd) > /var/run/chronyd.pid
+ touch /var/lock/subsys/chronyd
+ fi
+ echo
+ ;;
+ stop)
+ # Stop daemons.
+ echo -n "Shutting down chronyd: "
+ killproc chronyd
+ echo
+ rm -f /var/lock/subsys/chronyd
+ ;;
+ status)
+ status chronyd
+ ;;
+ restart|reload)
+ $0 stop
+ $0 start
+ ;;
+ condrestart)
+ if [ -f /var/lock/subsys/chronyd ]; then
+ $0 stop
+ $0 start
+ fi
+ ;;
+ "")
+ echo "Usage: chronyd
+{start|stop|restart|reload|condrestart|status|[on|off]line etc}"
+ exit 1
+ ;;
+
+accheck|cmdaccheck|clients|manual|rtcdata|sources|sourcestats|tracking|clients)
+ dochrony "$@"
+ ;;
+ *)
+ echo -n "Chrony $1: "
+ dochrony "$@" > /dev/null
+ [ $? -eq 0 ] && echo_success || echo_failure
+ echo
+esac
+
+exit 0
+
diff --git a/contrib/stephan_boettcher_1 b/contrib/stephan_boettcher_1
new file mode 100644
index 0000000..e5eda11
--- /dev/null
+++ b/contrib/stephan_boettcher_1
@@ -0,0 +1,162 @@
+From stephan@nevis1.nevis.columbia.edu Mon Jun 7 20:51:57 1999
+Date: 04 Jun 1999 00:17:25 -0400
+From: Stephan I. Boettcher <stephan@nevis1.nevis.columbia.edu>
+To: richard@rrbcurnow.freeserve.co.uk
+Subject: chrony 1.1 sysV startup script for notebooks
+
+
+Dear Richard,
+
+I installed chrony on my notebook, running RedHat 5.1 Linux.
+It looks like it works. No problems.
+
+Thank you!
+
+I like to donate my sysV startup script, appended below.
+
+Special feature: the `online' command scans the config file to
+selectively turn some servers online, depending on the pcmcia SCHEME.
+
+booting: /etc/rc.d/init.d/chrony start
+/etc/ppp/ip-up: /etc/rc.d/init.d/chrony online
+/etc/ppp/ip-down: /etc/rc.d/init.d/chrony offline
+logrotate cron: /etc/rc.d/init.d/chrony cyclelogs
+a user: /etc/rc.d/init.d/chrony status
+a sysadmin: /etc/rc.d/init.d/chrony restart
+shutdown: /etc/rc.d/init.d/chrony stop
+
+Best regards
+Stephan
+
+--
+
+------------------------------------------------------------------------
+Stephan Boettcher FAX: +1-914-591-4540
+Columbia University, Nevis Labs Tel: +1-914-591-2863
+P.O. Box 137, 136 South Broadway mailto:stephan@nevis1.columbia.edu
+Irvington, NY 10533, USA http://www.nevis.columbia.edu/~stephan
+------------------------------------------------------------------------
+
+########################### cut here ###################################
+#! /bin/bash
+#
+# /etc/rc.d/init.d/chrony
+#
+# SYS V startup script for
+# chrony ntp daemon
+# on Linux 2.0.3x notebooks with pcmcia scheme support
+# $Id: stephan_boettcher_1,v 1.1 2000/04/24 21:36:04 richard Exp $
+#
+# 1999-06-02 SiB <stephan@nevis1.columbia.edu>
+#
+# For PCMCIA users:
+# In /etc/chrony.conf, precede the server commands for each SCHEME
+# with a comment line that contains the word SCHEME and the name of
+# the scheme(s) that should use the servers, up to the next line that
+# contains the word SCHEME. The servers must be `offline' and
+# specified by their IP address. The hostname will not do.
+#
+# Like:
+#
+# # SCHEME nevisppp nevislan
+# # stephanpc.nevis.columbia.edu
+# server 192.12.82.222 offline
+#
+# # SCHEME desyppp desylan
+#
+# # dsygw2.desy.de
+# server 131.169.30.15 offline
+# # dscomsa.desy.de
+# server 131.169.197.35 offline
+
+CONF=/etc/chrony.conf
+CHRONYD=/usr/local/sbin/chronyd
+CHRONYC=/usr/local/bin/chronyc
+KEYS=/etc/chrony.keys
+
+# See if we got all we need:
+
+[ -f $CHRONYD -a -f $CHRONYC -a -r $CONF ] || exit
+
+
+[ -r $KEYS ] \
+&& CMDKEY=`awk '/^commandkey/{print $2}' $CONF` \
+&& PASSWORD=`awk -v KEY=$CMDKEY '$1==KEY{print $2}' $KEYS`
+
+
+case "$1" in
+
+ start)
+ echo -n "Starting chronyd "
+ $CHRONYD -r -s -f $CONF
+ echo
+ ;;
+
+ stop)
+ echo -n "Shutting down chronyd "
+ /usr/bin/killall chronyd
+ echo
+ ;;
+
+ restart)
+ $0 stop
+ $0 start
+ ;;
+
+ on*)
+
+ [ -f /var/run/pcmcia-scheme ] && SCHEME=`cat /var/run/pcmcia-scheme`
+
+ awk -v SCHEME=${SCHEME:-default} -v PASSWORD=$PASSWORD \
+ '
+ BEGIN {
+ SEL=1;
+ print "password", PASSWORD;
+ }
+ /SCHEME/ {
+ SEL=match($0, SCHEME);
+ }
+ SEL && /^server[ \t]*[0-9.]+[ \t].*offline/ {
+ print "online 255.255.255.255/" $2;
+ }
+ ' \
+ $CONF \
+ | $CHRONYC
+
+ ;;
+
+ off*)
+ cat <<-EOF | $CHRONYC
+ password $PASSWORD
+ offline
+ trimrtc
+ dump
+ EOF
+ ;;
+
+ *log*)
+ cat <<-EOF | $CHRONYC
+ password $PASSWORD
+ cyclelogs
+ EOF
+ ;;
+
+ stat*)
+ cat <<-EOF | $CHRONYC
+ sources
+ sourcestats
+ tracking
+ rtcdata
+ EOF
+ ;;
+
+ *)
+ echo "Usage: chronyd {start|stop|restart|status|online|offline|cyclelogs}"
+ exit 1
+ ;;
+
+esac
+
+exit 0
+
+
diff --git a/contrib/wolfgang_weisselberg1 b/contrib/wolfgang_weisselberg1
new file mode 100644
index 0000000..2c41752
--- /dev/null
+++ b/contrib/wolfgang_weisselberg1
@@ -0,0 +1,118 @@
+
+> Is it possible to limit chronyc to only those commands that
+> are readonly plus those necessary to bring a dialup connection up
+> and down? That is: online offline dump writertc and password.
+
+This is trivial on the same host and workable for non-local
+hosts: use a wrapper program or script. An *untested*
+sample follows. To use it, best create a special user (say
+chronyc) and a special group (say chronyg). Make the script
+chronyc:chronyg, and 4750 (suid, rwxr-x---). Add all users
+who may run the script to the group chronyg.
+
+Make a chrony password file e.g.
+/usr/local/etc/chrony_password. It should be owned by chronyc
+and readable only for the owner, containing only the chrony
+password (and maybe a newline) in the first line.
+
+In this way only the script (call it run_chrony, for example)
+can read the password. It will allow only those commands you
+explicitely allow. You can add a password check -- especially
+if you add an internet port so you can access it over the
+internet this is advisable. You really want to add logging
+to this untested script as well.
+
+
+BTW, if you use some sort of PPP, you probably can use
+/etc/ppp/ip-up and /etc/ppp/ip-down to transparently set chrony
+on- and offline as the ip connection goes up and comes down.
+This is _far_ more user friendly, IMHO, and a DOS by switching
+chrony offline all the time is avoided as well.
+
+
+#! /usr/bin/perl -T
+use v5.6.1;
+use warnings;
+use strict;
+
+sub laundered_command();
+sub order_chrony($$);
+sub read_password();
+sub usage($);
+
+our $CHRONY = "/usr/local/bin/chronyc";
+
+# NOTE: select the file system protection wisely for the
+# PASSWORDFILE!
+our $PASSWORDFILE = "/usr/local/etc/chrony_password";
+
+our @ALLOWED_COMMANDS = (
+ 'online', # switch online mode on
+ 'offline', # switch online mode off
+ 'dump', # save measurements to file
+ 'writerc', # save RTC accumulated data
+
+ 'clients', # which clients are served by us?
+ 'rtcdata', # Quality of RTC measurements
+ 'sources(?: -v)?', # Show our sources (verbose)
+ 'sourcestats(?: -v)?', # How good are our sources (verbose)?
+ 'tracking', # whom do we adjust to?
+
+ # 'burst \d+/\d+', # allow them to send bursts?
+);
+
+usage("No command given.") unless $ARGV[0];
+
+%ENV = (); # nuke all environment variables. Rather
+ # drastic, but better safe than sorry!
+ # Add whatever you really need to get it
+ # working (again).
+$ENV{'PATH'} = '/usr/local/bin:/bin:/usr/bin';
+
+order_chrony(laundered_command(), read_password());
+
+exit 0; # command succeeded
+
+############################################################
+
+sub usage($) {
+ print STDERR "Error: ", shift, "\n";
+
+ # OK, this eats the -v...
+ print STDERR "Legal commands are:\n\t", join "\n",
+ map { $_ =~ m:(\w+):; $1 } @ALLOWED_COMMANDS;
+ exit 1; # error
+}
+
+############################################################
+
+sub laundered_command() {
+ my $regexp = "^(" . join ( "|", @ALLOWED_COMMANDS ) . ")\$";
+ my $parameters = join " ", @ARGV;
+ $parameters =~ m:$regexp: or usage("Command $parameters not allowed.");
+
+ return $1; # this value, then, is untainted.
+};
+
+############################################################
+
+sub read_password() {
+ open PASS, $PASSWORDFILE
+ or die "Could not read protected password file: $!";
+ my $password = <PASS>;
+ chomp $password;
+ return $password;
+};
+
+############################################################
+
+sub order_chrony($$) {
+ my ($clean_command, $password) = @_;
+ open CHRONY, "| $CHRONY &> /dev/null" or die "could not run $CHRONY: $!\n";
+ print CHRONY "password $password\n";
+ print CHRONY "$clean_command\n";
+ close CHRONY
+ or die "Error running command $clean_command\n", "\ton $CHRONY: $!\n";
+}
+
+############################################################
diff --git a/doc/Makefile.in b/doc/Makefile.in
new file mode 100644
index 0000000..1777da5
--- /dev/null
+++ b/doc/Makefile.in
@@ -0,0 +1,76 @@
+ADOC = asciidoctor
+ADOC_FLAGS =
+SED = sed
+HTML_TO_TXT = w3m -dump -T text/html
+
+MAN_FILES = chrony.conf.man chronyc.man chronyd.man
+TXT_FILES = faq.txt installation.txt
+HTML_FILES = $(MAN_FILES:%.man=%.html) $(TXT_FILES:%.txt=%.html)
+MAN_IN_FILES = $(MAN_FILES:%.man=%.man.in)
+
+SYSCONFDIR = @SYSCONFDIR@
+BINDIR = @BINDIR@
+SBINDIR = @SBINDIR@
+MANDIR = @MANDIR@
+DOCDIR = @DOCDIR@
+CHRONYRUNDIR = @CHRONYRUNDIR@
+CHRONYVARDIR = @CHRONYVARDIR@
+CHRONY_VERSION = @CHRONY_VERSION@
+DEFAULT_USER = @DEFAULT_USER@
+DEFAULT_HWCLOCK_FILE = @DEFAULT_HWCLOCK_FILE@
+DEFAULT_PID_FILE = @DEFAULT_PID_FILE@
+DEFAULT_RTC_DEVICE = @DEFAULT_RTC_DEVICE@
+
+SED_COMMANDS = "s%\@SYSCONFDIR\@%$(SYSCONFDIR)%g;\
+ s%\@BINDIR\@%$(BINDIR)%g;\
+ s%\@SBINDIR\@%$(SBINDIR)%g;\
+ s%\@CHRONY_VERSION\@%$(CHRONY_VERSION)%g;\
+ s%\@DEFAULT_HWCLOCK_FILE\@%$(DEFAULT_HWCLOCK_FILE)%g;\
+ s%\@DEFAULT_PID_FILE\@%$(DEFAULT_PID_FILE)%g;\
+ s%\@DEFAULT_RTC_DEVICE\@%$(DEFAULT_RTC_DEVICE)%g;\
+ s%\@DEFAULT_USER\@%$(DEFAULT_USER)%g;\
+ s%\@CHRONYRUNDIR\@%$(CHRONYRUNDIR)%g;\
+ s%\@CHRONYVARDIR\@%$(CHRONYVARDIR)%g;"
+
+man: $(MAN_FILES) $(MAN_IN_FILES)
+html: $(HTML_FILES)
+txt: $(TXT_FILES)
+docs: man html
+
+%.html: %.adoc
+ $(ADOC) $(ADOC_FLAGS) -b html -o - $< | $(SED) -e $(SED_COMMANDS) > $@
+
+%.man.in: %.adoc
+ $(ADOC) $(ADOC_FLAGS) -b manpage -o $@ $<
+
+%.man: %.man.in
+ $(SED) -e $(SED_COMMANDS) < $< > $@
+
+%.txt: %.html
+ $(HTML_TO_TXT) < $< > $@
+
+install: $(MAN_FILES)
+ [ -d $(DESTDIR)$(MANDIR)/man1 ] || mkdir -p $(DESTDIR)$(MANDIR)/man1
+ [ -d $(DESTDIR)$(MANDIR)/man5 ] || mkdir -p $(DESTDIR)$(MANDIR)/man5
+ [ -d $(DESTDIR)$(MANDIR)/man8 ] || mkdir -p $(DESTDIR)$(MANDIR)/man8
+ cp chronyc.man $(DESTDIR)$(MANDIR)/man1/chronyc.1
+ chmod 644 $(DESTDIR)$(MANDIR)/man1/chronyc.1
+ cp chronyd.man $(DESTDIR)$(MANDIR)/man8/chronyd.8
+ chmod 644 $(DESTDIR)$(MANDIR)/man8/chronyd.8
+ cp chrony.conf.man $(DESTDIR)$(MANDIR)/man5/chrony.conf.5
+ chmod 644 $(DESTDIR)$(MANDIR)/man5/chrony.conf.5
+
+install-docs: $(HTML_FILES)
+ [ -d $(DESTDIR)$(DOCDIR) ] || mkdir -p $(DESTDIR)$(DOCDIR)
+ for f in $(HTML_FILES); do \
+ cp $$f $(DESTDIR)$(DOCDIR); \
+ chmod 644 $(DESTDIR)$(DOCDIR)/$$f; \
+ done
+
+clean:
+ rm -f $(MAN_FILES) $(TXT_FILES) $(HTML_FILES)
+ rm -f $(MAN_IN_FILES)
+
+distclean:
+ rm -f $(MAN_FILES) $(TXT_FILES) $(HTML_FILES)
+ rm -f Makefile
diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc
new file mode 100644
index 0000000..cb3f95c
--- /dev/null
+++ b/doc/chrony.conf.adoc
@@ -0,0 +1,3160 @@
+// This file is part of chrony
+//
+// Copyright (C) Richard P. Curnow 1997-2003
+// Copyright (C) Stephen Wadeley 2016
+// Copyright (C) Bryan Christianson 2017
+// Copyright (C) Miroslav Lichvar 2009-2023
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of version 2 of the GNU General Public License as
+// 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.
+
+= chrony.conf(5)
+:doctype: manpage
+:man manual: Configuration Files
+:man source: chrony @CHRONY_VERSION@
+
+== NAME
+chrony.conf - chronyd configuration file
+
+== SYNOPSIS
+*chrony.conf*
+
+== DESCRIPTION
+
+This file configures the *chronyd* daemon. The compiled-in location is
+_@SYSCONFDIR@/chrony.conf_. Other locations can be specified on the
+*chronyd* command line with the *-f* option.
+
+Each directive in the configuration file is placed on a separate line. The
+following sections describe each of the directives in turn. The directives are
+not case-sensitive. Generally, the directives can occur in any order in the file
+and if a directive is specified multiple times, only the last one will be
+effective. Exceptions are noted in the descriptions.
+
+The configuration directives can also be specified directly on the *chronyd*
+command line. In this case each argument is parsed as a new line and the
+configuration file is ignored.
+
+While the number of supported directives is large, only a few of them are
+typically needed. See the <<examples,*EXAMPLES*>> section for configuration in
+typical operating scenarios.
+
+The configuration file might contain comment lines. A comment line is any line
+that starts with zero or more spaces followed by any one of the following
+characters: *!*, *;*, *#*, *%*. Any line with this format will be ignored.
+
+== DIRECTIVES
+
+=== Time sources
+
+[[server]]*server* _hostname_ [_option_]...::
+The *server* directive specifies an NTP server which can be used as a time
+source. The client-server relationship is strictly hierarchical: a client might
+synchronise its system time to that of the server, but the server's system time
+will never be influenced by that of a client.
++
+The server can be specified by its hostname or IP address. If the hostname cannot
+be resolved on start, *chronyd* will try it again in increasing intervals, and
+also when the <<chronyc.adoc#online,*online*>> command is issued in *chronyc*.
++
+The DNS record can change over time. The used address will be replaced with a
+newly resolved address when the server becomes unreachable (i.e. no valid
+response to last 8 requests), unsynchronised, a falseticker (i.e. does not
+agree with a majority of other sources), or the root distance is too large (the
+limit can be configured by the <<maxdistance,*maxdistance*>> directive). The
+automatic replacement happens at most once per 30 minutes.
++
+This directive can be used multiple times to specify multiple servers.
++
+The directive supports the following options:
++
+*minpoll* _poll_:::
+This option specifies the minimum interval between requests sent to the server
+as a power of 2 in seconds. For example, *minpoll 5* would mean that the
+polling interval should not drop below 32 seconds. The default is 6 (64
+seconds), the minimum is -7 (1/128th of a second), and the maximum is 24 (6
+months). Note that intervals shorter than 6 (64 seconds) should generally not
+be used with public servers on the Internet, because it might be considered
+abuse. A sub-second interval will be enabled only when the server is reachable
+and the round-trip delay is shorter than 10 milliseconds, i.e. the server
+should be in a local network.
+*maxpoll* _poll_:::
+This option specifies the maximum interval between requests sent to the server
+as a power of 2 in seconds. For example, *maxpoll 9* indicates that the polling
+interval should stay at or below 9 (512 seconds). The default is 10 (1024
+seconds), the minimum is -7 (1/128th of a second), and the maximum is 24 (6
+months).
+*iburst*:::
+With this option, *chronyd* will start with a burst of 4-8 requests in order to
+make the first update of the clock sooner. It will also repeat the burst every
+time the source is switched from the offline state to online with the
+<<chronyc.adoc#online,*online*>> command in *chronyc*.
+*burst*:::
+With this option, *chronyd* will send a burst of up to 4 requests when it
+cannot get a good measurement from the
+server. The number of requests in the burst is limited by the current polling
+interval to keep the average interval at or above the minimum interval, i.e.
+the current interval needs to be at least two times longer than the minimum
+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 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)
+should *chronyd* use to authenticate requests sent to the server and verify its
+responses. The server must have the same key for this number configured,
+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.
+*certset* _ID_:::
+This option specifies which set of trusted certificates should be used to verify
+the server's certificate when the *nts* option is enabled. Sets of certificates
+can be specified with the <<ntstrustedcerts,*ntstrustedcerts*>> directive. The
+default set is 0, which by default contains certificates of the system's
+default trusted certificate authorities.
+*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
+indicate that the request, or the response, or both were delayed. If only one
+of the messages was delayed the measurement error is likely to be substantial.
++
+For small variations in the round-trip delay, *chronyd* uses a weighting scheme
+when processing the measurements. However, beyond a certain level of delay the
+measurements are likely to be so corrupted as to be useless. (This is
+particularly so on wireless networks and other slow links, where a long delay
+probably indicates a highly asymmetric delay caused by the response waiting
+behind a lot of packets related to a download of some sort).
++
+If the user knows that round trip delays above a certain level should cause the
+measurement to be ignored, this level can be defined with the *maxdelay*
+option. For example, *maxdelay 0.3* would indicate that measurements with a
+round-trip delay greater than 0.3 seconds should be ignored. The default value
+is 3 seconds and the maximum value is 1000 seconds.
+*maxdelayratio* _ratio_:::
+This option is similar to the *maxdelay* option above. *chronyd* keeps a record
+of the minimum round-trip delay amongst the previous measurements that it has
+buffered. If a measurement has a round-trip delay that is greater than the
+specified ratio times the minimum delay, it will be rejected. By default, this
+test is disabled.
+*maxdelaydevratio* _ratio_:::
+If a measurement has a ratio of the increase in the round-trip delay from the
+minimum delay amongst the previous measurements to the standard deviation of
+the previous measurements that is greater than the specified ratio, it will be
+rejected. The default is 10.0.
+*maxdelayquant* _p_:::
+This option disables the *maxdelaydevratio* test and specifies the maximum
+acceptable delay as a quantile of the round-trip delay instead of a function of
+the minimum delay amongst the buffered measurements. If a measurement has a
+round-trip delay that is greater than a long-term estimate of the _p_-quantile,
+it will be rejected.
++
+The specified _p_ value should be between 0.05 and 0.95. For example,
+*maxdelayquant 0.2* would indicate that only measurements with the lowest 20
+percent of round-trip delays should be accepted. Note that it can take many
+measurements for the estimated quantile to reach the expected value. This
+option is intended for synchronisation in mostly static local networks with
+very short polling intervals and possibly combined with the *filter* option.
+By default, this test is disabled in favour of the *maxdelaydevratio* test.
+*mindelay* _delay_:::
+This option specifies a fixed minimum round-trip delay to be used instead of
+the minimum amongst the previous measurements. This can be useful in networks
+with static configuration to improve the stability of corrections for
+asymmetric jitter, weighting of the measurements, and the *maxdelayratio* and
+*maxdelaydevratio* tests. The value should be set accurately in order to have a
+positive effect on the synchronisation.
+*asymmetry* _ratio_:::
+This option specifies the asymmetry of the network jitter on the path to the
+source, which is used to correct the measured offset according to the delay.
+The asymmetry can be between -0.5 and +0.5. A negative value means the delay of
+packets sent to the source is more variable than the delay of packets sent from
+the source back. By default, *chronyd* estimates the asymmetry automatically.
+*offset* _offset_:::
+This option specifies a correction (in seconds) which will be applied to
+offsets measured with this source. It's particularly useful to compensate for a
+known asymmetry in network delay or timestamping errors. For example, if
+packets sent to the source were on average delayed by 100 microseconds more
+than packets sent from the source back, the correction would be -0.00005 (-50
+microseconds). The default is 0.0.
+*minsamples* _samples_:::
+Set the minimum number of samples kept for this source. This overrides the
+<<minsamples,*minsamples*>> directive.
+*maxsamples* _samples_:::
+Set the maximum number of samples kept for this source. This overrides the
+<<maxsamples,*maxsamples*>> directive.
+*filter* _polls_:::
+This option enables a median filter to reduce noise in NTP measurements. The
+filter will process samples collected in the specified number of polls
+into a single sample. It is intended to be used with very short polling
+intervals in local networks where it is acceptable to generate a lot of NTP
+traffic.
+*offline*:::
+If the server will not be reachable when *chronyd* is started, the *offline*
+option can be specified. *chronyd* will not try to poll the server until it is
+enabled to do so (by using the <<chronyc.adoc#online,*online*>> command in
+*chronyc*).
+*auto_offline*:::
+With this option, the server will be assumed to have gone offline when sending
+a request fails, e.g. due to a missing route to the network. This option avoids
+the need to run the <<chronyc.adoc#offline,*offline*>> command from *chronyc*
+when disconnecting the network link. (It will still be necessary to use the
+<<chronyc.adoc#online,*online*>> command when the link has been established, to
+enable measurements to start.)
+*prefer*:::
+Prefer this source over sources without the *prefer* option.
+*noselect*:::
+Never select this source. This is particularly useful for monitoring.
+*trust*:::
+Assume time from this source is always true. It can be rejected as a
+falseticker in the source selection only if another source with this option
+does not agree with it.
+*require*:::
+Require that at least one of the sources specified with this option is
+selectable (i.e. recently reachable and not a falseticker) before updating the
+clock. Together with the *trust* option this might be useful to allow a trusted
+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 the interleaved mode of NTP. It enables the server to
+respond with more accurate transmit timestamps (e.g. kernel or hardware
+timestamps), which cannot be contained in the transmitted packet itself and
+need to refer to a previous packet instead. This can significantly improve the
+accuracy and stability of the measurements.
++
+The interleaved mode is compatible with servers that support only the basic
+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.
+<<clientloglimit,*clientloglimit*>> is too small), or it might be overwritten
+by other clients that have the same IP address (e.g. computers behind NAT or
+someone sending requests with a spoofed source address).
++
+The *xleave* option can be combined with the *presend* option in order to
+shorten the interval in which the server has to keep the state to be able to
+respond in the interleaved mode.
+*polltarget* _target_:::
+Target number of measurements to use for the regression algorithm which
+*chronyd* will try to maintain by adjusting the polling interval between
+*minpoll* and *maxpoll*. A higher target makes *chronyd* prefer shorter polling
+intervals. The default is 8 and a useful range is from 6 to 60.
+*port* _port_:::
+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 4460.
+*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
+skewed due to either the client or the server having to do an ARP lookup on the
+other party prior to transmitting a packet. This is more of a problem with long
+sampling intervals, which might be similar in duration to the lifetime of entries
+in the ARP caches of the machines.
++
+In order to avoid this problem, the *presend* option can be used. It takes a
+single integer argument, which is the smallest polling interval for which an
+extra pair of NTP packets will be exchanged between the client and the server
+prior to the actual measurement. For example, with the following option
+included in a *server* directive:
++
+----
+presend 9
+----
++
+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.
++
+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
+increase stratum of the source to the specified minimum, so *chronyd* will
+avoid selecting that source. This is useful with low-stratum sources that are
+known to be unreliable or inaccurate and which should be used only when other
+sources are unreachable.
+*version* _version_:::
+This option sets the NTP version of packets sent to the server. This can be
+useful when the server runs an old NTP implementation that does not respond to
+requests using a newer version. The default version depends on other options.
+If the *extfield* or *xleave* option is used, the default version is 4. If
+those options are not used and the *key* option specifies a key using a hash
+function with output size longer than 160 bits (e.g. SHA256), the default
+version is 3 for compatibility with older *chronyd* servers. In other cases,
+the default version is 4.
+*copy*:::
+This option specifies that the server and client are closely related, their
+configuration does not allow a synchronisation loop to form between them, and
+the client is allowed to assume the reference ID and stratum of the server.
+This is useful when multiple instances of `chronyd` are running on one computer
+(e.g. for security or performance reasons), one primarily operating as a client
+to synchronise the system clock and other instances started with the *-x*
+option to operate as NTP servers for other computers with their NTP clocks
+synchronised to the first instance.
+*extfield* _type_:::
+This option enables an NTPv4 extension field specified by its type as a
+hexadecimal number. It will be included in requests sent to the server and
+processed in received responses if the server supports it. Note that some
+server implementations do not respond to requests containing an unknown
+extension field (*chronyd* as a server responded to such requests since
+version 2.0).
++
+This option can be used multiple times to enable multiple extension fields.
++
+The following extension fields are supported:
++
+_F323_::::
+An experimental extension field to enable several improvements that were
+proposed for the next version of the NTP protocol (NTPv5). The field contains
+root delay and dispersion in higher resolution and a monotonic receive
+timestamp, which enables a frequency transfer between the server and client to
+significantly improve stability of the synchronisation. This field should be
+enabled only for servers known to be running *chronyd* version 4.2 or later.
+_F324_::::
+An experimental extension field to enable the use of the Precision Time
+Protocol (PTP) correction field in NTP-over-PTP messages updated by one-step
+end-to-end transparent clocks in network switches and routers to significantly
+improve accuracy and stability of the synchronisation. NTP-over-PTP can be
+enabled by the <<ptpport,*ptpport*>> directive and setting the *port* option to
+the PTP port. The corrections are applied only to NTP measurements with HW
+timestamps (enabled by the <<hwtimestamp,*hwtimestamp*>> directive). This
+field should be enabled only for servers known to be running *chronyd* version
+4.5 or later.
+{blank}:::
+
+[[pool]]*pool* _name_ [_option_]...::
+The syntax of this directive is similar to that for the <<server,*server*>>
+directive, except that it is used to specify a pool of NTP servers rather than
+a single NTP server. The pool name is expected to resolve to multiple addresses
+which might change over time.
++
+This directive can be used multiple times to specify multiple pools.
++
+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* _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.
++
+An example of the *pool* directive is
++
+----
+pool pool.ntp.org iburst maxsources 3
+----
+
+[[peer]]*peer* _hostname_ [_option_]...::
+The syntax of this directive is identical to that for the <<server,*server*>>
+directive, except that it specifies a symmetric association with an NTP peer
+instead of a client/server association with an NTP server. A single symmetric
+association allows the peers to be both servers and clients to each other. This
+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.
++
+This directive can be used multiple times to specify multiple peers.
++
+The following options of the *server* directive do not work in the *peer*
+directive: *iburst*, *burst*, *nts*, *presend*, *copy*.
++
+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.
++
+Note that the symmetric mode is less secure than the client/server mode. A
+denial-of-service attack is possible on unauthenticated symmetric associations,
+i.e. when the peer was specified without the *key* option. An attacker who does
+not see network traffic between two hosts, but knows that they are peering with
+each other, can periodically send them unauthenticated packets with spoofed
+source addresses in order to disrupt their NTP state and prevent them from
+synchronising to each other. When the association is authenticated, an attacker
+who does see the network traffic, but cannot prevent the packets from reaching
+the other host, can still disrupt the state by replaying old packets. The
+attacker has effectively the same power as a man-in-the-middle attacker. A
+partial protection against this attack is implemented in *chronyd*, which can
+protect the peers if they are using the same polling interval and they never
+sent an authenticated packet with a timestamp from future, but it should not be
+relied on as it is difficult to ensure the conditions are met. If two hosts
+should be able to synchronise to each other in both directions, it is
+recommended to use two separate client/server associations (specified by the
+<<server,*server*>> directive on both hosts) instead.
+
+[[initstepslew]]*initstepslew* _step-threshold_ [_hostname_]...::
+(This directive is deprecated in favour of the <<makestep,*makestep*>>
+directive.)
++
+The purpose of the *initstepslew* directive is to allow *chronyd* to make a
+rapid measurement of the system clock error at boot time, and to correct the
+system clock by stepping before normal operation begins. Since this would
+normally be performed only at an appropriate point in the system boot sequence,
+no other software should be adversely affected by the step.
++
+If the correction required is less than a specified threshold, a slew is used
+instead. This makes it safer to restart *chronyd* whilst the system is in
+normal operation.
++
+The *initstepslew* directive takes a threshold and a list of NTP servers as
+arguments. Each of the servers is rapidly polled several times, and a majority
+voting mechanism used to find the most likely range of system clock error that
+is present. A step or slew is applied to the system clock to correct this
+error. *chronyd* then enters its normal operating mode.
++
+An example of the use of the directive is:
++
+----
+initstepslew 30 ntp1.example.net ntp2.example.net ntp3.example.net
+----
++
+where 3 NTP servers are used to make the measurement. The _30_ indicates that
+if the system's error is found to be 30 seconds or less, a slew will be used to
+correct it; if the error is above 30 seconds, a step will be used.
++
+The *initstepslew* directive can also be used in an isolated LAN environment,
+where the clocks are set manually. The most stable computer is chosen as the
+primary server and the other computers are its clients. If each of the clients
+is configured with the <<local,*local*>> directive, the server can be set up
+with an *initstepslew* directive which references some or all of the clients.
+Then, if the server machine has to be rebooted, the clients can be relied on to
+act analogously to a flywheel and preserve the time for a short period while
+the server completes its reboot.
++
+The *initstepslew* directive is functionally similar to a combination of the
+<<makestep,*makestep*>> and <<server,*server*>> directives with the *iburst*
+option. The main difference is that the *initstepslew* servers are used only
+before normal operation begins and that the foreground *chronyd* process waits
+for *initstepslew* to finish before exiting. This prevent programs started in
+the boot sequence after *chronyd* from reading the clock before it has been
+stepped. With the *makestep* directive, the
+<<chronyc.adoc#waitsync,*waitsync*>> command of *chronyc* can be used instead.
+
+[[refclock]]*refclock* _driver_ _parameter_[:__option__]... [_option_]...::
+The *refclock* directive specifies a hardware reference clock to be used as a
+time source. It has two mandatory parameters, a driver name and a
+driver-specific parameter. The two parameters are followed by zero or more
+refclock options. Some drivers have special options, which can be appended to
+the driver-specific parameter using the *:* character.
++
+This directive can be used multiple times to specify multiple reference clocks.
++
+There are four drivers included in *chronyd*:
++
+*PPS*:::
+Driver for the kernel PPS (pulse per second) API. The parameter is the path to
+the PPS device (typically _/dev/pps?_). As PPS refclocks do not supply full
+time, another time source (e.g. NTP server or non-PPS refclock) is needed to
+complete samples from the PPS refclock. An alternative is to enable the
+<<local,*local*>> directive to allow synchronisation with some unknown but
+constant offset. The driver supports the following option:
++
+*clear*::::
+By default, the PPS refclock uses assert events (rising edge) for
+synchronisation. With this option, it will use clear events (falling edge)
+instead.
++
+{blank}:::
+Examples:
++
+----
+refclock PPS /dev/pps0 lock NMEA refid GPS1
+refclock SOCK /var/run/chrony.clk.ttyS0.sock offset 0.5 delay 0.2 refid NMEA noselect
+refclock PPS /dev/pps1:clear refid GPS2
+----
++
+*SOCK*:::
+Unix domain socket driver. This driver uses a datagram socket to receive
+samples from another application running on the system. The parameter is the
+path to the socket, which *chronyd* will create on start. The format of the
+messages is described in the _refclock_sock.c_ file in the chrony source code.
++
+An application which supports the SOCK protocol is the *gpsd* daemon. It can
+provide accurate measurements using the receiver's PPS signal, and since
+version 3.25 also (much less accurate) measurements based on the timing of
+serial data (e.g. NMEA), which can be useful when the receiver does not provide
+a PPS signal, or it cannot be connected to the computer. The paths where *gpsd*
+expects the sockets to be created by *chronyd* are described in the *gpsd(8)*
+man page. Note that *gpsd* needs to be started after *chronyd* in order to
+connect to the socket.
++
+Examples:
++
+----
+refclock SOCK /var/run/chrony.ttyS0.sock refid GPS1 poll 2 filter 4
+refclock SOCK /var/run/chrony.clk.ttyUSB0.sock refid GPS2 offset 0.2 delay 0.1
+----
++
+*SHM*:::
+NTP shared memory driver. This driver implements the protocol of the *ntpd*
+driver type 28. It is functionally similar to the SOCK driver, but uses a
+shared memory segment instead of a socket. The parameter is the unit number,
+typically a small number like 0, 1, 2, or 3, from which is derived the key of
+the memory segment as 0x4e545030 + unit.
++
+The driver supports the following option:
++
+*perm*=_mode_::::
+This option specifies the permissions of the shared memory segment created by
+*chronyd*. They are specified as a numeric mode. The default value is 0600
+(read-write access for owner only).
+{blank}:::
++
+Unlike with the SOCK driver, there is no prescribed order of starting *chronyd*
+and the program providing measurements. Both are expected to create the memory
+segment if it does not exist. *chronyd* will attach to an existing segment even
+if it has a different owner than root or different permissions than the
+permissions specified by the *perm* option. The segment needs to be created
+before untrusted applications or users can execute code to prevent an attacker
+from feeding *chronyd* with false measurements. The owner and permissions of
+the segment can be verified with the *ipcs -m* command. For this reason, the
+SHM driver is deprecated in favor of SOCK.
++
+Examples:
++
+----
+refclock SHM 0 poll 3 refid GPS1
+refclock SHM 1:perm=0644 refid GPS2
+----
++
+*PHC*:::
+PTP hardware clock (PHC) driver. The parameter is the path to the device of
+the PTP clock which should be used as a time source. If the clock is kept in
+TAI instead of UTC (e.g. it is synchronised by a PTP daemon), the current
+UTC-TAI offset needs to be specified by the *offset* option. Alternatively, the
+*pps* refclock option can be enabled to treat the PHC as a PPS refclock, using
+only the sub-second offset for synchronisation. The driver supports the
+following options:
++
+*nocrossts*::::
+This option disables use of precise cross timestamping.
+*extpps*::::
+This option enables a PPS mode in which the PTP clock is timestamping pulses
+of an external PPS signal connected to the clock. The clock does not need to be
+synchronised, but another time source is needed to complete the PPS samples.
+Note that some PTP clocks cannot be configured to timestamp only assert or
+clear events, and it is necessary to use the *width* option to filter wrong
+PPS samples.
+*pin*=_index_::::
+This option specifies the index of the pin which should be enabled for the
+PPS timestamping. If the PHC does not have configurable pins (i.e. the channel
+function is fixed), the index needs to be set to -1 to disable the pin
+configuration. The default value is 0.
+*channel*=_index_::::
+This option specifies the index of the channel for the PPS mode. The default
+value is 0.
+*clear*::::
+This option enables timestamping of clear events (falling edge) instead of
+assert events (rising edge) in the PPS mode. This may not work with some
+clocks.
+{blank}:::
++
+Examples:
++
+----
+refclock PHC /dev/ptp0 poll 0 dpoll -2 offset -37
+refclock PHC /dev/ptp1:nocrossts poll 3 pps
+refclock PHC /dev/ptp2:extpps:pin=1 width 0.2 poll 2
+----
++
+{blank}::
+The *refclock* directive supports the following options:
++
+*poll* _poll_:::
+Timestamps produced by refclock drivers are not used immediately, but they are
+stored and processed by a median filter in the polling interval specified by
+this option. This is defined as a power of 2 and can be negative to specify a
+sub-second interval. The default is 4 (16 seconds). A shorter interval allows
+*chronyd* to react faster to changes in the frequency of the system clock, but
+it might have a negative effect on its accuracy if the samples have a lot of
+jitter.
+*dpoll* _dpoll_:::
+Some drivers do not listen for external events and try to produce samples in
+their own polling interval. This is defined as a power of 2 and can be negative
+to specify a sub-second interval. The default is 0 (1 second).
+*refid* _refid_:::
+This option is used to specify the reference ID of the refclock, as up to four
+ASCII characters. The default reference ID is composed from the first three
+characters of the driver name and the number of the refclock. Each refclock
+must have a unique reference ID.
+*lock* _refid_:::
+This option can be used to lock a PPS refclock to another refclock, which is
+specified by its reference ID. In this mode received PPS samples are paired
+directly with raw samples from the specified refclock.
+*rate* _rate_:::
+This option sets the rate of the pulses in the PPS signal (in Hz). This option
+controls how the pulses will be completed with real time. To actually receive
+more than one pulse per second, a negative *dpoll* has to be specified (-3 for
+a 5Hz signal). The default is 1.
+*maxlockage* _pulses_:::
+This option specifies in number of pulses how old can be samples from the
+refclock specified by the *lock* option to be paired with the pulses.
+Increasing this value is useful when the samples are produced at a lower rate
+than the pulses. The default is 2.
+*width* _width_:::
+This option specifies the width of the pulses (in seconds). It is used to
+filter PPS samples when the driver provides samples for both rising and falling
+edges. Note that it reduces the maximum allowed error of the time source which
+completes the PPS samples. If the duty cycle is configurable, 50% should be
+preferred in order to maximise the allowed error.
+*pps*:::
+This options forces *chronyd* to treat any refclock (e.g. SHM or PHC) as a PPS
+refclock. This can be useful when the refclock provides time with a variable
+offset of a whole number of seconds (e.g. it uses TAI instead of UTC). Another
+time source is needed to complete samples from the refclock.
+*offset* _offset_:::
+This option can be used to compensate for a constant error. The specified
+offset (in seconds) is applied to all samples produced by the reference clock.
+The default is 0.0.
+*delay* _delay_:::
+This option sets the NTP delay of the source (in seconds). Half of this value
+is included in the maximum assumed error which is used in the source selection
+algorithm. Increasing the delay is useful to avoid having no majority in the
+source selection or to make it prefer other sources. The default is 1e-9 (1
+nanosecond).
+*stratum* _stratum_:::
+This option sets the NTP stratum of the refclock. This can be useful when the
+refclock provides time with a stratum other than 0. The default is 0.
+*precision* _precision_:::
+This option sets the precision of the reference clock (in seconds). The default
+value is the estimated precision of the system clock.
+*maxdispersion* _dispersion_:::
+Maximum allowed dispersion for filtered samples (in seconds). Samples with
+larger estimated dispersion are ignored. By default, this limit is disabled.
+*filter* _samples_:::
+This option sets the length of the median filter which is used to reduce the
+noise in the measurements. With each poll about 40 percent of the stored
+samples are discarded and one final sample is calculated as an average of the
+remaining samples. If the length is 4 or more, at least 4 samples have to be
+collected between polls. For lengths below 4, the filter has to be full. The
+default is 64. With drivers that perform their own polling (PPS, PHC, SHM), the
+maximum value is adjusted to the number of driver polls per source poll, i.e.
+2^(_poll_ - _dpoll_).
+*prefer*:::
+Prefer this source over sources without the prefer option.
+*noselect*:::
+Never select this source. This is useful for monitoring or with sources which
+are not very accurate, but are locked with a PPS refclock.
+*trust*:::
+Assume time from this source is always true. It can be rejected as a
+falseticker in the source selection only if another source with this option
+does not agree with it.
+*require*:::
+Require that at least one of the sources specified with this option is
+selectable (i.e. recently reachable and not a falseticker) before updating the
+clock. Together with the *trust* option this can be useful to allow a trusted,
+but not very precise, reference clock to be safely combined with
+unauthenticated NTP 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.
+*tai*:::
+This option indicates that the reference clock keeps time in TAI instead of UTC
+and that *chronyd* should correct its offset by the current TAI-UTC offset. The
+<<leapsectz,*leapsectz*>> directive must be used with this option and the
+database must be kept up to date in order for this correction to work as
+expected. This option does not make sense with PPS refclocks.
+*local*:::
+This option specifies that the reference clock is an unsynchronised clock which
+is more stable than the system clock (e.g. TCXO, OCXO, or atomic clock) and
+it should be used as a local standard to stabilise the system clock. The
+refclock will bypass the source selection. There should be at most one refclock
+specified with this option and it should have the shortest polling interval
+among all configured sources.
+*minsamples* _samples_:::
+Set the minimum number of samples kept for this source. This overrides the
+<<minsamples,*minsamples*>> directive.
+*maxsamples* _samples_:::
+Set the maximum number of samples kept for this source. This overrides the
+<<maxsamples,*maxsamples*>> directive.
+
+[[manual]]*manual*::
+The *manual* directive enables support at run-time for the
+<<chronyc.adoc#settime,*settime*>> command in *chronyc*. If no *manual*
+directive is included, any attempt to use the *settime* command in *chronyc*
+will be met with an error message.
++
+Note that the *settime* command can be enabled at run-time using
+the <<chronyc.adoc#manual,*manual*>> command in *chronyc*. (The idea of the two
+commands is that the *manual* command controls the manual clock driver's
+behaviour, whereas the *settime* command allows samples of manually entered
+time to be provided.)
+
+[[acquisitionport]]*acquisitionport* _port_::
+By default, *chronyd* as an NTP client opens a new socket for each request with
+the source port chosen randomly by the operating system. The *acquisitionport*
+directive can be used to specify the source port and use only one socket (per
+IPv4 or IPv6 address family) for all configured servers. This can be useful for
+getting through some firewalls. It should not be used if not necessary as there
+is a small impact on security of the client. If set to 0, the source port of
+the permanent socket will be chosen randomly by the operating system.
++
+It can be set to the same port as is used by the NTP server (which can be
+configured with the <<port,*port*>> directive) to use only one socket for all
+NTP packets.
++
+An example of the *acquisitionport* directive is:
++
+----
+acquisitionport 1123
+----
++
+This would change the source port used for client requests to UDP port 1123.
+You could then persuade the firewall administrator to open that port.
+
+[[bindacqaddress]]*bindacqaddress* _address_::
+The *bindacqaddress* directive specifies a local IP address to which
+*chronyd* will bind its NTP and NTS-KE client sockets. The syntax is similar to
+the <<bindaddress,*bindaddress*>> and <<bindcmdaddress,*bindcmdaddress*>>
+directives.
++
+For each of the IPv4 and IPv6 protocols, only one *bindacqaddress* directive
+can be specified.
+
+[[bindacqdevice]]*bindacqdevice* _interface_::
+The *bindacqdevice* directive binds the client sockets to a network device
+specified by the interface name. This can be useful when the local address is
+dynamic, or to enable an NTP source specified with a link-local IPv6 address.
+This directive can specify only one interface and it is supported on Linux
+only.
++
+An example of the directive is:
++
+----
+bindacqdevice eth0
+----
+
+[[dscp]]*dscp* _point_::
+The *dscp* directive sets the Differentiated Services Code Point (DSCP) in
+transmitted NTP packets to the specified value. It can improve stability of NTP
+measurements in local networks where switches or routers are configured to
+prioritise forwarding of packets with specific DSCP values. The default value
+is 0 and the maximum value is 63.
++
+An example of the directive (setting the Expedited Forwarding class) is:
++
+----
+dscp 46
+----
+
+[[dumpdir]]*dumpdir* _directory_::
+To compute the rate of gain or loss of time, *chronyd* has to store a
+measurement history for each of the time sources it uses.
++
+All supported systems, with the exception of macOS 10.12 and earlier, have
+operating system support for setting the rate of gain or loss to compensate for
+known errors.
+(On macOS 10.12 and earlier, *chronyd* must simulate such a capability by
+periodically slewing the system clock forwards or backwards by a suitable amount
+to compensate for the error built up since the previous slew.)
++
+For such systems, it is possible to save the measurement history across
+restarts of *chronyd* (assuming no changes are made to the system clock
+behaviour whilst it is not running). The *dumpdir* directive defines the
+directory where the measurement histories are saved when *chronyd* exits,
+or the <<chronyc.adoc#dump,*dump*>> command in *chronyc* is issued.
++
+If the directory does not exist, it will be created automatically.
++
+The *-r* option of *chronyd* enables loading of the dump files on start. All
+dump files found in the directory will be removed after start, even if the *-r*
+option is not present.
++
+An example of the directive is:
++
+----
+dumpdir @CHRONYRUNDIR@
+----
++
+A source whose IP address is _1.2.3.4_ would have its measurement history saved
+in the file _@CHRONYRUNDIR@/1.2.3.4.dat_. History of reference clocks is saved
+to files named by their reference ID in form of _refid:XXXXXXXX.dat_.
+
+[[maxsamples]]*maxsamples* _samples_::
+The *maxsamples* directive sets the default maximum number of samples that
+*chronyd* should keep for each source. This setting can be overridden for
+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
+*chronyd* should keep for each source. This setting can be overridden for
+individual sources in the <<server,*server*>> and <<refclock,*refclock*>>
+directives. The default value is 6. The useful range is 4 to 64.
++
+Forcing *chronyd* to keep more samples than it would normally keep reduces
+noise in the estimated frequency and offset, but slows down the response to
+changes in the frequency and offset of the clock. The offsets in the
+<<chronyc.adoc#tracking,*tracking*>> and
+<<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.
++
+If the directory does not exist, it will be created automatically.
++
+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) and the maximum value is 2^31-1 (68 years).
++
+The interval must be longer than polling intervals of all configured NTP
+sources using NTS, otherwise the source with a longer polling interval will
+refresh the keys on each poll and no NTP packets will be exchanged.
+
+[[ntstrustedcerts]]*ntstrustedcerts* [_set-ID_] _file_|_directory_::
+This directive specifies a file or directory containing trusted certificates
+(in the PEM format) which are needed to verify certificates of NTS-KE servers,
+e.g. certificates of trusted certificate authorities (CA) or self-signed
+certificates of the servers.
++
+The optional _set-ID_ argument is a number in the range 0 through 2^32-1, which
+selects the set of certificates where certificates from the specified file
+or directory are added. The default ID is 0, which is a set containing the
+system's default trusted CAs (unless the *nosystemcert* directive is present).
+All other sets are empty by default. A set of certificates can be selected for
+verification of an NTS server by the *certset* option in the *server* or *pool*
+directive.
++
+This directive can be used multiple times to specify one or more sets of
+trusted certificates, each containing certificates from one or more files
+and/or directories.
++
+It is not necessary to restart *chronyd* in order to reload the certificates if
+they change (e.g. after a renewal).
++
+An example is:
++
+----
+ntstrustedcerts /etc/pki/nts/ca1.example.net.crt
+ntstrustedcerts 1 /etc/pki/nts/ca2.example.net.crt
+ntstrustedcerts 1 /etc/pki/nts/ca3.example.net.crt
+ntstrustedcerts 2 /etc/pki/nts/ntp2.example.net.crt
+----
+
+[[nosystemcert]]*nosystemcert*::
+This directive disables the system's default trusted CAs. Only certificates
+specified by the *ntstrustedcerts* directive will be trusted.
+
+[[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 and should be used only as a last resort,
+preferably with a minimal number of trusted certificates. 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.
+
+[[refresh]]*refresh* _interval_::
+This directive specifies the interval (in seconds) between refreshing IP
+addresses of NTP sources specified by hostname. If the hostname no longer
+resolves to the currently used address, it will be replaced with one of the new
+addresses to avoid using a server which is no longer intended for service, even
+if it is still responding correctly and would not be replaced as unreachable.
+Only one source is refreshed at a time. The default value is 1209600 (2 weeks)
+and the maximum value is 2^31-1 (68 years). A value of 0 disables the periodic
+refreshment.
++
+The <<chronyc.adoc#refresh,*refresh*>> command can be used to refresh all
+sources immediately.
+
+=== Source selection
+
+[[authselectmode]]*authselectmode* _mode_::
+NTP sources can be specified with the *key* or *nts* option to enable
+authentication to limit the impact of man-in-the-middle attacks. The
+attackers can drop or delay NTP packets (up to the *maxdelay* and
+<<maxdistance,*maxdistance*>> limits), but they cannot modify the timestamps
+contained in the packets. The attack can cause only a limited slew or step, and
+also cause the clock to run faster or slower than real time (up to double of
+the <<maxdrift,*maxdrift*>> limit).
++
+When authentication is enabled for an NTP source, it is important to disable
+unauthenticated NTP sources which could be exploited in the attack, e.g. if
+they are not reachable only over a trusted network. Alternatively, the source
+selection can be configured with the *require* and *trust* options to
+synchronise to the unauthenticated sources only if they agree with the
+authenticated sources and might have a positive impact on the accuracy of the
+clock. Note that in this case the impact of the attack is higher. The attackers
+cannot cause an arbitrarily large step or slew, but they have more control over
+the frequency of the clock and can cause *chronyd* to report false information,
+e.g. a significantly smaller root delay and dispersion.
++
+This directive determines the default selection options for authenticated and
+unauthenticated sources in order to simplify the configuration with the
+configuration file and *chronyc* commands. It sets a policy for authentication.
++
+Sources specified with the *noselect* option are ignored (not counted as either
+authenticated or unauthenticated), and they always have only the selection
+options specified in the configuration.
++
+There are four modes:
++
+*require*:::
+Authentication is strictly required for NTP sources in this mode. If any
+unauthenticated NTP sources are specified, they will automatically get the
+*noselect* option to prevent them from being selected for synchronisation.
+*prefer*:::
+In this mode, authentication is optional and preferred. If it is enabled for at
+least one NTP source, all unauthenticated NTP sources will get the *noselect*
+option.
+*mix*:::
+In this mode, authentication is optional and synchronisation to a mix of
+authenticated and unauthenticated NTP sources is allowed. If both authenticated
+and unauthenticated NTP sources are specified, all authenticated NTP sources
+and reference clocks will get the *require* and *trust* options to prevent
+synchronisation to unauthenticated NTP sources if they do not agree with a
+majority of the authenticated sources and reference clocks. This is the default
+mode.
+*ignore*:::
+In this mode, authentication is ignored in the source selection. All sources
+will have only the selection options that were specified in the configuration
+file, or *chronyc* command. This was the behaviour of *chronyd* in versions
+before 4.0.
+{blank}::
++
+As an example, the following configuration using the default *mix* mode:
++
+----
+server ntp1.example.net nts
+server ntp2.example.net nts
+server ntp3.example.net
+refclock SOCK /var/run/chrony.ttyS0.sock
+----
++
+is equivalent to the following configuration using the *ignore* mode:
++
+----
+authselectmode ignore
+server ntp1.example.net nts require trust
+server ntp2.example.net nts require trust
+server ntp3.example.net
+refclock /var/run/chrony.ttyS0.sock require trust
+----
+
+[[combinelimit]]*combinelimit* _limit_::
+When *chronyd* has multiple sources available for synchronisation, it has to
+select one source as the synchronisation source. The measured offsets and
+frequencies of the system clock relative to the other sources, however, can be
+combined with the selected source to improve the accuracy of the system clock.
++
+The *combinelimit* directive limits which sources are included in the combining
+algorithm. Their synchronisation distance has to be shorter than the distance
+of the selected source multiplied by the value of the limit. Also, their
+measured frequencies have to be close to the frequency of the selected source.
+If the selected source was specified with the *prefer* option, it can be
+combined only with other sources specified with this option.
++
+By default, the limit is 3. Setting the limit to 0 effectively disables the
+source combining algorithm and only the selected source will be used to control
+the system clock.
+
+[[maxdistance]]*maxdistance* _distance_::
+The *maxdistance* directive sets the maximum root distance of a source to be
+acceptable for synchronisation of the clock. Sources that have a distance
+larger than the specified distance will be rejected. The distance estimates the
+maximum error of the source. It includes the root dispersion and half of the
+root delay (round-trip time) accumulated on the path to the primary source.
++
+By default, the maximum root distance is 3 seconds.
++
+Setting *maxdistance* to a larger value can be useful to allow synchronisation
+with a server that only has a very infrequent connection to its sources and can
+accumulate a large dispersion between updates of its clock.
+
+[[maxjitter]]*maxjitter* _jitter_::
+The *maxjitter* directive sets the maximum allowed jitter of the sources to not
+be rejected by the source selection algorithm. This prevents synchronisation
+with sources that have a small root distance, but their time is too variable.
++
+By default, the maximum jitter is 1 second.
+
+[[minsources]]*minsources* _sources_::
+The *minsources* directive sets the minimum number of sources that need to be
+considered as selectable in the source selection algorithm before the local
+clock is updated. The default value is 1.
++
+Setting this option to a larger number can be used to improve the reliability.
+More sources will have to agree with each other and the clock will not be
+updated when only one source (which could be serving incorrect time) is
+reachable.
+
+[[reselectdist]]*reselectdist* _distance_::
+When *chronyd* selects a synchronisation source from available sources, it
+will prefer the one with the shortest synchronisation distance. However, to
+avoid frequent reselecting when there are sources with similar distance, a
+fixed distance is added to the distance for sources that are currently not
+selected. This can be set with the *reselectdist* directive. By default, the
+distance is 100 microseconds.
+
+[[stratumweight]]*stratumweight* _distance_::
+The *stratumweight* directive sets how much distance should be added per
+stratum to the synchronisation distance when *chronyd* selects the
+synchronisation source from available sources.
++
+By default, the weight is 0.001 seconds. This means that the stratum of the sources
+in the selection process matters only when the differences between the
+distances are in milliseconds.
+
+=== System clock
+
+[[clockprecision]]*clockprecision* _precision_::
+The *clockprecision* directive specifies the precision of the system clock (in
+seconds). It is used by *chronyd* to estimate the minimum noise in NTP
+measurements and randomise low-order bits of timestamps in NTP responses. By
+default, the precision is measured on start as the minimum time to read the
+clock.
++
+The measured value works well in most cases. However, it generally
+overestimates the precision and it can be sensitive to the CPU speed, which can
+change over time to save power. In some cases with a high-precision clocksource
+(e.g. the Time Stamp Counter of the CPU) and hardware timestamping, setting the
+precision on the server to a smaller value can improve stability of clients'
+NTP measurements. The server's precision is reported on clients by the
+<<chronyc.adoc#ntpdata,*ntpdata*>> command.
++
+An example setting the precision to 8 nanoseconds is:
++
+----
+clockprecision 8e-9
+----
+
+[[corrtimeratio]]*corrtimeratio* _ratio_::
+When *chronyd* is slewing the system clock to correct an offset, the rate at
+which it is slewing adds to the frequency error of the clock. On all supported
+systems, with the exception of macOS 12 and earlier, this rate can be
+controlled.
++
+The *corrtimeratio* directive sets the ratio between the duration in which the
+clock is slewed for an average correction according to the source history and
+the interval in which the corrections are done (usually the NTP polling
+interval). Corrections larger than the average take less time and smaller
+corrections take more time, the amount of the correction and the correction
+time are inversely proportional.
++
+Increasing *corrtimeratio* improves the overall frequency error of the system
+clock, but increases the overall time error as the corrections take longer.
++
+By default, the ratio is set to 3, the time accuracy of the clock is preferred
+over its frequency accuracy.
++
+The maximum allowed slew rate can be set by the <<maxslewrate,*maxslewrate*>>
+directive. The current remaining correction is shown in the
+<<chronyc.adoc#tracking,*tracking*>> report as the *System time* value.
+
+[[driftfile]]*driftfile* _file_::
+One of the main activities of the *chronyd* program is to work out the rate at
+which the system clock gains or loses time relative to real time.
++
+Whenever *chronyd* computes a new value of the gain or loss rate, it is desirable
+to record it somewhere. This allows *chronyd* to begin compensating the system
+clock at that rate whenever it is restarted, even before it has had a chance to
+obtain an equally good estimate of the rate during the new run. (This process
+can take many minutes, at least.)
++
+The *driftfile* directive allows a file to be specified into which *chronyd*
+can store the rate information. Two parameters are recorded in the file. The
+first is the rate at which the system clock gains or loses time, expressed in
+parts per million, with gains positive. Therefore, a value of 100.0 indicates
+that when the system clock has advanced by a second, it has gained 100
+microseconds in reality (so the true time has only advanced by 999900
+microseconds). The second is an estimate of the error bound around the first
+value in which the true rate actually lies.
++
+An example of the driftfile directive is:
++
+----
+driftfile @CHRONYVARDIR@/drift
+----
+
+[[fallbackdrift]]*fallbackdrift* _min-interval_ _max-interval_::
+Fallback drifts are long-term averages of the system clock drift calculated
+over exponentially increasing intervals. They are used to avoid quickly
+drifting away from true time when the clock was not updated for a longer period
+of time and there was a short-term deviation in the drift before the updates
+stopped.
++
+The directive specifies the minimum and maximum interval since the last clock
+update to switch between fallback drifts. They are defined as a power of 2 (in
+seconds). The syntax is as follows:
++
+----
+fallbackdrift 16 19
+----
++
+In this example, the minimum interval is 16 (18 hours) and the maximum interval is
+19 (6 days). The system clock frequency will be set to the first fallback 18
+hours after last clock update, to the second after 36 hours, and so on. This
+might be a good setting to cover frequency changes due to daily and weekly
+temperature fluctuations. When the frequency is set to a fallback, the state of
+the clock will change to '`Not synchronised`'.
++
+By default (or if the specified maximum or minimum is 0), no fallbacks are used
+and the clock frequency changes only with new measurements from NTP sources,
+reference clocks, or manual input.
+
+[[leapsecmode]]*leapsecmode* _mode_::
+A leap second is an adjustment that is occasionally applied to UTC to keep it
+close to the mean solar time. When a leap second is inserted, the last day of
+June or December has an extra second 23:59:60.
++
+For computer clocks that is a problem. The Unix time is defined as number of
+seconds since 00:00:00 UTC on 1 January 1970 without leap seconds. The system
+clock cannot have time 23:59:60, every minute has 60 seconds and every day has
+86400 seconds by definition. The inserted leap second is skipped and the clock
+is suddenly ahead of UTC by one second. The *leapsecmode* directive selects how
+that error is corrected. There are four options:
++
+*system*:::
+When inserting a leap second, the kernel steps the system clock backwards by
+one second when the clock gets to 00:00:00 UTC. When deleting a leap second, it
+steps forward by one second when the clock gets to 23:59:59 UTC. This is the
+default mode when the system driver supports leap seconds (i.e. all supported
+systems with the exception of macOS 12 and earlier).
+*step*:::
+This is similar to the *system* mode, except the clock is stepped by
+*chronyd* instead of the kernel. It can be useful to avoid bugs in the kernel
+code that would be executed in the *system* mode. This is the default mode
+when the system driver does not support leap seconds.
+*slew*:::
+The clock is corrected by slewing started at 00:00:00 UTC when a leap second
+is inserted or 23:59:59 UTC when a leap second is deleted. This might be
+preferred over the *system* and *step* modes when applications running on the
+system are sensitive to jumps in the system time and it is acceptable that the
+clock will be off for a longer time. On Linux with the default
+<<maxslewrate,*maxslewrate*>> value the correction takes 12 seconds.
+*ignore*:::
+No correction is applied to the clock for the leap second. The clock will be
+corrected later in normal operation when new measurements are made and the
+estimated offset includes the one second error. This option is particularly
+useful when multiple *chronyd* instances are running on the system, one
+controlling the system clock and others started with the *-x* option, which
+should rely on the first instance to correct the system clock and ignore it for
+the correction of their own NTP clock running on top of the system clock.
+{blank}::
++
+When serving time to NTP clients that cannot be configured to correct their
+clocks for a leap second by slewing, or to clients that would correct at
+slightly different rates when it is necessary to keep them close together, the
+*slew* mode can be combined with the <<smoothtime,*smoothtime*>> directive to
+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 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
+leap second in exactly the same way for synchronisation.
++
+This feature must be used carefully, because the server is intentionally not
+serving its best estimate of the true time.
++
+A recommended configuration to enable a server leap smear is:
++
+----
+leapsecmode slew
+maxslewrate 1000
+smoothtime 400 0.001024 leaponly
+----
++
+The first directive is necessary to disable the clock step which would reset
+the smoothing process. The second directive limits the slewing rate of the
+local clock to 1000 ppm, which improves the stability of the smoothing process
+when the local correction starts and ends. The third directive enables the
+server time smoothing process. It will start when the clock gets to 00:00:00
+UTC and it will take 62500 seconds (about 17.36 hours) to finish. The frequency
+offset will be changing by 0.001024 ppm per second and will reach a maximum of
+32 ppm in 31250 seconds. The *leaponly* option makes the duration of the leap
+smear constant and allows the clients to safely synchronise with multiple
+identically configured leap smearing servers.
++
+The duration of the leap smear can be calculated from the specified wander as
++
+----
+duration = sqrt(4 / wander)
+----
+
+[[leapsectz]]*leapsectz* _timezone_::
+This directive specifies a timezone in the system timezone database which
+*chronyd* can use to determine when will the next leap second occur and what is
+the current offset between TAI and UTC. It will periodically check if 23:59:59
+and 23:59:60 are valid times in the timezone. This normally works with the
+_right/UTC_ timezone.
++
+When a leap second is announced, the timezone needs to be updated at least 12
+hours before the leap second. It is not necessary to restart *chronyd*.
++
+This directive is useful with reference clocks and other time sources which do
+not announce leap seconds, or announce them too late for an NTP server to
+forward them to its own clients. Clients of leap smearing servers must not
+use this directive.
++
+It is also useful when the system clock is required to have correct TAI-UTC
+offset. Note that the offset is set only when leap seconds are handled by the
+kernel, i.e. <<leapsecmode,*leapsecmode*>> is set to *system*.
++
+The specified timezone is not used as an exclusive source of information about
+leap seconds. If a majority of time sources announce on the last day of June or
+December that a leap second should be inserted or deleted, it will be accepted
+even if it is not included in the timezone.
++
+An example of the directive is:
++
+----
+leapsectz right/UTC
+----
++
+The following shell command verifies that the timezone contains leap seconds
+and can be used with this directive:
++
+----
+$ TZ=right/UTC date -d 'Dec 31 2008 23:59:60'
+Wed Dec 31 23:59:60 UTC 2008
+----
+
+[[makestep]]*makestep* _threshold_ _limit_::
+Normally *chronyd* will cause the system to gradually correct any time offset,
+by slowing down or speeding up the clock as required. In certain situations,
+e.g. when *chronyd* is initially started, the system clock might be so far
+adrift that this slewing process would take a very long time to correct the
+system clock.
++
+This directive forces *chronyd* to step the system clock if the adjustment is
+larger than a threshold value, but only if there were no more clock updates
+since *chronyd* was started than the specified limit. A negative value disables
+the limit.
++
+On most systems it is desirable to step the system clock only on boot, before
+starting programs that rely on time advancing monotonically forwards.
++
+An example of the use of this directive is:
++
+----
+makestep 0.1 3
+----
++
+This would step the system clock if the adjustment is larger than 0.1 seconds, but
+only in the first three clock updates.
+
+[[maxchange]]*maxchange* _offset_ _start_ _ignore_::
+This directive sets the maximum offset to be accepted on a clock update. The
+offset is measured relative to the current estimate of the true time, which is
+different from the system time if a previous slew did not finish.
++
+The check is enabled after the specified number of clock updates to allow a
+large initial offset to be corrected on start. Offsets larger than the
+specified maximum will be ignored for the specified number of times. Another
+large offset will cause *chronyd* to give up and exit. A negative value
+can be used to disable the limit to ignore all large offsets. A syslog message
+will be generated when an offset is ignored or it causes the exit.
++
+An example of the use of this directive is:
++
+----
+maxchange 1000 1 2
+----
++
+After the first clock update, *chronyd* will check the offset on every clock
+update, it will ignore two adjustments larger than 1000 seconds and exit on
+another one.
+
+[[maxclockerror]]*maxclockerror* _error-in-ppm_::
+The *maxclockerror* directive sets the maximum assumed frequency error that the
+system clock can gain on its own between clock updates. It describes the
+stability of the clock.
++
+By default, the maximum error is 1 ppm.
++
+Typical values for _error-in-ppm_ might be 10 for a low quality clock and 0.1
+for a high quality clock using a temperature compensated crystal oscillator.
+
+[[maxdrift]]*maxdrift* _drift-in-ppm_::
+This directive specifies the maximum assumed drift (frequency error) of the
+system clock. It limits the frequency adjustment that *chronyd* is allowed to
+use to correct the measured drift. It is an additional limit to the maximum
+adjustment that can be set by the system driver (100000 ppm on Linux, 500 ppm
+on FreeBSD, NetBSD, and macOS 10.13+, 32500 ppm on illumos).
++
+By default, the maximum assumed drift is 500000 ppm, i.e. the adjustment is
+limited by the system driver rather than this directive.
+
+[[maxupdateskew]]*maxupdateskew* _skew-in-ppm_::
+One of *chronyd*'s tasks is to work out how fast or slow the computer's clock
+runs relative to its reference sources. In addition, it computes an estimate of
+the error bounds around the estimated value.
++
+If the range of error is too large, it probably indicates that the measurements
+have not settled down yet, and that the estimated gain or loss rate is not very
+reliable.
++
+The *maxupdateskew* directive sets the threshold for determining whether an
+estimate might be so unreliable that it should not be used. By default, the
+threshold is 1000 ppm.
++
+Typical values for _skew-in-ppm_ might be 100 for NTP sources polled over a
+wireless network, and 10 or smaller for sources on a local wired network.
++
+It should be noted that this is not the only means of protection against using
+unreliable estimates. At all times, *chronyd* keeps track of both the estimated
+gain or loss rate, and the error bound on the estimate. When a new estimate is
+generated following another measurement from one of the sources, a weighted
+combination algorithm is used to update the existing estimate. If it has
+significantly smaller error bounds than the new estimate, the existing estimate
+will dominate in the new combined value.
+
+[[maxslewrate]]*maxslewrate* _rate-in-ppm_::
+The *maxslewrate* directive sets the maximum rate at which *chronyd* is allowed
+to slew the time. It limits the slew rate controlled by the correction time
+ratio (which can be set by the <<corrtimeratio,*corrtimeratio*>> directive) and
+is effective only on systems where *chronyd* is able to control the rate (i.e.
+all supported systems with the exception of macOS 12 or earlier).
++
+For each system there is a maximum frequency offset of the clock that can be set
+by the driver. On Linux it is 100000 ppm, on FreeBSD, NetBSD and macOS 10.13+ it
+is 5000 ppm, and on illumos it is 32500 ppm. Also, due to a kernel limitation,
+setting *maxslewrate* on FreeBSD, NetBSD, macOS 10.13+ to a value between 500
+ppm and 5000 ppm will effectively set it to 500 ppm.
++
+By default, the maximum slew rate is set to 83333.333 ppm (one twelfth).
+
+[[tempcomp]]
+*tempcomp* _file_ _interval_ _T0_ _k0_ _k1_ _k2_::
+*tempcomp* _file_ _interval_ _points-file_::
+Normally, changes in the rate of drift of the system clock are caused mainly by
+changes in the temperature of the crystal oscillator on the motherboard.
++
+If there are temperature measurements available from a sensor close to the
+oscillator, the *tempcomp* directive can be used to compensate for the changes
+in the temperature and improve the stability and accuracy of the clock.
++
+The result depends on many factors, including the resolution of the sensor, the
+amount of noise in the measurements, the polling interval of the time source,
+the compensation update interval, how well the compensation is specified, and
+how close the sensor is to the oscillator. When it is working well, the
+frequency reported in the _tracking.log_ file is more stable and the maximum
+reached offset is smaller.
++
+There are two forms of the directive. The first one has six parameters: a path
+to the file containing the current temperature from the sensor (in text
+format), the compensation update interval (in seconds), and temperature
+coefficients _T0_, _k0_, _k1_, _k2_.
++
+The frequency compensation is calculated (in ppm) as
++
+----
+comp = k0 + (T - T0) * k1 + (T - T0)^2 * k2
+----
++
+The result has to be between -10 ppm and 10 ppm, otherwise the measurement is
+considered invalid and will be ignored. The _k0_ coefficient can be adjusted to
+keep the compensation in that range.
++
+An example of the use is:
++
+----
+tempcomp /sys/class/hwmon/hwmon0/temp2_input 30 26000 0.0 0.000183 0.0
+----
++
+The measured temperature will be read from the file in the Linux sysfs
+filesystem every 30 seconds. When the temperature is 26000 (26 degrees
+Celsius), the frequency correction will be zero. When it is 27000 (27 degrees
+Celsius), the clock will be set to run faster by 0.183 ppm, etc.
++
+The second form has three parameters: the path to the sensor file, the update
+interval, and a path to a file containing a list of (temperature, compensation)
+points, from which the compensation is linearly interpolated or extrapolated.
++
+An example is:
++
+----
+tempcomp /sys/class/hwmon/hwmon0/temp2_input 30 /etc/chrony.tempcomp
+----
++
+where the _/etc/chrony.tempcomp_ file could have
++
+----
+20000 1.0
+21000 0.64
+22000 0.36
+23000 0.16
+24000 0.04
+25000 0.0
+26000 0.04
+27000 0.16
+28000 0.36
+29000 0.64
+30000 1.0
+----
++
+Valid measurements with corresponding compensations are logged to the
+_tempcomp.log_ file if enabled by the <<log,*log tempcomp*>> directive.
+
+=== NTP server
+
+[[allow]]*allow* [*all*] [_subnet_]::
+The *allow* directive is used to designate a particular subnet from which NTP
+clients are allowed to access the computer as an NTP server. It also controls
+access of NTS-KE clients when NTS is enabled on the server.
++
+The default is that no clients are allowed access, i.e. *chronyd* operates
+purely as an NTP client. If the *allow* directive is used, *chronyd* will be
+both a client of its servers, and a server to other clients.
++
+This directive can be used multiple times.
++
+Examples of the use of the directive are as follows:
++
+----
+allow 1.2.3.4
+allow 3.4.5.0/24
+allow 3.4.5
+allow 2001:db8::/32
+allow 0/0
+allow ::/0
+allow
+----
++
+The first directive allows access from an IPv4 address. The second directive
+allows access from all computers in an IPv4 subnet specified in the CIDR
+notation. The third directive specifies the same subnet using a simpler
+notation where the prefix length is determined by the number of dots. The
+fourth directive specifies an IPv6 subnet. The fifth and sixth directives allow
+access from all IPv4 and IPv6 addresses respectively. The seventh directive
+allows access from all addresses (both IPv4 or IPv6).
++
+A second form of the directive, *allow all*, has a greater effect, depending on
+the ordering of directives in the configuration file. To illustrate the effect,
+consider the two examples:
++
+----
+allow 1.2.3.4
+deny 1.2.3.0/24
+allow 1.2.0.0/16
+----
++
+and
++
+----
+allow 1.2.3.4
+deny 1.2.3.0/24
+allow all 1.2.0.0/16
+----
++
+In the first example, the effect is the same regardless of what order the three
+directives are given in. So the _1.2.0.0/16_ subnet is allowed access, except
+for the _1.2.3.0/24_ subnet, which is denied access, however the host _1.2.3.4_
+is allowed access.
++
+In the second example, the *allow all 1.2.0.0/16* directive overrides the
+effect of _any_ previous directive relating to a subnet within the specified
+subnet. Within a configuration file this capability is probably rather moot;
+however, it is of greater use for reconfiguration at run-time via *chronyc*
+with the <<chronyc.adoc#allow,*allow all*>> command.
++
+The rules are internally represented as a tree of tables with one level per
+four bits of the IPv4 or IPv6 address. The order of the *allow* and *deny*
+directives matters if they modify the same records of one table, i.e. if one
+subnet is included in the other subnet and their prefix lengths are at the same
+level. For example, _1.2.3.0/28_ and _1.2.3.0/29_ are in different tables, but
+_1.2.3.0/25_ and _1.2.3.0/28_ are in the same table. The configuration can be
+verified for individual addresses with the <<chronyc.adoc#accheck,*accheck*>>
+command in *chronyc*.
++
+A hostname can be specified in the directives instead of the IP address, but
+the name must be resolvable when *chronyd* is started, i.e. the network is
+already up and DNS is working. If the hostname resolves to multiple addresses,
+only the first address (in the order returned by the system resolver) will be
+allowed or denied.
++
+Note, if the <<initstepslew,*initstepslew*>> directive is used in the
+configuration file, each of the computers listed in that directive must allow
+client access by this computer for it to work.
+
+[[deny]]*deny* [*all*] [_subnet_]::
+This is similar to the <<allow,*allow*>> directive, except that it denies NTP
+and NTS-KE client access to a particular subnet or host, rather than allowing
+it.
++
+The syntax is identical and the directive can be used multiple times too.
++
+There is also a *deny all* directive with similar behaviour to the *allow all*
+directive.
+
+[[bindaddress]]*bindaddress* _address_::
+The *bindaddress* directive binds the sockets on which *chronyd* listens for
+NTP and NTS-KE requests to a local address of the computer. On systems other
+than Linux, the address of the computer needs to be already configured when
+*chronyd* is started.
++
+An example of the use of the directive is:
++
+----
+bindaddress 192.168.1.1
+----
++
+Currently, for each of the IPv4 and IPv6 protocols, only one *bindaddress*
+directive can be specified. Therefore, it is not useful on computers which
+should serve NTP on multiple network interfaces.
+
+[[binddevice]]*binddevice* _interface_::
+The *binddevice* directive binds the NTP and NTS-KE server sockets to a network
+device specified by the interface name. This directive can specify only one
+interface and it is supported on Linux only.
++
+An example of the directive is:
++
+----
+binddevice eth0
+----
+
+[[broadcast]]*broadcast* _interval_ _address_ [_port_]::
+The *broadcast* directive is used to declare a broadcast address to which
+chronyd should send packets in the NTP broadcast mode (i.e. make *chronyd* act
+as a broadcast server). Broadcast clients on that subnet will be able to
+synchronise.
++
+This directive can be used multiple times to specify multiple addresses.
++
+The syntax is as follows:
++
+----
+broadcast 32 192.168.1.255
+broadcast 64 192.168.2.255 12123
+broadcast 64 ff02::101
+----
++
+In the first example, the destination port defaults to UDP port 123 (the normal NTP
+port). In the second example, the destination port is specified as 12123. The
+first parameter in each case (32 or 64 respectively) is the interval in seconds
+between broadcast packets being sent. The second parameter in each case is the
+broadcast address to send the packet to. This should correspond to the
+broadcast address of one of the network interfaces on the computer where
+*chronyd* is running.
++
+You can have more than 1 *broadcast* directive if you have more than 1 network
+interface onto which you want to send NTP broadcast packets.
++
+*chronyd* itself cannot act as a broadcast client; it must always be configured
+as a point-to-point client by defining specific NTP servers and peers. This
+broadcast server feature is intended for providing a time source to other NTP
+implementations.
++
+If *ntpd* is used as the broadcast client, it will try to measure the
+round-trip delay between the server and client with normal client mode packets.
+Thus, the broadcast subnet should also be the subject of an <<allow,*allow*>>
+directive.
+
+[[clientloglimit]]*clientloglimit* _limit_::
+This directive specifies the maximum amount of memory that *chronyd* is allowed
+to allocate for logging of client accesses and the state that *chronyd* as an
+NTP server needs to support the interleaved mode for its clients. The default
+limit is 524288 bytes, which enables monitoring of up to 4096 IP addresses at
+the same time and holding NTP timestamps for up to 4096 clients using the
+interleaved mode (depending on uniformity of their polling interval). The
+number of addresses and timestamps is always a power of 2. The maximum
+effective value is 2147483648 (2 GB), which corresponds to 16777216 addresses
+and timestamps.
++
+An example of the use of this directive is:
++
+----
+clientloglimit 1048576
+----
+
+[[noclientlog]]*noclientlog*::
+This directive, which takes no arguments, specifies that client accesses are
+not to be logged. Normally they are logged, allowing statistics to be reported
+using the <<chronyc.adoc#clients,*clients*>> command in *chronyc*. This option
+also effectively disables server support for the NTP interleaved mode.
+
+[[local]]*local* [_option_]...::
+The *local* directive enables a local reference mode, which allows *chronyd*
+operating as an NTP server to appear synchronised to real time (from the
+viewpoint of clients polling it), even when it was never synchronised or
+the last update of the clock happened a long time ago.
++
+This directive is normally used in an isolated network, where computers are
+required to be synchronised to one another, but not necessarily to real time.
+The server can be kept vaguely in line with real time by manual input.
++
+The *local* directive has the following options:
++
+*stratum* _stratum_:::
+This option sets the stratum of the server which will be reported to clients
+when the local reference is active. The specified value is in the range 1
+through 15, and the default value is 10. It should be larger than the maximum
+expected stratum in the network when external NTP servers are accessible.
++
+Stratum 1 indicates a computer that has a true real-time reference directly
+connected to it (e.g. GPS, atomic clock, etc.), such computers are expected to
+be very close to real time. Stratum 2 computers are those which have a stratum
+1 server; stratum 3 computers have a stratum 2 server and so on. A value
+of 10 indicates that the clock is so many hops away from a reference clock that
+its time is fairly unreliable.
+*distance* _distance_:::
+This option sets the threshold for the root distance which will activate the local
+reference. If *chronyd* was synchronised to some source, the local reference
+will not be activated until its root distance reaches the specified value (the
+rate at which the distance is increasing depends on how well the clock was
+tracking the source). The default value is 1 second.
++
+The current root distance can be calculated from root delay and root dispersion
+(reported by the <<chronyc.adoc#tracking,*tracking*>> command in *chronyc*) as:
++
+----
+distance = delay / 2 + dispersion
+----
+*orphan*:::
+This option enables a special '`orphan`' mode, where sources with stratum equal
+to the local _stratum_ are assumed to not serve real time. They are ignored
+unless no other source is selectable and their reference IDs are smaller than
+the local reference ID.
++
+This allows multiple servers in the network to use the same *local*
+configuration and to be synchronised to one another, without confusing clients
+that poll more than one server. Each server needs to be configured to poll all
+other servers with the *local* directive. This ensures only the server with the
+smallest reference ID has the local reference active and others are
+synchronised to it. If that server stops responding, the server with the second
+smallest reference ID will take over when its local reference mode activates
+(root distance reaches the threshold configured by the *distance* option).
++
+The *orphan* mode is compatible with the *ntpd*'s orphan mode (enabled by the
+*tos orphan* command).
+{blank}::
++
+An example of the directive is:
++
+----
+local stratum 10 orphan distance 0.1
+----
+
+[[ntpsigndsocket]]*ntpsigndsocket* _directory_::
+This directive specifies the location of the Samba *ntp_signd* socket when it
+is running as a Domain Controller (DC). If *chronyd* is compiled with this
+feature, responses to MS-SNTP clients will be signed by the *smbd* daemon.
++
+Note that MS-SNTP requests are not authenticated and any client that is allowed
+to access the server by the <<allow,*allow*>> directive, or the
+<<chronyc.adoc#allow,*allow*>> command in *chronyc*, can get an MS-SNTP
+response signed with a trust account's password and try to crack the password
+in a brute-force attack. Access to the server should be carefully controlled.
++
+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 4460.
++
+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. The file should also include
+any intermediate certificates that the clients will need to validate the
+server's certificate. The file needs to be readable by the user under which
+*chronyd* is running after dropping root privileges.
++
+This directive can be used multiple times to specify multiple certificates for
+different names of the server.
++
+The files are loaded only once. *chronyd* needs to be restarted in order to
+load a renewed certificate. The <<ntsdumpdir,*ntsdumpdir*>> and
+<<dumpdir,*dumpdir*>> directives with the *-r* option of *chronyd* are
+recommended for a near-seamless server operation.
+
+[[ntsserverkey]]*ntsserverkey* _file_::
+This directive specifies a file containing a private key in the PEM format
+for *chronyd* to operate as an NTS server. The file needs to be readable by
+the user under which *chronyd* is running after dropping root privileges. For
+security reasons, it should not be readable by other users.
++
+This directive can be used multiple times to specify multiple keys. The number
+of keys must be the same as the number of certificates and the corresponding
+files must be specified in the same order.
+
+[[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. The
+maximum practical value is half of the system *FD_SETSIZE* constant (usually
+1024).
+
+[[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 (as a fully qualified domain name) 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 from the
+_/dev/urandom_ device. The server keeps two previous keys to give the clients
+time to get new cookies encrypted by the latest key. The interval is measured
+as the server's operating time, i.e. the actual interval can be longer if
+*chronyd* is not running continuously. The default interval is 604800 seconds
+(1 week). The maximum value is 2^31-1 (68 years).
++
+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 file includes the subsequent key to which the NTS-KE server will
+switch on the next rotation, i.e. the process copying and reloading the file
+does not need to be timed precisely (it can be delayed by up to one rotation
+interval). The NTS-KE servers need to be configured with the
+<<ntsntpserver,*ntsntpserver*>> 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
+<<allow,*allow*>> directive or the <<chronyc.adoc#allow,*allow*>> command in
+*chronyc*, an NTP peer is configured, or the broadcast server mode is enabled.
++
+The default value is 123, the standard NTP port. If set to 0, *chronyd* will
+never open the server port and will operate strictly in a client-only mode. The
+source port used in NTP client requests can be set by the
+<<acquisitionport,*acquisitionport*>> directive.
+
+[[ratelimit]]*ratelimit* [_option_]...::
+This directive enables response rate limiting for NTP packets. Its purpose is
+to reduce network traffic with misconfigured or broken NTP clients that are
+polling the server too frequently. The limits are applied to individual IP
+addresses. If multiple clients share one IP address (e.g. multiple hosts behind
+NAT), the sum of their traffic will be limited. If a client that increases its
+polling rate when it does not receive a reply is detected, its rate limiting
+will be temporarily suspended to avoid increasing the overall amount of
+traffic. The maximum number of IP addresses which can be monitored at the same
+time depends on the memory limit set by the <<clientloglimit,*clientloglimit*>>
+directive.
++
+The *ratelimit* directive supports a number of options (which can be defined
+in any order):
++
+*interval* _interval_:::
+This option sets the minimum interval between responses. It is defined as a
+power of 2 in seconds. The default value is 3 (8 seconds). The minimum value
+is -19 (524288 packets per second) and the maximum value is 12 (one packet per
+4096 seconds). Note that with values below -4 the rate limiting is coarse
+(responses are allowed in bursts, even if the interval between them is shorter
+than the specified interval).
+*burst* _responses_:::
+This option sets the maximum number of responses that can be sent in a burst,
+temporarily exceeding the limit specified by the *interval* option. This is
+useful for clients that make rapid measurements on start (e.g. *chronyd* with
+the *iburst* option). The default value is 8. The minimum value is 1 and the
+maximum value is 255.
+*leak* _rate_:::
+This option sets the rate at which responses are randomly allowed even if the
+limits specified by the *interval* and *burst* options are exceeded. This is
+necessary to prevent an attacker who is sending requests with a spoofed
+source address from completely blocking responses to that address. The leak
+rate is defined as a power of 1/2 and it is 2 by default, i.e. on average at
+least every fourth request has a response. The minimum value is 1 and the
+maximum value is 4.
+{blank}::
++
+An example use of the directive is:
++
+----
+ratelimit interval 1 burst 16
+----
++
+This would reduce the response rate for IP addresses sending packets on average
+more than once per 2 seconds, or sending packets in bursts of more than 16
+packets, by up to 75% (with default *leak* of 2).
+
+[[ntsratelimit]]*ntsratelimit* [_option_]...::
+This directive enables rate limiting of NTS-KE requests. It is similar to the
+<<ratelimit,*ratelimit*>> directive, except the default interval is 6
+(1 connection per 64 seconds).
++
+An example of the use of the directive is:
++
+----
+ntsratelimit interval 3 burst 1
+----
+
+[[smoothtime]]*smoothtime* _max-freq_ _max-wander_ [*leaponly*]::
+The *smoothtime* directive can be used to enable smoothing of the time that
+*chronyd* serves to its clients to make it easier for them to track it and keep
+their clocks close together even when large offset or frequency corrections are
+applied to the server's clock, for example after being offline for a longer
+time.
++
+BE WARNED: The server is intentionally not serving its best estimate of the
+true time. If a large offset has been accumulated, it can take a very long time
+to smooth it out. This directive should be used only when the clients are not
+configured to also poll another NTP server, because they could reject this
+server as a falseticker or fail to select a source completely.
++
+The smoothing process is implemented with a quadratic spline function with two
+or three pieces. It is independent from any slewing applied to the local system
+clock, but the accumulated offset and frequency will be reset when the clock is
+corrected by stepping, e.g. by the <<makestep,*makestep*>> directive or the
+<<chronyc.adoc#makestep,*makestep*>> command in *chronyc*. The process can be
+reset without stepping the clock by the <<chronyc.adoc#smoothtime,*smoothtime
+reset*>> command.
++
+The first two arguments of the directive are the maximum frequency offset of
+the smoothed time to the tracked NTP time (in ppm) and the maximum rate at
+which the frequency offset is allowed to change (in ppm per second). *leaponly*
+is an optional third argument which enables a mode where only leap seconds are
+smoothed out and normal offset and frequency changes are ignored. The *leaponly*
+option is useful in a combination with the <<leapsecmode,*leapsecmode slew*>>
+directive to allow the clients to use multiple time smoothing servers safely.
++
+The smoothing process is activated automatically when 1/10000 of the estimated
+skew of the local clock falls below the maximum rate of frequency change. It
+can be also activated manually by the <<chronyc.adoc#smoothtime,*smoothtime
+activate*>> command, which is particularly useful when the clock is
+synchronised only with manual input and the skew is always larger than the
+threshold. The <<chronyc.adoc#smoothing,*smoothing*>> command can be used to
+monitor the process.
++
+An example suitable for clients using *ntpd* and 1024 second polling interval
+could be:
++
+----
+smoothtime 400 0.001
+----
++
+An example suitable for clients using *chronyd* on Linux could be:
++
+----
+smoothtime 50000 0.01
+----
+
+=== Command and monitoring access
+
+[[bindcmdaddress]]*bindcmdaddress* _address_::
+The *bindcmdaddress* directive specifies a local IP address to which *chronyd*
+will bind the UDP socket listening for monitoring command packets (issued
+by *chronyc*). On systems other than Linux, the address of the interface needs
+to be already configured when *chronyd* is started.
++
+This directive can also change the path of the Unix domain command socket,
+which is used by *chronyc* to send configuration commands. The socket must be
+in a directory that is accessible only by the root or _chrony_ user. The
+directory will be created on start if it does not exist. The compiled-in default
+path of the socket is _@CHRONYRUNDIR@/chronyd.sock_. The socket can be
+disabled by setting the path to _/_.
++
+By default, *chronyd* binds the UDP sockets to the addresses _127.0.0.1_ and
+_::1_ (i.e. the loopback interface). This blocks all access except from
+localhost. To listen for command packets on all interfaces, you can add the
+lines:
++
+----
+bindcmdaddress 0.0.0.0
+bindcmdaddress ::
+----
++
+to the configuration file.
++
+For each of the IPv4, IPv6, and Unix domain protocols, only one
+*bindcmdaddress* directive can be specified.
++
+An example that sets the path of the Unix domain command socket is:
++
+----
+bindcmdaddress /var/run/chrony/chronyd.sock
+----
+
+[[bindcmddevice]]*bindcmddevice* _interface_::
+The *bindcmddevice* directive binds the UDP command sockets to a network device
+specified by the interface name. This directive can specify only one interface
+and it is supported on Linux only.
++
+An example of the directive is:
++
+----
+bindcmddevice eth0
+----
+
+[[cmdallow]]*cmdallow* [*all*] [_subnet_]::
+This is similar to the <<allow,*allow*>> directive, except that it allows
+monitoring access (rather than NTP client access) to a particular subnet or
+host. (By '`monitoring access`' is meant that *chronyc* can be run on those
+hosts and retrieve monitoring data from *chronyd* on this computer.)
++
+The syntax is identical to the *allow* directive.
++
+There is also a *cmdallow all* directive with similar behaviour to the *allow
+all* directive (but applying to monitoring access in this case, of course).
++
+Note that *chronyd* has to be configured with the
+<<bindcmdaddress,*bindcmdaddress*>> directive to not listen only on the
+loopback interface to actually allow remote access.
+
+[[cmddeny]]*cmddeny* [*all*] [_subnet_]::
+This is similar to the <<cmdallow,*cmdallow*>> directive, except that it denies
+monitoring access to a particular subnet or host, rather than allowing it.
++
+The syntax is identical.
++
+There is also a *cmddeny all* directive with similar behaviour to the *cmdallow
+all* directive.
+
+[[cmdport]]*cmdport* _port_::
+The *cmdport* directive allows the port that is used for run-time monitoring
+(via the *chronyc* program) to be altered from its default (323). If set to 0,
+*chronyd* will not open the port, which disables remote *chronyc* access (with
+a non-default *bindcmdaddress*) and local access for unprivileged users. It
+does not disable the Unix domain command socket.
++
+An example shows the syntax:
++
+----
+cmdport 257
+----
++
+This would make *chronyd* use UDP 257 as its command port. (*chronyc* would
+need to be run with the *-p 257* option to inter-operate correctly.)
+
+[[cmdratelimit]]*cmdratelimit* [_option_]...::
+This directive enables response rate limiting for command packets. It is
+similar to the <<ratelimit,*ratelimit*>> directive, except responses to
+localhost are never limited and the default interval is -4 (16 packets per
+second).
++
+An example of the use of the directive is:
++
+----
+cmdratelimit interval 2
+----
+
+=== Real-time clock (RTC)
+
+[[hwclockfile]]*hwclockfile* _file_::
+The *hwclockfile* directive sets the location of the adjtime file which is
+used by the *hwclock* program on Linux. *chronyd* parses the file to find out
+if the RTC keeps local time or UTC. It overrides the <<rtconutc,*rtconutc*>>
+directive.
++
+The compiled-in default value is '_@DEFAULT_HWCLOCK_FILE@_'.
++
+An example of the directive is:
++
+----
+hwclockfile /etc/adjtime
+----
+
+[[rtcautotrim]]*rtcautotrim* _threshold_::
+The *rtcautotrim* directive is used to keep the RTC close to the system clock
+automatically. When the system clock is synchronised and the estimated error
+between the two clocks is larger than the specified threshold, *chronyd* will
+trim the RTC as if the <<chronyc.adoc#trimrtc,*trimrtc*>> command in *chronyc*
+was issued. The trimming operation is accurate to only about 1 second, which is
+the minimum effective threshold.
++
+This directive is effective only with the <<rtcfile,*rtcfile*>> directive.
++
+An example of the use of this directive is:
++
+----
+rtcautotrim 30
+----
++
+This would set the threshold error to 30 seconds.
+
+[[rtcdevice]]*rtcdevice* _device_::
+The *rtcdevice* directive sets the path to the device file for accessing the
+RTC. The default path is _@DEFAULT_RTC_DEVICE@_.
+
+[[rtcfile]]*rtcfile* _file_::
+The *rtcfile* directive defines the name of the file in which *chronyd* can
+save parameters associated with tracking the accuracy of the RTC.
++
+An example of the directive is:
++
+----
+rtcfile @CHRONYVARDIR@/rtc
+----
++
+*chronyd* saves information in this file when it exits and when the *writertc*
+command is issued in *chronyc*. The information saved is the RTC's error at
+some epoch, that epoch (in seconds since January 1 1970), and the rate at which
+the RTC gains or loses time.
++
+So far, the support for real-time clocks is limited; their code is even more
+system-specific than the rest of the software. You can only use the RTC
+facilities (the <<rtcfile,*rtcfile*>> directive and the *-s* command-line
+option to *chronyd*) if the following three conditions apply:
++
+. You are running Linux.
+. The kernel is compiled with extended real-time clock support (i.e. the
+ _/dev/rtc_ device is capable of doing useful things).
+. You do not have other applications that need to make use of _/dev/rtc_ at all.
+
+[[rtconutc]]*rtconutc*::
+*chronyd* assumes by default that the RTC keeps local time (including any
+daylight saving changes). This is convenient on PCs running Linux which are
+dual-booted with Windows.
++
+If you keep the RTC on local time and your computer is off when daylight saving
+(summer time) starts or ends, the computer's system time will be one hour in
+error when you next boot and start chronyd.
++
+An alternative is for the RTC to keep Universal Coordinated Time (UTC). This
+does not suffer from the 1 hour problem when daylight saving starts or ends.
++
+If the *rtconutc* directive appears, it means the RTC is required to keep UTC.
+The directive takes no arguments. It is equivalent to specifying the *-u*
+switch to the Linux *hwclock* program.
++
+Note that this setting is overridden by the <<hwclockfile,*hwclockfile*>> file
+and is not relevant for the <<rtcsync,*rtcsync*>> directive.
+
+[[rtcsync]]*rtcsync*::
+The *rtcsync* directive enables a mode where the system time is periodically
+copied to the RTC and *chronyd* does not try to track its drift. This directive
+cannot be used with the <<rtcfile,*rtcfile*>> directive.
++
+On Linux, the RTC copy is performed by the kernel every 11 minutes.
++
+On macOS, <<chronyd,*chronyd*>> will perform the RTC copy every 60 minutes
+when the system clock is in a synchronised state.
++
+On other systems this directive does nothing.
+
+=== Logging
+
+[[log]]*log* [_option_]...::
+The *log* directive indicates that certain information is to be logged.
+The log files are written to the directory specified by the <<logdir,*logdir*>>
+directive. A banner is periodically written to the files to indicate the
+meanings of the columns.
++
+*rawmeasurements*:::
+This option logs the raw NTP measurements and related information to a file
+called _measurements.log_. An entry is made for each packet received from the
+source. This can be useful when debugging a problem. An example line (which
+actually appears as a single line in the file) from the log file is shown
+below.
++
+----
+2016-11-09 05:40:50 203.0.113.15 N 2 111 111 1111 10 10 1.0 \
+ -4.966e-03 2.296e-01 1.577e-05 1.615e-01 7.446e-03 CB00717B 4B D K
+----
++
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
++
+. Date [2015-10-13]
+. Hour:Minute:Second. Note that the date-time pair is expressed in UTC, not the
+ local time zone. [05:40:50]
+. IP address of server or peer from which measurement came [203.0.113.15]
+. Leap status (_N_ means normal, _+_ means that the last minute of the current
+ month has 61 seconds, _-_ means that the last minute of the month has 59
+ seconds, _?_ means the remote computer is not currently synchronised.) [N]
+. Stratum of remote computer. [2]
+. RFC 5905 tests 1 through 3 (1=pass, 0=fail) [111]
+. RFC 5905 tests 5 through 7 (1=pass, 0=fail) [111]
+. Results of the *maxdelay*, *maxdelayratio*, and *maxdelaydevratio* (or
+ *maxdelayquant*) tests, and a test for synchronisation loop (1=pass,
+ 0=fail). The first test from these four also checks the server precision,
+ response time, and whether an interleaved response is acceptable for
+ synchronisation. [1111]
+. Local poll [10]
+. Remote poll [10]
+. '`Score`' (an internal score within each polling level used to decide when to
+ increase or decrease the polling level. This is adjusted based on number of
+ measurements currently being used for the regression algorithm). [1.0]
+. The estimated local clock error (_theta_ in RFC 5905). Positive indicates
+ that the local clock is slow of the remote source. [-4.966e-03]
+. The peer delay (_delta_ in RFC 5905). [2.296e-01]
+. The peer dispersion (_epsilon_ in RFC 5905). [1.577e-05]
+. The root delay (_DELTA_ in RFC 5905). [1.615e-01]
+. The root dispersion (_EPSILON_ in RFC 5905). [7.446e-03]
+. Reference ID of the server's source as a hexadecimal number. [CB00717B]
+. NTP mode of the received packet (_1_=active peer, _2_=passive peer,
+ _4_=server, _B_=basic, _I_=interleaved). [4B]
+. Source of the local transmit timestamp
+ (_D_=daemon, _K_=kernel, _H_=hardware). [D]
+. Source of the local receive timestamp
+ (_D_=daemon, _K_=kernel, _H_=hardware). [K]
++
+*measurements*:::
+This option is identical to the *rawmeasurements* option, except it logs only
+valid measurements from synchronised sources, i.e. measurements which passed
+the RFC 5905 tests 1 through 7. This can be useful for producing graphs of the
+source's performance.
++
+*statistics*:::
+This option logs information about the regression processing to a file called
+_statistics.log_. An example line (which actually appears as a single line in
+the file) from the log file is shown below.
++
+----
+2016-08-10 05:40:50 203.0.113.15 6.261e-03 -3.247e-03 \
+ 2.220e-03 1.874e-06 1.080e-06 7.8e-02 16 0 8 0.00
+----
++
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
++
+. Date [2015-07-22]
+. Hour:Minute:Second. Note that the date-time pair is expressed in
+ UTC, not the local time zone. [05:40:50]
+. IP address of server or peer from which measurement comes [203.0.113.15]
+. The estimated standard deviation of the measurements from the source (in
+ seconds). [6.261e-03]
+. The estimated offset of the source (in seconds, positive means the local
+ clock is estimated to be fast, in this case). [-3.247e-03]
+. The estimated standard deviation of the offset estimate (in seconds).
+ [2.220e-03]
+. The estimated rate at which the local clock is gaining or losing time
+ relative to the source (in seconds per second, positive means the local clock
+ is gaining). This is relative to the compensation currently being applied to
+ the local clock, _not_ to the local clock without any compensation.
+ [1.874e-06]
+. The estimated error in the rate value (in seconds per second). [1.080e-06].
+. The ratio of |old_rate - new_rate| / old_rate_error. Large values
+ indicate the statistics are not modelling the source very well. [7.8e-02]
+. The number of measurements currently being used for the regression
+ algorithm. [16]
+. The new starting index (the oldest sample has index 0; this is the method
+ used to prune old samples when it no longer looks like the measurements fit a
+ linear model). [0, i.e. no samples discarded this time]
+. The number of runs. The number of runs of regression residuals with the same
+ sign is computed. If this is too small it indicates that the measurements are
+ no longer represented well by a linear model and that some older samples need
+ to be discarded. The number of runs for the data that is being retained is
+ tabulated. Values of approximately half the number of samples are expected.
+ [8]
+. The estimated or configured asymmetry of network jitter on the path to the
+ source which was used to correct the measured offsets. The asymmetry can be
+ between -0.5 and +0.5. A negative value means the delay of packets sent to
+ the source is more variable than the delay of packets sent from the source
+ back. [0.00, i.e. no correction for asymmetry]
++
+*selection*:::
+This option logs information about selection of sources for synchronisation to
+a file called _selection.log_. Note that the rate of entries written to this
+file grows quadratically with the number of specified sources (each measurement
+triggers the selection for all sources). An example line (which actually
+appears as a single line in the file) from the log file is shown below.
++
+----
+2022-05-01 02:01:20 203.0.113.15 * ----- 377 1.00 \
+ 4.228e+01 -1.575e-04 1.239e-04
+----
++
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
++
+. Date [2022-05-01]
+. Hour:Minute:Second. Note that the date-time pair is expressed in
+ UTC, not the local time zone. [02:01:20]
+. IP address or reference ID of the source. [203.0.113.15]
+. State of the source indicated with one of the following symbols. [*]
+{blank}::::
+Not considered selectable for synchronisation:
+* _N_ - has the *noselect* option.
+* _s_ - is not synchronised.
+* _M_ - does not have enough measurements.
+* _d_ - has a root distance larger than the maximum distance (configured by the
+ <<maxdistance,*maxdistance*>> directive).
+* _~_ - has a jitter larger than the maximum jitter (configured by the
+ <<maxjitter,*maxjitter*>> directive).
+* _w_ - waits for other sources to get out of the _M_ state.
+* _S_ - has older measurements than other sources.
+* _O_ - has a stratum equal or larger than the orphan stratum (configured by
+ the <<local,*local*>> directive).
+* _T_ - does not fully agree with sources that have the *trust* option.
+* _x_ - does not agree with other sources (falseticker).
+{blank}::::
+Considered selectable for synchronisation, but not currently used:
+* _W_ - waits for other sources to be selectable (required by the
+ <<minsources,*minsources*>> directive, or the *require* option of
+ another source).
+* _P_ - another selectable source is preferred due to the *prefer* option.
+* _U_ - waits for a new measurement (after selecting a different best source).
+* _D_ - has, or recently had, a root distance which is too large to be combined
+ with other sources (configured by the <<combinelimit,*combinelimit*>>
+ directive).
+{blank}::::
+Used for synchronisation of the local clock:
+* _+_ - combined with the best source.
+* _*_ - selected as the best source to update the reference data (e.g. root
+ delay, root dispersion).
+. Current effective selection options of the source. which can be different
+ from the configured options due to the authentication selection mode
+ (configured by the <<authselectmode,*authselectmode*>> directive). [-----]
+* _N_ indicates the *noselect* option.
+* _P_ indicates the *prefer* option.
+* _T_ indicates the *trust* option.
+* _R_ indicates the *require* option.
+. Reachability register printed as an octal number. The register has 8 bits and
+ is updated on every received or missed packet from the source. A value of 377
+ indicates that a valid reply was received for all from the last eight
+ transmissions. [377]
+. Current score against the source in the _*_ state. The scoring system avoids
+ frequent reselection when multiple sources have a similar root distance. A
+ value larger than 1 indicates this source was better than the _*_ source in
+ recent selections. If the score reaches 10, the best source will be reselected
+ and the scores will be reset to 1. [1.00]
+. Interval since the last measurement of the source in seconds. [4.228e+01]
+. Lower endpoint of the interval which was expected to contain the true offset
+ of the local clock determined by the root distance of the source. [-1.575e-04]
+. Upper endpoint of the interval which was expected to contain the true offset
+ of the local clock determined by the root distance of the source. [1.239e-04]
++
+*tracking*:::
+This option logs changes to the estimate of the system's gain or loss rate, and
+any slews made, to a file called _tracking.log_. An example line (which
+actually appears as a single line in the file) from the log file is shown
+below.
++
+----
+2017-08-22 13:22:36 203.0.113.15 2 -3.541 0.075 -8.621e-06 N \
+ 2 2.940e-03 -2.084e-04 1.534e-02 3.472e-04 8.304e-03
+----
++
+The columns are as follows (the quantities in square brackets are the
+values from the example line above) :
++
+. Date [2017-08-22]
+. Hour:Minute:Second. Note that the date-time pair is expressed in UTC, not the
+ local time zone. [13:22:36]
+. The IP address of the server or peer to which the local system is synchronised.
+ [203.0.113.15]
+. The stratum of the local system. [2]
+. The local system frequency (in ppm, positive means the local system runs fast
+ of UTC). [-3.541]
+. The error bounds on the frequency (in ppm). [0.075]
+. The estimated local offset at the epoch, which is normally corrected by
+ slewing the local clock (in seconds, positive indicates the clock is fast of
+ UTC). [-8.621e-06]
+. Leap status (_N_ means normal, _+_ means that the last minute of this month
+ has 61 seconds, _-_ means that the last minute of the month has 59 seconds,
+ _?_ means the clock is not currently synchronised.) [N]
+. The number of combined sources. [2]
+. The estimated standard deviation of the combined offset (in seconds).
+ [2.940e-03]
+. The remaining offset correction from the previous update (in seconds,
+ positive means the system clock is slow of UTC). [-2.084e-04]
+. The total of the network path delays to the reference clock to which
+ the local clock is ultimately synchronised (in seconds). [1.534e-02]
+. The total dispersion accumulated through all the servers back to the
+ reference clock to which the local clock is ultimately synchronised
+ (in seconds). [3.472e-04]
+. The maximum estimated error of the system clock in the interval since the
+ previous update (in seconds). It includes the offset, remaining offset
+ correction, root delay, and dispersion from the previous update with the
+ dispersion which accumulated in the interval. [8.304e-03]
++
+*rtc*:::
+This option logs information about the system's real-time clock. An example
+line (which actually appears as a single line in the file) from the _rtc.log_
+file is shown below.
++
+----
+2015-07-22 05:40:50 -0.037360 1 -0.037434\
+ -37.948 12 5 120
+----
++
+The columns are as follows (the quantities in square brackets are the
+values from the example line above):
++
+. Date [2015-07-22]
+. Hour:Minute:Second. Note that the date-time pair is expressed in UTC, not the
+ local time zone. [05:40:50]
+. The measured offset between the RTC and the system clock in seconds.
+ Positive indicates that the RTC is fast of the system time [-0.037360].
+. Flag indicating whether the regression has produced valid coefficients.
+ (1 for yes, 0 for no). [1]
+. Offset at the current time predicted by the regression process. A large
+ difference between this value and the measured offset tends to indicate that
+ the measurement is an outlier with a serious measurement error. [-0.037434]
+. The rate at which the RTC is losing or gaining time relative to the system
+ clock. In ppm, with positive indicating that the RTC is gaining time.
+ [-37.948]
+. The number of measurements used in the regression. [12]
+. The number of runs of regression residuals of the same sign. Low values
+ indicate that a straight line is no longer a good model of the measured data
+ and that older measurements should be discarded. [5]
+. The measurement interval used prior to the measurement being made (in
+ seconds). [120]
++
+*refclocks*:::
+This option logs the raw and filtered reference clock measurements to a file
+called _refclocks.log_. An example line (which actually appears as a single
+line in the file) from the log file is shown below.
++
+----
+2009-11-30 14:33:27.000000 PPS2 7 N 1 4.900000e-07 -6.741777e-07 1.000e-06
+----
++
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
++
+. Date [2009-11-30]
+. Hour:Minute:Second.Microsecond. Note that the date-time pair is expressed in
+ UTC, not the local time zone. [14:33:27.000000]
+. Reference ID of the reference clock from which the measurement came. [PPS2]
+. Sequence number of driver poll within one polling interval for raw samples,
+ or _-_ for filtered samples. [7]
+. Leap status (_N_ means normal, _+_ means that the last minute of the current
+ month has 61 seconds, _-_ means that the last minute of the month has 59
+ seconds). [N]
+. Flag indicating whether the sample comes from PPS source. (1 for yes,
+ 0 for no, or _-_ for filtered sample). [1]
+. Local clock error measured by reference clock driver, or _-_ for filtered sample.
+ [4.900000e-07]
+. Local clock error with applied corrections. Positive indicates that the local
+ clock is slow. [-6.741777e-07]
+. Assumed dispersion of the sample. [1.000e-06]
++
+*tempcomp*:::
+This option logs the temperature measurements and system rate compensations to
+a file called _tempcomp.log_. An example line (which actually appears as a
+single line in the file) from the log file is shown below.
++
+----
+2015-04-19 10:39:48 2.8000e+04 3.6600e-01
+----
++
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
++
+. Date [2015-04-19]
+. Hour:Minute:Second. Note that the date-time pair is expressed in UTC, not the
+ local time zone. [10:39:48]
+. Temperature read from the sensor. [2.8000e+04]
+. Applied compensation in ppm, positive means the system clock is running
+ faster than it would be without the compensation. [3.6600e-01]
++
+{blank}::
+An example of the directive is:
++
+----
+log measurements statistics tracking
+----
+
+[[logbanner]]*logbanner* _entries_::
+A banner is periodically written to the log files enabled by the <<log,*log*>>
+directive to indicate the meanings of the columns.
++
+The *logbanner* directive specifies after how many entries in the log file
+should be the banner written. The default is 32, and 0 can be used to disable
+it entirely.
+
+[[logchange]]*logchange* _threshold_::
+This directive sets the threshold for the adjustment of the system clock that
+will generate a syslog message. Clock errors detected via NTP packets,
+reference clocks, or timestamps entered via the
+<<chronyc.adoc#settime,*settime*>> command of *chronyc* are logged.
++
+By default, the threshold is 1 second.
++
+An example of the use is:
++
+----
+logchange 0.1
+----
++
+which would cause a syslog message to be generated if a system clock error of over
+0.1 seconds starts to be compensated.
+
+[[logdir]]*logdir* _directory_::
+This directive specifies the directory for writing log files enabled by the
+*log* directive. If the directory does not exist, it will be created
+automatically.
++
+An example of the use of this directive is:
++
+----
+logdir /var/log/chrony
+----
+
+[[mailonchange]]*mailonchange* _email_ _threshold_::
+This directive defines an email address to which mail should be sent if
+*chronyd* applies a correction exceeding a particular threshold to the system
+clock.
++
+An example of the use of this directive is:
++
+----
+mailonchange root@localhost 0.5
+----
++
+This would send a mail message to root if a change of more than 0.5 seconds
+were applied to the system clock.
++
+This directive cannot be used when a system call filter is enabled by the *-F*
+option as the *chronyd* process will not be allowed to fork and execute the
+sendmail binary.
+
+=== Miscellaneous
+
+[[confdir]]*confdir* _directory_...::
+The *confdir* directive includes configuration files with the _.conf_ suffix
+from a directory. The files are included in the lexicographical order of the
+file names.
++
+Multiple directories (up to 10) can be specified with a single *confdir*
+directive. In this case, if multiple directories contain a file with the same
+name, only the first file in the order of the specified directories will be
+included. This enables a fragmented configuration where existing fragments can
+be replaced by adding files to a different directory.
++
+This directive can be used multiple times.
++
+An example of the directive is:
++
+----
+confdir @SYSCONFDIR@/chrony.d
+----
+
+[[sourcedir]]*sourcedir* _directory_...::
+The *sourcedir* directive is identical to the *confdir* directive, except the
+configuration files have the _.sources_ suffix, they can only specify NTP
+sources (i.e. the *server*, *pool*, and *peer* directives), they are expected
+to have all lines terminated by the newline character, and they can be
+reloaded by the <<chronyc.adoc#reload,*reload sources*>> command in
+*chronyc*. It is particularly useful with dynamic sources like NTP servers
+received from a DHCP server, which can be written to a file specific to the
+network interface by a networking script.
++
+This directive can be used multiple times.
++
+An example of the directive is:
++
+----
+sourcedir /var/run/chrony-dhcp
+----
+
+[[include]]*include* _pattern_::
+The *include* directive includes a configuration file, or multiple configuration
+files if a wildcard pattern is specified. Unlike with the *confdir* directive,
+the full name of the files needs to be specified and at least one file is
+required to exist.
++
+This directive can be used multiple times.
++
+An example of the directive is:
++
+----
+include @SYSCONFDIR@/chrony.d/*.conf
+----
+
+[[hwtimestamp]]*hwtimestamp* _interface_ [_option_]...::
+This directive enables hardware timestamping of NTP packets sent to and
+received from the specified network interface. The network interface controller
+(NIC) uses its own clock to accurately timestamp the actual transmissions and
+receptions, avoiding processing and queueing delays in the kernel, network
+driver, and hardware. This can significantly improve the accuracy of the
+timestamps and the measured offset, which is used for synchronisation of the
+system clock. In order to get the best results, both sides receiving and
+sending NTP packets (i.e. server and client, or two peers) need to use HW
+timestamping. If the server or peer supports the interleaved mode, it needs to
+be enabled by the *xleave* option in the <<server,*server*>> or the
+<<peer,*peer*>> directive.
++
+This directive is supported on Linux 3.19 and newer. The NIC must support HW
+timestamping, which can be verified with the *ethtool -T* command. The list of
+capabilities should include _hardware-raw-clock_, _hardware-transmit_, and
+_hardware-receive_. The receive filter _all_, or _ntp_, is necessary for
+timestamping of received NTP packets. Timestamping of packets received on
+bridged and bonded interfaces is supported on Linux 4.13 and newer. If HW
+timestamping does not work for received packets, *chronyd* will use kernel
+receive timestamps instead. Transmit-only HW timestamping can still be useful
+to improve stability of the synchronisation.
++
+*chronyd* does not synchronise the NIC clock. It assumes the clock is running
+free. Multiple instances of *chronyd* can use the same interface with enabled
+HW timestamping. Applications which need HW timestamping with a synchronised
+clock (e.g. a PTP daemon) should use a virtual clock running on top of the
+physical clock created by writing to _/sys/class/ptp/ptpX/n_vclocks_. This
+feature is available on Linux 5.14 and newer.
++
+If the kernel supports software timestamping, it will be enabled for all
+interfaces automatically.
++
+The source of timestamps (i.e. hardware, kernel, or daemon) is indicated on the
+client side in the _measurements.log_ file (if enabled by the <<log,*log*>>
+directive) and the <<chronyc.adoc#ntpdata,*ntpdata*>> report. On the server
+side, the number of served timestamps from each source is provided in the
+<<chronyc.adoc#serverstats,*serverstats*>> report.
++
+This directive can be used multiple times to enable HW timestamping on multiple
+interfaces. If the specified interface is _*_, *chronyd* will try to enable HW
+timestamping on all available interfaces.
++
+The *hwtimestamp* directive has the following options:
++
+*minpoll* _poll_:::
+This option specifies the minimum interval between readings of the NIC clock.
+It's defined as a power of 2. It should correspond to the minimum polling
+interval of all NTP sources and the minimum expected polling interval of NTP
+clients. The default value is 0 (1 second), the minimum value is -6 (1/64th
+of a second), and the maximum value is 20 (about 12 days).
+*maxpoll* _poll_:::
+This option specifies the maximum interval between readings of the NIC clock,
+as a power of 2. The default value is *minpoll* + 1, i.e. 1 (2 seconds) with
+the default *minpoll* of 0. The minimum and maximum values are the same as with
+the *minpoll* option.
+*minsamples* _samples_:::
+This option specifies the minimum number of readings kept for tracking of the
+NIC clock. The default value is 2.
+*maxsamples* _samples_:::
+This option specifies the maximum number of readings kept for tracking of the
+NIC clock. The default value is 16.
+*precision* _precision_:::
+This option specifies the assumed precision of reading of the NIC clock. The
+default value is 100e-9 (100 nanoseconds).
+*txcomp* _compensation_:::
+This option specifies the difference in seconds between the actual transmission
+time at the physical layer and the reported transmit timestamp. This value will
+be added to transmit timestamps obtained from the NIC. The default value is 0.
+*rxcomp* _compensation_:::
+This option specifies the difference in seconds between the reported receive
+timestamp and the actual reception time at the physical layer. This value will
+be subtracted from receive timestamps obtained from the NIC. The default value
+is 0.
+*nocrossts*:::
+Some hardware can precisely cross timestamp the NIC clock with the system
+clock. This option disables the use of the cross timestamping.
+*rxfilter* _filter_:::
+This option selects the receive timestamping filter. The _filter_ can be one of
+the following:
+_all_::::
+Enables timestamping of all received packets.
+_ntp_::::
+Enables timestamping of received NTP packets.
+_ptp_::::
+Enables timestamping of received PTP packets.
+_none_::::
+Disables timestamping of received packets.
+{blank}:::
+The most specific filter for timestamping of NTP packets supported by the NIC
+is selected by default. Some NICs can timestamp PTP packets only. By default,
+they will be configured with the _none_ filter and expected to provide hardware
+timestamps for transmitted packets only. Timestamping of PTP packets is useful
+with NTP-over-PTP enabled by the <<chrony.conf.adoc#ptpport,*ptpport*>>
+directive, or when another application is receiving PTP packets on the
+interface. Forcing timestamping of all packets with the _all_ filter could be
+useful if the NIC supported both the _all_ and _ntp_ filters, and it should
+timestamp both NTP and PTP packets, or NTP packets on a different UDP port.
+{blank}::
++
+Examples of the directive are:
++
+----
+hwtimestamp eth0
+hwtimestamp eth1 txcomp 300e-9 rxcomp 645e-9
+hwtimestamp *
+----
+
+[[hwtstimeout]]*hwtstimeout* _timeout_::
+If hardware timestamping is used with a close NTP server, or the NIC or its
+driver is slow in providing the transmit timestamp of NTP requests, a response
+from the server can be received before the transmit timestamp of the request.
+To avoid calculating the offset with a less accurate transmit timestamp,
+*chronyd* can save the response for later processing and wait for the hardware
+transmit timestamp. There is no guarantee that the timestamp will be provided
+(NICs typically have a limited rate of transmit timestamping). This directive
+configures how long should *chronyd* wait for the timestamp after receiving a
+valid response from the server. If a second valid response is received from the
+server while waiting for the timestamp, they will be both processed
+immediately.
++
+The default value is 0.001 seconds, which should be sufficient with most
+hardware. If you frequently see kernel transmit timestamps in the
+_measurements.log_ file or <<chronyc.adoc#ntpdata,*ntpdata*>> report, and it is
+not a server handling a high rate of requests in the interleaved mode on the
+same interface (which would compete with timestamping of the server's own
+requests), increasing the timeout to 0.01 or possibly even longer might help.
+Note that the maximum timeout is limited by the NTP polling interval.
+
+[[keyfile]]*keyfile* _file_::
+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:
++
+----
+keyfile @SYSCONFDIR@/chrony.keys
+----
++
+The argument is simply the name of the file containing the ID-key pairs. The
+format of the file is shown below:
++
+----
+10 tulip
+11 hyacinth
+20 MD5 ASCII:crocus
+25 SHA1 HEX:933F62BE1D604E68A81B557F18CFA200483F5B70
+30 AES128 HEX:7EA62AE64D190114D46D5A082F948EC1
+31 AES256 HEX:37DDCBC67BB902BCB8E995977FAB4D2B5642F5B32EBCEEE421921D97E5CBFE39
+ ...
+----
++
+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, GnuTLS, NSS, or LibTomCrypt), the following functions are available: *MD5*,
+*SHA1*, *SHA256*, *SHA384*, *SHA512*. Depending on which library and version is
+*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 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).
++
+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
+SHA1 keys.
++
+For security reasons, the file should be readable only by root and the user
+under which *chronyd* is normally running (to allow *chronyd* to re-read the
+file when the <<chronyc.adoc#rekey,*rekey*>> command is issued by *chronyc*).
+
+[[lock_all]]*lock_all*::
+The *lock_all* directive will lock the *chronyd* process into RAM so that it
+will never be paged out. This can result in lower and more consistent latency.
+The directive is supported on Linux, FreeBSD, NetBSD, and illumos.
+
+[[pidfile]]*pidfile* _file_::
+Unless *chronyd* is started with the *-Q* option, it writes its process ID
+(PID) to a file, and checks this file on startup to see if another *chronyd*
+might already be running on the system. By default, the file used is
+_@DEFAULT_PID_FILE@_. The *pidfile* directive allows the name to be changed,
+e.g.:
++
+----
+pidfile /run/chronyd.pid
+----
+
+[[ptpport]]*ptpport* _port_::
+The *ptpport* directive enables *chronyd* to send and receive NTP messages
+contained in PTP event messages (NTP-over-PTP) to enable hardware timestamping
+on NICs which cannot timestamp NTP packets, but can timestamp unicast PTP
+packets, and also use corrections provided by PTP one-step end-to-end
+transparent clocks in network switches and routers. The port recognized by the
+NICs and PTP transparent clocks is 319 (PTP event port). The default value is 0
+(disabled).
++
+The NTP-over-PTP support is experimental. The protocol and configuration can
+change in future. It should be used only in local networks.
++
+The PTP port will be open even if *chronyd* is not configured to operate as a
+server or client. The directive does not change the default protocol of
+specified NTP sources. Each NTP source that should use NTP-over-PTP needs to
+be specified with the *port* option set to the PTP port. To actually enable
+hardware timestamping on NICs which can timestamp PTP packets only, the
+*rxfilter* option of the *hwtimestamp* directive needs to be set to _ptp_. The
+extension field _F324_ needs to be enabled to use the corrections provided by
+the PTP transparent clocks.
++
+An example of client configuration is:
++
+----
+server ntp1.example.net minpoll 0 maxpoll 0 xleave port 319 extfield F324
+hwtimestamp * rxfilter ptp
+ptpport 319
+----
+
+[[sched_priority]]*sched_priority* _priority_::
+On Linux, FreeBSD, NetBSD, and illumos, the *sched_priority* directive will
+select the SCHED_FIFO real-time scheduler at the specified priority (which must
+be between 0 and 100). On macOS, this option must have either a value of 0 (the
+default) to disable the thread time constraint policy or 1 for the policy to be
+enabled.
++
+On systems other than macOS, this directive uses the *pthread_setschedparam()*
+system call to instruct the kernel to use the SCHED_FIFO first-in, first-out
+real-time scheduling policy for *chronyd* with the specified priority. This
+means that whenever *chronyd* is ready to run it will run, interrupting
+whatever else is running unless it is a higher priority real-time process. This
+should not impact performance as *chronyd* resource requirements are modest,
+but it should result in lower and more consistent latency since *chronyd* will
+not need to wait for the scheduler to get around to running it. You should not
+use this unless you really need it. The *pthread_setschedparam(3)* man page has
+more details.
++
+On macOS, this directive uses the *thread_policy_set()* kernel call to
+specify real-time scheduling. As noted above, you should not use this directive
+unless you really need it.
+
+[[user]]*user* _user_::
+The *user* directive sets the name of the system user to which *chronyd* will
+switch after start in order to drop root privileges.
++
+On Linux, *chronyd* needs to be compiled with support for the *libcap* library.
+On macOS, FreeBSD, NetBSD and illumos *chronyd* forks into two processes.
+The child process retains root privileges, but can only perform a very limited
+range of privileged system calls on behalf of the parent.
++
+The compiled-in default value is _@DEFAULT_USER@_.
+
+[[examples]]
+== EXAMPLES
+
+=== NTP client with permanent connection to NTP servers
+
+This section shows how to configure *chronyd* for computers that are connected
+to the Internet (or to any network containing true NTP servers which ultimately
+derive their time from a reference clock) permanently or most of the time.
+
+To operate in this mode, you will need to know the names of the NTP servers
+you want to use. You might be able to find names of suitable servers by one of
+the following methods:
+
+* Your institution might already operate servers on its network.
+ Contact your system administrator to find out.
+* Your ISP probably has one or more NTP servers available for its
+ customers.
+* Somewhere under the NTP homepage there is a list of public
+ stratum 1 and stratum 2 servers. You should find one or more servers that are
+ near to you. Check that their access policy allows you to use their
+ facilities.
+* Use public servers from the https://www.pool.ntp.org/[pool.ntp.org] project.
+
+Assuming that your NTP servers are called _ntp1.example.net_, _ntp2.example.net_
+and _ntp3.example.net_, your _chrony.conf_ file could contain as a minimum:
+
+----
+server ntp1.example.net
+server ntp2.example.net
+server ntp3.example.net
+----
+
+However, you will probably want to include some of the other directives. The
+<<driftfile,*driftfile*>>, <<makestep,*makestep*>> and <<rtcsync,*rtcsync*>>
+might be particularly useful. Also, the *iburst* option of the
+<<server,*server*>> directive is useful to speed up the initial
+synchronisation. The smallest useful configuration file would look something
+like:
+
+----
+server ntp1.example.net iburst
+server ntp2.example.net iburst
+server ntp3.example.net iburst
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+----
+
+When using a pool of NTP servers (one name is used for multiple servers which
+might change over time), it is better to specify them with the <<pool,*pool*>>
+directive instead of multiple *server* directives. The configuration file could
+in this case look like:
+
+----
+pool pool.ntp.org iburst
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+----
+
+If the servers (or pool) support the Network Time Security (NTS)
+authentication mechanism and *chronyd* is compiled with NTS support, the *nts*
+option will enable a secure synchronisation to the servers. The configuration
+file could look like:
+
+----
+server ntp1.example.net iburst nts
+server ntp2.example.net iburst nts
+server ntp3.example.net iburst nts
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+----
+
+=== NTP client with infrequent connection to NTP servers
+
+This section shows how to configure *chronyd* for computers that have
+occasional connections to NTP servers. In this case, you will need some
+additional configuration to tell *chronyd* when the connection goes up and
+down. This saves the program from continuously trying to poll the servers when
+they are inaccessible.
+
+Again, assuming that your NTP servers are called _ntp1.example.net_,
+_ntp2.example.net_ and _ntp3.example.net_, your _chrony.conf_ file would now
+contain:
+
+----
+server ntp1.example.net offline
+server ntp2.example.net offline
+server ntp3.example.net offline
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+----
+
+The *offline* keyword indicates that the servers start in an offline state, and
+that they should not be contacted until *chronyd* receives notification from
+*chronyc* that the link to the Internet is present. To tell *chronyd* when to
+start and finish sampling the servers, the <<chronyc.adoc#online,*online*>> and
+<<chronyc.adoc#offline,*offline*>> commands of *chronyc* need to be used.
+
+To give an example of their use, assuming that *pppd* is the program being
+used to connect to the Internet and that *chronyc* has been installed at
+_@BINDIR@/chronyc_, the script _/etc/ppp/ip-up_ would include:
+
+----
+@BINDIR@/chronyc online
+----
+
+and the script _/etc/ppp/ip-down_ would include:
+
+----
+@BINDIR@/chronyc offline
+----
+
+*chronyd*'s polling of the servers would now only occur whilst the machine is
+actually connected to the Internet.
+
+=== Isolated networks
+
+This section shows how to configure *chronyd* for computers that never have
+network connectivity to any computer which ultimately derives its time from a
+reference clock.
+
+In this situation, one computer is selected to be the primary timeserver. The
+other computers are either direct clients of the server, or clients of clients.
+
+The <<local,*local*>> directive enables a local reference mode, which allows
+*chronyd* to appear synchronised even when it is not.
+
+The rate value in the server's drift file needs to be set to the average rate
+at which the server gains or loses time. *chronyd* includes support for this,
+in the form of the <<manual,*manual*>> directive and the
+<<chronyc.adoc#settime,*settime*>> command in the *chronyc* program.
+
+If the server is rebooted, *chronyd* can re-read the drift rate from the drift
+file. However, the server has no accurate estimate of the current time. To get
+around this, the system can be configured so that the server can initially set
+itself to a '`majority-vote`' of selected clients' times; this allows the
+clients to '`flywheel`' the server while it is rebooting.
+
+The <<smoothtime,*smoothtime*>> directive is useful when the clocks of the
+clients need to stay close together when the local time is adjusted by the
+<<chronyc.adoc#settime,*settime*>> command. The smoothing process needs to be
+activated by the <<chronyc.adoc#smoothtime,*smoothtime activate*>> command when
+the local time is ready to be served. After that point, any adjustments will be
+smoothed out.
+
+A typical configuration file for the server (called _ntp.local_) might be
+(assuming the clients and the server are in the _192.168.165.x_ subnet):
+
+----
+initstepslew 1 client1 client3 client6
+driftfile @CHRONYVARDIR@/drift
+local stratum 8
+manual
+allow 192.168.165.0/24
+smoothtime 400 0.01
+rtcsync
+----
+
+For the clients that have to resynchronise the server when it restarts,
+the configuration file might be:
+
+----
+server ntp.local iburst
+driftfile @CHRONYVARDIR@/drift
+allow 192.168.165.0/24
+makestep 1.0 3
+rtcsync
+----
+
+The rest of the clients would be the same, except that the *allow* directive is
+not required.
+
+If there is no suitable computer to be designated as the primary server, or
+there is a requirement to keep the clients synchronised even when it fails, the
+*orphan* option of the *local* directive enables a special mode where the
+server is selected from multiple computers automatically. They all need to use
+the same *local* configuration and poll one another. The server with the
+smallest reference ID (which is based on its IP address) will take the role of
+the primary server and others will be synchronised to it. When it fails, the
+server with the second smallest reference ID will take over and so on.
+
+A configuration file for the first server might be (assuming there are three
+servers called _ntp1.local_, _ntp2.local_, and _ntp3.local_):
+
+----
+initstepslew 1 ntp2.local ntp3.local
+server ntp2.local
+server ntp3.local
+driftfile @CHRONYVARDIR@/drift
+local stratum 8 orphan
+manual
+allow 192.168.165.0/24
+rtcsync
+----
+
+The other servers would be the same, except the hostnames in the *initstepslew*
+and *server* directives would be modified to specify the other servers. Their
+clients might be configured to poll all three servers.
+
+=== RTC tracking
+
+This section considers a computer which has occasional connections to the
+Internet and is turned off between '`sessions`'. In this case, *chronyd* relies
+on the computer's RTC to maintain the time between the periods when it is
+powered up. It assumes that Linux is run exclusively on the computer. Dual-boot
+systems might work; it depends what (if anything) the other system does to the
+RTC. On 2.6 and later kernels, if your motherboard has a HPET, you will need to
+enable the *HPET_EMULATE_RTC* option in your kernel configuration. Otherwise,
+*chronyd* will not be able to interact with the RTC device and will give up
+using it.
+
+When the computer is connected to the Internet, *chronyd* has access to
+external NTP servers which it makes measurements from. These measurements are
+saved, and straight-line fits are performed on them to provide an estimate of
+the computer's time error and rate of gaining or losing time.
+
+When the computer is taken offline from the Internet, the best estimate of the
+gain or loss rate is used to free-run the computer until it next goes online.
+
+Whilst the computer is running, *chronyd* makes measurements of the RTC (via
+the _/dev/rtc_ interface, which must be compiled into the kernel). An estimate
+is made of the RTC error at a particular RTC second, and the rate at which the
+RTC gains or loses time relative to true time.
+
+When the computer is powered down, the measurement histories for all the NTP
+servers are saved to files, and the RTC tracking information is also
+saved to a file (if the <<rtcfile,*rtcfile*>> directive has been specified).
+These pieces of information are also saved if the <<chronyc.adoc#dump,*dump*>>
+and <<chronyc.adoc#writertc,*writertc*>> commands respectively are issued
+through *chronyc*.
+
+When the computer is rebooted, *chronyd* reads the current RTC time and the RTC
+information saved at the last shutdown. This information is used to set the
+system clock to the best estimate of what its time would have been now, had it
+been left running continuously. The measurement histories for the servers are
+then reloaded.
+
+The next time the computer goes online, the previous sessions' measurements can
+contribute to the line-fitting process, which gives a much better estimate of
+the computer's gain or loss rate.
+
+One problem with saving the measurements and RTC data when the machine is shut
+down is what happens if there is a power failure; the most recent data will not
+be saved. Although *chronyd* is robust enough to cope with this, some
+performance might be lost. (The main danger arises if the RTC has been changed
+during the session, with the *trimrtc* command in *chronyc*. Because of this,
+*trimrtc* will make sure that a meaningful RTC file is saved after the
+change is completed).
+
+The easiest protection against power failure is to put the *dump* and
+*writertc* commands in the same place as the *offline* command is issued to
+take *chronyd* offline; because *chronyd* free-runs between online sessions, no
+parameters will change significantly between going offline from the Internet
+and any power failure.
+
+A final point regards computers which are left running for extended periods and
+where it is desired to spin down the hard disc when it is not in use (e.g. when
+not accessed for 15 minutes). *chronyd* has been planned so it supports such
+operation; this is the reason why the RTC tracking parameters are not saved to
+disc after every update, but only when the user requests such a write, or
+during the shutdown sequence. The only other facility that will generate
+periodic writes to the disc is the *log rtc* facility in the configuration
+file; this option should not be used if you want your disc to spin down.
+
+To illustrate how a computer might be configured for this case, example
+configuration files are shown.
+
+For the _chrony.conf_ file, the following can be used as an example.
+
+----
+server ntp1.example.net maxdelay 0.4 offline
+server ntp2.example.net maxdelay 0.4 offline
+server ntp3.example.net maxdelay 0.4 offline
+logdir /var/log/chrony
+log statistics measurements tracking
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+maxupdateskew 100.0
+dumpdir @CHRONYVARDIR@
+rtcfile @CHRONYVARDIR@/rtc
+----
+
+*pppd* is used for connecting to the Internet. This runs two scripts
+_/etc/ppp/ip-up_ and _/etc/ppp/ip-down_ when the link goes online and offline
+respectively.
+
+The relevant part of the _/etc/ppp/ip-up_ file is:
+
+----
+@BINDIR@/chronyc online
+----
+
+and the relevant part of the _/etc/ppp/ip-down_ script is:
+
+----
+@BINDIR@/chronyc -m offline dump writertc
+----
+
+*chronyd* is started during the boot sequence with the *-r* and *-s* options.
+It might need to be started before any software that depends on the system clock
+not jumping or moving backwards, depending on the directives in *chronyd*'s
+configuration file.
+
+For the system shutdown, *chronyd* should receive a SIGTERM several seconds
+before the final SIGKILL; the SIGTERM causes the measurement histories and RTC
+information to be saved.
+
+=== Public NTP server
+
+*chronyd* can be configured to operate as a public NTP server, e.g. to join the
+https://www.pool.ntp.org/en/join.html[pool.ntp.org] project. The configuration
+is similar to the NTP client with permanent connection, except it needs to
+allow client access from all addresses. It is recommended to find at least four
+good servers (e.g. from the pool, or on the NTP homepage). If the server has a
+hardware reference clock (e.g. a GPS receiver), it can be specified by the
+<<refclock,*refclock*>> directive.
+
+The amount of memory used for logging client accesses can be increased in order
+to enable clients to use the interleaved mode even when the server has a large
+number of clients, and better support rate limiting if it is enabled by the
+<<ratelimit,*ratelimit*>> directive. The system timezone database, if it is
+kept up to date and includes the _right/UTC_ timezone, can be used as a
+reliable source to determine when a leap second will be applied to UTC. The
+*-r* option with the <<dumpdir,*dumpdir*>> directive shortens the time in which
+*chronyd* will not be able to serve time to its clients when it needs to be
+restarted (e.g. after upgrading to a newer version, or a change in the
+configuration).
+
+The configuration file could look like:
+
+----
+server ntp1.example.net iburst
+server ntp2.example.net iburst
+server ntp3.example.net iburst
+server ntp4.example.net iburst
+makestep 1.0 3
+rtcsync
+allow
+clientloglimit 100000000
+leapsectz right/UTC
+driftfile @CHRONYVARDIR@/drift
+dumpdir @CHRONYRUNDIR@
+----
+
+== SEE ALSO
+
+<<chronyc.adoc#,*chronyc(1)*>>, <<chronyd.adoc#,*chronyd(8)*>>
+
+== BUGS
+
+For instructions on how to report bugs, please visit
+https://chrony-project.org/.
+
+== AUTHORS
+
+chrony was written by Richard Curnow, Miroslav Lichvar, and others.
diff --git a/doc/chrony.conf.man.in b/doc/chrony.conf.man.in
new file mode 100644
index 0000000..66d2358
--- /dev/null
+++ b/doc/chrony.conf.man.in
@@ -0,0 +1,5043 @@
+'\" t
+.\" Title: chrony.conf
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2023-12-05
+.\" Manual: Configuration Files
+.\" Source: chrony @CHRONY_VERSION@
+.\" Language: English
+.\"
+.TH "CHRONY.CONF" "5" "2023-12-05" "chrony @CHRONY_VERSION@" "Configuration Files"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+chrony.conf \- chronyd configuration file
+.SH "SYNOPSIS"
+.sp
+\fBchrony.conf\fP
+.SH "DESCRIPTION"
+.sp
+This file configures the \fBchronyd\fP daemon. The compiled\-in location is
+\fI@SYSCONFDIR@/chrony.conf\fP. Other locations can be specified on the
+\fBchronyd\fP command line with the \fB\-f\fP option.
+.sp
+Each directive in the configuration file is placed on a separate line. The
+following sections describe each of the directives in turn. The directives are
+not case\-sensitive. Generally, the directives can occur in any order in the file
+and if a directive is specified multiple times, only the last one will be
+effective. Exceptions are noted in the descriptions.
+.sp
+The configuration directives can also be specified directly on the \fBchronyd\fP
+command line. In this case each argument is parsed as a new line and the
+configuration file is ignored.
+.sp
+While the number of supported directives is large, only a few of them are
+typically needed. See the \fBEXAMPLES\fP section for configuration in
+typical operating scenarios.
+.sp
+The configuration file might contain comment lines. A comment line is any line
+that starts with zero or more spaces followed by any one of the following
+characters: \fB!\fP, \fB;\fP, \fB#\fP, \fB%\fP. Any line with this format will be ignored.
+.SH "DIRECTIVES"
+.SS "Time sources"
+.sp
+\fBserver\fP \fIhostname\fP [\fIoption\fP]...
+.RS 4
+The \fBserver\fP directive specifies an NTP server which can be used as a time
+source. The client\-server relationship is strictly hierarchical: a client might
+synchronise its system time to that of the server, but the server\(cqs system time
+will never be influenced by that of a client.
+.sp
+The server can be specified by its hostname or IP address. If the hostname cannot
+be resolved on start, \fBchronyd\fP will try it again in increasing intervals, and
+also when the \fBonline\fP command is issued in \fBchronyc\fP.
+.sp
+The DNS record can change over time. The used address will be replaced with a
+newly resolved address when the server becomes unreachable (i.e. no valid
+response to last 8 requests), unsynchronised, a falseticker (i.e. does not
+agree with a majority of other sources), or the root distance is too large (the
+limit can be configured by the \fBmaxdistance\fP directive). The
+automatic replacement happens at most once per 30 minutes.
+.sp
+This directive can be used multiple times to specify multiple servers.
+.sp
+The directive supports the following options:
+.sp
+\fBminpoll\fP \fIpoll\fP
+.RS 4
+This option specifies the minimum interval between requests sent to the server
+as a power of 2 in seconds. For example, \fBminpoll 5\fP would mean that the
+polling interval should not drop below 32 seconds. The default is 6 (64
+seconds), the minimum is \-7 (1/128th of a second), and the maximum is 24 (6
+months). Note that intervals shorter than 6 (64 seconds) should generally not
+be used with public servers on the Internet, because it might be considered
+abuse. A sub\-second interval will be enabled only when the server is reachable
+and the round\-trip delay is shorter than 10 milliseconds, i.e. the server
+should be in a local network.
+.RE
+.sp
+\fBmaxpoll\fP \fIpoll\fP
+.RS 4
+This option specifies the maximum interval between requests sent to the server
+as a power of 2 in seconds. For example, \fBmaxpoll 9\fP indicates that the polling
+interval should stay at or below 9 (512 seconds). The default is 10 (1024
+seconds), the minimum is \-7 (1/128th of a second), and the maximum is 24 (6
+months).
+.RE
+.sp
+\fBiburst\fP
+.RS 4
+With this option, \fBchronyd\fP will start with a burst of 4\-8 requests in order to
+make the first update of the clock sooner. It will also repeat the burst every
+time the source is switched from the offline state to online with the
+\fBonline\fP command in \fBchronyc\fP.
+.RE
+.sp
+\fBburst\fP
+.RS 4
+With this option, \fBchronyd\fP will send a burst of up to 4 requests when it
+cannot get a good measurement from the
+server. The number of requests in the burst is limited by the current polling
+interval to keep the average interval at or above the minimum interval, i.e.
+the current interval needs to be at least two times longer than the minimum
+interval in order to allow a burst with two requests.
+.RE
+.sp
+\fBkey\fP \fIID\fP
+.RS 4
+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 key specified in the key file,
+which is specified by the \fBkeyfile\fP directive.
+.sp
+The \fBkey\fP option specifies which key (with an ID in the range 1 through 2^32\-1)
+should \fBchronyd\fP use to authenticate requests sent to the server and verify its
+responses. The server must have the same key for this number configured,
+otherwise no relationship between the computers will be possible.
+.sp
+If the server is running \fBntpd\fP and the output size of the hash function used
+by the key is longer than 160 bits (e.g. SHA256), the \fBversion\fP option needs to
+be set to 4 for compatibility.
+.RE
+.sp
+\fBnts\fP
+.RS 4
+This option enables authentication using the Network Time Security (NTS)
+mechanism. Unlike with the \fBkey\fP 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.
+.RE
+.sp
+\fBcertset\fP \fIID\fP
+.RS 4
+This option specifies which set of trusted certificates should be used to verify
+the server\(cqs certificate when the \fBnts\fP option is enabled. Sets of certificates
+can be specified with the \fBntstrustedcerts\fP directive. The
+default set is 0, which by default contains certificates of the system\(cqs
+default trusted certificate authorities.
+.RE
+.sp
+\fBmaxdelay\fP \fIdelay\fP
+.RS 4
+\fBchronyd\fP uses the network round\-trip delay to the server to determine how
+accurate a particular measurement is likely to be. Long round\-trip delays
+indicate that the request, or the response, or both were delayed. If only one
+of the messages was delayed the measurement error is likely to be substantial.
+.sp
+For small variations in the round\-trip delay, \fBchronyd\fP uses a weighting scheme
+when processing the measurements. However, beyond a certain level of delay the
+measurements are likely to be so corrupted as to be useless. (This is
+particularly so on wireless networks and other slow links, where a long delay
+probably indicates a highly asymmetric delay caused by the response waiting
+behind a lot of packets related to a download of some sort).
+.sp
+If the user knows that round trip delays above a certain level should cause the
+measurement to be ignored, this level can be defined with the \fBmaxdelay\fP
+option. For example, \fBmaxdelay 0.3\fP would indicate that measurements with a
+round\-trip delay greater than 0.3 seconds should be ignored. The default value
+is 3 seconds and the maximum value is 1000 seconds.
+.RE
+.sp
+\fBmaxdelayratio\fP \fIratio\fP
+.RS 4
+This option is similar to the \fBmaxdelay\fP option above. \fBchronyd\fP keeps a record
+of the minimum round\-trip delay amongst the previous measurements that it has
+buffered. If a measurement has a round\-trip delay that is greater than the
+specified ratio times the minimum delay, it will be rejected. By default, this
+test is disabled.
+.RE
+.sp
+\fBmaxdelaydevratio\fP \fIratio\fP
+.RS 4
+If a measurement has a ratio of the increase in the round\-trip delay from the
+minimum delay amongst the previous measurements to the standard deviation of
+the previous measurements that is greater than the specified ratio, it will be
+rejected. The default is 10.0.
+.RE
+.sp
+\fBmaxdelayquant\fP \fIp\fP
+.RS 4
+This option disables the \fBmaxdelaydevratio\fP test and specifies the maximum
+acceptable delay as a quantile of the round\-trip delay instead of a function of
+the minimum delay amongst the buffered measurements. If a measurement has a
+round\-trip delay that is greater than a long\-term estimate of the \fIp\fP\-quantile,
+it will be rejected.
+.sp
+The specified \fIp\fP value should be between 0.05 and 0.95. For example,
+\fBmaxdelayquant 0.2\fP would indicate that only measurements with the lowest 20
+percent of round\-trip delays should be accepted. Note that it can take many
+measurements for the estimated quantile to reach the expected value. This
+option is intended for synchronisation in mostly static local networks with
+very short polling intervals and possibly combined with the \fBfilter\fP option.
+By default, this test is disabled in favour of the \fBmaxdelaydevratio\fP test.
+.RE
+.sp
+\fBmindelay\fP \fIdelay\fP
+.RS 4
+This option specifies a fixed minimum round\-trip delay to be used instead of
+the minimum amongst the previous measurements. This can be useful in networks
+with static configuration to improve the stability of corrections for
+asymmetric jitter, weighting of the measurements, and the \fBmaxdelayratio\fP and
+\fBmaxdelaydevratio\fP tests. The value should be set accurately in order to have a
+positive effect on the synchronisation.
+.RE
+.sp
+\fBasymmetry\fP \fIratio\fP
+.RS 4
+This option specifies the asymmetry of the network jitter on the path to the
+source, which is used to correct the measured offset according to the delay.
+The asymmetry can be between \-0.5 and +0.5. A negative value means the delay of
+packets sent to the source is more variable than the delay of packets sent from
+the source back. By default, \fBchronyd\fP estimates the asymmetry automatically.
+.RE
+.sp
+\fBoffset\fP \fIoffset\fP
+.RS 4
+This option specifies a correction (in seconds) which will be applied to
+offsets measured with this source. It\(cqs particularly useful to compensate for a
+known asymmetry in network delay or timestamping errors. For example, if
+packets sent to the source were on average delayed by 100 microseconds more
+than packets sent from the source back, the correction would be \-0.00005 (\-50
+microseconds). The default is 0.0.
+.RE
+.sp
+\fBminsamples\fP \fIsamples\fP
+.RS 4
+Set the minimum number of samples kept for this source. This overrides the
+\fBminsamples\fP directive.
+.RE
+.sp
+\fBmaxsamples\fP \fIsamples\fP
+.RS 4
+Set the maximum number of samples kept for this source. This overrides the
+\fBmaxsamples\fP directive.
+.RE
+.sp
+\fBfilter\fP \fIpolls\fP
+.RS 4
+This option enables a median filter to reduce noise in NTP measurements. The
+filter will process samples collected in the specified number of polls
+into a single sample. It is intended to be used with very short polling
+intervals in local networks where it is acceptable to generate a lot of NTP
+traffic.
+.RE
+.sp
+\fBoffline\fP
+.RS 4
+If the server will not be reachable when \fBchronyd\fP is started, the \fBoffline\fP
+option can be specified. \fBchronyd\fP will not try to poll the server until it is
+enabled to do so (by using the \fBonline\fP command in
+\fBchronyc\fP).
+.RE
+.sp
+\fBauto_offline\fP
+.RS 4
+With this option, the server will be assumed to have gone offline when sending
+a request fails, e.g. due to a missing route to the network. This option avoids
+the need to run the \fBoffline\fP command from \fBchronyc\fP
+when disconnecting the network link. (It will still be necessary to use the
+\fBonline\fP command when the link has been established, to
+enable measurements to start.)
+.RE
+.sp
+\fBprefer\fP
+.RS 4
+Prefer this source over sources without the \fBprefer\fP option.
+.RE
+.sp
+\fBnoselect\fP
+.RS 4
+Never select this source. This is particularly useful for monitoring.
+.RE
+.sp
+\fBtrust\fP
+.RS 4
+Assume time from this source is always true. It can be rejected as a
+falseticker in the source selection only if another source with this option
+does not agree with it.
+.RE
+.sp
+\fBrequire\fP
+.RS 4
+Require that at least one of the sources specified with this option is
+selectable (i.e. recently reachable and not a falseticker) before updating the
+clock. Together with the \fBtrust\fP option this might be useful to allow a trusted
+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.
+.RE
+.sp
+\fBxleave\fP
+.RS 4
+This option enables the interleaved mode of NTP. It enables the server to
+respond with more accurate transmit timestamps (e.g. kernel or hardware
+timestamps), which cannot be contained in the transmitted packet itself and
+need to refer to a previous packet instead. This can significantly improve the
+accuracy and stability of the measurements.
+.sp
+The interleaved mode is compatible with servers that support only the basic
+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.
+\fBclientloglimit\fP is too small), or it might be overwritten
+by other clients that have the same IP address (e.g. computers behind NAT or
+someone sending requests with a spoofed source address).
+.sp
+The \fBxleave\fP option can be combined with the \fBpresend\fP option in order to
+shorten the interval in which the server has to keep the state to be able to
+respond in the interleaved mode.
+.RE
+.sp
+\fBpolltarget\fP \fItarget\fP
+.RS 4
+Target number of measurements to use for the regression algorithm which
+\fBchronyd\fP will try to maintain by adjusting the polling interval between
+\fBminpoll\fP and \fBmaxpoll\fP. A higher target makes \fBchronyd\fP prefer shorter polling
+intervals. The default is 8 and a useful range is from 6 to 60.
+.RE
+.sp
+\fBport\fP \fIport\fP
+.RS 4
+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).
+.RE
+.sp
+\fBntsport\fP \fIport\fP
+.RS 4
+This option specifies the TCP port on which the server is listening for NTS\-KE
+connections when the \fBnts\fP option is enabled. The default is 4460.
+.RE
+.sp
+\fBpresend\fP \fIpoll\fP
+.RS 4
+If the timing measurements being made by \fBchronyd\fP are the only network data
+passing between two computers, you might find that some measurements are badly
+skewed due to either the client or the server having to do an ARP lookup on the
+other party prior to transmitting a packet. This is more of a problem with long
+sampling intervals, which might be similar in duration to the lifetime of entries
+in the ARP caches of the machines.
+.sp
+In order to avoid this problem, the \fBpresend\fP option can be used. It takes a
+single integer argument, which is the smallest polling interval for which an
+extra pair of NTP packets will be exchanged between the client and the server
+prior to the actual measurement. For example, with the following option
+included in a \fBserver\fP directive:
+.sp
+.if n .RS 4
+.nf
+.fam C
+presend 9
+.fam
+.fi
+.if n .RE
+.sp
+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.
+.sp
+If the \fBpresend\fP option is used together with the \fBxleave\fP option, \fBchronyd\fP
+will send two extra packets instead of one.
+.RE
+.sp
+\fBminstratum\fP \fIstratum\fP
+.RS 4
+When the synchronisation source is selected from available sources, sources
+with lower stratum are normally slightly preferred. This option can be used to
+increase stratum of the source to the specified minimum, so \fBchronyd\fP will
+avoid selecting that source. This is useful with low\-stratum sources that are
+known to be unreliable or inaccurate and which should be used only when other
+sources are unreachable.
+.RE
+.sp
+\fBversion\fP \fIversion\fP
+.RS 4
+This option sets the NTP version of packets sent to the server. This can be
+useful when the server runs an old NTP implementation that does not respond to
+requests using a newer version. The default version depends on other options.
+If the \fBextfield\fP or \fBxleave\fP option is used, the default version is 4. If
+those options are not used and the \fBkey\fP option specifies a key using a hash
+function with output size longer than 160 bits (e.g. SHA256), the default
+version is 3 for compatibility with older \fBchronyd\fP servers. In other cases,
+the default version is 4.
+.RE
+.sp
+\fBcopy\fP
+.RS 4
+This option specifies that the server and client are closely related, their
+configuration does not allow a synchronisation loop to form between them, and
+the client is allowed to assume the reference ID and stratum of the server.
+This is useful when multiple instances of \f(CRchronyd\fP are running on one computer
+(e.g. for security or performance reasons), one primarily operating as a client
+to synchronise the system clock and other instances started with the \fB\-x\fP
+option to operate as NTP servers for other computers with their NTP clocks
+synchronised to the first instance.
+.RE
+.sp
+\fBextfield\fP \fItype\fP
+.RS 4
+This option enables an NTPv4 extension field specified by its type as a
+hexadecimal number. It will be included in requests sent to the server and
+processed in received responses if the server supports it. Note that some
+server implementations do not respond to requests containing an unknown
+extension field (\fBchronyd\fP as a server responded to such requests since
+version 2.0).
+.sp
+This option can be used multiple times to enable multiple extension fields.
+.sp
+The following extension fields are supported:
+.sp
+\fIF323\fP
+.RS 4
+An experimental extension field to enable several improvements that were
+proposed for the next version of the NTP protocol (NTPv5). The field contains
+root delay and dispersion in higher resolution and a monotonic receive
+timestamp, which enables a frequency transfer between the server and client to
+significantly improve stability of the synchronisation. This field should be
+enabled only for servers known to be running \fBchronyd\fP version 4.2 or later.
+.RE
+.sp
+\fIF324\fP
+.RS 4
+An experimental extension field to enable the use of the Precision Time
+Protocol (PTP) correction field in NTP\-over\-PTP messages updated by one\-step
+end\-to\-end transparent clocks in network switches and routers to significantly
+improve accuracy and stability of the synchronisation. NTP\-over\-PTP can be
+enabled by the \fBptpport\fP directive and setting the \fBport\fP option to
+the PTP port. The corrections are applied only to NTP measurements with HW
+timestamps (enabled by the \fBhwtimestamp\fP directive). This
+field should be enabled only for servers known to be running \fBchronyd\fP version
+4.5 or later.
+.RE
+.RE
+.sp
+
+.RS 4
+.RE
+.RE
+.sp
+\fBpool\fP \fIname\fP [\fIoption\fP]...
+.RS 4
+The syntax of this directive is similar to that for the \fBserver\fP
+directive, except that it is used to specify a pool of NTP servers rather than
+a single NTP server. The pool name is expected to resolve to multiple addresses
+which might change over time.
+.sp
+This directive can be used multiple times to specify multiple pools.
+.sp
+All options valid in the \fBserver\fP directive can be used in this
+directive too. There is one option specific to the \fBpool\fP directive:
+.sp
+\fBmaxsources\fP \fIsources\fP
+.RS 4
+This option sets the desired number of sources to be used from the pool.
+\fBchronyd\fP 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.
+.sp
+An example of the \fBpool\fP directive is
+.sp
+.if n .RS 4
+.nf
+.fam C
+pool pool.ntp.org iburst maxsources 3
+.fam
+.fi
+.if n .RE
+.RE
+.RE
+.sp
+\fBpeer\fP \fIhostname\fP [\fIoption\fP]...
+.RS 4
+The syntax of this directive is identical to that for the \fBserver\fP
+directive, except that it specifies a symmetric association with an NTP peer
+instead of a client/server association with an NTP server. A single symmetric
+association allows the peers to be both servers and clients to each other. This
+is mainly useful when the NTP implementation of the peer (e.g. \fBntpd\fP) supports
+ephemeral symmetric associations and does not need to be configured with an
+address of this host. \fBchronyd\fP does not support ephemeral associations.
+.sp
+This directive can be used multiple times to specify multiple peers.
+.sp
+The following options of the \fBserver\fP directive do not work in the \fBpeer\fP
+directive: \fBiburst\fP, \fBburst\fP, \fBnts\fP, \fBpresend\fP, \fBcopy\fP.
+.sp
+When using the \fBxleave\fP 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 \fBkey\fP option to enable authentication, both
+peers must use the same key and the same key number.
+.sp
+Note that the symmetric mode is less secure than the client/server mode. A
+denial\-of\-service attack is possible on unauthenticated symmetric associations,
+i.e. when the peer was specified without the \fBkey\fP option. An attacker who does
+not see network traffic between two hosts, but knows that they are peering with
+each other, can periodically send them unauthenticated packets with spoofed
+source addresses in order to disrupt their NTP state and prevent them from
+synchronising to each other. When the association is authenticated, an attacker
+who does see the network traffic, but cannot prevent the packets from reaching
+the other host, can still disrupt the state by replaying old packets. The
+attacker has effectively the same power as a man\-in\-the\-middle attacker. A
+partial protection against this attack is implemented in \fBchronyd\fP, which can
+protect the peers if they are using the same polling interval and they never
+sent an authenticated packet with a timestamp from future, but it should not be
+relied on as it is difficult to ensure the conditions are met. If two hosts
+should be able to synchronise to each other in both directions, it is
+recommended to use two separate client/server associations (specified by the
+\fBserver\fP directive on both hosts) instead.
+.RE
+.sp
+\fBinitstepslew\fP \fIstep\-threshold\fP [\fIhostname\fP]...
+.RS 4
+(This directive is deprecated in favour of the \fBmakestep\fP
+directive.)
+.sp
+The purpose of the \fBinitstepslew\fP directive is to allow \fBchronyd\fP to make a
+rapid measurement of the system clock error at boot time, and to correct the
+system clock by stepping before normal operation begins. Since this would
+normally be performed only at an appropriate point in the system boot sequence,
+no other software should be adversely affected by the step.
+.sp
+If the correction required is less than a specified threshold, a slew is used
+instead. This makes it safer to restart \fBchronyd\fP whilst the system is in
+normal operation.
+.sp
+The \fBinitstepslew\fP directive takes a threshold and a list of NTP servers as
+arguments. Each of the servers is rapidly polled several times, and a majority
+voting mechanism used to find the most likely range of system clock error that
+is present. A step or slew is applied to the system clock to correct this
+error. \fBchronyd\fP then enters its normal operating mode.
+.sp
+An example of the use of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+initstepslew 30 ntp1.example.net ntp2.example.net ntp3.example.net
+.fam
+.fi
+.if n .RE
+.sp
+where 3 NTP servers are used to make the measurement. The \fI30\fP indicates that
+if the system\(cqs error is found to be 30 seconds or less, a slew will be used to
+correct it; if the error is above 30 seconds, a step will be used.
+.sp
+The \fBinitstepslew\fP directive can also be used in an isolated LAN environment,
+where the clocks are set manually. The most stable computer is chosen as the
+primary server and the other computers are its clients. If each of the clients
+is configured with the \fBlocal\fP directive, the server can be set up
+with an \fBinitstepslew\fP directive which references some or all of the clients.
+Then, if the server machine has to be rebooted, the clients can be relied on to
+act analogously to a flywheel and preserve the time for a short period while
+the server completes its reboot.
+.sp
+The \fBinitstepslew\fP directive is functionally similar to a combination of the
+\fBmakestep\fP and \fBserver\fP directives with the \fBiburst\fP
+option. The main difference is that the \fBinitstepslew\fP servers are used only
+before normal operation begins and that the foreground \fBchronyd\fP process waits
+for \fBinitstepslew\fP to finish before exiting. This prevent programs started in
+the boot sequence after \fBchronyd\fP from reading the clock before it has been
+stepped. With the \fBmakestep\fP directive, the
+\fBwaitsync\fP command of \fBchronyc\fP can be used instead.
+.RE
+.sp
+\fBrefclock\fP \fIdriver\fP \fIparameter\fP[:\fIoption\fP]... [\fIoption\fP]...
+.RS 4
+The \fBrefclock\fP directive specifies a hardware reference clock to be used as a
+time source. It has two mandatory parameters, a driver name and a
+driver\-specific parameter. The two parameters are followed by zero or more
+refclock options. Some drivers have special options, which can be appended to
+the driver\-specific parameter using the \fB:\fP character.
+.sp
+This directive can be used multiple times to specify multiple reference clocks.
+.sp
+There are four drivers included in \fBchronyd\fP:
+.sp
+\fBPPS\fP
+.RS 4
+Driver for the kernel PPS (pulse per second) API. The parameter is the path to
+the PPS device (typically \fI/dev/pps?\fP). As PPS refclocks do not supply full
+time, another time source (e.g. NTP server or non\-PPS refclock) is needed to
+complete samples from the PPS refclock. An alternative is to enable the
+\fBlocal\fP directive to allow synchronisation with some unknown but
+constant offset. The driver supports the following option:
+.sp
+\fBclear\fP
+.RS 4
+By default, the PPS refclock uses assert events (rising edge) for
+synchronisation. With this option, it will use clear events (falling edge)
+instead.
+.RE
+.RE
+.sp
+
+.RS 4
+Examples:
+.sp
+.if n .RS 4
+.nf
+.fam C
+refclock PPS /dev/pps0 lock NMEA refid GPS1
+refclock SOCK /var/run/chrony.clk.ttyS0.sock offset 0.5 delay 0.2 refid NMEA noselect
+refclock PPS /dev/pps1:clear refid GPS2
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBSOCK\fP
+.RS 4
+Unix domain socket driver. This driver uses a datagram socket to receive
+samples from another application running on the system. The parameter is the
+path to the socket, which \fBchronyd\fP will create on start. The format of the
+messages is described in the \fIrefclock_sock.c\fP file in the chrony source code.
+.sp
+An application which supports the SOCK protocol is the \fBgpsd\fP daemon. It can
+provide accurate measurements using the receiver\(cqs PPS signal, and since
+version 3.25 also (much less accurate) measurements based on the timing of
+serial data (e.g. NMEA), which can be useful when the receiver does not provide
+a PPS signal, or it cannot be connected to the computer. The paths where \fBgpsd\fP
+expects the sockets to be created by \fBchronyd\fP are described in the \fBgpsd(8)\fP
+man page. Note that \fBgpsd\fP needs to be started after \fBchronyd\fP in order to
+connect to the socket.
+.sp
+Examples:
+.sp
+.if n .RS 4
+.nf
+.fam C
+refclock SOCK /var/run/chrony.ttyS0.sock refid GPS1 poll 2 filter 4
+refclock SOCK /var/run/chrony.clk.ttyUSB0.sock refid GPS2 offset 0.2 delay 0.1
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBSHM\fP
+.RS 4
+NTP shared memory driver. This driver implements the protocol of the \fBntpd\fP
+driver type 28. It is functionally similar to the SOCK driver, but uses a
+shared memory segment instead of a socket. The parameter is the unit number,
+typically a small number like 0, 1, 2, or 3, from which is derived the key of
+the memory segment as 0x4e545030 + unit.
+.sp
+The driver supports the following option:
+.sp
+\fBperm\fP=\fImode\fP
+.RS 4
+This option specifies the permissions of the shared memory segment created by
+\fBchronyd\fP. They are specified as a numeric mode. The default value is 0600
+(read\-write access for owner only).
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+Unlike with the SOCK driver, there is no prescribed order of starting \fBchronyd\fP
+and the program providing measurements. Both are expected to create the memory
+segment if it does not exist. \fBchronyd\fP will attach to an existing segment even
+if it has a different owner than root or different permissions than the
+permissions specified by the \fBperm\fP option. The segment needs to be created
+before untrusted applications or users can execute code to prevent an attacker
+from feeding \fBchronyd\fP with false measurements. The owner and permissions of
+the segment can be verified with the \fBipcs \-m\fP command. For this reason, the
+SHM driver is deprecated in favor of SOCK.
+.sp
+Examples:
+.sp
+.if n .RS 4
+.nf
+.fam C
+refclock SHM 0 poll 3 refid GPS1
+refclock SHM 1:perm=0644 refid GPS2
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBPHC\fP
+.RS 4
+PTP hardware clock (PHC) driver. The parameter is the path to the device of
+the PTP clock which should be used as a time source. If the clock is kept in
+TAI instead of UTC (e.g. it is synchronised by a PTP daemon), the current
+UTC\-TAI offset needs to be specified by the \fBoffset\fP option. Alternatively, the
+\fBpps\fP refclock option can be enabled to treat the PHC as a PPS refclock, using
+only the sub\-second offset for synchronisation. The driver supports the
+following options:
+.sp
+\fBnocrossts\fP
+.RS 4
+This option disables use of precise cross timestamping.
+.RE
+.sp
+\fBextpps\fP
+.RS 4
+This option enables a PPS mode in which the PTP clock is timestamping pulses
+of an external PPS signal connected to the clock. The clock does not need to be
+synchronised, but another time source is needed to complete the PPS samples.
+Note that some PTP clocks cannot be configured to timestamp only assert or
+clear events, and it is necessary to use the \fBwidth\fP option to filter wrong
+PPS samples.
+.RE
+.sp
+\fBpin\fP=\fIindex\fP
+.RS 4
+This option specifies the index of the pin which should be enabled for the
+PPS timestamping. If the PHC does not have configurable pins (i.e. the channel
+function is fixed), the index needs to be set to \-1 to disable the pin
+configuration. The default value is 0.
+.RE
+.sp
+\fBchannel\fP=\fIindex\fP
+.RS 4
+This option specifies the index of the channel for the PPS mode. The default
+value is 0.
+.RE
+.sp
+\fBclear\fP
+.RS 4
+This option enables timestamping of clear events (falling edge) instead of
+assert events (rising edge) in the PPS mode. This may not work with some
+clocks.
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+Examples:
+.sp
+.if n .RS 4
+.nf
+.fam C
+refclock PHC /dev/ptp0 poll 0 dpoll \-2 offset \-37
+refclock PHC /dev/ptp1:nocrossts poll 3 pps
+refclock PHC /dev/ptp2:extpps:pin=1 width 0.2 poll 2
+.fam
+.fi
+.if n .RE
+.RE
+.RE
+.sp
+
+.RS 4
+The \fBrefclock\fP directive supports the following options:
+.sp
+\fBpoll\fP \fIpoll\fP
+.RS 4
+Timestamps produced by refclock drivers are not used immediately, but they are
+stored and processed by a median filter in the polling interval specified by
+this option. This is defined as a power of 2 and can be negative to specify a
+sub\-second interval. The default is 4 (16 seconds). A shorter interval allows
+\fBchronyd\fP to react faster to changes in the frequency of the system clock, but
+it might have a negative effect on its accuracy if the samples have a lot of
+jitter.
+.RE
+.sp
+\fBdpoll\fP \fIdpoll\fP
+.RS 4
+Some drivers do not listen for external events and try to produce samples in
+their own polling interval. This is defined as a power of 2 and can be negative
+to specify a sub\-second interval. The default is 0 (1 second).
+.RE
+.sp
+\fBrefid\fP \fIrefid\fP
+.RS 4
+This option is used to specify the reference ID of the refclock, as up to four
+ASCII characters. The default reference ID is composed from the first three
+characters of the driver name and the number of the refclock. Each refclock
+must have a unique reference ID.
+.RE
+.sp
+\fBlock\fP \fIrefid\fP
+.RS 4
+This option can be used to lock a PPS refclock to another refclock, which is
+specified by its reference ID. In this mode received PPS samples are paired
+directly with raw samples from the specified refclock.
+.RE
+.sp
+\fBrate\fP \fIrate\fP
+.RS 4
+This option sets the rate of the pulses in the PPS signal (in Hz). This option
+controls how the pulses will be completed with real time. To actually receive
+more than one pulse per second, a negative \fBdpoll\fP has to be specified (\-3 for
+a 5Hz signal). The default is 1.
+.RE
+.sp
+\fBmaxlockage\fP \fIpulses\fP
+.RS 4
+This option specifies in number of pulses how old can be samples from the
+refclock specified by the \fBlock\fP option to be paired with the pulses.
+Increasing this value is useful when the samples are produced at a lower rate
+than the pulses. The default is 2.
+.RE
+.sp
+\fBwidth\fP \fIwidth\fP
+.RS 4
+This option specifies the width of the pulses (in seconds). It is used to
+filter PPS samples when the driver provides samples for both rising and falling
+edges. Note that it reduces the maximum allowed error of the time source which
+completes the PPS samples. If the duty cycle is configurable, 50% should be
+preferred in order to maximise the allowed error.
+.RE
+.sp
+\fBpps\fP
+.RS 4
+This options forces \fBchronyd\fP to treat any refclock (e.g. SHM or PHC) as a PPS
+refclock. This can be useful when the refclock provides time with a variable
+offset of a whole number of seconds (e.g. it uses TAI instead of UTC). Another
+time source is needed to complete samples from the refclock.
+.RE
+.sp
+\fBoffset\fP \fIoffset\fP
+.RS 4
+This option can be used to compensate for a constant error. The specified
+offset (in seconds) is applied to all samples produced by the reference clock.
+The default is 0.0.
+.RE
+.sp
+\fBdelay\fP \fIdelay\fP
+.RS 4
+This option sets the NTP delay of the source (in seconds). Half of this value
+is included in the maximum assumed error which is used in the source selection
+algorithm. Increasing the delay is useful to avoid having no majority in the
+source selection or to make it prefer other sources. The default is 1e\-9 (1
+nanosecond).
+.RE
+.sp
+\fBstratum\fP \fIstratum\fP
+.RS 4
+This option sets the NTP stratum of the refclock. This can be useful when the
+refclock provides time with a stratum other than 0. The default is 0.
+.RE
+.sp
+\fBprecision\fP \fIprecision\fP
+.RS 4
+This option sets the precision of the reference clock (in seconds). The default
+value is the estimated precision of the system clock.
+.RE
+.sp
+\fBmaxdispersion\fP \fIdispersion\fP
+.RS 4
+Maximum allowed dispersion for filtered samples (in seconds). Samples with
+larger estimated dispersion are ignored. By default, this limit is disabled.
+.RE
+.sp
+\fBfilter\fP \fIsamples\fP
+.RS 4
+This option sets the length of the median filter which is used to reduce the
+noise in the measurements. With each poll about 40 percent of the stored
+samples are discarded and one final sample is calculated as an average of the
+remaining samples. If the length is 4 or more, at least 4 samples have to be
+collected between polls. For lengths below 4, the filter has to be full. The
+default is 64. With drivers that perform their own polling (PPS, PHC, SHM), the
+maximum value is adjusted to the number of driver polls per source poll, i.e.
+2^(\fIpoll\fP \- \fIdpoll\fP).
+.RE
+.sp
+\fBprefer\fP
+.RS 4
+Prefer this source over sources without the prefer option.
+.RE
+.sp
+\fBnoselect\fP
+.RS 4
+Never select this source. This is useful for monitoring or with sources which
+are not very accurate, but are locked with a PPS refclock.
+.RE
+.sp
+\fBtrust\fP
+.RS 4
+Assume time from this source is always true. It can be rejected as a
+falseticker in the source selection only if another source with this option
+does not agree with it.
+.RE
+.sp
+\fBrequire\fP
+.RS 4
+Require that at least one of the sources specified with this option is
+selectable (i.e. recently reachable and not a falseticker) before updating the
+clock. Together with the \fBtrust\fP option this can be useful to allow a trusted,
+but not very precise, reference clock to be safely combined with
+unauthenticated NTP 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.
+.RE
+.sp
+\fBtai\fP
+.RS 4
+This option indicates that the reference clock keeps time in TAI instead of UTC
+and that \fBchronyd\fP should correct its offset by the current TAI\-UTC offset. The
+\fBleapsectz\fP directive must be used with this option and the
+database must be kept up to date in order for this correction to work as
+expected. This option does not make sense with PPS refclocks.
+.RE
+.sp
+\fBlocal\fP
+.RS 4
+This option specifies that the reference clock is an unsynchronised clock which
+is more stable than the system clock (e.g. TCXO, OCXO, or atomic clock) and
+it should be used as a local standard to stabilise the system clock. The
+refclock will bypass the source selection. There should be at most one refclock
+specified with this option and it should have the shortest polling interval
+among all configured sources.
+.RE
+.sp
+\fBminsamples\fP \fIsamples\fP
+.RS 4
+Set the minimum number of samples kept for this source. This overrides the
+\fBminsamples\fP directive.
+.RE
+.sp
+\fBmaxsamples\fP \fIsamples\fP
+.RS 4
+Set the maximum number of samples kept for this source. This overrides the
+\fBmaxsamples\fP directive.
+.RE
+.RE
+.sp
+\fBmanual\fP
+.RS 4
+The \fBmanual\fP directive enables support at run\-time for the
+\fBsettime\fP command in \fBchronyc\fP. If no \fBmanual\fP
+directive is included, any attempt to use the \fBsettime\fP command in \fBchronyc\fP
+will be met with an error message.
+.sp
+Note that the \fBsettime\fP command can be enabled at run\-time using
+the \fBmanual\fP command in \fBchronyc\fP. (The idea of the two
+commands is that the \fBmanual\fP command controls the manual clock driver\(cqs
+behaviour, whereas the \fBsettime\fP command allows samples of manually entered
+time to be provided.)
+.RE
+.sp
+\fBacquisitionport\fP \fIport\fP
+.RS 4
+By default, \fBchronyd\fP as an NTP client opens a new socket for each request with
+the source port chosen randomly by the operating system. The \fBacquisitionport\fP
+directive can be used to specify the source port and use only one socket (per
+IPv4 or IPv6 address family) for all configured servers. This can be useful for
+getting through some firewalls. It should not be used if not necessary as there
+is a small impact on security of the client. If set to 0, the source port of
+the permanent socket will be chosen randomly by the operating system.
+.sp
+It can be set to the same port as is used by the NTP server (which can be
+configured with the \fBport\fP directive) to use only one socket for all
+NTP packets.
+.sp
+An example of the \fBacquisitionport\fP directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+acquisitionport 1123
+.fam
+.fi
+.if n .RE
+.sp
+This would change the source port used for client requests to UDP port 1123.
+You could then persuade the firewall administrator to open that port.
+.RE
+.sp
+\fBbindacqaddress\fP \fIaddress\fP
+.RS 4
+The \fBbindacqaddress\fP directive specifies a local IP address to which
+\fBchronyd\fP will bind its NTP and NTS\-KE client sockets. The syntax is similar to
+the \fBbindaddress\fP and \fBbindcmdaddress\fP
+directives.
+.sp
+For each of the IPv4 and IPv6 protocols, only one \fBbindacqaddress\fP directive
+can be specified.
+.RE
+.sp
+\fBbindacqdevice\fP \fIinterface\fP
+.RS 4
+The \fBbindacqdevice\fP directive binds the client sockets to a network device
+specified by the interface name. This can be useful when the local address is
+dynamic, or to enable an NTP source specified with a link\-local IPv6 address.
+This directive can specify only one interface and it is supported on Linux
+only.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+bindacqdevice eth0
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBdscp\fP \fIpoint\fP
+.RS 4
+The \fBdscp\fP directive sets the Differentiated Services Code Point (DSCP) in
+transmitted NTP packets to the specified value. It can improve stability of NTP
+measurements in local networks where switches or routers are configured to
+prioritise forwarding of packets with specific DSCP values. The default value
+is 0 and the maximum value is 63.
+.sp
+An example of the directive (setting the Expedited Forwarding class) is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+dscp 46
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBdumpdir\fP \fIdirectory\fP
+.RS 4
+To compute the rate of gain or loss of time, \fBchronyd\fP has to store a
+measurement history for each of the time sources it uses.
+.sp
+All supported systems, with the exception of macOS 10.12 and earlier, have
+operating system support for setting the rate of gain or loss to compensate for
+known errors.
+(On macOS 10.12 and earlier, \fBchronyd\fP must simulate such a capability by
+periodically slewing the system clock forwards or backwards by a suitable amount
+to compensate for the error built up since the previous slew.)
+.sp
+For such systems, it is possible to save the measurement history across
+restarts of \fBchronyd\fP (assuming no changes are made to the system clock
+behaviour whilst it is not running). The \fBdumpdir\fP directive defines the
+directory where the measurement histories are saved when \fBchronyd\fP exits,
+or the \fBdump\fP command in \fBchronyc\fP is issued.
+.sp
+If the directory does not exist, it will be created automatically.
+.sp
+The \fB\-r\fP option of \fBchronyd\fP enables loading of the dump files on start. All
+dump files found in the directory will be removed after start, even if the \fB\-r\fP
+option is not present.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+dumpdir @CHRONYRUNDIR@
+.fam
+.fi
+.if n .RE
+.sp
+A source whose IP address is \fI1.2.3.4\fP would have its measurement history saved
+in the file \fI@CHRONYRUNDIR@/1.2.3.4.dat\fP. History of reference clocks is saved
+to files named by their reference ID in form of \fIrefid:XXXXXXXX.dat\fP.
+.RE
+.sp
+\fBmaxsamples\fP \fIsamples\fP
+.RS 4
+The \fBmaxsamples\fP directive sets the default maximum number of samples that
+\fBchronyd\fP should keep for each source. This setting can be overridden for
+individual sources in the \fBserver\fP and \fBrefclock\fP
+directives. The default value is 0, which disables the configurable limit. The
+useful range is 4 to 64.
+.sp
+As a special case, setting \fBmaxsamples\fP to 1 disables frequency tracking in
+order to make the sources immediately selectable with only one sample. This can
+be useful when \fBchronyd\fP is started with the \fB\-q\fP or \fB\-Q\fP option.
+.RE
+.sp
+\fBminsamples\fP \fIsamples\fP
+.RS 4
+The \fBminsamples\fP directive sets the default minimum number of samples that
+\fBchronyd\fP should keep for each source. This setting can be overridden for
+individual sources in the \fBserver\fP and \fBrefclock\fP
+directives. The default value is 6. The useful range is 4 to 64.
+.sp
+Forcing \fBchronyd\fP to keep more samples than it would normally keep reduces
+noise in the estimated frequency and offset, but slows down the response to
+changes in the frequency and offset of the clock. The offsets in the
+\fBtracking\fP and
+\fBsourcestats\fP reports (and the \fItracking.log\fP and
+\fIstatistics.log\fP files) may be smaller than the actual offsets.
+.RE
+.sp
+\fBntsdumpdir\fP \fIdirectory\fP
+.RS 4
+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
+\fBchronyd\fP 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.
+\fI1.2.3.4.nts\fP). By default, the client does not save the cookies.
+.sp
+If the directory does not exist, it will be created automatically.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+ntsdumpdir @CHRONYVARDIR@
+.fam
+.fi
+.if n .RE
+.sp
+This directory is used also by the NTS server to save keys.
+.RE
+.sp
+\fBntsrefresh\fP \fIinterval\fP
+.RS 4
+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) and the maximum value is 2^31\-1 (68 years).
+.sp
+The interval must be longer than polling intervals of all configured NTP
+sources using NTS, otherwise the source with a longer polling interval will
+refresh the keys on each poll and no NTP packets will be exchanged.
+.RE
+.sp
+\fBntstrustedcerts\fP [\fIset\-ID\fP] \fIfile\fP|\fIdirectory\fP
+.RS 4
+This directive specifies a file or directory containing trusted certificates
+(in the PEM format) which are needed to verify certificates of NTS\-KE servers,
+e.g. certificates of trusted certificate authorities (CA) or self\-signed
+certificates of the servers.
+.sp
+The optional \fIset\-ID\fP argument is a number in the range 0 through 2^32\-1, which
+selects the set of certificates where certificates from the specified file
+or directory are added. The default ID is 0, which is a set containing the
+system\(cqs default trusted CAs (unless the \fBnosystemcert\fP directive is present).
+All other sets are empty by default. A set of certificates can be selected for
+verification of an NTS server by the \fBcertset\fP option in the \fBserver\fP or \fBpool\fP
+directive.
+.sp
+This directive can be used multiple times to specify one or more sets of
+trusted certificates, each containing certificates from one or more files
+and/or directories.
+.sp
+It is not necessary to restart \fBchronyd\fP in order to reload the certificates if
+they change (e.g. after a renewal).
+.sp
+An example is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+ntstrustedcerts /etc/pki/nts/ca1.example.net.crt
+ntstrustedcerts 1 /etc/pki/nts/ca2.example.net.crt
+ntstrustedcerts 1 /etc/pki/nts/ca3.example.net.crt
+ntstrustedcerts 2 /etc/pki/nts/ntp2.example.net.crt
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBnosystemcert\fP
+.RS 4
+This directive disables the system\(cqs default trusted CAs. Only certificates
+specified by the \fBntstrustedcerts\fP directive will be trusted.
+.RE
+.sp
+\fBnocerttimecheck\fP \fIlimit\fP
+.RS 4
+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 and should be used only as a last resort,
+preferably with a minimal number of trusted certificates. The default value is
+0, which means the time checks are always enabled.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+nocerttimecheck 1
+.fam
+.fi
+.if n .RE
+.sp
+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.
+.RE
+.sp
+\fBrefresh\fP \fIinterval\fP
+.RS 4
+This directive specifies the interval (in seconds) between refreshing IP
+addresses of NTP sources specified by hostname. If the hostname no longer
+resolves to the currently used address, it will be replaced with one of the new
+addresses to avoid using a server which is no longer intended for service, even
+if it is still responding correctly and would not be replaced as unreachable.
+Only one source is refreshed at a time. The default value is 1209600 (2 weeks)
+and the maximum value is 2^31\-1 (68 years). A value of 0 disables the periodic
+refreshment.
+.sp
+The \fBrefresh\fP command can be used to refresh all
+sources immediately.
+.RE
+.SS "Source selection"
+.sp
+\fBauthselectmode\fP \fImode\fP
+.RS 4
+NTP sources can be specified with the \fBkey\fP or \fBnts\fP option to enable
+authentication to limit the impact of man\-in\-the\-middle attacks. The
+attackers can drop or delay NTP packets (up to the \fBmaxdelay\fP and
+\fBmaxdistance\fP limits), but they cannot modify the timestamps
+contained in the packets. The attack can cause only a limited slew or step, and
+also cause the clock to run faster or slower than real time (up to double of
+the \fBmaxdrift\fP limit).
+.sp
+When authentication is enabled for an NTP source, it is important to disable
+unauthenticated NTP sources which could be exploited in the attack, e.g. if
+they are not reachable only over a trusted network. Alternatively, the source
+selection can be configured with the \fBrequire\fP and \fBtrust\fP options to
+synchronise to the unauthenticated sources only if they agree with the
+authenticated sources and might have a positive impact on the accuracy of the
+clock. Note that in this case the impact of the attack is higher. The attackers
+cannot cause an arbitrarily large step or slew, but they have more control over
+the frequency of the clock and can cause \fBchronyd\fP to report false information,
+e.g. a significantly smaller root delay and dispersion.
+.sp
+This directive determines the default selection options for authenticated and
+unauthenticated sources in order to simplify the configuration with the
+configuration file and \fBchronyc\fP commands. It sets a policy for authentication.
+.sp
+Sources specified with the \fBnoselect\fP option are ignored (not counted as either
+authenticated or unauthenticated), and they always have only the selection
+options specified in the configuration.
+.sp
+There are four modes:
+.sp
+\fBrequire\fP
+.RS 4
+Authentication is strictly required for NTP sources in this mode. If any
+unauthenticated NTP sources are specified, they will automatically get the
+\fBnoselect\fP option to prevent them from being selected for synchronisation.
+.RE
+.sp
+\fBprefer\fP
+.RS 4
+In this mode, authentication is optional and preferred. If it is enabled for at
+least one NTP source, all unauthenticated NTP sources will get the \fBnoselect\fP
+option.
+.RE
+.sp
+\fBmix\fP
+.RS 4
+In this mode, authentication is optional and synchronisation to a mix of
+authenticated and unauthenticated NTP sources is allowed. If both authenticated
+and unauthenticated NTP sources are specified, all authenticated NTP sources
+and reference clocks will get the \fBrequire\fP and \fBtrust\fP options to prevent
+synchronisation to unauthenticated NTP sources if they do not agree with a
+majority of the authenticated sources and reference clocks. This is the default
+mode.
+.RE
+.sp
+\fBignore\fP
+.RS 4
+In this mode, authentication is ignored in the source selection. All sources
+will have only the selection options that were specified in the configuration
+file, or \fBchronyc\fP command. This was the behaviour of \fBchronyd\fP in versions
+before 4.0.
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+As an example, the following configuration using the default \fBmix\fP mode:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net nts
+server ntp2.example.net nts
+server ntp3.example.net
+refclock SOCK /var/run/chrony.ttyS0.sock
+.fam
+.fi
+.if n .RE
+.sp
+is equivalent to the following configuration using the \fBignore\fP mode:
+.sp
+.if n .RS 4
+.nf
+.fam C
+authselectmode ignore
+server ntp1.example.net nts require trust
+server ntp2.example.net nts require trust
+server ntp3.example.net
+refclock /var/run/chrony.ttyS0.sock require trust
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBcombinelimit\fP \fIlimit\fP
+.RS 4
+When \fBchronyd\fP has multiple sources available for synchronisation, it has to
+select one source as the synchronisation source. The measured offsets and
+frequencies of the system clock relative to the other sources, however, can be
+combined with the selected source to improve the accuracy of the system clock.
+.sp
+The \fBcombinelimit\fP directive limits which sources are included in the combining
+algorithm. Their synchronisation distance has to be shorter than the distance
+of the selected source multiplied by the value of the limit. Also, their
+measured frequencies have to be close to the frequency of the selected source.
+If the selected source was specified with the \fBprefer\fP option, it can be
+combined only with other sources specified with this option.
+.sp
+By default, the limit is 3. Setting the limit to 0 effectively disables the
+source combining algorithm and only the selected source will be used to control
+the system clock.
+.RE
+.sp
+\fBmaxdistance\fP \fIdistance\fP
+.RS 4
+The \fBmaxdistance\fP directive sets the maximum root distance of a source to be
+acceptable for synchronisation of the clock. Sources that have a distance
+larger than the specified distance will be rejected. The distance estimates the
+maximum error of the source. It includes the root dispersion and half of the
+root delay (round\-trip time) accumulated on the path to the primary source.
+.sp
+By default, the maximum root distance is 3 seconds.
+.sp
+Setting \fBmaxdistance\fP to a larger value can be useful to allow synchronisation
+with a server that only has a very infrequent connection to its sources and can
+accumulate a large dispersion between updates of its clock.
+.RE
+.sp
+\fBmaxjitter\fP \fIjitter\fP
+.RS 4
+The \fBmaxjitter\fP directive sets the maximum allowed jitter of the sources to not
+be rejected by the source selection algorithm. This prevents synchronisation
+with sources that have a small root distance, but their time is too variable.
+.sp
+By default, the maximum jitter is 1 second.
+.RE
+.sp
+\fBminsources\fP \fIsources\fP
+.RS 4
+The \fBminsources\fP directive sets the minimum number of sources that need to be
+considered as selectable in the source selection algorithm before the local
+clock is updated. The default value is 1.
+.sp
+Setting this option to a larger number can be used to improve the reliability.
+More sources will have to agree with each other and the clock will not be
+updated when only one source (which could be serving incorrect time) is
+reachable.
+.RE
+.sp
+\fBreselectdist\fP \fIdistance\fP
+.RS 4
+When \fBchronyd\fP selects a synchronisation source from available sources, it
+will prefer the one with the shortest synchronisation distance. However, to
+avoid frequent reselecting when there are sources with similar distance, a
+fixed distance is added to the distance for sources that are currently not
+selected. This can be set with the \fBreselectdist\fP directive. By default, the
+distance is 100 microseconds.
+.RE
+.sp
+\fBstratumweight\fP \fIdistance\fP
+.RS 4
+The \fBstratumweight\fP directive sets how much distance should be added per
+stratum to the synchronisation distance when \fBchronyd\fP selects the
+synchronisation source from available sources.
+.sp
+By default, the weight is 0.001 seconds. This means that the stratum of the sources
+in the selection process matters only when the differences between the
+distances are in milliseconds.
+.RE
+.SS "System clock"
+.sp
+\fBclockprecision\fP \fIprecision\fP
+.RS 4
+The \fBclockprecision\fP directive specifies the precision of the system clock (in
+seconds). It is used by \fBchronyd\fP to estimate the minimum noise in NTP
+measurements and randomise low\-order bits of timestamps in NTP responses. By
+default, the precision is measured on start as the minimum time to read the
+clock.
+.sp
+The measured value works well in most cases. However, it generally
+overestimates the precision and it can be sensitive to the CPU speed, which can
+change over time to save power. In some cases with a high\-precision clocksource
+(e.g. the Time Stamp Counter of the CPU) and hardware timestamping, setting the
+precision on the server to a smaller value can improve stability of clients\*(Aq
+NTP measurements. The server\(cqs precision is reported on clients by the
+\fBntpdata\fP command.
+.sp
+An example setting the precision to 8 nanoseconds is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+clockprecision 8e\-9
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBcorrtimeratio\fP \fIratio\fP
+.RS 4
+When \fBchronyd\fP is slewing the system clock to correct an offset, the rate at
+which it is slewing adds to the frequency error of the clock. On all supported
+systems, with the exception of macOS 12 and earlier, this rate can be
+controlled.
+.sp
+The \fBcorrtimeratio\fP directive sets the ratio between the duration in which the
+clock is slewed for an average correction according to the source history and
+the interval in which the corrections are done (usually the NTP polling
+interval). Corrections larger than the average take less time and smaller
+corrections take more time, the amount of the correction and the correction
+time are inversely proportional.
+.sp
+Increasing \fBcorrtimeratio\fP improves the overall frequency error of the system
+clock, but increases the overall time error as the corrections take longer.
+.sp
+By default, the ratio is set to 3, the time accuracy of the clock is preferred
+over its frequency accuracy.
+.sp
+The maximum allowed slew rate can be set by the \fBmaxslewrate\fP
+directive. The current remaining correction is shown in the
+\fBtracking\fP report as the \fBSystem time\fP value.
+.RE
+.sp
+\fBdriftfile\fP \fIfile\fP
+.RS 4
+One of the main activities of the \fBchronyd\fP program is to work out the rate at
+which the system clock gains or loses time relative to real time.
+.sp
+Whenever \fBchronyd\fP computes a new value of the gain or loss rate, it is desirable
+to record it somewhere. This allows \fBchronyd\fP to begin compensating the system
+clock at that rate whenever it is restarted, even before it has had a chance to
+obtain an equally good estimate of the rate during the new run. (This process
+can take many minutes, at least.)
+.sp
+The \fBdriftfile\fP directive allows a file to be specified into which \fBchronyd\fP
+can store the rate information. Two parameters are recorded in the file. The
+first is the rate at which the system clock gains or loses time, expressed in
+parts per million, with gains positive. Therefore, a value of 100.0 indicates
+that when the system clock has advanced by a second, it has gained 100
+microseconds in reality (so the true time has only advanced by 999900
+microseconds). The second is an estimate of the error bound around the first
+value in which the true rate actually lies.
+.sp
+An example of the driftfile directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+driftfile @CHRONYVARDIR@/drift
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBfallbackdrift\fP \fImin\-interval\fP \fImax\-interval\fP
+.RS 4
+Fallback drifts are long\-term averages of the system clock drift calculated
+over exponentially increasing intervals. They are used to avoid quickly
+drifting away from true time when the clock was not updated for a longer period
+of time and there was a short\-term deviation in the drift before the updates
+stopped.
+.sp
+The directive specifies the minimum and maximum interval since the last clock
+update to switch between fallback drifts. They are defined as a power of 2 (in
+seconds). The syntax is as follows:
+.sp
+.if n .RS 4
+.nf
+.fam C
+fallbackdrift 16 19
+.fam
+.fi
+.if n .RE
+.sp
+In this example, the minimum interval is 16 (18 hours) and the maximum interval is
+19 (6 days). The system clock frequency will be set to the first fallback 18
+hours after last clock update, to the second after 36 hours, and so on. This
+might be a good setting to cover frequency changes due to daily and weekly
+temperature fluctuations. When the frequency is set to a fallback, the state of
+the clock will change to \(oqNot synchronised\(cq.
+.sp
+By default (or if the specified maximum or minimum is 0), no fallbacks are used
+and the clock frequency changes only with new measurements from NTP sources,
+reference clocks, or manual input.
+.RE
+.sp
+\fBleapsecmode\fP \fImode\fP
+.RS 4
+A leap second is an adjustment that is occasionally applied to UTC to keep it
+close to the mean solar time. When a leap second is inserted, the last day of
+June or December has an extra second 23:59:60.
+.sp
+For computer clocks that is a problem. The Unix time is defined as number of
+seconds since 00:00:00 UTC on 1 January 1970 without leap seconds. The system
+clock cannot have time 23:59:60, every minute has 60 seconds and every day has
+86400 seconds by definition. The inserted leap second is skipped and the clock
+is suddenly ahead of UTC by one second. The \fBleapsecmode\fP directive selects how
+that error is corrected. There are four options:
+.sp
+\fBsystem\fP
+.RS 4
+When inserting a leap second, the kernel steps the system clock backwards by
+one second when the clock gets to 00:00:00 UTC. When deleting a leap second, it
+steps forward by one second when the clock gets to 23:59:59 UTC. This is the
+default mode when the system driver supports leap seconds (i.e. all supported
+systems with the exception of macOS 12 and earlier).
+.RE
+.sp
+\fBstep\fP
+.RS 4
+This is similar to the \fBsystem\fP mode, except the clock is stepped by
+\fBchronyd\fP instead of the kernel. It can be useful to avoid bugs in the kernel
+code that would be executed in the \fBsystem\fP mode. This is the default mode
+when the system driver does not support leap seconds.
+.RE
+.sp
+\fBslew\fP
+.RS 4
+The clock is corrected by slewing started at 00:00:00 UTC when a leap second
+is inserted or 23:59:59 UTC when a leap second is deleted. This might be
+preferred over the \fBsystem\fP and \fBstep\fP modes when applications running on the
+system are sensitive to jumps in the system time and it is acceptable that the
+clock will be off for a longer time. On Linux with the default
+\fBmaxslewrate\fP value the correction takes 12 seconds.
+.RE
+.sp
+\fBignore\fP
+.RS 4
+No correction is applied to the clock for the leap second. The clock will be
+corrected later in normal operation when new measurements are made and the
+estimated offset includes the one second error. This option is particularly
+useful when multiple \fBchronyd\fP instances are running on the system, one
+controlling the system clock and others started with the \fB\-x\fP option, which
+should rely on the first instance to correct the system clock and ignore it for
+the correction of their own NTP clock running on top of the system clock.
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+When serving time to NTP clients that cannot be configured to correct their
+clocks for a leap second by slewing, or to clients that would correct at
+slightly different rates when it is necessary to keep them close together, the
+\fBslew\fP mode can be combined with the \fBsmoothtime\fP directive to
+enable a server leap smear.
+.sp
+When smearing a leap second, the leap status is suppressed on the server and
+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
+leap second in exactly the same way for synchronisation.
+.sp
+This feature must be used carefully, because the server is intentionally not
+serving its best estimate of the true time.
+.sp
+A recommended configuration to enable a server leap smear is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+leapsecmode slew
+maxslewrate 1000
+smoothtime 400 0.001024 leaponly
+.fam
+.fi
+.if n .RE
+.sp
+The first directive is necessary to disable the clock step which would reset
+the smoothing process. The second directive limits the slewing rate of the
+local clock to 1000 ppm, which improves the stability of the smoothing process
+when the local correction starts and ends. The third directive enables the
+server time smoothing process. It will start when the clock gets to 00:00:00
+UTC and it will take 62500 seconds (about 17.36 hours) to finish. The frequency
+offset will be changing by 0.001024 ppm per second and will reach a maximum of
+32 ppm in 31250 seconds. The \fBleaponly\fP option makes the duration of the leap
+smear constant and allows the clients to safely synchronise with multiple
+identically configured leap smearing servers.
+.sp
+The duration of the leap smear can be calculated from the specified wander as
+.sp
+.if n .RS 4
+.nf
+.fam C
+duration = sqrt(4 / wander)
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBleapsectz\fP \fItimezone\fP
+.RS 4
+This directive specifies a timezone in the system timezone database which
+\fBchronyd\fP can use to determine when will the next leap second occur and what is
+the current offset between TAI and UTC. It will periodically check if 23:59:59
+and 23:59:60 are valid times in the timezone. This normally works with the
+\fIright/UTC\fP timezone.
+.sp
+When a leap second is announced, the timezone needs to be updated at least 12
+hours before the leap second. It is not necessary to restart \fBchronyd\fP.
+.sp
+This directive is useful with reference clocks and other time sources which do
+not announce leap seconds, or announce them too late for an NTP server to
+forward them to its own clients. Clients of leap smearing servers must not
+use this directive.
+.sp
+It is also useful when the system clock is required to have correct TAI\-UTC
+offset. Note that the offset is set only when leap seconds are handled by the
+kernel, i.e. \fBleapsecmode\fP is set to \fBsystem\fP.
+.sp
+The specified timezone is not used as an exclusive source of information about
+leap seconds. If a majority of time sources announce on the last day of June or
+December that a leap second should be inserted or deleted, it will be accepted
+even if it is not included in the timezone.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+leapsectz right/UTC
+.fam
+.fi
+.if n .RE
+.sp
+The following shell command verifies that the timezone contains leap seconds
+and can be used with this directive:
+.sp
+.if n .RS 4
+.nf
+.fam C
+$ TZ=right/UTC date \-d \*(AqDec 31 2008 23:59:60\*(Aq
+Wed Dec 31 23:59:60 UTC 2008
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBmakestep\fP \fIthreshold\fP \fIlimit\fP
+.RS 4
+Normally \fBchronyd\fP will cause the system to gradually correct any time offset,
+by slowing down or speeding up the clock as required. In certain situations,
+e.g. when \fBchronyd\fP is initially started, the system clock might be so far
+adrift that this slewing process would take a very long time to correct the
+system clock.
+.sp
+This directive forces \fBchronyd\fP to step the system clock if the adjustment is
+larger than a threshold value, but only if there were no more clock updates
+since \fBchronyd\fP was started than the specified limit. A negative value disables
+the limit.
+.sp
+On most systems it is desirable to step the system clock only on boot, before
+starting programs that rely on time advancing monotonically forwards.
+.sp
+An example of the use of this directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+makestep 0.1 3
+.fam
+.fi
+.if n .RE
+.sp
+This would step the system clock if the adjustment is larger than 0.1 seconds, but
+only in the first three clock updates.
+.RE
+.sp
+\fBmaxchange\fP \fIoffset\fP \fIstart\fP \fIignore\fP
+.RS 4
+This directive sets the maximum offset to be accepted on a clock update. The
+offset is measured relative to the current estimate of the true time, which is
+different from the system time if a previous slew did not finish.
+.sp
+The check is enabled after the specified number of clock updates to allow a
+large initial offset to be corrected on start. Offsets larger than the
+specified maximum will be ignored for the specified number of times. Another
+large offset will cause \fBchronyd\fP to give up and exit. A negative value
+can be used to disable the limit to ignore all large offsets. A syslog message
+will be generated when an offset is ignored or it causes the exit.
+.sp
+An example of the use of this directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+maxchange 1000 1 2
+.fam
+.fi
+.if n .RE
+.sp
+After the first clock update, \fBchronyd\fP will check the offset on every clock
+update, it will ignore two adjustments larger than 1000 seconds and exit on
+another one.
+.RE
+.sp
+\fBmaxclockerror\fP \fIerror\-in\-ppm\fP
+.RS 4
+The \fBmaxclockerror\fP directive sets the maximum assumed frequency error that the
+system clock can gain on its own between clock updates. It describes the
+stability of the clock.
+.sp
+By default, the maximum error is 1 ppm.
+.sp
+Typical values for \fIerror\-in\-ppm\fP might be 10 for a low quality clock and 0.1
+for a high quality clock using a temperature compensated crystal oscillator.
+.RE
+.sp
+\fBmaxdrift\fP \fIdrift\-in\-ppm\fP
+.RS 4
+This directive specifies the maximum assumed drift (frequency error) of the
+system clock. It limits the frequency adjustment that \fBchronyd\fP is allowed to
+use to correct the measured drift. It is an additional limit to the maximum
+adjustment that can be set by the system driver (100000 ppm on Linux, 500 ppm
+on FreeBSD, NetBSD, and macOS 10.13+, 32500 ppm on illumos).
+.sp
+By default, the maximum assumed drift is 500000 ppm, i.e. the adjustment is
+limited by the system driver rather than this directive.
+.RE
+.sp
+\fBmaxupdateskew\fP \fIskew\-in\-ppm\fP
+.RS 4
+One of \fBchronyd\fP\*(Aqs tasks is to work out how fast or slow the computer\(cqs clock
+runs relative to its reference sources. In addition, it computes an estimate of
+the error bounds around the estimated value.
+.sp
+If the range of error is too large, it probably indicates that the measurements
+have not settled down yet, and that the estimated gain or loss rate is not very
+reliable.
+.sp
+The \fBmaxupdateskew\fP directive sets the threshold for determining whether an
+estimate might be so unreliable that it should not be used. By default, the
+threshold is 1000 ppm.
+.sp
+Typical values for \fIskew\-in\-ppm\fP might be 100 for NTP sources polled over a
+wireless network, and 10 or smaller for sources on a local wired network.
+.sp
+It should be noted that this is not the only means of protection against using
+unreliable estimates. At all times, \fBchronyd\fP keeps track of both the estimated
+gain or loss rate, and the error bound on the estimate. When a new estimate is
+generated following another measurement from one of the sources, a weighted
+combination algorithm is used to update the existing estimate. If it has
+significantly smaller error bounds than the new estimate, the existing estimate
+will dominate in the new combined value.
+.RE
+.sp
+\fBmaxslewrate\fP \fIrate\-in\-ppm\fP
+.RS 4
+The \fBmaxslewrate\fP directive sets the maximum rate at which \fBchronyd\fP is allowed
+to slew the time. It limits the slew rate controlled by the correction time
+ratio (which can be set by the \fBcorrtimeratio\fP directive) and
+is effective only on systems where \fBchronyd\fP is able to control the rate (i.e.
+all supported systems with the exception of macOS 12 or earlier).
+.sp
+For each system there is a maximum frequency offset of the clock that can be set
+by the driver. On Linux it is 100000 ppm, on FreeBSD, NetBSD and macOS 10.13+ it
+is 5000 ppm, and on illumos it is 32500 ppm. Also, due to a kernel limitation,
+setting \fBmaxslewrate\fP on FreeBSD, NetBSD, macOS 10.13+ to a value between 500
+ppm and 5000 ppm will effectively set it to 500 ppm.
+.sp
+By default, the maximum slew rate is set to 83333.333 ppm (one twelfth).
+.RE
+.sp
+\fBtempcomp\fP \fIfile\fP \fIinterval\fP \fIT0\fP \fIk0\fP \fIk1\fP \fIk2\fP, \fBtempcomp\fP \fIfile\fP \fIinterval\fP \fIpoints\-file\fP
+.RS 4
+Normally, changes in the rate of drift of the system clock are caused mainly by
+changes in the temperature of the crystal oscillator on the motherboard.
+.sp
+If there are temperature measurements available from a sensor close to the
+oscillator, the \fBtempcomp\fP directive can be used to compensate for the changes
+in the temperature and improve the stability and accuracy of the clock.
+.sp
+The result depends on many factors, including the resolution of the sensor, the
+amount of noise in the measurements, the polling interval of the time source,
+the compensation update interval, how well the compensation is specified, and
+how close the sensor is to the oscillator. When it is working well, the
+frequency reported in the \fItracking.log\fP file is more stable and the maximum
+reached offset is smaller.
+.sp
+There are two forms of the directive. The first one has six parameters: a path
+to the file containing the current temperature from the sensor (in text
+format), the compensation update interval (in seconds), and temperature
+coefficients \fIT0\fP, \fIk0\fP, \fIk1\fP, \fIk2\fP.
+.sp
+The frequency compensation is calculated (in ppm) as
+.sp
+.if n .RS 4
+.nf
+.fam C
+comp = k0 + (T \- T0) * k1 + (T \- T0)^2 * k2
+.fam
+.fi
+.if n .RE
+.sp
+The result has to be between \-10 ppm and 10 ppm, otherwise the measurement is
+considered invalid and will be ignored. The \fIk0\fP coefficient can be adjusted to
+keep the compensation in that range.
+.sp
+An example of the use is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+tempcomp /sys/class/hwmon/hwmon0/temp2_input 30 26000 0.0 0.000183 0.0
+.fam
+.fi
+.if n .RE
+.sp
+The measured temperature will be read from the file in the Linux sysfs
+filesystem every 30 seconds. When the temperature is 26000 (26 degrees
+Celsius), the frequency correction will be zero. When it is 27000 (27 degrees
+Celsius), the clock will be set to run faster by 0.183 ppm, etc.
+.sp
+The second form has three parameters: the path to the sensor file, the update
+interval, and a path to a file containing a list of (temperature, compensation)
+points, from which the compensation is linearly interpolated or extrapolated.
+.sp
+An example is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+tempcomp /sys/class/hwmon/hwmon0/temp2_input 30 /etc/chrony.tempcomp
+.fam
+.fi
+.if n .RE
+.sp
+where the \fI/etc/chrony.tempcomp\fP file could have
+.sp
+.if n .RS 4
+.nf
+.fam C
+20000 1.0
+21000 0.64
+22000 0.36
+23000 0.16
+24000 0.04
+25000 0.0
+26000 0.04
+27000 0.16
+28000 0.36
+29000 0.64
+30000 1.0
+.fam
+.fi
+.if n .RE
+.sp
+Valid measurements with corresponding compensations are logged to the
+\fItempcomp.log\fP file if enabled by the \fBlog tempcomp\fP directive.
+.RE
+.SS "NTP server"
+.sp
+\fBallow\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+The \fBallow\fP directive is used to designate a particular subnet from which NTP
+clients are allowed to access the computer as an NTP server. It also controls
+access of NTS\-KE clients when NTS is enabled on the server.
+.sp
+The default is that no clients are allowed access, i.e. \fBchronyd\fP operates
+purely as an NTP client. If the \fBallow\fP directive is used, \fBchronyd\fP will be
+both a client of its servers, and a server to other clients.
+.sp
+This directive can be used multiple times.
+.sp
+Examples of the use of the directive are as follows:
+.sp
+.if n .RS 4
+.nf
+.fam C
+allow 1.2.3.4
+allow 3.4.5.0/24
+allow 3.4.5
+allow 2001:db8::/32
+allow 0/0
+allow ::/0
+allow
+.fam
+.fi
+.if n .RE
+.sp
+The first directive allows access from an IPv4 address. The second directive
+allows access from all computers in an IPv4 subnet specified in the CIDR
+notation. The third directive specifies the same subnet using a simpler
+notation where the prefix length is determined by the number of dots. The
+fourth directive specifies an IPv6 subnet. The fifth and sixth directives allow
+access from all IPv4 and IPv6 addresses respectively. The seventh directive
+allows access from all addresses (both IPv4 or IPv6).
+.sp
+A second form of the directive, \fBallow all\fP, has a greater effect, depending on
+the ordering of directives in the configuration file. To illustrate the effect,
+consider the two examples:
+.sp
+.if n .RS 4
+.nf
+.fam C
+allow 1.2.3.4
+deny 1.2.3.0/24
+allow 1.2.0.0/16
+.fam
+.fi
+.if n .RE
+.sp
+and
+.sp
+.if n .RS 4
+.nf
+.fam C
+allow 1.2.3.4
+deny 1.2.3.0/24
+allow all 1.2.0.0/16
+.fam
+.fi
+.if n .RE
+.sp
+In the first example, the effect is the same regardless of what order the three
+directives are given in. So the \fI1.2.0.0/16\fP subnet is allowed access, except
+for the \fI1.2.3.0/24\fP subnet, which is denied access, however the host \fI1.2.3.4\fP
+is allowed access.
+.sp
+In the second example, the \fBallow all 1.2.0.0/16\fP directive overrides the
+effect of \fIany\fP previous directive relating to a subnet within the specified
+subnet. Within a configuration file this capability is probably rather moot;
+however, it is of greater use for reconfiguration at run\-time via \fBchronyc\fP
+with the \fBallow all\fP command.
+.sp
+The rules are internally represented as a tree of tables with one level per
+four bits of the IPv4 or IPv6 address. The order of the \fBallow\fP and \fBdeny\fP
+directives matters if they modify the same records of one table, i.e. if one
+subnet is included in the other subnet and their prefix lengths are at the same
+level. For example, \fI1.2.3.0/28\fP and \fI1.2.3.0/29\fP are in different tables, but
+\fI1.2.3.0/25\fP and \fI1.2.3.0/28\fP are in the same table. The configuration can be
+verified for individual addresses with the \fBaccheck\fP
+command in \fBchronyc\fP.
+.sp
+A hostname can be specified in the directives instead of the IP address, but
+the name must be resolvable when \fBchronyd\fP is started, i.e. the network is
+already up and DNS is working. If the hostname resolves to multiple addresses,
+only the first address (in the order returned by the system resolver) will be
+allowed or denied.
+.sp
+Note, if the \fBinitstepslew\fP directive is used in the
+configuration file, each of the computers listed in that directive must allow
+client access by this computer for it to work.
+.RE
+.sp
+\fBdeny\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+This is similar to the \fBallow\fP directive, except that it denies NTP
+and NTS\-KE client access to a particular subnet or host, rather than allowing
+it.
+.sp
+The syntax is identical and the directive can be used multiple times too.
+.sp
+There is also a \fBdeny all\fP directive with similar behaviour to the \fBallow all\fP
+directive.
+.RE
+.sp
+\fBbindaddress\fP \fIaddress\fP
+.RS 4
+The \fBbindaddress\fP directive binds the sockets on which \fBchronyd\fP listens for
+NTP and NTS\-KE requests to a local address of the computer. On systems other
+than Linux, the address of the computer needs to be already configured when
+\fBchronyd\fP is started.
+.sp
+An example of the use of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+bindaddress 192.168.1.1
+.fam
+.fi
+.if n .RE
+.sp
+Currently, for each of the IPv4 and IPv6 protocols, only one \fBbindaddress\fP
+directive can be specified. Therefore, it is not useful on computers which
+should serve NTP on multiple network interfaces.
+.RE
+.sp
+\fBbinddevice\fP \fIinterface\fP
+.RS 4
+The \fBbinddevice\fP directive binds the NTP and NTS\-KE server sockets to a network
+device specified by the interface name. This directive can specify only one
+interface and it is supported on Linux only.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+binddevice eth0
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBbroadcast\fP \fIinterval\fP \fIaddress\fP [\fIport\fP]
+.RS 4
+The \fBbroadcast\fP directive is used to declare a broadcast address to which
+chronyd should send packets in the NTP broadcast mode (i.e. make \fBchronyd\fP act
+as a broadcast server). Broadcast clients on that subnet will be able to
+synchronise.
+.sp
+This directive can be used multiple times to specify multiple addresses.
+.sp
+The syntax is as follows:
+.sp
+.if n .RS 4
+.nf
+.fam C
+broadcast 32 192.168.1.255
+broadcast 64 192.168.2.255 12123
+broadcast 64 ff02::101
+.fam
+.fi
+.if n .RE
+.sp
+In the first example, the destination port defaults to UDP port 123 (the normal NTP
+port). In the second example, the destination port is specified as 12123. The
+first parameter in each case (32 or 64 respectively) is the interval in seconds
+between broadcast packets being sent. The second parameter in each case is the
+broadcast address to send the packet to. This should correspond to the
+broadcast address of one of the network interfaces on the computer where
+\fBchronyd\fP is running.
+.sp
+You can have more than 1 \fBbroadcast\fP directive if you have more than 1 network
+interface onto which you want to send NTP broadcast packets.
+.sp
+\fBchronyd\fP itself cannot act as a broadcast client; it must always be configured
+as a point\-to\-point client by defining specific NTP servers and peers. This
+broadcast server feature is intended for providing a time source to other NTP
+implementations.
+.sp
+If \fBntpd\fP is used as the broadcast client, it will try to measure the
+round\-trip delay between the server and client with normal client mode packets.
+Thus, the broadcast subnet should also be the subject of an \fBallow\fP
+directive.
+.RE
+.sp
+\fBclientloglimit\fP \fIlimit\fP
+.RS 4
+This directive specifies the maximum amount of memory that \fBchronyd\fP is allowed
+to allocate for logging of client accesses and the state that \fBchronyd\fP as an
+NTP server needs to support the interleaved mode for its clients. The default
+limit is 524288 bytes, which enables monitoring of up to 4096 IP addresses at
+the same time and holding NTP timestamps for up to 4096 clients using the
+interleaved mode (depending on uniformity of their polling interval). The
+number of addresses and timestamps is always a power of 2. The maximum
+effective value is 2147483648 (2 GB), which corresponds to 16777216 addresses
+and timestamps.
+.sp
+An example of the use of this directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+clientloglimit 1048576
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBnoclientlog\fP
+.RS 4
+This directive, which takes no arguments, specifies that client accesses are
+not to be logged. Normally they are logged, allowing statistics to be reported
+using the \fBclients\fP command in \fBchronyc\fP. This option
+also effectively disables server support for the NTP interleaved mode.
+.RE
+.sp
+\fBlocal\fP [\fIoption\fP]...
+.RS 4
+The \fBlocal\fP directive enables a local reference mode, which allows \fBchronyd\fP
+operating as an NTP server to appear synchronised to real time (from the
+viewpoint of clients polling it), even when it was never synchronised or
+the last update of the clock happened a long time ago.
+.sp
+This directive is normally used in an isolated network, where computers are
+required to be synchronised to one another, but not necessarily to real time.
+The server can be kept vaguely in line with real time by manual input.
+.sp
+The \fBlocal\fP directive has the following options:
+.sp
+\fBstratum\fP \fIstratum\fP
+.RS 4
+This option sets the stratum of the server which will be reported to clients
+when the local reference is active. The specified value is in the range 1
+through 15, and the default value is 10. It should be larger than the maximum
+expected stratum in the network when external NTP servers are accessible.
+.sp
+Stratum 1 indicates a computer that has a true real\-time reference directly
+connected to it (e.g. GPS, atomic clock, etc.), such computers are expected to
+be very close to real time. Stratum 2 computers are those which have a stratum
+1 server; stratum 3 computers have a stratum 2 server and so on. A value
+of 10 indicates that the clock is so many hops away from a reference clock that
+its time is fairly unreliable.
+.RE
+.sp
+\fBdistance\fP \fIdistance\fP
+.RS 4
+This option sets the threshold for the root distance which will activate the local
+reference. If \fBchronyd\fP was synchronised to some source, the local reference
+will not be activated until its root distance reaches the specified value (the
+rate at which the distance is increasing depends on how well the clock was
+tracking the source). The default value is 1 second.
+.sp
+The current root distance can be calculated from root delay and root dispersion
+(reported by the \fBtracking\fP command in \fBchronyc\fP) as:
+.sp
+.if n .RS 4
+.nf
+.fam C
+distance = delay / 2 + dispersion
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBorphan\fP
+.RS 4
+This option enables a special \(oqorphan\(cq mode, where sources with stratum equal
+to the local \fIstratum\fP are assumed to not serve real time. They are ignored
+unless no other source is selectable and their reference IDs are smaller than
+the local reference ID.
+.sp
+This allows multiple servers in the network to use the same \fBlocal\fP
+configuration and to be synchronised to one another, without confusing clients
+that poll more than one server. Each server needs to be configured to poll all
+other servers with the \fBlocal\fP directive. This ensures only the server with the
+smallest reference ID has the local reference active and others are
+synchronised to it. If that server stops responding, the server with the second
+smallest reference ID will take over when its local reference mode activates
+(root distance reaches the threshold configured by the \fBdistance\fP option).
+.sp
+The \fBorphan\fP mode is compatible with the \fBntpd\fP\*(Aqs orphan mode (enabled by the
+\fBtos orphan\fP command).
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+local stratum 10 orphan distance 0.1
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBntpsigndsocket\fP \fIdirectory\fP
+.RS 4
+This directive specifies the location of the Samba \fBntp_signd\fP socket when it
+is running as a Domain Controller (DC). If \fBchronyd\fP is compiled with this
+feature, responses to MS\-SNTP clients will be signed by the \fBsmbd\fP daemon.
+.sp
+Note that MS\-SNTP requests are not authenticated and any client that is allowed
+to access the server by the \fBallow\fP directive, or the
+\fBallow\fP command in \fBchronyc\fP, can get an MS\-SNTP
+response signed with a trust account\(cqs password and try to crack the password
+in a brute\-force attack. Access to the server should be carefully controlled.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+ntpsigndsocket /var/lib/samba/ntp_signd
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBntsport\fP \fIport\fP
+.RS 4
+This directive specifies the TCP port on which \fBchronyd\fP will provide the NTS
+Key Establishment (NTS\-KE) service. The default port is 4460.
+.sp
+The port will be open only when a certificate and key is specified by the
+\fBntsservercert\fP and \fBntsserverkey\fP directives.
+.RE
+.sp
+\fBntsservercert\fP \fIfile\fP
+.RS 4
+This directive specifies a file containing a certificate in the PEM format
+for \fBchronyd\fP to operate as an NTS server. The file should also include
+any intermediate certificates that the clients will need to validate the
+server\(cqs certificate. The file needs to be readable by the user under which
+\fBchronyd\fP is running after dropping root privileges.
+.sp
+This directive can be used multiple times to specify multiple certificates for
+different names of the server.
+.sp
+The files are loaded only once. \fBchronyd\fP needs to be restarted in order to
+load a renewed certificate. The \fBntsdumpdir\fP and
+\fBdumpdir\fP directives with the \fB\-r\fP option of \fBchronyd\fP are
+recommended for a near\-seamless server operation.
+.RE
+.sp
+\fBntsserverkey\fP \fIfile\fP
+.RS 4
+This directive specifies a file containing a private key in the PEM format
+for \fBchronyd\fP to operate as an NTS server. The file needs to be readable by
+the user under which \fBchronyd\fP is running after dropping root privileges. For
+security reasons, it should not be readable by other users.
+.sp
+This directive can be used multiple times to specify multiple keys. The number
+of keys must be the same as the number of certificates and the corresponding
+files must be specified in the same order.
+.RE
+.sp
+\fBntsprocesses\fP \fIprocesses\fP
+.RS 4
+This directive specifies how many helper processes will \fBchronyd\fP 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
+\fBchronyd\fP process. The default value is 1.
+.RE
+.sp
+\fBmaxntsconnections\fP \fIconnections\fP
+.RS 4
+This directive specifies the maximum number of concurrent NTS\-KE connections
+per process that the NTS server will accept. The default value is 100. The
+maximum practical value is half of the system \fBFD_SETSIZE\fP constant (usually
+1024).
+.RE
+.sp
+\fBntsdumpdir\fP \fIdirectory\fP
+.RS 4
+This directive specifies a directory where \fBchronyd\fP 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 \fIntskeys\fP. When \fBchronyd\fP 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.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+ntsdumpdir @CHRONYVARDIR@
+.fam
+.fi
+.if n .RE
+.sp
+This directory is used also by the NTS client to save NTS cookies.
+.RE
+.sp
+\fBntsntpserver\fP \fIhostname\fP
+.RS 4
+This directive specifies the hostname (as a fully qualified domain name) 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
+\fBntsrotate\fP 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.
+.RE
+.sp
+\fBntsrotate\fP \fIinterval\fP
+.RS 4
+This directive specifies the rotation interval (in seconds) of the server key
+which encrypts the NTS cookies. New keys are generated automatically from the
+\fI/dev/urandom\fP device. The server keeps two previous keys to give the clients
+time to get new cookies encrypted by the latest key. The interval is measured
+as the server\(cqs operating time, i.e. the actual interval can be longer if
+\fBchronyd\fP is not running continuously. The default interval is 604800 seconds
+(1 week). The maximum value is 2^31\-1 (68 years).
+.sp
+The automatic rotation of the keys can be disabled by setting \fBntsrotate\fP to 0.
+In this case the keys are assumed to be managed externally. \fBchronyd\fP will not
+save the keys to the \fIntskeys\fP file and will reload the keys from the file when
+the \fBrekey\fP command is issued in \fBchronyc\fP. The file can
+be periodically copied from another server running \fBchronyd\fP (which does
+not have \fBntsrotate\fP set to 0) in order to have one or more servers dedicated
+to NTS\-KE. The file includes the subsequent key to which the NTS\-KE server will
+switch on the next rotation, i.e. the process copying and reloading the file
+does not need to be timed precisely (it can be delayed by up to one rotation
+interval). The NTS\-KE servers need to be configured with the
+\fBntsntpserver\fP directive to point the clients to the right NTP
+server.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+ntsrotate 2592000
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBport\fP \fIport\fP
+.RS 4
+This option allows you to configure the port on which \fBchronyd\fP will listen for
+NTP requests. The port will be open only when an address is allowed by the
+\fBallow\fP directive or the \fBallow\fP command in
+\fBchronyc\fP, an NTP peer is configured, or the broadcast server mode is enabled.
+.sp
+The default value is 123, the standard NTP port. If set to 0, \fBchronyd\fP will
+never open the server port and will operate strictly in a client\-only mode. The
+source port used in NTP client requests can be set by the
+\fBacquisitionport\fP directive.
+.RE
+.sp
+\fBratelimit\fP [\fIoption\fP]...
+.RS 4
+This directive enables response rate limiting for NTP packets. Its purpose is
+to reduce network traffic with misconfigured or broken NTP clients that are
+polling the server too frequently. The limits are applied to individual IP
+addresses. If multiple clients share one IP address (e.g. multiple hosts behind
+NAT), the sum of their traffic will be limited. If a client that increases its
+polling rate when it does not receive a reply is detected, its rate limiting
+will be temporarily suspended to avoid increasing the overall amount of
+traffic. The maximum number of IP addresses which can be monitored at the same
+time depends on the memory limit set by the \fBclientloglimit\fP
+directive.
+.sp
+The \fBratelimit\fP directive supports a number of options (which can be defined
+in any order):
+.sp
+\fBinterval\fP \fIinterval\fP
+.RS 4
+This option sets the minimum interval between responses. It is defined as a
+power of 2 in seconds. The default value is 3 (8 seconds). The minimum value
+is \-19 (524288 packets per second) and the maximum value is 12 (one packet per
+4096 seconds). Note that with values below \-4 the rate limiting is coarse
+(responses are allowed in bursts, even if the interval between them is shorter
+than the specified interval).
+.RE
+.sp
+\fBburst\fP \fIresponses\fP
+.RS 4
+This option sets the maximum number of responses that can be sent in a burst,
+temporarily exceeding the limit specified by the \fBinterval\fP option. This is
+useful for clients that make rapid measurements on start (e.g. \fBchronyd\fP with
+the \fBiburst\fP option). The default value is 8. The minimum value is 1 and the
+maximum value is 255.
+.RE
+.sp
+\fBleak\fP \fIrate\fP
+.RS 4
+This option sets the rate at which responses are randomly allowed even if the
+limits specified by the \fBinterval\fP and \fBburst\fP options are exceeded. This is
+necessary to prevent an attacker who is sending requests with a spoofed
+source address from completely blocking responses to that address. The leak
+rate is defined as a power of 1/2 and it is 2 by default, i.e. on average at
+least every fourth request has a response. The minimum value is 1 and the
+maximum value is 4.
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+An example use of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+ratelimit interval 1 burst 16
+.fam
+.fi
+.if n .RE
+.sp
+This would reduce the response rate for IP addresses sending packets on average
+more than once per 2 seconds, or sending packets in bursts of more than 16
+packets, by up to 75% (with default \fBleak\fP of 2).
+.RE
+.sp
+\fBntsratelimit\fP [\fIoption\fP]...
+.RS 4
+This directive enables rate limiting of NTS\-KE requests. It is similar to the
+\fBratelimit\fP directive, except the default interval is 6
+(1 connection per 64 seconds).
+.sp
+An example of the use of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+ntsratelimit interval 3 burst 1
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBsmoothtime\fP \fImax\-freq\fP \fImax\-wander\fP [\fBleaponly\fP]
+.RS 4
+The \fBsmoothtime\fP directive can be used to enable smoothing of the time that
+\fBchronyd\fP serves to its clients to make it easier for them to track it and keep
+their clocks close together even when large offset or frequency corrections are
+applied to the server\(cqs clock, for example after being offline for a longer
+time.
+.sp
+BE WARNED: The server is intentionally not serving its best estimate of the
+true time. If a large offset has been accumulated, it can take a very long time
+to smooth it out. This directive should be used only when the clients are not
+configured to also poll another NTP server, because they could reject this
+server as a falseticker or fail to select a source completely.
+.sp
+The smoothing process is implemented with a quadratic spline function with two
+or three pieces. It is independent from any slewing applied to the local system
+clock, but the accumulated offset and frequency will be reset when the clock is
+corrected by stepping, e.g. by the \fBmakestep\fP directive or the
+\fBmakestep\fP command in \fBchronyc\fP. The process can be
+reset without stepping the clock by the \fBsmoothtime
+reset\fP command.
+.sp
+The first two arguments of the directive are the maximum frequency offset of
+the smoothed time to the tracked NTP time (in ppm) and the maximum rate at
+which the frequency offset is allowed to change (in ppm per second). \fBleaponly\fP
+is an optional third argument which enables a mode where only leap seconds are
+smoothed out and normal offset and frequency changes are ignored. The \fBleaponly\fP
+option is useful in a combination with the \fBleapsecmode slew\fP
+directive to allow the clients to use multiple time smoothing servers safely.
+.sp
+The smoothing process is activated automatically when 1/10000 of the estimated
+skew of the local clock falls below the maximum rate of frequency change. It
+can be also activated manually by the \fBsmoothtime
+activate\fP command, which is particularly useful when the clock is
+synchronised only with manual input and the skew is always larger than the
+threshold. The \fBsmoothing\fP command can be used to
+monitor the process.
+.sp
+An example suitable for clients using \fBntpd\fP and 1024 second polling interval
+could be:
+.sp
+.if n .RS 4
+.nf
+.fam C
+smoothtime 400 0.001
+.fam
+.fi
+.if n .RE
+.sp
+An example suitable for clients using \fBchronyd\fP on Linux could be:
+.sp
+.if n .RS 4
+.nf
+.fam C
+smoothtime 50000 0.01
+.fam
+.fi
+.if n .RE
+.RE
+.SS "Command and monitoring access"
+.sp
+\fBbindcmdaddress\fP \fIaddress\fP
+.RS 4
+The \fBbindcmdaddress\fP directive specifies a local IP address to which \fBchronyd\fP
+will bind the UDP socket listening for monitoring command packets (issued
+by \fBchronyc\fP). On systems other than Linux, the address of the interface needs
+to be already configured when \fBchronyd\fP is started.
+.sp
+This directive can also change the path of the Unix domain command socket,
+which is used by \fBchronyc\fP to send configuration commands. The socket must be
+in a directory that is accessible only by the root or \fIchrony\fP user. The
+directory will be created on start if it does not exist. The compiled\-in default
+path of the socket is \fI@CHRONYRUNDIR@/chronyd.sock\fP. The socket can be
+disabled by setting the path to \fI/\fP.
+.sp
+By default, \fBchronyd\fP binds the UDP sockets to the addresses \fI127.0.0.1\fP and
+\fI::1\fP (i.e. the loopback interface). This blocks all access except from
+localhost. To listen for command packets on all interfaces, you can add the
+lines:
+.sp
+.if n .RS 4
+.nf
+.fam C
+bindcmdaddress 0.0.0.0
+bindcmdaddress ::
+.fam
+.fi
+.if n .RE
+.sp
+to the configuration file.
+.sp
+For each of the IPv4, IPv6, and Unix domain protocols, only one
+\fBbindcmdaddress\fP directive can be specified.
+.sp
+An example that sets the path of the Unix domain command socket is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+bindcmdaddress /var/run/chrony/chronyd.sock
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBbindcmddevice\fP \fIinterface\fP
+.RS 4
+The \fBbindcmddevice\fP directive binds the UDP command sockets to a network device
+specified by the interface name. This directive can specify only one interface
+and it is supported on Linux only.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+bindcmddevice eth0
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBcmdallow\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+This is similar to the \fBallow\fP directive, except that it allows
+monitoring access (rather than NTP client access) to a particular subnet or
+host. (By \(oqmonitoring access\(cq is meant that \fBchronyc\fP can be run on those
+hosts and retrieve monitoring data from \fBchronyd\fP on this computer.)
+.sp
+The syntax is identical to the \fBallow\fP directive.
+.sp
+There is also a \fBcmdallow all\fP directive with similar behaviour to the \fBallow
+all\fP directive (but applying to monitoring access in this case, of course).
+.sp
+Note that \fBchronyd\fP has to be configured with the
+\fBbindcmdaddress\fP directive to not listen only on the
+loopback interface to actually allow remote access.
+.RE
+.sp
+\fBcmddeny\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+This is similar to the \fBcmdallow\fP directive, except that it denies
+monitoring access to a particular subnet or host, rather than allowing it.
+.sp
+The syntax is identical.
+.sp
+There is also a \fBcmddeny all\fP directive with similar behaviour to the \fBcmdallow
+all\fP directive.
+.RE
+.sp
+\fBcmdport\fP \fIport\fP
+.RS 4
+The \fBcmdport\fP directive allows the port that is used for run\-time monitoring
+(via the \fBchronyc\fP program) to be altered from its default (323). If set to 0,
+\fBchronyd\fP will not open the port, which disables remote \fBchronyc\fP access (with
+a non\-default \fBbindcmdaddress\fP) and local access for unprivileged users. It
+does not disable the Unix domain command socket.
+.sp
+An example shows the syntax:
+.sp
+.if n .RS 4
+.nf
+.fam C
+cmdport 257
+.fam
+.fi
+.if n .RE
+.sp
+This would make \fBchronyd\fP use UDP 257 as its command port. (\fBchronyc\fP would
+need to be run with the \fB\-p 257\fP option to inter\-operate correctly.)
+.RE
+.sp
+\fBcmdratelimit\fP [\fIoption\fP]...
+.RS 4
+This directive enables response rate limiting for command packets. It is
+similar to the \fBratelimit\fP directive, except responses to
+localhost are never limited and the default interval is \-4 (16 packets per
+second).
+.sp
+An example of the use of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+cmdratelimit interval 2
+.fam
+.fi
+.if n .RE
+.RE
+.SS "Real\-time clock (RTC)"
+.sp
+\fBhwclockfile\fP \fIfile\fP
+.RS 4
+The \fBhwclockfile\fP directive sets the location of the adjtime file which is
+used by the \fBhwclock\fP program on Linux. \fBchronyd\fP parses the file to find out
+if the RTC keeps local time or UTC. It overrides the \fBrtconutc\fP
+directive.
+.sp
+The compiled\-in default value is \*(Aq\fI@DEFAULT_HWCLOCK_FILE@\fP\*(Aq.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+hwclockfile /etc/adjtime
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBrtcautotrim\fP \fIthreshold\fP
+.RS 4
+The \fBrtcautotrim\fP directive is used to keep the RTC close to the system clock
+automatically. When the system clock is synchronised and the estimated error
+between the two clocks is larger than the specified threshold, \fBchronyd\fP will
+trim the RTC as if the \fBtrimrtc\fP command in \fBchronyc\fP
+was issued. The trimming operation is accurate to only about 1 second, which is
+the minimum effective threshold.
+.sp
+This directive is effective only with the \fBrtcfile\fP directive.
+.sp
+An example of the use of this directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+rtcautotrim 30
+.fam
+.fi
+.if n .RE
+.sp
+This would set the threshold error to 30 seconds.
+.RE
+.sp
+\fBrtcdevice\fP \fIdevice\fP
+.RS 4
+The \fBrtcdevice\fP directive sets the path to the device file for accessing the
+RTC. The default path is \fI@DEFAULT_RTC_DEVICE@\fP.
+.RE
+.sp
+\fBrtcfile\fP \fIfile\fP
+.RS 4
+The \fBrtcfile\fP directive defines the name of the file in which \fBchronyd\fP can
+save parameters associated with tracking the accuracy of the RTC.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+rtcfile @CHRONYVARDIR@/rtc
+.fam
+.fi
+.if n .RE
+.sp
+\fBchronyd\fP saves information in this file when it exits and when the \fBwritertc\fP
+command is issued in \fBchronyc\fP. The information saved is the RTC\(cqs error at
+some epoch, that epoch (in seconds since January 1 1970), and the rate at which
+the RTC gains or loses time.
+.sp
+So far, the support for real\-time clocks is limited; their code is even more
+system\-specific than the rest of the software. You can only use the RTC
+facilities (the \fBrtcfile\fP directive and the \fB\-s\fP command\-line
+option to \fBchronyd\fP) if the following three conditions apply:
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+You are running Linux.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+The kernel is compiled with extended real\-time clock support (i.e. the
+\fI/dev/rtc\fP device is capable of doing useful things).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+You do not have other applications that need to make use of \fI/dev/rtc\fP at all.
+.RE
+.RE
+.sp
+\fBrtconutc\fP
+.RS 4
+\fBchronyd\fP assumes by default that the RTC keeps local time (including any
+daylight saving changes). This is convenient on PCs running Linux which are
+dual\-booted with Windows.
+.sp
+If you keep the RTC on local time and your computer is off when daylight saving
+(summer time) starts or ends, the computer\(cqs system time will be one hour in
+error when you next boot and start chronyd.
+.sp
+An alternative is for the RTC to keep Universal Coordinated Time (UTC). This
+does not suffer from the 1 hour problem when daylight saving starts or ends.
+.sp
+If the \fBrtconutc\fP directive appears, it means the RTC is required to keep UTC.
+The directive takes no arguments. It is equivalent to specifying the \fB\-u\fP
+switch to the Linux \fBhwclock\fP program.
+.sp
+Note that this setting is overridden by the \fBhwclockfile\fP file
+and is not relevant for the \fBrtcsync\fP directive.
+.RE
+.sp
+\fBrtcsync\fP
+.RS 4
+The \fBrtcsync\fP directive enables a mode where the system time is periodically
+copied to the RTC and \fBchronyd\fP does not try to track its drift. This directive
+cannot be used with the \fBrtcfile\fP directive.
+.sp
+On Linux, the RTC copy is performed by the kernel every 11 minutes.
+.sp
+On macOS, \fBchronyd\fP will perform the RTC copy every 60 minutes
+when the system clock is in a synchronised state.
+.sp
+On other systems this directive does nothing.
+.RE
+.SS "Logging"
+.sp
+\fBlog\fP [\fIoption\fP]...
+.RS 4
+The \fBlog\fP directive indicates that certain information is to be logged.
+The log files are written to the directory specified by the \fBlogdir\fP
+directive. A banner is periodically written to the files to indicate the
+meanings of the columns.
+.sp
+\fBrawmeasurements\fP
+.RS 4
+This option logs the raw NTP measurements and related information to a file
+called \fImeasurements.log\fP. An entry is made for each packet received from the
+source. This can be useful when debugging a problem. An example line (which
+actually appears as a single line in the file) from the log file is shown
+below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+2016\-11\-09 05:40:50 203.0.113.15 N 2 111 111 1111 10 10 1.0 \(rs
+ \-4.966e\-03 2.296e\-01 1.577e\-05 1.615e\-01 7.446e\-03 CB00717B 4B D K
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Date [2015\-10\-13]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Hour:Minute:Second. Note that the date\-time pair is expressed in UTC, not the
+local time zone. [05:40:50]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+IP address of server or peer from which measurement came [203.0.113.15]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+Leap status (\fIN\fP means normal, \fI+\fP means that the last minute of the current
+month has 61 seconds, \fI\-\fP means that the last minute of the month has 59
+seconds, \fI?\fP means the remote computer is not currently synchronised.) [N]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+Stratum of remote computer. [2]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 6.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 6." 4.2
+.\}
+RFC 5905 tests 1 through 3 (1=pass, 0=fail) [111]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 7.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 7." 4.2
+.\}
+RFC 5905 tests 5 through 7 (1=pass, 0=fail) [111]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 8.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 8." 4.2
+.\}
+Results of the \fBmaxdelay\fP, \fBmaxdelayratio\fP, and \fBmaxdelaydevratio\fP (or
+\fBmaxdelayquant\fP) tests, and a test for synchronisation loop (1=pass,
+0=fail). The first test from these four also checks the server precision,
+response time, and whether an interleaved response is acceptable for
+synchronisation. [1111]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 9.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 9." 4.2
+.\}
+Local poll [10]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 10.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 10." 4.2
+.\}
+Remote poll [10]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 11.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 11." 4.2
+.\}
+\(oqScore\(cq (an internal score within each polling level used to decide when to
+increase or decrease the polling level. This is adjusted based on number of
+measurements currently being used for the regression algorithm). [1.0]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 12.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 12." 4.2
+.\}
+The estimated local clock error (\fItheta\fP in RFC 5905). Positive indicates
+that the local clock is slow of the remote source. [\-4.966e\-03]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 13.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 13." 4.2
+.\}
+The peer delay (\fIdelta\fP in RFC 5905). [2.296e\-01]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 14.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 14." 4.2
+.\}
+The peer dispersion (\fIepsilon\fP in RFC 5905). [1.577e\-05]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 15.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 15." 4.2
+.\}
+The root delay (\fIDELTA\fP in RFC 5905). [1.615e\-01]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 16.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 16." 4.2
+.\}
+The root dispersion (\fIEPSILON\fP in RFC 5905). [7.446e\-03]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 17.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 17." 4.2
+.\}
+Reference ID of the server\(cqs source as a hexadecimal number. [CB00717B]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 18.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 18." 4.2
+.\}
+NTP mode of the received packet (\fI1\fP=active peer, \fI2\fP=passive peer,
+\fI4\fP=server, \fIB\fP=basic, \fII\fP=interleaved). [4B]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 19.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 19." 4.2
+.\}
+Source of the local transmit timestamp
+(\fID\fP=daemon, \fIK\fP=kernel, \fIH\fP=hardware). [D]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 20.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 20." 4.2
+.\}
+Source of the local receive timestamp
+(\fID\fP=daemon, \fIK\fP=kernel, \fIH\fP=hardware). [K]
+.RE
+.RE
+.sp
+\fBmeasurements\fP
+.RS 4
+This option is identical to the \fBrawmeasurements\fP option, except it logs only
+valid measurements from synchronised sources, i.e. measurements which passed
+the RFC 5905 tests 1 through 7. This can be useful for producing graphs of the
+source\(cqs performance.
+.RE
+.sp
+\fBstatistics\fP
+.RS 4
+This option logs information about the regression processing to a file called
+\fIstatistics.log\fP. An example line (which actually appears as a single line in
+the file) from the log file is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+2016\-08\-10 05:40:50 203.0.113.15 6.261e\-03 \-3.247e\-03 \(rs
+ 2.220e\-03 1.874e\-06 1.080e\-06 7.8e\-02 16 0 8 0.00
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Date [2015\-07\-22]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Hour:Minute:Second. Note that the date\-time pair is expressed in
+UTC, not the local time zone. [05:40:50]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+IP address of server or peer from which measurement comes [203.0.113.15]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+The estimated standard deviation of the measurements from the source (in
+seconds). [6.261e\-03]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+The estimated offset of the source (in seconds, positive means the local
+clock is estimated to be fast, in this case). [\-3.247e\-03]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 6.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 6." 4.2
+.\}
+The estimated standard deviation of the offset estimate (in seconds).
+[2.220e\-03]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 7.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 7." 4.2
+.\}
+The estimated rate at which the local clock is gaining or losing time
+relative to the source (in seconds per second, positive means the local clock
+is gaining). This is relative to the compensation currently being applied to
+the local clock, \fInot\fP to the local clock without any compensation.
+[1.874e\-06]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 8.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 8." 4.2
+.\}
+The estimated error in the rate value (in seconds per second). [1.080e\-06].
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 9.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 9." 4.2
+.\}
+The ratio of |old_rate \- new_rate| / old_rate_error. Large values
+indicate the statistics are not modelling the source very well. [7.8e\-02]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 10.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 10." 4.2
+.\}
+The number of measurements currently being used for the regression
+algorithm. [16]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 11.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 11." 4.2
+.\}
+The new starting index (the oldest sample has index 0; this is the method
+used to prune old samples when it no longer looks like the measurements fit a
+linear model). [0, i.e. no samples discarded this time]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 12.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 12." 4.2
+.\}
+The number of runs. The number of runs of regression residuals with the same
+sign is computed. If this is too small it indicates that the measurements are
+no longer represented well by a linear model and that some older samples need
+to be discarded. The number of runs for the data that is being retained is
+tabulated. Values of approximately half the number of samples are expected.
+[8]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 13.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 13." 4.2
+.\}
+The estimated or configured asymmetry of network jitter on the path to the
+source which was used to correct the measured offsets. The asymmetry can be
+between \-0.5 and +0.5. A negative value means the delay of packets sent to
+the source is more variable than the delay of packets sent from the source
+back. [0.00, i.e. no correction for asymmetry]
+.RE
+.RE
+.sp
+\fBselection\fP
+.RS 4
+This option logs information about selection of sources for synchronisation to
+a file called \fIselection.log\fP. Note that the rate of entries written to this
+file grows quadratically with the number of specified sources (each measurement
+triggers the selection for all sources). An example line (which actually
+appears as a single line in the file) from the log file is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+2022\-05\-01 02:01:20 203.0.113.15 * \-\-\-\-\- 377 1.00 \(rs
+ 4.228e+01 \-1.575e\-04 1.239e\-04
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Date [2022\-05\-01]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Hour:Minute:Second. Note that the date\-time pair is expressed in
+UTC, not the local time zone. [02:01:20]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+IP address or reference ID of the source. [203.0.113.15]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+State of the source indicated with one of the following symbols. [*]
+.sp
+
+.RS 4
+Not considered selectable for synchronisation:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIN\fP \- has the \fBnoselect\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIs\fP \- is not synchronised.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIM\fP \- does not have enough measurements.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fId\fP \- has a root distance larger than the maximum distance (configured by the
+\fBmaxdistance\fP directive).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI~\fP \- has a jitter larger than the maximum jitter (configured by the
+\fBmaxjitter\fP directive).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIw\fP \- waits for other sources to get out of the \fIM\fP state.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIS\fP \- has older measurements than other sources.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIO\fP \- has a stratum equal or larger than the orphan stratum (configured by
+the \fBlocal\fP directive).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIT\fP \- does not fully agree with sources that have the \fBtrust\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIx\fP \- does not agree with other sources (falseticker).
+.RE
+.RE
+.sp
+
+.RS 4
+Considered selectable for synchronisation, but not currently used:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIW\fP \- waits for other sources to be selectable (required by the
+\fBminsources\fP directive, or the \fBrequire\fP option of
+another source).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIP\fP \- another selectable source is preferred due to the \fBprefer\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIU\fP \- waits for a new measurement (after selecting a different best source).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fID\fP \- has, or recently had, a root distance which is too large to be combined
+with other sources (configured by the \fBcombinelimit\fP
+directive).
+.RE
+.RE
+.sp
+
+.RS 4
+Used for synchronisation of the local clock:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI+\fP \- combined with the best source.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI*\fP \- selected as the best source to update the reference data (e.g. root
+delay, root dispersion).
+.RE
+.RE
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+Current effective selection options of the source. which can be different
+from the configured options due to the authentication selection mode
+(configured by the \fBauthselectmode\fP directive). [\-\-\-\-\-]
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIN\fP indicates the \fBnoselect\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIP\fP indicates the \fBprefer\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIT\fP indicates the \fBtrust\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIR\fP indicates the \fBrequire\fP option.
+.RE
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 6.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 6." 4.2
+.\}
+Reachability register printed as an octal number. The register has 8 bits and
+is updated on every received or missed packet from the source. A value of 377
+indicates that a valid reply was received for all from the last eight
+transmissions. [377]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 7.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 7." 4.2
+.\}
+Current score against the source in the \fI*\fP state. The scoring system avoids
+frequent reselection when multiple sources have a similar root distance. A
+value larger than 1 indicates this source was better than the \fI*\fP source in
+recent selections. If the score reaches 10, the best source will be reselected
+and the scores will be reset to 1. [1.00]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 8.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 8." 4.2
+.\}
+Interval since the last measurement of the source in seconds. [4.228e+01]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 9.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 9." 4.2
+.\}
+Lower endpoint of the interval which was expected to contain the true offset
+of the local clock determined by the root distance of the source. [\-1.575e\-04]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 10.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 10." 4.2
+.\}
+Upper endpoint of the interval which was expected to contain the true offset
+of the local clock determined by the root distance of the source. [1.239e\-04]
+.RE
+.RE
+.sp
+\fBtracking\fP
+.RS 4
+This option logs changes to the estimate of the system\(cqs gain or loss rate, and
+any slews made, to a file called \fItracking.log\fP. An example line (which
+actually appears as a single line in the file) from the log file is shown
+below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+2017\-08\-22 13:22:36 203.0.113.15 2 \-3.541 0.075 \-8.621e\-06 N \(rs
+ 2 2.940e\-03 \-2.084e\-04 1.534e\-02 3.472e\-04 8.304e\-03
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows (the quantities in square brackets are the
+values from the example line above) :
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Date [2017\-08\-22]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Hour:Minute:Second. Note that the date\-time pair is expressed in UTC, not the
+local time zone. [13:22:36]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+The IP address of the server or peer to which the local system is synchronised.
+[203.0.113.15]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+The stratum of the local system. [2]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+The local system frequency (in ppm, positive means the local system runs fast
+of UTC). [\-3.541]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 6.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 6." 4.2
+.\}
+The error bounds on the frequency (in ppm). [0.075]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 7.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 7." 4.2
+.\}
+The estimated local offset at the epoch, which is normally corrected by
+slewing the local clock (in seconds, positive indicates the clock is fast of
+UTC). [\-8.621e\-06]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 8.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 8." 4.2
+.\}
+Leap status (\fIN\fP means normal, \fI+\fP means that the last minute of this month
+has 61 seconds, \fI\-\fP means that the last minute of the month has 59 seconds,
+\fI?\fP means the clock is not currently synchronised.) [N]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 9.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 9." 4.2
+.\}
+The number of combined sources. [2]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 10.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 10." 4.2
+.\}
+The estimated standard deviation of the combined offset (in seconds).
+[2.940e\-03]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 11.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 11." 4.2
+.\}
+The remaining offset correction from the previous update (in seconds,
+positive means the system clock is slow of UTC). [\-2.084e\-04]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 12.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 12." 4.2
+.\}
+The total of the network path delays to the reference clock to which
+the local clock is ultimately synchronised (in seconds). [1.534e\-02]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 13.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 13." 4.2
+.\}
+The total dispersion accumulated through all the servers back to the
+reference clock to which the local clock is ultimately synchronised
+(in seconds). [3.472e\-04]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 14.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 14." 4.2
+.\}
+The maximum estimated error of the system clock in the interval since the
+previous update (in seconds). It includes the offset, remaining offset
+correction, root delay, and dispersion from the previous update with the
+dispersion which accumulated in the interval. [8.304e\-03]
+.RE
+.RE
+.sp
+\fBrtc\fP
+.RS 4
+This option logs information about the system\(cqs real\-time clock. An example
+line (which actually appears as a single line in the file) from the \fIrtc.log\fP
+file is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+2015\-07\-22 05:40:50 \-0.037360 1 \-0.037434\(rs
+ \-37.948 12 5 120
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows (the quantities in square brackets are the
+values from the example line above):
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Date [2015\-07\-22]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Hour:Minute:Second. Note that the date\-time pair is expressed in UTC, not the
+local time zone. [05:40:50]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+The measured offset between the RTC and the system clock in seconds.
+Positive indicates that the RTC is fast of the system time [\-0.037360].
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+Flag indicating whether the regression has produced valid coefficients.
+(1 for yes, 0 for no). [1]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+Offset at the current time predicted by the regression process. A large
+difference between this value and the measured offset tends to indicate that
+the measurement is an outlier with a serious measurement error. [\-0.037434]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 6.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 6." 4.2
+.\}
+The rate at which the RTC is losing or gaining time relative to the system
+clock. In ppm, with positive indicating that the RTC is gaining time.
+[\-37.948]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 7.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 7." 4.2
+.\}
+The number of measurements used in the regression. [12]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 8.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 8." 4.2
+.\}
+The number of runs of regression residuals of the same sign. Low values
+indicate that a straight line is no longer a good model of the measured data
+and that older measurements should be discarded. [5]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 9.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 9." 4.2
+.\}
+The measurement interval used prior to the measurement being made (in
+seconds). [120]
+.RE
+.RE
+.sp
+\fBrefclocks\fP
+.RS 4
+This option logs the raw and filtered reference clock measurements to a file
+called \fIrefclocks.log\fP. An example line (which actually appears as a single
+line in the file) from the log file is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+2009\-11\-30 14:33:27.000000 PPS2 7 N 1 4.900000e\-07 \-6.741777e\-07 1.000e\-06
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Date [2009\-11\-30]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Hour:Minute:Second.Microsecond. Note that the date\-time pair is expressed in
+UTC, not the local time zone. [14:33:27.000000]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+Reference ID of the reference clock from which the measurement came. [PPS2]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+Sequence number of driver poll within one polling interval for raw samples,
+or \fI\-\fP for filtered samples. [7]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+Leap status (\fIN\fP means normal, \fI+\fP means that the last minute of the current
+month has 61 seconds, \fI\-\fP means that the last minute of the month has 59
+seconds). [N]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 6.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 6." 4.2
+.\}
+Flag indicating whether the sample comes from PPS source. (1 for yes,
+0 for no, or \fI\-\fP for filtered sample). [1]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 7.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 7." 4.2
+.\}
+Local clock error measured by reference clock driver, or \fI\-\fP for filtered sample.
+[4.900000e\-07]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 8.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 8." 4.2
+.\}
+Local clock error with applied corrections. Positive indicates that the local
+clock is slow. [\-6.741777e\-07]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 9.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 9." 4.2
+.\}
+Assumed dispersion of the sample. [1.000e\-06]
+.RE
+.RE
+.sp
+\fBtempcomp\fP
+.RS 4
+This option logs the temperature measurements and system rate compensations to
+a file called \fItempcomp.log\fP. An example line (which actually appears as a
+single line in the file) from the log file is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+2015\-04\-19 10:39:48 2.8000e+04 3.6600e\-01
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows (the quantities in square brackets are the values
+from the example line above):
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Date [2015\-04\-19]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Hour:Minute:Second. Note that the date\-time pair is expressed in UTC, not the
+local time zone. [10:39:48]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+Temperature read from the sensor. [2.8000e+04]
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+Applied compensation in ppm, positive means the system clock is running
+faster than it would be without the compensation. [3.6600e\-01]
+.RE
+.RE
+.RE
+.sp
+
+.RS 4
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+log measurements statistics tracking
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBlogbanner\fP \fIentries\fP
+.RS 4
+A banner is periodically written to the log files enabled by the \fBlog\fP
+directive to indicate the meanings of the columns.
+.sp
+The \fBlogbanner\fP directive specifies after how many entries in the log file
+should be the banner written. The default is 32, and 0 can be used to disable
+it entirely.
+.RE
+.sp
+\fBlogchange\fP \fIthreshold\fP
+.RS 4
+This directive sets the threshold for the adjustment of the system clock that
+will generate a syslog message. Clock errors detected via NTP packets,
+reference clocks, or timestamps entered via the
+\fBsettime\fP command of \fBchronyc\fP are logged.
+.sp
+By default, the threshold is 1 second.
+.sp
+An example of the use is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+logchange 0.1
+.fam
+.fi
+.if n .RE
+.sp
+which would cause a syslog message to be generated if a system clock error of over
+0.1 seconds starts to be compensated.
+.RE
+.sp
+\fBlogdir\fP \fIdirectory\fP
+.RS 4
+This directive specifies the directory for writing log files enabled by the
+\fBlog\fP directive. If the directory does not exist, it will be created
+automatically.
+.sp
+An example of the use of this directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+logdir /var/log/chrony
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBmailonchange\fP \fIemail\fP \fIthreshold\fP
+.RS 4
+This directive defines an email address to which mail should be sent if
+\fBchronyd\fP applies a correction exceeding a particular threshold to the system
+clock.
+.sp
+An example of the use of this directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+mailonchange root@localhost 0.5
+.fam
+.fi
+.if n .RE
+.sp
+This would send a mail message to root if a change of more than 0.5 seconds
+were applied to the system clock.
+.sp
+This directive cannot be used when a system call filter is enabled by the \fB\-F\fP
+option as the \fBchronyd\fP process will not be allowed to fork and execute the
+sendmail binary.
+.RE
+.SS "Miscellaneous"
+.sp
+\fBconfdir\fP \fIdirectory\fP...
+.RS 4
+The \fBconfdir\fP directive includes configuration files with the \fI.conf\fP suffix
+from a directory. The files are included in the lexicographical order of the
+file names.
+.sp
+Multiple directories (up to 10) can be specified with a single \fBconfdir\fP
+directive. In this case, if multiple directories contain a file with the same
+name, only the first file in the order of the specified directories will be
+included. This enables a fragmented configuration where existing fragments can
+be replaced by adding files to a different directory.
+.sp
+This directive can be used multiple times.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+confdir @SYSCONFDIR@/chrony.d
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBsourcedir\fP \fIdirectory\fP...
+.RS 4
+The \fBsourcedir\fP directive is identical to the \fBconfdir\fP directive, except the
+configuration files have the \fI.sources\fP suffix, they can only specify NTP
+sources (i.e. the \fBserver\fP, \fBpool\fP, and \fBpeer\fP directives), they are expected
+to have all lines terminated by the newline character, and they can be
+reloaded by the \fBreload sources\fP command in
+\fBchronyc\fP. It is particularly useful with dynamic sources like NTP servers
+received from a DHCP server, which can be written to a file specific to the
+network interface by a networking script.
+.sp
+This directive can be used multiple times.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+sourcedir /var/run/chrony\-dhcp
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBinclude\fP \fIpattern\fP
+.RS 4
+The \fBinclude\fP directive includes a configuration file, or multiple configuration
+files if a wildcard pattern is specified. Unlike with the \fBconfdir\fP directive,
+the full name of the files needs to be specified and at least one file is
+required to exist.
+.sp
+This directive can be used multiple times.
+.sp
+An example of the directive is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+include @SYSCONFDIR@/chrony.d/*.conf
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBhwtimestamp\fP \fIinterface\fP [\fIoption\fP]...
+.RS 4
+This directive enables hardware timestamping of NTP packets sent to and
+received from the specified network interface. The network interface controller
+(NIC) uses its own clock to accurately timestamp the actual transmissions and
+receptions, avoiding processing and queueing delays in the kernel, network
+driver, and hardware. This can significantly improve the accuracy of the
+timestamps and the measured offset, which is used for synchronisation of the
+system clock. In order to get the best results, both sides receiving and
+sending NTP packets (i.e. server and client, or two peers) need to use HW
+timestamping. If the server or peer supports the interleaved mode, it needs to
+be enabled by the \fBxleave\fP option in the \fBserver\fP or the
+\fBpeer\fP directive.
+.sp
+This directive is supported on Linux 3.19 and newer. The NIC must support HW
+timestamping, which can be verified with the \fBethtool \-T\fP command. The list of
+capabilities should include \fIhardware\-raw\-clock\fP, \fIhardware\-transmit\fP, and
+\fIhardware\-receive\fP. The receive filter \fIall\fP, or \fIntp\fP, is necessary for
+timestamping of received NTP packets. Timestamping of packets received on
+bridged and bonded interfaces is supported on Linux 4.13 and newer. If HW
+timestamping does not work for received packets, \fBchronyd\fP will use kernel
+receive timestamps instead. Transmit\-only HW timestamping can still be useful
+to improve stability of the synchronisation.
+.sp
+\fBchronyd\fP does not synchronise the NIC clock. It assumes the clock is running
+free. Multiple instances of \fBchronyd\fP can use the same interface with enabled
+HW timestamping. Applications which need HW timestamping with a synchronised
+clock (e.g. a PTP daemon) should use a virtual clock running on top of the
+physical clock created by writing to \fI/sys/class/ptp/ptpX/n_vclocks\fP. This
+feature is available on Linux 5.14 and newer.
+.sp
+If the kernel supports software timestamping, it will be enabled for all
+interfaces automatically.
+.sp
+The source of timestamps (i.e. hardware, kernel, or daemon) is indicated on the
+client side in the \fImeasurements.log\fP file (if enabled by the \fBlog\fP
+directive) and the \fBntpdata\fP report. On the server
+side, the number of served timestamps from each source is provided in the
+\fBserverstats\fP report.
+.sp
+This directive can be used multiple times to enable HW timestamping on multiple
+interfaces. If the specified interface is \fI*\fP, \fBchronyd\fP will try to enable HW
+timestamping on all available interfaces.
+.sp
+The \fBhwtimestamp\fP directive has the following options:
+.sp
+\fBminpoll\fP \fIpoll\fP
+.RS 4
+This option specifies the minimum interval between readings of the NIC clock.
+It\(cqs defined as a power of 2. It should correspond to the minimum polling
+interval of all NTP sources and the minimum expected polling interval of NTP
+clients. The default value is 0 (1 second), the minimum value is \-6 (1/64th
+of a second), and the maximum value is 20 (about 12 days).
+.RE
+.sp
+\fBmaxpoll\fP \fIpoll\fP
+.RS 4
+This option specifies the maximum interval between readings of the NIC clock,
+as a power of 2. The default value is \fBminpoll\fP + 1, i.e. 1 (2 seconds) with
+the default \fBminpoll\fP of 0. The minimum and maximum values are the same as with
+the \fBminpoll\fP option.
+.RE
+.sp
+\fBminsamples\fP \fIsamples\fP
+.RS 4
+This option specifies the minimum number of readings kept for tracking of the
+NIC clock. The default value is 2.
+.RE
+.sp
+\fBmaxsamples\fP \fIsamples\fP
+.RS 4
+This option specifies the maximum number of readings kept for tracking of the
+NIC clock. The default value is 16.
+.RE
+.sp
+\fBprecision\fP \fIprecision\fP
+.RS 4
+This option specifies the assumed precision of reading of the NIC clock. The
+default value is 100e\-9 (100 nanoseconds).
+.RE
+.sp
+\fBtxcomp\fP \fIcompensation\fP
+.RS 4
+This option specifies the difference in seconds between the actual transmission
+time at the physical layer and the reported transmit timestamp. This value will
+be added to transmit timestamps obtained from the NIC. The default value is 0.
+.RE
+.sp
+\fBrxcomp\fP \fIcompensation\fP
+.RS 4
+This option specifies the difference in seconds between the reported receive
+timestamp and the actual reception time at the physical layer. This value will
+be subtracted from receive timestamps obtained from the NIC. The default value
+is 0.
+.RE
+.sp
+\fBnocrossts\fP
+.RS 4
+Some hardware can precisely cross timestamp the NIC clock with the system
+clock. This option disables the use of the cross timestamping.
+.RE
+.sp
+\fBrxfilter\fP \fIfilter\fP
+.RS 4
+This option selects the receive timestamping filter. The \fIfilter\fP can be one of
+the following:
+.sp
+\fIall\fP
+.RS 4
+Enables timestamping of all received packets.
+.RE
+.sp
+\fIntp\fP
+.RS 4
+Enables timestamping of received NTP packets.
+.RE
+.sp
+\fIptp\fP
+.RS 4
+Enables timestamping of received PTP packets.
+.RE
+.sp
+\fInone\fP
+.RS 4
+Disables timestamping of received packets.
+.RE
+.RE
+.sp
+
+.RS 4
+The most specific filter for timestamping of NTP packets supported by the NIC
+is selected by default. Some NICs can timestamp PTP packets only. By default,
+they will be configured with the \fInone\fP filter and expected to provide hardware
+timestamps for transmitted packets only. Timestamping of PTP packets is useful
+with NTP\-over\-PTP enabled by the \fBptpport\fP
+directive, or when another application is receiving PTP packets on the
+interface. Forcing timestamping of all packets with the \fIall\fP filter could be
+useful if the NIC supported both the \fIall\fP and \fIntp\fP filters, and it should
+timestamp both NTP and PTP packets, or NTP packets on a different UDP port.
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+Examples of the directive are:
+.sp
+.if n .RS 4
+.nf
+.fam C
+hwtimestamp eth0
+hwtimestamp eth1 txcomp 300e\-9 rxcomp 645e\-9
+hwtimestamp *
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBhwtstimeout\fP \fItimeout\fP
+.RS 4
+If hardware timestamping is used with a close NTP server, or the NIC or its
+driver is slow in providing the transmit timestamp of NTP requests, a response
+from the server can be received before the transmit timestamp of the request.
+To avoid calculating the offset with a less accurate transmit timestamp,
+\fBchronyd\fP can save the response for later processing and wait for the hardware
+transmit timestamp. There is no guarantee that the timestamp will be provided
+(NICs typically have a limited rate of transmit timestamping). This directive
+configures how long should \fBchronyd\fP wait for the timestamp after receiving a
+valid response from the server. If a second valid response is received from the
+server while waiting for the timestamp, they will be both processed
+immediately.
+.sp
+The default value is 0.001 seconds, which should be sufficient with most
+hardware. If you frequently see kernel transmit timestamps in the
+\fImeasurements.log\fP file or \fBntpdata\fP report, and it is
+not a server handling a high rate of requests in the interleaved mode on the
+same interface (which would compete with timestamping of the server\(cqs own
+requests), increasing the timeout to 0.01 or possibly even longer might help.
+Note that the maximum timeout is limited by the NTP polling interval.
+.RE
+.sp
+\fBkeyfile\fP \fIfile\fP
+.RS 4
+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.
+.sp
+The format of the directive is shown in the example below:
+.sp
+.if n .RS 4
+.nf
+.fam C
+keyfile @SYSCONFDIR@/chrony.keys
+.fam
+.fi
+.if n .RE
+.sp
+The argument is simply the name of the file containing the ID\-key pairs. The
+format of the file is shown below:
+.sp
+.if n .RS 4
+.nf
+.fam C
+10 tulip
+11 hyacinth
+20 MD5 ASCII:crocus
+25 SHA1 HEX:933F62BE1D604E68A81B557F18CFA200483F5B70
+30 AES128 HEX:7EA62AE64D190114D46D5A082F948EC1
+31 AES256 HEX:37DDCBC67BB902BCB8E995977FAB4D2B5642F5B32EBCEEE421921D97E5CBFE39
+ ...
+.fam
+.fi
+.if n .RE
+.sp
+Each line consists of an ID, optional type, and key.
+.sp
+The ID can be any positive integer in the range 1 through 2^32\-1.
+.sp
+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 \fBMD5\fP, which is always
+supported.
+If \fBchronyd\fP was built with enabled support for hashing using a crypto library
+(Nettle, GnuTLS, NSS, or LibTomCrypt), the following functions are available: \fBMD5\fP,
+\fBSHA1\fP, \fBSHA256\fP, \fBSHA384\fP, \fBSHA512\fP. Depending on which library and version is
+\fBchronyd\fP using, some of the following hash functions and ciphers may
+also be available:
+\fBSHA3\-224\fP, \fBSHA3\-256\fP, \fBSHA3\-384\fP, \fBSHA3\-512\fP, \fBTIGER\fP, \fBWHIRLPOOL\fP, \fBAES128\fP,
+\fBAES256\fP.
+.sp
+The key can be specified as a string of ASCII characters not containing white
+space with an optional \fBASCII:\fP prefix, or as a hexadecimal number with the
+\fBHEX:\fP 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).
+.sp
+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 \fBHEX:\fP prefix). \fBchronyd\fP will log a warning to syslog on start if a
+source is specified in the configuration file with a key shorter than 80 bits.
+.sp
+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.
+.sp
+The \fBkeygen\fP command of \fBchronyc\fP can be used to
+generate random keys for the key file. By default, it generates 160\-bit MD5 or
+SHA1 keys.
+.sp
+For security reasons, the file should be readable only by root and the user
+under which \fBchronyd\fP is normally running (to allow \fBchronyd\fP to re\-read the
+file when the \fBrekey\fP command is issued by \fBchronyc\fP).
+.RE
+.sp
+\fBlock_all\fP
+.RS 4
+The \fBlock_all\fP directive will lock the \fBchronyd\fP process into RAM so that it
+will never be paged out. This can result in lower and more consistent latency.
+The directive is supported on Linux, FreeBSD, NetBSD, and illumos.
+.RE
+.sp
+\fBpidfile\fP \fIfile\fP
+.RS 4
+Unless \fBchronyd\fP is started with the \fB\-Q\fP option, it writes its process ID
+(PID) to a file, and checks this file on startup to see if another \fBchronyd\fP
+might already be running on the system. By default, the file used is
+\fI@DEFAULT_PID_FILE@\fP. The \fBpidfile\fP directive allows the name to be changed,
+e.g.:
+.sp
+.if n .RS 4
+.nf
+.fam C
+pidfile /run/chronyd.pid
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBptpport\fP \fIport\fP
+.RS 4
+The \fBptpport\fP directive enables \fBchronyd\fP to send and receive NTP messages
+contained in PTP event messages (NTP\-over\-PTP) to enable hardware timestamping
+on NICs which cannot timestamp NTP packets, but can timestamp unicast PTP
+packets, and also use corrections provided by PTP one\-step end\-to\-end
+transparent clocks in network switches and routers. The port recognized by the
+NICs and PTP transparent clocks is 319 (PTP event port). The default value is 0
+(disabled).
+.sp
+The NTP\-over\-PTP support is experimental. The protocol and configuration can
+change in future. It should be used only in local networks.
+.sp
+The PTP port will be open even if \fBchronyd\fP is not configured to operate as a
+server or client. The directive does not change the default protocol of
+specified NTP sources. Each NTP source that should use NTP\-over\-PTP needs to
+be specified with the \fBport\fP option set to the PTP port. To actually enable
+hardware timestamping on NICs which can timestamp PTP packets only, the
+\fBrxfilter\fP option of the \fBhwtimestamp\fP directive needs to be set to \fIptp\fP. The
+extension field \fIF324\fP needs to be enabled to use the corrections provided by
+the PTP transparent clocks.
+.sp
+An example of client configuration is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net minpoll 0 maxpoll 0 xleave port 319 extfield F324
+hwtimestamp * rxfilter ptp
+ptpport 319
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBsched_priority\fP \fIpriority\fP
+.RS 4
+On Linux, FreeBSD, NetBSD, and illumos, the \fBsched_priority\fP directive 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 constraint policy or 1 for the policy to be
+enabled.
+.sp
+On systems other than macOS, this directive uses the \fBpthread_setschedparam()\fP
+system call to instruct the kernel to use the SCHED_FIFO first\-in, first\-out
+real\-time scheduling policy for \fBchronyd\fP with the specified priority. This
+means that whenever \fBchronyd\fP is ready to run it will run, interrupting
+whatever else is running unless it is a higher priority real\-time process. This
+should not impact performance as \fBchronyd\fP resource requirements are modest,
+but it should result in lower and more consistent latency since \fBchronyd\fP will
+not need to wait for the scheduler to get around to running it. You should not
+use this unless you really need it. The \fBpthread_setschedparam(3)\fP man page has
+more details.
+.sp
+On macOS, this directive uses the \fBthread_policy_set()\fP kernel call to
+specify real\-time scheduling. As noted above, you should not use this directive
+unless you really need it.
+.RE
+.sp
+\fBuser\fP \fIuser\fP
+.RS 4
+The \fBuser\fP directive sets the name of the system user to which \fBchronyd\fP will
+switch after start in order to drop root privileges.
+.sp
+On Linux, \fBchronyd\fP needs to be compiled with support for the \fBlibcap\fP library.
+On macOS, FreeBSD, NetBSD and illumos \fBchronyd\fP forks into two processes.
+The child process retains root privileges, but can only perform a very limited
+range of privileged system calls on behalf of the parent.
+.sp
+The compiled\-in default value is \fI@DEFAULT_USER@\fP.
+.RE
+.SH "EXAMPLES"
+.SS "NTP client with permanent connection to NTP servers"
+.sp
+This section shows how to configure \fBchronyd\fP for computers that are connected
+to the Internet (or to any network containing true NTP servers which ultimately
+derive their time from a reference clock) permanently or most of the time.
+.sp
+To operate in this mode, you will need to know the names of the NTP servers
+you want to use. You might be able to find names of suitable servers by one of
+the following methods:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+Your institution might already operate servers on its network.
+Contact your system administrator to find out.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+Your ISP probably has one or more NTP servers available for its
+customers.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+Somewhere under the NTP homepage there is a list of public
+stratum 1 and stratum 2 servers. You should find one or more servers that are
+near to you. Check that their access policy allows you to use their
+facilities.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+Use public servers from the \c
+.URL "https://www.pool.ntp.org/" "pool.ntp.org" ""
+project.
+.RE
+.sp
+Assuming that your NTP servers are called \fIntp1.example.net\fP, \fIntp2.example.net\fP
+and \fIntp3.example.net\fP, your \fIchrony.conf\fP file could contain as a minimum:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net
+server ntp2.example.net
+server ntp3.example.net
+.fam
+.fi
+.if n .RE
+.sp
+However, you will probably want to include some of the other directives. The
+\fBdriftfile\fP, \fBmakestep\fP and \fBrtcsync\fP
+might be particularly useful. Also, the \fBiburst\fP option of the
+\fBserver\fP directive is useful to speed up the initial
+synchronisation. The smallest useful configuration file would look something
+like:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net iburst
+server ntp2.example.net iburst
+server ntp3.example.net iburst
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+.fam
+.fi
+.if n .RE
+.sp
+When using a pool of NTP servers (one name is used for multiple servers which
+might change over time), it is better to specify them with the \fBpool\fP
+directive instead of multiple \fBserver\fP directives. The configuration file could
+in this case look like:
+.sp
+.if n .RS 4
+.nf
+.fam C
+pool pool.ntp.org iburst
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+.fam
+.fi
+.if n .RE
+.sp
+If the servers (or pool) support the Network Time Security (NTS)
+authentication mechanism and \fBchronyd\fP is compiled with NTS support, the \fBnts\fP
+option will enable a secure synchronisation to the servers. The configuration
+file could look like:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net iburst nts
+server ntp2.example.net iburst nts
+server ntp3.example.net iburst nts
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+.fam
+.fi
+.if n .RE
+.SS "NTP client with infrequent connection to NTP servers"
+.sp
+This section shows how to configure \fBchronyd\fP for computers that have
+occasional connections to NTP servers. In this case, you will need some
+additional configuration to tell \fBchronyd\fP when the connection goes up and
+down. This saves the program from continuously trying to poll the servers when
+they are inaccessible.
+.sp
+Again, assuming that your NTP servers are called \fIntp1.example.net\fP,
+\fIntp2.example.net\fP and \fIntp3.example.net\fP, your \fIchrony.conf\fP file would now
+contain:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net offline
+server ntp2.example.net offline
+server ntp3.example.net offline
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+rtcsync
+.fam
+.fi
+.if n .RE
+.sp
+The \fBoffline\fP keyword indicates that the servers start in an offline state, and
+that they should not be contacted until \fBchronyd\fP receives notification from
+\fBchronyc\fP that the link to the Internet is present. To tell \fBchronyd\fP when to
+start and finish sampling the servers, the \fBonline\fP and
+\fBoffline\fP commands of \fBchronyc\fP need to be used.
+.sp
+To give an example of their use, assuming that \fBpppd\fP is the program being
+used to connect to the Internet and that \fBchronyc\fP has been installed at
+\fI@BINDIR@/chronyc\fP, the script \fI/etc/ppp/ip\-up\fP would include:
+.sp
+.if n .RS 4
+.nf
+.fam C
+@BINDIR@/chronyc online
+.fam
+.fi
+.if n .RE
+.sp
+and the script \fI/etc/ppp/ip\-down\fP would include:
+.sp
+.if n .RS 4
+.nf
+.fam C
+@BINDIR@/chronyc offline
+.fam
+.fi
+.if n .RE
+.sp
+\fBchronyd\fP\*(Aqs polling of the servers would now only occur whilst the machine is
+actually connected to the Internet.
+.SS "Isolated networks"
+.sp
+This section shows how to configure \fBchronyd\fP for computers that never have
+network connectivity to any computer which ultimately derives its time from a
+reference clock.
+.sp
+In this situation, one computer is selected to be the primary timeserver. The
+other computers are either direct clients of the server, or clients of clients.
+.sp
+The \fBlocal\fP directive enables a local reference mode, which allows
+\fBchronyd\fP to appear synchronised even when it is not.
+.sp
+The rate value in the server\(cqs drift file needs to be set to the average rate
+at which the server gains or loses time. \fBchronyd\fP includes support for this,
+in the form of the \fBmanual\fP directive and the
+\fBsettime\fP command in the \fBchronyc\fP program.
+.sp
+If the server is rebooted, \fBchronyd\fP can re\-read the drift rate from the drift
+file. However, the server has no accurate estimate of the current time. To get
+around this, the system can be configured so that the server can initially set
+itself to a \(oqmajority\-vote\(cq of selected clients\*(Aq times; this allows the
+clients to \(oqflywheel\(cq the server while it is rebooting.
+.sp
+The \fBsmoothtime\fP directive is useful when the clocks of the
+clients need to stay close together when the local time is adjusted by the
+\fBsettime\fP command. The smoothing process needs to be
+activated by the \fBsmoothtime activate\fP command when
+the local time is ready to be served. After that point, any adjustments will be
+smoothed out.
+.sp
+A typical configuration file for the server (called \fIntp.local\fP) might be
+(assuming the clients and the server are in the \fI192.168.165.x\fP subnet):
+.sp
+.if n .RS 4
+.nf
+.fam C
+initstepslew 1 client1 client3 client6
+driftfile @CHRONYVARDIR@/drift
+local stratum 8
+manual
+allow 192.168.165.0/24
+smoothtime 400 0.01
+rtcsync
+.fam
+.fi
+.if n .RE
+.sp
+For the clients that have to resynchronise the server when it restarts,
+the configuration file might be:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp.local iburst
+driftfile @CHRONYVARDIR@/drift
+allow 192.168.165.0/24
+makestep 1.0 3
+rtcsync
+.fam
+.fi
+.if n .RE
+.sp
+The rest of the clients would be the same, except that the \fBallow\fP directive is
+not required.
+.sp
+If there is no suitable computer to be designated as the primary server, or
+there is a requirement to keep the clients synchronised even when it fails, the
+\fBorphan\fP option of the \fBlocal\fP directive enables a special mode where the
+server is selected from multiple computers automatically. They all need to use
+the same \fBlocal\fP configuration and poll one another. The server with the
+smallest reference ID (which is based on its IP address) will take the role of
+the primary server and others will be synchronised to it. When it fails, the
+server with the second smallest reference ID will take over and so on.
+.sp
+A configuration file for the first server might be (assuming there are three
+servers called \fIntp1.local\fP, \fIntp2.local\fP, and \fIntp3.local\fP):
+.sp
+.if n .RS 4
+.nf
+.fam C
+initstepslew 1 ntp2.local ntp3.local
+server ntp2.local
+server ntp3.local
+driftfile @CHRONYVARDIR@/drift
+local stratum 8 orphan
+manual
+allow 192.168.165.0/24
+rtcsync
+.fam
+.fi
+.if n .RE
+.sp
+The other servers would be the same, except the hostnames in the \fBinitstepslew\fP
+and \fBserver\fP directives would be modified to specify the other servers. Their
+clients might be configured to poll all three servers.
+.SS "RTC tracking"
+.sp
+This section considers a computer which has occasional connections to the
+Internet and is turned off between \(oqsessions\(cq. In this case, \fBchronyd\fP relies
+on the computer\(cqs RTC to maintain the time between the periods when it is
+powered up. It assumes that Linux is run exclusively on the computer. Dual\-boot
+systems might work; it depends what (if anything) the other system does to the
+RTC. On 2.6 and later kernels, if your motherboard has a HPET, you will need to
+enable the \fBHPET_EMULATE_RTC\fP option in your kernel configuration. Otherwise,
+\fBchronyd\fP will not be able to interact with the RTC device and will give up
+using it.
+.sp
+When the computer is connected to the Internet, \fBchronyd\fP has access to
+external NTP servers which it makes measurements from. These measurements are
+saved, and straight\-line fits are performed on them to provide an estimate of
+the computer\(cqs time error and rate of gaining or losing time.
+.sp
+When the computer is taken offline from the Internet, the best estimate of the
+gain or loss rate is used to free\-run the computer until it next goes online.
+.sp
+Whilst the computer is running, \fBchronyd\fP makes measurements of the RTC (via
+the \fI/dev/rtc\fP interface, which must be compiled into the kernel). An estimate
+is made of the RTC error at a particular RTC second, and the rate at which the
+RTC gains or loses time relative to true time.
+.sp
+When the computer is powered down, the measurement histories for all the NTP
+servers are saved to files, and the RTC tracking information is also
+saved to a file (if the \fBrtcfile\fP directive has been specified).
+These pieces of information are also saved if the \fBdump\fP
+and \fBwritertc\fP commands respectively are issued
+through \fBchronyc\fP.
+.sp
+When the computer is rebooted, \fBchronyd\fP reads the current RTC time and the RTC
+information saved at the last shutdown. This information is used to set the
+system clock to the best estimate of what its time would have been now, had it
+been left running continuously. The measurement histories for the servers are
+then reloaded.
+.sp
+The next time the computer goes online, the previous sessions\*(Aq measurements can
+contribute to the line\-fitting process, which gives a much better estimate of
+the computer\(cqs gain or loss rate.
+.sp
+One problem with saving the measurements and RTC data when the machine is shut
+down is what happens if there is a power failure; the most recent data will not
+be saved. Although \fBchronyd\fP is robust enough to cope with this, some
+performance might be lost. (The main danger arises if the RTC has been changed
+during the session, with the \fBtrimrtc\fP command in \fBchronyc\fP. Because of this,
+\fBtrimrtc\fP will make sure that a meaningful RTC file is saved after the
+change is completed).
+.sp
+The easiest protection against power failure is to put the \fBdump\fP and
+\fBwritertc\fP commands in the same place as the \fBoffline\fP command is issued to
+take \fBchronyd\fP offline; because \fBchronyd\fP free\-runs between online sessions, no
+parameters will change significantly between going offline from the Internet
+and any power failure.
+.sp
+A final point regards computers which are left running for extended periods and
+where it is desired to spin down the hard disc when it is not in use (e.g. when
+not accessed for 15 minutes). \fBchronyd\fP has been planned so it supports such
+operation; this is the reason why the RTC tracking parameters are not saved to
+disc after every update, but only when the user requests such a write, or
+during the shutdown sequence. The only other facility that will generate
+periodic writes to the disc is the \fBlog rtc\fP facility in the configuration
+file; this option should not be used if you want your disc to spin down.
+.sp
+To illustrate how a computer might be configured for this case, example
+configuration files are shown.
+.sp
+For the \fIchrony.conf\fP file, the following can be used as an example.
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net maxdelay 0.4 offline
+server ntp2.example.net maxdelay 0.4 offline
+server ntp3.example.net maxdelay 0.4 offline
+logdir /var/log/chrony
+log statistics measurements tracking
+driftfile @CHRONYVARDIR@/drift
+makestep 1.0 3
+maxupdateskew 100.0
+dumpdir @CHRONYVARDIR@
+rtcfile @CHRONYVARDIR@/rtc
+.fam
+.fi
+.if n .RE
+.sp
+\fBpppd\fP is used for connecting to the Internet. This runs two scripts
+\fI/etc/ppp/ip\-up\fP and \fI/etc/ppp/ip\-down\fP when the link goes online and offline
+respectively.
+.sp
+The relevant part of the \fI/etc/ppp/ip\-up\fP file is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+@BINDIR@/chronyc online
+.fam
+.fi
+.if n .RE
+.sp
+and the relevant part of the \fI/etc/ppp/ip\-down\fP script is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+@BINDIR@/chronyc \-m offline dump writertc
+.fam
+.fi
+.if n .RE
+.sp
+\fBchronyd\fP is started during the boot sequence with the \fB\-r\fP and \fB\-s\fP options.
+It might need to be started before any software that depends on the system clock
+not jumping or moving backwards, depending on the directives in \fBchronyd\fP\*(Aqs
+configuration file.
+.sp
+For the system shutdown, \fBchronyd\fP should receive a SIGTERM several seconds
+before the final SIGKILL; the SIGTERM causes the measurement histories and RTC
+information to be saved.
+.SS "Public NTP server"
+.sp
+\fBchronyd\fP can be configured to operate as a public NTP server, e.g. to join the
+.URL "https://www.pool.ntp.org/en/join.html" "pool.ntp.org" ""
+project. The configuration
+is similar to the NTP client with permanent connection, except it needs to
+allow client access from all addresses. It is recommended to find at least four
+good servers (e.g. from the pool, or on the NTP homepage). If the server has a
+hardware reference clock (e.g. a GPS receiver), it can be specified by the
+\fBrefclock\fP directive.
+.sp
+The amount of memory used for logging client accesses can be increased in order
+to enable clients to use the interleaved mode even when the server has a large
+number of clients, and better support rate limiting if it is enabled by the
+\fBratelimit\fP directive. The system timezone database, if it is
+kept up to date and includes the \fIright/UTC\fP timezone, can be used as a
+reliable source to determine when a leap second will be applied to UTC. The
+\fB\-r\fP option with the \fBdumpdir\fP directive shortens the time in which
+\fBchronyd\fP will not be able to serve time to its clients when it needs to be
+restarted (e.g. after upgrading to a newer version, or a change in the
+configuration).
+.sp
+The configuration file could look like:
+.sp
+.if n .RS 4
+.nf
+.fam C
+server ntp1.example.net iburst
+server ntp2.example.net iburst
+server ntp3.example.net iburst
+server ntp4.example.net iburst
+makestep 1.0 3
+rtcsync
+allow
+clientloglimit 100000000
+leapsectz right/UTC
+driftfile @CHRONYVARDIR@/drift
+dumpdir @CHRONYRUNDIR@
+.fam
+.fi
+.if n .RE
+.SH "SEE ALSO"
+.sp
+\fBchronyc(1)\fP, \fBchronyd(8)\fP
+.SH "BUGS"
+.sp
+For instructions on how to report bugs, please visit
+.URL "https://chrony\-project.org/" "" "."
+.SH "AUTHORS"
+.sp
+chrony was written by Richard Curnow, Miroslav Lichvar, and others. \ No newline at end of file
diff --git a/doc/chronyc.adoc b/doc/chronyc.adoc
new file mode 100644
index 0000000..96a0551
--- /dev/null
+++ b/doc/chronyc.adoc
@@ -0,0 +1,1559 @@
+// This file is part of chrony
+//
+// Copyright (C) Richard P. Curnow 1997-2003
+// Copyright (C) Stephen Wadeley 2016
+// Copyright (C) Miroslav Lichvar 2009-2017, 2019-2023
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of version 2 of the GNU General Public License as
+// 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.
+
+= chronyc(1)
+:doctype: manpage
+:man manual: User manual
+:man source: chrony @CHRONY_VERSION@
+
+== NAME
+
+chronyc - command-line interface for chrony daemon
+
+== SYNOPSIS
+
+*chronyc* [_OPTION_]... [_COMMAND_]...
+
+== DESCRIPTION
+
+*chronyc* is a command-line interface program which can be used to monitor
+*chronyd*'s performance and to change various operating parameters whilst it is
+running.
+
+If no commands are specified on the command line, *chronyc* will expect input
+from the user. The prompt _chronyc>_ will be displayed when it is being run
+from a terminal. If *chronyc*'s input or output are redirected from or to a file,
+the prompt will not be shown.
+
+There are two ways *chronyc* can access *chronyd*. One is the Internet
+Protocol (IPv4 or IPv6) and the other is a Unix domain socket, which is
+accessible locally by the root or _chrony_ user. By default, *chronyc* first
+tries to connect to the Unix domain socket. The compiled-in default path is
+_@CHRONYRUNDIR@/chronyd.sock_. If that fails (e.g. because *chronyc* is
+running under a non-root user), it will try to connect to 127.0.0.1 and then
+::1.
+
+Only the following monitoring commands, which do not affect the behaviour of
+*chronyd*, are allowed from the network: *activity*, *manual list*,
+*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
+default, the commands are accepted only from localhost (127.0.0.1 or ::1).
+
+All other commands are allowed only through the Unix domain socket. When sent
+over the network, *chronyd* will respond with a '`Not authorised`' error, even
+if it is from localhost.
+
+Having full access to *chronyd* via *chronyc* is more or less equivalent to
+being able to modify the *chronyd*'s configuration file and restart it.
+
+== OPTIONS
+
+*-4*::
+With this option hostnames will be resolved only to IPv4 addresses.
+
+*-6*::
+With this option hostnames will be resolved only to IPv6 addresses.
+
+*-n*::
+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 original hostnames or IP addresses of NTP
+sources that were specified in the configuration file, or *chronyc* commands.
+Without the *-n* and *-N* option, the printed hostnames are obtained from
+reverse DNS lookups and can be different from the specified hostnames.
+
+*-c*::
+This option enables printing of reports in a comma-separated values (CSV)
+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.
+
+*-e*::
+With this option each *chronyc* response will end with a line containing a
+single dot.
+
+*-d*::
+This option enables printing of debugging messages if *chronyc* was compiled
+with debugging support.
+
+*-m*::
+Normally, all arguments on the command line are interpreted as one command.
+With this option multiple commands can be specified. Each argument will be
+interpreted as a whole command.
+
+*-h* _host_::
+This option specifies the host to be contacted by *chronyc*. It can be
+specified with a hostname, IP address, or path to the local Unix domain socket.
+Multiple values can be specified as a comma-separated list to provide a
+fallback.
++
+The default value is _@CHRONYRUNDIR@/chronyd.sock,127.0.0.1,::1_, i.e. the host
+where *chronyc* is being run. First, it tries to connect to the Unix domain
+socket and if that fails (e.g. due to running under a non-root user), it
+will try to connect to 127.0.0.1 and then ::1.
+
+*-p* _port_::
+This option allows the user to specify the UDP port number which the target
+*chronyd* is using for its monitoring connections. This defaults to 323; there
+would rarely be a need to change this.
+
+*-f* _file_::
+This option is ignored and is provided only for compatibility.
+
+*-a*::
+This option is ignored and is provided only for compatibility.
+
+*-v*, *--version*::
+With this option *chronyc* displays its version number on the terminal and
+exits.
+
+*--help*::
+With this option *chronyc* displays a help message on the terminal and
+exits.
+
+== COMMANDS
+
+This section describes each of the commands available within the *chronyc*
+program.
+
+=== System clock
+
+[[tracking]]*tracking*::
+The *tracking* command displays parameters about the system's clock
+performance. An example of the output is shown below.
++
+----
+Reference ID : CB00710F (ntp1.example.net)
+Stratum : 3
+Ref time (UTC) : Fri Jan 27 09:49:17 2017
+System time : 0.000006523 seconds slow of NTP time
+Last offset : -0.000006747 seconds
+RMS offset : 0.000035822 seconds
+Frequency : 3.225 ppm slow
+Residual freq : -0.000 ppm
+Skew : 0.129 ppm
+Root delay : 0.013639022 seconds
+Root dispersion : 0.001100737 seconds
+Update interval : 64.2 seconds
+Leap status : Normal
+----
++
+The fields are explained as follows:
++
+*Reference ID*:::
+This is the reference ID and name (or IP address) of the server to which the
+computer is currently synchronised. For IPv4 addresses, the reference ID is
+equal to the address and for IPv6 addresses it is the first 32 bits of the MD5
+sum of the address.
++
+If the reference ID is _7F7F0101_ and there is no name or IP address, it means
+the computer is not synchronised to any external source and that you have the
+_local_ mode operating (via the <<local,*local*>> command in *chronyc*, or the
+<<chrony.conf.adoc#local,*local*>> directive in the configuration file).
++
+The reference ID is printed as a hexadecimal number. Note that in older
+versions it used to be printed in quad-dotted notation and could be confused
+with an IPv4 address.
+*Stratum*:::
+The stratum indicates how many hops away from a computer with an attached
+reference clock we are. Such a computer is a stratum-1 computer, so the
+computer in the example is two hops away (i.e. _ntp1.example.net_ is a
+stratum-2 and is synchronised from a stratum-1).
+*Ref time*:::
+This is the time (UTC) at which the last measurement from the reference
+source was processed.
+*System time*:::
+This is the current offset between the NTP clock and system clock. The NTP
+clock is a software (virtual) clock maintained by *chronyd*, which is
+synchronised to the configured time sources and provides time to NTP clients.
+The system clock is synchronised to the NTP clock. To avoid steps in the
+system time, which might have adverse consequences for certain applications,
+the system clock is normally corrected only by speeding up or slowing down (up
+to the rate configured by the <<chrony.conf.adoc#maxslewrate,*maxslewrate*>>
+directive). If the offset is too large, this correction will take a very long
+time. A step can be forced by the <<makestep,*makestep*>> command, or the
+<<chrony.conf.adoc#makestep,*makestep*>> directive in the configuration file.
++
+Note that all other offsets reported by *chronyc* and most offsets in the log
+files are relative to the NTP clock, not the system clock.
+*Last offset*:::
+This is the estimated local offset on the last clock update. A positive value
+indicates the local time (as previously estimated true time) was ahead of the
+time sources.
+*RMS offset*:::
+This is a long-term average of the offset value.
+*Frequency*:::
+The '`frequency`' is the rate by which the system's clock would be wrong if
+*chronyd* was not correcting it. It is expressed in ppm (parts per million).
+For example, a value of 1 ppm would mean that when the system's clock thinks it
+has advanced 1 second, it has actually advanced by 1.000001 seconds relative to
+true time.
+*Residual freq*:::
+This shows the '`residual frequency`' for the currently selected reference
+source. This reflects any difference between what the measurements from the
+reference source indicate the frequency should be and the frequency currently
+being used.
++
+The reason this is not always zero is that a smoothing procedure is
+applied to the frequency. Each time a measurement from the reference
+source is obtained and a new residual frequency computed, the estimated
+accuracy of this residual is compared with the estimated accuracy (see
+'`skew`' next) of the existing frequency value. A weighted average is
+computed for the new frequency, with weights depending on these accuracies.
+If the measurements from the reference source follow a consistent trend, the
+residual will be driven to zero over time.
+*Skew*:::
+This is the estimated error bound on the frequency.
+*Root delay*:::
+This is the total of the network path delays to the stratum-1 computer from
+which the computer is ultimately synchronised.
+*Root dispersion*:::
+This is the total dispersion accumulated through all the computers back to
+the stratum-1 computer from which the computer is ultimately synchronised.
+Dispersion is due to system clock resolution, statistical measurement
+variations, etc.
++
+An absolute bound on the computer's clock accuracy (assuming the stratum-1
+computer is correct) is given by:
++
+----
+clock_error <= |system_time_offset| + root_dispersion + (0.5 * root_delay)
+----
+*Update interval*:::
+This is the interval between the last two clock updates.
+*Leap status*:::
+This is the leap status, which can be _Normal_, _Insert second_, _Delete
+second_ or _Not synchronised_.
+
+[[makestep]]*makestep*::
+*makestep* _threshold_ _limit_::
+Normally *chronyd* will cause the system to gradually correct any time offset,
+by slowing down or speeding up the clock as required. In certain situations,
+the system clock might be so far adrift that this slewing process would take a
+very long time to correct the system clock.
++
+The *makestep* command can be used in this situation. There are two forms of
+the command. The first form has no parameters. It tells *chronyd* to cancel any
+remaining correction that was being slewed and jump the system clock by the
+equivalent amount, making it correct immediately.
++
+The second form configures the automatic stepping, similarly to the
+<<chrony.conf.adoc#makestep,*makestep*>> directive. It has two parameters,
+stepping threshold (in seconds) and number of future clock updates for which
+the threshold will be active. This can be used with the <<burst,*burst*>>
+command to quickly make a new measurement and correct the clock by stepping if
+needed, without waiting for *chronyd* to complete the measurement and update
+the clock.
++
+----
+makestep 0.1 1
+burst 1/2
+----
++
+BE WARNED: Certain software will be seriously affected by such jumps in the
+system time. (That is the reason why *chronyd* uses slewing normally.)
+
+[[maxupdateskew]]*maxupdateskew* _skew-in-ppm_::
+This command has the same effect as the
+<<chrony.conf.adoc#maxupdateskew,*maxupdateskew*>> directive in the
+configuration file.
+
+[[waitsync]]*waitsync* [_max-tries_ [_max-correction_ [_max-skew_ [_interval_]]]]::
+The *waitsync* command waits for *chronyd* to synchronise.
++
+Up to four optional arguments can be specified. The first is the maximum number
+of tries before giving up and returning a non-zero error code. When 0 is
+specified, or there are no arguments, the number of tries will not be limited.
++
+The second and third arguments are the maximum allowed remaining correction of
+the system clock and the maximum allowed skew (in ppm) as reported by the
+<<tracking,*tracking*>> command in the *System time* and *Skew* fields. If not
+specified or zero, the value will not be checked.
++
+The fourth argument is the interval specified in seconds in which the check is
+repeated. The interval is 10 seconds by default.
++
+An example is:
++
+----
+waitsync 60 0.01
+----
++
+which will wait up to about 10 minutes (60 times 10 seconds) for *chronyd* to
+synchronise to a source and the remaining correction to be less than 10
+milliseconds.
+
+=== Time sources
+
+[[sources]]*sources* [*-a*] [*-v*]::
+This command displays information about the current time sources that *chronyd*
+is accessing.
++
+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.
++
+----
+MS Name/IP address Stratum Poll Reach LastRx Last sample
+===============================================================================
+#* GPS0 0 4 377 11 -479ns[ -621ns] +/- 134ns
+^? ntp1.example.net 2 6 377 23 -923us[ -924us] +/- 43ms
+^+ ntp2.example.net 1 6 377 21 -2629us[-2619us] +/- 86ms
+----
++
+The columns are as follows:
++
+*M*:::
+This indicates the mode of the source. _^_ means a server, _=_ means a peer
+and _#_ indicates a locally connected reference clock.
+*S*:::
+This column indicates the selection state of the source.
+* _*_ indicates the best source which is currently selected for
+ synchronisation.
+* _+_ indicates other sources selected for synchronisation, which are combined
+ with the best source.
+* _-_ indicates a source which is considered to be selectable for
+ synchronisation, but not currently selected.
+* _x_ indicates a source which *chronyd* thinks is a falseticker (i.e. its
+ time is inconsistent with a majority of other sources, or sources specified
+ with the *trust* option).
+* _~_ indicates a source whose time appears to have too much variability.
+* _?_ indicates a source which is not considered to be selectable for
+ synchronisation for other reasons (e.g. unreachable, not synchronised, or
+ does not have enough measurements).
+{blank}:::
+The <<selectdata,*selectdata*>> command can be used to get more details about
+the selection state.
+*Name/IP address*:::
+This shows the name or the IP address of the source, or reference ID for reference
+clocks.
+*Stratum*:::
+This shows the stratum of the source, as reported in its most recently
+received sample. Stratum 1 indicates a computer with a locally attached
+reference clock. A computer that is synchronised to a stratum 1 computer is
+at stratum 2. A computer that is synchronised to a stratum 2 computer is at
+stratum 3, and so on.
+*Poll*:::
+This shows the rate at which the source is being polled, as a base-2
+logarithm of the interval in seconds. Thus, a value of 6 would indicate that
+a measurement is being made every 64 seconds. *chronyd* automatically varies
+the polling rate in response to prevailing conditions.
+*Reach*:::
+This shows the source's reachability register printed as an octal number. The
+register has 8 bits and is updated on every received or missed packet from
+the source. A value of 377 indicates that a valid reply was received for all
+from the last eight transmissions.
+*LastRx*:::
+This column shows how long ago the last good sample (which is shown in the next
+column) was received from the source. Measurements that failed some tests are
+ignored. This is normally in seconds. The letters _m_, _h_, _d_ or _y_ indicate
+minutes, hours, days, or years.
+*Last sample*:::
+This column shows the offset between the local clock and the source at the
+last measurement. The number in the square brackets shows the actual measured
+offset. This can be suffixed by _ns_ (indicating nanoseconds), _us_
+(indicating microseconds), _ms_ (indicating milliseconds), or _s_ (indicating
+seconds). The number to the left of the square brackets shows the original
+measurement, adjusted to allow for any slews applied to the local clock
+since. Positive offsets indicate that the local clock is ahead of the source.
+The number following the _+/-_ indicator shows the margin of error in the
+measurement (NTP root distance).
+
+[[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*.
++
+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:
++
+----
+Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
+===============================================================================
+ntp1.example.net 11 5 46m -0.001 0.045 1us 25us
+----
++
+The columns are as follows:
++
+*Name/IP Address*:::
+This is the name or IP address of the NTP server (or peer) or reference ID of the
+reference clock to which the rest of the line relates.
+*NP*:::
+This is the number of sample points currently being retained for the server.
+The drift rate and current offset are estimated by performing a linear
+regression through these points.
+*NR*:::
+This is the number of runs of residuals having the same sign following the
+last regression. If this number starts to become too small relative to the
+number of samples, it indicates that a straight line is no longer a good fit
+to the data. If the number of runs is too low, *chronyd* discards older
+samples and re-runs the regression until the number of runs becomes
+acceptable.
+*Span*:::
+This is the interval between the oldest and newest samples. If no unit is
+shown the value is in seconds. In the example, the interval is 46 minutes.
+*Frequency*:::
+This is the estimated residual frequency for the server, in parts per
+million. In this case, the computer's clock is estimated to be running 1 part
+in 10^9 slow relative to the server.
+*Freq Skew*:::
+This is the estimated error bounds on *Freq* (again in parts per million).
+*Offset*:::
+This is the estimated offset of the source.
+*Std Dev*:::
+This is the estimated sample standard deviation.
+
+[[selectdata]]*selectdata* [*-a*] [*-v*]::
+The *selectdata* command displays information specific to the selection of time
+sources. If the *-a* option is specified, all sources are displayed, including
+those that do not have a known address yet. With the *-v* option, extra caption
+lines are shown as a reminder of the meanings of the columns.
++
+An example of the output is shown below.
++
+----
+S Name/IP Address Auth COpts EOpts Last Score Interval Leap
+=======================================================================
+D ntp1.example.net Y ----- --TR- 4 1.0 -61ms +62ms N
+* ntp2.example.net N ----- ----- 0 1.0 -6846us +7305us N
++ ntp3.example.net N ----- ----- 10 1.0 -7381us +7355us N
+----
++
+The columns are as follows:
++
+*S*:::
+This column indicates the state of the source after the last source selection.
+It is similar to the state reported by the *sources* command, but more
+states are reported.
+{blank}:::
+The following states indicate the source is not considered selectable for
+synchronisation:
+* _N_ - has the *noselect* option.
+* _s_ - is not synchronised.
+* _M_ - does not have enough measurements.
+* _d_ - has a root distance larger than the maximum distance (configured by the
+ <<chrony.conf.adoc#maxdistance,*maxdistance*>> directive).
+* _~_ - has a jitter larger than the maximum jitter (configured by the
+ <<chrony.conf.adoc#maxjitter,*maxjitter*>> directive).
+* _w_ - waits for other sources to get out of the _M_ state.
+* _S_ - has older measurements than other sources.
+* _O_ - has a stratum equal or larger than the orphan stratum (configured by
+ the <<chrony.conf.adoc#local,*local*>> directive).
+* _T_ - does not fully agree with sources that have the *trust* option.
+* _x_ - does not agree with other sources (falseticker).
+{blank}:::
+The following states indicate the source is considered selectable, but it is
+not currently used for synchronisation:
+* _W_ - waits for other sources to be selectable (required by the
+ <<chrony.conf.adoc#minsources,*minsources*>> directive, or
+ the *require* option of another source).
+* _P_ - another selectable source is preferred due to the *prefer* option.
+* _U_ - waits for a new measurement (after selecting a different best source).
+* _D_ - has, or recently had, a root distance which is too large to be combined
+ with other sources (configured by the
+ <<chrony.conf.adoc#combinelimit,*combinelimit*>> directive).
+{blank}:::
+The following states indicate the source is used for synchronisation of the
+local clock:
+* _+_ - combined with the best source.
+* _*_ - selected as the best source to update the reference data (e.g. root
+ delay, root dispersion).
+*Name/IP address*:::
+This column shows the name or IP address of the source if it is an NTP server,
+or the reference ID if it is a reference clock.
+*Auth*:::
+This column indicites whether an authentication mechanism is enabled for the
+source. _Y_ means yes and _N_ means no.
+*COpts*:::
+This column displays the configured selection options of the source.
+* _N_ indicates the *noselect* option.
+* _P_ indicates the *prefer* option.
+* _T_ indicates the *trust* option.
+* _R_ indicates the *require* option.
+*EOpts*:::
+This column displays the current effective selection options of the source,
+which can be different from the configured options due to the authentication
+selection mode (configured by the
+<<chrony.conf.adoc#authselectmode,*authselectmode*>> directive). The symbols
+are the same as in the *COpts* column.
+*Last*:::
+This column displays how long ago was the last measurement of the source made
+when the selection was performed.
+*Score*:::
+This column displays the current score against the source in the _*_ state. The
+scoring system avoids frequent reselection when multiple sources have a similar
+root distance. A value larger than 1 indicates this source was better than the
+_*_ source in recent selections. If the score reaches 10, the best source will
+be reselected and the scores will be reset to 1.
+*Interval*:::
+This column displays the lower and upper endpoint of the interval which was
+expected to contain the true offset of the local clock considering the root
+distance at the time of the selection.
+*Leap*:::
+This column displays the current leap status of the source.
+* _N_ indicates the normal status (no leap second).
+* _+_ indicates that a leap second will be inserted at the end of the month.
+* _-_ indicates that a leap second will be deleted at the end of the month.
+* _?_ indicates the unknown status (i.e. no valid measurement was made).
+
+[[selectopts]]*selectopts* _address|refid_ [_+|-option_]...::
+The *selectopts* command modifies the configured selection options of an NTP
+source specified by IP address (or the _ID#XXXXXXXXXX_ identifier used for
+unknown addresses), or a reference clock specified by reference ID as a string.
++
+The selection options can be added with the *+* symbol or removed with the *-*
+symbol. The *selectdata* command can be used to verify the configuration. The
+modified options will be applied in the next source selection, e.g. when a new
+measurement is made, or the *reselect* command is executed.
++
+An example of using this command is shown below.
++
+----
+selectopts 1.2.3.4 -noselect +prefer
+selectopts GPS +trust
+----
+
+[[reselect]]*reselect*::
+To avoid excessive switching between sources, *chronyd* can stay synchronised
+to a source even when it is not currently the best one among the available
+sources.
++
+The *reselect* command can be used to force *chronyd* to reselect the best
+synchronisation source.
+
+[[reselectdist]]*reselectdist* _distance_::
+The *reselectdist* command sets the reselection distance. It is equivalent to
+the <<chrony.conf.adoc#reselectdist,*reselectdist*>> directive in the
+configuration file.
+
+=== NTP sources
+
+[[activity]]*activity*::
+This command reports the number of servers and peers that are online and
+offline. If the *auto_offline* option is used in specifying some of the servers
+or peers, the *activity* command can be useful for detecting when all of them
+have entered the offline state after the network link has been disconnected.
++
+The report shows the number of servers and peers in 5 states:
++
+*online*:::
+the server or peer is currently online (i.e. assumed by *chronyd* to be reachable)
+*offline*:::
+the server or peer is currently offline (i.e. assumed by *chronyd* to be
+unreachable, and no measurements from it will be attempted.)
+*burst_online*:::
+a burst command has been initiated for the server or peer and is being
+performed; after the burst is complete, the server or peer will be returned to
+the online state.
+*burst_offline*:::
+a burst command has been initiated for the server or peer and is being
+performed; after the burst is complete, the server or peer will be returned to
+the offline state.
+*unresolved*:::
+the name of the server or peer was not resolved to an address yet; this source is
+not visible in the *sources* and *sourcestats* reports.
+
+[[authdata]]*authdata* [*-a*]::
+The *authdata* command displays information specific to authentication of NTP
+sources. If the *-a* option is specified, all sources are displayed, including
+those that do not have a known address yet. An example of the output is
+shown below.
++
+----
+Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+ntp1.example.net NTS 1 15 256 135m 0 0 8 100
+ntp2.example.net SK 30 13 128 - 0 0 0 0
+ntp3.example.net - 0 0 0 - 0 0 0 0
+----
++
+The columns are as follows:
++
+*Name/IP address*:::
+This column shows the name or the IP address of the source.
+*Mode*:::
+This column shows which mechanism authenticates NTP packets received from the
+source. _NTS_ means Network Time Security, _SK_ means a symmetric key, and _-_
+means authentication is disabled.
+*KeyID*:::
+This column shows an identifier of the key used for authentication. With a
+symmetric key, it is the ID from the <<chrony.conf.adoc#keyfile,key file>>.
+With NTS, it is a number starting at zero and incremented by one with each
+successful key establishment using the NTS-KE protocol, i.e. it shows how many
+times the key establishment was performed with this source.
+*Type*:::
+This columns shows an identifier of the algorithm used for authentication.
+With a symmetric key, it is the hash function or cipher specified in the key
+file. With NTS, it is an authenticated encryption with associated data (AEAD)
+algorithm, which is negotiated in the NTS-KE protocol. The following values can
+be reported:
+* 1: MD5
+* 2: SHA1
+* 3: SHA256
+* 4: SHA384
+* 5: SHA512
+* 6: SHA3-224
+* 7: SHA3-256
+* 8: SHA3-384
+* 9: SHA3-512
+* 10: TIGER
+* 11: WHIRLPOOL
+* 13: AES128
+* 14: AES256
+* 15: AEAD-AES-SIV-CMAC-256
+* 30: AEAD-AES-128-GCM-SIV
+*KLen*:::
+This column shows the length of the key in bits.
+*Last*:::
+This column shows how long ago the last successful key establishment was
+performed. It is in seconds, or letters _m_, _h_, _d_ or _y_ indicate minutes,
+hours, days, or years.
+*Atmp*:::
+This column shows the number of attempts to perform the key establishment since
+the last successful key establishment. A number larger than 1 indicates a
+problem with the network or server.
+*NAK*:::
+This column shows whether an NTS NAK was received since the last request.
+A NAK indicates that authentication failed on the server side due to
+*chronyd* using a cookie which is no longer valid and that it needs to perform
+the key establishment again in order to get new cookies.
+*Cook*:::
+This column shows the number of NTS cookies that *chronyd* currently has. If
+the key establishment was successful, a number smaller than 8 indicates a
+problem with the network or server.
+*CLen*:::
+This column shows the length in bytes of the NTS cookie which will be used in
+the next request.
+
+[[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
+(with a known address) if no address was specified. An example of the output is
+shown below.
++
+----
+Remote address : 203.0.113.15 (CB00710F)
+Remote port : 123
+Local address : 203.0.113.74 (CB00714A)
+Leap status : Normal
+Version : 4
+Mode : Server
+Stratum : 1
+Poll interval : 10 (1024 seconds)
+Precision : -24 (0.000000060 seconds)
+Root delay : 0.000015 seconds
+Root dispersion : 0.000015 seconds
+Reference ID : 47505300 (GPS)
+Reference time : Fri Nov 25 15:22:12 2016
+Offset : -0.000060878 seconds
+Peer delay : 0.000175634 seconds
+Peer dispersion : 0.000000681 seconds
+Response time : 0.000053050 seconds
+Jitter asymmetry: +0.00
+NTP tests : 111 111 1111
+Interleaved : No
+Authenticated : No
+TX timestamping : Kernel
+RX timestamping : Kernel
+Total TX : 24
+Total RX : 24
+Total valid RX : 24
+Total good RX : 22
+----
++
+The fields are explained as follows:
++
+*Remote address*:::
+The IP address of the NTP server or peer, and the corresponding reference ID.
+*Remote port*:::
+The UDP port number to which the request was sent. The standard NTP port is
+123.
+*Local address*:::
+The local IP address which received the response, and the corresponding
+reference ID.
+*Leap status*:::
+*Version*:::
+*Mode*:::
+*Stratum*:::
+*Poll interval*:::
+*Precision*:::
+*Root delay*:::
+*Root dispersion*:::
+*Reference ID*:::
+*Reference time*:::
+The NTP values from the last valid response.
+*Offset*:::
+*Peer delay*:::
+*Peer dispersion*:::
+The measured values.
+*Response time*:::
+The time the server or peer spent in processing of the request and waiting
+before sending the response.
+*Jitter asymmetry*:::
+The estimated asymmetry of network jitter on the path to the source. The
+asymmetry can be between -0.5 and 0.5. A negative value means the delay of
+packets sent to the source is more variable than the delay of packets sent
+from the source back.
+*NTP tests*:::
+Results of RFC 5905 tests 1 through 3, 5 through 7, and tests for maximum
+delay, delay ratio, delay dev ratio (or delay quantile), and synchronisation
+loop.
+*Interleaved*:::
+This shows if the response was in the interleaved mode.
+*Authenticated*:::
+This shows if the response was authenticated.
+*TX timestamping*:::
+The source of the local transmit timestamp. Valid values are _Daemon_,
+_Kernel_, and _Hardware_.
+*RX timestamping*:::
+The source of the local receive timestamp.
+*Total TX*:::
+The number of packets sent to the source.
+*Total RX*:::
+The number of all packets received from the source.
+*Total valid RX*:::
+The number of packets which passed the first two groups of NTP tests.
+*Total good RX*:::
+The number of packets which passed all three groups of NTP tests, i.e. the NTP
+measurement was accepted.
+
+[[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 identical to that for the
+<<chrony.conf.adoc#peer,*peer*>> directive in the configuration file.
++
+An example of using this command is shown below.
++
+----
+add peer ntp1.example.net minpoll 6 maxpoll 10 key 25
+----
+
+[[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 ntp1.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 identical to that for the <<chrony.conf.adoc#server,*server*>>
+directive in the configuration file.
++
+An example of using this command is shown below:
++
+----
+add server ntp1.example.net minpoll 6 maxpoll 10 key 25
+----
+
+[[delete]]*delete* _address_::
+The *delete* command allows an NTP server or peer to be removed
+from the current set of sources.
+
+[[burst]]
+*burst* _good_/_max_ [_mask_/_masked-address_]::
+*burst* _good_/_max_ [_masked-address_/_masked-bits_]::
+*burst* _good_/_max_ [_address_]::
+The *burst* command tells *chronyd* to make a set of measurements to each of
+its NTP sources over a short duration (rather than the usual periodic
+measurements that it makes). After such a burst, *chronyd* will revert to the
+previous state for each source. This might be either online, if the source was
+being periodically measured in the normal way, or offline, if the source had
+been indicated as being offline. (A source can be switched between the online
+and offline states with the <<online,*online*>> and <<offline,*offline*>>
+commands.)
++
+The _mask_ and _masked-address_ arguments are optional, in which case *chronyd*
+will initiate a burst for all of its currently defined sources.
++
+The arguments have the following meaning and format:
++
+_good_:::
+This defines the number of good measurements that *chronyd* will want to
+obtain from each source. A measurement is good if it passes certain tests,
+for example, the round trip time to the source must be acceptable. (This
+allows *chronyd* to reject measurements that are likely to be bogus.)
+_max_:::
+This defines the maximum number of measurements that *chronyd* will attempt
+to make, even if the required number of good measurements has not been
+obtained.
+_mask_:::
+This is an IP address with which the IP address of each of *chronyd*'s
+sources is to be masked.
+_masked-address_:::
+This is an IP address. If the masked IP address of a source matches this
+value then the burst command is applied to that source.
+_masked-bits_:::
+This can be used with _masked-address_ for CIDR notation, which is a shorter
+alternative to the form with mask.
+_address_:::
+This is an IP address or a hostname. The burst command is applied only to
+that source.
+{blank}::
++
+If no _mask_ or _masked-address_ arguments are provided, every source will be
+matched.
++
+An example of the two-argument form of the command is:
++
+----
+burst 2/10
+----
++
+This will cause *chronyd* to attempt to get two good measurements from each
+source, stopping after two have been obtained, but in no event will it try more
+than ten probes to the source.
++
+Examples of the four-argument form of the command are:
++
+----
+burst 2/10 255.255.0.0/1.2.0.0
+burst 2/10 2001:db8:789a::/48
+----
++
+In the first case, the two out of ten sampling will only be applied to sources
+whose IPv4 addresses are of the form _1.2.x.y_, where _x_ and _y_ are
+arbitrary. In the second case, the sampling will be applied to sources whose
+IPv6 addresses have first 48 bits equal to _2001:db8:789a_.
++
+Example of the three-argument form of the command is:
++
+----
+burst 2/10 ntp1.example.net
+----
+
+[[maxdelay]]*maxdelay* _address_ _delay_::
+This allows the *maxdelay* option for one of the sources to be modified, in the
+same way as specifying the *maxdelay* option for the
+<<chrony.conf.adoc#server,*server*>> directive in the configuration file.
+
+[[maxdelaydevratio]]*maxdelaydevratio* _address_ _ratio_::
+This allows the *maxdelaydevratio* option for one of the sources to be
+modified, in the same way as specifying the *maxdelaydevratio* option for the
+<<chrony.conf.adoc#server,*server*>> directive in the configuration file.
+
+[[maxdelayratio]]*maxdelayratio* _address_ _ratio_::
+This allows the *maxdelayratio* option for one of the sources to be modified,
+in the same way as specifying the *maxdelayratio* option for the
+<<chrony.conf.adoc#server,*server*>> directive in the configuration file.
+
+[[maxpoll]]*maxpoll* _address_ _maxpoll_::
+The *maxpoll* command is used to modify the maximum polling interval for one of
+the current set of sources. It is equivalent to the *maxpoll* option in the
+<<chrony.conf.adoc#server,*server*>> directive in the configuration file.
++
+Note that the new maximum polling interval only takes effect after the next
+measurement has been made.
+
+[[minpoll]]*minpoll* _address_ _minpoll_::
+The *minpoll* command is used to modify the minimum polling interval for one of
+the current set of sources. It is equivalent to the *minpoll* option in the
+<<chrony.conf.adoc#server,*server*>> directive in the configuration file.
++
+Note that the new minimum polling interval only takes effect after the next
+measurement has been made.
+
+[[minstratum]]*minstratum* _address_ _minstratum_::
+The *minstratum* command is used to modify the minimum stratum for one of the
+current set of sources. It is equivalent to the *minstratum* option in the
+<<chrony.conf.adoc#server,*server*>> directive in the configuration file.
+
+[[offline]]
+*offline* [_address_]::
+*offline* [_masked-address_/_masked-bits_]::
+*offline* [_mask_/_masked-address_]::
+The *offline* command is used to warn *chronyd* that the network connection to
+a particular host or hosts is about to be lost, e.g. on computers with
+intermittent connection to their time sources.
++
+Another case where *offline* could be used is where a computer serves time to a
+local group of computers, and has a permanent connection to true time servers
+outside the organisation. However, the external connection is heavily loaded at
+certain times of the day and the measurements obtained are less reliable at
+those times. In this case, it is probably most useful to determine the
+gain or loss rate during the quiet periods and let the whole network coast through
+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 (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.
++
+----
+offline
+offline 255.255.255.0/1.2.3.0
+offline 2001:db8:789a::/48
+offline ntp1.example.net
+----
++
+The second form means that the *offline* command is to be applied to any source
+whose IPv4 address is in the _1.2.3_ subnet. (The host's address is logically
+and-ed with the mask, and if the result matches the _masked-address_ the host
+is processed.) The third form means that the command is to be applied to all
+sources whose IPv6 addresses have their first 48 bits equal to _2001:db8:789a_. The
+fourth form means that the command is to be applied only to that one source.
++
+The wildcard form of the address is equivalent to:
++
+----
+offline 0.0.0.0/0.0.0.0
+offline ::/0
+----
+
+[[online]]
+*online* [_address_]::
+*online* [_masked-address_/_masked-bits_]::
+*online* [_mask_/_masked-address_]::
+The *online* command is opposite in function to the <<offline,*offline*>>
+command. It is used to advise *chronyd* that network connectivity to a
+particular source or sources has been restored.
++
+The syntax is identical to that of the <<offline,*offline*>> command.
+
+[[onoffline]]
+*onoffline*::
+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 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
+current set of sources. It is equivalent to the *polltarget* option in the
+<<chrony.conf.adoc#server,*server*>> directive in the configuration file.
+
+[[refresh]]*refresh*::
+The *refresh* command can be used to force *chronyd* to resolve the names of
+configured NTP sources to IP addresses again and replace any addresses missing
+in the list of resolved addresses.
++
+Sources that stop responding are replaced with newly resolved addresses
+automatically after 8 polling intervals. This command can be used to replace
+them immediately, e.g. after suspending and resuming the machine in a different
+network.
++
+Note that with pools which have more than 16 addresses, or not all IPv4 or IPv6
+addresses are included in a single DNS response (e.g. pool.ntp.org), this
+command might replace the addresses even if they are still in the pool.
+
+[[reload]]*reload* *sources*::
+The *reload sources* command causes *chronyd* to re-read all _*.sources_ files
+from the directories specified by the
+<<chrony.conf.adoc#sourcedir,*sourcedir*>> directive.
++
+Note that modified sources (e.g. specified with a new option) are not modified
+in memory. They are removed and added again, which causes them to lose old
+measurements and reset the selection state.
+
+[[sourcename]]*sourcename* _address_::
+The *sourcename* command prints the original hostname or address that was
+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]]
+*manual* *on*::
+*manual* *off*::
+*manual* *delete* _index_::
+*manual* *list*::
+*manual* *reset*::
+The manual command enables and disables use of the <<settime,*settime*>>
+command, and is used to modify the behaviour of the manual clock driver.
++
+The *on* form of the command enables use of the *settime* command.
++
+The *off* form of the command disables use of the *settime* command.
++
+The *list* form of the command lists all the samples currently stored in
+*chronyd*. The output is illustrated below.
++
+----
+210 n_samples = 1
+# Date Time(UTC) Slewed Original Residual
+====================================================
+ 0 27Jan99 22:09:20 0.00 0.97 0.00
+----
++
+The columns are as as follows:
++
+. The sample index (used for the *manual delete* command).
+. The date and time of the sample.
+. The system clock error when the timestamp was entered, adjusted to allow
+ for changes made to the system clock since.
+. The system clock error when the timestamp was entered, as it originally was
+ (without allowing for changes to the system clock since).
+. The regression residual at this point, in seconds. This allows '`outliers`'
+ to be easily spotted, so that they can be deleted using the *manual delete*
+ command.
+{blank}::
++
+The *delete* form of the command deletes a single sample. The parameter is the
+index of the sample, as shown in the first column of the output from *manual
+list*. Following deletion of the data point, the current error and drift rate
+are re-estimated from the remaining data points and the system clock trimmed if
+necessary. This option is intended to allow '`outliers`' to be discarded, i.e.
+samples where the administrator realises they have entered a very poor
+timestamp.
++
+The *reset* form of the command deletes all samples at once. The system clock
+is left running as it was before the command was entered.
+
+[[settime]]*settime* _time_::
+The *settime* command allows the current time to be entered manually, if this
+option has been configured into *chronyd*. (It can be configured either with
+the <<chrony.conf.adoc#manual,*manual*>> directive in the configuration file,
+or with the <<manual,*manual*>> command of *chronyc*.)
++
+It should be noted that the computer's sense of time will only be as accurate
+as the reference you use for providing this input (e.g. your watch), as well as
+how well you can time the press of the return key.
++
+Providing your computer's time zone is set up properly, you will be able to
+enter a local time (rather than UTC).
++
+The response to a successful *settime* command indicates the amount that the
+computer's clock was wrong. It should be apparent from this if you have entered
+the time wrongly, e.g. with the wrong time zone.
++
+The rate of drift of the system clock is estimated by a regression process
+using the entered measurement and all previous measurements entered during the
+present run of *chronyd*. However, the entered measurement is used for
+adjusting the current clock offset (rather than the estimated intercept from
+the regression, which is ignored). Contrast what happens with the
+<<manual,*manual delete*>> command, where the intercept is used to set the
+current offset (since there is no measurement that has just been entered in
+that case).
++
+The time is parsed by the public domain _getdate_ algorithm. Consequently, you
+can only specify time to the nearest second.
++
+Examples of inputs that are valid are shown below:
++
+----
+settime 16:30
+settime 16:30:05
+settime Nov 21, 2015 16:30:05
+----
++
+For a full description of getdate, see the getdate documentation
+(bundled, for example, with the source for GNU tar).
+
+=== NTP access
+
+[[accheck]]*accheck* _address_::
+This command allows you to check whether client NTP access is allowed from a
+particular host.
++
+Examples of use, showing a named host and a numeric IP address, are as follows:
++
+----
+accheck ntp1.example.net
+accheck 1.2.3.4
+accheck 2001:db8::1
+----
++
+This command can be used to examine the effect of a series of *allow*, *allow
+all*, *deny*, and *deny all* commands specified either via *chronyc*, or in
+*chronyd*'s configuration file.
+
+[[clients]]*clients* [*-p* _packets_] [*-k*] [*-r*]::
+This command shows a list of clients that have accessed the server, through
+the NTP, command, or NTS-KE port. It does not include accesses over the Unix
+domain command socket.
++
+The *-p* option specifies the minimum number of received NTP or command
+packets, or accepted NTS-KE connections, needed to include a client in the
+list. The default value is 0, i.e. all clients are reported. With the *-k*
+option the last four columns will show the NTS-KE accesses instead of command
+accesses. If the *-r* option is specified, *chronyd* will reset the counters of
+received and dropped packets or connections after reporting the current values.
++
+An example of the output is:
++
+----
+Hostname NTP Drop Int IntL Last Cmd Drop Int Last
+===============================================================================
+localhost 2 0 2 - 133 15 0 -1 7
+ntp1.example.net 12 0 6 - 23 0 0 - -
+----
++
+Each row shows the data for a single host. Only hosts that have passed the host
+access checks (set with the <<allow,*allow*>>, <<deny,*deny*>>,
+<<cmdallow,*cmdallow*>> and <<cmddeny,*cmddeny*>> commands or configuration
+file directives) are logged. The intervals are displayed as a power of 2 in
+seconds.
++
+The columns are as follows:
++
+. The hostname of the client.
+. The number of NTP packets received from the client.
+. The number of NTP packets dropped to limit the response rate.
+. The average interval between NTP packets.
+. The average interval between NTP packets after limiting the response rate.
+. Time since the last NTP packet was received
+. The number of command packets or NTS-KE connections received/accepted from
+ the client.
+. The number of command packets or NTS-KE connections dropped to limit the
+ response rate.
+. The average interval between command packets or NTS-KE connections.
+. Time since the last command packet or NTS-KE connection was
+ received/accepted.
+
+[[serverstats]]*serverstats*::
+The *serverstats* command displays NTP and command server statistics.
++
+An example of the output is shown below.
++
+----
+NTP packets received : 1598
+NTP packets dropped : 8
+Command packets received : 19
+Command packets dropped : 0
+Client log records dropped : 0
+NTS-KE connections accepted: 3
+NTS-KE connections dropped : 0
+Authenticated NTP packets : 189
+Interleaved NTP packets : 43
+NTP timestamps held : 44
+NTP timestamp span : 120
+NTP daemon RX timestamps : 0
+NTP daemon TX timestamps : 1537
+NTP kernel RX timestamps : 1590
+NTP kernel TX timestamps : 43
+NTP hardware RX timestamps : 0
+NTP hardware TX timestamps : 0
+----
++
+The fields have the following meaning:
++
+*NTP packets received*:::
+The number of valid NTP requests received by the server.
+*NTP packets dropped*:::
+The number of NTP requests dropped by the server due to rate limiting
+(configured by the <<chrony.conf.adoc#ratelimit,*ratelimit*>> directive).
+*Command packets received*:::
+The number of command requests received by the server.
+*Command packets dropped*:::
+The number of command requests dropped by the server due to rate limiting
+(configured by the <<chrony.conf.adoc#cmdratelimit,*cmdratelimit*>> directive).
+*Client log records dropped*:::
+The number of client log records dropped by the server to limit the memory use
+(configured by the <<chrony.conf.adoc#clientloglimit,*clientloglimit*>>
+directive).
+*NTS-KE connections accepted*:::
+The number of NTS-KE connections accepted by the server.
+*NTS-KE connections dropped*:::
+The number of NTS-KE connections dropped by the server due to rate limiting
+(configured by the <<chrony.conf.adoc#ntsratelimit,*ntsratelimit*>> directive).
+*Authenticated NTP packets*:::
+The number of received NTP requests that were authenticated (with a symmetric
+key or NTS).
+*Interleaved NTP packets*:::
+The number of received NTP requests that were detected to be in the interleaved
+mode.
+*NTP timestamps held*:::
+The number of pairs of receive and transmit timestamps that the server is
+currently holding in memory for clients using the interleaved mode.
+*NTP timestamp span*:::
+The interval (in seconds) covered by the currently held NTP timestamps.
+*NTP daemon RX timestamps*:::
+The number of NTP responses which included a receive timestamp captured by the
+daemon.
+*NTP daemon TX timestamps*:::
+The number of NTP responses which included a transmit timestamp captured by the
+daemon.
+*NTP kernel RX timestamps*:::
+The number of NTP responses which included a receive timestamp captured by the
+kernel.
+*NTP kernel TX timestamps*:::
+The number of NTP responses (in the interleaved mode) which included a transmit
+timestamp captured by the kernel.
+*NTP hardware RX timestamps*:::
+The number of NTP responses which included a receive timestamp captured by the
+NIC.
+*NTP hardware TX timestamps*:::
+The number of NTP responses (in the interleaved mode) which included a transmit
+timestamp captured by the NIC.
+
+[[allow]]*allow* [*all*] [_subnet_]::
+The effect of the allow command is identical to the
+<<chrony.conf.adoc#allow,*allow*>> directive in the configuration file.
++
+The syntax is illustrated in the following examples:
++
+----
+allow 1.2.3.4
+allow all 3.4.5.0/24
+allow 2001:db8:789a::/48
+allow 0/0
+allow ::/0
+allow
+allow all
+----
+
+[[deny]]*deny* [*all*] [_subnet_]::
+The effect of the allow command is identical to the
+<<chrony.conf.adoc#deny,*deny*>> directive in the configuration file.
++
+The syntax is illustrated in the following examples:
++
+----
+deny 1.2.3.4
+deny all 3.4.5.0/24
+deny 2001:db8:789a::/48
+deny 0/0
+deny ::/0
+deny
+deny all
+----
+
+[[local]]
+*local* [_option_]...::
+*local* *off*::
+The *local* command allows *chronyd* to be told that it is to appear as a
+reference source, even if it is not itself properly synchronised to an external
+source. This can be used on isolated networks, to allow a computer to be the
+primary time server for other computers.
++
+The first form enables the local reference mode on the host. The syntax is
+identical to the <<chrony.conf.adoc#local,*local*>> directive in the
+configuration file.
++
+The second form disables the local reference mode.
+
+[[smoothing]]*smoothing*::
+The *smoothing* command displays the current state of the NTP server time
+smoothing, which can be enabled with the
+<<chrony.conf.adoc#smoothtime,*smoothtime*>> directive. An example of the
+output is shown below.
++
+----
+Active : Yes
+Offset : +1.000268817 seconds
+Frequency : -0.142859 ppm
+Wander : -0.010000 ppm per second
+Last update : 17.8 seconds ago
+Remaining time : 19988.4 seconds
+----
++
+The fields are explained as follows:
++
+*Active*:::
+This shows if the server time smoothing is currently active. Possible values
+are _Yes_ and _No_. If the *leaponly* option is included in the *smoothtime*
+directive, _(leap second only)_ will be shown on the line.
+*Offset*:::
+This is the current offset applied to the time sent to NTP clients. Positive
+value means the clients are getting time that's ahead of true time.
+*Frequency*:::
+The current frequency offset of the served time. Negative value means the
+time observed by clients is running slower than true time.
+*Wander*:::
+The current frequency wander of the served time. Negative value means the
+time observed by clients is slowing down.
+*Last update*:::
+This field shows how long ago the time smoothing process was updated, e.g.
+*chronyd* accumulated a new measurement.
+*Remaining time*:::
+The time it would take for the smoothing process to get to zero offset and
+frequency if there were no more updates.
+
+[[smoothtime]]
+*smoothtime* *activate*::
+*smoothtime* *reset*::
+The *smoothtime* command can be used to activate or reset the server time
+smoothing process if it is configured with the
+<<chrony.conf.adoc#smoothtime,*smoothtime*>> directive.
+
+=== Monitoring access
+
+[[cmdaccheck]]*cmdaccheck* _address_::
+This command is similar to the <<accheck,*accheck*>> command, except that it is
+used to check whether monitoring access is permitted from a named host.
++
+Examples of use are as follows:
++
+----
+cmdaccheck ntp1.example.net
+cmdaccheck 1.2.3.4
+cmdaccheck 2001:db8::1
+----
+
+[[cmdallow]]*cmdallow* [*all*] [_subnet_]::
+This is similar to the <<allow,*allow*>> command, except that it is used to
+allow particular hosts or subnets to use *chronyc* to monitor with *chronyd* on
+the current host.
+
+[[cmddeny]]*cmddeny* [*all*] [_subnet_]::
+This is similar to the <<deny,*deny*>> command, except that it is used to allow
+particular hosts or subnets to use *chronyc* to monitor *chronyd* on the
+current host.
+
+=== Real-time clock (RTC)
+
+[[rtcdata]]*rtcdata*::
+The *rtcdata* command displays the current RTC parameters.
++
+An example output is shown below.
++
+----
+RTC ref time (GMT) : Sat May 30 07:25:56 2015
+Number of samples : 10
+Number of runs : 5
+Sample span period : 549
+RTC is fast by : -1.632736 seconds
+RTC gains time at : -107.623 ppm
+----
++
+The fields have the following meaning:
++
+*RTC ref time (GMT)*:::
+This is the RTC reading the last time its error was measured.
+*Number of samples*:::
+This is the number of previous measurements being used to determine the RTC
+gain or loss rate.
+*Number of runs*:::
+This is the number of runs of residuals of the same sign following the
+regression fit for (RTC error) versus (RTC time). A value which is small
+indicates that the measurements are not well approximated by a linear model,
+and that the algorithm will tend to delete the older measurements to improve
+the fit.
+*Sample span period*:::
+This is the period that the measurements span (from the oldest to the
+newest). Without a unit the value is in seconds; suffixes _m_ for minutes,
+_h_ for hours, _d_ for days or _y_ for years can be used.
+*RTC is fast by*:::
+This is the estimate of how many seconds fast the RTC when it thought
+the time was at the reference time (above). If this value is large, you
+might (or might not) want to use the <<trimrtc,*trimrtc*>> command to bring the
+RTC into line with the system clock. (Note, a large error will not affect
+*chronyd*'s operation, unless it becomes so big as to start causing rounding
+errors.)
+*RTC gains time at*:::
+This is the amount of time gained (positive) or lost (negative) by the real
+time clock for each second that it ticks. It is measured in parts per
+million. So if the value shown was +1, suppose the RTC was exactly right when
+it crosses a particular second boundary. Then it would be 1 microsecond fast
+when it crosses its next second boundary.
+
+[[trimrtc]]*trimrtc*::
+The *trimrtc* command is used to correct the system's real-time clock (RTC) to
+the main system clock. It has no effect if the error between the two clocks is
+currently estimated at less than a second.
++
+The command takes no arguments. It performs the following steps (if the RTC is
+more than 1 second away from the system clock):
++
+. Remember the currently estimated gain or loss rate of the RTC and flush the
+ previous measurements.
+. Step the real-time clock to bring it within a second of the system clock.
+. Make several measurements to accurately determine the new offset between
+ the RTC and the system clock (i.e. the remaining fraction of a second
+ error).
+. Save the RTC parameters to the RTC file (specified with the
+ <<chrony.conf.adoc#rtcfile,*rtcfile*>> directive in the configuration file).
+{blank}::
++
+The last step is done as a precaution against the computer suffering a power
+failure before either the daemon exits or the <<writertc,*writertc*>> command
+is issued.
++
+*chronyd* will still work perfectly well both whilst operating and across
+machine reboots even if the *trimrtc* command is never used (and the RTC is
+allowed to drift away from true time). The *trimrtc* command is provided as a
+method by which it can be corrected, in a manner compatible with *chronyd*
+using it to maintain accurate time across machine reboots.
++
+The *trimrtc* command can be executed automatically by *chronyd* with the
+<<chrony.conf.adoc#rtcautotrim,*rtcautotrim*>> directive in the configuration
+file.
+
+[[writertc]]*writertc*::
+The *writertc* command writes the currently estimated error and gain or loss rate
+parameters for the RTC to the RTC file (specified with the
+<<chrony.conf.adoc#rtcfile,*rtcfile*>> directive). This information is also
+written automatically when *chronyd* is killed (by the SIGHUP, SIGINT, SIGQUIT
+or SIGTERM signals) or when the <<trimrtc,*trimrtc*>> command is issued.
+
+=== Other daemon commands
+
+[[cyclelogs]]*cyclelogs*::
+The *cyclelogs* command causes all of *chronyd*'s open log files to be closed
+and re-opened. This allows them to be renamed so that they can be periodically
+purged. An example of how to do this is shown below.
++
+----
+# mv /var/log/chrony/measurements.log /var/log/chrony/measurements1.log
+# chronyc cyclelogs
+# rm /var/log/chrony/measurements1.log
+----
+
+[[dump]]*dump*::
+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 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. 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.
+
+[[reset]]*reset* *sources*::
+The *reset sources* 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). *chronyd* will
+drop the measurements automatically when it detects the clock has made an
+unexpected jump, but the detection is not completely reliable.
+
+[[shutdown]]*shutdown*::
+The *shutdown* command causes *chronyd* to exit. This is equivalent to sending
+the process the SIGTERM signal.
+
+=== Client commands
+
+[[dns]]*dns* _option_::
+The *dns* command configures how hostnames and IP addresses are resolved in
+*chronyc*. IP addresses can be resolved to hostnames when printing results of
+<<sources,*sources*>>, <<sourcestats,*sourcestats*>>, <<tracking,*tracking*>>
+and <<clients,*clients*>> commands. Hostnames are resolved in commands that
+take an address as argument.
++
+There are five options:
++
+*dns -n*:::
+Disables resolving IP addresses to hostnames. Raw IP addresses will be
+displayed.
+*dns +n*:::
+Enables resolving IP addresses to hostnames. This is the default unless
+*chronyc* was started with *-n* option.
+*dns -4*:::
+Resolves hostnames only to IPv4 addresses.
+*dns -6*:::
+Resolves hostnames only to IPv6 addresses.
+*dns -46*:::
+Resolves hostnames to both address families. This is the default behaviour
+unless *chronyc* was started with the *-4* or *-6* option.
+
+[[timeout]]*timeout* _timeout_::
+The *timeout* command sets the initial timeout for *chronyc* requests in
+milliseconds. If no response is received from *chronyd*, the timeout is doubled
+and the request is resent. The maximum number of retries is configured with the
+<<retries,*retries*>> command.
++
+By default, the timeout is 1000 milliseconds.
+
+[[retries]]*retries* _retries_::
+The *retries* command sets the maximum number of retries for *chronyc* requests
+before giving up. The response timeout is controlled by the
+<<timeout,*timeout*>> command.
++
+The default is 2.
+
+[[keygen]]*keygen* [_id_ [_type_ [_bits_]]]::
+The *keygen* command generates a key that can be added to the
+key file (specified with the <<chrony.conf.adoc#keyfile,*keyfile*>> directive)
+to allow NTP authentication between server and client, or peers. The key is
+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 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:
++
+----
+keygen 73 SHA1 256
+----
++
+which generates a 256-bit SHA1 key with number 73. The printed line should
+then be securely transferred and added to the key files on both server and
+client, or peers. A different key should be generated for each client or peer.
++
+An example using the AES128 cipher is:
++
+----
+keygen 151 AES128
+----
+
+[[exit]]*exit*::
+[[quit]]*quit*::
+The *exit* and *quit* commands exit from *chronyc* and return the user to the shell.
+
+[[help]]*help*::
+The *help* command displays a summary of the commands and their arguments.
+
+== SEE ALSO
+
+<<chrony.conf.adoc#,*chrony.conf(5)*>>, <<chronyd.adoc#,*chronyd(8)*>>
+
+== BUGS
+
+For instructions on how to report bugs, please visit
+https://chrony-project.org/.
+
+== AUTHORS
+
+chrony was written by Richard Curnow, Miroslav Lichvar, and others.
diff --git a/doc/chronyc.man.in b/doc/chronyc.man.in
new file mode 100644
index 0000000..4541fc6
--- /dev/null
+++ b/doc/chronyc.man.in
@@ -0,0 +1,2756 @@
+'\" t
+.\" Title: chronyc
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2023-12-05
+.\" Manual: User manual
+.\" Source: chrony @CHRONY_VERSION@
+.\" Language: English
+.\"
+.TH "CHRONYC" "1" "2023-12-05" "chrony @CHRONY_VERSION@" "User manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+chronyc \- command\-line interface for chrony daemon
+.SH "SYNOPSIS"
+.sp
+\fBchronyc\fP [\fIOPTION\fP]... [\fICOMMAND\fP]...
+.SH "DESCRIPTION"
+.sp
+\fBchronyc\fP is a command\-line interface program which can be used to monitor
+\fBchronyd\fP\*(Aqs performance and to change various operating parameters whilst it is
+running.
+.sp
+If no commands are specified on the command line, \fBchronyc\fP will expect input
+from the user. The prompt \fIchronyc>\fP will be displayed when it is being run
+from a terminal. If \fBchronyc\fP\*(Aqs input or output are redirected from or to a file,
+the prompt will not be shown.
+.sp
+There are two ways \fBchronyc\fP can access \fBchronyd\fP. One is the Internet
+Protocol (IPv4 or IPv6) and the other is a Unix domain socket, which is
+accessible locally by the root or \fIchrony\fP user. By default, \fBchronyc\fP first
+tries to connect to the Unix domain socket. The compiled\-in default path is
+\fI@CHRONYRUNDIR@/chronyd.sock\fP. If that fails (e.g. because \fBchronyc\fP is
+running under a non\-root user), it will try to connect to 127.0.0.1 and then
+::1.
+.sp
+Only the following monitoring commands, which do not affect the behaviour of
+\fBchronyd\fP, are allowed from the network: \fBactivity\fP, \fBmanual list\fP,
+\fBrtcdata\fP, \fBsmoothing\fP, \fBsourcename\fP, \fBsources\fP, \fBsourcestats\fP, \fBtracking\fP,
+\fBwaitsync\fP. The
+set of hosts from which \fBchronyd\fP will accept these commands can be configured
+with the \fBcmdallow\fP directive in the \fBchronyd\fP\*(Aqs
+configuration file or the \fBcmdallow\fP command in \fBchronyc\fP. By
+default, the commands are accepted only from localhost (127.0.0.1 or ::1).
+.sp
+All other commands are allowed only through the Unix domain socket. When sent
+over the network, \fBchronyd\fP will respond with a \(oqNot authorised\(cq error, even
+if it is from localhost.
+.sp
+Having full access to \fBchronyd\fP via \fBchronyc\fP is more or less equivalent to
+being able to modify the \fBchronyd\fP\*(Aqs configuration file and restart it.
+.SH "OPTIONS"
+.sp
+\fB\-4\fP
+.RS 4
+With this option hostnames will be resolved only to IPv4 addresses.
+.RE
+.sp
+\fB\-6\fP
+.RS 4
+With this option hostnames will be resolved only to IPv6 addresses.
+.RE
+.sp
+\fB\-n\fP
+.RS 4
+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.
+.RE
+.sp
+\fB\-N\fP
+.RS 4
+This option enables printing of original hostnames or IP addresses of NTP
+sources that were specified in the configuration file, or \fBchronyc\fP commands.
+Without the \fB\-n\fP and \fB\-N\fP option, the printed hostnames are obtained from
+reverse DNS lookups and can be different from the specified hostnames.
+.RE
+.sp
+\fB\-c\fP
+.RS 4
+This option enables printing of reports in a comma\-separated values (CSV)
+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.
+.RE
+.sp
+\fB\-e\fP
+.RS 4
+With this option each \fBchronyc\fP response will end with a line containing a
+single dot.
+.RE
+.sp
+\fB\-d\fP
+.RS 4
+This option enables printing of debugging messages if \fBchronyc\fP was compiled
+with debugging support.
+.RE
+.sp
+\fB\-m\fP
+.RS 4
+Normally, all arguments on the command line are interpreted as one command.
+With this option multiple commands can be specified. Each argument will be
+interpreted as a whole command.
+.RE
+.sp
+\fB\-h\fP \fIhost\fP
+.RS 4
+This option specifies the host to be contacted by \fBchronyc\fP. It can be
+specified with a hostname, IP address, or path to the local Unix domain socket.
+Multiple values can be specified as a comma\-separated list to provide a
+fallback.
+.sp
+The default value is \fI@CHRONYRUNDIR@/chronyd.sock,127.0.0.1,::1\fP, i.e. the host
+where \fBchronyc\fP is being run. First, it tries to connect to the Unix domain
+socket and if that fails (e.g. due to running under a non\-root user), it
+will try to connect to 127.0.0.1 and then ::1.
+.RE
+.sp
+\fB\-p\fP \fIport\fP
+.RS 4
+This option allows the user to specify the UDP port number which the target
+\fBchronyd\fP is using for its monitoring connections. This defaults to 323; there
+would rarely be a need to change this.
+.RE
+.sp
+\fB\-f\fP \fIfile\fP
+.RS 4
+This option is ignored and is provided only for compatibility.
+.RE
+.sp
+\fB\-a\fP
+.RS 4
+This option is ignored and is provided only for compatibility.
+.RE
+.sp
+\fB\-v\fP, \fB\-\-version\fP
+.RS 4
+With this option \fBchronyc\fP displays its version number on the terminal and
+exits.
+.RE
+.sp
+\fB\-\-help\fP
+.RS 4
+With this option \fBchronyc\fP displays a help message on the terminal and
+exits.
+.RE
+.SH "COMMANDS"
+.sp
+This section describes each of the commands available within the \fBchronyc\fP
+program.
+.SS "System clock"
+.sp
+\fBtracking\fP
+.RS 4
+The \fBtracking\fP command displays parameters about the system\(cqs clock
+performance. An example of the output is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+Reference ID : CB00710F (ntp1.example.net)
+Stratum : 3
+Ref time (UTC) : Fri Jan 27 09:49:17 2017
+System time : 0.000006523 seconds slow of NTP time
+Last offset : \-0.000006747 seconds
+RMS offset : 0.000035822 seconds
+Frequency : 3.225 ppm slow
+Residual freq : \-0.000 ppm
+Skew : 0.129 ppm
+Root delay : 0.013639022 seconds
+Root dispersion : 0.001100737 seconds
+Update interval : 64.2 seconds
+Leap status : Normal
+.fam
+.fi
+.if n .RE
+.sp
+The fields are explained as follows:
+.sp
+\fBReference ID\fP
+.RS 4
+This is the reference ID and name (or IP address) of the server to which the
+computer is currently synchronised. For IPv4 addresses, the reference ID is
+equal to the address and for IPv6 addresses it is the first 32 bits of the MD5
+sum of the address.
+.sp
+If the reference ID is \fI7F7F0101\fP and there is no name or IP address, it means
+the computer is not synchronised to any external source and that you have the
+\fIlocal\fP mode operating (via the \fBlocal\fP command in \fBchronyc\fP, or the
+\fBlocal\fP directive in the configuration file).
+.sp
+The reference ID is printed as a hexadecimal number. Note that in older
+versions it used to be printed in quad\-dotted notation and could be confused
+with an IPv4 address.
+.RE
+.sp
+\fBStratum\fP
+.RS 4
+The stratum indicates how many hops away from a computer with an attached
+reference clock we are. Such a computer is a stratum\-1 computer, so the
+computer in the example is two hops away (i.e. \fIntp1.example.net\fP is a
+stratum\-2 and is synchronised from a stratum\-1).
+.RE
+.sp
+\fBRef time\fP
+.RS 4
+This is the time (UTC) at which the last measurement from the reference
+source was processed.
+.RE
+.sp
+\fBSystem time\fP
+.RS 4
+This is the current offset between the NTP clock and system clock. The NTP
+clock is a software (virtual) clock maintained by \fBchronyd\fP, which is
+synchronised to the configured time sources and provides time to NTP clients.
+The system clock is synchronised to the NTP clock. To avoid steps in the
+system time, which might have adverse consequences for certain applications,
+the system clock is normally corrected only by speeding up or slowing down (up
+to the rate configured by the \fBmaxslewrate\fP
+directive). If the offset is too large, this correction will take a very long
+time. A step can be forced by the \fBmakestep\fP command, or the
+\fBmakestep\fP directive in the configuration file.
+.sp
+Note that all other offsets reported by \fBchronyc\fP and most offsets in the log
+files are relative to the NTP clock, not the system clock.
+.RE
+.sp
+\fBLast offset\fP
+.RS 4
+This is the estimated local offset on the last clock update. A positive value
+indicates the local time (as previously estimated true time) was ahead of the
+time sources.
+.RE
+.sp
+\fBRMS offset\fP
+.RS 4
+This is a long\-term average of the offset value.
+.RE
+.sp
+\fBFrequency\fP
+.RS 4
+The \(oqfrequency\(cq is the rate by which the system\(cqs clock would be wrong if
+\fBchronyd\fP was not correcting it. It is expressed in ppm (parts per million).
+For example, a value of 1 ppm would mean that when the system\(cqs clock thinks it
+has advanced 1 second, it has actually advanced by 1.000001 seconds relative to
+true time.
+.RE
+.sp
+\fBResidual freq\fP
+.RS 4
+This shows the \(oqresidual frequency\(cq for the currently selected reference
+source. This reflects any difference between what the measurements from the
+reference source indicate the frequency should be and the frequency currently
+being used.
+.sp
+The reason this is not always zero is that a smoothing procedure is
+applied to the frequency. Each time a measurement from the reference
+source is obtained and a new residual frequency computed, the estimated
+accuracy of this residual is compared with the estimated accuracy (see
+\(oqskew\(cq next) of the existing frequency value. A weighted average is
+computed for the new frequency, with weights depending on these accuracies.
+If the measurements from the reference source follow a consistent trend, the
+residual will be driven to zero over time.
+.RE
+.sp
+\fBSkew\fP
+.RS 4
+This is the estimated error bound on the frequency.
+.RE
+.sp
+\fBRoot delay\fP
+.RS 4
+This is the total of the network path delays to the stratum\-1 computer from
+which the computer is ultimately synchronised.
+.RE
+.sp
+\fBRoot dispersion\fP
+.RS 4
+This is the total dispersion accumulated through all the computers back to
+the stratum\-1 computer from which the computer is ultimately synchronised.
+Dispersion is due to system clock resolution, statistical measurement
+variations, etc.
+.sp
+An absolute bound on the computer\(cqs clock accuracy (assuming the stratum\-1
+computer is correct) is given by:
+.sp
+.if n .RS 4
+.nf
+.fam C
+clock_error <= |system_time_offset| + root_dispersion + (0.5 * root_delay)
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBUpdate interval\fP
+.RS 4
+This is the interval between the last two clock updates.
+.RE
+.sp
+\fBLeap status\fP
+.RS 4
+This is the leap status, which can be \fINormal\fP, \fIInsert second\fP, \fIDelete
+second\fP or \fINot synchronised\fP.
+.RE
+.RE
+.sp
+\fBmakestep\fP, \fBmakestep\fP \fIthreshold\fP \fIlimit\fP
+.RS 4
+Normally \fBchronyd\fP will cause the system to gradually correct any time offset,
+by slowing down or speeding up the clock as required. In certain situations,
+the system clock might be so far adrift that this slewing process would take a
+very long time to correct the system clock.
+.sp
+The \fBmakestep\fP command can be used in this situation. There are two forms of
+the command. The first form has no parameters. It tells \fBchronyd\fP to cancel any
+remaining correction that was being slewed and jump the system clock by the
+equivalent amount, making it correct immediately.
+.sp
+The second form configures the automatic stepping, similarly to the
+\fBmakestep\fP directive. It has two parameters,
+stepping threshold (in seconds) and number of future clock updates for which
+the threshold will be active. This can be used with the \fBburst\fP
+command to quickly make a new measurement and correct the clock by stepping if
+needed, without waiting for \fBchronyd\fP to complete the measurement and update
+the clock.
+.sp
+.if n .RS 4
+.nf
+.fam C
+makestep 0.1 1
+burst 1/2
+.fam
+.fi
+.if n .RE
+.sp
+BE WARNED: Certain software will be seriously affected by such jumps in the
+system time. (That is the reason why \fBchronyd\fP uses slewing normally.)
+.RE
+.sp
+\fBmaxupdateskew\fP \fIskew\-in\-ppm\fP
+.RS 4
+This command has the same effect as the
+\fBmaxupdateskew\fP directive in the
+configuration file.
+.RE
+.sp
+\fBwaitsync\fP [\fImax\-tries\fP [\fImax\-correction\fP [\fImax\-skew\fP [\fIinterval\fP]]]]
+.RS 4
+The \fBwaitsync\fP command waits for \fBchronyd\fP to synchronise.
+.sp
+Up to four optional arguments can be specified. The first is the maximum number
+of tries before giving up and returning a non\-zero error code. When 0 is
+specified, or there are no arguments, the number of tries will not be limited.
+.sp
+The second and third arguments are the maximum allowed remaining correction of
+the system clock and the maximum allowed skew (in ppm) as reported by the
+\fBtracking\fP command in the \fBSystem time\fP and \fBSkew\fP fields. If not
+specified or zero, the value will not be checked.
+.sp
+The fourth argument is the interval specified in seconds in which the check is
+repeated. The interval is 10 seconds by default.
+.sp
+An example is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+waitsync 60 0.01
+.fam
+.fi
+.if n .RE
+.sp
+which will wait up to about 10 minutes (60 times 10 seconds) for \fBchronyd\fP to
+synchronise to a source and the remaining correction to be less than 10
+milliseconds.
+.RE
+.SS "Time sources"
+.sp
+\fBsources\fP [\fB\-a\fP] [\fB\-v\fP]
+.RS 4
+This command displays information about the current time sources that \fBchronyd\fP
+is accessing.
+.sp
+If the \fB\-a\fP 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
+\fIID#XXXXXXXXXX\fP, which can be used in other commands expecting a source address.
+.sp
+The \fB\-v\fP option enables a verbose output. In this case,
+extra caption lines are shown as a reminder of the meanings of the columns.
+.sp
+.if n .RS 4
+.nf
+.fam C
+MS Name/IP address Stratum Poll Reach LastRx Last sample
+===============================================================================
+#* GPS0 0 4 377 11 \-479ns[ \-621ns] +/\- 134ns
+^? ntp1.example.net 2 6 377 23 \-923us[ \-924us] +/\- 43ms
+^+ ntp2.example.net 1 6 377 21 \-2629us[\-2619us] +/\- 86ms
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows:
+.sp
+\fBM\fP
+.RS 4
+This indicates the mode of the source. \fI^\fP means a server, \fI=\fP means a peer
+and \fI#\fP indicates a locally connected reference clock.
+.RE
+.sp
+\fBS\fP
+.RS 4
+This column indicates the selection state of the source.
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI*\fP indicates the best source which is currently selected for
+synchronisation.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI+\fP indicates other sources selected for synchronisation, which are combined
+with the best source.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI\-\fP indicates a source which is considered to be selectable for
+synchronisation, but not currently selected.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIx\fP indicates a source which \fBchronyd\fP thinks is a falseticker (i.e. its
+time is inconsistent with a majority of other sources, or sources specified
+with the \fBtrust\fP option).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI~\fP indicates a source whose time appears to have too much variability.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI?\fP indicates a source which is not considered to be selectable for
+synchronisation for other reasons (e.g. unreachable, not synchronised, or
+does not have enough measurements).
+.RE
+.RE
+.sp
+
+.RS 4
+The \fBselectdata\fP command can be used to get more details about
+the selection state.
+.RE
+.sp
+\fBName/IP address\fP
+.RS 4
+This shows the name or the IP address of the source, or reference ID for reference
+clocks.
+.RE
+.sp
+\fBStratum\fP
+.RS 4
+This shows the stratum of the source, as reported in its most recently
+received sample. Stratum 1 indicates a computer with a locally attached
+reference clock. A computer that is synchronised to a stratum 1 computer is
+at stratum 2. A computer that is synchronised to a stratum 2 computer is at
+stratum 3, and so on.
+.RE
+.sp
+\fBPoll\fP
+.RS 4
+This shows the rate at which the source is being polled, as a base\-2
+logarithm of the interval in seconds. Thus, a value of 6 would indicate that
+a measurement is being made every 64 seconds. \fBchronyd\fP automatically varies
+the polling rate in response to prevailing conditions.
+.RE
+.sp
+\fBReach\fP
+.RS 4
+This shows the source\(cqs reachability register printed as an octal number. The
+register has 8 bits and is updated on every received or missed packet from
+the source. A value of 377 indicates that a valid reply was received for all
+from the last eight transmissions.
+.RE
+.sp
+\fBLastRx\fP
+.RS 4
+This column shows how long ago the last good sample (which is shown in the next
+column) was received from the source. Measurements that failed some tests are
+ignored. This is normally in seconds. The letters \fIm\fP, \fIh\fP, \fId\fP or \fIy\fP indicate
+minutes, hours, days, or years.
+.RE
+.sp
+\fBLast sample\fP
+.RS 4
+This column shows the offset between the local clock and the source at the
+last measurement. The number in the square brackets shows the actual measured
+offset. This can be suffixed by \fIns\fP (indicating nanoseconds), \fIus\fP
+(indicating microseconds), \fIms\fP (indicating milliseconds), or \fIs\fP (indicating
+seconds). The number to the left of the square brackets shows the original
+measurement, adjusted to allow for any slews applied to the local clock
+since. Positive offsets indicate that the local clock is ahead of the source.
+The number following the \fI+/\-\fP indicator shows the margin of error in the
+measurement (NTP root distance).
+.RE
+.RE
+.sp
+\fBsourcestats\fP [\fB\-a\fP] [\fB\-v\fP]
+.RS 4
+The \fBsourcestats\fP command displays information about the drift rate and offset
+estimation process for each of the sources currently being examined by
+\fBchronyd\fP.
+.sp
+If the \fB\-a\fP 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
+\fIID#XXXXXXXXXX\fP, which can be used in other commands expecting a source address.
+.sp
+The \fB\-v\fP option enables a verbose output. In this case,
+extra caption lines are shown as a reminder of the meanings of the columns.
+.sp
+An example report is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
+===============================================================================
+ntp1.example.net 11 5 46m \-0.001 0.045 1us 25us
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows:
+.sp
+\fBName/IP Address\fP
+.RS 4
+This is the name or IP address of the NTP server (or peer) or reference ID of the
+reference clock to which the rest of the line relates.
+.RE
+.sp
+\fBNP\fP
+.RS 4
+This is the number of sample points currently being retained for the server.
+The drift rate and current offset are estimated by performing a linear
+regression through these points.
+.RE
+.sp
+\fBNR\fP
+.RS 4
+This is the number of runs of residuals having the same sign following the
+last regression. If this number starts to become too small relative to the
+number of samples, it indicates that a straight line is no longer a good fit
+to the data. If the number of runs is too low, \fBchronyd\fP discards older
+samples and re\-runs the regression until the number of runs becomes
+acceptable.
+.RE
+.sp
+\fBSpan\fP
+.RS 4
+This is the interval between the oldest and newest samples. If no unit is
+shown the value is in seconds. In the example, the interval is 46 minutes.
+.RE
+.sp
+\fBFrequency\fP
+.RS 4
+This is the estimated residual frequency for the server, in parts per
+million. In this case, the computer\(cqs clock is estimated to be running 1 part
+in 10^9 slow relative to the server.
+.RE
+.sp
+\fBFreq Skew\fP
+.RS 4
+This is the estimated error bounds on \fBFreq\fP (again in parts per million).
+.RE
+.sp
+\fBOffset\fP
+.RS 4
+This is the estimated offset of the source.
+.RE
+.sp
+\fBStd Dev\fP
+.RS 4
+This is the estimated sample standard deviation.
+.RE
+.RE
+.sp
+\fBselectdata\fP [\fB\-a\fP] [\fB\-v\fP]
+.RS 4
+The \fBselectdata\fP command displays information specific to the selection of time
+sources. If the \fB\-a\fP option is specified, all sources are displayed, including
+those that do not have a known address yet. With the \fB\-v\fP option, extra caption
+lines are shown as a reminder of the meanings of the columns.
+.sp
+An example of the output is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+S Name/IP Address Auth COpts EOpts Last Score Interval Leap
+=======================================================================
+D ntp1.example.net Y \-\-\-\-\- \-\-TR\- 4 1.0 \-61ms +62ms N
+* ntp2.example.net N \-\-\-\-\- \-\-\-\-\- 0 1.0 \-6846us +7305us N
++ ntp3.example.net N \-\-\-\-\- \-\-\-\-\- 10 1.0 \-7381us +7355us N
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows:
+.sp
+\fBS\fP
+.RS 4
+This column indicates the state of the source after the last source selection.
+It is similar to the state reported by the \fBsources\fP command, but more
+states are reported.
+.RE
+.sp
+
+.RS 4
+The following states indicate the source is not considered selectable for
+synchronisation:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIN\fP \- has the \fBnoselect\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIs\fP \- is not synchronised.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIM\fP \- does not have enough measurements.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fId\fP \- has a root distance larger than the maximum distance (configured by the
+\fBmaxdistance\fP directive).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI~\fP \- has a jitter larger than the maximum jitter (configured by the
+\fBmaxjitter\fP directive).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIw\fP \- waits for other sources to get out of the \fIM\fP state.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIS\fP \- has older measurements than other sources.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIO\fP \- has a stratum equal or larger than the orphan stratum (configured by
+the \fBlocal\fP directive).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIT\fP \- does not fully agree with sources that have the \fBtrust\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIx\fP \- does not agree with other sources (falseticker).
+.RE
+.RE
+.sp
+
+.RS 4
+The following states indicate the source is considered selectable, but it is
+not currently used for synchronisation:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIW\fP \- waits for other sources to be selectable (required by the
+\fBminsources\fP directive, or
+the \fBrequire\fP option of another source).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIP\fP \- another selectable source is preferred due to the \fBprefer\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIU\fP \- waits for a new measurement (after selecting a different best source).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fID\fP \- has, or recently had, a root distance which is too large to be combined
+with other sources (configured by the
+\fBcombinelimit\fP directive).
+.RE
+.RE
+.sp
+
+.RS 4
+The following states indicate the source is used for synchronisation of the
+local clock:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI+\fP \- combined with the best source.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI*\fP \- selected as the best source to update the reference data (e.g. root
+delay, root dispersion).
+.RE
+.RE
+.sp
+\fBName/IP address\fP
+.RS 4
+This column shows the name or IP address of the source if it is an NTP server,
+or the reference ID if it is a reference clock.
+.RE
+.sp
+\fBAuth\fP
+.RS 4
+This column indicites whether an authentication mechanism is enabled for the
+source. \fIY\fP means yes and \fIN\fP means no.
+.RE
+.sp
+\fBCOpts\fP
+.RS 4
+This column displays the configured selection options of the source.
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIN\fP indicates the \fBnoselect\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIP\fP indicates the \fBprefer\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIT\fP indicates the \fBtrust\fP option.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIR\fP indicates the \fBrequire\fP option.
+.RE
+.RE
+.sp
+\fBEOpts\fP
+.RS 4
+This column displays the current effective selection options of the source,
+which can be different from the configured options due to the authentication
+selection mode (configured by the
+\fBauthselectmode\fP directive). The symbols
+are the same as in the \fBCOpts\fP column.
+.RE
+.sp
+\fBLast\fP
+.RS 4
+This column displays how long ago was the last measurement of the source made
+when the selection was performed.
+.RE
+.sp
+\fBScore\fP
+.RS 4
+This column displays the current score against the source in the \fI*\fP state. The
+scoring system avoids frequent reselection when multiple sources have a similar
+root distance. A value larger than 1 indicates this source was better than the
+\fI*\fP source in recent selections. If the score reaches 10, the best source will
+be reselected and the scores will be reset to 1.
+.RE
+.sp
+\fBInterval\fP
+.RS 4
+This column displays the lower and upper endpoint of the interval which was
+expected to contain the true offset of the local clock considering the root
+distance at the time of the selection.
+.RE
+.sp
+\fBLeap\fP
+.RS 4
+This column displays the current leap status of the source.
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fIN\fP indicates the normal status (no leap second).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI+\fP indicates that a leap second will be inserted at the end of the month.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI\-\fP indicates that a leap second will be deleted at the end of the month.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+\fI?\fP indicates the unknown status (i.e. no valid measurement was made).
+.RE
+.RE
+.RE
+.sp
+\fBselectopts\fP \fIaddress|refid\fP [\fI+|\-option\fP]...
+.RS 4
+The \fBselectopts\fP command modifies the configured selection options of an NTP
+source specified by IP address (or the \fIID#XXXXXXXXXX\fP identifier used for
+unknown addresses), or a reference clock specified by reference ID as a string.
+.sp
+The selection options can be added with the \fB+\fP symbol or removed with the \fB\-\fP
+symbol. The \fBselectdata\fP command can be used to verify the configuration. The
+modified options will be applied in the next source selection, e.g. when a new
+measurement is made, or the \fBreselect\fP command is executed.
+.sp
+An example of using this command is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+selectopts 1.2.3.4 \-noselect +prefer
+selectopts GPS +trust
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBreselect\fP
+.RS 4
+To avoid excessive switching between sources, \fBchronyd\fP can stay synchronised
+to a source even when it is not currently the best one among the available
+sources.
+.sp
+The \fBreselect\fP command can be used to force \fBchronyd\fP to reselect the best
+synchronisation source.
+.RE
+.sp
+\fBreselectdist\fP \fIdistance\fP
+.RS 4
+The \fBreselectdist\fP command sets the reselection distance. It is equivalent to
+the \fBreselectdist\fP directive in the
+configuration file.
+.RE
+.SS "NTP sources"
+.sp
+\fBactivity\fP
+.RS 4
+This command reports the number of servers and peers that are online and
+offline. If the \fBauto_offline\fP option is used in specifying some of the servers
+or peers, the \fBactivity\fP command can be useful for detecting when all of them
+have entered the offline state after the network link has been disconnected.
+.sp
+The report shows the number of servers and peers in 5 states:
+.sp
+\fBonline\fP
+.RS 4
+the server or peer is currently online (i.e. assumed by \fBchronyd\fP to be reachable)
+.RE
+.sp
+\fBoffline\fP
+.RS 4
+the server or peer is currently offline (i.e. assumed by \fBchronyd\fP to be
+unreachable, and no measurements from it will be attempted.)
+.RE
+.sp
+\fBburst_online\fP
+.RS 4
+a burst command has been initiated for the server or peer and is being
+performed; after the burst is complete, the server or peer will be returned to
+the online state.
+.RE
+.sp
+\fBburst_offline\fP
+.RS 4
+a burst command has been initiated for the server or peer and is being
+performed; after the burst is complete, the server or peer will be returned to
+the offline state.
+.RE
+.sp
+\fBunresolved\fP
+.RS 4
+the name of the server or peer was not resolved to an address yet; this source is
+not visible in the \fBsources\fP and \fBsourcestats\fP reports.
+.RE
+.RE
+.sp
+\fBauthdata\fP [\fB\-a\fP]
+.RS 4
+The \fBauthdata\fP command displays information specific to authentication of NTP
+sources. If the \fB\-a\fP option is specified, all sources are displayed, including
+those that do not have a known address yet. An example of the output is
+shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+ntp1.example.net NTS 1 15 256 135m 0 0 8 100
+ntp2.example.net SK 30 13 128 \- 0 0 0 0
+ntp3.example.net \- 0 0 0 \- 0 0 0 0
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as follows:
+.sp
+\fBName/IP address\fP
+.RS 4
+This column shows the name or the IP address of the source.
+.RE
+.sp
+\fBMode\fP
+.RS 4
+This column shows which mechanism authenticates NTP packets received from the
+source. \fINTS\fP means Network Time Security, \fISK\fP means a symmetric key, and \fI\-\fP
+means authentication is disabled.
+.RE
+.sp
+\fBKeyID\fP
+.RS 4
+This column shows an identifier of the key used for authentication. With a
+symmetric key, it is the ID from the key file.
+With NTS, it is a number starting at zero and incremented by one with each
+successful key establishment using the NTS\-KE protocol, i.e. it shows how many
+times the key establishment was performed with this source.
+.RE
+.sp
+\fBType\fP
+.RS 4
+This columns shows an identifier of the algorithm used for authentication.
+With a symmetric key, it is the hash function or cipher specified in the key
+file. With NTS, it is an authenticated encryption with associated data (AEAD)
+algorithm, which is negotiated in the NTS\-KE protocol. The following values can
+be reported:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+1: MD5
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+2: SHA1
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+3: SHA256
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+4: SHA384
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+5: SHA512
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+6: SHA3\-224
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+7: SHA3\-256
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+8: SHA3\-384
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+9: SHA3\-512
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+10: TIGER
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+11: WHIRLPOOL
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+13: AES128
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+14: AES256
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+15: AEAD\-AES\-SIV\-CMAC\-256
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+. sp -1
+. IP \(bu 2.3
+.\}
+30: AEAD\-AES\-128\-GCM\-SIV
+.RE
+.RE
+.sp
+\fBKLen\fP
+.RS 4
+This column shows the length of the key in bits.
+.RE
+.sp
+\fBLast\fP
+.RS 4
+This column shows how long ago the last successful key establishment was
+performed. It is in seconds, or letters \fIm\fP, \fIh\fP, \fId\fP or \fIy\fP indicate minutes,
+hours, days, or years.
+.RE
+.sp
+\fBAtmp\fP
+.RS 4
+This column shows the number of attempts to perform the key establishment since
+the last successful key establishment. A number larger than 1 indicates a
+problem with the network or server.
+.RE
+.sp
+\fBNAK\fP
+.RS 4
+This column shows whether an NTS NAK was received since the last request.
+A NAK indicates that authentication failed on the server side due to
+\fBchronyd\fP using a cookie which is no longer valid and that it needs to perform
+the key establishment again in order to get new cookies.
+.RE
+.sp
+\fBCook\fP
+.RS 4
+This column shows the number of NTS cookies that \fBchronyd\fP currently has. If
+the key establishment was successful, a number smaller than 8 indicates a
+problem with the network or server.
+.RE
+.sp
+\fBCLen\fP
+.RS 4
+This column shows the length in bytes of the NTS cookie which will be used in
+the next request.
+.RE
+.RE
+.sp
+\fBntpdata\fP [\fIaddress\fP]
+.RS 4
+The \fBntpdata\fP command displays the last valid measurement and other
+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.
+.sp
+.if n .RS 4
+.nf
+.fam C
+Remote address : 203.0.113.15 (CB00710F)
+Remote port : 123
+Local address : 203.0.113.74 (CB00714A)
+Leap status : Normal
+Version : 4
+Mode : Server
+Stratum : 1
+Poll interval : 10 (1024 seconds)
+Precision : \-24 (0.000000060 seconds)
+Root delay : 0.000015 seconds
+Root dispersion : 0.000015 seconds
+Reference ID : 47505300 (GPS)
+Reference time : Fri Nov 25 15:22:12 2016
+Offset : \-0.000060878 seconds
+Peer delay : 0.000175634 seconds
+Peer dispersion : 0.000000681 seconds
+Response time : 0.000053050 seconds
+Jitter asymmetry: +0.00
+NTP tests : 111 111 1111
+Interleaved : No
+Authenticated : No
+TX timestamping : Kernel
+RX timestamping : Kernel
+Total TX : 24
+Total RX : 24
+Total valid RX : 24
+Total good RX : 22
+.fam
+.fi
+.if n .RE
+.sp
+The fields are explained as follows:
+.sp
+\fBRemote address\fP
+.RS 4
+The IP address of the NTP server or peer, and the corresponding reference ID.
+.RE
+.sp
+\fBRemote port\fP
+.RS 4
+The UDP port number to which the request was sent. The standard NTP port is
+123.
+.RE
+.sp
+\fBLocal address\fP
+.RS 4
+The local IP address which received the response, and the corresponding
+reference ID.
+.RE
+.sp
+\fBLeap status\fP, \fBVersion\fP, \fBMode\fP, \fBStratum\fP, \fBPoll interval\fP, \fBPrecision\fP, \fBRoot delay\fP, \fBRoot dispersion\fP, \fBReference ID\fP, \fBReference time\fP
+.RS 4
+The NTP values from the last valid response.
+.RE
+.sp
+\fBOffset\fP, \fBPeer delay\fP, \fBPeer dispersion\fP
+.RS 4
+The measured values.
+.RE
+.sp
+\fBResponse time\fP
+.RS 4
+The time the server or peer spent in processing of the request and waiting
+before sending the response.
+.RE
+.sp
+\fBJitter asymmetry\fP
+.RS 4
+The estimated asymmetry of network jitter on the path to the source. The
+asymmetry can be between \-0.5 and 0.5. A negative value means the delay of
+packets sent to the source is more variable than the delay of packets sent
+from the source back.
+.RE
+.sp
+\fBNTP tests\fP
+.RS 4
+Results of RFC 5905 tests 1 through 3, 5 through 7, and tests for maximum
+delay, delay ratio, delay dev ratio (or delay quantile), and synchronisation
+loop.
+.RE
+.sp
+\fBInterleaved\fP
+.RS 4
+This shows if the response was in the interleaved mode.
+.RE
+.sp
+\fBAuthenticated\fP
+.RS 4
+This shows if the response was authenticated.
+.RE
+.sp
+\fBTX timestamping\fP
+.RS 4
+The source of the local transmit timestamp. Valid values are \fIDaemon\fP,
+\fIKernel\fP, and \fIHardware\fP.
+.RE
+.sp
+\fBRX timestamping\fP
+.RS 4
+The source of the local receive timestamp.
+.RE
+.sp
+\fBTotal TX\fP
+.RS 4
+The number of packets sent to the source.
+.RE
+.sp
+\fBTotal RX\fP
+.RS 4
+The number of all packets received from the source.
+.RE
+.sp
+\fBTotal valid RX\fP
+.RS 4
+The number of packets which passed the first two groups of NTP tests.
+.RE
+.sp
+\fBTotal good RX\fP
+.RS 4
+The number of packets which passed all three groups of NTP tests, i.e. the NTP
+measurement was accepted.
+.RE
+.RE
+.sp
+\fBadd peer\fP \fIname\fP [\fIoption\fP]...
+.RS 4
+The \fBadd peer\fP command allows a new NTP peer to be added whilst
+\fBchronyd\fP is running.
+.sp
+Following the words \fBadd peer\fP, the syntax of the following
+parameters and options is identical to that for the
+\fBpeer\fP directive in the configuration file.
+.sp
+An example of using this command is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+add peer ntp1.example.net minpoll 6 maxpoll 10 key 25
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBadd pool\fP \fIname\fP [\fIoption\fP]...
+.RS 4
+The \fBadd pool\fP command allows a pool of NTP servers to be added whilst
+\fBchronyd\fP is running.
+.sp
+Following the words \fBadd pool\fP, the syntax of the following parameters and
+options is identical to that for the \fBpool\fP
+directive in the configuration file.
+.sp
+An example of using this command is shown below:
+.sp
+.if n .RS 4
+.nf
+.fam C
+add pool ntp1.example.net maxsources 3 iburst
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBadd server\fP \fIname\fP [\fIoption\fP]...
+.RS 4
+The \fBadd server\fP command allows a new NTP server to be added whilst
+\fBchronyd\fP is running.
+.sp
+Following the words \fBadd server\fP, the syntax of the following parameters and
+options is identical to that for the \fBserver\fP
+directive in the configuration file.
+.sp
+An example of using this command is shown below:
+.sp
+.if n .RS 4
+.nf
+.fam C
+add server ntp1.example.net minpoll 6 maxpoll 10 key 25
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBdelete\fP \fIaddress\fP
+.RS 4
+The \fBdelete\fP command allows an NTP server or peer to be removed
+from the current set of sources.
+.RE
+.sp
+\fBburst\fP \fIgood\fP/\fImax\fP [\fImask\fP/\fImasked\-address\fP], \fBburst\fP \fIgood\fP/\fImax\fP [\fImasked\-address\fP/\fImasked\-bits\fP], \fBburst\fP \fIgood\fP/\fImax\fP [\fIaddress\fP]
+.RS 4
+The \fBburst\fP command tells \fBchronyd\fP to make a set of measurements to each of
+its NTP sources over a short duration (rather than the usual periodic
+measurements that it makes). After such a burst, \fBchronyd\fP will revert to the
+previous state for each source. This might be either online, if the source was
+being periodically measured in the normal way, or offline, if the source had
+been indicated as being offline. (A source can be switched between the online
+and offline states with the \fBonline\fP and \fBoffline\fP
+commands.)
+.sp
+The \fImask\fP and \fImasked\-address\fP arguments are optional, in which case \fBchronyd\fP
+will initiate a burst for all of its currently defined sources.
+.sp
+The arguments have the following meaning and format:
+.sp
+\fIgood\fP
+.RS 4
+This defines the number of good measurements that \fBchronyd\fP will want to
+obtain from each source. A measurement is good if it passes certain tests,
+for example, the round trip time to the source must be acceptable. (This
+allows \fBchronyd\fP to reject measurements that are likely to be bogus.)
+.RE
+.sp
+\fImax\fP
+.RS 4
+This defines the maximum number of measurements that \fBchronyd\fP will attempt
+to make, even if the required number of good measurements has not been
+obtained.
+.RE
+.sp
+\fImask\fP
+.RS 4
+This is an IP address with which the IP address of each of \fBchronyd\fP\*(Aqs
+sources is to be masked.
+.RE
+.sp
+\fImasked\-address\fP
+.RS 4
+This is an IP address. If the masked IP address of a source matches this
+value then the burst command is applied to that source.
+.RE
+.sp
+\fImasked\-bits\fP
+.RS 4
+This can be used with \fImasked\-address\fP for CIDR notation, which is a shorter
+alternative to the form with mask.
+.RE
+.sp
+\fIaddress\fP
+.RS 4
+This is an IP address or a hostname. The burst command is applied only to
+that source.
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+If no \fImask\fP or \fImasked\-address\fP arguments are provided, every source will be
+matched.
+.sp
+An example of the two\-argument form of the command is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+burst 2/10
+.fam
+.fi
+.if n .RE
+.sp
+This will cause \fBchronyd\fP to attempt to get two good measurements from each
+source, stopping after two have been obtained, but in no event will it try more
+than ten probes to the source.
+.sp
+Examples of the four\-argument form of the command are:
+.sp
+.if n .RS 4
+.nf
+.fam C
+burst 2/10 255.255.0.0/1.2.0.0
+burst 2/10 2001:db8:789a::/48
+.fam
+.fi
+.if n .RE
+.sp
+In the first case, the two out of ten sampling will only be applied to sources
+whose IPv4 addresses are of the form \fI1.2.x.y\fP, where \fIx\fP and \fIy\fP are
+arbitrary. In the second case, the sampling will be applied to sources whose
+IPv6 addresses have first 48 bits equal to \fI2001:db8:789a\fP.
+.sp
+Example of the three\-argument form of the command is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+burst 2/10 ntp1.example.net
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBmaxdelay\fP \fIaddress\fP \fIdelay\fP
+.RS 4
+This allows the \fBmaxdelay\fP option for one of the sources to be modified, in the
+same way as specifying the \fBmaxdelay\fP option for the
+\fBserver\fP directive in the configuration file.
+.RE
+.sp
+\fBmaxdelaydevratio\fP \fIaddress\fP \fIratio\fP
+.RS 4
+This allows the \fBmaxdelaydevratio\fP option for one of the sources to be
+modified, in the same way as specifying the \fBmaxdelaydevratio\fP option for the
+\fBserver\fP directive in the configuration file.
+.RE
+.sp
+\fBmaxdelayratio\fP \fIaddress\fP \fIratio\fP
+.RS 4
+This allows the \fBmaxdelayratio\fP option for one of the sources to be modified,
+in the same way as specifying the \fBmaxdelayratio\fP option for the
+\fBserver\fP directive in the configuration file.
+.RE
+.sp
+\fBmaxpoll\fP \fIaddress\fP \fImaxpoll\fP
+.RS 4
+The \fBmaxpoll\fP command is used to modify the maximum polling interval for one of
+the current set of sources. It is equivalent to the \fBmaxpoll\fP option in the
+\fBserver\fP directive in the configuration file.
+.sp
+Note that the new maximum polling interval only takes effect after the next
+measurement has been made.
+.RE
+.sp
+\fBminpoll\fP \fIaddress\fP \fIminpoll\fP
+.RS 4
+The \fBminpoll\fP command is used to modify the minimum polling interval for one of
+the current set of sources. It is equivalent to the \fBminpoll\fP option in the
+\fBserver\fP directive in the configuration file.
+.sp
+Note that the new minimum polling interval only takes effect after the next
+measurement has been made.
+.RE
+.sp
+\fBminstratum\fP \fIaddress\fP \fIminstratum\fP
+.RS 4
+The \fBminstratum\fP command is used to modify the minimum stratum for one of the
+current set of sources. It is equivalent to the \fBminstratum\fP option in the
+\fBserver\fP directive in the configuration file.
+.RE
+.sp
+\fBoffline\fP [\fIaddress\fP], \fBoffline\fP [\fImasked\-address\fP/\fImasked\-bits\fP], \fBoffline\fP [\fImask\fP/\fImasked\-address\fP]
+.RS 4
+The \fBoffline\fP command is used to warn \fBchronyd\fP that the network connection to
+a particular host or hosts is about to be lost, e.g. on computers with
+intermittent connection to their time sources.
+.sp
+Another case where \fBoffline\fP could be used is where a computer serves time to a
+local group of computers, and has a permanent connection to true time servers
+outside the organisation. However, the external connection is heavily loaded at
+certain times of the day and the measurements obtained are less reliable at
+those times. In this case, it is probably most useful to determine the
+gain or loss rate during the quiet periods and let the whole network coast through
+the loaded periods. The \fBoffline\fP and \fBonline\fP commands can be used to achieve
+this.
+.sp
+There are four forms of the \fBoffline\fP command. The first form is a wildcard,
+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.
+.sp
+.if n .RS 4
+.nf
+.fam C
+offline
+offline 255.255.255.0/1.2.3.0
+offline 2001:db8:789a::/48
+offline ntp1.example.net
+.fam
+.fi
+.if n .RE
+.sp
+The second form means that the \fBoffline\fP command is to be applied to any source
+whose IPv4 address is in the \fI1.2.3\fP subnet. (The host\(cqs address is logically
+and\-ed with the mask, and if the result matches the \fImasked\-address\fP the host
+is processed.) The third form means that the command is to be applied to all
+sources whose IPv6 addresses have their first 48 bits equal to \fI2001:db8:789a\fP. The
+fourth form means that the command is to be applied only to that one source.
+.sp
+The wildcard form of the address is equivalent to:
+.sp
+.if n .RS 4
+.nf
+.fam C
+offline 0.0.0.0/0.0.0.0
+offline ::/0
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBonline\fP [\fIaddress\fP], \fBonline\fP [\fImasked\-address\fP/\fImasked\-bits\fP], \fBonline\fP [\fImask\fP/\fImasked\-address\fP]
+.RS 4
+The \fBonline\fP command is opposite in function to the \fBoffline\fP
+command. It is used to advise \fBchronyd\fP that network connectivity to a
+particular source or sources has been restored.
+.sp
+The syntax is identical to that of the \fBoffline\fP command.
+.RE
+.sp
+\fBonoffline\fP
+.RS 4
+The \fBonoffline\fP command tells \fBchronyd\fP 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 network
+route to the source is present.
+.RE
+.sp
+\fBpolltarget\fP \fIaddress\fP \fIpolltarget\fP
+.RS 4
+The \fBpolltarget\fP command is used to modify the poll target for one of the
+current set of sources. It is equivalent to the \fBpolltarget\fP option in the
+\fBserver\fP directive in the configuration file.
+.RE
+.sp
+\fBrefresh\fP
+.RS 4
+The \fBrefresh\fP command can be used to force \fBchronyd\fP to resolve the names of
+configured NTP sources to IP addresses again and replace any addresses missing
+in the list of resolved addresses.
+.sp
+Sources that stop responding are replaced with newly resolved addresses
+automatically after 8 polling intervals. This command can be used to replace
+them immediately, e.g. after suspending and resuming the machine in a different
+network.
+.sp
+Note that with pools which have more than 16 addresses, or not all IPv4 or IPv6
+addresses are included in a single DNS response (e.g. pool.ntp.org), this
+command might replace the addresses even if they are still in the pool.
+.RE
+.sp
+\fBreload\fP \fBsources\fP
+.RS 4
+The \fBreload sources\fP command causes \fBchronyd\fP to re\-read all \fI*.sources\fP files
+from the directories specified by the
+\fBsourcedir\fP directive.
+.sp
+Note that modified sources (e.g. specified with a new option) are not modified
+in memory. They are removed and added again, which causes them to lose old
+measurements and reset the selection state.
+.RE
+.sp
+\fBsourcename\fP \fIaddress\fP
+.RS 4
+The \fBsourcename\fP command prints the original hostname or address that was
+specified for an NTP source in the configuration file, or the \fBadd\fP command.
+This command is an alternative to the \fB\-N\fP option, which can be useful in
+scripts.
+.sp
+Note that different NTP sources can share the same name, e.g. servers from a
+pool.
+.RE
+.SS "Manual time input"
+.sp
+\fBmanual\fP \fBon\fP, \fBmanual\fP \fBoff\fP, \fBmanual\fP \fBdelete\fP \fIindex\fP, \fBmanual\fP \fBlist\fP, \fBmanual\fP \fBreset\fP
+.RS 4
+The manual command enables and disables use of the \fBsettime\fP
+command, and is used to modify the behaviour of the manual clock driver.
+.sp
+The \fBon\fP form of the command enables use of the \fBsettime\fP command.
+.sp
+The \fBoff\fP form of the command disables use of the \fBsettime\fP command.
+.sp
+The \fBlist\fP form of the command lists all the samples currently stored in
+\fBchronyd\fP. The output is illustrated below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+210 n_samples = 1
+# Date Time(UTC) Slewed Original Residual
+====================================================
+ 0 27Jan99 22:09:20 0.00 0.97 0.00
+.fam
+.fi
+.if n .RE
+.sp
+The columns are as as follows:
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+The sample index (used for the \fBmanual delete\fP command).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+The date and time of the sample.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+The system clock error when the timestamp was entered, adjusted to allow
+for changes made to the system clock since.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+The system clock error when the timestamp was entered, as it originally was
+(without allowing for changes to the system clock since).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+The regression residual at this point, in seconds. This allows \(oqoutliers\(cq
+to be easily spotted, so that they can be deleted using the \fBmanual delete\fP
+command.
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+The \fBdelete\fP form of the command deletes a single sample. The parameter is the
+index of the sample, as shown in the first column of the output from \fBmanual
+list\fP. Following deletion of the data point, the current error and drift rate
+are re\-estimated from the remaining data points and the system clock trimmed if
+necessary. This option is intended to allow \(oqoutliers\(cq to be discarded, i.e.
+samples where the administrator realises they have entered a very poor
+timestamp.
+.sp
+The \fBreset\fP form of the command deletes all samples at once. The system clock
+is left running as it was before the command was entered.
+.RE
+.sp
+\fBsettime\fP \fItime\fP
+.RS 4
+The \fBsettime\fP command allows the current time to be entered manually, if this
+option has been configured into \fBchronyd\fP. (It can be configured either with
+the \fBmanual\fP directive in the configuration file,
+or with the \fBmanual\fP command of \fBchronyc\fP.)
+.sp
+It should be noted that the computer\(cqs sense of time will only be as accurate
+as the reference you use for providing this input (e.g. your watch), as well as
+how well you can time the press of the return key.
+.sp
+Providing your computer\(cqs time zone is set up properly, you will be able to
+enter a local time (rather than UTC).
+.sp
+The response to a successful \fBsettime\fP command indicates the amount that the
+computer\(cqs clock was wrong. It should be apparent from this if you have entered
+the time wrongly, e.g. with the wrong time zone.
+.sp
+The rate of drift of the system clock is estimated by a regression process
+using the entered measurement and all previous measurements entered during the
+present run of \fBchronyd\fP. However, the entered measurement is used for
+adjusting the current clock offset (rather than the estimated intercept from
+the regression, which is ignored). Contrast what happens with the
+\fBmanual delete\fP command, where the intercept is used to set the
+current offset (since there is no measurement that has just been entered in
+that case).
+.sp
+The time is parsed by the public domain \fIgetdate\fP algorithm. Consequently, you
+can only specify time to the nearest second.
+.sp
+Examples of inputs that are valid are shown below:
+.sp
+.if n .RS 4
+.nf
+.fam C
+settime 16:30
+settime 16:30:05
+settime Nov 21, 2015 16:30:05
+.fam
+.fi
+.if n .RE
+.sp
+For a full description of getdate, see the getdate documentation
+(bundled, for example, with the source for GNU tar).
+.RE
+.SS "NTP access"
+.sp
+\fBaccheck\fP \fIaddress\fP
+.RS 4
+This command allows you to check whether client NTP access is allowed from a
+particular host.
+.sp
+Examples of use, showing a named host and a numeric IP address, are as follows:
+.sp
+.if n .RS 4
+.nf
+.fam C
+accheck ntp1.example.net
+accheck 1.2.3.4
+accheck 2001:db8::1
+.fam
+.fi
+.if n .RE
+.sp
+This command can be used to examine the effect of a series of \fBallow\fP, \fBallow
+all\fP, \fBdeny\fP, and \fBdeny all\fP commands specified either via \fBchronyc\fP, or in
+\fBchronyd\fP\*(Aqs configuration file.
+.RE
+.sp
+\fBclients\fP [\fB\-p\fP \fIpackets\fP] [\fB\-k\fP] [\fB\-r\fP]
+.RS 4
+This command shows a list of clients that have accessed the server, through
+the NTP, command, or NTS\-KE port. It does not include accesses over the Unix
+domain command socket.
+.sp
+The \fB\-p\fP option specifies the minimum number of received NTP or command
+packets, or accepted NTS\-KE connections, needed to include a client in the
+list. The default value is 0, i.e. all clients are reported. With the \fB\-k\fP
+option the last four columns will show the NTS\-KE accesses instead of command
+accesses. If the \fB\-r\fP option is specified, \fBchronyd\fP will reset the counters of
+received and dropped packets or connections after reporting the current values.
+.sp
+An example of the output is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+Hostname NTP Drop Int IntL Last Cmd Drop Int Last
+===============================================================================
+localhost 2 0 2 \- 133 15 0 \-1 7
+ntp1.example.net 12 0 6 \- 23 0 0 \- \-
+.fam
+.fi
+.if n .RE
+.sp
+Each row shows the data for a single host. Only hosts that have passed the host
+access checks (set with the \fBallow\fP, \fBdeny\fP,
+\fBcmdallow\fP and \fBcmddeny\fP commands or configuration
+file directives) are logged. The intervals are displayed as a power of 2 in
+seconds.
+.sp
+The columns are as follows:
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+The hostname of the client.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+The number of NTP packets received from the client.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+The number of NTP packets dropped to limit the response rate.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+The average interval between NTP packets.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 5.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 5." 4.2
+.\}
+The average interval between NTP packets after limiting the response rate.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 6.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 6." 4.2
+.\}
+Time since the last NTP packet was received
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 7.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 7." 4.2
+.\}
+The number of command packets or NTS\-KE connections received/accepted from
+the client.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 8.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 8." 4.2
+.\}
+The number of command packets or NTS\-KE connections dropped to limit the
+response rate.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 9.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 9." 4.2
+.\}
+The average interval between command packets or NTS\-KE connections.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 10.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 10." 4.2
+.\}
+Time since the last command packet or NTS\-KE connection was
+received/accepted.
+.RE
+.RE
+.sp
+\fBserverstats\fP
+.RS 4
+The \fBserverstats\fP command displays NTP and command server statistics.
+.sp
+An example of the output is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+NTP packets received : 1598
+NTP packets dropped : 8
+Command packets received : 19
+Command packets dropped : 0
+Client log records dropped : 0
+NTS\-KE connections accepted: 3
+NTS\-KE connections dropped : 0
+Authenticated NTP packets : 189
+Interleaved NTP packets : 43
+NTP timestamps held : 44
+NTP timestamp span : 120
+NTP daemon RX timestamps : 0
+NTP daemon TX timestamps : 1537
+NTP kernel RX timestamps : 1590
+NTP kernel TX timestamps : 43
+NTP hardware RX timestamps : 0
+NTP hardware TX timestamps : 0
+.fam
+.fi
+.if n .RE
+.sp
+The fields have the following meaning:
+.sp
+\fBNTP packets received\fP
+.RS 4
+The number of valid NTP requests received by the server.
+.RE
+.sp
+\fBNTP packets dropped\fP
+.RS 4
+The number of NTP requests dropped by the server due to rate limiting
+(configured by the \fBratelimit\fP directive).
+.RE
+.sp
+\fBCommand packets received\fP
+.RS 4
+The number of command requests received by the server.
+.RE
+.sp
+\fBCommand packets dropped\fP
+.RS 4
+The number of command requests dropped by the server due to rate limiting
+(configured by the \fBcmdratelimit\fP directive).
+.RE
+.sp
+\fBClient log records dropped\fP
+.RS 4
+The number of client log records dropped by the server to limit the memory use
+(configured by the \fBclientloglimit\fP
+directive).
+.RE
+.sp
+\fBNTS\-KE connections accepted\fP
+.RS 4
+The number of NTS\-KE connections accepted by the server.
+.RE
+.sp
+\fBNTS\-KE connections dropped\fP
+.RS 4
+The number of NTS\-KE connections dropped by the server due to rate limiting
+(configured by the \fBntsratelimit\fP directive).
+.RE
+.sp
+\fBAuthenticated NTP packets\fP
+.RS 4
+The number of received NTP requests that were authenticated (with a symmetric
+key or NTS).
+.RE
+.sp
+\fBInterleaved NTP packets\fP
+.RS 4
+The number of received NTP requests that were detected to be in the interleaved
+mode.
+.RE
+.sp
+\fBNTP timestamps held\fP
+.RS 4
+The number of pairs of receive and transmit timestamps that the server is
+currently holding in memory for clients using the interleaved mode.
+.RE
+.sp
+\fBNTP timestamp span\fP
+.RS 4
+The interval (in seconds) covered by the currently held NTP timestamps.
+.RE
+.sp
+\fBNTP daemon RX timestamps\fP
+.RS 4
+The number of NTP responses which included a receive timestamp captured by the
+daemon.
+.RE
+.sp
+\fBNTP daemon TX timestamps\fP
+.RS 4
+The number of NTP responses which included a transmit timestamp captured by the
+daemon.
+.RE
+.sp
+\fBNTP kernel RX timestamps\fP
+.RS 4
+The number of NTP responses which included a receive timestamp captured by the
+kernel.
+.RE
+.sp
+\fBNTP kernel TX timestamps\fP
+.RS 4
+The number of NTP responses (in the interleaved mode) which included a transmit
+timestamp captured by the kernel.
+.RE
+.sp
+\fBNTP hardware RX timestamps\fP
+.RS 4
+The number of NTP responses which included a receive timestamp captured by the
+NIC.
+.RE
+.sp
+\fBNTP hardware TX timestamps\fP
+.RS 4
+The number of NTP responses (in the interleaved mode) which included a transmit
+timestamp captured by the NIC.
+.RE
+.RE
+.sp
+\fBallow\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+The effect of the allow command is identical to the
+\fBallow\fP directive in the configuration file.
+.sp
+The syntax is illustrated in the following examples:
+.sp
+.if n .RS 4
+.nf
+.fam C
+allow 1.2.3.4
+allow all 3.4.5.0/24
+allow 2001:db8:789a::/48
+allow 0/0
+allow ::/0
+allow
+allow all
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBdeny\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+The effect of the allow command is identical to the
+\fBdeny\fP directive in the configuration file.
+.sp
+The syntax is illustrated in the following examples:
+.sp
+.if n .RS 4
+.nf
+.fam C
+deny 1.2.3.4
+deny all 3.4.5.0/24
+deny 2001:db8:789a::/48
+deny 0/0
+deny ::/0
+deny
+deny all
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBlocal\fP [\fIoption\fP]..., \fBlocal\fP \fBoff\fP
+.RS 4
+The \fBlocal\fP command allows \fBchronyd\fP to be told that it is to appear as a
+reference source, even if it is not itself properly synchronised to an external
+source. This can be used on isolated networks, to allow a computer to be the
+primary time server for other computers.
+.sp
+The first form enables the local reference mode on the host. The syntax is
+identical to the \fBlocal\fP directive in the
+configuration file.
+.sp
+The second form disables the local reference mode.
+.RE
+.sp
+\fBsmoothing\fP
+.RS 4
+The \fBsmoothing\fP command displays the current state of the NTP server time
+smoothing, which can be enabled with the
+\fBsmoothtime\fP directive. An example of the
+output is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+Active : Yes
+Offset : +1.000268817 seconds
+Frequency : \-0.142859 ppm
+Wander : \-0.010000 ppm per second
+Last update : 17.8 seconds ago
+Remaining time : 19988.4 seconds
+.fam
+.fi
+.if n .RE
+.sp
+The fields are explained as follows:
+.sp
+\fBActive\fP
+.RS 4
+This shows if the server time smoothing is currently active. Possible values
+are \fIYes\fP and \fINo\fP. If the \fBleaponly\fP option is included in the \fBsmoothtime\fP
+directive, \fI(leap second only)\fP will be shown on the line.
+.RE
+.sp
+\fBOffset\fP
+.RS 4
+This is the current offset applied to the time sent to NTP clients. Positive
+value means the clients are getting time that\(cqs ahead of true time.
+.RE
+.sp
+\fBFrequency\fP
+.RS 4
+The current frequency offset of the served time. Negative value means the
+time observed by clients is running slower than true time.
+.RE
+.sp
+\fBWander\fP
+.RS 4
+The current frequency wander of the served time. Negative value means the
+time observed by clients is slowing down.
+.RE
+.sp
+\fBLast update\fP
+.RS 4
+This field shows how long ago the time smoothing process was updated, e.g.
+\fBchronyd\fP accumulated a new measurement.
+.RE
+.sp
+\fBRemaining time\fP
+.RS 4
+The time it would take for the smoothing process to get to zero offset and
+frequency if there were no more updates.
+.RE
+.RE
+.sp
+\fBsmoothtime\fP \fBactivate\fP, \fBsmoothtime\fP \fBreset\fP
+.RS 4
+The \fBsmoothtime\fP command can be used to activate or reset the server time
+smoothing process if it is configured with the
+\fBsmoothtime\fP directive.
+.RE
+.SS "Monitoring access"
+.sp
+\fBcmdaccheck\fP \fIaddress\fP
+.RS 4
+This command is similar to the \fBaccheck\fP command, except that it is
+used to check whether monitoring access is permitted from a named host.
+.sp
+Examples of use are as follows:
+.sp
+.if n .RS 4
+.nf
+.fam C
+cmdaccheck ntp1.example.net
+cmdaccheck 1.2.3.4
+cmdaccheck 2001:db8::1
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBcmdallow\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+This is similar to the \fBallow\fP command, except that it is used to
+allow particular hosts or subnets to use \fBchronyc\fP to monitor with \fBchronyd\fP on
+the current host.
+.RE
+.sp
+\fBcmddeny\fP [\fBall\fP] [\fIsubnet\fP]
+.RS 4
+This is similar to the \fBdeny\fP command, except that it is used to allow
+particular hosts or subnets to use \fBchronyc\fP to monitor \fBchronyd\fP on the
+current host.
+.RE
+.SS "Real\-time clock (RTC)"
+.sp
+\fBrtcdata\fP
+.RS 4
+The \fBrtcdata\fP command displays the current RTC parameters.
+.sp
+An example output is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+RTC ref time (GMT) : Sat May 30 07:25:56 2015
+Number of samples : 10
+Number of runs : 5
+Sample span period : 549
+RTC is fast by : \-1.632736 seconds
+RTC gains time at : \-107.623 ppm
+.fam
+.fi
+.if n .RE
+.sp
+The fields have the following meaning:
+.sp
+\fBRTC ref time (GMT)\fP
+.RS 4
+This is the RTC reading the last time its error was measured.
+.RE
+.sp
+\fBNumber of samples\fP
+.RS 4
+This is the number of previous measurements being used to determine the RTC
+gain or loss rate.
+.RE
+.sp
+\fBNumber of runs\fP
+.RS 4
+This is the number of runs of residuals of the same sign following the
+regression fit for (RTC error) versus (RTC time). A value which is small
+indicates that the measurements are not well approximated by a linear model,
+and that the algorithm will tend to delete the older measurements to improve
+the fit.
+.RE
+.sp
+\fBSample span period\fP
+.RS 4
+This is the period that the measurements span (from the oldest to the
+newest). Without a unit the value is in seconds; suffixes \fIm\fP for minutes,
+\fIh\fP for hours, \fId\fP for days or \fIy\fP for years can be used.
+.RE
+.sp
+\fBRTC is fast by\fP
+.RS 4
+This is the estimate of how many seconds fast the RTC when it thought
+the time was at the reference time (above). If this value is large, you
+might (or might not) want to use the \fBtrimrtc\fP command to bring the
+RTC into line with the system clock. (Note, a large error will not affect
+\fBchronyd\fP\*(Aqs operation, unless it becomes so big as to start causing rounding
+errors.)
+.RE
+.sp
+\fBRTC gains time at\fP
+.RS 4
+This is the amount of time gained (positive) or lost (negative) by the real
+time clock for each second that it ticks. It is measured in parts per
+million. So if the value shown was +1, suppose the RTC was exactly right when
+it crosses a particular second boundary. Then it would be 1 microsecond fast
+when it crosses its next second boundary.
+.RE
+.RE
+.sp
+\fBtrimrtc\fP
+.RS 4
+The \fBtrimrtc\fP command is used to correct the system\(cqs real\-time clock (RTC) to
+the main system clock. It has no effect if the error between the two clocks is
+currently estimated at less than a second.
+.sp
+The command takes no arguments. It performs the following steps (if the RTC is
+more than 1 second away from the system clock):
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 1.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 1." 4.2
+.\}
+Remember the currently estimated gain or loss rate of the RTC and flush the
+previous measurements.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 2.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 2." 4.2
+.\}
+Step the real\-time clock to bring it within a second of the system clock.
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 3.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 3." 4.2
+.\}
+Make several measurements to accurately determine the new offset between
+the RTC and the system clock (i.e. the remaining fraction of a second
+error).
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04' 4.\h'+01'\c
+.\}
+.el \{\
+. sp -1
+. IP " 4." 4.2
+.\}
+Save the RTC parameters to the RTC file (specified with the
+\fBrtcfile\fP directive in the configuration file).
+.RE
+.RE
+.sp
+
+.RS 4
+.sp
+The last step is done as a precaution against the computer suffering a power
+failure before either the daemon exits or the \fBwritertc\fP command
+is issued.
+.sp
+\fBchronyd\fP will still work perfectly well both whilst operating and across
+machine reboots even if the \fBtrimrtc\fP command is never used (and the RTC is
+allowed to drift away from true time). The \fBtrimrtc\fP command is provided as a
+method by which it can be corrected, in a manner compatible with \fBchronyd\fP
+using it to maintain accurate time across machine reboots.
+.sp
+The \fBtrimrtc\fP command can be executed automatically by \fBchronyd\fP with the
+\fBrtcautotrim\fP directive in the configuration
+file.
+.RE
+.sp
+\fBwritertc\fP
+.RS 4
+The \fBwritertc\fP command writes the currently estimated error and gain or loss rate
+parameters for the RTC to the RTC file (specified with the
+\fBrtcfile\fP directive). This information is also
+written automatically when \fBchronyd\fP is killed (by the SIGHUP, SIGINT, SIGQUIT
+or SIGTERM signals) or when the \fBtrimrtc\fP command is issued.
+.RE
+.SS "Other daemon commands"
+.sp
+\fBcyclelogs\fP
+.RS 4
+The \fBcyclelogs\fP command causes all of \fBchronyd\fP\*(Aqs open log files to be closed
+and re\-opened. This allows them to be renamed so that they can be periodically
+purged. An example of how to do this is shown below.
+.sp
+.if n .RS 4
+.nf
+.fam C
+# mv /var/log/chrony/measurements.log /var/log/chrony/measurements1.log
+# chronyc cyclelogs
+# rm /var/log/chrony/measurements1.log
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBdump\fP
+.RS 4
+The \fBdump\fP command causes \fBchronyd\fP 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 \fBdumpdir\fP
+directive and also write server NTS keys and client NTS cookies to the
+directory specified by the \fBntsdumpdir\fP
+directive. Note that \fBchronyd\fP does this automatically when it exits. This
+command is mainly useful for inspection whilst \fBchronyd\fP is running.
+.RE
+.sp
+\fBrekey\fP
+.RS 4
+The \fBrekey\fP command causes \fBchronyd\fP to re\-read the key file specified in the
+configuration file by the \fBkeyfile\fP directive. It
+also re\-reads the server NTS keys if
+\fBntsdumpdir\fP is specified and
+automatic rotation is disabled in the
+configuration file.
+.RE
+.sp
+\fBreset\fP \fBsources\fP
+.RS 4
+The \fBreset sources\fP command causes \fBchronyd\fP to drop all measurements and
+switch to the unsynchronised state. This command can help \fBchronyd\fP 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). \fBchronyd\fP will
+drop the measurements automatically when it detects the clock has made an
+unexpected jump, but the detection is not completely reliable.
+.RE
+.sp
+\fBshutdown\fP
+.RS 4
+The \fBshutdown\fP command causes \fBchronyd\fP to exit. This is equivalent to sending
+the process the SIGTERM signal.
+.RE
+.SS "Client commands"
+.sp
+\fBdns\fP \fIoption\fP
+.RS 4
+The \fBdns\fP command configures how hostnames and IP addresses are resolved in
+\fBchronyc\fP. IP addresses can be resolved to hostnames when printing results of
+\fBsources\fP, \fBsourcestats\fP, \fBtracking\fP
+and \fBclients\fP commands. Hostnames are resolved in commands that
+take an address as argument.
+.sp
+There are five options:
+.sp
+\fBdns \-n\fP
+.RS 4
+Disables resolving IP addresses to hostnames. Raw IP addresses will be
+displayed.
+.RE
+.sp
+\fBdns +n\fP
+.RS 4
+Enables resolving IP addresses to hostnames. This is the default unless
+\fBchronyc\fP was started with \fB\-n\fP option.
+.RE
+.sp
+\fBdns \-4\fP
+.RS 4
+Resolves hostnames only to IPv4 addresses.
+.RE
+.sp
+\fBdns \-6\fP
+.RS 4
+Resolves hostnames only to IPv6 addresses.
+.RE
+.sp
+\fBdns \-46\fP
+.RS 4
+Resolves hostnames to both address families. This is the default behaviour
+unless \fBchronyc\fP was started with the \fB\-4\fP or \fB\-6\fP option.
+.RE
+.RE
+.sp
+\fBtimeout\fP \fItimeout\fP
+.RS 4
+The \fBtimeout\fP command sets the initial timeout for \fBchronyc\fP requests in
+milliseconds. If no response is received from \fBchronyd\fP, the timeout is doubled
+and the request is resent. The maximum number of retries is configured with the
+\fBretries\fP command.
+.sp
+By default, the timeout is 1000 milliseconds.
+.RE
+.sp
+\fBretries\fP \fIretries\fP
+.RS 4
+The \fBretries\fP command sets the maximum number of retries for \fBchronyc\fP requests
+before giving up. The response timeout is controlled by the
+\fBtimeout\fP command.
+.sp
+The default is 2.
+.RE
+.sp
+\fBkeygen\fP [\fIid\fP [\fItype\fP [\fIbits\fP]]]
+.RS 4
+The \fBkeygen\fP command generates a key that can be added to the
+key file (specified with the \fBkeyfile\fP directive)
+to allow NTP authentication between server and client, or peers. The key is
+generated from the \fI/dev/urandom\fP device and it is printed to standard output.
+.sp
+The command has three optional arguments. The first argument is the key number
+(by default 1), which will be specified with the \fBkey\fP option of the \fBserver\fP
+or \fBpeer\fP 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).
+.sp
+An example is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+keygen 73 SHA1 256
+.fam
+.fi
+.if n .RE
+.sp
+which generates a 256\-bit SHA1 key with number 73. The printed line should
+then be securely transferred and added to the key files on both server and
+client, or peers. A different key should be generated for each client or peer.
+.sp
+An example using the AES128 cipher is:
+.sp
+.if n .RS 4
+.nf
+.fam C
+keygen 151 AES128
+.fam
+.fi
+.if n .RE
+.RE
+.sp
+\fBexit\fP, \fBquit\fP
+.RS 4
+The \fBexit\fP and \fBquit\fP commands exit from \fBchronyc\fP and return the user to the shell.
+.RE
+.sp
+\fBhelp\fP
+.RS 4
+The \fBhelp\fP command displays a summary of the commands and their arguments.
+.RE
+.SH "SEE ALSO"
+.sp
+\fBchrony.conf(5)\fP, \fBchronyd(8)\fP
+.SH "BUGS"
+.sp
+For instructions on how to report bugs, please visit
+.URL "https://chrony\-project.org/" "" "."
+.SH "AUTHORS"
+.sp
+chrony was written by Richard Curnow, Miroslav Lichvar, and others. \ No newline at end of file
diff --git a/doc/chronyd.adoc b/doc/chronyd.adoc
new file mode 100644
index 0000000..887be48
--- /dev/null
+++ b/doc/chronyd.adoc
@@ -0,0 +1,235 @@
+// This file is part of chrony
+//
+// Copyright (C) Richard P. Curnow 1997-2003
+// Copyright (C) Miroslav Lichvar 2009-2017
+//
+// 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.
+
+= chronyd(8)
+:doctype: manpage
+:man manual: System Administration
+:man source: chrony @CHRONY_VERSION@
+
+== NAME
+
+chronyd - chrony daemon
+
+== SYNOPSIS
+
+*chronyd* [_OPTION_]... [_DIRECTIVE_]...
+
+== DESCRIPTION
+
+*chronyd* is a daemon for synchronisation of the system clock. It can
+synchronise the clock with NTP servers, reference clocks (e.g. a GPS receiver),
+and manual input using wristwatch and keyboard via *chronyc*. It can also
+operate as an NTPv4 (RFC 5905) server and peer to provide a time service to
+other computers in the network.
+
+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_.
+
+Informational messages, warnings, and errors will be logged to syslog.
+
+== OPTIONS
+
+*-4*::
+With this option hostnames will be resolved only to IPv4 addresses and only
+IPv4 sockets will be created.
+
+*-6*::
+With this option hostnames will be resolved only to IPv6 addresses and only
+IPv6 sockets will be created.
+
+*-f* _file_::
+This option can be used to specify an alternate location for the configuration
+file. The compiled-in 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. If
+*chronyd* was compiled with enabled support for debugging, this option can be
+used twice to enable debug messages.
+
+*-l* _file_::
+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: -1
+(debug, if compiled with enabled support for debugging), 0 (informational), 1
+(warning), 2 (non-fatal error), and 3 (fatal error). The default value is 0.
+
+*-p*::
+When run in this mode, *chronyd* will print the configuration and exit. It will
+not detach from the terminal. This option can be used to verify the syntax of
+the configuration and get the whole configuration, even if it is split into
+multiple files and read by the *include* or *confdir* directive.
+
+*-q*::
+When run in this mode, *chronyd* will set the system clock once and exit. It
+will not detach from the terminal.
+
+*-Q*::
+This option is similar to the *-q* option, except it only prints the offset
+without making any corrections of the clock and disables server ports to allow
+*chronyd* to be started without root privileges, assuming the configuration
+does not have any directives which would require them (e.g. *refclock*,
+*hwtimestamp*, *rtcfile*, etc).
+
+*-r*::
+This option will try to reload and then delete files containing sample
+histories for each of the servers and reference clocks being used. The
+files are expected to be in the directory specified by the
+<<chrony.conf.adoc#dumpdir,*dumpdir*>>
+directive in the configuration file. This option is useful if you want to stop
+and restart *chronyd* briefly for any reason, e.g. to install a new version.
+However, it should be used only on systems where the kernel can maintain clock
+compensation whilst not under *chronyd*'s control (i.e. Linux, FreeBSD, NetBSD,
+illumos, and macOS 10.13 or later).
+
+*-R*::
+When this option is used, the <<chrony.conf.adoc#initstepslew,*initstepslew*>>
+directive and the <<chrony.conf.adoc#makestep,*makestep*>> directive used with
+a positive limit will be ignored. This option is useful when restarting
+*chronyd* and can be used in conjunction with the *-r* option.
+
+*-s*::
+This option will set the system clock from the computer's real-time clock (RTC)
+or to the last modification time of the file specified by the
+<<chrony.conf.adoc#driftfile,*driftfile*>> directive. Real-time clocks are
+supported only on Linux.
++
+If used in conjunction with the *-r* flag, *chronyd* will attempt to preserve
+the old samples after setting the system clock from the RTC. This can be used
+to allow *chronyd* to perform long term averaging of the gain or loss rate
+across system reboots, and is useful for systems with intermittent access to
+network that are shut down when not in use. For this to work well, it relies
+on *chronyd* having been able to determine accurate statistics for the
+difference between the RTC and system clock last time the computer was on.
++
+If the last modification time of the drift file is later than both the current
+time and the RTC time, the system time will be set to it to restore the time
+when *chronyd* was previously stopped. This is useful on computers that have no
+RTC or the RTC is broken (e.g. it has no battery).
+
+*-t* _timeout_::
+This option sets a timeout (in seconds) after which *chronyd* will exit. If the
+clock is not synchronised, it will exit with a non-zero status. This is useful
+with the *-q* or *-Q* option to shorten the maximum time waiting for
+measurements, or with the *-r* option to limit the time when *chronyd* is
+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. The compiled-in default value is
+_@DEFAULT_USER@_.
++
+On Linux, *chronyd* needs to be compiled with support for the *libcap* library.
+On macOS, FreeBSD, NetBSD, and illumos *chronyd* forks into two processes.
+The child process retains root privileges, but can only perform a very limited
+range of privileged system calls on behalf of the parent.
+
+*-U*::
+This option disables a check for root privileges to allow *chronyd* to be
+started under a non-root user, assuming the process will have all capabilities
+(e.g. provided by the service manager) and access to all files, directories,
+and devices, needed to operate correctly in the specified configuration. Note
+that different capabilities might be needed with different configurations and
+different Linux kernel versions. Starting *chronyd* under a non-root user is
+not recommended when the configuration is not known, or at least limited to
+specific directives.
+
+*-F* _level_::
+This option configures system call filters loaded by *chronyd* processes if it
+was compiled with support for the Linux secure computing (seccomp) facility.
+Three levels are defined: 0, 1, 2. The filters are disabled at level 0. At
+levels 1 and 2, *chronyd* will be killed if it makes a system call which is
+blocked by the filters. The level can be specified as a negative number to
+trigger the SIGSYS signal instead of SIGKILL, which can be useful for
+debugging. The default value is 0.
++
+At level 1, the filters allow only selected system calls that are normally
+expected to be made by *chronyd*. Other system calls are blocked. This level is
+recommended only if it is known to work on the version of the system where
+*chrony* is installed. The filters need to allow also system calls made by
+libraries that *chronyd* is using (e.g. libc), but different versions or
+implementations of the libraries might make different system calls. If the
+filters are missing a system call, *chronyd* could be killed even in normal
+operation.
++
+At level 2, the filters block only a small number of specific system calls
+(e.g. fork and exec). This approach should avoid false positives, but the
+protection of the system against a compromised *chronyd* process is much more
+limited.
++
+The filters cannot be enabled with the *mailonchange* directive.
+
+*-P* _priority_::
+On Linux, FreeBSD, NetBSD, and illumos 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 to disable the thread
+time constraint policy or 1 for the policy to be enabled. Other systems do not
+support this option. The default value is 0.
+
+*-m*::
+This option will lock *chronyd* into RAM so that it will never be paged out.
+This mode is only supported on Linux, FreeBSD, NetBSD, and illumos.
+
+*-x*::
+This option disables the control of the system clock. *chronyd* will not try to
+make any adjustments of the clock. It will assume the clock is free running and
+still track its offset and frequency relative to the estimated true time. This
+option allows *chronyd* to be started without the capability to adjust or set
+the system clock (e.g. in some containers) to operate as an NTP server.
+
+*-v*, *--version*::
+With this option *chronyd* will print version number to the terminal and exit.
+
+*-h*, *--help*::
+With this option *chronyd* will print a help message to the terminal and exit.
+
+== ENVIRONMENT VARIABLES
+
+*LISTEN_FDS*::
+On Linux systems, the systemd service manager may pass file descriptors for
+pre-initialised sockets to *chronyd*. The service manager allocates and binds
+the file descriptors, and passes a copy to each spawned instance of the
+service. This allows for zero-downtime service restarts as the sockets buffer
+client requests until the service is able to handle them. The service manager
+sets the LISTEN_FDS environment variable to the number of passed file
+descriptors.
+
+== FILES
+
+_@SYSCONFDIR@/chrony.conf_
+
+== SEE ALSO
+
+<<chronyc.adoc#,*chronyc(1)*>>, <<chrony.conf.adoc#,*chrony.conf(5)*>>
+
+== BUGS
+
+For instructions on how to report bugs, please visit
+https://chrony-project.org/.
+
+== AUTHORS
+
+chrony was written by Richard Curnow, Miroslav Lichvar, and others.
diff --git a/doc/chronyd.man.in b/doc/chronyd.man.in
new file mode 100644
index 0000000..96e87a0
--- /dev/null
+++ b/doc/chronyd.man.in
@@ -0,0 +1,278 @@
+'\" t
+.\" Title: chronyd
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2023-12-05
+.\" Manual: System Administration
+.\" Source: chrony @CHRONY_VERSION@
+.\" Language: English
+.\"
+.TH "CHRONYD" "8" "2023-12-05" "chrony @CHRONY_VERSION@" "System Administration"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+chronyd \- chrony daemon
+.SH "SYNOPSIS"
+.sp
+\fBchronyd\fP [\fIOPTION\fP]... [\fIDIRECTIVE\fP]...
+.SH "DESCRIPTION"
+.sp
+\fBchronyd\fP is a daemon for synchronisation of the system clock. It can
+synchronise the clock with NTP servers, reference clocks (e.g. a GPS receiver),
+and manual input using wristwatch and keyboard via \fBchronyc\fP. It can also
+operate as an NTPv4 (RFC 5905) server and peer to provide a time service to
+other computers in the network.
+.sp
+If no configuration directives are specified on the command line, \fBchronyd\fP
+will read them from a configuration file. The compiled\-in default location of
+the file is \fI@SYSCONFDIR@/chrony.conf\fP.
+.sp
+Informational messages, warnings, and errors will be logged to syslog.
+.SH "OPTIONS"
+.sp
+\fB\-4\fP
+.RS 4
+With this option hostnames will be resolved only to IPv4 addresses and only
+IPv4 sockets will be created.
+.RE
+.sp
+\fB\-6\fP
+.RS 4
+With this option hostnames will be resolved only to IPv6 addresses and only
+IPv6 sockets will be created.
+.RE
+.sp
+\fB\-f\fP \fIfile\fP
+.RS 4
+This option can be used to specify an alternate location for the configuration
+file. The compiled\-in default value is \fI@SYSCONFDIR@/chrony.conf\fP.
+.RE
+.sp
+\fB\-n\fP
+.RS 4
+When run in this mode, the program will not detach itself from the terminal.
+.RE
+.sp
+\fB\-d\fP
+.RS 4
+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. If
+\fBchronyd\fP was compiled with enabled support for debugging, this option can be
+used twice to enable debug messages.
+.RE
+.sp
+\fB\-l\fP \fIfile\fP
+.RS 4
+This option enables writing of log messages to a file instead of syslog or the
+terminal.
+.RE
+.sp
+\fB\-L\fP \fIlevel\fP
+.RS 4
+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: \-1
+(debug, if compiled with enabled support for debugging), 0 (informational), 1
+(warning), 2 (non\-fatal error), and 3 (fatal error). The default value is 0.
+.RE
+.sp
+\fB\-p\fP
+.RS 4
+When run in this mode, \fBchronyd\fP will print the configuration and exit. It will
+not detach from the terminal. This option can be used to verify the syntax of
+the configuration and get the whole configuration, even if it is split into
+multiple files and read by the \fBinclude\fP or \fBconfdir\fP directive.
+.RE
+.sp
+\fB\-q\fP
+.RS 4
+When run in this mode, \fBchronyd\fP will set the system clock once and exit. It
+will not detach from the terminal.
+.RE
+.sp
+\fB\-Q\fP
+.RS 4
+This option is similar to the \fB\-q\fP option, except it only prints the offset
+without making any corrections of the clock and disables server ports to allow
+\fBchronyd\fP to be started without root privileges, assuming the configuration
+does not have any directives which would require them (e.g. \fBrefclock\fP,
+\fBhwtimestamp\fP, \fBrtcfile\fP, etc).
+.RE
+.sp
+\fB\-r\fP
+.RS 4
+This option will try to reload and then delete files containing sample
+histories for each of the servers and reference clocks being used. The
+files are expected to be in the directory specified by the
+\fBdumpdir\fP
+directive in the configuration file. This option is useful if you want to stop
+and restart \fBchronyd\fP briefly for any reason, e.g. to install a new version.
+However, it should be used only on systems where the kernel can maintain clock
+compensation whilst not under \fBchronyd\fP\*(Aqs control (i.e. Linux, FreeBSD, NetBSD,
+illumos, and macOS 10.13 or later).
+.RE
+.sp
+\fB\-R\fP
+.RS 4
+When this option is used, the \fBinitstepslew\fP
+directive and the \fBmakestep\fP directive used with
+a positive limit will be ignored. This option is useful when restarting
+\fBchronyd\fP and can be used in conjunction with the \fB\-r\fP option.
+.RE
+.sp
+\fB\-s\fP
+.RS 4
+This option will set the system clock from the computer\(cqs real\-time clock (RTC)
+or to the last modification time of the file specified by the
+\fBdriftfile\fP directive. Real\-time clocks are
+supported only on Linux.
+.sp
+If used in conjunction with the \fB\-r\fP flag, \fBchronyd\fP will attempt to preserve
+the old samples after setting the system clock from the RTC. This can be used
+to allow \fBchronyd\fP to perform long term averaging of the gain or loss rate
+across system reboots, and is useful for systems with intermittent access to
+network that are shut down when not in use. For this to work well, it relies
+on \fBchronyd\fP having been able to determine accurate statistics for the
+difference between the RTC and system clock last time the computer was on.
+.sp
+If the last modification time of the drift file is later than both the current
+time and the RTC time, the system time will be set to it to restore the time
+when \fBchronyd\fP was previously stopped. This is useful on computers that have no
+RTC or the RTC is broken (e.g. it has no battery).
+.RE
+.sp
+\fB\-t\fP \fItimeout\fP
+.RS 4
+This option sets a timeout (in seconds) after which \fBchronyd\fP will exit. If the
+clock is not synchronised, it will exit with a non\-zero status. This is useful
+with the \fB\-q\fP or \fB\-Q\fP option to shorten the maximum time waiting for
+measurements, or with the \fB\-r\fP option to limit the time when \fBchronyd\fP is
+running, but still allow it to adjust the frequency of the system clock.
+.RE
+.sp
+\fB\-u\fP \fIuser\fP
+.RS 4
+This option sets the name of the system user to which \fBchronyd\fP will switch
+after start in order to drop root privileges. It overrides the
+\fBuser\fP directive. The compiled\-in default value is
+\fI@DEFAULT_USER@\fP.
+.sp
+On Linux, \fBchronyd\fP needs to be compiled with support for the \fBlibcap\fP library.
+On macOS, FreeBSD, NetBSD, and illumos \fBchronyd\fP forks into two processes.
+The child process retains root privileges, but can only perform a very limited
+range of privileged system calls on behalf of the parent.
+.RE
+.sp
+\fB\-U\fP
+.RS 4
+This option disables a check for root privileges to allow \fBchronyd\fP to be
+started under a non\-root user, assuming the process will have all capabilities
+(e.g. provided by the service manager) and access to all files, directories,
+and devices, needed to operate correctly in the specified configuration. Note
+that different capabilities might be needed with different configurations and
+different Linux kernel versions. Starting \fBchronyd\fP under a non\-root user is
+not recommended when the configuration is not known, or at least limited to
+specific directives.
+.RE
+.sp
+\fB\-F\fP \fIlevel\fP
+.RS 4
+This option configures system call filters loaded by \fBchronyd\fP processes if it
+was compiled with support for the Linux secure computing (seccomp) facility.
+Three levels are defined: 0, 1, 2. The filters are disabled at level 0. At
+levels 1 and 2, \fBchronyd\fP will be killed if it makes a system call which is
+blocked by the filters. The level can be specified as a negative number to
+trigger the SIGSYS signal instead of SIGKILL, which can be useful for
+debugging. The default value is 0.
+.sp
+At level 1, the filters allow only selected system calls that are normally
+expected to be made by \fBchronyd\fP. Other system calls are blocked. This level is
+recommended only if it is known to work on the version of the system where
+\fBchrony\fP is installed. The filters need to allow also system calls made by
+libraries that \fBchronyd\fP is using (e.g. libc), but different versions or
+implementations of the libraries might make different system calls. If the
+filters are missing a system call, \fBchronyd\fP could be killed even in normal
+operation.
+.sp
+At level 2, the filters block only a small number of specific system calls
+(e.g. fork and exec). This approach should avoid false positives, but the
+protection of the system against a compromised \fBchronyd\fP process is much more
+limited.
+.sp
+The filters cannot be enabled with the \fBmailonchange\fP directive.
+.RE
+.sp
+\fB\-P\fP \fIpriority\fP
+.RS 4
+On Linux, FreeBSD, NetBSD, and illumos 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 to disable the thread
+time constraint policy or 1 for the policy to be enabled. Other systems do not
+support this option. The default value is 0.
+.RE
+.sp
+\fB\-m\fP
+.RS 4
+This option will lock \fBchronyd\fP into RAM so that it will never be paged out.
+This mode is only supported on Linux, FreeBSD, NetBSD, and illumos.
+.RE
+.sp
+\fB\-x\fP
+.RS 4
+This option disables the control of the system clock. \fBchronyd\fP will not try to
+make any adjustments of the clock. It will assume the clock is free running and
+still track its offset and frequency relative to the estimated true time. This
+option allows \fBchronyd\fP to be started without the capability to adjust or set
+the system clock (e.g. in some containers) to operate as an NTP server.
+.RE
+.sp
+\fB\-v\fP, \fB\-\-version\fP
+.RS 4
+With this option \fBchronyd\fP will print version number to the terminal and exit.
+.RE
+.sp
+\fB\-h\fP, \fB\-\-help\fP
+.RS 4
+With this option \fBchronyd\fP will print a help message to the terminal and exit.
+.RE
+.SH "ENVIRONMENT VARIABLES"
+.sp
+\fBLISTEN_FDS\fP
+.RS 4
+On Linux systems, the systemd service manager may pass file descriptors for
+pre\-initialised sockets to \fBchronyd\fP. The service manager allocates and binds
+the file descriptors, and passes a copy to each spawned instance of the
+service. This allows for zero\-downtime service restarts as the sockets buffer
+client requests until the service is able to handle them. The service manager
+sets the LISTEN_FDS environment variable to the number of passed file
+descriptors.
+.RE
+.SH "FILES"
+.sp
+\fI@SYSCONFDIR@/chrony.conf\fP
+.SH "SEE ALSO"
+.sp
+\fBchronyc(1)\fP, \fBchrony.conf(5)\fP
+.SH "BUGS"
+.sp
+For instructions on how to report bugs, please visit
+.URL "https://chrony\-project.org/" "" "."
+.SH "AUTHORS"
+.sp
+chrony was written by Richard Curnow, Miroslav Lichvar, and others. \ No newline at end of file
diff --git a/doc/faq.adoc b/doc/faq.adoc
new file mode 100644
index 0000000..8fd350f
--- /dev/null
+++ b/doc/faq.adoc
@@ -0,0 +1,1172 @@
+// This file is part of chrony
+//
+// Copyright (C) Richard P. Curnow 1997-2003
+// Copyright (C) Luke Valenta 2023
+// Copyright (C) Miroslav Lichvar 2014-2016, 2020-2023
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of version 2 of the GNU General Public License as
+// 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.
+
+= Frequently Asked Questions
+:toc:
+:numbered:
+
+== `chrony` compared to other programs
+
+=== How does `chrony` compare to `ntpd`?
+
+`chrony` and `ntpd` are two different implementations of the Network Time
+Protocol (NTP).
+
+`chrony` is a newer implementation, which was designed to work well in a wider
+range of conditions. It can usually synchronise the system clock faster and
+with better time accuracy. It has many features, but it does not implement some
+of the less useful NTP modes like broadcast client or multicast server/client.
+
+If your computer is connected to the Internet only for few minutes at a time,
+the network connection is often congested, you turn your computer off or
+suspend it frequently, the clock is not very stable (e.g. there are rapid
+changes in the temperature or it is a virtual machine), or you want to use NTP
+on an isolated network with no hardware reference clocks in sight, `chrony`
+will probably work better for you.
+
+For a more detailed comparison of features and performance, see the
+https://chrony-project.org/comparison.html[comparison page] on the `chrony`
+website.
+
+=== Should I prefer `chrony` over `timesyncd` if I do not need to run a server?
+
+Generally, yes.
+
+`systemd-timesyncd` is a very simple NTP client included in the `systemd`
+suite. It lacks almost all features of `chrony` and other advanced client
+implementations listed on the
+https://chrony-project.org/comparison.html[comparison page]. One of its main
+limitations is that it cannot poll multiple servers at the same time and detect
+servers having incorrect time (falsetickers in the NTP terminology). It should
+be used only with trusted reliable servers, ideally in local network.
+
+Using `timesyncd` with `pool.ntp.org` is problematic. The pool is very
+robust as a whole, but the individual servers run by volunteers cannot be
+relied on. Occasionally, servers drift away or make a step to distant past or
+future due to misconfiguration, problematic implementation, and other bugs
+(e.g. in firmware of a GPS receiver). The pool monitoring system detects such
+servers and quickly removes them from the pool DNS, but clients like
+`timesyncd` cannot recover from that. They follow the server as long as it
+claims to be synchronised. They need to be restarted in order to get a new
+address from the pool DNS.
+
+Note that the complexity of NTP and clock synchronisation is on the client
+side. The amount of code in `chrony` specific to NTP server is very small and
+it is disabled by default. If it was removed, it would not significantly reduce
+the amount of memory or storage needed.
+
+== Configuration issues
+
+=== What is the minimum recommended configuration for an NTP client?
+
+First, the client needs to know which NTP servers it should ask for the current
+time. They are specified by the `server` or `pool` directive. The `pool`
+directive is used with names that resolve to multiple addresses of different
+servers. For reliable operation, the client should have at least three servers.
+
+The `iburst` option enables a burst of requests to speed up the initial
+synchronisation.
+
+To stabilise the initial synchronisation on the next start, the estimated drift
+of the system clock is saved to a file specified by the `driftfile` directive.
+
+If the system clock can be far from the true time after boot for any reason,
+`chronyd` should be allowed to correct it quickly by stepping instead of
+slewing, which would take a very long time. The `makestep` directive does
+that.
+
+In order to keep the real-time clock (RTC) close to the true time, so the
+system time is reasonably close to the true time when it is initialised on the
+next boot from the RTC, the `rtcsync` directive enables a mode in which the
+system time is periodically copied to the RTC. It is supported on Linux and
+macOS.
+
+If you wanted to use public NTP servers from the
+https://www.pool.ntp.org/[pool.ntp.org] project, the minimal _chrony.conf_ file
+could be:
+
+----
+pool pool.ntp.org iburst
+driftfile /var/lib/chrony/drift
+makestep 1 3
+rtcsync
+----
+
+=== How do I make an NTP server?
+
+By default, `chronyd` does not operate as an NTP server. You need to add an
+`allow` directive to the _chrony.conf_ file in order for `chronyd` to open the
+server NTP port and respond to client requests.
+
+----
+allow 192.168.1.0/24
+----
+
+An `allow` directive with no specified subnet allows access from all IPv4 and
+IPv6 addresses.
+
+=== Should all computers on a LAN be clients of an external server?
+
+It depends on the requirements. Usually, the best configuration is to make one
+computer the server, with the others as clients of it. Add a `local` directive
+to the server's _chrony.conf_ file. This configuration will be better because
+
+* the load on the external connection is less
+* the load on the external NTP server(s) is less
+* if your external connection goes down, the computers on the LAN
+ will maintain a common time with each other.
+
+=== Must I specify servers by IP address if DNS is not available on `chronyd` start?
+
+No, `chronyd` will keep trying to resolve
+the names specified by the `server`, `pool`, and `peer` directives in an
+increasing interval until it succeeds. The `online` command can be issued from
+`chronyc` to force `chronyd` to try to resolve the names immediately.
+
+=== How can I make `chronyd` more secure?
+
+If you do not need to use `chronyc`, or you want to run `chronyc` only
+under the root or _chrony_ user (which can access `chronyd` through a Unix
+domain socket), you can disable the IPv4 and IPv6 command sockets (by default
+listening on localhost) by adding `cmdport 0` to the configuration file.
+
+You can specify an unprivileged user with the `-u` option, or the `user`
+directive in the _chrony.conf_ file, to which `chronyd` will switch after start
+in order to drop root privileges. The configure script has a `--with-user`
+option, which sets the default user. On Linux, `chronyd` needs to be compiled
+with support for the `libcap` library. On other systems, `chronyd` forks into
+two processes. The child process retains root privileges, but can only perform
+a very limited range of privileged system calls on behalf of the parent.
+
+Also, if `chronyd` is compiled with support for the Linux secure computing
+(seccomp) facility, you can enable a system call filter with the `-F` option.
+It will significantly reduce the kernel attack surface and possibly prevent
+kernel exploits from the `chronyd` process if it is compromised. It is
+recommended to enable the filter only when it is known to work on the version of
+the system where `chrony` is installed as the filter needs to allow also system
+calls made from libraries that `chronyd` is using (e.g. libc) and different
+versions or implementations of the libraries might make different system calls.
+If the filter is missing some system call, `chronyd` could be killed even in
+normal operation.
+
+=== How can I make the system clock more secure?
+
+An NTP client synchronising the system clock to an NTP server is susceptible to
+various attacks, which can break applications and network protocols relying on
+accuracy of the clock (e.g. DNSSEC, Kerberos, TLS, WireGuard).
+
+Generally, a man-in-the-middle (MITM) attacker between the client and server
+can
+
+* make fake responses, or modify real responses from the server, to create an
+ arbitrarily large time and frequency offset, make the server appear more
+ accurate, insert a leap second, etc.
+* delay the requests and/or responses to create a limited time offset and
+ temporarily also a limited frequency offset
+* drop the requests or responses to prevent updates of the clock with new
+ measurements
+* redirect the requests to a different server
+
+The attacks can be combined for a greater effect. The attacker can delay
+packets to create a significant frequency offset first and then drop all
+subsequent packets to let the clock quickly drift away from the true time.
+The attacker might also be able to control the server's clock.
+
+Some attacks cannot be prevented. Monitoring is needed for detection, e.g. the
+reachability register in the `sources` report shows missing packets. The extent
+to which the attacker can control the client's clock depends on its
+configuration.
+
+Enable authentication to prevent `chronyd` from accepting modified, fake, or
+redirected packets. It can be enabled with a symmetric key specified by the
+`key` option, or Network Time Security (NTS) by the `nts` option (supported
+since `chrony` version 4.0). The server needs to support the selected
+authentication mechanism. Symmetric keys have to be configured on both client
+and server, and each client must have its own key (one per server).
+
+The maximum offset that the attacker can insert in an NTP measurement by
+delaying packets can be limited by the `maxdelay` option. The default value is
+3 seconds. The measured delay is reported as the peer delay in the `ntpdata`
+report and `measurements` log. Set the `maxdelay` option to a value larger than
+the maximum value that is normally observed. Note that the delay can increase
+significantly even when not under an attack, e.g. when the network is congested
+or the routing has changed.
+
+The maximum accepted change in time offset between clock updates can be limited
+by the `maxchange` directive. Larger changes in the offset will be ignored or
+cause `chronyd` to exit. Note that the attacker can get around this limit by
+splitting the offset into multiple smaller offsets and/or creating a large
+frequency offset. When this directive is used, `chronyd` will have to be
+restarted after a successful attack. It will not be able to recover on its own.
+It must not be restarted automatically (e.g. by the service manager).
+
+The impact of a large accepted time offset can be reduced by disabling clock
+steps, i.e. by not using the `makestep` and `initstepslew` directives. The
+offset will be slowly corrected by speeding up or slowing down the clock at a
+rate which can be limited by the `maxslewrate` directive. Disabling clock steps
+completely is practical only if the clock cannot gain a larger error on its
+own, e.g. when the computer is shut down or suspended, and the `maxslewrate`
+limit is large enough to correct an expected error in an acceptable time. The
+`rtcfile` directive with the `-s` option can be used to compensate for the RTC
+drift.
+
+A more practical approach is to enable `makestep` for a limited number of clock
+updates (the 2nd argument of the directive) and limit the offset change in all
+updates by the `maxchange` directive. The attacker will be able to make only a
+limited step and only if the attack starts in a short window after booting the
+computer, or when `chronyd` is restarted without the `-R` option.
+
+The frequency offset can be limited by the `maxdrift` directive. The measured
+frequency offset is reported in the drift file, `tracking` report, and
+`tracking` log. Set `maxdrift` to a value larger than the maximum absolute
+value that is normally observed. Note that the frequency of the clock can
+change due to aging of the crystal, differences in calibration of the clock
+source between reboots, migrated virtual machine, etc. A typical computer clock
+has a drift smaller than 100 parts per million (ppm), but much larger drifts
+are possible (e.g. in some virtual machines).
+
+Use only trusted servers, which you expect to be well configured and managed,
+using authentication for their own servers, etc. Use multiple servers, ideally
+in different locations. The attacker will have to deal with a majority of the
+servers in order to pass the source selection and update the clock with a large
+offset. Use the `minsources` directive to increase the required number of
+selectable sources to make the selection more robust.
+
+Do not specify servers as peers. The symmetric mode is less secure than the
+client/server mode. If not authenticated, it is vulnerable to off-path
+denial-of-service attacks, and even when it is authenticated, it is still
+susceptible to replay attacks.
+
+Mixing of authenticated and unauthenticated servers should generally be
+avoided. If mixing is necessary (e.g. for a more accurate and stable
+synchronisation to a closer server which does not support authentication), the
+authenticated servers should be configured as trusted and required to not allow
+the unauthenticated servers to override the authenticated servers in the source
+selection. Since `chrony` version 4.0, the selection options are enabled in
+such a case automatically. This behaviour can be disabled or modified by the
+`authselectmode` directive.
+
+An example of a client configuration limiting the impact of the attacks could
+be
+
+----
+server ntp1.example.net iburst nts maxdelay 0.1
+server ntp2.example.net iburst nts maxdelay 0.2
+server ntp3.example.net iburst nts maxdelay 0.05
+server ntp4.example.net iburst nts maxdelay 0.1
+server ntp5.example.net iburst nts maxdelay 0.1
+minsources 3
+maxchange 100 0 0
+makestep 0.001 1
+maxdrift 100
+maxslewrate 100
+driftfile /var/lib/chrony/drift
+ntsdumpdir /var/lib/chrony
+rtcsync
+----
+
+=== How can I improve the accuracy of the system clock with NTP sources?
+
+Select NTP servers that are well synchronised, stable and close to your
+network. It is better to use more than one server. Three or four is usually
+recommended as the minimum, so `chronyd` can detect servers that serve false
+time and combine measurements from multiple sources.
+
+If you have a network card with hardware timestamping supported on Linux, it
+can be enabled by the `hwtimestamp` directive. It should make local receive and
+transmit timestamps of NTP packets much more stable and accurate.
+
+The `server` directive has some useful options: `minpoll`, `maxpoll`,
+`polltarget`, `maxdelay`, `maxdelayratio`, `maxdelaydevratio`, `xleave`,
+`filter`.
+
+The first three options set the minimum and maximum allowed polling interval,
+and how should be the actual interval adjusted in the specified range. Their
+default values are 6 (64 seconds) for `minpoll`, 10 (1024 seconds) for
+`maxpoll` and 8 (samples) for `polltarget`. The default values should be used
+for general servers on the Internet. With your own NTP servers, or if you have
+permission to poll some servers more frequently, setting these options for
+shorter polling intervals might significantly improve the accuracy of the
+system clock.
+
+The optimal polling interval depends mainly on two factors, stability of the
+network latency and stability of the system clock (which mainly depends on the
+temperature sensitivity of the crystal oscillator and the maximum rate of the
+temperature change).
+
+Generally, if the `sourcestats` command usually reports a small number of
+samples retained for a source (e.g. fewer than 16), a shorter polling interval
+should be considered. If the number of samples is usually at the maximum of 64,
+a longer polling interval might work better.
+
+An example of the directive for an NTP server on the Internet that you are
+allowed to poll frequently could be
+
+----
+server ntp.example.net minpoll 4 maxpoll 6 polltarget 16
+----
+
+An example using shorter polling intervals with a server located in the same
+LAN could be
+
+----
+server ntp.local minpoll 2 maxpoll 4 polltarget 30
+----
+
+The maxdelay options are useful to ignore measurements with an unusually large
+delay (e.g. due to congestion in the network) and improve the stability of the
+synchronisation. The `maxdelaydevratio` option could be added to the example
+with local NTP server
+
+----
+server ntp.local minpoll 2 maxpoll 4 polltarget 30 maxdelaydevratio 2
+----
+
+If your server supports the interleaved mode (e.g. it is running `chronyd`),
+the `xleave` option should be added to the `server` directive to enable the
+server to provide the client with more accurate transmit timestamps (kernel or
+preferably hardware). For example:
+
+----
+server ntp.local minpoll 2 maxpoll 4 xleave
+----
+
+When combined with local hardware timestamping, good network switches, and even
+shorter polling intervals, a sub-microsecond accuracy and stability of a few
+tens of nanoseconds might be possible. For example:
+
+----
+server ntp.local minpoll 0 maxpoll 0 xleave
+hwtimestamp eth0
+----
+
+For best stability, the CPU should be running at a constant frequency (i.e.
+disabled power saving and performance boosting). Energy-Efficient Ethernet
+(EEE) should be disabled in the network. The switches should be configured to
+prioritize NTP packets, especially if the network is expected to be heavily
+loaded. The `dscp` directive can be used to set the Differentiated Services
+Code Point in transmitted NTP packets if needed.
+
+If it is acceptable for NTP clients in the network to send requests at a high
+rate, a sub-second polling interval can be specified. A median filter
+can be enabled in order to update the clock at a reduced rate with more stable
+measurements. For example:
+
+----
+server ntp.local minpoll -6 maxpoll -6 filter 15 xleave
+hwtimestamp eth0 minpoll -6
+----
+
+Since `chrony` version 4.3, the minimum `minpoll` is -7 and a filter using a
+long-term estimate of a delay quantile can be enabled by the `maxdelayquant`
+option to replace the default `maxdelaydevratio` filter, which is sensitive to
+outliers corrupting the minimum delay. For example:
+
+----
+server ntp.local minpoll -7 maxpoll -7 filter 31 maxdelayquant 0.3 xleave
+----
+
+Since version 4.2, `chronyd` supports an NTPv4
+extension field containing an additional timestamp to enable frequency transfer
+and significantly improve stability of synchronisation. It can be enabled by
+the `extfield F323` option. For example:
+
+----
+server ntp.local minpoll 0 maxpoll 0 xleave extfield F323
+----
+
+Since version 4.5, `chronyd` can apply corrections from PTP one-step end-to-end
+transparent clocks (e.g. network switches) to significantly improve accuracy of
+synchronisation in local networks. It requires the PTP transport to be enabled
+by the `ptpport` directive, HW timestamping, and the `extfield F324` option.
+For example:
+
+----
+server ntp.local minpoll -4 maxpoll -4 xleave extfield F323 extfield F324 port 319
+ptpport 319
+hwtimestamp eth0 minpoll -4
+----
+
+=== Does `chronyd` have an ntpdate mode?
+
+Yes. With the `-q` option `chronyd` will set the system clock once and exit.
+With the `-Q` option it will print the measured offset without setting the
+clock. If you do not want to use a configuration file, NTP servers can be
+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 to one (supported since `chrony` version 4.0), and a
+timeout can be specified with the `-t` option. The following command would take
+only up to about one second.
+
+----
+# chronyd -q -t 1 'server pool.ntp.org iburst maxsamples 1'
+----
+
+It is not recommended to run `chronyd` with the `-q` option periodically (e.g.
+from a cron job) as a replacement for the daemon mode, because it performs
+significantly worse (e.g. the clock is stepped and its frequency is not
+corrected). If you must run it this way and you are using a public NTP server,
+make sure `chronyd` does not always start around the first second of a minute,
+e.g. by adding a random sleep before the `chronyd` command. Public servers
+typically receive large bursts of requests around the first second as there is
+a large number of NTP clients started from cron with no delay.
+
+=== Can `chronyd` be configured to control the clock like `ntpd`?
+
+It is not possible to perfectly emulate `ntpd`, but there are some options that
+can configure `chronyd` to behave more like `ntpd` if there is a reason to
+prefer that.
+
+In the following example the `minsamples` directive slows down the response to
+changes in the frequency and offset of the clock. The `maxslewrate` and
+`corrtimeratio` directives reduce the maximum frequency error due to an offset
+correction and the `maxdrift` directive reduces the maximum assumed frequency
+error of the clock. The `makestep` directive enables a step threshold and the
+`maxchange` directive enables a panic threshold. The `maxclockerror` directive
+increases the minimum dispersion rate.
+
+----
+minsamples 32
+maxslewrate 500
+corrtimeratio 100
+maxdrift 500
+makestep 0.128 -1
+maxchange 1000 1 1
+maxclockerror 15
+----
+
+Note that increasing `minsamples` might cause the offsets in the `tracking` and
+`sourcestats` reports/logs to be significantly smaller than the actual offsets
+and be unsuitable for monitoring.
+
+=== Can NTP server be separated from NTP client?
+
+Yes, it is possible to run multiple instances of `chronyd` on a computer at the
+same time. One can operate primarily as an NTP client to synchronise the system
+clock and another as a server for other computers. If they use the same
+filesystem, they need to be configured with different pidfiles, Unix domain
+command sockets, and any other file or directory specified in the configuration
+file. If they run in the same network namespace, they need to use different NTP
+and command ports, or bind the ports to different addresses or interfaces.
+
+The server instance should be started with the `-x` option to prevent it from
+adjusting the system clock and interfering with the client instance. It can be
+configured as a client to synchronise its NTP clock to other servers, or the
+client instance running on the same computer. In the latter case, the `copy`
+option (added in `chrony` version 4.1) can be used to assume the reference ID
+and stratum of the client instance, which enables detection of synchronisation
+loops with its own clients.
+
+On Linux, starting with `chrony` version 4.0, it is possible to run multiple
+server instances sharing a port to better utilise multiple cores of the CPU.
+Note that for rate limiting and client/server interleaved mode to work well
+it is necessary that all packets received from the same address are handled by
+the same server instance.
+
+An example configuration of the client instance could be
+
+----
+pool pool.ntp.org iburst
+allow 127.0.0.1
+port 11123
+driftfile /var/lib/chrony/drift
+makestep 1 3
+rtcsync
+----
+
+and configuration of the first server instance could be
+
+----
+server 127.0.0.1 port 11123 minpoll 0 maxpoll 0 copy
+allow
+cmdport 11323
+bindcmdaddress /var/run/chrony/chronyd-server1.sock
+pidfile /var/run/chronyd-server1.pid
+driftfile /var/lib/chrony/drift-server1
+----
+
+=== How can `chronyd` be configured to minimise downtime during restarts?
+
+The `dumpdir` directive in _chrony.conf_ provides `chronyd` a location to save
+a measurement history of the sources it uses when the service exits. The `-r`
+option then enables `chronyd` to load state from the dump files, reducing the
+synchronisation time after a restart.
+
+Similarly, the `ntsdumpdir` directive provides a location for `chronyd` to save
+NTS cookies received from the server to avoid making a NTS-KE request when
+`chronyd` is started. When operating as an NTS server, `chronyd` also saves
+cookies keys to this directory to allow clients to continue to use the old keys
+after a server restart for a more seamless experience.
+
+On Linux systems,
+https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html[systemd
+socket activation] provides a mechanism to reuse server sockets across
+`chronyd` restarts, so that client requests will be buffered until the service
+is again able to handle the requests. This allows for zero-downtime service
+restarts, simplified dependency logic at boot, and on-demand service spawning
+(for instance, for separated server `chronyd` instances run with the `-x`
+flag).
+
+Socket activation is supported since `chrony` version 4.5.
+The service manager (systemd) creates sockets and
+passes file descriptors to them to the process via the `LISTEN_FDS` environment
+variable. Before opening new sockets, `chronyd` first checks for and attempts
+to reuse matching sockets passed from the service manager. For instance, if an
+IPv4 datagram socket bound on `bindaddress` and `port` is available, it will be
+used by the NTP server to accept incoming IPv4 requests.
+
+An example systemd socket unit is below, where `chronyd` is configured with
+`bindaddress 0.0.0.0`, `bindaddress ::`, `port 123`, and `ntsport 4460`.
+
+----
+[Unit]
+Description=chronyd server sockets
+
+[Socket]
+Service=chronyd.service
+# IPv4 NTP server
+ListenDatagram=0.0.0.0:123
+# IPv6 NTP server
+ListenDatagram=[::]:123
+# IPv4 NTS-KE server
+ListenStream=0.0.0.0:4460
+# IPv6 NTS-KE server
+ListenStream=[::]:4460
+BindIPv6Only=ipv6-only
+
+[Install]
+WantedBy=sockets.target
+----
+
+=== Should be a leap smear enabled on NTP server?
+
+With the `smoothtime` and `leapsecmode` directives it is possible to enable a
+server leap smear in order to hide leap seconds from clients and force them to
+follow a slow server's adjustment instead.
+
+This feature should be used only in local networks and only when necessary,
+e.g. when the clients cannot be configured to handle the leap seconds as
+needed, or their number is so large that configuring them all would be
+impractical. The clients should use only one leap-smearing server, or multiple
+identically configured leap-smearing servers. Note that some clients can get
+leap seconds from other sources (e.g. with the `leapsectz` directive in
+`chrony`) and they will not work correctly with a leap smearing server.
+
+=== How should `chronyd` be configured with `gpsd`?
+
+A GPS or other GNSS receiver can be used as a reference clock with `gpsd`. It
+can work as one or two separate time sources for each connected receiver. The
+first time source is based on timestamping of messages sent by the receiver.
+Typically, it is accurate to milliseconds. The other source is much more
+accurate. It is timestamping a pulse-per-second (PPS) signal, usually connected
+to a serial port (e.g. DCD pin) or GPIO pin.
+
+If the PPS signal is connected to the serial port which is receiving messages
+from the GPS/GNSS receiver, `gpsd` should detect and use it automatically. If
+it is connected to a GPIO pin, or another serial port, the PPS device needs to
+be specified on the command line as an additional data source. On Linux, the
+`ldattach` utility can be used to create a PPS device for a serial device.
+
+The PPS-based time source provided by `gpsd` is available as a `SHM 1`
+refclock, or other odd number if `gpsd` is configured with multiple receivers,
+and also as `SOCK /var/run/chrony.DEV.sock` where `DEV` is the name of the
+serial device (e.g. ttyS0).
+
+The message-based time source is available as a `SHM 0` refclock (or other even
+number) and since `gpsd` version 3.25 also as
+`SOCK /var/run/chrony.clk.DEV.sock` where `DEV` is the name of the serial
+device.
+
+The SOCK refclocks should be preferred over SHM for better security
+(the shared memory segment needs to be created by `chronyd` or `gpsd` with an
+expected owner and permissions before an untrusted application or user has a
+chance to create its own in order to feed `chronyd` with false measurements).
+`gpsd` needs to be started after `chronyd` in order to connect to the socket.
+
+With `chronyd` and `gpsd` both supporting PPS, there are two different
+recommended configurations:
+
+----
+# First option
+refclock SOCK /var/run/chrony.ttyS0.sock refid GPS
+
+# Second option
+refclock PPS /dev/pps0 lock NMEA refid GPS
+refclock SOCK /var/run/chrony.clk.ttyS0.sock offset 0.5 delay 0.1 refid NMEA noselect
+----
+
+They both have some advantages:
+
+* `SOCK` can be more accurate than `PPS` if `gpsd` corrects for the
+ sawtooth error provided by the receiver in serial data
+* `PPS` can be used with higher PPS rates (specified by the `rate` option),
+ but it requires a second refclock or another time source to pair pulses
+ with seconds, and the `SOCK` offset needs to be specified
+ <<using-pps-refclock,correctly>> to compensate for the message delay, while
+ `gpsd` can apply HW-specific information
+
+If the PPS signal is not available, or cannot be used for some reason, the only
+option is the message-based timing
+
+----
+refclock SOCK /var/run/chrony.clk.ttyS0.sock offset 0.5 delay 0.1 refid GPS
+----
+
+or the SHM equivalent if using `gpsd` version before 3.25
+
+----
+refclock SHM 0 offset 0.5 delay 0.1 refid GPS
+----
+
+=== Does `chrony` support PTP?
+
+No, the Precision Time Protocol (PTP) is not supported as a protocol for
+synchronisation of clocks and there are no plans
+to support it. It is a complex protocol, which shares some issues with the
+NTP broadcast mode. One of the main differences between NTP and PTP is that PTP
+was designed to be easily supported in hardware (e.g. network switches and
+routers) in order to make more stable and accurate measurements. PTP relies on
+the hardware support. NTP does not rely on any support in the hardware, but if
+it had the same support as PTP, it could perform equally well.
+
+On Linux, `chrony` supports hardware clocks that some NICs have for PTP. They
+are called PTP hardware clocks (PHC). They can be used as reference clocks
+(specified by the `refclock` directive) and for hardware timestamping of NTP
+packets (enabled by the `hwtimestamp` directive) if the NIC can timestamp other
+packets than PTP, which is usually the case at least for transmitted packets.
+The `ethtool -T` command can be used to verify the timestamping support.
+
+As an experimental feature added in version 4.2, `chrony` can use PTP as a
+transport for NTP messages (NTP over PTP) to enable hardware timestamping on
+hardware which can timestamp PTP packets only. It can be enabled by the
+`ptpport` directive. Since version 4.5, `chrony` can also apply corrections
+provided by PTP one-step end-to-end transparent clocks to reach the accuracy of
+ordinary PTP clocks. The application of PTP corrections can be enabled by the
+`extfield F324` option.
+
+=== How can I avoid using wrong PHC refclock?
+
+If your system has multiple PHC devices, normally named by `udev` as
+_/dev/ptp0_, _/dev/ptp1_, and so on, their order can change randomly across
+reboots depending on the order of initialisation of their drivers. If a PHC
+refclock is specified by this name, `chronyd` could be using a wrong refclock
+after reboot. To prevent that, you can configure `udev` to create a stable
+symlink for `chronyd` with a rule like this (e.g. written to
+_/etc/udev/rules.d/80-phc.rules_):
+
+----
+KERNEL=="ptp[0-9]*", DEVPATH=="/devices/pci0000:00/0000:00:01.2/0000:02:00.0/ptp/*", SYMLINK+="ptp-i350-1"
+----
+
+You can get the full _DEVPATH_ of an existing PHC device with the `udevadm
+info` command. You will need to execute the `udevadm trigger` command, or
+reboot the system, for these changes to take effect.
+
+=== Why are client log records dropped before reaching `clientloglimit`?
+
+The number of dropped client log records reported by the `serverstats` command
+can be increasing before the number of clients reported by the `clients` command
+reaches the maximum value corresponding to the memory limit set by the
+`clientloglimit` directive.
+
+This is due to the design of the data structure keeping the client records. It
+is a hash table which can store only up to 16 colliding addresses per slot. If
+a slot has more collisions and the table already has the maximum size, the
+oldest record will be dropped and replaced by the new client.
+
+Note that the size of the table is always a power of two and it can only grow.
+The limit set by the `clientloglimit` directive takes into account that two
+copies of the table exist when it is being resized. This means the actual
+memory usage reported by `top` and other utilities can be significantly smaller
+than the limit even when the maximum number of records is used.
+
+The absolute maximum number of client records kept at the same time is
+16777216.
+
+=== What happened to the `commandkey` and `generatecommandkey` directives?
+
+They were removed in version 2.2. Authentication is no longer supported in the
+command protocol. Commands that required authentication are now allowed only
+through a Unix domain socket, which is accessible only by the root and _chrony_
+users. If you need to configure `chronyd` remotely or locally without the root
+password, please consider using ssh and/or sudo to run `chronyc` under the root
+or _chrony_ user on the host where `chronyd` is running.
+
+== Computer is not synchronising
+
+This is the most common problem. There are a number of reasons, see the
+following questions.
+
+=== Behind a firewall?
+
+Check the `Reach` value printed by the ``chronyc``'s `sources` command. If it
+is zero, it means `chronyd` did not get any valid responses from the NTP server
+you are trying to use. If there is a firewall between you and the server, the
+requests sent to the UDP port 123 of the server or responses sent back from
+the port might be blocked. Try using a tool like `wireshark` or `tcpdump` to
+see if you are getting any responses from the server.
+
+When `chronyd` is receiving responses from the servers, the output of the
+`sources` command issued few minutes after `chronyd` start might look like
+this:
+
+----
+MS Name/IP address Stratum Poll Reach LastRx Last sample
+===============================================================================
+^* ntp1.example.net 2 6 377 34 +484us[ -157us] +/- 30ms
+^- ntp2.example.net 2 6 377 34 +33ms[ +32ms] +/- 47ms
+^+ ntp3.example.net 3 6 377 35 -1397us[-2033us] +/- 60ms
+----
+
+=== Are NTP servers specified with the `offline` option?
+
+Check that the ``chronyc``'s `online` and `offline` commands are used
+appropriately (e.g. in the system networking scripts). The `activity` command
+prints the number of sources that are currently online and offline. For
+example:
+
+----
+200 OK
+3 sources online
+0 sources offline
+0 sources doing burst (return to online)
+0 sources doing burst (return to offline)
+0 sources with unknown address
+----
+
+=== Is name resolution working correctly?
+
+NTP servers specified by their hostname (instead of an IP address) have to have
+their names resolved before `chronyd` can send any requests to them. If the
+`activity` command prints a non-zero number of sources with unknown address,
+there is an issue with the resolution. Typically, a DNS server is specified in
+_/etc/resolv.conf_. Make sure it is working correctly.
+
+Since `chrony` version 4.0, you can run `chronyc -N sources -a` command to
+print all sources, even those that do not have a known address yet, with their
+names as they were specified in the configuration. This can be useful to verify
+that the names specified in the configuration are used as expected.
+
+=== Is `chronyd` allowed to step the system clock?
+
+By default, `chronyd` adjusts the clock gradually by slowing it down or
+speeding it up. If the clock is too far from the true time, it will take
+a long time to correct the error. The `System time` value printed by the
+``chronyc``'s `tracking` command is the remaining correction that needs to be
+applied to the system clock.
+
+The `makestep` directive can be used to allow `chronyd` to step the clock. For
+example, if _chrony.conf_ had
+
+----
+makestep 1 3
+----
+
+the clock would be stepped in the first three updates if its offset was larger
+than one second. Normally, it is recommended to allow the step only in the first
+few updates, but in some cases (e.g. a computer without an RTC or virtual
+machine which can be suspended and resumed with an incorrect time) it might be
+necessary to allow the step on any clock update. The example above would change
+to
+
+----
+makestep 1 -1
+----
+
+=== Using NTS?
+
+The Network Time Security (NTS) mechanism uses Transport Layer Security (TLS)
+to establish the keys needed for authentication of NTP packets.
+
+Run the `authdata` command to check whether the key establishment was
+successful:
+
+----
+# chronyc -N authdata
+Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+ntp1.example.net NTS 1 15 256 33m 0 0 8 100
+ntp2.example.net NTS 1 15 256 33m 0 0 8 100
+ntp3.example.net NTS 1 15 256 33m 0 0 8 100
+----
+
+The KeyID, Type, and KLen columns should have non-zero values. If they are
+zero, check the system log for error messages from `chronyd`. One possible
+cause of failure is a firewall blocking the client's connection to the server's
+TCP port 4460.
+
+Another possible cause of failure is a certificate that is failing to verify
+because the client's clock is wrong. This is a chicken-and-egg problem with NTS.
+You might need to manually correct the date, or temporarily disable NTS, in
+order to get NTS working. If your computer has an RTC and it is backed up by a
+good battery, this operation should be needed only once, assuming the RTC will
+be set periodically with the `rtcsync` directive, or compensated with the
+`rtcfile` directive and the `-s` option.
+
+If the computer does not have an RTC or battery, you can use the `-s` option
+without `rtcfile` directive to restore time of the last shutdown or reboot from
+the drift file. The clock will start behind the true time, but if the computer
+was not shut down for too long and the server's certificate was not renewed too
+close to its expiration, it should be sufficient for the time checks to
+succeed.
+
+If you run your own server, you can use a self-signed certificate covering
+all dates where the client can start (e.g. years 1970-2100). The certificate
+needs to be installed on the client and specified with the `ntstrustedcerts`
+directive. The server can have multiple names and certificates. To avoid
+trusting a certificate for too long, a new certificate can be added to the
+server periodically (e.g. once per year) and the client can have the server
+name and trusted certificate updated automatically (e.g. using a package
+repository, or a cron script downloading the files directly from the server
+over HTTPS). A client that was shut down for years will still be able to
+synchronise its clock and perform the update as long as the server keeps
+the old certificate.
+
+As a last resort, you can disable the time checks by the `nocerttimecheck`
+directive. This has some important security implications. To reduce the
+security risk, you can use the `nosystemcert` and `ntstrustedcerts` directives
+to disable the system's default trusted certificate authorities and trust only
+a minimal set of selected authorities needed to validate the certificates of
+used NTP servers.
+
+=== Using a Windows NTP server?
+
+A common issue with Windows NTP servers is that they report a very large root
+dispersion (e.g. three seconds or more), which causes `chronyd` to ignore the
+server for being too inaccurate. The `sources` command might show a valid
+measurement, but the server is not selected for synchronisation. You can check
+the root dispersion of the server with the ``chronyc``'s `ntpdata` command.
+
+The `maxdistance` value needs to be increased in _chrony.conf_ to enable
+synchronisation to such a server. For example:
+
+----
+maxdistance 16.0
+----
+
+=== An unreachable source is selected?
+
+When `chronyd` is configured with multiple time sources, it tries to select the
+most accurate and stable sources for synchronisation of the system clock. They
+are marked with the _*_ or _+_ symbol in the report printed by the `sources`
+command.
+
+When the best source (marked with the _*_ symbol) becomes unreachable (e.g. NTP
+server stops responding), `chronyd` will not immediately switch
+to the second best source in an attempt to minimise the error of the clock. It
+will let the clock run free for as long as its estimated error (in terms of
+root distance) based on previous measurements is smaller than the estimated
+error of the second source, and there is still an interval which contains some
+measurements from both sources.
+
+If the first source was significantly better than the second source, it can
+take many hours before the second source is selected, depending on its polling
+interval. You can force a faster reselection by increasing the clock error rate
+(`maxclockerror` directive), shortening the polling interval (`maxpoll`
+option), or reducing the number of samples (`maxsamples` option).
+
+=== Does selected source drop new measurements?
+
+`chronyd` can drop a large number of successive NTP measurements if they are
+not passing some of the NTP tests. The `sources` command can report for a
+selected source the fully-reachable value of 377 in the Reach column and at the
+same time a LastRx value that is much larger than the current polling interval.
+If the source is online, this indicates that a number of measurements was
+dropped. You can use the `ntpdata` command to check the NTP tests for the last
+measurement. Usually, it is the test C which fails.
+
+This can be an issue when there is a long-lasting increase in the measured
+delay, e.g. due to a routing change in the network. Unfortunately, `chronyd`
+does not know for how long it should wait for the delay to come back to the
+original values, or whether it is a permanent increase and it should start from
+scratch.
+
+The test C is an adaptive filter. It can take many hours before it accepts
+a measurement with the larger delay, and even much longer before it drops all
+measurements with smaller delay, which determine an expected delay used by the
+test. You can use the `reset sources` command to drop all measurements
+immediately (available in chrony 4.0 and later). If this issue happens
+frequently, you can effectively disable the test by setting the
+`maxdelaydevratio` option to a very large value (e.g. 1000000), or speed up the
+recovery by increasing the clock error rate with the `maxclockerror` directive.
+
+[[using-pps-refclock]]
+=== Using a PPS reference clock?
+
+A pulse-per-second (PPS) reference clock requires a non-PPS time source to
+determine which second of UTC corresponds to each pulse. If it is another
+reference clock specified with the `lock` option in the `refclock` directive,
+the offset between the two reference clocks must be smaller than 0.4 seconds
+(0.2 seconds with `chrony` versions before 4.1) in
+order for the PPS reference clock to work. With NMEA reference clocks it is
+common to have a larger offset. It needs to be corrected with the `offset`
+option.
+
+One approach to find out a good value of the `offset` option is to configure
+the reference clocks with the `noselect` option and compare them to an NTP
+server. For example, if the `sourcestats` command showed
+
+----
+Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
+==============================================================================
+PPS0 0 0 0 +0.000 2000.000 +0ns 4000ms
+NMEA 58 30 231 -96.494 38.406 +504ms 6080us
+ntp1.example.net 7 3 200 -2.991 16.141 -107us 492us
+----
+
+the offset of the NMEA source would need to be increased by about 0.504
+seconds. It does not have to be very accurate. As long as the offset of the
+NMEA reference clock stays below the limit, the PPS reference clock should be
+able to determine the seconds corresponding to the pulses and allow the samples
+to be used for synchronisation.
+
+== Issues with `chronyc`
+
+=== I keep getting the error `506 Cannot talk to daemon`
+
+When accessing `chronyd` remotely, make sure that the _chrony.conf_ file (on
+the computer where `chronyd` is running) has a `cmdallow` entry for the
+computer you are running `chronyc` on and an appropriate `bindcmdaddress`
+directive. This is not necessary for localhost.
+
+Perhaps `chronyd` is not running. Try using the `ps` command (e.g. on Linux,
+`ps -auxw`) to see if it is running. Or try `netstat -a` and see if the UDP
+port 323 is listening. If `chronyd` is not running, you might have a problem
+with the way you are trying to start it (e.g. at boot time).
+
+Perhaps you have a firewall set up in a way that blocks packets on the UDP
+port 323. You need to amend the firewall configuration in this case.
+
+=== I keep getting the error `501 Not authorised`
+
+This error indicates that `chronyc` sent the command to `chronyd` using a UDP
+socket instead of the Unix domain socket (e.g. _/var/run/chrony/chronyd.sock_),
+which is required for some commands. For security reasons, only the root and
+_chrony_ users are allowed to access the socket.
+
+It is also possible that the socket does not exist. `chronyd` will not create
+the socket if the directory has a wrong owner or permissions. In this case
+there should be an error message from `chronyd` in the system log.
+
+=== What is the reference ID reported by the `tracking` command?
+
+The reference ID is a 32-bit value used in NTP to prevent synchronisation
+loops.
+
+In `chrony` versions before 3.0 it was printed in the
+quad-dotted notation, even if the reference source did not actually have an
+IPv4 address. For IPv4 addresses, the reference ID is equal to the address, but
+for IPv6 addresses it is the first 32 bits of the MD5 sum of the address. For
+reference clocks, the reference ID is the value specified with the `refid`
+option in the `refclock` directive.
+
+Since version 3.0, the reference ID is printed as a hexadecimal number to avoid
+confusion with IPv4 addresses.
+
+If you need to get the IP address of the current reference source, use the `-n`
+option to disable resolving of IP addresses and read the second field (printed
+in parentheses) on the `Reference ID` line.
+
+=== Is the `chronyc` / `chronyd` protocol documented anywhere?
+
+Only by the source code. See _cmdmon.c_ (`chronyd` side) and _client.c_
+(`chronyc` side).
+
+Note that this protocol is not compatible with the mode 6 or mode 7 protocol
+supported by `ntpd`, i.e. the `ntpq` or `ntpdc` utility cannot be used to
+monitor `chronyd`, and `chronyc` cannot be used to monitor `ntpd`.
+
+== Real-time clock issues
+
+=== What is the real-time clock (RTC)?
+
+This is the clock which keeps the time even when your computer is turned off.
+It is used to initialise the system clock on boot. It normally does not drift
+more than few seconds per day.
+
+There are two approaches how `chronyd` can work with it. One is to use the
+`rtcsync` directive, which tells `chronyd` to enable a kernel mode which sets
+the RTC from the system clock every 11 minutes. `chronyd` itself will not touch
+the RTC. If the computer is not turned off for a long time, the RTC should
+still be close to the true time when the system clock will be initialised from
+it on the next boot.
+
+The other option is to use the `rtcfile` directive, which tells `chronyd` to
+monitor the rate at which the RTC gains or loses time. When `chronyd` is
+started with the `-s` option on the next boot, it will set the system time from
+the RTC and also compensate for the drift it has measured previously. The
+`rtcautotrim` directive can be used to keep the RTC close to the true time, but
+it is not strictly necessary if its only purpose is to set the system clock when
+`chronyd` is started on boot. See the documentation for details.
+
+=== Does `hwclock` have to be disabled?
+
+The `hwclock` program is run by default in the boot and/or shutdown
+scripts in some Linux installations. With the kernel RTC synchronisation
+(`rtcsync` directive), the RTC will be set also every 11 minutes as long as the
+system clock is synchronised. If you want to use ``chronyd``'s RTC monitoring
+(`rtcfile` directive), it is important to disable `hwclock` in the shutdown
+procedure. If you do not do that, it will overwrite the RTC with a new value, unknown
+to `chronyd`. At the next reboot, `chronyd` started with the `-s` option will
+compensate this (wrong) time with its estimate of how far the RTC has drifted
+whilst the power was off, giving a meaningless initial system time.
+
+There is no need to remove `hwclock` from the boot process, as long as `chronyd`
+is started after it has run.
+
+=== I just keep getting the `513 RTC driver not running` message
+
+For the real-time clock support to work, you need the following three
+things
+
+* an RTC in your computer
+* a Linux kernel with enabled RTC support
+* an `rtcfile` directive in your _chrony.conf_ file
+
+=== I get `Could not open /dev/rtc, Device or resource busy` in my syslog file
+
+Some other program running on the system might be using the device.
+
+=== When I start `chronyd`, the log says `Could not enable RTC interrupt : Invalid argument` (or it may say `disable`)
+
+Your real-time clock hardware might not support the required ioctl requests:
+
+* `RTC_UIE_ON`
+* `RTC_UIE_OFF`
+
+A possible solution could be to build the Linux kernel with support for software
+emulation instead; try enabling the following configuration option when building
+the Linux kernel:
+
+* `CONFIG_RTC_INTF_DEV_UIE_EMUL`
+
+=== What if my computer does not have an RTC or backup battery?
+
+In this case you can still use the `-s` option to set the system clock to the
+last modification time of the drift file, which should correspond to the system
+time when `chronyd` was previously stopped. The initial system time will be
+increasing across reboots and applications started after `chronyd` will not
+observe backward steps.
+
+== NTP-specific issues
+
+=== Can `chronyd` be driven from broadcast/multicast NTP servers?
+
+No, the broadcast/multicast client mode is not supported and there is currently
+no plan to implement it. While this mode can simplify configuration
+of clients in large networks, it is inherently less accurate and less secure
+(even with authentication) than the ordinary client/server mode.
+
+When configuring a large number of clients in a network, it is recommended to
+use the `pool` directive with a DNS name which resolves to addresses of
+multiple NTP servers. The clients will automatically replace the servers when
+they become unreachable, or otherwise unsuitable for synchronisation, with new
+servers from the pool.
+
+Even with very modest hardware, an NTP server can serve time to hundreds of
+thousands of clients using the ordinary client/server mode.
+
+=== Can `chronyd` transmit broadcast NTP packets?
+
+Yes, the `broadcast` directive can be used to enable the broadcast server mode
+to serve time to clients in the network which support the broadcast client mode
+(it is not supported in `chronyd`). Note that this mode should generally be
+avoided. See the previous question.
+
+=== Can `chronyd` keep the system clock a fixed offset away from real time?
+
+Yes. Starting from version 3.0, an offset can be specified by the `offset`
+option for all time sources in the _chrony.conf_ file.
+
+=== What happens if the network connection is dropped without using ``chronyc``'s `offline` command first?
+
+`chronyd` will keep trying to access the sources that it thinks are online, and
+it will take longer before new measurements are actually made and the clock is
+corrected when the network is connected again. If the sources were set to
+offline, `chronyd` would make new measurements immediately after issuing the
+`online` command.
+
+Unless the network connection lasts only few minutes (less than the maximum
+polling interval), the delay is usually not a problem, and it might be acceptable
+to keep all sources online all the time.
+
+=== Why is an offset measured between two computers synchronised to each another?
+
+When two computers are synchronised to each other using the client/server or
+symmetric NTP mode, there is an expectation that NTP measurements between the
+two computers made on both ends show an average offset close to zero.
+
+With `chronyd` that can be expected only when the interleaved mode is enabled
+by the `xleave` option. Otherwise, `chronyd` will use different transmit
+timestamps (e.g. daemon timestamp vs kernel timestamp) for serving time and
+synchronisation of its own clock, which will cause the other computer to
+measure a significant offset.
+
+== Operation
+
+=== What clocks does `chronyd` use?
+
+There are several different clocks used by `chronyd`:
+
+* *System clock:* software clock maintained by the kernel. It is the main clock
+ used by applications running on the computer. It is synchronised by `chronyd`
+ to its NTP clock, unless started with the *-x* option.
+* *NTP clock:* software clock (virtual) based on the system clock and internal
+ to `chronyd`. It keeps the best estimate of the true time according to the
+ configured time sources, which is served to NTP clients unless time smoothing
+ is enabled by the *smoothtime* directive. The *System time* value in the
+ `tracking` report is the current offset between the system and NTP clock.
+* *Real-time clock (RTC):* hardware clock keeping time even when the
+ computer is turned off. It is used by the kernel to initialise the system
+ clock on boot and also by `chronyd` to compensate for its measured drift if
+ configured with the `rtcfile` directive and started with the `-s` option.
+ The clock can be kept accurate only by stepping enabled by the `rtcsync` or
+ `rtcautotrim` directive.
+* *Reference clock:* hardware clock used as a time source. It is specified by
+ the `refclock` directive.
+* *NIC clock (also known as PTP hardware clock):* hardware clock timestamping
+ packets received and transmitted by a network device specified by the
+ *hwtimestamp* directive. The clock is expected to be running free. It is not
+ synchronised by `chronyd`. Its offset is tracked relative to the NTP clock in
+ order to convert the hardware timestamps.
+
+== Operating systems
+
+=== Does `chrony` support Windows?
+
+No. The `chronyc` program (the command-line client used for configuring
+`chronyd` while it is running) has been successfully built and run under
+Cygwin in the past. `chronyd` is not portable, because part of it is
+very system-dependent. It needs adapting to work with Windows'
+equivalent of the adjtimex() call, and it needs to be made to work as a
+service.
+
+=== Are there any plans to support Windows?
+
+We have no plans to do this. Anyone is welcome to pick this work up and
+contribute it back to the project.
diff --git a/doc/installation.adoc b/doc/installation.adoc
new file mode 100644
index 0000000..b683911
--- /dev/null
+++ b/doc/installation.adoc
@@ -0,0 +1,200 @@
+// This file is part of chrony
+//
+// Copyright (C) Richard P. Curnow 1997-2003
+// Copyright (C) Miroslav Lichvar 2009-2016
+//
+// 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.
+
+= Installation
+
+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.
+
+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:
+
+* pkg-config: detection of development libraries
+* Nettle, GnuTLS, 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
+
+----
+./configure
+----
+
+This is a shell script that automatically determines the system type. There is
+an optional parameter `--prefix`, which indicates the directory tree where the
+software should be installed. For example,
+
+----
+./configure --prefix=/opt/free
+----
+
+will install the `chronyd` daemon into `/opt/free/sbin` and the `chronyc`
+control program into `/opt/free/bin`. The default value for the prefix is
+`/usr/local`.
+
+The `configure` script assumes you want to use `gcc` as your compiler. If you
+want to use a different compiler, you can configure this way:
+
+----
+CC=cc ./configure --prefix=/opt/free
+----
+
+for Bourne-family shells, or
+
+----
+setenv CC cc
+setenv CFLAGS -O
+./configure --prefix=/opt/free
+----
+
+for C-family shells.
+
+If the software cannot (yet) be built on your system, an error message will be
+shown. Otherwise, `Makefile` will be generated.
+
+On Linux, if development files for the libcap library are available, `chronyd`
+will be built with support for dropping root privileges. On other systems no
+extra library is needed. The default user which `chronyd` should run as can be
+specified with the `--with-user` option of the `configure` script.
+
+If development files for the POSIX threads library are available, `chronyd`
+will be built with support for asynchronous resolving of hostnames specified in
+the `server`, `peer`, and `pool` directives. This allows `chronyd` operating as
+a server to respond to client requests when resolving a hostname. If you don't
+want to enable the support, specify the `--disable-asyncdns` flag to
+`configure`.
+
+If development files for the https://www.lysator.liu.se/~nisse/nettle/[Nettle],
+https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS[NSS], or
+https://www.libtom.net/LibTomCrypt/[libtomcrypt] library are available,
+`chronyd` will be built with support for other cryptographic hash functions
+than MD5, which can be used for NTP authentication with a symmetric key. If you
+don't want to enable the support, specify the `--disable-sechash` flag to
+`configure`.
+
+If development files for the editline library are available,
+`chronyc` will be built with line editing support. If you don't want this,
+specify the `--disable-readline` flag to `configure`.
+
+If a `timepps.h` header is available (e.g. from the
+http://linuxpps.org[LinuxPPS project]), `chronyd` will be built with PPS API
+reference clock driver. If the header is installed in a location that isn't
+normally searched by the compiler, you can add it to the searched locations by
+setting the `CPPFLAGS` variable to `-I/path/to/timepps`.
+
+The `--help` option can be specified to `configure` to print all options
+supported by the script.
+
+Now type
+
+----
+make
+----
+
+to build the programs.
+
+If you want to build the manual in HTML, type
+
+----
+make docs
+----
+
+Once the programs have been successfully compiled, they need to be installed in
+their target locations. This step normally needs to be performed by the
+superuser, and requires the following command to be entered.
+
+----
+make install
+----
+
+This will install the binaries and man pages.
+
+To install the HTML version of the manual, enter the command
+
+----
+make install-docs
+----
+
+Now that the software is successfully installed, the next step is to set up a
+configuration file. The default location of the file is _/etc/chrony.conf_.
+Several examples of configuration with comments are included in the examples
+directory. Suppose you want to use public NTP servers from the pool.ntp.org
+project as your time reference. A minimal useful configuration file could be
+
+----
+pool pool.ntp.org iburst
+makestep 1.0 3
+rtcsync
+----
+
+Then, `chronyd` can be run. For security reasons, it's recommended to create an
+unprivileged user for `chronyd` and specify it with the `-u` command-line
+option or the `user` directive in the configuration file, or set the default
+user with the `--with-user` configure option before building.
+
+== Support for system call filtering
+
+`chronyd` can be built with support for the Linux secure computing (seccomp)
+facility. This requires development files for the
+https://github.com/seccomp/libseccomp[libseccomp] library and the
+`--enable-scfilter` option specified to `configure`. The `-F` option of
+`chronyd` will enable a system call filter, which should significantly reduce
+the kernel attack surface and possibly prevent kernel exploits from `chronyd`
+if it is compromised.
+
+== Extra options for package builders
+
+The `configure` and `make` procedures have some extra options that may be
+useful if you are building a distribution package for `chrony`.
+
+The `--mandir=DIR` option to `configure` specifies an installation directory
+for the man pages. This overrides the `man` subdirectory of the argument to the
+`--prefix` option.
+
+----
+./configure --prefix=/usr --mandir=/usr/share/man
+----
+
+to set both options together.
+
+The final option is the `DESTDIR` option to the `make` command. For example,
+you could use the commands
+
+----
+./configure --prefix=/usr --mandir=/usr/share/man
+make all docs
+make install DESTDIR=./tmp
+cd tmp
+tar cvf - . | gzip -9 > chrony.tar.gz
+----
+
+to build a package. When untarred within the root directory, this will install
+the files to the intended final locations.
diff --git a/examples/chrony-wait.service b/examples/chrony-wait.service
new file mode 100644
index 0000000..374f633
--- /dev/null
+++ b/examples/chrony-wait.service
@@ -0,0 +1,46 @@
+[Unit]
+Description=Wait for chrony to synchronize system clock
+Documentation=man:chronyc(1)
+After=chronyd.service
+Requires=chronyd.service
+Before=time-sync.target
+Wants=time-sync.target
+
+[Service]
+Type=oneshot
+# Wait for chronyd to update the clock and the remaining
+# correction to be less than 0.1 seconds
+ExecStart=/usr/bin/chronyc -h 127.0.0.1,::1 waitsync 0 0.1 0.0 1
+# Wait for at most 3 minutes
+TimeoutStartSec=180
+RemainAfterExit=yes
+StandardOutput=null
+
+CapabilityBoundingSet=
+DevicePolicy=closed
+DynamicUser=yes
+IPAddressAllow=localhost
+IPAddressDeny=any
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+PrivateDevices=yes
+PrivateUsers=yes
+ProtectClock=yes
+ProtectControlGroups=yes
+ProtectHome=yes
+ProtectHostname=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectKernelTunables=yes
+ProtectProc=invisible
+ProtectSystem=strict
+RestrictAddressFamilies=AF_INET AF_INET6
+RestrictNamespaces=yes
+RestrictRealtime=yes
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+SystemCallFilter=~@privileged @resources
+UMask=0777
+
+[Install]
+WantedBy=multi-user.target
diff --git a/examples/chrony.conf.example1 b/examples/chrony.conf.example1
new file mode 100644
index 0000000..5e93ea7
--- /dev/null
+++ b/examples/chrony.conf.example1
@@ -0,0 +1,12 @@
+# Use public NTP servers from the pool.ntp.org project.
+pool pool.ntp.org iburst
+
+# Record the rate at which the system clock gains/losses time.
+driftfile /var/lib/chrony/drift
+
+# Allow the system clock to be stepped in the first three updates
+# if its offset is larger than 1 second.
+makestep 1.0 3
+
+# Enable kernel synchronization of the real-time clock (RTC).
+rtcsync
diff --git a/examples/chrony.conf.example2 b/examples/chrony.conf.example2
new file mode 100644
index 0000000..bf2bbdd
--- /dev/null
+++ b/examples/chrony.conf.example2
@@ -0,0 +1,47 @@
+# Use public servers from the pool.ntp.org project.
+# Please consider joining the pool (https://www.pool.ntp.org/join.html).
+pool pool.ntp.org iburst
+
+# Record the rate at which the system clock gains/losses time.
+driftfile /var/lib/chrony/drift
+
+# Allow the system clock to be stepped in the first three updates
+# if its offset is larger than 1 second.
+makestep 1.0 3
+
+# Enable kernel synchronization of the real-time clock (RTC).
+rtcsync
+
+# Enable hardware timestamping on all interfaces that support it.
+#hwtimestamp *
+
+# Increase the minimum number of selectable sources required to adjust
+# the system clock.
+#minsources 2
+
+# Allow NTP client access from local network.
+#allow 192.168.0.0/16
+
+# Serve time even if not synchronized to a time source.
+#local stratum 10
+
+# Require authentication (nts or key option) for all NTP sources.
+#authselectmode require
+
+# Specify file containing keys for NTP authentication.
+#keyfile /etc/chrony.keys
+
+# Save NTS keys and cookies.
+ntsdumpdir /var/lib/chrony
+
+# Insert/delete leap seconds by slewing instead of stepping.
+#leapsecmode slew
+
+# Get TAI-UTC offset and leap seconds from the system tz database.
+#leapsectz right/UTC
+
+# Specify directory for log files.
+logdir /var/log/chrony
+
+# Select which information is logged.
+#log measurements statistics tracking
diff --git a/examples/chrony.conf.example3 b/examples/chrony.conf.example3
new file mode 100644
index 0000000..6d84c01
--- /dev/null
+++ b/examples/chrony.conf.example3
@@ -0,0 +1,334 @@
+#######################################################################
+#
+# This is an example chrony configuration file. You should copy it to
+# /etc/chrony.conf after uncommenting and editing the options that you
+# want to enable. The more obscure options are not included. Refer
+# to the documentation for these.
+#
+#######################################################################
+### COMMENTS
+# Any of the following lines are comments (you have a choice of
+# comment start character):
+# a comment
+% a comment
+! a comment
+; a comment
+#
+# Below, the '!' form is used for lines that you might want to
+# uncomment and edit to make your own chrony.conf file.
+#
+#######################################################################
+#######################################################################
+### SPECIFY YOUR NTP SERVERS
+# Most computers using chrony will send measurement requests to one or
+# more 'NTP servers'. You will probably find that your Internet Service
+# Provider or company have one or more NTP servers that you can specify.
+# Failing that, there are a lot of public NTP servers. There is a list
+# you can access at http://support.ntp.org/bin/view/Servers/WebHome or
+# you can use servers from the pool.ntp.org project.
+
+! server ntp1.example.net iburst
+! server ntp2.example.net iburst
+! server ntp3.example.net iburst
+
+! pool pool.ntp.org iburst
+
+#######################################################################
+### AVOIDING POTENTIALLY BOGUS CHANGES TO YOUR CLOCK
+#
+# To avoid changes being made to your computer's gain/loss compensation
+# when the measurement history is too erratic, you might want to enable
+# one of the following lines. The first seems good with servers on the
+# Internet, the second seems OK for a LAN environment.
+
+! maxupdateskew 100
+! maxupdateskew 5
+
+# If you want to increase the minimum number of selectable sources
+# required to update the system clock in order to make the
+# synchronisation more reliable, uncomment (and edit) the following
+# line.
+
+! minsources 2
+
+# If your computer has a good stable clock (e.g. it is not a virtual
+# machine), you might also want to reduce the maximum assumed drift
+# (frequency error) of the clock (the value is specified in ppm).
+
+! maxdrift 100
+
+# By default, chronyd allows synchronisation to an unauthenticated NTP
+# source (i.e. specified without the nts and key options) if it agrees with
+# a majority of authenticated NTP sources, or if no authenticated source is
+# specified. If you don't want chronyd to ever synchronise to an
+# unauthenticated NTP source, uncomment the first from the following lines.
+# If you don't want to synchronise to an unauthenticated NTP source only
+# when an authenticated source is specified, uncomment the second line.
+# If you want chronyd to ignore authentication in the source selection,
+# uncomment the third line.
+
+! authselectmode require
+! authselectmode prefer
+! authselectmode ignore
+
+#######################################################################
+### FILENAMES ETC
+# Chrony likes to keep information about your computer's clock in files.
+# The 'driftfile' stores the computer's clock gain/loss rate in parts
+# per million. When chronyd starts, the system clock can be tuned
+# immediately so that it doesn't gain or lose any more time. You
+# generally want this, so it is uncommented.
+
+driftfile /var/lib/chrony/drift
+
+# If you want to enable NTP authentication with symmetric keys, you will need
+# to uncomment the following line and edit the file to set up the keys.
+
+! keyfile /etc/chrony.keys
+
+# If you specify an NTP server with the nts option to enable authentication
+# with the Network Time Security (NTS) mechanism, or enable server NTS with
+# the ntsservercert and ntsserverkey directives below, the following line will
+# allow the client/server to save the NTS keys and cookies in order to reduce
+# the number of key establishments (NTS-KE sessions).
+
+ntsdumpdir /var/lib/chrony
+
+# If chronyd is configured to act as an NTP server and you want to enable NTS
+# for its clients, you will need a TLS certificate and private key. Uncomment
+# and edit the following lines to specify the locations of the certificate and
+# key.
+
+! ntsservercert /etc/.../nts-server.crt
+! ntsserverkey /etc/.../nts-server.key
+
+# chronyd can save the measurement history for the servers to files when
+# it exits. This is useful in 2 situations:
+#
+# 1. If you stop chronyd and restart it with the '-r' option (e.g. after
+# an upgrade), the old measurements will still be relevant when chronyd
+# is restarted. This will reduce the time needed to get accurate
+# gain/loss measurements.
+#
+# 2. On Linux, if you use the RTC support and start chronyd with
+# '-r -s' on bootup, measurements from the last boot will still be
+# useful (the real time clock is used to 'flywheel' chronyd between
+# boots).
+#
+# Uncomment the following line to use this.
+
+! dumpdir /var/lib/chrony
+
+# chronyd writes its process ID to a file. If you try to start a second
+# copy of chronyd, it will detect that the process named in the file is
+# still running and bail out. If you want to change the path to the PID
+# file, uncomment this line and edit it. The default path is shown.
+
+! pidfile /var/run/chrony/chronyd.pid
+
+# If the system timezone database is kept up to date and includes the
+# right/UTC timezone, chronyd can use it to determine the current
+# TAI-UTC offset and when will the next leap second occur.
+
+! leapsectz right/UTC
+
+#######################################################################
+### INITIAL CLOCK CORRECTION
+# This option is useful to quickly correct the clock on start if it's
+# off by a large amount. The value '1.0' means that if the error is less
+# than 1 second, it will be gradually removed by speeding up or slowing
+# down your computer's clock until it is correct. If the error is above
+# 1 second, an immediate time jump will be applied to correct it. The
+# value '3' means the step is allowed only in the first three updates of
+# the clock. Some software can get upset if the system clock jumps
+# (especially backwards), so be careful!
+
+! makestep 1.0 3
+
+#######################################################################
+### LEAP SECONDS
+# A leap second is an occasional one-second correction of the UTC
+# time scale. By default, chronyd tells the kernel to insert/delete
+# the leap second, which makes a backward/forward step to correct the
+# clock for it. As with the makestep directive, this jump can upset
+# some applications. If you prefer chronyd to make a gradual
+# correction, causing the clock to be off for a longer time, uncomment
+# the following line.
+
+! leapsecmode slew
+
+#######################################################################
+### LOGGING
+# If you want to log information about the time measurements chronyd has
+# gathered, you might want to enable the following lines. You probably
+# only need this if you really enjoy looking at the logs, you want to
+# produce some graphs of your system's timekeeping performance, or you
+# need help in debugging a problem.
+
+! logdir /var/log/chrony
+! log measurements statistics tracking
+
+# If you have real time clock support enabled (see below), you might want
+# this line instead:
+
+! log measurements statistics tracking rtc
+
+#######################################################################
+### ACTING AS AN NTP SERVER
+# You might want the computer to be an NTP server for other computers.
+#
+# By default, chronyd does not allow any clients to access it. You need
+# to explicitly enable access using 'allow' and 'deny' directives.
+#
+# e.g. to enable client access from the 192.168.*.* class B subnet,
+
+! allow 192.168/16
+
+# .. but disallow the 192.168.100.* subnet of that,
+
+! deny 192.168.100/24
+
+# You can have as many allow and deny directives as you need. The order
+# is unimportant.
+
+# If you want to present your computer's time for others to synchronise
+# with, even if you don't seem to be synchronised to any NTP servers
+# yourself, enable the following line. The value 10 may be varied
+# between 1 and 15. You should avoid small values because you will look
+# like a real NTP server. The value 10 means that you appear to be 10
+# NTP 'hops' away from an authoritative source (atomic clock, GPS
+# receiver, radio clock etc).
+
+! local stratum 10
+
+# Normally, chronyd will keep track of how many times each client
+# machine accesses it. The information can be accessed by the 'clients'
+# command of chronyc. You can disable this facility by uncommenting the
+# following line. This will save a bit of memory if you have many
+# clients and it will also disable support for the interleaved mode.
+
+! noclientlog
+
+# The clientlog size is limited to 512KB by default. If you have many
+# clients, you might want to increase the limit.
+
+! clientloglimit 4194304
+
+# By default, chronyd tries to respond to all valid NTP requests from
+# allowed addresses. If you want to limit the response rate for NTP
+# clients that are sending requests too frequently, uncomment and edit
+# the following line.
+
+! ratelimit interval 3 burst 8
+
+#######################################################################
+### REPORTING BIG CLOCK CHANGES
+# Perhaps you want to know if chronyd suddenly detects any large error
+# in your computer's clock. This might indicate a fault or a problem
+# with the server(s) you are using, for example.
+#
+# The next option causes a message to be written to syslog when chronyd
+# has to correct an error above 0.5 seconds (you can use any amount you
+# like).
+
+! logchange 0.5
+
+# The next option will send email to the named person when chronyd has
+# to correct an error above 0.5 seconds. (If you need to send mail to
+# several people, you need to set up a mailing list or sendmail alias
+# for them and use the address of that.)
+
+! mailonchange wibble@example.net 0.5
+
+#######################################################################
+### COMMAND ACCESS
+# The program chronyc is used to show the current operation of chronyd
+# and to change parts of its configuration whilst it is running.
+
+# By default chronyd binds to the loopback interface. Uncomment the
+# following lines to allow receiving command packets from remote hosts.
+
+! bindcmdaddress 0.0.0.0
+! bindcmdaddress ::
+
+# Normally, chronyd will only allow connections from chronyc on the same
+# machine as itself. This is for security. If you have a subnet
+# 192.168.*.* and you want to be able to use chronyc from any machine on
+# it, you could uncomment the following line. (Edit this to your own
+# situation.)
+
+! cmdallow 192.168/16
+
+# You can add as many 'cmdallow' and 'cmddeny' lines as you like. The
+# syntax and meaning is the same as for 'allow' and 'deny', except that
+# 'cmdallow' and 'cmddeny' control access to the chronyd's command port.
+
+# Rate limiting can be enabled also for command packets. (Note,
+# commands from localhost are never limited.)
+
+! cmdratelimit interval -4 burst 16
+
+#######################################################################
+### HARDWARE TIMESTAMPING
+# On Linux, if the network interface controller and its driver support
+# hardware timestamping, it can significantly improve the accuracy of
+# synchronisation. It can be enabled on specified interfaces only, or it
+# can be enabled on all interfaces that support it.
+
+! hwtimestamp eth0
+! hwtimestamp *
+
+#######################################################################
+### REAL TIME CLOCK
+# chronyd can characterise the system's real-time clock. This is the
+# clock that keeps running when the power is turned off, so that the
+# machine knows the approximate time when it boots again. The error at
+# a particular epoch and gain/loss rate can be written to a file and
+# used later by chronyd when it is started with the '-s' option.
+#
+# You need to have 'enhanced RTC support' compiled into your Linux
+# kernel. (Note, these options apply only to Linux.)
+
+! rtcfile /var/lib/chrony/rtc
+
+# Your RTC can be set to keep Universal Coordinated Time (UTC) or local
+# time. (Local time means UTC +/- the effect of your timezone.) If you
+# use UTC, chronyd will function correctly even if the computer is off
+# at the epoch when you enter or leave summer time (aka daylight saving
+# time). However, if you dual boot your system with Microsoft Windows,
+# that will work better if your RTC maintains local time. You take your
+# pick!
+
+! rtconutc
+
+# By default chronyd assumes that the enhanced RTC device is accessed as
+# /dev/rtc. If it's accessed somewhere else on your system (e.g. you're
+# using devfs), uncomment and edit the following line.
+
+! rtcdevice /dev/misc/rtc
+
+# Alternatively, if not using the -s option, this directive can be used
+# to enable a mode in which the RTC is periodically set to the system
+# time, with no tracking of its drift.
+
+! rtcsync
+
+#######################################################################
+### REAL TIME SCHEDULER
+# This directive tells chronyd to use the real-time FIFO scheduler with the
+# specified priority (which must be between 0 and 100). This should result
+# in reduced latency. You don't need it unless you really have a requirement
+# for extreme clock stability. Works only on Linux. Note that the "-P"
+# command-line switch will override this.
+
+! sched_priority 1
+
+#######################################################################
+### LOCKING CHRONYD INTO RAM
+# This directive tells chronyd to use the mlockall() syscall to lock itself
+# into RAM so that it will never be paged out. This should result in reduced
+# latency. You don't need it unless you really have a requirement
+# for extreme clock stability. Works only on Linux. Note that the "-m"
+# command-line switch will also enable this feature.
+
+! lock_all
diff --git a/examples/chrony.keys.example b/examples/chrony.keys.example
new file mode 100644
index 0000000..05e720c
--- /dev/null
+++ b/examples/chrony.keys.example
@@ -0,0 +1,15 @@
+# This is an example chrony keys file. It enables authentication of NTP
+# packets with symmetric keys when its location is specified by the keyfile
+# directive in chrony.conf(5). It should be readable only by root and the
+# user under which chronyd is running.
+#
+# Don't use the example keys! It's recommended to generate random keys using
+# the chronyc keygen command.
+
+# Examples of valid keys:
+
+#1 MD5 AVeryLongAndRandomPassword
+#2 MD5 HEX:12114855C7931009B4049EF3EFC48A139C3F989F
+#3 SHA1 HEX:B2159C05D6A219673A3B7E896B6DE07F6A440995
+#4 AES128 HEX:2DA837C4B6573748CA692B8C828E4891
+#5 AES256 HEX:2666B8099BFF2D5BA20876121788ED24D2BE59111B8FFB562F0F56AE6EC7246E
diff --git a/examples/chrony.logrotate b/examples/chrony.logrotate
new file mode 100644
index 0000000..2823a1a
--- /dev/null
+++ b/examples/chrony.logrotate
@@ -0,0 +1,8 @@
+/var/log/chrony/*.log {
+ missingok
+ nocreate
+ sharedscripts
+ postrotate
+ /usr/bin/chronyc cyclelogs > /dev/null 2>&1 || true
+ endscript
+}
diff --git a/examples/chrony.nm-dispatcher.dhcp b/examples/chrony.nm-dispatcher.dhcp
new file mode 100644
index 0000000..547ce83
--- /dev/null
+++ b/examples/chrony.nm-dispatcher.dhcp
@@ -0,0 +1,49 @@
+#!/bin/sh
+# This is a NetworkManager dispatcher script for chronyd to update
+# its NTP sources with servers from DHCP options passed by NetworkManager
+# in the DHCP4_NTP_SERVERS and DHCP6_DHCP6_NTP_SERVERS environment variables.
+
+export LC_ALL=C
+
+interface=$1
+action=$2
+
+chronyc=/usr/bin/chronyc
+server_options=iburst
+server_dir=/var/run/chrony-dhcp
+
+dhcp_server_file=$server_dir/$interface.sources
+dhcp_ntp_servers="$DHCP4_NTP_SERVERS $DHCP6_DHCP6_NTP_SERVERS"
+
+add_servers_from_dhcp() {
+ rm -f "$dhcp_server_file"
+ for server in $dhcp_ntp_servers; do
+ # Check for invalid characters (from the DHCPv6 NTP FQDN suboption)
+ len1=$(printf '%s' "$server" | wc -c)
+ len2=$(printf '%s' "$server" | tr -d -c 'A-Za-z0-9:.-' | wc -c)
+ if [ "$len1" -ne "$len2" ] || [ "$len2" -lt 1 ] || [ "$len2" -gt 255 ]; then
+ continue
+ fi
+
+ printf 'server %s %s\n' "$server" "$server_options" >> "$dhcp_server_file"
+ done
+ $chronyc reload sources > /dev/null 2>&1 || :
+}
+
+clear_servers_from_dhcp() {
+ if [ -f "$dhcp_server_file" ]; then
+ rm -f "$dhcp_server_file"
+ $chronyc reload sources > /dev/null 2>&1 || :
+ fi
+}
+
+mkdir -p $server_dir
+
+case "$action" in
+ up|dhcp4-change|dhcp6-change)
+ add_servers_from_dhcp;;
+ down)
+ clear_servers_from_dhcp;;
+esac
+
+exit 0
diff --git a/examples/chrony.nm-dispatcher.onoffline b/examples/chrony.nm-dispatcher.onoffline
new file mode 100644
index 0000000..18f5c3f
--- /dev/null
+++ b/examples/chrony.nm-dispatcher.onoffline
@@ -0,0 +1,29 @@
+#!/bin/sh
+# This is a NetworkManager dispatcher / networkd-dispatcher script for
+# chronyd to set its NTP sources online or offline when a network interface
+# is configured or removed
+
+export LC_ALL=C
+
+chronyc=/usr/bin/chronyc
+
+# For NetworkManager consider only selected events
+if [ $# -ge 2 ]; then
+ case "$2" in
+ up|down|connectivity-change)
+ ;;
+ dhcp4-change|dhcp6-change)
+ # Actions "up" and "connectivity-change" in some cases do not
+ # guarantee that the interface has a route (e.g. a bond).
+ # dhcp(x)-change handles at least cases that use DHCP.
+ ;;
+ *)
+ exit 0;;
+ esac
+fi
+
+# Note: for networkd-dispatcher routable.d ~= on and off.d ~= off
+
+$chronyc onoffline > /dev/null 2>&1
+
+exit 0
diff --git a/examples/chronyd-restricted.service b/examples/chronyd-restricted.service
new file mode 100644
index 0000000..30ba7d9
--- /dev/null
+++ b/examples/chronyd-restricted.service
@@ -0,0 +1,58 @@
+# This is a more restricted version of the chronyd service intended for
+# minimal NTP/NTS client configurations. The daemon is started without root
+# privileges and is allowed to write only to its own runtime, state, and log
+# directories. It cannot bind to privileged ports in order to operate as an
+# NTP server, or provide monitoring access over IPv4/IPv6. It cannot use
+# reference clocks, HW timestamping, RTC tracking, and other features.
+[Unit]
+Description=NTP client (restricted)
+Documentation=man:chronyd(8) man:chrony.conf(5)
+After=chronyd.service ntpdate.service sntp.service ntpd.service
+Conflicts=chronyd.service ntpd.service systemd-timesyncd.service
+ConditionCapability=CAP_SYS_TIME
+
+[Service]
+Type=forking
+PIDFile=/run/chrony/chronyd.pid
+EnvironmentFile=-/etc/sysconfig/chronyd
+ExecStart=/usr/sbin/chronyd -U $OPTIONS
+
+User=chrony
+LogsDirectory=chrony
+LogsDirectoryMode=0750
+RuntimeDirectory=chrony
+RuntimeDirectoryMode=0750
+RuntimeDirectoryPreserve=restart
+StateDirectory=chrony
+StateDirectoryMode=0750
+
+AmbientCapabilities=CAP_SYS_TIME
+CapabilityBoundingSet=CAP_SYS_TIME
+DevicePolicy=closed
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+PrivateDevices=yes
+PrivateTmp=yes
+# This breaks adjtimex()
+#PrivateUsers=yes
+ProtectControlGroups=yes
+ProtectHome=yes
+ProtectHostname=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectKernelTunables=yes
+ProtectProc=invisible
+ProtectSystem=strict
+RemoveIPC=yes
+RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
+RestrictNamespaces=yes
+RestrictRealtime=yes
+RestrictSUIDSGID=yes
+SystemCallArchitectures=native
+SystemCallFilter=~@cpu-emulation @debug @module @mount @obsolete @raw-io
+SystemCallFilter=~@reboot @resources @swap
+UMask=0077
+
+[Install]
+WantedBy=multi-user.target
diff --git a/examples/chronyd.service b/examples/chronyd.service
new file mode 100644
index 0000000..a42eb92
--- /dev/null
+++ b/examples/chronyd.service
@@ -0,0 +1,48 @@
+[Unit]
+Description=NTP client/server
+Documentation=man:chronyd(8) man:chrony.conf(5)
+After=ntpdate.service sntp.service ntpd.service
+Conflicts=ntpd.service systemd-timesyncd.service
+ConditionCapability=CAP_SYS_TIME
+
+[Service]
+Type=forking
+PIDFile=/run/chrony/chronyd.pid
+EnvironmentFile=-/etc/sysconfig/chronyd
+ExecStart=/usr/sbin/chronyd $OPTIONS
+
+CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE
+CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_KILL CAP_LEASE CAP_LINUX_IMMUTABLE
+CapabilityBoundingSet=~CAP_MAC_ADMIN CAP_MAC_OVERRIDE CAP_MKNOD CAP_SYS_ADMIN
+CapabilityBoundingSet=~CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_MODULE CAP_SYS_PACCT
+CapabilityBoundingSet=~CAP_SYS_PTRACE CAP_SYS_RAWIO CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM
+DeviceAllow=char-pps rw
+DeviceAllow=char-ptp rw
+DeviceAllow=char-rtc rw
+DevicePolicy=closed
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+PrivateTmp=yes
+ProtectControlGroups=yes
+ProtectHome=yes
+ProtectHostname=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectKernelTunables=yes
+ProtectProc=invisible
+ProtectSystem=strict
+ReadWritePaths=/run /var/lib/chrony -/var/log
+RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
+RestrictNamespaces=yes
+RestrictSUIDSGID=yes
+SystemCallArchitectures=native
+SystemCallFilter=~@cpu-emulation @debug @module @mount @obsolete @raw-io @reboot @swap
+
+# Adjust restrictions for /usr/sbin/sendmail (mailonchange directive)
+NoNewPrivileges=no
+ReadWritePaths=-/var/spool
+RestrictAddressFamilies=AF_NETLINK
+
+[Install]
+WantedBy=multi-user.target
diff --git a/getdate.c b/getdate.c
new file mode 100644
index 0000000..65d1b99
--- /dev/null
+++ b/getdate.c
@@ -0,0 +1,2601 @@
+/* A Bison parser, made by GNU Bison 3.8.2. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <https://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30802
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.8.2"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* First part of user prologue. */
+#line 1 "getdate.y"
+
+/*
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+**
+** This code is in the public domain and has no copyright.
+*/
+
+#include "config.h"
+
+/* Since the code of getdate.y is not included in the Emacs executable
+ itself, there is no need to #define static in this file. Even if
+ the code were included in the Emacs executable, it probably
+ wouldn't do any harm to #undef it here; this will only cause
+ problems if we try to write to a static variable, which I don't
+ think this code needs to do. */
+#ifdef emacs
+# undef static
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#if HAVE_STDLIB_H
+# include <stdlib.h> /* for `free'; used by Bison 1.27 */
+#endif
+
+#if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII))
+# define IN_CTYPE_DOMAIN(c) 1
+#else
+# define IN_CTYPE_DOMAIN(c) isascii(c)
+#endif
+
+#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
+#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
+#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c))
+#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
+
+/* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char.
+ - It's guaranteed to evaluate its argument exactly once.
+ - It's typically faster.
+ Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
+ only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless
+ it's important to use the locale's definition of `digit' even when the
+ host does not conform to Posix. */
+#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
+
+#if defined (STDC_HEADERS) || defined (USG)
+# include <string.h>
+#endif
+
+#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __attribute__(x)
+#endif
+
+#ifndef ATTRIBUTE_UNUSED
+# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+/* Some old versions of bison generate parsers that use bcopy.
+ That loses on systems that don't provide the function, so we have
+ to redefine it here. */
+#if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
+# define bcopy(from, to, len) memcpy ((to), (from), (len))
+#endif
+
+/* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc),
+ as well as gratuitiously global symbol names, so we can have multiple
+ yacc generated parsers in the same program. Note that these are only
+ the variables produced by yacc. If other parser generators (bison,
+ byacc, etc) produce additional global names that conflict at link time,
+ then those parser generators need to be fixed instead of adding those
+ names to this list. */
+
+#define yymaxdepth gd_maxdepth
+#define yyparse gd_parse
+#define yylex gd_lex
+#define yyerror gd_error
+#define yylval gd_lval
+#define yychar gd_char
+#define yydebug gd_debug
+#define yypact gd_pact
+#define yyr1 gd_r1
+#define yyr2 gd_r2
+#define yydef gd_def
+#define yychk gd_chk
+#define yypgo gd_pgo
+#define yyact gd_act
+#define yyexca gd_exca
+#define yyerrflag gd_errflag
+#define yynerrs gd_nerrs
+#define yyps gd_ps
+#define yypv gd_pv
+#define yys gd_s
+#define yy_yys gd_yys
+#define yystate gd_state
+#define yytmp gd_tmp
+#define yyv gd_v
+#define yy_yyv gd_yyv
+#define yyval gd_val
+#define yylloc gd_lloc
+#define yyreds gd_reds /* With YYDEBUG defined */
+#define yytoks gd_toks /* With YYDEBUG defined */
+#define yylhs gd_yylhs
+#define yylen gd_yylen
+#define yydefred gd_yydefred
+#define yydgoto gd_yydgoto
+#define yysindex gd_yysindex
+#define yyrindex gd_yyrindex
+#define yygindex gd_yygindex
+#define yytable gd_yytable
+#define yycheck gd_yycheck
+
+static int yylex (void);
+static int yyerror (char *s);
+
+#define EPOCH 1970
+#define HOUR(x) ((x) * 60)
+
+#define MAX_BUFF_LEN 128 /* size of buffer to read the date into */
+
+/*
+** An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+ const char *name;
+ int type;
+ int value;
+} TABLE;
+
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+** Global variables. We could get rid of most of these by using a good
+** union as the yacc stack. (This routine was originally written before
+** yacc had the %union construct.) Maybe someday; right now we only use
+** the %union very rarely.
+*/
+static const char *yyInput;
+static int yyDayOrdinal;
+static int yyDayNumber;
+static int yyHaveDate;
+static int yyHaveDay;
+static int yyHaveRel;
+static int yyHaveTime;
+static int yyHaveZone;
+static int yyTimezone;
+static int yyDay;
+static int yyHour;
+static int yyMinutes;
+static int yyMonth;
+static int yySeconds;
+static int yyYear;
+static MERIDIAN yyMeridian;
+static int yyRelDay;
+static int yyRelHour;
+static int yyRelMinutes;
+static int yyRelMonth;
+static int yyRelSeconds;
+static int yyRelYear;
+
+
+#line 244 "getdate.c"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ tAGO = 258, /* tAGO */
+ tDAY = 259, /* tDAY */
+ tDAY_UNIT = 260, /* tDAY_UNIT */
+ tDAYZONE = 261, /* tDAYZONE */
+ tDST = 262, /* tDST */
+ tHOUR_UNIT = 263, /* tHOUR_UNIT */
+ tID = 264, /* tID */
+ tMERIDIAN = 265, /* tMERIDIAN */
+ tMINUTE_UNIT = 266, /* tMINUTE_UNIT */
+ tMONTH = 267, /* tMONTH */
+ tMONTH_UNIT = 268, /* tMONTH_UNIT */
+ tSEC_UNIT = 269, /* tSEC_UNIT */
+ tSNUMBER = 270, /* tSNUMBER */
+ tUNUMBER = 271, /* tUNUMBER */
+ tYEAR_UNIT = 272, /* tYEAR_UNIT */
+ tZONE = 273 /* tZONE */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 177 "getdate.y"
+
+ int Number;
+ enum _MERIDIAN Meridian;
+
+#line 314 "getdate.c"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+extern YYSTYPE yylval;
+
+
+int yyparse (void);
+
+
+
+/* Symbol kind. */
+enum yysymbol_kind_t
+{
+ YYSYMBOL_YYEMPTY = -2,
+ YYSYMBOL_YYEOF = 0, /* "end of file" */
+ YYSYMBOL_YYerror = 1, /* error */
+ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */
+ YYSYMBOL_tAGO = 3, /* tAGO */
+ YYSYMBOL_tDAY = 4, /* tDAY */
+ YYSYMBOL_tDAY_UNIT = 5, /* tDAY_UNIT */
+ YYSYMBOL_tDAYZONE = 6, /* tDAYZONE */
+ YYSYMBOL_tDST = 7, /* tDST */
+ YYSYMBOL_tHOUR_UNIT = 8, /* tHOUR_UNIT */
+ YYSYMBOL_tID = 9, /* tID */
+ YYSYMBOL_tMERIDIAN = 10, /* tMERIDIAN */
+ YYSYMBOL_tMINUTE_UNIT = 11, /* tMINUTE_UNIT */
+ YYSYMBOL_tMONTH = 12, /* tMONTH */
+ YYSYMBOL_tMONTH_UNIT = 13, /* tMONTH_UNIT */
+ YYSYMBOL_tSEC_UNIT = 14, /* tSEC_UNIT */
+ YYSYMBOL_tSNUMBER = 15, /* tSNUMBER */
+ YYSYMBOL_tUNUMBER = 16, /* tUNUMBER */
+ YYSYMBOL_tYEAR_UNIT = 17, /* tYEAR_UNIT */
+ YYSYMBOL_tZONE = 18, /* tZONE */
+ YYSYMBOL_19_ = 19, /* ':' */
+ YYSYMBOL_20_ = 20, /* ',' */
+ YYSYMBOL_21_ = 21, /* '/' */
+ YYSYMBOL_YYACCEPT = 22, /* $accept */
+ YYSYMBOL_spec = 23, /* spec */
+ YYSYMBOL_item = 24, /* item */
+ YYSYMBOL_time = 25, /* time */
+ YYSYMBOL_zone = 26, /* zone */
+ YYSYMBOL_day = 27, /* day */
+ YYSYMBOL_date = 28, /* date */
+ YYSYMBOL_rel = 29, /* rel */
+ YYSYMBOL_relunit = 30, /* relunit */
+ YYSYMBOL_number = 31, /* number */
+ YYSYMBOL_o_merid = 32 /* o_merid */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_int8 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if !defined yyoverflow
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* !defined yyoverflow */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yy_state_t yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 2
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 50
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 22
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 11
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 51
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 61
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 273
+
+
+/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, with out-of-bounds checking. */
+#define YYTRANSLATE(YYX) \
+ (0 <= (YYX) && (YYX) <= YYMAXUTOK \
+ ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \
+ : YYSYMBOL_YYUNDEF)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex. */
+static const yytype_int8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 20, 2, 2, 21, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 19, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18
+};
+
+#if YYDEBUG
+/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_int16 yyrline[] =
+{
+ 0, 193, 193, 194, 197, 200, 203, 206, 209, 212,
+ 215, 221, 227, 236, 242, 254, 257, 261, 266, 270,
+ 274, 280, 284, 302, 308, 314, 318, 323, 327, 334,
+ 342, 345, 348, 351, 354, 357, 360, 363, 366, 369,
+ 372, 375, 378, 381, 384, 387, 390, 393, 396, 401,
+ 435, 438
+};
+#endif
+
+/** Accessing symbol of state STATE. */
+#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State])
+
+#if YYDEBUG || 0
+/* The user-facing name of the symbol whose (internal) number is
+ YYSYMBOL. No bounds checking. */
+static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED;
+
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "\"end of file\"", "error", "\"invalid token\"", "tAGO", "tDAY",
+ "tDAY_UNIT", "tDAYZONE", "tDST", "tHOUR_UNIT", "tID", "tMERIDIAN",
+ "tMINUTE_UNIT", "tMONTH", "tMONTH_UNIT", "tSEC_UNIT", "tSNUMBER",
+ "tUNUMBER", "tYEAR_UNIT", "tZONE", "':'", "','", "'/'", "$accept",
+ "spec", "item", "time", "zone", "day", "date", "rel", "relunit",
+ "number", "o_merid", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#define YYPACT_NINF (-20)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-1)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int8 yypact[] =
+{
+ -20, 0, -20, -19, -20, -20, -20, -20, -13, -20,
+ -20, 30, 15, -20, 14, -20, -20, -20, -20, -20,
+ -20, 19, -20, -20, 4, -20, -20, -20, -20, -20,
+ -20, -20, -20, -20, -20, -20, -6, -20, -20, 16,
+ -20, 17, 23, -20, -20, 24, -20, -20, -20, 27,
+ 28, -20, -20, -20, 29, -20, 32, -8, -20, -20,
+ -20
+};
+
+/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_int8 yydefact[] =
+{
+ 2, 0, 1, 18, 39, 16, 42, 45, 0, 36,
+ 48, 0, 49, 33, 15, 3, 4, 5, 7, 6,
+ 8, 30, 9, 19, 25, 38, 41, 44, 35, 47,
+ 32, 20, 37, 40, 10, 43, 27, 34, 46, 0,
+ 31, 0, 0, 17, 29, 0, 24, 28, 23, 50,
+ 21, 26, 51, 12, 0, 11, 0, 50, 22, 14,
+ 13
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -20, -20, -20, -20, -20, -20, -20, -20, -20, -20,
+ -7
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ 0, 1, 15, 16, 17, 18, 19, 20, 21, 22,
+ 55
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_int8 yytable[] =
+{
+ 2, 23, 52, 24, 3, 4, 5, 59, 6, 46,
+ 47, 7, 8, 9, 10, 11, 12, 13, 14, 31,
+ 32, 43, 44, 33, 45, 34, 35, 36, 37, 38,
+ 39, 48, 40, 49, 41, 25, 42, 52, 26, 50,
+ 51, 27, 53, 28, 29, 57, 54, 30, 58, 56,
+ 60
+};
+
+static const yytype_int8 yycheck[] =
+{
+ 0, 20, 10, 16, 4, 5, 6, 15, 8, 15,
+ 16, 11, 12, 13, 14, 15, 16, 17, 18, 4,
+ 5, 7, 3, 8, 20, 10, 11, 12, 13, 14,
+ 15, 15, 17, 16, 19, 5, 21, 10, 8, 16,
+ 16, 11, 15, 13, 14, 16, 19, 17, 16, 21,
+ 57
+};
+
+/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ state STATE-NUM. */
+static const yytype_int8 yystos[] =
+{
+ 0, 23, 0, 4, 5, 6, 8, 11, 12, 13,
+ 14, 15, 16, 17, 18, 24, 25, 26, 27, 28,
+ 29, 30, 31, 20, 16, 5, 8, 11, 13, 14,
+ 17, 4, 5, 8, 10, 11, 12, 13, 14, 15,
+ 17, 19, 21, 7, 3, 20, 15, 16, 15, 16,
+ 16, 16, 10, 15, 19, 32, 21, 16, 16, 15,
+ 32
+};
+
+/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr1[] =
+{
+ 0, 22, 23, 23, 24, 24, 24, 24, 24, 24,
+ 25, 25, 25, 25, 25, 26, 26, 26, 27, 27,
+ 27, 28, 28, 28, 28, 28, 28, 28, 28, 29,
+ 29, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 31,
+ 32, 32
+};
+
+/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 0, 2, 1, 1, 1, 1, 1, 1,
+ 2, 4, 4, 6, 6, 1, 1, 2, 1, 2,
+ 2, 3, 5, 3, 3, 2, 4, 2, 3, 2,
+ 1, 2, 2, 1, 2, 2, 1, 2, 2, 1,
+ 2, 2, 1, 2, 2, 1, 2, 2, 1, 1,
+ 0, 1
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYNOMEM goto yyexhaustedlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*-----------------------------------.
+| Print this symbol's value on YYO. |
+`-----------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (!yyvaluep)
+ return;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/*---------------------------.
+| Print this symbol on YYO. |
+`---------------------------*/
+
+static void
+yy_symbol_print (FILE *yyo,
+ yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ yy_symbol_value_print (yyo, yykind, yyvaluep);
+ YYFPRINTF (yyo, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)]);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep)
+{
+ YY_USE (yyvaluep);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YY_USE (yykind);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+/* Lookahead token kind. */
+int yychar;
+
+/* The semantic value of the lookahead symbol. */
+YYSTYPE yylval;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ int yyn;
+ /* The return value of yyparse. */
+ int yyresult;
+ /* Lookahead symbol kind. */
+ yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ goto yysetstate;
+
+
+/*------------------------------------------------------------.
+| yynewstate -- push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+
+/*--------------------------------------------------------------------.
+| yysetstate -- set current state (the top of the stack) to yystate. |
+`--------------------------------------------------------------------*/
+yysetstate:
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+ YY_ASSERT (0 <= yystate && yystate < YYNSTATES);
+ YY_IGNORE_USELESS_CAST_BEGIN
+ *yyssp = YY_CAST (yy_state_t, yystate);
+ YY_IGNORE_USELESS_CAST_END
+ YY_STACK_PRINT (yyss, yyssp);
+
+ if (yyss + yystacksize - 1 <= yyssp)
+#if !defined yyoverflow && !defined YYSTACK_RELOCATE
+ YYNOMEM;
+#else
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYPTRDIFF_T yysize = yyssp - yyss + 1;
+
+# if defined yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ yy_state_t *yyss1 = yyss;
+ YYSTYPE *yyvs1 = yyvs;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * YYSIZEOF (*yyssp),
+ &yyvs1, yysize * YYSIZEOF (*yyvsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+# else /* defined YYSTACK_RELOCATE */
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ YYNOMEM;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yy_state_t *yyss1 = yyss;
+ union yyalloc *yyptr =
+ YY_CAST (union yyalloc *,
+ YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize))));
+ if (! yyptr)
+ YYNOMEM;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YY_IGNORE_USELESS_CAST_BEGIN
+ YYDPRINTF ((stderr, "Stack size increased to %ld\n",
+ YY_CAST (long, yystacksize)));
+ YY_IGNORE_USELESS_CAST_END
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */
+
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token\n"));
+ yychar = yylex ();
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = YYEOF;
+ yytoken = YYSYMBOL_YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else if (yychar == YYerror)
+ {
+ /* The scanner already issued an error message, process directly
+ to error recovery. But do not keep the error token as
+ lookahead, it is too special and may lead us to an endless
+ loop in error recovery. */
+ yychar = YYUNDEF;
+ yytoken = YYSYMBOL_YYerror;
+ goto yyerrlab1;
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 4: /* item: time */
+#line 197 "getdate.y"
+ {
+ yyHaveTime++;
+ }
+#line 1364 "getdate.c"
+ break;
+
+ case 5: /* item: zone */
+#line 200 "getdate.y"
+ {
+ yyHaveZone++;
+ }
+#line 1372 "getdate.c"
+ break;
+
+ case 6: /* item: date */
+#line 203 "getdate.y"
+ {
+ yyHaveDate++;
+ }
+#line 1380 "getdate.c"
+ break;
+
+ case 7: /* item: day */
+#line 206 "getdate.y"
+ {
+ yyHaveDay++;
+ }
+#line 1388 "getdate.c"
+ break;
+
+ case 8: /* item: rel */
+#line 209 "getdate.y"
+ {
+ yyHaveRel++;
+ }
+#line 1396 "getdate.c"
+ break;
+
+ case 10: /* time: tUNUMBER tMERIDIAN */
+#line 215 "getdate.y"
+ {
+ yyHour = (yyvsp[-1].Number);
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = (yyvsp[0].Meridian);
+ }
+#line 1407 "getdate.c"
+ break;
+
+ case 11: /* time: tUNUMBER ':' tUNUMBER o_merid */
+#line 221 "getdate.y"
+ {
+ yyHour = (yyvsp[-3].Number);
+ yyMinutes = (yyvsp[-1].Number);
+ yySeconds = 0;
+ yyMeridian = (yyvsp[0].Meridian);
+ }
+#line 1418 "getdate.c"
+ break;
+
+ case 12: /* time: tUNUMBER ':' tUNUMBER tSNUMBER */
+#line 227 "getdate.y"
+ {
+ yyHour = (yyvsp[-3].Number);
+ yyMinutes = (yyvsp[-1].Number);
+ yyMeridian = MER24;
+ yyHaveZone++;
+ yyTimezone = ((yyvsp[0].Number) < 0
+ ? -(yyvsp[0].Number) % 100 + (-(yyvsp[0].Number) / 100) * 60
+ : - ((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60));
+ }
+#line 1432 "getdate.c"
+ break;
+
+ case 13: /* time: tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid */
+#line 236 "getdate.y"
+ {
+ yyHour = (yyvsp[-5].Number);
+ yyMinutes = (yyvsp[-3].Number);
+ yySeconds = (yyvsp[-1].Number);
+ yyMeridian = (yyvsp[0].Meridian);
+ }
+#line 1443 "getdate.c"
+ break;
+
+ case 14: /* time: tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER */
+#line 242 "getdate.y"
+ {
+ yyHour = (yyvsp[-5].Number);
+ yyMinutes = (yyvsp[-3].Number);
+ yySeconds = (yyvsp[-1].Number);
+ yyMeridian = MER24;
+ yyHaveZone++;
+ yyTimezone = ((yyvsp[0].Number) < 0
+ ? -(yyvsp[0].Number) % 100 + (-(yyvsp[0].Number) / 100) * 60
+ : - ((yyvsp[0].Number) % 100 + ((yyvsp[0].Number) / 100) * 60));
+ }
+#line 1458 "getdate.c"
+ break;
+
+ case 15: /* zone: tZONE */
+#line 254 "getdate.y"
+ {
+ yyTimezone = (yyvsp[0].Number);
+ }
+#line 1466 "getdate.c"
+ break;
+
+ case 16: /* zone: tDAYZONE */
+#line 257 "getdate.y"
+ {
+ yyTimezone = (yyvsp[0].Number) - 60;
+ }
+#line 1474 "getdate.c"
+ break;
+
+ case 17: /* zone: tZONE tDST */
+#line 261 "getdate.y"
+ {
+ yyTimezone = (yyvsp[-1].Number) - 60;
+ }
+#line 1482 "getdate.c"
+ break;
+
+ case 18: /* day: tDAY */
+#line 266 "getdate.y"
+ {
+ yyDayOrdinal = 1;
+ yyDayNumber = (yyvsp[0].Number);
+ }
+#line 1491 "getdate.c"
+ break;
+
+ case 19: /* day: tDAY ',' */
+#line 270 "getdate.y"
+ {
+ yyDayOrdinal = 1;
+ yyDayNumber = (yyvsp[-1].Number);
+ }
+#line 1500 "getdate.c"
+ break;
+
+ case 20: /* day: tUNUMBER tDAY */
+#line 274 "getdate.y"
+ {
+ yyDayOrdinal = (yyvsp[-1].Number);
+ yyDayNumber = (yyvsp[0].Number);
+ }
+#line 1509 "getdate.c"
+ break;
+
+ case 21: /* date: tUNUMBER '/' tUNUMBER */
+#line 280 "getdate.y"
+ {
+ yyMonth = (yyvsp[-2].Number);
+ yyDay = (yyvsp[0].Number);
+ }
+#line 1518 "getdate.c"
+ break;
+
+ case 22: /* date: tUNUMBER '/' tUNUMBER '/' tUNUMBER */
+#line 284 "getdate.y"
+ {
+ /* Interpret as YYYY/MM/DD if $1 >= 1000, otherwise as MM/DD/YY.
+ The goal in recognizing YYYY/MM/DD is solely to support legacy
+ machine-generated dates like those in an RCS log listing. If
+ you want portability, use the ISO 8601 format. */
+ if ((yyvsp[-4].Number) >= 1000)
+ {
+ yyYear = (yyvsp[-4].Number);
+ yyMonth = (yyvsp[-2].Number);
+ yyDay = (yyvsp[0].Number);
+ }
+ else
+ {
+ yyMonth = (yyvsp[-4].Number);
+ yyDay = (yyvsp[-2].Number);
+ yyYear = (yyvsp[0].Number);
+ }
+ }
+#line 1541 "getdate.c"
+ break;
+
+ case 23: /* date: tUNUMBER tSNUMBER tSNUMBER */
+#line 302 "getdate.y"
+ {
+ /* ISO 8601 format. yyyy-mm-dd. */
+ yyYear = (yyvsp[-2].Number);
+ yyMonth = -(yyvsp[-1].Number);
+ yyDay = -(yyvsp[0].Number);
+ }
+#line 1552 "getdate.c"
+ break;
+
+ case 24: /* date: tUNUMBER tMONTH tSNUMBER */
+#line 308 "getdate.y"
+ {
+ /* e.g. 17-JUN-1992. */
+ yyDay = (yyvsp[-2].Number);
+ yyMonth = (yyvsp[-1].Number);
+ yyYear = -(yyvsp[0].Number);
+ }
+#line 1563 "getdate.c"
+ break;
+
+ case 25: /* date: tMONTH tUNUMBER */
+#line 314 "getdate.y"
+ {
+ yyMonth = (yyvsp[-1].Number);
+ yyDay = (yyvsp[0].Number);
+ }
+#line 1572 "getdate.c"
+ break;
+
+ case 26: /* date: tMONTH tUNUMBER ',' tUNUMBER */
+#line 318 "getdate.y"
+ {
+ yyMonth = (yyvsp[-3].Number);
+ yyDay = (yyvsp[-2].Number);
+ yyYear = (yyvsp[0].Number);
+ }
+#line 1582 "getdate.c"
+ break;
+
+ case 27: /* date: tUNUMBER tMONTH */
+#line 323 "getdate.y"
+ {
+ yyMonth = (yyvsp[0].Number);
+ yyDay = (yyvsp[-1].Number);
+ }
+#line 1591 "getdate.c"
+ break;
+
+ case 28: /* date: tUNUMBER tMONTH tUNUMBER */
+#line 327 "getdate.y"
+ {
+ yyMonth = (yyvsp[-1].Number);
+ yyDay = (yyvsp[-2].Number);
+ yyYear = (yyvsp[0].Number);
+ }
+#line 1601 "getdate.c"
+ break;
+
+ case 29: /* rel: relunit tAGO */
+#line 334 "getdate.y"
+ {
+ yyRelSeconds = -yyRelSeconds;
+ yyRelMinutes = -yyRelMinutes;
+ yyRelHour = -yyRelHour;
+ yyRelDay = -yyRelDay;
+ yyRelMonth = -yyRelMonth;
+ yyRelYear = -yyRelYear;
+ }
+#line 1614 "getdate.c"
+ break;
+
+ case 31: /* relunit: tUNUMBER tYEAR_UNIT */
+#line 345 "getdate.y"
+ {
+ yyRelYear += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1622 "getdate.c"
+ break;
+
+ case 32: /* relunit: tSNUMBER tYEAR_UNIT */
+#line 348 "getdate.y"
+ {
+ yyRelYear += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1630 "getdate.c"
+ break;
+
+ case 33: /* relunit: tYEAR_UNIT */
+#line 351 "getdate.y"
+ {
+ yyRelYear += (yyvsp[0].Number);
+ }
+#line 1638 "getdate.c"
+ break;
+
+ case 34: /* relunit: tUNUMBER tMONTH_UNIT */
+#line 354 "getdate.y"
+ {
+ yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1646 "getdate.c"
+ break;
+
+ case 35: /* relunit: tSNUMBER tMONTH_UNIT */
+#line 357 "getdate.y"
+ {
+ yyRelMonth += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1654 "getdate.c"
+ break;
+
+ case 36: /* relunit: tMONTH_UNIT */
+#line 360 "getdate.y"
+ {
+ yyRelMonth += (yyvsp[0].Number);
+ }
+#line 1662 "getdate.c"
+ break;
+
+ case 37: /* relunit: tUNUMBER tDAY_UNIT */
+#line 363 "getdate.y"
+ {
+ yyRelDay += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1670 "getdate.c"
+ break;
+
+ case 38: /* relunit: tSNUMBER tDAY_UNIT */
+#line 366 "getdate.y"
+ {
+ yyRelDay += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1678 "getdate.c"
+ break;
+
+ case 39: /* relunit: tDAY_UNIT */
+#line 369 "getdate.y"
+ {
+ yyRelDay += (yyvsp[0].Number);
+ }
+#line 1686 "getdate.c"
+ break;
+
+ case 40: /* relunit: tUNUMBER tHOUR_UNIT */
+#line 372 "getdate.y"
+ {
+ yyRelHour += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1694 "getdate.c"
+ break;
+
+ case 41: /* relunit: tSNUMBER tHOUR_UNIT */
+#line 375 "getdate.y"
+ {
+ yyRelHour += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1702 "getdate.c"
+ break;
+
+ case 42: /* relunit: tHOUR_UNIT */
+#line 378 "getdate.y"
+ {
+ yyRelHour += (yyvsp[0].Number);
+ }
+#line 1710 "getdate.c"
+ break;
+
+ case 43: /* relunit: tUNUMBER tMINUTE_UNIT */
+#line 381 "getdate.y"
+ {
+ yyRelMinutes += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1718 "getdate.c"
+ break;
+
+ case 44: /* relunit: tSNUMBER tMINUTE_UNIT */
+#line 384 "getdate.y"
+ {
+ yyRelMinutes += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1726 "getdate.c"
+ break;
+
+ case 45: /* relunit: tMINUTE_UNIT */
+#line 387 "getdate.y"
+ {
+ yyRelMinutes += (yyvsp[0].Number);
+ }
+#line 1734 "getdate.c"
+ break;
+
+ case 46: /* relunit: tUNUMBER tSEC_UNIT */
+#line 390 "getdate.y"
+ {
+ yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1742 "getdate.c"
+ break;
+
+ case 47: /* relunit: tSNUMBER tSEC_UNIT */
+#line 393 "getdate.y"
+ {
+ yyRelSeconds += (yyvsp[-1].Number) * (yyvsp[0].Number);
+ }
+#line 1750 "getdate.c"
+ break;
+
+ case 48: /* relunit: tSEC_UNIT */
+#line 396 "getdate.y"
+ {
+ yyRelSeconds += (yyvsp[0].Number);
+ }
+#line 1758 "getdate.c"
+ break;
+
+ case 49: /* number: tUNUMBER */
+#line 402 "getdate.y"
+ {
+ if (yyHaveTime && yyHaveDate && !yyHaveRel)
+ yyYear = (yyvsp[0].Number);
+ else
+ {
+ if ((yyvsp[0].Number)>10000)
+ {
+ yyHaveDate++;
+ yyDay= ((yyvsp[0].Number))%100;
+ yyMonth= ((yyvsp[0].Number)/100)%100;
+ yyYear = (yyvsp[0].Number)/10000;
+ }
+ else
+ {
+ yyHaveTime++;
+ if ((yyvsp[0].Number) < 100)
+ {
+ yyHour = (yyvsp[0].Number);
+ yyMinutes = 0;
+ }
+ else
+ {
+ yyHour = (yyvsp[0].Number) / 100;
+ yyMinutes = (yyvsp[0].Number) % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = MER24;
+ }
+ }
+ }
+#line 1793 "getdate.c"
+ break;
+
+ case 50: /* o_merid: %empty */
+#line 435 "getdate.y"
+ {
+ (yyval.Meridian) = MER24;
+ }
+#line 1801 "getdate.c"
+ break;
+
+ case 51: /* o_merid: tMERIDIAN */
+#line 439 "getdate.y"
+ {
+ (yyval.Meridian) = (yyvsp[0].Meridian);
+ }
+#line 1809 "getdate.c"
+ break;
+
+
+#line 1813 "getdate.c"
+
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ yyerror (YY_("syntax error"));
+ }
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and the
+ label yyerrorlab therefore never appears in user code. */
+ if (0)
+ YYERROR;
+ ++yynerrs;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ /* Pop stack until we find a state that shifts the error token. */
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYSYMBOL_YYerror;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturnlab;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturnlab;
+
+
+/*-----------------------------------------------------------.
+| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. |
+`-----------------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturnlab;
+
+
+/*----------------------------------------------------------.
+| yyreturnlab -- parsing is finished, clean up and return. |
+`----------------------------------------------------------*/
+yyreturnlab:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ YY_ACCESSING_SYMBOL (+*yyssp), yyvsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+
+ return yyresult;
+}
+
+#line 444 "getdate.y"
+
+
+/* Include this file down here because bison inserts code above which
+ may define-away `const'. We want the prototype for get_date to have
+ the same signature as the function definition does. */
+#include "getdate.h"
+
+extern struct tm *gmtime (const time_t *timep);
+extern struct tm *localtime (const time_t *timep);
+extern time_t mktime (struct tm *tm);
+
+/* Month and day table. */
+static TABLE const MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "sept", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 1 },
+ { "tuesday", tDAY, 2 },
+ { "tues", tDAY, 2 },
+ { "wednesday", tDAY, 3 },
+ { "wednes", tDAY, 3 },
+ { "thursday", tDAY, 4 },
+ { "thur", tDAY, 4 },
+ { "thurs", tDAY, 4 },
+ { "friday", tDAY, 5 },
+ { "saturday", tDAY, 6 },
+ { NULL, 0, 0 }
+};
+
+/* Time units table. */
+static TABLE const UnitsTable[] = {
+ { "year", tYEAR_UNIT, 1 },
+ { "month", tMONTH_UNIT, 1 },
+ { "fortnight", tDAY_UNIT, 14 },
+ { "week", tDAY_UNIT, 7 },
+ { "day", tDAY_UNIT, 1 },
+ { "hour", tHOUR_UNIT, 1 },
+ { "minute", tMINUTE_UNIT, 1 },
+ { "min", tMINUTE_UNIT, 1 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+ { NULL, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static TABLE const OtherTable[] = {
+ { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
+ { "today", tMINUTE_UNIT, 0 },
+ { "now", tMINUTE_UNIT, 0 },
+ { "last", tUNUMBER, -1 },
+ { "this", tMINUTE_UNIT, 0 },
+ { "next", tUNUMBER, 1 },
+ { "first", tUNUMBER, 1 },
+/* { "second", tUNUMBER, 2 }, */
+ { "third", tUNUMBER, 3 },
+ { "fourth", tUNUMBER, 4 },
+ { "fifth", tUNUMBER, 5 },
+ { "sixth", tUNUMBER, 6 },
+ { "seventh", tUNUMBER, 7 },
+ { "eighth", tUNUMBER, 8 },
+ { "ninth", tUNUMBER, 9 },
+ { "tenth", tUNUMBER, 10 },
+ { "eleventh", tUNUMBER, 11 },
+ { "twelfth", tUNUMBER, 12 },
+ { "ago", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/* The timezone table. */
+static TABLE const TimezoneTable[] = {
+ { "gmt", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "utc", tZONE, HOUR ( 0) },
+ { "wet", tZONE, HOUR ( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "wat", tZONE, HOUR ( 1) }, /* West Africa */
+ { "at", tZONE, HOUR ( 2) }, /* Azores */
+#if 0
+ /* For completeness. BST is also British Summer, and GST is
+ * also Guam Standard. */
+ { "bst", tZONE, HOUR ( 3) }, /* Brazil Standard */
+ { "gst", tZONE, HOUR ( 3) }, /* Greenland Standard */
+#endif
+#if 0
+ { "nft", tZONE, HOUR (3.5) }, /* Newfoundland */
+ { "nst", tZONE, HOUR (3.5) }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR (3.5) }, /* Newfoundland Daylight */
+#endif
+ { "ast", tZONE, HOUR ( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR ( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR ( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR ( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR ( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR ( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR ( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR ( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR ( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR ( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR ( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR ( 9) }, /* Yukon Daylight */
+ { "hst", tZONE, HOUR (10) }, /* Hawaii Standard */
+ { "hdt", tDAYZONE, HOUR (10) }, /* Hawaii Daylight */
+ { "cat", tZONE, HOUR (10) }, /* Central Alaska */
+ { "ahst", tZONE, HOUR (10) }, /* Alaska-Hawaii Standard */
+ { "nt", tZONE, HOUR (11) }, /* Nome */
+ { "idlw", tZONE, HOUR (12) }, /* International Date Line West */
+ { "cet", tZONE, -HOUR (1) }, /* Central European */
+ { "met", tZONE, -HOUR (1) }, /* Middle European */
+ { "mewt", tZONE, -HOUR (1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
+ { "mesz", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR (1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR (1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR (1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR (1) }, /* French Summer */
+ { "eet", tZONE, -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
+ { "bt", tZONE, -HOUR (3) }, /* Baghdad, USSR Zone 2 */
+#if 0
+ { "it", tZONE, -HOUR (3.5) },/* Iran */
+#endif
+ { "zp4", tZONE, -HOUR (4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR (5) }, /* USSR Zone 4 */
+#if 0
+ { "ist", tZONE, -HOUR (5.5) },/* Indian Standard */
+#endif
+ { "zp6", tZONE, -HOUR (6) }, /* USSR Zone 5 */
+#if 0
+ /* For completeness. NST is also Newfoundland Standard, and SST is
+ * also Swedish Summer. */
+ { "nst", tZONE, -HOUR (6.5) },/* North Sumatra */
+ { "sst", tZONE, -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
+#endif /* 0 */
+ { "wast", tZONE, -HOUR (7) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR (7) }, /* West Australian Daylight */
+#if 0
+ { "jt", tZONE, -HOUR (7.5) },/* Java (3pm in Cronusland!) */
+#endif
+ { "cct", tZONE, -HOUR (8) }, /* China Coast, USSR Zone 7 */
+ { "jst", tZONE, -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
+#if 0
+ { "cast", tZONE, -HOUR (9.5) },/* Central Australian Standard */
+ { "cadt", tDAYZONE, -HOUR (9.5) },/* Central Australian Daylight */
+#endif
+ { "east", tZONE, -HOUR (10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR (10) }, /* Eastern Australian Daylight */
+ { "gst", tZONE, -HOUR (10) }, /* Guam Standard, USSR Zone 9 */
+ { "nzt", tZONE, -HOUR (12) }, /* New Zealand */
+ { "nzst", tZONE, -HOUR (12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR (12) }, /* New Zealand Daylight */
+ { "idle", tZONE, -HOUR (12) }, /* International Date Line East */
+ { NULL, 0, 0 }
+};
+
+/* Military timezone table. */
+static TABLE const MilitaryTable[] = {
+ { "a", tZONE, HOUR ( 1) },
+ { "b", tZONE, HOUR ( 2) },
+ { "c", tZONE, HOUR ( 3) },
+ { "d", tZONE, HOUR ( 4) },
+ { "e", tZONE, HOUR ( 5) },
+ { "f", tZONE, HOUR ( 6) },
+ { "g", tZONE, HOUR ( 7) },
+ { "h", tZONE, HOUR ( 8) },
+ { "i", tZONE, HOUR ( 9) },
+ { "k", tZONE, HOUR ( 10) },
+ { "l", tZONE, HOUR ( 11) },
+ { "m", tZONE, HOUR ( 12) },
+ { "n", tZONE, HOUR (- 1) },
+ { "o", tZONE, HOUR (- 2) },
+ { "p", tZONE, HOUR (- 3) },
+ { "q", tZONE, HOUR (- 4) },
+ { "r", tZONE, HOUR (- 5) },
+ { "s", tZONE, HOUR (- 6) },
+ { "t", tZONE, HOUR (- 7) },
+ { "u", tZONE, HOUR (- 8) },
+ { "v", tZONE, HOUR (- 9) },
+ { "w", tZONE, HOUR (-10) },
+ { "x", tZONE, HOUR (-11) },
+ { "y", tZONE, HOUR (-12) },
+ { "z", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+
+
+
+/* ARGSUSED */
+static int
+yyerror (char *s ATTRIBUTE_UNUSED)
+{
+ return 0;
+}
+
+static int
+ToHour (int Hours, MERIDIAN Meridian)
+{
+ switch (Meridian)
+ {
+ case MER24:
+ if (Hours < 0 || Hours > 23)
+ return -1;
+ return Hours;
+ case MERam:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ return Hours;
+ case MERpm:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ return Hours + 12;
+ default:
+ abort ();
+ }
+ /* NOTREACHED */
+}
+
+static int
+ToYear (int Year)
+{
+ if (Year < 0)
+ Year = -Year;
+
+ /* XPG4 suggests that years 00-68 map to 2000-2068, and
+ years 69-99 map to 1969-1999. */
+ if (Year < 69)
+ Year += 2000;
+ else if (Year < 100)
+ Year += 1900;
+
+ return Year;
+}
+
+static int
+LookupWord (char *buff)
+{
+ register char *p;
+ register char *q;
+ register const TABLE *tp;
+ int i;
+ int abbrev;
+
+ /* Make it lowercase. */
+ for (p = buff; *p; p++)
+ if (ISUPPER ((unsigned char) *p))
+ *p = tolower ((unsigned char) *p);
+
+ if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0)
+ {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0)
+ {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+
+ /* See if we have an abbreviation for a month. */
+ if (strlen (buff) == 3)
+ abbrev = 1;
+ else if (strlen (buff) == 4 && buff[3] == '.')
+ {
+ abbrev = 1;
+ buff[3] = '\0';
+ }
+ else
+ abbrev = 0;
+
+ for (tp = MonthDayTable; tp->name; tp++)
+ {
+ if (abbrev)
+ {
+ if (strncmp (buff, tp->name, 3) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ else if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ if (strcmp (buff, "dst") == 0)
+ return tDST;
+
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Strip off any plural and try the units table again. */
+ i = strlen (buff) - 1;
+ if (buff[i] == 's')
+ {
+ buff[i] = '\0';
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ buff[i] = 's'; /* Put back for "this" in OtherTable. */
+ }
+
+ for (tp = OtherTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Military timezones. */
+ if (buff[1] == '\0' && ISALPHA ((unsigned char) *buff))
+ {
+ for (tp = MilitaryTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Drop out any periods and try the timezone table again. */
+ for (i = 0, p = q = buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ else
+ i++;
+ *p = '\0';
+ if (i)
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ return tID;
+}
+
+static int
+yylex ()
+{
+ register unsigned char c;
+ register char *p;
+ char buff[20];
+ int Count;
+ int sign;
+
+ for (;;)
+ {
+ while (ISSPACE ((unsigned char) *yyInput))
+ yyInput++;
+
+ if (ISDIGIT (c = *yyInput) || c == '-' || c == '+')
+ {
+ if (c == '-' || c == '+')
+ {
+ sign = c == '-' ? -1 : 1;
+ if (!ISDIGIT (*++yyInput))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ for (yylval.Number = 0; ISDIGIT (c = *yyInput++);)
+ yylval.Number = 10 * yylval.Number + c - '0';
+ yyInput--;
+ if (sign < 0)
+ yylval.Number = -yylval.Number;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ if (ISALPHA (c))
+ {
+ for (p = buff; (c = *yyInput++, ISALPHA (c)) || c == '.';)
+ if (p < &buff[sizeof buff - 1])
+ *p++ = c;
+ *p = '\0';
+ yyInput--;
+ return LookupWord (buff);
+ }
+ if (c != '(')
+ return *yyInput++;
+ Count = 0;
+ do
+ {
+ c = *yyInput++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ Count++;
+ else if (c == ')')
+ Count--;
+ }
+ while (Count > 0);
+ }
+}
+
+#define TM_YEAR_ORIGIN 1900
+
+/* Yield A - B, measured in seconds. */
+static long
+difftm (struct tm *a, struct tm *b)
+{
+ int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
+ int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
+ long days = (
+ /* difference in day of year */
+ a->tm_yday - b->tm_yday
+ /* + intervening leap days */
+ + ((ay >> 2) - (by >> 2))
+ - (ay / 100 - by / 100)
+ + ((ay / 100 >> 2) - (by / 100 >> 2))
+ /* + difference in years * 365 */
+ + (long) (ay - by) * 365
+ );
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+
+time_t
+get_date (const char *p, const time_t *now)
+{
+ struct tm tm, tm0, *tmp;
+ time_t Start;
+
+ yyInput = p;
+ Start = now ? *now : time ((time_t *) NULL);
+ tmp = localtime (&Start);
+ if (!tmp)
+ return -1;
+ yyYear = tmp->tm_year + TM_YEAR_ORIGIN;
+ yyMonth = tmp->tm_mon + 1;
+ yyDay = tmp->tm_mday;
+ yyHour = tmp->tm_hour;
+ yyMinutes = tmp->tm_min;
+ yySeconds = tmp->tm_sec;
+ tm.tm_isdst = tmp->tm_isdst;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMinutes = 0;
+ yyRelHour = 0;
+ yyRelDay = 0;
+ yyRelMonth = 0;
+ yyRelYear = 0;
+ yyHaveDate = 0;
+ yyHaveDay = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+ yyHaveZone = 0;
+
+ if (yyparse ()
+ || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
+ return -1;
+
+ tm.tm_year = ToYear (yyYear) - TM_YEAR_ORIGIN + yyRelYear;
+ tm.tm_mon = yyMonth - 1 + yyRelMonth;
+ tm.tm_mday = yyDay + yyRelDay;
+ if (yyHaveTime || (yyHaveRel && !yyHaveDate && !yyHaveDay))
+ {
+ tm.tm_hour = ToHour (yyHour, yyMeridian);
+ if (tm.tm_hour < 0)
+ return -1;
+ tm.tm_min = yyMinutes;
+ tm.tm_sec = yySeconds;
+ }
+ else
+ {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ }
+ tm.tm_hour += yyRelHour;
+ tm.tm_min += yyRelMinutes;
+ tm.tm_sec += yyRelSeconds;
+
+ /* Let mktime deduce tm_isdst if we have an absolute timestamp,
+ or if the relative timestamp mentions days, months, or years. */
+ if (yyHaveDate | yyHaveDay | yyHaveTime | yyRelDay | yyRelMonth | yyRelYear)
+ tm.tm_isdst = -1;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (Start == (time_t) -1)
+ {
+
+ /* Guard against falsely reporting errors near the time_t boundaries
+ when parsing times in other time zones. For example, if the min
+ time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
+ of UTC, then the min localtime value is 1970-01-01 08:00:00; if
+ we apply mktime to 1970-01-01 00:00:00 we will get an error, so
+ we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
+ zone by 24 hours to compensate. This algorithm assumes that
+ there is no DST transition within a day of the time_t boundaries. */
+ if (yyHaveZone)
+ {
+ tm = tm0;
+ if (tm.tm_year <= EPOCH - TM_YEAR_ORIGIN)
+ {
+ tm.tm_mday++;
+ yyTimezone -= 24 * 60;
+ }
+ else
+ {
+ tm.tm_mday--;
+ yyTimezone += 24 * 60;
+ }
+ Start = mktime (&tm);
+ }
+
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (yyHaveDay && !yyHaveDate)
+ {
+ tm.tm_mday += ((yyDayNumber - tm.tm_wday + 7) % 7
+ + 7 * (yyDayOrdinal - (0 < yyDayOrdinal)));
+ Start = mktime (&tm);
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (yyHaveZone)
+ {
+ long delta;
+ struct tm *gmt = gmtime (&Start);
+ if (!gmt)
+ return -1;
+ delta = yyTimezone * 60L + difftm (&tm, gmt);
+ if ((Start + delta < Start) != (delta < 0))
+ return -1; /* time_t overflow */
+ Start += delta;
+ }
+
+ return Start;
+}
+
+#if defined (TEST)
+
+/* ARGSUSED */
+int
+main (ac, av)
+ int ac;
+ char *av[];
+{
+ char buff[MAX_BUFF_LEN + 1];
+ time_t d;
+
+ (void) printf ("Enter date, or blank line to exit.\n\t> ");
+ (void) fflush (stdout);
+
+ buff[MAX_BUFF_LEN] = 0;
+ while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
+ {
+ d = get_date (buff, (time_t *) NULL);
+ if (d == -1)
+ (void) printf ("Bad format - couldn't convert.\n");
+ else
+ (void) printf ("%s", ctime (&d));
+ (void) printf ("\t> ");
+ (void) fflush (stdout);
+ }
+ exit (0);
+ /* NOTREACHED */
+}
+#endif /* defined (TEST) */
diff --git a/getdate.h b/getdate.h
new file mode 100644
index 0000000..a190d2d
--- /dev/null
+++ b/getdate.h
@@ -0,0 +1,28 @@
+/* Copyright (C) 1995 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ 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. */
+
+/* Modified from the original to add stdlib.h and string.h */
+
+#ifndef GOT_GETDATE_H
+#define GOT_GETDATE_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+time_t get_date (const char *p, const time_t *now);
+
+#endif /* GOT_GETDATE_H */
diff --git a/getdate.y b/getdate.y
new file mode 100644
index 0000000..47d368d
--- /dev/null
+++ b/getdate.y
@@ -0,0 +1,1039 @@
+%{
+/*
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+**
+** This code is in the public domain and has no copyright.
+*/
+
+#include "config.h"
+
+/* Since the code of getdate.y is not included in the Emacs executable
+ itself, there is no need to #define static in this file. Even if
+ the code were included in the Emacs executable, it probably
+ wouldn't do any harm to #undef it here; this will only cause
+ problems if we try to write to a static variable, which I don't
+ think this code needs to do. */
+#ifdef emacs
+# undef static
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#if HAVE_STDLIB_H
+# include <stdlib.h> /* for `free'; used by Bison 1.27 */
+#endif
+
+#if defined (STDC_HEADERS) || (!defined (isascii) && !defined (HAVE_ISASCII))
+# define IN_CTYPE_DOMAIN(c) 1
+#else
+# define IN_CTYPE_DOMAIN(c) isascii(c)
+#endif
+
+#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
+#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
+#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper (c))
+#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
+
+/* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char.
+ - It's guaranteed to evaluate its argument exactly once.
+ - It's typically faster.
+ Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
+ only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless
+ it's important to use the locale's definition of `digit' even when the
+ host does not conform to Posix. */
+#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
+
+#if defined (STDC_HEADERS) || defined (USG)
+# include <string.h>
+#endif
+
+#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __attribute__(x)
+#endif
+
+#ifndef ATTRIBUTE_UNUSED
+# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+/* Some old versions of bison generate parsers that use bcopy.
+ That loses on systems that don't provide the function, so we have
+ to redefine it here. */
+#if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
+# define bcopy(from, to, len) memcpy ((to), (from), (len))
+#endif
+
+/* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc),
+ as well as gratuitiously global symbol names, so we can have multiple
+ yacc generated parsers in the same program. Note that these are only
+ the variables produced by yacc. If other parser generators (bison,
+ byacc, etc) produce additional global names that conflict at link time,
+ then those parser generators need to be fixed instead of adding those
+ names to this list. */
+
+#define yymaxdepth gd_maxdepth
+#define yyparse gd_parse
+#define yylex gd_lex
+#define yyerror gd_error
+#define yylval gd_lval
+#define yychar gd_char
+#define yydebug gd_debug
+#define yypact gd_pact
+#define yyr1 gd_r1
+#define yyr2 gd_r2
+#define yydef gd_def
+#define yychk gd_chk
+#define yypgo gd_pgo
+#define yyact gd_act
+#define yyexca gd_exca
+#define yyerrflag gd_errflag
+#define yynerrs gd_nerrs
+#define yyps gd_ps
+#define yypv gd_pv
+#define yys gd_s
+#define yy_yys gd_yys
+#define yystate gd_state
+#define yytmp gd_tmp
+#define yyv gd_v
+#define yy_yyv gd_yyv
+#define yyval gd_val
+#define yylloc gd_lloc
+#define yyreds gd_reds /* With YYDEBUG defined */
+#define yytoks gd_toks /* With YYDEBUG defined */
+#define yylhs gd_yylhs
+#define yylen gd_yylen
+#define yydefred gd_yydefred
+#define yydgoto gd_yydgoto
+#define yysindex gd_yysindex
+#define yyrindex gd_yyrindex
+#define yygindex gd_yygindex
+#define yytable gd_yytable
+#define yycheck gd_yycheck
+
+static int yylex (void);
+static int yyerror (char *s);
+
+#define EPOCH 1970
+#define HOUR(x) ((x) * 60)
+
+#define MAX_BUFF_LEN 128 /* size of buffer to read the date into */
+
+/*
+** An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+ const char *name;
+ int type;
+ int value;
+} TABLE;
+
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+** Global variables. We could get rid of most of these by using a good
+** union as the yacc stack. (This routine was originally written before
+** yacc had the %union construct.) Maybe someday; right now we only use
+** the %union very rarely.
+*/
+static const char *yyInput;
+static int yyDayOrdinal;
+static int yyDayNumber;
+static int yyHaveDate;
+static int yyHaveDay;
+static int yyHaveRel;
+static int yyHaveTime;
+static int yyHaveZone;
+static int yyTimezone;
+static int yyDay;
+static int yyHour;
+static int yyMinutes;
+static int yyMonth;
+static int yySeconds;
+static int yyYear;
+static MERIDIAN yyMeridian;
+static int yyRelDay;
+static int yyRelHour;
+static int yyRelMinutes;
+static int yyRelMonth;
+static int yyRelSeconds;
+static int yyRelYear;
+
+%}
+
+/* This grammar has 13 shift/reduce conflicts. */
+%expect 13
+
+%union {
+ int Number;
+ enum _MERIDIAN Meridian;
+}
+
+%token tAGO tDAY tDAY_UNIT tDAYZONE tDST tHOUR_UNIT tID
+%token tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
+%token tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
+
+%type <Number> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tMINUTE_UNIT
+%type <Number> tMONTH tMONTH_UNIT
+%type <Number> tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
+%type <Meridian> tMERIDIAN o_merid
+
+%%
+
+spec : /* NULL */
+ | spec item
+ ;
+
+item : time {
+ yyHaveTime++;
+ }
+ | zone {
+ yyHaveZone++;
+ }
+ | date {
+ yyHaveDate++;
+ }
+ | day {
+ yyHaveDay++;
+ }
+ | rel {
+ yyHaveRel++;
+ }
+ | number
+ ;
+
+time : tUNUMBER tMERIDIAN {
+ yyHour = $1;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = 0;
+ yyMeridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yyMeridian = MER24;
+ yyHaveZone++;
+ yyTimezone = ($4 < 0
+ ? -$4 % 100 + (-$4 / 100) * 60
+ : - ($4 % 100 + ($4 / 100) * 60));
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = MER24;
+ yyHaveZone++;
+ yyTimezone = ($6 < 0
+ ? -$6 % 100 + (-$6 / 100) * 60
+ : - ($6 % 100 + ($6 / 100) * 60));
+ }
+ ;
+
+zone : tZONE {
+ yyTimezone = $1;
+ }
+ | tDAYZONE {
+ yyTimezone = $1 - 60;
+ }
+ |
+ tZONE tDST {
+ yyTimezone = $1 - 60;
+ }
+ ;
+
+day : tDAY {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tDAY ',' {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tUNUMBER tDAY {
+ yyDayOrdinal = $1;
+ yyDayNumber = $2;
+ }
+ ;
+
+date : tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ /* Interpret as YYYY/MM/DD if $1 >= 1000, otherwise as MM/DD/YY.
+ The goal in recognizing YYYY/MM/DD is solely to support legacy
+ machine-generated dates like those in an RCS log listing. If
+ you want portability, use the ISO 8601 format. */
+ if ($1 >= 1000)
+ {
+ yyYear = $1;
+ yyMonth = $3;
+ yyDay = $5;
+ }
+ else
+ {
+ yyMonth = $1;
+ yyDay = $3;
+ yyYear = $5;
+ }
+ }
+ | tUNUMBER tSNUMBER tSNUMBER {
+ /* ISO 8601 format. yyyy-mm-dd. */
+ yyYear = $1;
+ yyMonth = -$2;
+ yyDay = -$3;
+ }
+ | tUNUMBER tMONTH tSNUMBER {
+ /* e.g. 17-JUN-1992. */
+ yyDay = $1;
+ yyMonth = $2;
+ yyYear = -$3;
+ }
+ | tMONTH tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ yyYear = $4;
+ }
+ | tUNUMBER tMONTH {
+ yyMonth = $2;
+ yyDay = $1;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ yyMonth = $2;
+ yyDay = $1;
+ yyYear = $3;
+ }
+ ;
+
+rel : relunit tAGO {
+ yyRelSeconds = -yyRelSeconds;
+ yyRelMinutes = -yyRelMinutes;
+ yyRelHour = -yyRelHour;
+ yyRelDay = -yyRelDay;
+ yyRelMonth = -yyRelMonth;
+ yyRelYear = -yyRelYear;
+ }
+ | relunit
+ ;
+
+relunit : tUNUMBER tYEAR_UNIT {
+ yyRelYear += $1 * $2;
+ }
+ | tSNUMBER tYEAR_UNIT {
+ yyRelYear += $1 * $2;
+ }
+ | tYEAR_UNIT {
+ yyRelYear += $1;
+ }
+ | tUNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tSNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tMONTH_UNIT {
+ yyRelMonth += $1;
+ }
+ | tUNUMBER tDAY_UNIT {
+ yyRelDay += $1 * $2;
+ }
+ | tSNUMBER tDAY_UNIT {
+ yyRelDay += $1 * $2;
+ }
+ | tDAY_UNIT {
+ yyRelDay += $1;
+ }
+ | tUNUMBER tHOUR_UNIT {
+ yyRelHour += $1 * $2;
+ }
+ | tSNUMBER tHOUR_UNIT {
+ yyRelHour += $1 * $2;
+ }
+ | tHOUR_UNIT {
+ yyRelHour += $1;
+ }
+ | tUNUMBER tMINUTE_UNIT {
+ yyRelMinutes += $1 * $2;
+ }
+ | tSNUMBER tMINUTE_UNIT {
+ yyRelMinutes += $1 * $2;
+ }
+ | tMINUTE_UNIT {
+ yyRelMinutes += $1;
+ }
+ | tUNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tSNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tSEC_UNIT {
+ yyRelSeconds += $1;
+ }
+ ;
+
+number : tUNUMBER
+ {
+ if (yyHaveTime && yyHaveDate && !yyHaveRel)
+ yyYear = $1;
+ else
+ {
+ if ($1>10000)
+ {
+ yyHaveDate++;
+ yyDay= ($1)%100;
+ yyMonth= ($1/100)%100;
+ yyYear = $1/10000;
+ }
+ else
+ {
+ yyHaveTime++;
+ if ($1 < 100)
+ {
+ yyHour = $1;
+ yyMinutes = 0;
+ }
+ else
+ {
+ yyHour = $1 / 100;
+ yyMinutes = $1 % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = MER24;
+ }
+ }
+ }
+ ;
+
+o_merid : /* NULL */
+ {
+ $$ = MER24;
+ }
+ | tMERIDIAN
+ {
+ $$ = $1;
+ }
+ ;
+
+%%
+
+/* Include this file down here because bison inserts code above which
+ may define-away `const'. We want the prototype for get_date to have
+ the same signature as the function definition does. */
+#include "getdate.h"
+
+extern struct tm *gmtime (const time_t *timep);
+extern struct tm *localtime (const time_t *timep);
+extern time_t mktime (struct tm *tm);
+
+/* Month and day table. */
+static TABLE const MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "sept", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 1 },
+ { "tuesday", tDAY, 2 },
+ { "tues", tDAY, 2 },
+ { "wednesday", tDAY, 3 },
+ { "wednes", tDAY, 3 },
+ { "thursday", tDAY, 4 },
+ { "thur", tDAY, 4 },
+ { "thurs", tDAY, 4 },
+ { "friday", tDAY, 5 },
+ { "saturday", tDAY, 6 },
+ { NULL, 0, 0 }
+};
+
+/* Time units table. */
+static TABLE const UnitsTable[] = {
+ { "year", tYEAR_UNIT, 1 },
+ { "month", tMONTH_UNIT, 1 },
+ { "fortnight", tDAY_UNIT, 14 },
+ { "week", tDAY_UNIT, 7 },
+ { "day", tDAY_UNIT, 1 },
+ { "hour", tHOUR_UNIT, 1 },
+ { "minute", tMINUTE_UNIT, 1 },
+ { "min", tMINUTE_UNIT, 1 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+ { NULL, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static TABLE const OtherTable[] = {
+ { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
+ { "today", tMINUTE_UNIT, 0 },
+ { "now", tMINUTE_UNIT, 0 },
+ { "last", tUNUMBER, -1 },
+ { "this", tMINUTE_UNIT, 0 },
+ { "next", tUNUMBER, 1 },
+ { "first", tUNUMBER, 1 },
+/* { "second", tUNUMBER, 2 }, */
+ { "third", tUNUMBER, 3 },
+ { "fourth", tUNUMBER, 4 },
+ { "fifth", tUNUMBER, 5 },
+ { "sixth", tUNUMBER, 6 },
+ { "seventh", tUNUMBER, 7 },
+ { "eighth", tUNUMBER, 8 },
+ { "ninth", tUNUMBER, 9 },
+ { "tenth", tUNUMBER, 10 },
+ { "eleventh", tUNUMBER, 11 },
+ { "twelfth", tUNUMBER, 12 },
+ { "ago", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/* The timezone table. */
+static TABLE const TimezoneTable[] = {
+ { "gmt", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "utc", tZONE, HOUR ( 0) },
+ { "wet", tZONE, HOUR ( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "wat", tZONE, HOUR ( 1) }, /* West Africa */
+ { "at", tZONE, HOUR ( 2) }, /* Azores */
+#if 0
+ /* For completeness. BST is also British Summer, and GST is
+ * also Guam Standard. */
+ { "bst", tZONE, HOUR ( 3) }, /* Brazil Standard */
+ { "gst", tZONE, HOUR ( 3) }, /* Greenland Standard */
+#endif
+#if 0
+ { "nft", tZONE, HOUR (3.5) }, /* Newfoundland */
+ { "nst", tZONE, HOUR (3.5) }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR (3.5) }, /* Newfoundland Daylight */
+#endif
+ { "ast", tZONE, HOUR ( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR ( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR ( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR ( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR ( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR ( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR ( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR ( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR ( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR ( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR ( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR ( 9) }, /* Yukon Daylight */
+ { "hst", tZONE, HOUR (10) }, /* Hawaii Standard */
+ { "hdt", tDAYZONE, HOUR (10) }, /* Hawaii Daylight */
+ { "cat", tZONE, HOUR (10) }, /* Central Alaska */
+ { "ahst", tZONE, HOUR (10) }, /* Alaska-Hawaii Standard */
+ { "nt", tZONE, HOUR (11) }, /* Nome */
+ { "idlw", tZONE, HOUR (12) }, /* International Date Line West */
+ { "cet", tZONE, -HOUR (1) }, /* Central European */
+ { "met", tZONE, -HOUR (1) }, /* Middle European */
+ { "mewt", tZONE, -HOUR (1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
+ { "mesz", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR (1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR (1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR (1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR (1) }, /* French Summer */
+ { "eet", tZONE, -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
+ { "bt", tZONE, -HOUR (3) }, /* Baghdad, USSR Zone 2 */
+#if 0
+ { "it", tZONE, -HOUR (3.5) },/* Iran */
+#endif
+ { "zp4", tZONE, -HOUR (4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR (5) }, /* USSR Zone 4 */
+#if 0
+ { "ist", tZONE, -HOUR (5.5) },/* Indian Standard */
+#endif
+ { "zp6", tZONE, -HOUR (6) }, /* USSR Zone 5 */
+#if 0
+ /* For completeness. NST is also Newfoundland Standard, and SST is
+ * also Swedish Summer. */
+ { "nst", tZONE, -HOUR (6.5) },/* North Sumatra */
+ { "sst", tZONE, -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
+#endif /* 0 */
+ { "wast", tZONE, -HOUR (7) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR (7) }, /* West Australian Daylight */
+#if 0
+ { "jt", tZONE, -HOUR (7.5) },/* Java (3pm in Cronusland!) */
+#endif
+ { "cct", tZONE, -HOUR (8) }, /* China Coast, USSR Zone 7 */
+ { "jst", tZONE, -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
+#if 0
+ { "cast", tZONE, -HOUR (9.5) },/* Central Australian Standard */
+ { "cadt", tDAYZONE, -HOUR (9.5) },/* Central Australian Daylight */
+#endif
+ { "east", tZONE, -HOUR (10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR (10) }, /* Eastern Australian Daylight */
+ { "gst", tZONE, -HOUR (10) }, /* Guam Standard, USSR Zone 9 */
+ { "nzt", tZONE, -HOUR (12) }, /* New Zealand */
+ { "nzst", tZONE, -HOUR (12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR (12) }, /* New Zealand Daylight */
+ { "idle", tZONE, -HOUR (12) }, /* International Date Line East */
+ { NULL, 0, 0 }
+};
+
+/* Military timezone table. */
+static TABLE const MilitaryTable[] = {
+ { "a", tZONE, HOUR ( 1) },
+ { "b", tZONE, HOUR ( 2) },
+ { "c", tZONE, HOUR ( 3) },
+ { "d", tZONE, HOUR ( 4) },
+ { "e", tZONE, HOUR ( 5) },
+ { "f", tZONE, HOUR ( 6) },
+ { "g", tZONE, HOUR ( 7) },
+ { "h", tZONE, HOUR ( 8) },
+ { "i", tZONE, HOUR ( 9) },
+ { "k", tZONE, HOUR ( 10) },
+ { "l", tZONE, HOUR ( 11) },
+ { "m", tZONE, HOUR ( 12) },
+ { "n", tZONE, HOUR (- 1) },
+ { "o", tZONE, HOUR (- 2) },
+ { "p", tZONE, HOUR (- 3) },
+ { "q", tZONE, HOUR (- 4) },
+ { "r", tZONE, HOUR (- 5) },
+ { "s", tZONE, HOUR (- 6) },
+ { "t", tZONE, HOUR (- 7) },
+ { "u", tZONE, HOUR (- 8) },
+ { "v", tZONE, HOUR (- 9) },
+ { "w", tZONE, HOUR (-10) },
+ { "x", tZONE, HOUR (-11) },
+ { "y", tZONE, HOUR (-12) },
+ { "z", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+
+
+
+/* ARGSUSED */
+static int
+yyerror (char *s ATTRIBUTE_UNUSED)
+{
+ return 0;
+}
+
+static int
+ToHour (int Hours, MERIDIAN Meridian)
+{
+ switch (Meridian)
+ {
+ case MER24:
+ if (Hours < 0 || Hours > 23)
+ return -1;
+ return Hours;
+ case MERam:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ return Hours;
+ case MERpm:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ return Hours + 12;
+ default:
+ abort ();
+ }
+ /* NOTREACHED */
+}
+
+static int
+ToYear (int Year)
+{
+ if (Year < 0)
+ Year = -Year;
+
+ /* XPG4 suggests that years 00-68 map to 2000-2068, and
+ years 69-99 map to 1969-1999. */
+ if (Year < 69)
+ Year += 2000;
+ else if (Year < 100)
+ Year += 1900;
+
+ return Year;
+}
+
+static int
+LookupWord (char *buff)
+{
+ register char *p;
+ register char *q;
+ register const TABLE *tp;
+ int i;
+ int abbrev;
+
+ /* Make it lowercase. */
+ for (p = buff; *p; p++)
+ if (ISUPPER ((unsigned char) *p))
+ *p = tolower ((unsigned char) *p);
+
+ if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0)
+ {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0)
+ {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+
+ /* See if we have an abbreviation for a month. */
+ if (strlen (buff) == 3)
+ abbrev = 1;
+ else if (strlen (buff) == 4 && buff[3] == '.')
+ {
+ abbrev = 1;
+ buff[3] = '\0';
+ }
+ else
+ abbrev = 0;
+
+ for (tp = MonthDayTable; tp->name; tp++)
+ {
+ if (abbrev)
+ {
+ if (strncmp (buff, tp->name, 3) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ else if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ if (strcmp (buff, "dst") == 0)
+ return tDST;
+
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Strip off any plural and try the units table again. */
+ i = strlen (buff) - 1;
+ if (buff[i] == 's')
+ {
+ buff[i] = '\0';
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ buff[i] = 's'; /* Put back for "this" in OtherTable. */
+ }
+
+ for (tp = OtherTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Military timezones. */
+ if (buff[1] == '\0' && ISALPHA ((unsigned char) *buff))
+ {
+ for (tp = MilitaryTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Drop out any periods and try the timezone table again. */
+ for (i = 0, p = q = buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ else
+ i++;
+ *p = '\0';
+ if (i)
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp (buff, tp->name) == 0)
+ {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ return tID;
+}
+
+static int
+yylex ()
+{
+ register unsigned char c;
+ register char *p;
+ char buff[20];
+ int Count;
+ int sign;
+
+ for (;;)
+ {
+ while (ISSPACE ((unsigned char) *yyInput))
+ yyInput++;
+
+ if (ISDIGIT (c = *yyInput) || c == '-' || c == '+')
+ {
+ if (c == '-' || c == '+')
+ {
+ sign = c == '-' ? -1 : 1;
+ if (!ISDIGIT (*++yyInput))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ for (yylval.Number = 0; ISDIGIT (c = *yyInput++);)
+ yylval.Number = 10 * yylval.Number + c - '0';
+ yyInput--;
+ if (sign < 0)
+ yylval.Number = -yylval.Number;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ if (ISALPHA (c))
+ {
+ for (p = buff; (c = *yyInput++, ISALPHA (c)) || c == '.';)
+ if (p < &buff[sizeof buff - 1])
+ *p++ = c;
+ *p = '\0';
+ yyInput--;
+ return LookupWord (buff);
+ }
+ if (c != '(')
+ return *yyInput++;
+ Count = 0;
+ do
+ {
+ c = *yyInput++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ Count++;
+ else if (c == ')')
+ Count--;
+ }
+ while (Count > 0);
+ }
+}
+
+#define TM_YEAR_ORIGIN 1900
+
+/* Yield A - B, measured in seconds. */
+static long
+difftm (struct tm *a, struct tm *b)
+{
+ int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
+ int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
+ long days = (
+ /* difference in day of year */
+ a->tm_yday - b->tm_yday
+ /* + intervening leap days */
+ + ((ay >> 2) - (by >> 2))
+ - (ay / 100 - by / 100)
+ + ((ay / 100 >> 2) - (by / 100 >> 2))
+ /* + difference in years * 365 */
+ + (long) (ay - by) * 365
+ );
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+
+time_t
+get_date (const char *p, const time_t *now)
+{
+ struct tm tm, tm0, *tmp;
+ time_t Start;
+
+ yyInput = p;
+ Start = now ? *now : time ((time_t *) NULL);
+ tmp = localtime (&Start);
+ if (!tmp)
+ return -1;
+ yyYear = tmp->tm_year + TM_YEAR_ORIGIN;
+ yyMonth = tmp->tm_mon + 1;
+ yyDay = tmp->tm_mday;
+ yyHour = tmp->tm_hour;
+ yyMinutes = tmp->tm_min;
+ yySeconds = tmp->tm_sec;
+ tm.tm_isdst = tmp->tm_isdst;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMinutes = 0;
+ yyRelHour = 0;
+ yyRelDay = 0;
+ yyRelMonth = 0;
+ yyRelYear = 0;
+ yyHaveDate = 0;
+ yyHaveDay = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+ yyHaveZone = 0;
+
+ if (yyparse ()
+ || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
+ return -1;
+
+ tm.tm_year = ToYear (yyYear) - TM_YEAR_ORIGIN + yyRelYear;
+ tm.tm_mon = yyMonth - 1 + yyRelMonth;
+ tm.tm_mday = yyDay + yyRelDay;
+ if (yyHaveTime || (yyHaveRel && !yyHaveDate && !yyHaveDay))
+ {
+ tm.tm_hour = ToHour (yyHour, yyMeridian);
+ if (tm.tm_hour < 0)
+ return -1;
+ tm.tm_min = yyMinutes;
+ tm.tm_sec = yySeconds;
+ }
+ else
+ {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ }
+ tm.tm_hour += yyRelHour;
+ tm.tm_min += yyRelMinutes;
+ tm.tm_sec += yyRelSeconds;
+
+ /* Let mktime deduce tm_isdst if we have an absolute timestamp,
+ or if the relative timestamp mentions days, months, or years. */
+ if (yyHaveDate | yyHaveDay | yyHaveTime | yyRelDay | yyRelMonth | yyRelYear)
+ tm.tm_isdst = -1;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (Start == (time_t) -1)
+ {
+
+ /* Guard against falsely reporting errors near the time_t boundaries
+ when parsing times in other time zones. For example, if the min
+ time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
+ of UTC, then the min localtime value is 1970-01-01 08:00:00; if
+ we apply mktime to 1970-01-01 00:00:00 we will get an error, so
+ we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
+ zone by 24 hours to compensate. This algorithm assumes that
+ there is no DST transition within a day of the time_t boundaries. */
+ if (yyHaveZone)
+ {
+ tm = tm0;
+ if (tm.tm_year <= EPOCH - TM_YEAR_ORIGIN)
+ {
+ tm.tm_mday++;
+ yyTimezone -= 24 * 60;
+ }
+ else
+ {
+ tm.tm_mday--;
+ yyTimezone += 24 * 60;
+ }
+ Start = mktime (&tm);
+ }
+
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (yyHaveDay && !yyHaveDate)
+ {
+ tm.tm_mday += ((yyDayNumber - tm.tm_wday + 7) % 7
+ + 7 * (yyDayOrdinal - (0 < yyDayOrdinal)));
+ Start = mktime (&tm);
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (yyHaveZone)
+ {
+ long delta;
+ struct tm *gmt = gmtime (&Start);
+ if (!gmt)
+ return -1;
+ delta = yyTimezone * 60L + difftm (&tm, gmt);
+ if ((Start + delta < Start) != (delta < 0))
+ return -1; /* time_t overflow */
+ Start += delta;
+ }
+
+ return Start;
+}
+
+#if defined (TEST)
+
+/* ARGSUSED */
+int
+main (ac, av)
+ int ac;
+ char *av[];
+{
+ char buff[MAX_BUFF_LEN + 1];
+ time_t d;
+
+ (void) printf ("Enter date, or blank line to exit.\n\t> ");
+ (void) fflush (stdout);
+
+ buff[MAX_BUFF_LEN] = 0;
+ while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
+ {
+ d = get_date (buff, (time_t *) NULL);
+ if (d == -1)
+ (void) printf ("Bad format - couldn't convert.\n");
+ else
+ (void) printf ("%s", ctime (&d));
+ (void) printf ("\t> ");
+ (void) fflush (stdout);
+ }
+ exit (0);
+ /* NOTREACHED */
+}
+#endif /* defined (TEST) */
diff --git a/hash.h b/hash.h
new file mode 100644
index 0000000..39abf27
--- /dev/null
+++ b/hash.h
@@ -0,0 +1,57 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2012
+ *
+ * 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 crypto hashing.
+
+ */
+
+#ifndef GOT_HASH_H
+#define GOT_HASH_H
+
+/* length of hash values produced by SHA512 */
+#define MAX_HASH_LENGTH 64
+
+typedef enum {
+ HSH_INVALID = 0,
+ HSH_MD5 = 1,
+ HSH_SHA1 = 2,
+ HSH_SHA256 = 3,
+ HSH_SHA384 = 4,
+ HSH_SHA512 = 5,
+ HSH_SHA3_224 = 6,
+ HSH_SHA3_256 = 7,
+ HSH_SHA3_384 = 8,
+ HSH_SHA3_512 = 9,
+ HSH_TIGER = 10,
+ HSH_WHIRLPOOL = 11,
+ HSH_MD5_NONCRYPTO = 10000, /* For NTPv4 reference ID */
+} HSH_Algorithm;
+
+extern int HSH_GetHashId(HSH_Algorithm algorithm);
+
+extern int HSH_Hash(int id, const void *in1, int in1_len, const void *in2, int in2_len,
+ unsigned char *out, int out_len);
+
+extern void HSH_Finalise(void);
+
+#endif
diff --git a/hash_gnutls.c b/hash_gnutls.c
new file mode 100644
index 0000000..269fdd8
--- /dev/null
+++ b/hash_gnutls.c
@@ -0,0 +1,145 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2021
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Crypto hashing using the GnuTLS library
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <gnutls/crypto.h>
+
+#include "hash.h"
+#include "logging.h"
+
+struct hash {
+ const HSH_Algorithm algorithm;
+ const gnutls_digest_algorithm_t type;
+ gnutls_hash_hd_t handle;
+};
+
+static struct hash hashes[] = {
+ { HSH_MD5_NONCRYPTO, GNUTLS_DIG_MD5, NULL },
+ { HSH_MD5, GNUTLS_DIG_MD5, NULL },
+ { HSH_SHA1, GNUTLS_DIG_SHA1, NULL },
+ { HSH_SHA256, GNUTLS_DIG_SHA256, NULL },
+ { HSH_SHA384, GNUTLS_DIG_SHA384, NULL },
+ { HSH_SHA512, GNUTLS_DIG_SHA512, NULL },
+ { HSH_SHA3_224, GNUTLS_DIG_SHA3_224, NULL },
+ { HSH_SHA3_256, GNUTLS_DIG_SHA3_256, NULL },
+ { HSH_SHA3_384, GNUTLS_DIG_SHA3_384, NULL },
+ { HSH_SHA3_512, GNUTLS_DIG_SHA3_512, NULL },
+ { 0, 0, NULL }
+};
+
+static int gnutls_initialised = 0;
+
+int
+HSH_GetHashId(HSH_Algorithm algorithm)
+{
+ int id, r;
+
+ if (!gnutls_initialised) {
+ r = gnutls_global_init();
+ if (r < 0)
+ LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r));
+ gnutls_initialised = 1;
+ }
+
+ for (id = 0; hashes[id].algorithm != 0; id++) {
+ if (hashes[id].algorithm == algorithm)
+ break;
+ }
+
+ if (hashes[id].algorithm == 0)
+ return -1;
+
+ if (hashes[id].handle)
+ return id;
+
+ if (algorithm == HSH_MD5_NONCRYPTO)
+ GNUTLS_FIPS140_SET_LAX_MODE();
+
+ r = gnutls_hash_init(&hashes[id].handle, hashes[id].type);
+
+ if (algorithm == HSH_MD5_NONCRYPTO)
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+
+ if (r < 0) {
+ DEBUG_LOG("Could not initialise %s : %s", "hash", gnutls_strerror(r));
+ hashes[id].handle = NULL;
+ return -1;
+ }
+
+ return id;
+}
+
+int
+HSH_Hash(int id, const void *in1, int in1_len, const void *in2, int in2_len,
+ unsigned char *out, int out_len)
+{
+ unsigned char buf[MAX_HASH_LENGTH];
+ gnutls_hash_hd_t handle;
+ int hash_len;
+
+ if (in1_len < 0 || in2_len < 0 || out_len < 0)
+ return 0;
+
+ handle = hashes[id].handle;
+ hash_len = gnutls_hash_get_len(hashes[id].type);
+
+ if (out_len > hash_len)
+ out_len = hash_len;
+
+ if (hash_len > sizeof (buf))
+ return 0;
+
+ if (gnutls_hash(handle, in1, in1_len) < 0 ||
+ (in2 && gnutls_hash(handle, in2, in2_len) < 0)) {
+ /* Reset the state */
+ gnutls_hash_output(handle, buf);
+ return 0;
+ }
+
+ gnutls_hash_output(handle, buf);
+ memcpy(out, buf, out_len);
+
+ return out_len;
+}
+
+void
+HSH_Finalise(void)
+{
+ int i;
+
+ if (!gnutls_initialised)
+ return;
+
+ for (i = 0; hashes[i].algorithm != 0; i++) {
+ if (hashes[i].handle)
+ gnutls_hash_deinit(hashes[i].handle, NULL);
+ }
+
+ gnutls_global_deinit();
+}
diff --git a/hash_intmd5.c b/hash_intmd5.c
new file mode 100644
index 0000000..a64b735
--- /dev/null
+++ b/hash_intmd5.c
@@ -0,0 +1,71 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2012
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing crypto hashing using internal MD5 implementation.
+
+ */
+
+#include "config.h"
+#include "sysincl.h"
+#include "hash.h"
+#include "memory.h"
+#include "util.h"
+
+#include "md5.c"
+
+static MD5_CTX ctx;
+
+int
+HSH_GetHashId(HSH_Algorithm algorithm)
+{
+ /* only MD5 is supported */
+ if (algorithm != HSH_MD5 && algorithm != HSH_MD5_NONCRYPTO)
+ return -1;
+
+ return 0;
+}
+
+int
+HSH_Hash(int id, const void *in1, int in1_len, const void *in2, int in2_len,
+ unsigned char *out, int out_len)
+{
+ if (in1_len < 0 || in2_len < 0 || out_len < 0)
+ return 0;
+
+ MD5Init(&ctx);
+ MD5Update(&ctx, in1, in1_len);
+ if (in2)
+ MD5Update(&ctx, in2, in2_len);
+ MD5Final(&ctx);
+
+ out_len = MIN(out_len, 16);
+
+ memcpy(out, ctx.digest, out_len);
+
+ return out_len;
+}
+
+void
+HSH_Finalise(void)
+{
+}
diff --git a/hash_nettle.c b/hash_nettle.c
new file mode 100644
index 0000000..4a214f6
--- /dev/null
+++ b/hash_nettle.c
@@ -0,0 +1,124 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2018
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing crypto hashing using the nettle library.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <nettle/nettle-meta.h>
+
+#include "hash.h"
+#include "memory.h"
+
+struct hash {
+ const HSH_Algorithm algorithm;
+ const char *int_name;
+ const struct nettle_hash *nettle_hash;
+ void *context;
+};
+
+static struct hash hashes[] = {
+ { HSH_MD5, "md5", NULL, NULL },
+ { HSH_SHA1, "sha1", NULL, NULL },
+ { HSH_SHA256, "sha256", NULL, NULL },
+ { HSH_SHA384, "sha384", NULL, NULL },
+ { HSH_SHA512, "sha512", NULL, NULL },
+ { HSH_SHA3_224, "sha3_224", NULL, NULL },
+ { HSH_SHA3_256, "sha3_256", NULL, NULL },
+ { HSH_SHA3_384, "sha3_384", NULL, NULL },
+ { HSH_SHA3_512, "sha3_512", NULL, NULL },
+ { 0, NULL, NULL, NULL }
+};
+
+int
+HSH_GetHashId(HSH_Algorithm algorithm)
+{
+ int id, nid;
+
+ if (algorithm == HSH_MD5_NONCRYPTO)
+ algorithm = HSH_MD5;
+
+ for (id = 0; hashes[id].algorithm != 0; id++) {
+ if (hashes[id].algorithm == algorithm)
+ break;
+ }
+
+ if (hashes[id].algorithm == 0)
+ return -1;
+
+ if (hashes[id].context)
+ return id;
+
+ for (nid = 0; nettle_hashes[nid]; nid++) {
+ if (!strcmp(hashes[id].int_name, nettle_hashes[nid]->name))
+ break;
+ }
+
+ if (!nettle_hashes[nid] || !nettle_hashes[nid]->context_size || !nettle_hashes[nid]->init)
+ return -1;
+
+ hashes[id].nettle_hash = nettle_hashes[nid];
+ hashes[id].context = Malloc(hashes[id].nettle_hash->context_size);
+
+ return id;
+}
+
+int
+HSH_Hash(int id, const void *in1, int in1_len, const void *in2, int in2_len,
+ unsigned char *out, int out_len)
+{
+ const struct nettle_hash *hash;
+ void *context;
+
+ if (in1_len < 0 || in2_len < 0 || out_len < 0)
+ return 0;
+
+ hash = hashes[id].nettle_hash;
+ context = hashes[id].context;
+
+ if (out_len > hash->digest_size)
+ out_len = hash->digest_size;
+
+ hash->init(context);
+ hash->update(context, in1_len, in1);
+ if (in2)
+ hash->update(context, in2_len, in2);
+ hash->digest(context, out_len, out);
+
+ return out_len;
+}
+
+void
+HSH_Finalise(void)
+{
+ int i;
+
+ for (i = 0; hashes[i].algorithm != 0; i++) {
+ if (hashes[i].context)
+ Free(hashes[i].context);
+ }
+}
diff --git a/hash_nss.c b/hash_nss.c
new file mode 100644
index 0000000..4302447
--- /dev/null
+++ b/hash_nss.c
@@ -0,0 +1,114 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2012
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing crypto hashing using NSSLOWHASH API of the NSS library.
+
+ */
+
+#include "config.h"
+
+#include <nss.h>
+#include <hasht.h>
+#include <nsslowhash.h>
+
+#include "hash.h"
+#include "util.h"
+
+static NSSLOWInitContext *ictx;
+
+struct hash {
+ HASH_HashType type;
+ HSH_Algorithm algorithm;
+ NSSLOWHASHContext *context;
+};
+
+static struct hash hashes[] = {
+ { HASH_AlgMD5, HSH_MD5, NULL },
+ { HASH_AlgSHA1, HSH_SHA1, NULL },
+ { HASH_AlgSHA256, HSH_SHA256, NULL },
+ { HASH_AlgSHA384, HSH_SHA384, NULL },
+ { HASH_AlgSHA512, HSH_SHA512, NULL },
+ { 0, 0, NULL }
+};
+
+int
+HSH_GetHashId(HSH_Algorithm algorithm)
+{
+ int i;
+
+ if (algorithm == HSH_MD5_NONCRYPTO)
+ algorithm = HSH_MD5;
+
+ for (i = 0; hashes[i].algorithm != 0; i++) {
+ if (hashes[i].algorithm == algorithm)
+ break;
+ }
+
+ if (hashes[i].algorithm == 0)
+ return -1; /* not found */
+
+ if (!ictx && !(ictx = NSSLOW_Init()))
+ return -1; /* couldn't init NSS */
+
+ if (!hashes[i].context &&
+ !(hashes[i].context = NSSLOWHASH_NewContext(ictx, hashes[i].type)))
+ return -1; /* couldn't init hash */
+
+ return i;
+}
+
+int
+HSH_Hash(int id, const void *in1, int in1_len, const void *in2, int in2_len,
+ unsigned char *out, int out_len)
+{
+ unsigned char buf[MAX_HASH_LENGTH];
+ unsigned int ret = 0;
+
+ if (in1_len < 0 || in2_len < 0 || out_len < 0)
+ return 0;
+
+ NSSLOWHASH_Begin(hashes[id].context);
+ NSSLOWHASH_Update(hashes[id].context, in1, in1_len);
+ if (in2)
+ NSSLOWHASH_Update(hashes[id].context, in2, in2_len);
+ NSSLOWHASH_End(hashes[id].context, buf, &ret, sizeof (buf));
+
+ ret = MIN(ret, out_len);
+ memcpy(out, buf, ret);
+
+ return ret;
+}
+
+void
+HSH_Finalise(void)
+{
+ int i;
+
+ for (i = 0; hashes[i].algorithm != 0; i++) {
+ if (hashes[i].context)
+ NSSLOWHASH_Destroy(hashes[i].context);
+ }
+
+ if (ictx)
+ NSSLOW_Shutdown(ictx);
+}
diff --git a/hash_tomcrypt.c b/hash_tomcrypt.c
new file mode 100644
index 0000000..9c425d2
--- /dev/null
+++ b/hash_tomcrypt.c
@@ -0,0 +1,126 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2012, 2018
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing crypto hashing using tomcrypt library.
+
+ */
+
+#include <tomcrypt.h>
+
+#include "config.h"
+#include "hash.h"
+#include "util.h"
+
+struct hash {
+ HSH_Algorithm algorithm;
+ const char *int_name;
+ const struct ltc_hash_descriptor *desc;
+};
+
+static const struct hash hashes[] = {
+ { HSH_MD5, "md5", &md5_desc },
+#ifdef LTC_SHA1
+ { HSH_SHA1, "sha1", &sha1_desc },
+#endif
+#ifdef LTC_SHA256
+ { HSH_SHA256, "sha256", &sha256_desc },
+#endif
+#ifdef LTC_SHA384
+ { HSH_SHA384, "sha384", &sha384_desc },
+#endif
+#ifdef LTC_SHA512
+ { HSH_SHA512, "sha512", &sha512_desc },
+#endif
+#ifdef LTC_SHA3
+ { HSH_SHA3_224, "sha3-224", &sha3_224_desc },
+ { HSH_SHA3_256, "sha3-256", &sha3_256_desc },
+ { HSH_SHA3_384, "sha3-384", &sha3_384_desc },
+ { HSH_SHA3_512, "sha3-512", &sha3_512_desc },
+#endif
+#ifdef LTC_TIGER
+ { HSH_TIGER, "tiger", &tiger_desc },
+#endif
+#ifdef LTC_WHIRLPOOL
+ { HSH_WHIRLPOOL, "whirlpool", &whirlpool_desc },
+#endif
+ { 0, NULL, NULL }
+};
+
+int
+HSH_GetHashId(HSH_Algorithm algorithm)
+{
+ int i, h;
+
+ if (algorithm == HSH_MD5_NONCRYPTO)
+ algorithm = HSH_MD5;
+
+ for (i = 0; hashes[i].algorithm != 0; i++) {
+ if (hashes[i].algorithm == algorithm)
+ break;
+ }
+
+ if (hashes[i].algorithm == 0)
+ return -1; /* not found */
+
+ h = find_hash(hashes[i].int_name);
+ if (h >= 0)
+ return h; /* already registered */
+
+ /* register and try again */
+ register_hash(hashes[i].desc);
+
+ return find_hash(hashes[i].int_name);
+}
+
+int
+HSH_Hash(int id, const void *in1, int in1_len, const void *in2, int in2_len,
+ unsigned char *out, int out_len)
+{
+ unsigned char buf[MAX_HASH_LENGTH];
+ unsigned long len;
+ int r;
+
+ if (in1_len < 0 || in2_len < 0 || out_len < 0)
+ return 0;
+
+ len = sizeof (buf);
+ if (in2)
+ r = hash_memory_multi(id, buf, &len,
+ in1, (unsigned long)in1_len,
+ in2, (unsigned long)in2_len, NULL, 0);
+ else
+ r = hash_memory(id, in1, in1_len, buf, &len);
+
+ if (r != CRYPT_OK)
+ return 0;
+
+ len = MIN(len, out_len);
+ memcpy(out, buf, len);
+
+ return len;
+}
+
+void
+HSH_Finalise(void)
+{
+}
diff --git a/hwclock.c b/hwclock.c
new file mode 100644
index 0000000..86c7e51
--- /dev/null
+++ b/hwclock.c
@@ -0,0 +1,334 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016-2018, 2022
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Tracking of hardware clocks (e.g. RTC, PHC)
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "hwclock.h"
+#include "local.h"
+#include "logging.h"
+#include "memory.h"
+#include "quantiles.h"
+#include "regress.h"
+#include "util.h"
+
+/* Minimum and maximum number of samples per clock */
+#define MIN_SAMPLES 2
+#define MAX_SAMPLES 64
+
+/* Maximum acceptable frequency offset of the clock */
+#define MAX_FREQ_OFFSET (2.0 / 3.0)
+
+/* Quantiles for filtering readings by delay */
+#define DELAY_QUANT_MIN_K 1
+#define DELAY_QUANT_MAX_K 2
+#define DELAY_QUANT_Q 10
+#define DELAY_QUANT_REPEAT 7
+#define DELAY_QUANT_MIN_STEP 1.0e-9
+
+struct HCL_Instance_Record {
+ /* HW and local reference timestamp */
+ struct timespec hw_ref;
+ struct timespec local_ref;
+
+ /* Samples stored as intervals (uncorrected for frequency error)
+ relative to local_ref and hw_ref */
+ double *x_data;
+ double *y_data;
+
+ /* Minimum, maximum and current number of samples */
+ int min_samples;
+ int max_samples;
+ int n_samples;
+
+ /* Maximum error of the last sample */
+ double last_err;
+
+ /* Minimum interval between samples */
+ double min_separation;
+
+ /* Expected precision of readings */
+ double precision;
+
+ /* Flag indicating the offset and frequency values are valid */
+ int valid_coefs;
+
+ /* Estimated offset and frequency of HW clock relative to local clock */
+ double offset;
+ double frequency;
+
+ /* Estimated quantiles of reading delay */
+ QNT_Instance delay_quants;
+};
+
+/* ================================================== */
+
+static void
+handle_slew(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything)
+{
+ HCL_Instance clock;
+ double delta;
+
+ clock = anything;
+
+ if (clock->n_samples)
+ UTI_AdjustTimespec(&clock->local_ref, cooked, &clock->local_ref, &delta, dfreq, doffset);
+ if (clock->valid_coefs)
+ clock->frequency /= 1.0 - dfreq;
+}
+
+/* ================================================== */
+
+HCL_Instance
+HCL_CreateInstance(int min_samples, int max_samples, double min_separation, double precision)
+{
+ HCL_Instance clock;
+
+ min_samples = CLAMP(MIN_SAMPLES, min_samples, MAX_SAMPLES);
+ max_samples = CLAMP(MIN_SAMPLES, max_samples, MAX_SAMPLES);
+ max_samples = MAX(min_samples, max_samples);
+
+ clock = MallocNew(struct HCL_Instance_Record);
+ clock->x_data = MallocArray(double, max_samples);
+ clock->y_data = MallocArray(double, max_samples);
+ clock->x_data[max_samples - 1] = 0.0;
+ clock->y_data[max_samples - 1] = 0.0;
+ clock->min_samples = min_samples;
+ clock->max_samples = max_samples;
+ clock->n_samples = 0;
+ clock->valid_coefs = 0;
+ clock->min_separation = min_separation;
+ clock->precision = precision;
+ clock->delay_quants = QNT_CreateInstance(DELAY_QUANT_MIN_K, DELAY_QUANT_MAX_K,
+ DELAY_QUANT_Q, DELAY_QUANT_REPEAT,
+ DELAY_QUANT_MIN_STEP);
+
+ LCL_AddParameterChangeHandler(handle_slew, clock);
+
+ return clock;
+}
+
+/* ================================================== */
+
+void HCL_DestroyInstance(HCL_Instance clock)
+{
+ LCL_RemoveParameterChangeHandler(handle_slew, clock);
+ QNT_DestroyInstance(clock->delay_quants);
+ Free(clock->y_data);
+ Free(clock->x_data);
+ Free(clock);
+}
+
+/* ================================================== */
+
+int
+HCL_NeedsNewSample(HCL_Instance clock, struct timespec *now)
+{
+ if (!clock->n_samples ||
+ fabs(UTI_DiffTimespecsToDouble(now, &clock->local_ref)) >= clock->min_separation)
+ return 1;
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+HCL_ProcessReadings(HCL_Instance clock, int n_readings, struct timespec tss[][3],
+ struct timespec *hw_ts, struct timespec *local_ts, double *err)
+{
+ double delay, raw_delay, min_delay, low_delay, high_delay, e, pred_err;
+ double delay_sum, hw_sum, local_sum, local_prec, freq;
+ int i, min_reading, combined;
+ struct timespec ts1, ts2;
+
+ if (n_readings < 1)
+ return 0;
+
+ /* Work out the current correction multiplier needed to get cooked delays */
+ LCL_CookTime(&tss[0][0], &ts1, NULL);
+ LCL_CookTime(&tss[n_readings - 1][2], &ts2, NULL);
+ if (UTI_CompareTimespecs(&tss[0][0], &tss[n_readings - 1][2]) < 0)
+ freq = UTI_DiffTimespecsToDouble(&ts1, &ts2) /
+ UTI_DiffTimespecsToDouble(&tss[0][0], &tss[n_readings - 1][2]);
+ else
+ freq = 1.0;
+
+ for (i = 0; i < n_readings; i++) {
+ delay = freq * UTI_DiffTimespecsToDouble(&tss[i][2], &tss[i][0]);
+
+ if (delay < 0.0) {
+ /* Step in the middle of a reading? */
+ DEBUG_LOG("Bad reading delay=%e", delay);
+ return 0;
+ }
+
+ if (i == 0 || min_delay > delay) {
+ min_delay = delay;
+ min_reading = i;
+ }
+
+ QNT_Accumulate(clock->delay_quants, delay);
+ }
+
+ local_prec = LCL_GetSysPrecisionAsQuantum();
+
+ low_delay = QNT_GetQuantile(clock->delay_quants, DELAY_QUANT_MIN_K);
+ high_delay = QNT_GetQuantile(clock->delay_quants, DELAY_QUANT_MAX_K);
+ low_delay = MIN(low_delay, high_delay);
+ high_delay = MAX(high_delay, low_delay + local_prec);
+
+ /* Combine readings with delay in the expected interval */
+ for (i = combined = 0, delay_sum = hw_sum = local_sum = 0.0; i < n_readings; i++) {
+ raw_delay = UTI_DiffTimespecsToDouble(&tss[i][2], &tss[i][0]);
+ delay = freq * raw_delay;
+
+ if (delay < low_delay || delay > high_delay)
+ continue;
+
+ delay_sum += delay;
+ hw_sum += UTI_DiffTimespecsToDouble(&tss[i][1], &tss[0][1]);
+ local_sum += UTI_DiffTimespecsToDouble(&tss[i][0], &tss[0][0]) + raw_delay / 2.0;
+ combined++;
+ }
+
+ DEBUG_LOG("Combined %d readings lo=%e hi=%e", combined, low_delay, high_delay);
+
+ if (combined > 0) {
+ UTI_AddDoubleToTimespec(&tss[0][1], hw_sum / combined, hw_ts);
+ UTI_AddDoubleToTimespec(&tss[0][0], local_sum / combined, local_ts);
+ *err = MAX(delay_sum / combined / 2.0, clock->precision);
+ return 1;
+ }
+
+ /* Accept the reading with minimum delay if its interval does not contain
+ the current offset predicted from previous samples */
+
+ *hw_ts = tss[min_reading][1];
+ UTI_AddDoubleToTimespec(&tss[min_reading][0], min_delay / freq / 2.0, local_ts);
+ *err = MAX(min_delay / 2.0, clock->precision);
+
+ pred_err = 0.0;
+ LCL_CookTime(local_ts, &ts1, NULL);
+ if (!HCL_CookTime(clock, hw_ts, &ts2, &e) ||
+ ((pred_err = UTI_DiffTimespecsToDouble(&ts1, &ts2)) > *err)) {
+ DEBUG_LOG("Accepted reading err=%e prerr=%e", *err, pred_err);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ================================================== */
+
+void
+HCL_AccumulateSample(HCL_Instance clock, struct timespec *hw_ts,
+ struct timespec *local_ts, double err)
+{
+ double hw_delta, local_delta, local_freq, raw_freq;
+ int i, n_runs, best_start;
+
+ local_freq = 1.0 - LCL_ReadAbsoluteFrequency() / 1.0e6;
+
+ /* Shift old samples */
+ if (clock->n_samples) {
+ if (clock->n_samples >= clock->max_samples)
+ clock->n_samples--;
+
+ hw_delta = UTI_DiffTimespecsToDouble(hw_ts, &clock->hw_ref);
+ local_delta = UTI_DiffTimespecsToDouble(local_ts, &clock->local_ref) / local_freq;
+
+ if (hw_delta <= 0.0 || local_delta < clock->min_separation / 2.0) {
+ clock->n_samples = 0;
+ DEBUG_LOG("HW clock reset interval=%f", local_delta);
+ }
+
+ for (i = clock->max_samples - clock->n_samples; i < clock->max_samples; i++) {
+ clock->y_data[i - 1] = clock->y_data[i] - hw_delta;
+ clock->x_data[i - 1] = clock->x_data[i] - local_delta;
+ }
+ }
+
+ clock->n_samples++;
+ clock->hw_ref = *hw_ts;
+ clock->local_ref = *local_ts;
+ clock->last_err = err;
+
+ /* Get new coefficients */
+ clock->valid_coefs =
+ RGR_FindBestRobustRegression(clock->x_data + clock->max_samples - clock->n_samples,
+ clock->y_data + clock->max_samples - clock->n_samples,
+ clock->n_samples, 1.0e-10, &clock->offset, &raw_freq,
+ &n_runs, &best_start);
+
+ if (!clock->valid_coefs) {
+ DEBUG_LOG("HW clock needs more samples");
+ return;
+ }
+
+ clock->frequency = raw_freq / local_freq;
+
+ /* Drop unneeded samples */
+ if (clock->n_samples > clock->min_samples)
+ clock->n_samples -= MIN(best_start, clock->n_samples - clock->min_samples);
+
+ /* If the fit doesn't cross the error interval of the last sample,
+ or the frequency is not sane, drop all samples and start again */
+ if (fabs(clock->offset) > err ||
+ fabs(clock->frequency - 1.0) > MAX_FREQ_OFFSET) {
+ DEBUG_LOG("HW clock reset");
+ clock->n_samples = 0;
+ clock->valid_coefs = 0;
+ }
+
+ DEBUG_LOG("HW clock samples=%d offset=%e freq=%e raw_freq=%e err=%e ref_diff=%e",
+ clock->n_samples, clock->offset, clock->frequency - 1.0, raw_freq - 1.0, err,
+ UTI_DiffTimespecsToDouble(&clock->hw_ref, &clock->local_ref));
+}
+
+/* ================================================== */
+
+int
+HCL_CookTime(HCL_Instance clock, struct timespec *raw, struct timespec *cooked, double *err)
+{
+ double offset, elapsed;
+
+ if (!clock->valid_coefs)
+ return 0;
+
+ elapsed = UTI_DiffTimespecsToDouble(raw, &clock->hw_ref);
+ offset = elapsed / clock->frequency - clock->offset;
+ UTI_AddDoubleToTimespec(&clock->local_ref, offset, cooked);
+
+ /* Fow now, just return the error of the last sample */
+ if (err)
+ *err = clock->last_err;
+
+ return 1;
+}
diff --git a/hwclock.h b/hwclock.h
new file mode 100644
index 0000000..c3415ad
--- /dev/null
+++ b/hwclock.h
@@ -0,0 +1,54 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * 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 tracking of hardware clocks */
+
+#ifndef GOT_HWCLOCK_H
+#define GOT_HWCLOCK_H
+
+typedef struct HCL_Instance_Record *HCL_Instance;
+
+/* Create a new HW clock instance */
+extern HCL_Instance HCL_CreateInstance(int min_samples, int max_samples,
+ double min_separation, double precision);
+
+/* Destroy a HW clock instance */
+extern void HCL_DestroyInstance(HCL_Instance clock);
+
+/* Check if a new sample should be accumulated at this time */
+extern int HCL_NeedsNewSample(HCL_Instance clock, struct timespec *now);
+
+/* Process new readings of the HW clock in form of (sys, hw, sys) triplets and
+ produce a sample which can be accumulated */
+extern int HCL_ProcessReadings(HCL_Instance clock, int n_readings, struct timespec tss[][3],
+ struct timespec *hw_ts, struct timespec *local_ts, double *err);
+
+/* Accumulate a new sample */
+extern void HCL_AccumulateSample(HCL_Instance clock, struct timespec *hw_ts,
+ struct timespec *local_ts, double err);
+
+/* Convert raw hardware time to cooked local time */
+extern int HCL_CookTime(HCL_Instance clock, struct timespec *raw, struct timespec *cooked,
+ double *err);
+
+#endif
diff --git a/keys.c b/keys.c
new file mode 100644
index 0000000..9225e6c
--- /dev/null
+++ b/keys.c
@@ -0,0 +1,441 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2012-2016, 2019-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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Module for managing keys used for authenticating NTP packets and commands
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "keys.h"
+#include "cmac.h"
+#include "cmdparse.h"
+#include "conf.h"
+#include "memory.h"
+#include "util.h"
+#include "local.h"
+#include "logging.h"
+
+/* 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;
+ int type;
+ int length;
+ KeyClass class;
+ union {
+ struct {
+ unsigned char *value;
+ int hash_id;
+ } ntp_mac;
+ CMC_Instance cmac;
+ } data;
+} Key;
+
+static ARR_Instance keys;
+
+static int cache_valid;
+static uint32_t cache_key_id;
+static int cache_key_pos;
+
+/* ================================================== */
+
+static void
+free_keys(void)
+{
+ unsigned int i;
+ Key *key;
+
+ 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;
+}
+
+/* ================================================== */
+
+void
+KEY_Initialise(void)
+{
+ keys = ARR_CreateInstance(sizeof (Key));
+ cache_valid = 0;
+ KEY_Reload();
+}
+
+/* ================================================== */
+
+void
+KEY_Finalise(void)
+{
+ free_keys();
+ ARR_DestroyInstance(keys);
+}
+
+/* ================================================== */
+
+static Key *
+get_key(unsigned int index)
+{
+ return ((Key *)ARR_GetElements(keys)) + index;
+}
+
+/* ================================================== */
+/* Decode key encoded in ASCII or HEX */
+
+static int
+decode_key(char *key)
+{
+ int len = strlen(key);
+
+ if (!strncmp(key, "ASCII:", 6)) {
+ memmove(key, key + 6, len - 6);
+ return len - 6;
+ } else if (!strncmp(key, "HEX:", 4)) {
+ return UTI_HexToBytes(key + 4, key, len);
+ } else {
+ /* assume ASCII */
+ return len;
+ }
+}
+
+/* ================================================== */
+
+/* Compare two keys */
+
+static int
+compare_keys_by_id(const void *a, const void *b)
+{
+ const Key *c = (const Key *) a;
+ const Key *d = (const Key *) b;
+
+ if (c->id < d->id) {
+ return -1;
+ } else if (c->id > d->id) {
+ return +1;
+ } else {
+ return 0;
+ }
+
+}
+
+/* ================================================== */
+
+void
+KEY_Reload(void)
+{
+ unsigned int i, line_number, key_length, cmac_key_length;
+ FILE *in;
+ char line[2048], *key_file, *key_value;
+ const char *key_type;
+ HSH_Algorithm hash_algorithm;
+ CMC_Algorithm cmac_algorithm;
+ int hash_id;
+ Key key;
+
+ free_keys();
+
+ key_file = CNF_GetKeysFile();
+ line_number = 0;
+
+ if (!key_file)
+ return;
+
+ if (!UTI_CheckFilePermissions(key_file, 0771))
+ ;
+
+ in = UTI_OpenFile(NULL, key_file, NULL, 'r', 0);
+ if (!in) {
+ LOG(LOGS_WARN, "Could not open keyfile %s", key_file);
+ return;
+ }
+
+ while (fgets(line, sizeof (line), in)) {
+ line_number++;
+
+ CPS_NormalizeLine(line);
+ if (!*line)
+ continue;
+
+ 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_length = decode_key(key_value);
+ if (key_length == 0) {
+ LOG(LOGS_WARN, "Could not decode key %"PRIu32, key.id);
+ continue;
+ }
+
+ hash_algorithm = UTI_HashNameToAlgorithm(key_type);
+ cmac_algorithm = UTI_CmacNameToAlgorithm(key_type);
+
+ if (hash_algorithm != 0) {
+ hash_id = HSH_GetHashId(hash_algorithm);
+ if (hash_id < 0) {
+ LOG(LOGS_WARN, "Unsupported %s in key %"PRIu32, "hash function", key.id);
+ continue;
+ }
+ key.class = NTP_MAC;
+ key.type = hash_algorithm;
+ key.length = key_length;
+ key.data.ntp_mac.value = MallocArray(unsigned char, key_length);
+ memcpy(key.data.ntp_mac.value, key_value, key_length);
+ key.data.ntp_mac.hash_id = hash_id;
+ } else if (cmac_algorithm != 0) {
+ cmac_key_length = CMC_GetKeyLength(cmac_algorithm);
+ if (cmac_key_length == 0) {
+ LOG(LOGS_WARN, "Unsupported %s in key %"PRIu32, "cipher", key.id);
+ continue;
+ } else 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.type = cmac_algorithm;
+ key.length = key_length;
+ key.data.cmac = CMC_CreateInstance(cmac_algorithm, (unsigned char *)key_value,
+ key_length);
+ assert(key.data.cmac);
+ } else {
+ LOG(LOGS_WARN, "Invalid type in key %"PRIu32, key.id);
+ continue;
+ }
+
+ ARR_AppendElement(keys, &key);
+ }
+
+ fclose(in);
+
+ /* Sort keys into order. Note, if there's a duplicate, it is
+ arbitrary which one we use later - the user should have been
+ more careful! */
+ qsort(ARR_GetElements(keys), ARR_GetSize(keys), sizeof (Key), compare_keys_by_id);
+
+ LOG(LOGS_INFO, "Loaded %u symmetric keys", ARR_GetSize(keys));
+
+ /* Check for duplicates */
+ for (i = 1; i < ARR_GetSize(keys); i++) {
+ if (get_key(i - 1)->id == get_key(i)->id)
+ LOG(LOGS_WARN, "Detected duplicate key %"PRIu32, get_key(i - 1)->id);
+ }
+
+ /* Erase any passwords from stack */
+ memset(line, 0, sizeof (line));
+}
+
+/* ================================================== */
+
+static int
+lookup_key(uint32_t id)
+{
+ Key specimen, *where, *keys_ptr;
+ int pos;
+
+ keys_ptr = ARR_GetElements(keys);
+ specimen.id = id;
+ where = (Key *)bsearch((void *)&specimen, keys_ptr, ARR_GetSize(keys),
+ sizeof (Key), compare_keys_by_id);
+ if (!where) {
+ return -1;
+ } else {
+ pos = where - keys_ptr;
+ return pos;
+ }
+}
+
+/* ================================================== */
+
+static Key *
+get_key_by_id(uint32_t key_id)
+{
+ int position;
+
+ if (cache_valid && key_id == cache_key_id)
+ return get_key(cache_key_pos);
+
+ position = lookup_key(key_id);
+
+ if (position >= 0) {
+ cache_valid = 1;
+ cache_key_pos = position;
+ cache_key_id = key_id;
+
+ return get_key(position);
+ }
+
+ return NULL;
+}
+
+/* ================================================== */
+
+int
+KEY_KeyKnown(uint32_t key_id)
+{
+ return get_key_by_id(key_id) != NULL;
+}
+
+/* ================================================== */
+
+int
+KEY_GetAuthLength(uint32_t key_id)
+{
+ unsigned char buf[MAX_HASH_LENGTH];
+ Key *key;
+
+ key = get_key_by_id(key_id);
+
+ if (!key)
+ return 0;
+
+ 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;
+ }
+}
+
+/* ================================================== */
+
+int
+KEY_CheckKeyLength(uint32_t key_id)
+{
+ Key *key;
+
+ key = get_key_by_id(key_id);
+
+ if (!key)
+ return 0;
+
+ return key->length >= MIN_SECURE_KEY_LENGTH;
+}
+
+/* ================================================== */
+
+int
+KEY_GetKeyInfo(uint32_t key_id, int *type, int *bits)
+{
+ Key *key;
+
+ key = get_key_by_id(key_id);
+
+ if (!key)
+ return 0;
+
+ *type = key->type;
+ *bits = 8 * key->length;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+generate_auth(Key *key, const void *data, int data_len, unsigned char *auth, int auth_len)
+{
+ switch (key->class) {
+ case NTP_MAC:
+ return HSH_Hash(key->data.ntp_mac.hash_id, key->data.ntp_mac.value,
+ key->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_auth(Key *key, const void *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_auth(key, data, data_len, buf, sizeof (buf));
+
+ return MIN(hash_len, trunc_len) == auth_len && !memcmp(buf, auth, auth_len);
+}
+
+/* ================================================== */
+
+int
+KEY_GenerateAuth(uint32_t key_id, const void *data, int data_len,
+ unsigned char *auth, int auth_len)
+{
+ Key *key;
+
+ key = get_key_by_id(key_id);
+
+ if (!key)
+ return 0;
+
+ return generate_auth(key, data, data_len, auth, auth_len);
+}
+
+/* ================================================== */
+
+int
+KEY_CheckAuth(uint32_t key_id, const void *data, int data_len,
+ const unsigned char *auth, int auth_len, int trunc_len)
+{
+ Key *key;
+
+ key = get_key_by_id(key_id);
+
+ if (!key)
+ return 0;
+
+ return check_auth(key, data, data_len, auth, auth_len, trunc_len);
+}
diff --git a/keys.h b/keys.h
new file mode 100644
index 0000000..c89fea9
--- /dev/null
+++ b/keys.h
@@ -0,0 +1,47 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 key management module
+ */
+
+#ifndef GOT_KEYS_H
+#define GOT_KEYS_H
+
+#include "sysincl.h"
+
+extern void KEY_Initialise(void);
+extern void KEY_Finalise(void);
+
+extern void KEY_Reload(void);
+
+extern int KEY_KeyKnown(uint32_t key_id);
+extern int KEY_GetAuthLength(uint32_t key_id);
+extern int KEY_CheckKeyLength(uint32_t key_id);
+extern int KEY_GetKeyInfo(uint32_t key_id, int *type, int *bits);
+
+extern int KEY_GenerateAuth(uint32_t key_id, const void *data, int data_len,
+ unsigned char *auth, int auth_len);
+extern int KEY_CheckAuth(uint32_t key_id, const void *data, int data_len,
+ const unsigned char *auth, int auth_len, int trunc_len);
+
+#endif /* GOT_KEYS_H */
diff --git a/local.c b/local.c
new file mode 100644
index 0000000..b32d0c6
--- /dev/null
+++ b/local.c
@@ -0,0 +1,781 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2011, 2014-2015
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ The routines in this file present a common local (system) clock
+ interface to the rest of the software.
+
+ They interface with the system specific driver files in sys_*.c
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "conf.h"
+#include "local.h"
+#include "localp.h"
+#include "memory.h"
+#include "smooth.h"
+#include "util.h"
+#include "logging.h"
+
+/* ================================================== */
+
+/* Variable to store the current frequency, in ppm */
+static double current_freq_ppm;
+
+/* Maximum allowed frequency, in ppm */
+static double max_freq_ppm;
+
+/* Temperature compensation, in ppm */
+static double temp_comp_ppm;
+
+/* ================================================== */
+/* Store the system dependent drivers */
+
+static lcl_ReadFrequencyDriver drv_read_freq;
+static lcl_SetFrequencyDriver drv_set_freq;
+static lcl_AccrueOffsetDriver drv_accrue_offset;
+static lcl_ApplyStepOffsetDriver drv_apply_step_offset;
+static lcl_OffsetCorrectionDriver drv_offset_convert;
+static lcl_SetLeapDriver drv_set_leap;
+static lcl_SetSyncStatusDriver drv_set_sync_status;
+
+/* ================================================== */
+
+/* Types and variables associated with handling the parameter change
+ list */
+
+typedef struct _ChangeListEntry {
+ struct _ChangeListEntry *next;
+ struct _ChangeListEntry *prev;
+ LCL_ParameterChangeHandler handler;
+ void *anything;
+} ChangeListEntry;
+
+static ChangeListEntry change_list;
+
+/* ================================================== */
+
+/* Types and variables associated with handling the parameter change
+ list */
+
+typedef struct _DispersionNotifyListEntry {
+ struct _DispersionNotifyListEntry *next;
+ struct _DispersionNotifyListEntry *prev;
+ LCL_DispersionNotifyHandler handler;
+ void *anything;
+} DispersionNotifyListEntry;
+
+static DispersionNotifyListEntry dispersion_notify_list;
+
+/* ================================================== */
+
+static int precision_log;
+static double precision_quantum;
+
+static double max_clock_error;
+
+/* ================================================== */
+
+/* Define the number of increments of the system clock that we want
+ to see to be fairly sure that we've got something approaching
+ the minimum increment. Even on a crummy implementation that can't
+ interpolate between 10ms ticks, we should get this done in
+ under 1s of busy waiting. */
+#define NITERS 100
+
+#define NSEC_PER_SEC 1000000000
+
+static double
+measure_clock_precision(void)
+{
+ struct timespec ts, old_ts;
+ int iters, diff, best;
+
+ LCL_ReadRawTime(&old_ts);
+
+ /* Assume we must be better than a second */
+ best = NSEC_PER_SEC;
+ iters = 0;
+
+ do {
+ LCL_ReadRawTime(&ts);
+
+ diff = NSEC_PER_SEC * (ts.tv_sec - old_ts.tv_sec) + (ts.tv_nsec - old_ts.tv_nsec);
+
+ old_ts = ts;
+ if (diff > 0) {
+ if (diff < best)
+ best = diff;
+ iters++;
+ }
+ } while (iters < NITERS);
+
+ assert(best > 0);
+
+ return 1.0e-9 * best;
+}
+
+/* ================================================== */
+
+void
+LCL_Initialise(void)
+{
+ change_list.next = change_list.prev = &change_list;
+
+ dispersion_notify_list.next = dispersion_notify_list.prev = &dispersion_notify_list;
+
+ /* Null out the system drivers, so that we die
+ if they never get defined before use */
+
+ drv_read_freq = NULL;
+ drv_set_freq = NULL;
+ drv_accrue_offset = NULL;
+ drv_offset_convert = NULL;
+
+ /* This ought to be set from the system driver layer */
+ current_freq_ppm = 0.0;
+ temp_comp_ppm = 0.0;
+
+ precision_quantum = CNF_GetClockPrecision();
+ if (precision_quantum <= 0.0)
+ precision_quantum = measure_clock_precision();
+
+ precision_quantum = CLAMP(1.0e-9, precision_quantum, 1.0);
+ precision_log = round(log(precision_quantum) / log(2.0));
+ /* NTP code doesn't support smaller log than -30 */
+ assert(precision_log >= -30);
+
+ DEBUG_LOG("Clock precision %.9f (%d)", precision_quantum, precision_log);
+
+ /* This is the maximum allowed frequency offset in ppm, the time must
+ never stop or run backwards */
+ max_freq_ppm = CNF_GetMaxDrift();
+ max_freq_ppm = CLAMP(0.0, max_freq_ppm, 500000.0);
+
+ max_clock_error = CNF_GetMaxClockError() * 1e-6;
+}
+
+/* ================================================== */
+
+void
+LCL_Finalise(void)
+{
+ /* Make sure all handlers have been removed */
+ if (change_list.next != &change_list)
+ assert(0);
+ if (dispersion_notify_list.next != &dispersion_notify_list)
+ assert(0);
+}
+
+/* ================================================== */
+
+/* Routine to read the system precision as a log to base 2 value. */
+int
+LCL_GetSysPrecisionAsLog(void)
+{
+ return precision_log;
+}
+
+/* ================================================== */
+/* Routine to read the system precision in terms of the actual time step */
+
+double
+LCL_GetSysPrecisionAsQuantum(void)
+{
+ return precision_quantum;
+}
+
+/* ================================================== */
+
+double
+LCL_GetMaxClockError(void)
+{
+ return max_clock_error;
+}
+
+/* ================================================== */
+
+void
+LCL_AddParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
+{
+ ChangeListEntry *ptr, *new_entry;
+
+ /* Check that the handler is not already registered */
+ for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+ if (!(ptr->handler != handler || ptr->anything != anything)) {
+ assert(0);
+ }
+ }
+
+ new_entry = MallocNew(ChangeListEntry);
+
+ new_entry->handler = handler;
+ new_entry->anything = anything;
+
+ /* Chain it into the list */
+ new_entry->next = &change_list;
+ new_entry->prev = change_list.prev;
+ change_list.prev->next = new_entry;
+ change_list.prev = new_entry;
+}
+
+/* ================================================== */
+
+/* Remove a handler */
+void LCL_RemoveParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
+{
+
+ ChangeListEntry *ptr;
+ int ok;
+
+ ptr = NULL;
+ ok = 0;
+
+ for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+ if (ptr->handler == handler && ptr->anything == anything) {
+ ok = 1;
+ break;
+ }
+ }
+
+ assert(ok);
+
+ /* Unlink entry from the list */
+ ptr->next->prev = ptr->prev;
+ ptr->prev->next = ptr->next;
+
+ Free(ptr);
+}
+
+/* ================================================== */
+
+int
+LCL_IsFirstParameterChangeHandler(LCL_ParameterChangeHandler handler)
+{
+ return change_list.next->handler == handler;
+}
+
+/* ================================================== */
+
+static void
+invoke_parameter_change_handlers(struct timespec *raw, struct timespec *cooked,
+ double dfreq, double doffset,
+ LCL_ChangeType change_type)
+{
+ ChangeListEntry *ptr;
+
+ for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
+ (ptr->handler)(raw, cooked, dfreq, doffset, change_type, ptr->anything);
+ }
+}
+
+/* ================================================== */
+
+void
+LCL_AddDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
+{
+ DispersionNotifyListEntry *ptr, *new_entry;
+
+ /* Check that the handler is not already registered */
+ for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
+ if (!(ptr->handler != handler || ptr->anything != anything)) {
+ assert(0);
+ }
+ }
+
+ new_entry = MallocNew(DispersionNotifyListEntry);
+
+ new_entry->handler = handler;
+ new_entry->anything = anything;
+
+ /* Chain it into the list */
+ new_entry->next = &dispersion_notify_list;
+ new_entry->prev = dispersion_notify_list.prev;
+ dispersion_notify_list.prev->next = new_entry;
+ dispersion_notify_list.prev = new_entry;
+}
+
+/* ================================================== */
+
+/* Remove a handler */
+extern
+void LCL_RemoveDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
+{
+
+ DispersionNotifyListEntry *ptr;
+ int ok;
+
+ ptr = NULL;
+ ok = 0;
+
+ for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
+ if (ptr->handler == handler && ptr->anything == anything) {
+ ok = 1;
+ break;
+ }
+ }
+
+ assert(ok);
+
+ /* Unlink entry from the list */
+ ptr->next->prev = ptr->prev;
+ ptr->prev->next = ptr->next;
+
+ Free(ptr);
+}
+
+/* ================================================== */
+
+void
+LCL_ReadRawTime(struct timespec *ts)
+{
+#if HAVE_CLOCK_GETTIME
+ if (clock_gettime(CLOCK_REALTIME, ts) < 0)
+ LOG_FATAL("clock_gettime() failed : %s", strerror(errno));
+#else
+ struct timeval tv;
+
+ if (gettimeofday(&tv, NULL) < 0)
+ LOG_FATAL("gettimeofday() failed : %s", strerror(errno));
+
+ UTI_TimevalToTimespec(&tv, ts);
+#endif
+}
+
+/* ================================================== */
+
+void
+LCL_ReadCookedTime(struct timespec *result, double *err)
+{
+ struct timespec raw;
+
+ LCL_ReadRawTime(&raw);
+ LCL_CookTime(&raw, result, err);
+}
+
+/* ================================================== */
+
+void
+LCL_CookTime(struct timespec *raw, struct timespec *cooked, double *err)
+{
+ double correction;
+
+ LCL_GetOffsetCorrection(raw, &correction, err);
+ UTI_AddDoubleToTimespec(raw, correction, cooked);
+}
+
+/* ================================================== */
+
+void
+LCL_GetOffsetCorrection(struct timespec *raw, double *correction, double *err)
+{
+ /* Call system specific driver to get correction */
+ (*drv_offset_convert)(raw, correction, err);
+}
+
+/* ================================================== */
+/* Return current frequency */
+
+double
+LCL_ReadAbsoluteFrequency(void)
+{
+ double freq;
+
+ freq = current_freq_ppm;
+
+ /* Undo temperature compensation */
+ if (temp_comp_ppm != 0.0) {
+ freq = (freq + temp_comp_ppm) / (1.0 - 1.0e-6 * temp_comp_ppm);
+ }
+
+ return freq;
+}
+
+/* ================================================== */
+
+static double
+clamp_freq(double freq)
+{
+ if (freq <= max_freq_ppm && freq >= -max_freq_ppm)
+ return freq;
+
+ LOG(LOGS_WARN, "Frequency %.1f ppm exceeds allowed maximum", freq);
+
+ return CLAMP(-max_freq_ppm, freq, max_freq_ppm);
+}
+
+/* ================================================== */
+
+static int
+check_offset(struct timespec *now, double offset)
+{
+ /* Check if the time will be still sane with accumulated offset */
+ if (UTI_IsTimeOffsetSane(now, -offset))
+ return 1;
+
+ LOG(LOGS_WARN, "Adjustment of %.1f seconds is invalid", -offset);
+ return 0;
+}
+
+/* ================================================== */
+
+/* This involves both setting the absolute frequency with the
+ system-specific driver, as well as calling all notify handlers */
+
+void
+LCL_SetAbsoluteFrequency(double afreq_ppm)
+{
+ struct timespec raw, cooked;
+ double dfreq;
+
+ afreq_ppm = clamp_freq(afreq_ppm);
+
+ /* Apply temperature compensation */
+ if (temp_comp_ppm != 0.0) {
+ afreq_ppm = afreq_ppm * (1.0 - 1.0e-6 * temp_comp_ppm) - temp_comp_ppm;
+ }
+
+ /* Call the system-specific driver for setting the frequency */
+
+ afreq_ppm = (*drv_set_freq)(afreq_ppm);
+
+ dfreq = (afreq_ppm - current_freq_ppm) / (1.0e6 - current_freq_ppm);
+
+ LCL_ReadRawTime(&raw);
+ LCL_CookTime(&raw, &cooked, NULL);
+
+ /* Dispatch to all handlers */
+ invoke_parameter_change_handlers(&raw, &cooked, dfreq, 0.0, LCL_ChangeAdjust);
+
+ current_freq_ppm = afreq_ppm;
+
+}
+
+/* ================================================== */
+
+void
+LCL_AccumulateDeltaFrequency(double dfreq)
+{
+ struct timespec raw, cooked;
+ double old_freq_ppm;
+
+ old_freq_ppm = current_freq_ppm;
+
+ /* Work out new absolute frequency. Note that absolute frequencies
+ are handled in units of ppm, whereas the 'dfreq' argument is in
+ terms of the gradient of the (offset) v (local time) function. */
+
+ current_freq_ppm += dfreq * (1.0e6 - current_freq_ppm);
+
+ current_freq_ppm = clamp_freq(current_freq_ppm);
+
+ /* Call the system-specific driver for setting the frequency */
+ current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
+ dfreq = (current_freq_ppm - old_freq_ppm) / (1.0e6 - old_freq_ppm);
+
+ LCL_ReadRawTime(&raw);
+ LCL_CookTime(&raw, &cooked, NULL);
+
+ /* Dispatch to all handlers */
+ invoke_parameter_change_handlers(&raw, &cooked, dfreq, 0.0, LCL_ChangeAdjust);
+}
+
+/* ================================================== */
+
+int
+LCL_AccumulateOffset(double offset, double corr_rate)
+{
+ struct timespec raw, cooked;
+
+ /* In this case, the cooked time to be passed to the notify clients
+ has to be the cooked time BEFORE the change was made */
+
+ LCL_ReadRawTime(&raw);
+ LCL_CookTime(&raw, &cooked, NULL);
+
+ if (!check_offset(&cooked, offset))
+ return 0;
+
+ (*drv_accrue_offset)(offset, corr_rate);
+
+ /* Dispatch to all handlers */
+ invoke_parameter_change_handlers(&raw, &cooked, 0.0, offset, LCL_ChangeAdjust);
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+LCL_ApplyStepOffset(double offset)
+{
+ struct timespec raw, cooked;
+
+ /* In this case, the cooked time to be passed to the notify clients
+ has to be the cooked time BEFORE the change was made */
+
+ LCL_ReadRawTime(&raw);
+ LCL_CookTime(&raw, &cooked, NULL);
+
+ if (!check_offset(&raw, offset))
+ return 0;
+
+ if (!(*drv_apply_step_offset)(offset)) {
+ LOG(LOGS_ERR, "Could not step system clock");
+ return 0;
+ }
+
+ /* Reset smoothing on all clock steps */
+ SMT_Reset(&cooked);
+
+ /* Dispatch to all handlers */
+ invoke_parameter_change_handlers(&raw, &cooked, 0.0, offset, LCL_ChangeStep);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+LCL_NotifyExternalTimeStep(struct timespec *raw, struct timespec *cooked,
+ double offset, double dispersion)
+{
+ LCL_CancelOffsetCorrection();
+
+ /* Dispatch to all handlers */
+ invoke_parameter_change_handlers(raw, cooked, 0.0, offset, LCL_ChangeUnknownStep);
+
+ lcl_InvokeDispersionNotifyHandlers(dispersion);
+}
+
+/* ================================================== */
+
+void
+LCL_NotifyLeap(int leap)
+{
+ struct timespec raw, cooked;
+
+ LCL_ReadRawTime(&raw);
+ LCL_CookTime(&raw, &cooked, NULL);
+
+ /* Smooth the leap second out */
+ SMT_Leap(&cooked, leap);
+
+ /* Dispatch to all handlers as if the clock was stepped */
+ invoke_parameter_change_handlers(&raw, &cooked, 0.0, -leap, LCL_ChangeStep);
+}
+
+/* ================================================== */
+
+int
+LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset, double corr_rate)
+{
+ struct timespec raw, cooked;
+ double old_freq_ppm;
+
+ LCL_ReadRawTime(&raw);
+ /* Due to modifying the offset, this has to be the cooked time prior
+ to the change we are about to make */
+ LCL_CookTime(&raw, &cooked, NULL);
+
+ if (!check_offset(&cooked, doffset))
+ return 0;
+
+ old_freq_ppm = current_freq_ppm;
+
+ /* Work out new absolute frequency. Note that absolute frequencies
+ are handled in units of ppm, whereas the 'dfreq' argument is in
+ terms of the gradient of the (offset) v (local time) function. */
+ current_freq_ppm += dfreq * (1.0e6 - current_freq_ppm);
+
+ current_freq_ppm = clamp_freq(current_freq_ppm);
+
+ DEBUG_LOG("old_freq=%.3fppm new_freq=%.3fppm offset=%.6fsec",
+ old_freq_ppm, current_freq_ppm, doffset);
+
+ /* Call the system-specific driver for setting the frequency */
+ current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
+ dfreq = (current_freq_ppm - old_freq_ppm) / (1.0e6 - old_freq_ppm);
+
+ (*drv_accrue_offset)(doffset, corr_rate);
+
+ /* Dispatch to all handlers */
+ invoke_parameter_change_handlers(&raw, &cooked, dfreq, doffset, LCL_ChangeAdjust);
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+LCL_AccumulateFrequencyAndOffsetNoHandlers(double dfreq, double doffset, double corr_rate)
+{
+ ChangeListEntry *first_handler;
+ int r;
+
+ first_handler = change_list.next;
+ change_list.next = &change_list;
+
+ r = LCL_AccumulateFrequencyAndOffset(dfreq, doffset, corr_rate);
+
+ change_list.next = first_handler;
+
+ return r;
+}
+
+/* ================================================== */
+
+void
+lcl_InvokeDispersionNotifyHandlers(double dispersion)
+{
+ DispersionNotifyListEntry *ptr;
+
+ for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
+ (ptr->handler)(dispersion, ptr->anything);
+ }
+
+}
+
+/* ================================================== */
+
+void
+lcl_RegisterSystemDrivers(lcl_ReadFrequencyDriver read_freq,
+ lcl_SetFrequencyDriver set_freq,
+ lcl_AccrueOffsetDriver accrue_offset,
+ lcl_ApplyStepOffsetDriver apply_step_offset,
+ lcl_OffsetCorrectionDriver offset_convert,
+ lcl_SetLeapDriver set_leap,
+ lcl_SetSyncStatusDriver set_sync_status)
+{
+ drv_read_freq = read_freq;
+ drv_set_freq = set_freq;
+ drv_accrue_offset = accrue_offset;
+ drv_apply_step_offset = apply_step_offset;
+ drv_offset_convert = offset_convert;
+ drv_set_leap = set_leap;
+ drv_set_sync_status = set_sync_status;
+
+ current_freq_ppm = (*drv_read_freq)();
+
+ DEBUG_LOG("Local freq=%.3fppm", current_freq_ppm);
+}
+
+/* ================================================== */
+/* Look at the current difference between the system time and the NTP
+ time, and make a step to cancel it. */
+
+int
+LCL_MakeStep(void)
+{
+ struct timespec raw;
+ double correction;
+
+ LCL_ReadRawTime(&raw);
+ LCL_GetOffsetCorrection(&raw, &correction, NULL);
+
+ if (!check_offset(&raw, -correction))
+ return 0;
+
+ /* Cancel remaining slew and make the step */
+ LCL_AccumulateOffset(correction, 0.0);
+ if (!LCL_ApplyStepOffset(-correction))
+ return 0;
+
+ LOG(LOGS_WARN, "System clock was stepped by %.6f seconds", correction);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+LCL_CancelOffsetCorrection(void)
+{
+ struct timespec raw;
+ double correction;
+
+ LCL_ReadRawTime(&raw);
+ LCL_GetOffsetCorrection(&raw, &correction, NULL);
+ LCL_AccumulateOffset(correction, 0.0);
+}
+
+/* ================================================== */
+
+int
+LCL_CanSystemLeap(void)
+{
+ return drv_set_leap ? 1 : 0;
+}
+
+/* ================================================== */
+
+void
+LCL_SetSystemLeap(int leap, int tai_offset)
+{
+ if (drv_set_leap) {
+ (drv_set_leap)(leap, tai_offset);
+ }
+}
+
+/* ================================================== */
+
+double
+LCL_SetTempComp(double comp)
+{
+ double uncomp_freq_ppm;
+
+ if (temp_comp_ppm == comp)
+ return comp;
+
+ /* Undo previous compensation */
+ current_freq_ppm = (current_freq_ppm + temp_comp_ppm) /
+ (1.0 - 1.0e-6 * temp_comp_ppm);
+
+ uncomp_freq_ppm = current_freq_ppm;
+
+ /* Apply new compensation */
+ current_freq_ppm = current_freq_ppm * (1.0 - 1.0e-6 * comp) - comp;
+
+ /* Call the system-specific driver for setting the frequency */
+ current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
+
+ temp_comp_ppm = (uncomp_freq_ppm - current_freq_ppm) /
+ (1.0e-6 * uncomp_freq_ppm + 1.0);
+
+ return temp_comp_ppm;
+}
+
+/* ================================================== */
+
+void
+LCL_SetSyncStatus(int synchronised, double est_error, double max_error)
+{
+ if (drv_set_sync_status) {
+ (drv_set_sync_status)(synchronised, est_error, max_error);
+ }
+}
+
+/* ================================================== */
diff --git a/local.h b/local.h
new file mode 100644
index 0000000..a23d275
--- /dev/null
+++ b/local.h
@@ -0,0 +1,229 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ This module provides an interface to the system time, and
+ insulates the rest of the program from the different way
+ that interface has to be done on various operating systems.
+ */
+
+#ifndef GOT_LOCAL_H
+#define GOT_LOCAL_H
+
+#include "sysincl.h"
+
+/* Read the system clock */
+extern void LCL_ReadRawTime(struct timespec *ts);
+
+/* Read the system clock, corrected according to all accumulated
+ drifts and uncompensated offsets.
+
+ In a kernel implementation with vernier frequency control (like
+ Linux), and if we were to apply offsets by stepping the clock, this
+ would be identical to raw time. In any other case (use of
+ adjtime()-like interface to correct offsets, and to adjust the
+ frequency), we must correct the raw time to get this value */
+
+extern void LCL_ReadCookedTime(struct timespec *ts, double *err);
+
+/* Convert raw time to cooked. */
+extern void LCL_CookTime(struct timespec *raw, struct timespec *cooked, double *err);
+
+/* Read the current offset between the system clock and true time
+ (i.e. 'cooked' - 'raw') (in seconds). */
+
+extern void LCL_GetOffsetCorrection(struct timespec *raw, double *correction, double *err);
+
+/* Type of routines that may be invoked as callbacks when there is a
+ change to the frequency or offset.
+
+ raw : raw local clock time at which change occurred
+
+ cooked : cooked local time at which change occurred
+
+ dfreq : delta frequency relative to previous value (in terms of
+ seconds gained by system clock per unit system clock time)
+
+ doffset : delta offset applied (positive => make local system fast
+ by that amount, negative => make it slow by that amount)
+
+ change_type : what type of change is being applied
+
+ anything : Passthrough argument from call to registration routine */
+
+
+typedef enum {
+ LCL_ChangeAdjust,
+ LCL_ChangeStep,
+ LCL_ChangeUnknownStep
+} LCL_ChangeType;
+
+typedef void (*LCL_ParameterChangeHandler)
+ (struct timespec *raw, struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything
+ );
+
+/* Add a handler. Then handler MUST NOT deregister itself!!! */
+extern void LCL_AddParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything);
+
+/* Remove a handler */
+extern void LCL_RemoveParameterChangeHandler(LCL_ParameterChangeHandler, void *anything);
+
+/* Check if a handler is invoked first when dispatching */
+extern int LCL_IsFirstParameterChangeHandler(LCL_ParameterChangeHandler handler);
+
+/* Function type for handlers to be called back when an indeterminate
+ offset is introduced into the local time. This situation occurs
+ when the frequency must be adjusted to effect a clock slew and
+ there is doubt about one of the endpoints of the interval over
+ which the frequency change was applied.It is expected that such
+ handlers will add extra dispersion to any existing samples stored
+ in their registers.
+
+ dispersion : The bound on how much error has been introduced in the
+ local clock, in seconds.
+
+ anything : passthrough from the registration routine
+
+ */
+
+typedef void (*LCL_DispersionNotifyHandler)(double dispersion, void *anything);
+
+/* Register a handler for being notified of dispersion being added to
+ the local clock. The handler MUST NOT unregister itself!!! */
+
+extern void LCL_AddDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything);
+
+/* Delete a handler */
+
+extern void LCL_RemoveDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything);
+
+
+/* Read the absolute system frequency, relative to the uncompensated
+ system. Returned in units of parts per million. Thus the result of
+ this is how many seconds fast the uncompensated system would be after
+ its own time has reached 1 million seconds from the start of the
+ measurement. */
+extern double LCL_ReadAbsoluteFrequency(void);
+
+/* Routine to set the absolute frequency. Only expected to be used
+ when either (i) reading the drift from a file at the start of a
+ run, or (ii) responsing to a user parameter 'poke'. This is
+ defined in ppm, as for the absolute frequency reading routine. */
+
+extern void LCL_SetAbsoluteFrequency(double afreq);
+
+/* Routine to apply a change of frequency to the local clock. The
+ argument is the estimated gain (positive) or loss (negative) of the
+ local clock relative to true time, per unit time of the PREVIOUS
+ frequency setting of the local clock. This is assumed to be based
+ on a regression of y=offset v x=cooked local time. */
+
+extern void LCL_AccumulateDeltaFrequency(double dfreq);
+
+/* Routine to apply an offset (in seconds) to the local clock. The
+ argument should be positive to move the clock backwards (i.e. the
+ local clock is currently fast of true time), or negative to move it
+ forwards (i.e. it is currently slow of true time). Provided is also
+ a suggested correction rate (correction time * offset). */
+
+extern int LCL_AccumulateOffset(double offset, double corr_rate);
+
+/* Routine to apply an immediate offset by doing a sudden step if
+ possible. (Intended for use after an initial estimate of offset has
+ been obtained, so that we don't end up using adjtime to achieve a
+ slew of an hour or something like that). A positive argument means
+ the system clock is fast on true time, i.e. it needs to be stepped
+ backwards. (Same convention as for AccumulateOffset routine). */
+
+extern int LCL_ApplyStepOffset(double offset);
+
+/* Routine to invoke notify handlers on an unexpected time jump
+ in system clock */
+extern void LCL_NotifyExternalTimeStep(struct timespec *raw, struct timespec *cooked,
+ double offset, double dispersion);
+
+/* Routine to invoke notify handlers on leap second when the system clock
+ doesn't correct itself */
+extern void LCL_NotifyLeap(int leap);
+
+/* Perform the combination of modifying the frequency and applying
+ a slew, in one easy step */
+extern int LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset, double corr_rate);
+
+/* Same as the routine above, except it does not call the registered
+ parameter change handlers */
+extern int LCL_AccumulateFrequencyAndOffsetNoHandlers(double dfreq, double doffset,
+ double corr_rate);
+
+/* Routine to read the system precision as a log to base 2 value. */
+extern int LCL_GetSysPrecisionAsLog(void);
+
+/* Routine to read the system precision in terms of the actual time step */
+extern double LCL_GetSysPrecisionAsQuantum(void);
+
+/* Routine to read the maximum frequency error of the local clock. This
+ is a frequency stability, not an absolute error. */
+extern double LCL_GetMaxClockError(void);
+
+/* Routine to initialise the module (to be called once at program
+ start-up) */
+
+extern void LCL_Initialise(void);
+
+/* Routine to finalise the module (to be called once at end of
+ run). */
+extern void LCL_Finalise(void);
+
+/* Routine to convert the outstanding system clock error to a step and
+ apply it, e.g. if the system clock has ended up an hour wrong due
+ to a timezone problem. */
+extern int LCL_MakeStep(void);
+
+/* Routine to cancel the outstanding system clock correction */
+extern void LCL_CancelOffsetCorrection(void);
+
+/* Check if the system driver supports leap seconds, i.e. LCL_SetSystemLeap
+ does something */
+extern int LCL_CanSystemLeap(void);
+
+/* Routine to set the system clock to correct itself for a leap second and also
+ set its TAI-UTC offset. If supported, leap second will be inserted at the
+ end of the day if the argument is positive, deleted if negative, and zero
+ resets the setting. */
+extern void LCL_SetSystemLeap(int leap, int tai_offset);
+
+/* Routine to set a frequency correction (in ppm) that should be applied
+ to local clock to compensate for temperature changes. A positive
+ argument means that the clock frequency should be increased. Return the
+ actual compensation (may be different from the requested compensation
+ due to clamping or rounding). */
+extern double LCL_SetTempComp(double comp);
+
+/* Routine to update the synchronisation status in the kernel to allow other
+ applications to know if the system clock is synchronised and error bounds */
+extern void LCL_SetSyncStatus(int synchronised, double est_error, double max_error);
+
+#endif /* GOT_LOCAL_H */
diff --git a/localp.h b/localp.h
new file mode 100644
index 0000000..6f65e43
--- /dev/null
+++ b/localp.h
@@ -0,0 +1,74 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Private include file for local.c and all system dependent
+ driver modules.
+ */
+
+
+#ifndef GOT_LOCALP_H
+#define GOT_LOCALP_H
+
+/* System driver to read the current local frequency, in ppm relative
+ to nominal. A positive value indicates that the local clock runs
+ fast when uncompensated. */
+typedef double (*lcl_ReadFrequencyDriver)(void);
+
+/* System driver to set the current local frequency, in ppm relative
+ to nominal. A positive value indicates that the local clock runs
+ fast when uncompensated. Return actual frequency (may be different
+ from the requested frequency due to clamping or rounding). */
+typedef double (*lcl_SetFrequencyDriver)(double freq_ppm);
+
+/* System driver to accrue an offset. A positive argument means slew
+ the clock forwards. The suggested correction rate of time to correct the
+ offset is given in 'corr_rate'. */
+typedef void (*lcl_AccrueOffsetDriver)(double offset, double corr_rate);
+
+/* System driver to apply a step offset. A positive argument means step
+ the clock forwards. */
+typedef int (*lcl_ApplyStepOffsetDriver)(double offset);
+
+/* System driver to convert a raw time to an adjusted (cooked) time.
+ The number of seconds returned in 'corr' have to be added to the
+ raw time to get the corrected time */
+typedef void (*lcl_OffsetCorrectionDriver)(struct timespec *raw, double *corr, double *err);
+
+/* System driver to schedule leap seconds and set TAI-UTC offset */
+typedef void (*lcl_SetLeapDriver)(int leap, int tai_offset);
+
+/* System driver to set the synchronisation status */
+typedef void (*lcl_SetSyncStatusDriver)(int synchronised, double est_error, double max_error);
+
+extern void lcl_InvokeDispersionNotifyHandlers(double dispersion);
+
+extern void
+lcl_RegisterSystemDrivers(lcl_ReadFrequencyDriver read_freq,
+ lcl_SetFrequencyDriver set_freq,
+ lcl_AccrueOffsetDriver accrue_offset,
+ lcl_ApplyStepOffsetDriver apply_step_offset,
+ lcl_OffsetCorrectionDriver offset_convert,
+ lcl_SetLeapDriver set_leap,
+ lcl_SetSyncStatusDriver set_sync_status);
+
+#endif /* GOT_LOCALP_H */
diff --git a/logging.c b/logging.c
new file mode 100644
index 0000000..ec9ec53
--- /dev/null
+++ b/logging.c
@@ -0,0 +1,387 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2011-2014, 2018-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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Module to handle logging of diagnostic information
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <syslog.h>
+
+#include "conf.h"
+#include "logging.h"
+#include "memory.h"
+#include "util.h"
+
+/* This is used by DEBUG_LOG macro */
+LOG_Severity log_min_severity = LOGS_INFO;
+
+/* Current logging contexts */
+static LOG_Context log_contexts;
+
+/* ================================================== */
+/* Flag indicating we have initialised */
+static int initialised = 0;
+
+static FILE *file_log = NULL;
+static int system_log = 0;
+
+static int parent_fd = 0;
+
+struct LogFile {
+ const char *name;
+ const char *banner;
+ FILE *file;
+ unsigned long writes;
+};
+
+static int n_filelogs = 0;
+
+/* Increase this when adding a new logfile */
+#define MAX_FILELOGS 6
+
+static struct LogFile logfiles[MAX_FILELOGS];
+
+/* Global prefix for debug messages */
+static char *debug_prefix;
+
+/* ================================================== */
+/* Init function */
+
+void
+LOG_Initialise(void)
+{
+ debug_prefix = Strdup("");
+ log_contexts = 0;
+
+ initialised = 1;
+ LOG_OpenFileLog(NULL);
+}
+
+/* ================================================== */
+/* Fini function */
+
+void
+LOG_Finalise(void)
+{
+ if (system_log)
+ closelog();
+
+ if (file_log)
+ fclose(file_log);
+
+ LOG_CycleLogFiles();
+
+ Free(debug_prefix);
+
+ initialised = 0;
+}
+
+/* ================================================== */
+
+static void log_message(int fatal, LOG_Severity severity, const char *message)
+{
+ if (system_log) {
+ int priority;
+ switch (severity) {
+ case LOGS_DEBUG:
+ priority = LOG_DEBUG;
+ break;
+ case LOGS_INFO:
+ priority = LOG_INFO;
+ break;
+ case LOGS_WARN:
+ priority = LOG_WARNING;
+ break;
+ case LOGS_ERR:
+ priority = LOG_ERR;
+ break;
+ case LOGS_FATAL:
+ priority = LOG_CRIT;
+ break;
+ default:
+ assert(0);
+ }
+ syslog(priority, fatal ? "Fatal error : %s" : "%s", message);
+ } else if (file_log) {
+ fprintf(file_log, fatal ? "Fatal error : %s\n" : "%s\n", message);
+ }
+}
+
+/* ================================================== */
+
+void LOG_Message(LOG_Severity severity,
+#if DEBUG > 0
+ int line_number, const char *filename, const char *function_name,
+#endif
+ const char *format, ...)
+{
+ char buf[2048];
+ va_list other_args;
+ time_t t;
+ struct tm *tm;
+
+ assert(initialised);
+ severity = CLAMP(LOGS_DEBUG, severity, LOGS_FATAL);
+
+ if (!system_log && file_log && severity >= log_min_severity) {
+ /* Don't clutter up syslog with timestamps and internal debugging info */
+ time(&t);
+ tm = gmtime(&t);
+ if (tm) {
+ strftime(buf, sizeof (buf), "%Y-%m-%dT%H:%M:%SZ", tm);
+ fprintf(file_log, "%s ", buf);
+ }
+#if DEBUG > 0
+ if (log_min_severity <= LOGS_DEBUG) {
+ /* Log severity to character mapping (debug, info, warn, err, fatal) */
+ const char severity_chars[LOGS_FATAL - LOGS_DEBUG + 1] = {'D', 'I', 'W', 'E', 'F'};
+
+ fprintf(file_log, "%c:%s%s:%d:(%s) ", severity_chars[severity - LOGS_DEBUG],
+ debug_prefix, filename, line_number, function_name);
+ }
+#endif
+ }
+
+ va_start(other_args, format);
+ vsnprintf(buf, sizeof(buf), format, other_args);
+ va_end(other_args);
+
+ switch (severity) {
+ case LOGS_DEBUG:
+ case LOGS_INFO:
+ case LOGS_WARN:
+ case LOGS_ERR:
+ if (severity >= log_min_severity)
+ log_message(0, severity, buf);
+ break;
+ case LOGS_FATAL:
+ 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 */
+ if (parent_fd > 0) {
+ if (write(parent_fd, buf, strlen(buf) + 1) < 0)
+ ; /* Not much we can do here */
+ } else if (system_log && parent_fd == 0) {
+ system_log = 0;
+ log_message(1, severity, buf);
+ }
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+void
+LOG_OpenFileLog(const char *log_file)
+{
+ FILE *f;
+
+ if (log_file) {
+ f = UTI_OpenFile(NULL, log_file, NULL, 'A', 0640);
+ } else {
+ f = stderr;
+ }
+
+ /* Enable line buffering */
+ setvbuf(f, NULL, _IOLBF, BUFSIZ);
+
+ if (file_log && file_log != stderr)
+ fclose(file_log);
+
+ file_log = f;
+}
+
+
+/* ================================================== */
+
+void
+LOG_OpenSystemLog(void)
+{
+ system_log = 1;
+ openlog("chronyd", LOG_PID, LOG_DAEMON);
+}
+
+/* ================================================== */
+
+void LOG_SetMinSeverity(LOG_Severity severity)
+{
+ /* Don't print any debug messages in a non-debug build */
+ log_min_severity = CLAMP(DEBUG > 0 ? LOGS_DEBUG : LOGS_INFO, severity, LOGS_FATAL);
+}
+
+/* ================================================== */
+
+LOG_Severity
+LOG_GetMinSeverity(void)
+{
+ return log_min_severity;
+}
+
+/* ================================================== */
+
+void
+LOG_SetContext(LOG_Context context)
+{
+ log_contexts |= context;
+}
+
+/* ================================================== */
+
+void
+LOG_UnsetContext(LOG_Context context)
+{
+ log_contexts &= ~context;
+}
+
+/* ================================================== */
+
+LOG_Severity
+LOG_GetContextSeverity(LOG_Context contexts)
+{
+ return log_contexts & contexts ? LOGS_INFO : LOGS_DEBUG;
+}
+
+/* ================================================== */
+
+void
+LOG_SetDebugPrefix(const char *prefix)
+{
+ Free(debug_prefix);
+ debug_prefix = Strdup(prefix);
+}
+
+/* ================================================== */
+
+void
+LOG_SetParentFd(int fd)
+{
+ parent_fd = fd;
+ if (file_log == stderr)
+ file_log = NULL;
+}
+
+/* ================================================== */
+
+void
+LOG_CloseParentFd()
+{
+ if (parent_fd > 0)
+ close(parent_fd);
+ parent_fd = -1;
+}
+
+/* ================================================== */
+
+LOG_FileID
+LOG_FileOpen(const char *name, const char *banner)
+{
+ if (n_filelogs >= MAX_FILELOGS) {
+ assert(0);
+ return -1;
+ }
+
+ logfiles[n_filelogs].name = name;
+ logfiles[n_filelogs].banner = banner;
+ logfiles[n_filelogs].file = NULL;
+ logfiles[n_filelogs].writes = 0;
+
+ return n_filelogs++;
+}
+
+/* ================================================== */
+
+void
+LOG_FileWrite(LOG_FileID id, const char *format, ...)
+{
+ va_list other_args;
+ int banner;
+
+ if (id < 0 || id >= n_filelogs || !logfiles[id].name)
+ return;
+
+ if (!logfiles[id].file) {
+ char *logdir = CNF_GetLogDir();
+
+ if (!logdir) {
+ LOG(LOGS_WARN, "logdir not specified");
+ logfiles[id].name = NULL;
+ return;
+ }
+
+ logfiles[id].file = UTI_OpenFile(logdir, logfiles[id].name, ".log", 'a', 0644);
+ if (!logfiles[id].file) {
+ /* Disable the log */
+ logfiles[id].name = NULL;
+ return;
+ }
+ }
+
+ banner = CNF_GetLogBanner();
+ if (banner && logfiles[id].writes++ % banner == 0) {
+ char bannerline[256];
+ int i, bannerlen;
+
+ bannerlen = MIN(strlen(logfiles[id].banner), sizeof (bannerline) - 1);
+
+ for (i = 0; i < bannerlen; i++)
+ bannerline[i] = '=';
+ bannerline[i] = '\0';
+
+ fprintf(logfiles[id].file, "%s\n", bannerline);
+ fprintf(logfiles[id].file, "%s\n", logfiles[id].banner);
+ fprintf(logfiles[id].file, "%s\n", bannerline);
+ }
+
+ va_start(other_args, format);
+ vfprintf(logfiles[id].file, format, other_args);
+ va_end(other_args);
+ fprintf(logfiles[id].file, "\n");
+
+ fflush(logfiles[id].file);
+}
+
+/* ================================================== */
+
+void
+LOG_CycleLogFiles(void)
+{
+ LOG_FileID i;
+
+ for (i = 0; i < n_filelogs; i++) {
+ if (logfiles[i].file)
+ fclose(logfiles[i].file);
+ logfiles[i].file = NULL;
+ logfiles[i].writes = 0;
+ }
+}
+
+/* ================================================== */
diff --git a/logging.h b/logging.h
new file mode 100644
index 0000000..ff2e30e
--- /dev/null
+++ b/logging.h
@@ -0,0 +1,143 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ * Copyright (C) Miroslav Lichvar 2013-2015
+ *
+ * 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 diagnostic logging module
+
+ */
+
+#ifndef GOT_LOGGING_H
+#define GOT_LOGGING_H
+
+#include "sysincl.h"
+
+/* Line logging macros. If the compiler is GNU C, we take advantage of
+ being able to get the function name also. */
+
+#ifdef __GNUC__
+#define FUNCTION_NAME __FUNCTION__
+#define FORMAT_ATTRIBUTE_PRINTF(str, first) __attribute__ ((format (printf, str, first)))
+#else
+#define FUNCTION_NAME ""
+#define FORMAT_ATTRIBUTE_PRINTF(str, first)
+#endif
+
+#if DEBUG > 0
+#define LOG_MESSAGE(severity, ...) \
+ LOG_Message(severity, __LINE__, __FILE__, FUNCTION_NAME, __VA_ARGS__)
+#else
+#define LOG_MESSAGE(severity, ...) \
+ LOG_Message(severity, __VA_ARGS__)
+#endif
+
+#define DEBUG_LOG(...) \
+ do { \
+ if (DEBUG && log_min_severity == LOGS_DEBUG) \
+ LOG_MESSAGE(LOGS_DEBUG, __VA_ARGS__); \
+ } while (0)
+
+#define LOG_FATAL(...) \
+ do { \
+ LOG_MESSAGE(LOGS_FATAL, __VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#define LOG(severity, ...) LOG_MESSAGE(severity, __VA_ARGS__)
+
+/* Definition of severity */
+typedef enum {
+ LOGS_DEBUG = -1,
+ LOGS_INFO = 0,
+ LOGS_WARN,
+ LOGS_ERR,
+ LOGS_FATAL,
+} LOG_Severity;
+
+/* Minimum severity of messages to be logged */
+extern LOG_Severity log_min_severity;
+
+/* Init function */
+extern void LOG_Initialise(void);
+
+/* Fini function */
+extern void LOG_Finalise(void);
+
+/* Line logging function */
+#if DEBUG > 0
+FORMAT_ATTRIBUTE_PRINTF(5, 6)
+extern void LOG_Message(LOG_Severity severity, int line_number, const char *filename,
+ const char *function_name, const char *format, ...);
+#else
+FORMAT_ATTRIBUTE_PRINTF(2, 3)
+extern void LOG_Message(LOG_Severity severity, const char *format, ...);
+#endif
+
+/* 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);
+
+/* Get the minimum severity */
+extern LOG_Severity LOG_GetMinSeverity(void);
+
+/* Flags for info messages that should be logged only in specific contexts */
+typedef enum {
+ LOGC_Command = 1,
+ LOGC_SourceFile = 2,
+} LOG_Context;
+
+/* Modify current contexts */
+extern void LOG_SetContext(LOG_Context context);
+extern void LOG_UnsetContext(LOG_Context context);
+
+/* Get severity depending on the current active contexts: INFO if they contain
+ at least one of the specified contexts, DEBUG otherwise */
+extern LOG_Severity LOG_GetContextSeverity(LOG_Context contexts);
+
+/* Set a prefix for debug messages */
+extern void LOG_SetDebugPrefix(const char *prefix);
+
+/* Log messages to a file instead of stderr, or stderr again if NULL */
+extern void LOG_OpenFileLog(const char *log_file);
+
+/* Log messages to syslog instead of stderr */
+extern void LOG_OpenSystemLog(void);
+
+/* Stop using stderr and send fatal message to the foreground process */
+extern void LOG_SetParentFd(int fd);
+
+/* Close the pipe to the foreground process so it can exit */
+extern void LOG_CloseParentFd(void);
+
+/* File logging functions */
+
+typedef int LOG_FileID;
+
+extern LOG_FileID LOG_FileOpen(const char *name, const char *banner);
+
+FORMAT_ATTRIBUTE_PRINTF(2, 3)
+extern void LOG_FileWrite(LOG_FileID id, const char *format, ...);
+
+extern void LOG_CycleLogFiles(void);
+
+#endif /* GOT_LOGGING_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..21d0fe7
--- /dev/null
+++ b/main.c
@@ -0,0 +1,706 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) John G. Hasler 2009
+ * Copyright (C) Miroslav Lichvar 2012-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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ The main program
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "main.h"
+#include "sched.h"
+#include "local.h"
+#include "sys.h"
+#include "ntp_io.h"
+#include "ntp_signd.h"
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "nts_ke_server.h"
+#include "nts_ntp_server.h"
+#include "socket.h"
+#include "sources.h"
+#include "sourcestats.h"
+#include "reference.h"
+#include "logging.h"
+#include "conf.h"
+#include "cmdmon.h"
+#include "keys.h"
+#include "manual.h"
+#include "rtc.h"
+#include "refclock.h"
+#include "clientlog.h"
+#include "nameserv.h"
+#include "privops.h"
+#include "smooth.h"
+#include "tempcomp.h"
+#include "util.h"
+
+/* ================================================== */
+
+/* Set when the initialisation chain has been completed. Prevents finalisation
+ * chain being run if a fatal error happened early. */
+
+static int initialised = 0;
+
+static int exit_status = 0;
+
+static int reload = 0;
+
+static REF_Mode ref_mode = REF_ModeNormal;
+
+/* ================================================== */
+
+static void
+do_platform_checks(void)
+{
+ struct timespec ts;
+
+ /* Require at least 32-bit integers, two's complement representation and
+ the usual implementation of conversion of unsigned integers */
+ assert(sizeof (int) >= 4);
+ assert(-1 == ~0);
+ assert((int32_t)4294967295U == (int32_t)-1);
+
+ /* Require time_t and tv_nsec in timespec to be signed */
+ ts.tv_sec = -1;
+ ts.tv_nsec = -1;
+ assert(ts.tv_sec < 0 && ts.tv_nsec < 0);
+}
+
+/* ================================================== */
+
+static void
+delete_pidfile(void)
+{
+ const char *pidfile = CNF_GetPidFile();
+
+ if (!pidfile)
+ return;
+
+ if (!UTI_RemoveFile(NULL, pidfile, NULL))
+ ;
+}
+
+/* ================================================== */
+
+void
+MAI_CleanupAndExit(void)
+{
+ if (!initialised) exit(exit_status);
+
+ LCL_CancelOffsetCorrection();
+ SRC_DumpSources();
+
+ /* Don't update clock when removing sources */
+ REF_SetMode(REF_ModeIgnore);
+
+ SMT_Finalise();
+ TMC_Finalise();
+ MNL_Finalise();
+ CLG_Finalise();
+ NKS_Finalise();
+ NNS_Finalise();
+ NSD_Finalise();
+ NSR_Finalise();
+ SST_Finalise();
+ NCR_Finalise();
+ NIO_Finalise();
+ CAM_Finalise();
+
+ KEY_Finalise();
+ RCL_Finalise();
+ SRC_Finalise();
+ REF_Finalise();
+ RTC_Finalise();
+ SYS_Finalise();
+
+ SCK_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ PRV_Finalise();
+
+ delete_pidfile();
+
+ CNF_Finalise();
+ HSH_Finalise();
+ LOG_Finalise();
+
+ UTI_ResetGetRandomFunctions();
+
+ exit(exit_status);
+}
+
+/* ================================================== */
+
+static void
+signal_cleanup(int x)
+{
+ SCH_QuitProgram();
+}
+
+/* ================================================== */
+
+static void
+quit_timeout(void *arg)
+{
+ LOG(LOGS_INFO, "Timeout reached");
+
+ /* Return with non-zero status if the clock is not synchronised */
+ exit_status = REF_GetOurStratum() >= NTP_MAX_STRATUM;
+ SCH_QuitProgram();
+}
+
+/* ================================================== */
+
+static void
+ntp_source_resolving_end(void)
+{
+ NSR_SetSourceResolvingEndHandler(NULL);
+
+ if (reload) {
+ /* Note, we want reload to come well after the initialisation from
+ the real time clock - this gives us a fighting chance that the
+ system-clock scale for the reloaded samples still has a
+ semblence of validity about it. */
+ SRC_ReloadSources();
+ }
+
+ SRC_RemoveDumpFiles();
+ RTC_StartMeasurements();
+ RCL_StartRefclocks();
+ NSR_StartSources();
+ NSR_AutoStartSources();
+
+ /* Special modes can end only when sources update their reachability.
+ Give up immediately if there are no active sources. */
+ if (ref_mode != REF_ModeNormal && !SRC_ActiveSources()) {
+ REF_SetUnsynchronised();
+ }
+}
+
+/* ================================================== */
+
+static void
+post_init_ntp_hook(void *anything)
+{
+ if (ref_mode == REF_ModeInitStepSlew) {
+ /* Remove the initstepslew sources and set normal mode */
+ NSR_RemoveAllSources();
+ ref_mode = REF_ModeNormal;
+ REF_SetMode(ref_mode);
+ }
+
+ /* Close the pipe to the foreground process so it can exit */
+ LOG_CloseParentFd();
+
+ CNF_AddSources();
+ CNF_AddBroadcasts();
+
+ NSR_SetSourceResolvingEndHandler(ntp_source_resolving_end);
+ NSR_ResolveSources();
+}
+
+/* ================================================== */
+
+static void
+reference_mode_end(int result)
+{
+ switch (ref_mode) {
+ case REF_ModeNormal:
+ case REF_ModeUpdateOnce:
+ case REF_ModePrintOnce:
+ exit_status = !result;
+ SCH_QuitProgram();
+ break;
+ case REF_ModeInitStepSlew:
+ /* Switch to the normal mode, the delay is used to prevent polling
+ interval shorter than the burst interval if some configured servers
+ were used also for initstepslew */
+ SCH_AddTimeoutByDelay(2.0, post_init_ntp_hook, NULL);
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+static void
+post_init_rtc_hook(void *anything)
+{
+ if (CNF_GetInitSources() > 0) {
+ CNF_AddInitSources();
+ NSR_StartSources();
+ assert(REF_GetMode() != REF_ModeNormal);
+ /* Wait for mode end notification */
+ } else {
+ (post_init_ntp_hook)(NULL);
+ }
+}
+
+/* ================================================== */
+
+static void
+check_pidfile(void)
+{
+ const char *pidfile = CNF_GetPidFile();
+ FILE *in;
+ int pid, count;
+
+ if (!pidfile)
+ return;
+
+ in = UTI_OpenFile(NULL, pidfile, NULL, 'r', 0);
+ if (!in)
+ return;
+
+ count = fscanf(in, "%d", &pid);
+ fclose(in);
+
+ if (count != 1)
+ return;
+
+ if (getsid(pid) < 0)
+ return;
+
+ LOG_FATAL("Another chronyd may already be running (pid=%d), check %s",
+ pid, pidfile);
+}
+
+/* ================================================== */
+
+static void
+write_pidfile(void)
+{
+ const char *pidfile = CNF_GetPidFile();
+ FILE *out;
+
+ if (!pidfile)
+ return;
+
+ out = UTI_OpenFile(NULL, pidfile, NULL, 'W', 0644);
+ fprintf(out, "%d\n", (int)getpid());
+ fclose(out);
+}
+
+/* ================================================== */
+
+#define DEV_NULL "/dev/null"
+
+static void
+go_daemon(void)
+{
+ int pid, fd, pipefd[2];
+
+ /* Create pipe which will the daemon use to notify the grandparent
+ when it's initialised or send an error message */
+ if (pipe(pipefd)) {
+ LOG_FATAL("pipe() failed : %s", strerror(errno));
+ }
+
+ /* Does this preserve existing signal handlers? */
+ pid = fork();
+
+ if (pid < 0) {
+ LOG_FATAL("fork() failed : %s", strerror(errno));
+ } else if (pid > 0) {
+ /* In the 'grandparent' */
+ char message[1024];
+ int r;
+
+ /* Don't exit before the 'parent' */
+ waitpid(pid, NULL, 0);
+
+ close(pipefd[1]);
+ r = read(pipefd[0], message, sizeof (message));
+ if (r) {
+ if (r > 0) {
+ /* Print the error message from the child */
+ message[sizeof (message) - 1] = '\0';
+ fprintf(stderr, "%s\n", message);
+ }
+ exit(1);
+ } else
+ exit(0);
+ } else {
+ close(pipefd[0]);
+
+ setsid();
+
+ /* Do 2nd fork, as-per recommended practice for launching daemons. */
+ pid = fork();
+
+ if (pid < 0) {
+ LOG_FATAL("fork() failed : %s", strerror(errno));
+ } else if (pid > 0) {
+ /* In the 'parent' */
+ close(pipefd[1]);
+ exit(0);
+ } else {
+ /* In the child we want to leave running as the daemon */
+
+ /* Change current directory to / */
+ if (chdir("/") < 0) {
+ LOG_FATAL("chdir() failed : %s", strerror(errno));
+ }
+
+ /* Don't keep stdin/out/err from before. But don't close
+ the parent pipe yet, or reusable file descriptors. */
+ for (fd=0; fd<1024; fd++) {
+ if (fd != pipefd[1] && !SCK_IsReusable(fd))
+ close(fd);
+ }
+
+ LOG_SetParentFd(pipefd[1]);
+
+ /* Open /dev/null as new stdin/out/err */
+ errno = 0;
+ if (open(DEV_NULL, O_RDONLY) != STDIN_FILENO ||
+ open(DEV_NULL, O_WRONLY) != STDOUT_FILENO ||
+ open(DEV_NULL, O_RDWR) != STDERR_FILENO)
+ LOG_FATAL("Could not open %s : %s", DEV_NULL, strerror(errno));
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+print_help(const char *progname)
+{
+ printf("Usage: %s [OPTION]... [DIRECTIVE]...\n\n"
+ "Options:\n"
+ " -4\t\tUse IPv4 addresses only\n"
+ " -6\t\tUse IPv6 addresses only\n"
+ " -f FILE\tSpecify configuration file (%s)\n"
+ " -n\t\tDon't run as daemon\n"
+ " -d\t\tDon't run as daemon and log to stderr\n"
+#if DEBUG > 0
+ " -d -d\t\tEnable debug messages\n"
+#endif
+ " -l FILE\tLog to file\n"
+ " -L LEVEL\tSet logging threshold (0)\n"
+ " -p\t\tPrint configuration and exit\n"
+ " -q\t\tSet clock and exit\n"
+ " -Q\t\tLog offset and exit\n"
+ " -r\t\tReload dump files\n"
+ " -R\t\tAdapt configuration for restart\n"
+ " -s\t\tSet clock from RTC\n"
+ " -t SECONDS\tExit after elapsed time\n"
+ " -u USER\tSpecify user (%s)\n"
+ " -U\t\tDon't check for root\n"
+ " -F LEVEL\tSet system call filter level (0)\n"
+ " -P PRIORITY\tSet process priority (0)\n"
+ " -m\t\tLock memory\n"
+ " -x\t\tDon't control clock\n"
+ " -v, --version\tPrint version and exit\n"
+ " -h, --help\tPrint usage and exit\n",
+ progname, DEFAULT_CONF_FILE, DEFAULT_USER);
+}
+
+/* ================================================== */
+
+static void
+print_version(void)
+{
+ printf("chronyd (chrony) version %s (%s)\n", CHRONY_VERSION, CHRONYD_FEATURES);
+}
+
+/* ================================================== */
+
+static int
+parse_int_arg(const char *arg)
+{
+ int i;
+
+ if (sscanf(arg, "%d", &i) != 1)
+ LOG_FATAL("Invalid argument %s", arg);
+ return i;
+}
+
+/* ================================================== */
+
+int main
+(int argc, char **argv)
+{
+ const char *conf_file = DEFAULT_CONF_FILE;
+ const char *progname = argv[0];
+ 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 = -1;
+ int scfilter_level = 0, lock_memory = 0, sched_priority = 0;
+ int clock_control = 1, system_log = 1, log_severity = LOGS_INFO;
+ int user_check = 1, config_args = 0, print_config = 0;
+
+ do_platform_checks();
+
+ LOG_Initialise();
+
+ /* Parse long command-line options */
+ for (optind = 1; optind < argc; optind++) {
+ if (!strcmp("--help", argv[optind])) {
+ print_help(progname);
+ return 0;
+ } else if (!strcmp("--version", argv[optind])) {
+ print_version();
+ return 0;
+ }
+ }
+
+ optind = 1;
+
+ /* Parse short command-line options */
+ while ((opt = getopt(argc, argv, "46df:F:hl:L:mnpP:qQrRst:u:Uvx")) != -1) {
+ switch (opt) {
+ case '4':
+ case '6':
+ address_family = opt == '4' ? IPADDR_INET4 : IPADDR_INET6;
+ break;
+ case 'd':
+ debug++;
+ nofork = 1;
+ system_log = 0;
+ break;
+ case 'f':
+ conf_file = optarg;
+ break;
+ case 'F':
+ scfilter_level = parse_int_arg(optarg);
+ break;
+ case 'l':
+ log_file = optarg;
+ break;
+ case 'L':
+ log_severity = parse_int_arg(optarg);
+ break;
+ case 'm':
+ lock_memory = 1;
+ break;
+ case 'n':
+ nofork = 1;
+ break;
+ case 'p':
+ print_config = 1;
+ user_check = 0;
+ nofork = 1;
+ system_log = 0;
+ log_severity = LOGS_WARN;
+ break;
+ case 'P':
+ sched_priority = parse_int_arg(optarg);
+ break;
+ case 'q':
+ ref_mode = REF_ModeUpdateOnce;
+ nofork = 1;
+ client_only = 0;
+ system_log = 0;
+ break;
+ case 'Q':
+ ref_mode = REF_ModePrintOnce;
+ nofork = 1;
+ client_only = 1;
+ user_check = 0;
+ clock_control = 0;
+ system_log = 0;
+ break;
+ case 'r':
+ reload = 1;
+ break;
+ case 'R':
+ restarted = 1;
+ break;
+ case 's':
+ do_init_rtc = 1;
+ break;
+ case 't':
+ timeout = parse_int_arg(optarg);
+ break;
+ case 'u':
+ user = optarg;
+ break;
+ case 'U':
+ user_check = 0;
+ break;
+ case 'v':
+ print_version();
+ return 0;
+ case 'x':
+ clock_control = 0;
+ break;
+ default:
+ print_help(progname);
+ return opt != 'h';
+ }
+ }
+
+ if (user_check && getuid() != 0)
+ LOG_FATAL("Not superuser");
+
+ /* Initialise reusable file descriptors before fork */
+ SCK_PreInitialise();
+
+ /* Turn into a daemon */
+ if (!nofork) {
+ go_daemon();
+ }
+
+ if (log_file) {
+ LOG_OpenFileLog(log_file);
+ } else if (system_log) {
+ LOG_OpenSystemLog();
+ }
+
+ LOG_SetMinSeverity(debug >= 2 ? LOGS_DEBUG : log_severity);
+
+ LOG(LOGS_INFO, "chronyd version %s starting (%s)", CHRONY_VERSION, CHRONYD_FEATURES);
+
+ DNS_SetAddressFamily(address_family);
+
+ CNF_Initialise(restarted, client_only);
+ if (print_config)
+ CNF_EnablePrint();
+
+ /* Parse the config file or the remaining command line arguments */
+ config_args = argc - optind;
+ if (!config_args) {
+ CNF_ReadFile(conf_file);
+ } else {
+ for (; optind < argc; optind++)
+ CNF_ParseLine(NULL, config_args + optind - argc + 1, argv[optind]);
+ }
+
+ if (print_config)
+ return 0;
+
+ /* Check whether another chronyd may already be running */
+ check_pidfile();
+
+ if (!user)
+ user = CNF_GetUser();
+
+ pw = getpwnam(user);
+ if (!pw)
+ LOG_FATAL("Could not get user/group ID of %s", user);
+
+ /* Create directories for sockets, log files, and dump files */
+ CNF_CreateDirs(pw->pw_uid, pw->pw_gid);
+
+ /* Write our pidfile to prevent other instances from running */
+ write_pidfile();
+
+ PRV_Initialise();
+ LCL_Initialise();
+ SCH_Initialise();
+ SCK_Initialise(address_family);
+
+ /* Start helper processes if needed */
+ NKS_PreInitialise(pw->pw_uid, pw->pw_gid, scfilter_level);
+
+ SYS_Initialise(clock_control);
+ RTC_Initialise(do_init_rtc);
+ SRC_Initialise();
+ RCL_Initialise();
+ KEY_Initialise();
+
+ /* Open privileged ports before dropping root */
+ CAM_Initialise();
+ NIO_Initialise();
+ NCR_Initialise();
+ CNF_SetupAccessRestrictions();
+
+ /* Command-line switch must have priority */
+ if (!sched_priority) {
+ sched_priority = CNF_GetSchedPriority();
+ }
+ if (sched_priority) {
+ SYS_SetScheduler(sched_priority);
+ }
+
+ if (lock_memory || CNF_GetLockMemory()) {
+ SYS_LockMemory();
+ }
+
+ /* Drop root privileges if the specified user has a non-zero UID */
+ if (!geteuid() && (pw->pw_uid || pw->pw_gid)) {
+ SYS_DropRoot(pw->pw_uid, pw->pw_gid, SYS_MAIN_PROCESS);
+
+ /* Warn if missing read access or having write access to keys */
+ CNF_CheckReadOnlyAccess();
+ }
+
+ if (!geteuid())
+ LOG(LOGS_WARN, "Running with root privileges");
+
+ REF_Initialise();
+ SST_Initialise();
+ NSR_Initialise();
+ NSD_Initialise();
+ NNS_Initialise();
+ NKS_Initialise();
+ CLG_Initialise();
+ MNL_Initialise();
+ TMC_Initialise();
+ SMT_Initialise();
+
+ /* From now on, it is safe to do finalisation on exit */
+ initialised = 1;
+
+ UTI_SetQuitSignalsHandler(signal_cleanup, 1);
+
+ CAM_OpenUnixSocket();
+
+ if (scfilter_level)
+ SYS_EnableSystemCallFilter(scfilter_level, SYS_MAIN_PROCESS);
+
+ if (ref_mode == REF_ModeNormal && CNF_GetInitSources() > 0) {
+ ref_mode = REF_ModeInitStepSlew;
+ }
+
+ REF_SetModeEndHandler(reference_mode_end);
+ REF_SetMode(ref_mode);
+
+ if (timeout >= 0)
+ SCH_AddTimeoutByDelay(timeout, quit_timeout, NULL);
+
+ if (do_init_rtc) {
+ RTC_TimeInit(post_init_rtc_hook, NULL);
+ } else {
+ post_init_rtc_hook(NULL);
+ }
+
+ /* The program normally runs under control of the main loop in
+ the scheduler. */
+ SCH_MainLoop();
+
+ LOG(LOGS_INFO, "chronyd exiting");
+
+ MAI_CleanupAndExit();
+
+ return 0;
+}
+
+/* ================================================== */
diff --git a/main.h b/main.h
new file mode 100644
index 0000000..d6d502a
--- /dev/null
+++ b/main.h
@@ -0,0 +1,35 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 main routine
+ */
+
+#ifndef GOT_MAIN_H
+#define GOT_MAIN_H
+
+/* Function to clean up at end of run */
+extern void MAI_CleanupAndExit(void);
+
+#endif /* GOT_MAIN_H */
+
+
diff --git a/manual.c b/manual.c
new file mode 100644
index 0000000..98a3aa2
--- /dev/null
+++ b/manual.c
@@ -0,0 +1,332 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines for implementing manual input of real time.
+
+ The daemon accepts manual time input over the control connection,
+ and adjusts the system time to match. Besides this, though, it can
+ determine the average rate of time loss or gain of the local system
+ and adjust the frequency accordingly.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "manual.h"
+#include "logging.h"
+#include "local.h"
+#include "conf.h"
+#include "util.h"
+#include "ntp.h"
+#include "reference.h"
+#include "regress.h"
+
+static int enabled = 0;
+
+/* More recent samples at highest indices */
+typedef struct {
+ struct timespec when; /* This is our 'cooked' time */
+ double orig_offset; /*+ Not modified by slew samples */
+ double offset; /*+ if we are fast of the supplied reference */
+ double residual; /*+ regression residual (sign convention given by
+ (measured-predicted)) */
+} Sample;
+
+#define MIN_SAMPLE_SEPARATION 1.0
+
+#define MAX_SAMPLES 16
+
+static Sample samples[16];
+static int n_samples;
+
+/* ================================================== */
+
+static void
+slew_samples(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *not_used);
+
+/* ================================================== */
+
+void
+MNL_Initialise(void)
+{
+ if (CNF_GetManualEnabled()) {
+ enabled = 1;
+ } else {
+ enabled = 0;
+ }
+
+ n_samples = 0;
+
+ LCL_AddParameterChangeHandler(slew_samples, NULL);
+}
+
+/* ================================================== */
+
+void
+MNL_Finalise(void)
+{
+ LCL_RemoveParameterChangeHandler(slew_samples, NULL);
+}
+
+/* ================================================== */
+
+static void
+estimate_and_set_system(struct timespec *now, int offset_provided, double offset,
+ double *reg_offset, double *dfreq_ppm, double *new_afreq_ppm)
+{
+ double agos[MAX_SAMPLES], offsets[MAX_SAMPLES];
+ double b0, b1;
+ int n_runs, best_start; /* Unused results from regression analyser */
+ int i;
+ double freq = 0.0;
+ double skew = 0.099999999; /* All 9's when printed to log file */
+ int found_freq;
+ double slew_by;
+
+ b0 = offset_provided ? offset : 0.0;
+ b1 = freq = 0.0;
+ found_freq = 0;
+
+ if (n_samples > 1) {
+ for (i=0; i<n_samples; i++) {
+ agos[i] = UTI_DiffTimespecsToDouble(&samples[n_samples - 1].when, &samples[i].when);
+ offsets[i] = samples[i].offset;
+ }
+
+ if (RGR_FindBestRobustRegression(agos, offsets, n_samples, 1.0e-8,
+ &b0, &b1, &n_runs, &best_start)) {
+ /* Ignore b0 from regression; treat offset as being the most
+ recently entered value. (If the administrator knows he's put
+ an outlier in, he will rerun the settime operation.) However,
+ the frequency estimate comes from the regression. */
+ freq = -b1;
+ found_freq = 1;
+ }
+ } else {
+ agos[0] = 0.0;
+ offsets[0] = b0;
+ }
+
+ if (offset_provided) {
+ slew_by = offset;
+ } else {
+ slew_by = b0;
+ }
+
+ if (found_freq) {
+ LOG(LOGS_INFO, "Making a frequency change of %.3f ppm and a slew of %.6f",
+ 1.0e6 * freq, slew_by);
+
+ REF_SetManualReference(now,
+ slew_by,
+ freq, skew);
+ } else {
+ LOG(LOGS_INFO, "Making a slew of %.6f", slew_by);
+ REF_SetManualReference(now,
+ slew_by,
+ 0.0, skew);
+ }
+
+ if (reg_offset) *reg_offset = b0;
+ if (dfreq_ppm) *dfreq_ppm = 1.0e6 * freq;
+ if (new_afreq_ppm) *new_afreq_ppm = LCL_ReadAbsoluteFrequency();
+
+ /* Calculate residuals to store them */
+ for (i=0; i<n_samples; i++) {
+ samples[i].residual = offsets[i] - (b0 + agos[i] * b1);
+ }
+
+}
+
+/* ================================================== */
+
+int
+MNL_AcceptTimestamp(struct timespec *ts, double *reg_offset, double *dfreq_ppm, double *new_afreq_ppm)
+{
+ struct timespec now;
+ double offset, diff;
+ int i;
+
+ if (enabled) {
+ LCL_ReadCookedTime(&now, NULL);
+
+ /* Make sure the provided timestamp is sane and the sample
+ is not too close to the last one */
+
+ if (!UTI_IsTimeOffsetSane(ts, 0.0))
+ return 0;
+
+ if (n_samples) {
+ diff = UTI_DiffTimespecsToDouble(&now, &samples[n_samples - 1].when);
+ if (diff < MIN_SAMPLE_SEPARATION)
+ return 0;
+ }
+
+ offset = UTI_DiffTimespecsToDouble(&now, ts);
+
+ /* Check if buffer full up */
+ if (n_samples == MAX_SAMPLES) {
+ /* Shift samples down */
+ for (i=1; i<n_samples; i++) {
+ samples[i-1] = samples[i];
+ }
+ --n_samples;
+ }
+
+ samples[n_samples].when = now;
+ samples[n_samples].offset = offset;
+ samples[n_samples].orig_offset = offset;
+ ++n_samples;
+
+ estimate_and_set_system(&now, 1, offset, reg_offset, dfreq_ppm, new_afreq_ppm);
+
+ return 1;
+
+ } else {
+
+ return 0;
+
+ }
+}
+
+/* ================================================== */
+
+static void
+slew_samples(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *not_used)
+{
+ double delta_time;
+ int i;
+
+ if (change_type == LCL_ChangeUnknownStep) {
+ MNL_Reset();
+ }
+
+ for (i=0; i<n_samples; i++) {
+ UTI_AdjustTimespec(&samples[i].when, cooked, &samples[i].when, &delta_time,
+ dfreq, doffset);
+ samples[i].offset += delta_time;
+ }
+}
+
+/* ================================================== */
+
+void
+MNL_Enable(void)
+{
+ enabled = 1;
+}
+
+
+/* ================================================== */
+
+void
+MNL_Disable(void)
+{
+ enabled = 0;
+}
+
+/* ================================================== */
+
+void
+MNL_Reset(void)
+{
+ n_samples = 0;
+}
+
+/* ================================================== */
+
+int
+MNL_IsEnabled(void)
+{
+ return enabled;
+}
+
+/* ================================================== */
+/* Generate report data for the REQ_MANUAL_LIST command/monitoring
+ protocol */
+
+void
+MNL_ReportSamples(RPT_ManualSamplesReport *report, int max, int *n)
+{
+ int i;
+
+ if (n_samples > max) {
+ *n = max;
+ } else {
+ *n = n_samples;
+ }
+
+ for (i=0; i<n_samples && i<max; i++) {
+ report[i].when = samples[i].when;
+ report[i].slewed_offset = samples[i].offset;
+ report[i].orig_offset = samples[i].orig_offset;
+ report[i].residual = samples[i].residual;
+ }
+}
+
+/* ================================================== */
+/* Delete a sample if it's within range, re-estimate the error and
+ drift and apply it to the system clock. */
+
+int
+MNL_DeleteSample(int index)
+{
+ int i;
+ struct timespec now;
+
+ if ((index < 0) || (index >= n_samples)) {
+ return 0;
+ }
+
+ /* Crunch the samples down onto the one being deleted */
+
+ for (i=index; i<(n_samples-1); i++) {
+ samples[i] = samples[i+1];
+ }
+
+ n_samples -= 1;
+
+ /* Now re-estimate. NULLs because we don't want the parameters back
+ in this case. */
+ LCL_ReadCookedTime(&now, NULL);
+ estimate_and_set_system(&now, 0, 0.0, NULL, NULL, NULL);
+
+ return 1;
+
+}
+
+/* ================================================== */
+
+
diff --git a/manual.h b/manual.h
new file mode 100644
index 0000000..7f3d0b2
--- /dev/null
+++ b/manual.h
@@ -0,0 +1,46 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 manual time input module.
+
+ */
+
+#ifndef GOT_MANUAL_H
+#define GOT_MANUAL_H
+
+#include "sysincl.h"
+#include "reports.h"
+
+extern void MNL_Initialise(void);
+extern void MNL_Finalise(void);
+extern int MNL_AcceptTimestamp(struct timespec *ts, double *reg_offset, double *dfreq_ppm, double *new_afreq_ppm);
+
+extern void MNL_Enable(void);
+extern void MNL_Disable(void);
+extern void MNL_Reset(void);
+extern int MNL_IsEnabled(void);
+
+extern void MNL_ReportSamples(RPT_ManualSamplesReport *report, int max, int *n);
+extern int MNL_DeleteSample(int index);
+
+#endif /* GOT_MANUAL_H */
diff --git a/md5.c b/md5.c
new file mode 100644
index 0000000..b8eeda3
--- /dev/null
+++ b/md5.c
@@ -0,0 +1,315 @@
+/*
+ ***********************************************************************
+ ** md5.c -- the source code for MD5 routines **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm **
+ ** Created: 2/17/90 RLR **
+ ** Revised: 1/91 SRD,AJ,BSK,JT Reference C Version **
+ ** Revised (for MD5): RLR 4/27/91 **
+ ** -- G modified to have y&~z instead of y&z **
+ ** -- FF, GG, HH modified to add in last register done **
+ ** -- Access pattern: round 2 works mod 5, round 3 works mod 3 **
+ ** -- distinct additive constant for each step **
+ ** -- round 4 added, working mod 7 **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. **
+ ** **
+ ** License to copy and use this software is granted provided that **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message- **
+ ** Digest Algorithm" in all material mentioning or referencing this **
+ ** software or this function. **
+ ** **
+ ** License is also granted to make and use derivative works **
+ ** provided that such works are identified as "derived from the RSA **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all **
+ ** material mentioning or referencing the derived work. **
+ ** **
+ ** RSA Data Security, Inc. makes no representations concerning **
+ ** either the merchantability of this software or the suitability **
+ ** of this software for any particular purpose. It is provided "as **
+ ** is" without express or implied warranty of any kind. **
+ ** **
+ ** These notices must be retained in any copies of any part of this **
+ ** documentation and/or software. **
+ ***********************************************************************
+ */
+
+#include "md5.h"
+
+/*
+ ***********************************************************************
+ ** Message-digest routines: **
+ ** To form the message digest for a message M **
+ ** (1) Initialize a context buffer mdContext using MD5Init **
+ ** (2) Call MD5Update on mdContext and M **
+ ** (3) Call MD5Final on mdContext **
+ ** The message digest is now in mdContext->digest[0...15] **
+ ***********************************************************************
+ */
+
+/* forward declaration */
+static void Transform (UINT4 *, UINT4 *);
+
+#ifdef __STDC__
+static const
+#else
+static
+#endif
+unsigned char PADDING[64] = {
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* F, G, H and I are basic MD5 functions */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits */
+#if defined(FAST_MD5) && defined(__GNUC__) && defined(mc68000)
+/*
+ * If we're on a 68000 based CPU and using a GNU C compiler with
+ * inline assembly code, we can speed this up a bit.
+ */
+inline UINT4 ROTATE_LEFT(UINT4 x, int n)
+{
+ asm("roll %2,%0" : "=d" (x) : "0" (x), "Ir" (n));
+ return x;
+}
+#else
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+#endif
+
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */
+/* Rotation is separate from addition to prevent recomputation */
+#define FF(a, b, c, d, x, s, ac) \
+ {(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) \
+ {(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) \
+ {(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) \
+ {(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* The routine MD5Init initializes the message-digest context
+ mdContext. All fields are set to zero.
+ */
+void MD5Init (MD5_CTX *mdContext)
+{
+ mdContext->i[0] = mdContext->i[1] = (UINT4)0;
+
+ /* Load magic initialization constants.
+ */
+ mdContext->buf[0] = (UINT4)0x67452301;
+ mdContext->buf[1] = (UINT4)0xefcdab89;
+ mdContext->buf[2] = (UINT4)0x98badcfe;
+ mdContext->buf[3] = (UINT4)0x10325476;
+}
+
+/* The routine MD5Update updates the message-digest context to
+ account for the presence of each of the characters inBuf[0..inLen-1]
+ in the message whose digest is being computed.
+ */
+void MD5Update (MD5_CTX *mdContext, unsigned const char *inBuf, unsigned int inLen)
+{
+ UINT4 in[16];
+ int mdi;
+ unsigned int i, ii;
+
+ /* compute number of bytes mod 64 */
+ mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+ /* update number of bits */
+ if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0])
+ mdContext->i[1]++;
+ mdContext->i[0] += ((UINT4)inLen << 3);
+ mdContext->i[1] += ((UINT4)inLen >> 29);
+
+ while (inLen--) {
+ /* add new character to buffer, increment mdi */
+ mdContext->in[mdi++] = *inBuf++;
+
+ /* transform if necessary */
+ if (mdi == 0x40) {
+ for (i = 0, ii = 0; i < 16; i++, ii += 4)
+ in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+ (((UINT4)mdContext->in[ii+2]) << 16) |
+ (((UINT4)mdContext->in[ii+1]) << 8) |
+ ((UINT4)mdContext->in[ii]);
+ Transform (mdContext->buf, in);
+ mdi = 0;
+ }
+ }
+}
+
+/* The routine MD5Final terminates the message-digest computation and
+ ends with the desired message digest in mdContext->digest[0...15].
+ */
+
+void MD5Final (MD5_CTX *mdContext)
+{
+ UINT4 in[16];
+ int mdi;
+ unsigned int i, ii;
+ unsigned int padLen;
+
+ /* save number of bits */
+ in[14] = mdContext->i[0];
+ in[15] = mdContext->i[1];
+
+ /* compute number of bytes mod 64 */
+ mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+ /* pad out to 56 mod 64 */
+ padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi);
+ MD5Update (mdContext, PADDING, padLen);
+
+ /* append length in bits and transform */
+ for (i = 0, ii = 0; i < 14; i++, ii += 4)
+ in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+ (((UINT4)mdContext->in[ii+2]) << 16) |
+ (((UINT4)mdContext->in[ii+1]) << 8) |
+ ((UINT4)mdContext->in[ii]);
+ Transform (mdContext->buf, in);
+
+ /* store buffer in digest */
+ for (i = 0, ii = 0; i < 4; i++, ii += 4) {
+ mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF);
+ mdContext->digest[ii+1] =
+ (unsigned char)((mdContext->buf[i] >> 8) & 0xFF);
+ mdContext->digest[ii+2] =
+ (unsigned char)((mdContext->buf[i] >> 16) & 0xFF);
+ mdContext->digest[ii+3] =
+ (unsigned char)((mdContext->buf[i] >> 24) & 0xFF);
+ }
+}
+
+/* Basic MD5 step. Transforms buf based on in.
+ */
+static void Transform (UINT4 *buf, UINT4 *in)
+{
+ UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+ /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+
+ FF ( a, b, c, d, in[ 0], S11, 0xd76aa478); /* 1 */
+ FF ( d, a, b, c, in[ 1], S12, 0xe8c7b756); /* 2 */
+ FF ( c, d, a, b, in[ 2], S13, 0x242070db); /* 3 */
+ FF ( b, c, d, a, in[ 3], S14, 0xc1bdceee); /* 4 */
+ FF ( a, b, c, d, in[ 4], S11, 0xf57c0faf); /* 5 */
+ FF ( d, a, b, c, in[ 5], S12, 0x4787c62a); /* 6 */
+ FF ( c, d, a, b, in[ 6], S13, 0xa8304613); /* 7 */
+ FF ( b, c, d, a, in[ 7], S14, 0xfd469501); /* 8 */
+ FF ( a, b, c, d, in[ 8], S11, 0x698098d8); /* 9 */
+ FF ( d, a, b, c, in[ 9], S12, 0x8b44f7af); /* 10 */
+ FF ( c, d, a, b, in[10], S13, 0xffff5bb1); /* 11 */
+ FF ( b, c, d, a, in[11], S14, 0x895cd7be); /* 12 */
+ FF ( a, b, c, d, in[12], S11, 0x6b901122); /* 13 */
+ FF ( d, a, b, c, in[13], S12, 0xfd987193); /* 14 */
+ FF ( c, d, a, b, in[14], S13, 0xa679438e); /* 15 */
+ FF ( b, c, d, a, in[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+ GG ( a, b, c, d, in[ 1], S21, 0xf61e2562); /* 17 */
+ GG ( d, a, b, c, in[ 6], S22, 0xc040b340); /* 18 */
+ GG ( c, d, a, b, in[11], S23, 0x265e5a51); /* 19 */
+ GG ( b, c, d, a, in[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG ( a, b, c, d, in[ 5], S21, 0xd62f105d); /* 21 */
+ GG ( d, a, b, c, in[10], S22, 0x2441453); /* 22 */
+ GG ( c, d, a, b, in[15], S23, 0xd8a1e681); /* 23 */
+ GG ( b, c, d, a, in[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG ( a, b, c, d, in[ 9], S21, 0x21e1cde6); /* 25 */
+ GG ( d, a, b, c, in[14], S22, 0xc33707d6); /* 26 */
+ GG ( c, d, a, b, in[ 3], S23, 0xf4d50d87); /* 27 */
+ GG ( b, c, d, a, in[ 8], S24, 0x455a14ed); /* 28 */
+ GG ( a, b, c, d, in[13], S21, 0xa9e3e905); /* 29 */
+ GG ( d, a, b, c, in[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG ( c, d, a, b, in[ 7], S23, 0x676f02d9); /* 31 */
+ GG ( b, c, d, a, in[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+ HH ( a, b, c, d, in[ 5], S31, 0xfffa3942); /* 33 */
+ HH ( d, a, b, c, in[ 8], S32, 0x8771f681); /* 34 */
+ HH ( c, d, a, b, in[11], S33, 0x6d9d6122); /* 35 */
+ HH ( b, c, d, a, in[14], S34, 0xfde5380c); /* 36 */
+ HH ( a, b, c, d, in[ 1], S31, 0xa4beea44); /* 37 */
+ HH ( d, a, b, c, in[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH ( c, d, a, b, in[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH ( b, c, d, a, in[10], S34, 0xbebfbc70); /* 40 */
+ HH ( a, b, c, d, in[13], S31, 0x289b7ec6); /* 41 */
+ HH ( d, a, b, c, in[ 0], S32, 0xeaa127fa); /* 42 */
+ HH ( c, d, a, b, in[ 3], S33, 0xd4ef3085); /* 43 */
+ HH ( b, c, d, a, in[ 6], S34, 0x4881d05); /* 44 */
+ HH ( a, b, c, d, in[ 9], S31, 0xd9d4d039); /* 45 */
+ HH ( d, a, b, c, in[12], S32, 0xe6db99e5); /* 46 */
+ HH ( c, d, a, b, in[15], S33, 0x1fa27cf8); /* 47 */
+ HH ( b, c, d, a, in[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+ II ( a, b, c, d, in[ 0], S41, 0xf4292244); /* 49 */
+ II ( d, a, b, c, in[ 7], S42, 0x432aff97); /* 50 */
+ II ( c, d, a, b, in[14], S43, 0xab9423a7); /* 51 */
+ II ( b, c, d, a, in[ 5], S44, 0xfc93a039); /* 52 */
+ II ( a, b, c, d, in[12], S41, 0x655b59c3); /* 53 */
+ II ( d, a, b, c, in[ 3], S42, 0x8f0ccc92); /* 54 */
+ II ( c, d, a, b, in[10], S43, 0xffeff47d); /* 55 */
+ II ( b, c, d, a, in[ 1], S44, 0x85845dd1); /* 56 */
+ II ( a, b, c, d, in[ 8], S41, 0x6fa87e4f); /* 57 */
+ II ( d, a, b, c, in[15], S42, 0xfe2ce6e0); /* 58 */
+ II ( c, d, a, b, in[ 6], S43, 0xa3014314); /* 59 */
+ II ( b, c, d, a, in[13], S44, 0x4e0811a1); /* 60 */
+ II ( a, b, c, d, in[ 4], S41, 0xf7537e82); /* 61 */
+ II ( d, a, b, c, in[11], S42, 0xbd3af235); /* 62 */
+ II ( c, d, a, b, in[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II ( b, c, d, a, in[ 9], S44, 0xeb86d391); /* 64 */
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+/*
+ ***********************************************************************
+ ** End of md5.c **
+ ******************************** (cut) ********************************
+ */
diff --git a/md5.h b/md5.h
new file mode 100644
index 0000000..5f37235
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,56 @@
+/*
+ ***********************************************************************
+ ** md5.h -- header file for implementation of MD5 **
+ ** RSA Data Security, Inc. MD5 Message-Digest Algorithm **
+ ** Created: 2/17/90 RLR **
+ ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version **
+ ** Revised (for MD5): RLR 4/27/91 **
+ ***********************************************************************
+ */
+
+/*
+ ***********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. **
+ ** **
+ ** License to copy and use this software is granted provided that **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message- **
+ ** Digest Algorithm" in all material mentioning or referencing this **
+ ** software or this function. **
+ ** **
+ ** License is also granted to make and use derivative works **
+ ** provided that such works are identified as "derived from the RSA **
+ ** Data Security, Inc. MD5 Message-Digest Algorithm" in all **
+ ** material mentioning or referencing the derived work. **
+ ** **
+ ** RSA Data Security, Inc. makes no representations concerning **
+ ** either the merchantability of this software or the suitability **
+ ** of this software for any particular purpose. It is provided "as **
+ ** is" without express or implied warranty of any kind. **
+ ** **
+ ** These notices must be retained in any copies of any part of this **
+ ** documentation and/or software. **
+ ***********************************************************************
+ */
+
+#include "sysincl.h"
+
+/* typedef a 32-bit type */
+typedef uint32_t UINT4;
+
+/* Data structure for MD5 (Message-Digest) computation */
+typedef struct {
+ UINT4 i[2]; /* number of _bits_ handled mod 2^64 */
+ UINT4 buf[4]; /* scratch buffer */
+ unsigned char in[64]; /* input buffer */
+ unsigned char digest[16]; /* actual digest after MD5Final call */
+} MD5_CTX;
+
+void MD5Init (MD5_CTX *mdContext);
+void MD5Update (MD5_CTX *, unsigned const char *, unsigned int);
+void MD5Final (MD5_CTX *);
+
+/*
+ ***********************************************************************
+ ** End of md5.h **
+ ******************************** (cut) ********************************
+ */
diff --git a/memory.c b/memory.c
new file mode 100644
index 0000000..25a484d
--- /dev/null
+++ b/memory.c
@@ -0,0 +1,98 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014, 2017
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Utility functions for memory allocation.
+
+ */
+
+#include "config.h"
+
+#include "logging.h"
+#include "memory.h"
+
+void *
+Malloc(size_t size)
+{
+ void *r;
+
+ r = malloc(size);
+ if (!r && size)
+ LOG_FATAL("Could not allocate memory");
+
+ return r;
+}
+
+void *
+Realloc(void *ptr, size_t size)
+{
+ void *r;
+
+ if (size == 0) {
+ Free(ptr);
+ return NULL;
+ }
+
+ r = realloc(ptr, size);
+ if (!r)
+ LOG_FATAL("Could not allocate memory");
+
+ return r;
+}
+
+static size_t
+get_array_size(size_t nmemb, size_t size)
+{
+ size_t array_size;
+
+ array_size = nmemb * size;
+
+ /* Check for overflow */
+ if (nmemb > 0 && array_size / nmemb != size)
+ LOG_FATAL("Could not allocate memory");
+
+ return array_size;
+}
+
+void *
+Malloc2(size_t nmemb, size_t size)
+{
+ return Malloc(get_array_size(nmemb, size));
+}
+
+void *
+Realloc2(void *ptr, size_t nmemb, size_t size)
+{
+ return Realloc(ptr, get_array_size(nmemb, size));
+}
+
+char *
+Strdup(const char *s)
+{
+ void *r;
+
+ r = strdup(s);
+ if (!r)
+ LOG_FATAL("Could not allocate memory");
+
+ return r;
+}
diff --git a/memory.h b/memory.h
new file mode 100644
index 0000000..7ae2c03
--- /dev/null
+++ b/memory.h
@@ -0,0 +1,45 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 memory functions
+ */
+
+#ifndef GOT_MEMORY_H
+#define GOT_MEMORY_H
+
+#include "sysincl.h"
+
+/* Wrappers checking for errors */
+extern void *Malloc(size_t size);
+extern void *Realloc(void *ptr, size_t size);
+extern void *Malloc2(size_t nmemb, size_t size);
+extern void *Realloc2(void *ptr, size_t nmemb, size_t size);
+extern char *Strdup(const char *s);
+
+/* Convenient macros */
+#define MallocNew(T) ((T *) Malloc(sizeof(T)))
+#define MallocArray(T, n) ((T *) Malloc2(n, sizeof(T)))
+#define ReallocArray(T, n, x) ((T *) Realloc2((void *)(x), n, sizeof(T)))
+#define Free(x) free(x)
+
+#endif /* GOT_MEMORY_H */
diff --git a/nameserv.c b/nameserv.c
new file mode 100644
index 0000000..9f7e648
--- /dev/null
+++ b/nameserv.c
@@ -0,0 +1,166 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009-2011
+ *
+ * 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 to do name to IP address conversion
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <netdb.h>
+#include <resolv.h>
+
+#include "nameserv.h"
+#include "socket.h"
+#include "util.h"
+
+/* ================================================== */
+
+static int address_family = IPADDR_UNSPEC;
+
+void
+DNS_SetAddressFamily(int family)
+{
+ address_family = family;
+}
+
+DNS_Status
+DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs)
+{
+ struct addrinfo hints, *res, *ai;
+ int i, result;
+ IPAddr ip;
+
+ max_addrs = MIN(max_addrs, DNS_MAX_ADDRESSES);
+
+ for (i = 0; i < max_addrs; i++)
+ ip_addrs[i].family = IPADDR_UNSPEC;
+
+ /* Avoid calling getaddrinfo() if the name is an IP address */
+ if (UTI_StringToIP(name, &ip)) {
+ if (address_family != IPADDR_UNSPEC && ip.family != address_family)
+ return DNS_Failure;
+ if (max_addrs >= 1)
+ ip_addrs[0] = ip;
+ return DNS_Success;
+ }
+
+ memset(&hints, 0, sizeof (hints));
+
+ switch (address_family) {
+ case IPADDR_INET4:
+ hints.ai_family = AF_INET;
+ break;
+#ifdef FEAT_IPV6
+ case IPADDR_INET6:
+ hints.ai_family = AF_INET6;
+ break;
+#endif
+ default:
+ hints.ai_family = AF_UNSPEC;
+ }
+ hints.ai_socktype = SOCK_DGRAM;
+
+ result = getaddrinfo(name, NULL, &hints, &res);
+
+ if (result) {
+#ifdef FORCE_DNSRETRY
+ return DNS_TryAgain;
+#else
+ return result == EAI_AGAIN ? DNS_TryAgain : DNS_Failure;
+#endif
+ }
+
+ for (ai = res, i = 0; i < max_addrs && ai != NULL; ai = ai->ai_next) {
+ switch (ai->ai_family) {
+ case AF_INET:
+ if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET4)
+ continue;
+ ip_addrs[i].family = IPADDR_INET4;
+ ip_addrs[i].addr.in4 = ntohl(((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr);
+ i++;
+ break;
+#ifdef FEAT_IPV6
+ case AF_INET6:
+ if (address_family != IPADDR_UNSPEC && address_family != IPADDR_INET6)
+ continue;
+ /* Don't return an address that would lose a scope ID */
+ if (((struct sockaddr_in6 *)ai->ai_addr)->sin6_scope_id != 0)
+ continue;
+ ip_addrs[i].family = IPADDR_INET6;
+ memcpy(&ip_addrs[i].addr.in6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr.s6_addr,
+ sizeof (ip_addrs->addr.in6));
+ i++;
+ break;
+#endif
+ }
+ }
+
+ freeaddrinfo(res);
+
+ return !max_addrs || ip_addrs[0].family != IPADDR_UNSPEC ? DNS_Success : DNS_Failure;
+}
+
+/* ================================================== */
+
+int
+DNS_IPAddress2Name(IPAddr *ip_addr, char *name, int len)
+{
+ char *result = NULL;
+#ifdef FEAT_IPV6
+ struct sockaddr_in6 saddr;
+#else
+ struct sockaddr_in saddr;
+#endif
+ IPSockAddr ip_saddr;
+ socklen_t slen;
+ char hbuf[NI_MAXHOST];
+
+ ip_saddr.ip_addr = *ip_addr;
+ ip_saddr.port = 0;
+
+ slen = SCK_IPSockAddrToSockaddr(&ip_saddr, (struct sockaddr *)&saddr, sizeof (saddr));
+ if (!getnameinfo((struct sockaddr *)&saddr, slen, hbuf, sizeof (hbuf), NULL, 0, 0))
+ result = hbuf;
+
+ if (result == NULL)
+ result = UTI_IPToString(ip_addr);
+ if (snprintf(name, len, "%s", result) >= len)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+DNS_Reload(void)
+{
+ res_init();
+}
+
+/* ================================================== */
+
diff --git a/nameserv.h b/nameserv.h
new file mode 100644
index 0000000..dbef61a
--- /dev/null
+++ b/nameserv.h
@@ -0,0 +1,52 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Module header for nameserver functions
+ */
+
+
+#ifndef GOT_NAMESERV_H
+#define GOT_NAMESERV_H
+
+#include "addressing.h"
+
+typedef enum {
+ DNS_Success,
+ DNS_TryAgain,
+ DNS_Failure
+} DNS_Status;
+
+/* Resolve names only to selected address family */
+extern void DNS_SetAddressFamily(int family);
+
+/* Maximum number of addresses returned by DNS_Name2IPAddress */
+#define DNS_MAX_ADDRESSES 16
+
+extern DNS_Status DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs);
+
+extern int DNS_IPAddress2Name(IPAddr *ip_addr, char *name, int len);
+
+extern void DNS_Reload(void);
+
+#endif /* GOT_NAMESERV_H */
+
diff --git a/nameserv_async.c b/nameserv_async.c
new file mode 100644
index 0000000..118443c
--- /dev/null
+++ b/nameserv_async.c
@@ -0,0 +1,130 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 to asynchronously convert name to IP address
+
+ */
+
+#include "config.h"
+#include "sysincl.h"
+
+#include "nameserv_async.h"
+#include "logging.h"
+#include "memory.h"
+#include "privops.h"
+#include "sched.h"
+#include "util.h"
+
+#ifdef USE_PTHREAD_ASYNCDNS
+#include <pthread.h>
+
+/* ================================================== */
+
+struct DNS_Async_Instance {
+ const char *name;
+ DNS_Status status;
+ IPAddr addresses[DNS_MAX_ADDRESSES];
+ DNS_NameResolveHandler handler;
+ void *arg;
+
+ pthread_t thread;
+ int pipe[2];
+};
+
+static pthread_mutex_t privops_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* ================================================== */
+
+static void *
+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)
+ ;
+
+ return NULL;
+}
+
+/* ================================================== */
+
+static void
+end_resolving(int fd, int event, void *anything)
+{
+ struct DNS_Async_Instance *inst = (struct DNS_Async_Instance *)anything;
+ int i;
+
+ if (pthread_join(inst->thread, NULL)) {
+ LOG_FATAL("pthread_join() failed");
+ }
+
+ SCH_RemoveFileHandler(inst->pipe[0]);
+ close(inst->pipe[0]);
+ close(inst->pipe[1]);
+
+ for (i = 0; inst->status == DNS_Success && i < DNS_MAX_ADDRESSES &&
+ inst->addresses[i].family != IPADDR_UNSPEC; i++)
+ ;
+
+ (inst->handler)(inst->status, i, inst->addresses, inst->arg);
+
+ Free(inst);
+}
+
+/* ================================================== */
+
+void
+DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *anything)
+{
+ struct DNS_Async_Instance *inst;
+
+ inst = MallocNew(struct DNS_Async_Instance);
+ inst->name = name;
+ inst->handler = handler;
+ inst->arg = anything;
+ inst->status = DNS_Failure;
+
+ if (pipe(inst->pipe)) {
+ LOG_FATAL("pipe() failed");
+ }
+
+ UTI_FdSetCloexec(inst->pipe[0]);
+ UTI_FdSetCloexec(inst->pipe[1]);
+
+ if (pthread_create(&inst->thread, NULL, start_resolving, inst)) {
+ LOG_FATAL("pthread_create() failed");
+ }
+
+ SCH_AddFileHandler(inst->pipe[0], SCH_FILE_INPUT, end_resolving, inst);
+}
+
+/* ================================================== */
+
+#else
+#error
+#endif
diff --git a/nameserv_async.h b/nameserv_async.h
new file mode 100644
index 0000000..b8479e1
--- /dev/null
+++ b/nameserv_async.h
@@ -0,0 +1,40 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 asynchronous nameserver functions
+ */
+
+
+#ifndef GOT_NAMESERV_ASYNC_H
+#define GOT_NAMESERV_ASYNC_H
+
+#include "nameserv.h"
+
+/* Function type for callback to process the result */
+typedef void (*DNS_NameResolveHandler)(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *anything);
+
+/* Request resolving of a name to IP address. The handler will be
+ called when the result is available. */
+extern void DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *anything);
+
+#endif
diff --git a/ntp.h b/ntp.h
new file mode 100644
index 0000000..165adbc
--- /dev/null
+++ b/ntp.h
@@ -0,0 +1,200 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * 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 containing common NTP bits and pieces
+ */
+
+#ifndef GOT_NTP_H
+#define GOT_NTP_H
+
+#include "sysincl.h"
+
+#include "hash.h"
+
+typedef struct {
+ uint32_t hi;
+ uint32_t lo;
+} NTP_int64;
+
+typedef uint32_t NTP_int32;
+
+/* The UDP port number used by NTP */
+#define NTP_PORT 123
+
+/* The NTP protocol version that we support */
+#define NTP_VERSION 4
+
+/* Maximum stratum number (infinity) */
+#define NTP_MAX_STRATUM 16
+
+/* 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)
+
+/* Type definition for leap bits */
+typedef enum {
+ LEAP_Normal = 0,
+ LEAP_InsertSecond = 1,
+ LEAP_DeleteSecond = 2,
+ LEAP_Unsynchronised = 3
+} NTP_Leap;
+
+typedef enum {
+ MODE_UNDEFINED = 0,
+ MODE_ACTIVE = 1,
+ MODE_PASSIVE = 2,
+ MODE_CLIENT = 3,
+ MODE_SERVER = 4,
+ MODE_BROADCAST = 5
+} NTP_Mode;
+
+typedef struct {
+ uint8_t lvm;
+ uint8_t stratum;
+ int8_t poll;
+ int8_t precision;
+ NTP_int32 root_delay;
+ NTP_int32 root_dispersion;
+ NTP_int32 reference_id;
+ NTP_int64 reference_ts;
+ NTP_int64 originate_ts;
+ NTP_int64 receive_ts;
+ NTP_int64 transmit_ts;
+
+ uint8_t extensions[NTP_MAX_EXTENSIONS_LENGTH];
+} NTP_Packet;
+
+#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)
+#define NTP_LVM_TO_VERSION(lvm) (((lvm) >> 3) & 0x7)
+#define NTP_LVM_TO_MODE(lvm) ((lvm) & 0x7)
+#define NTP_LVM(leap, version, mode) \
+ ((((leap) << 6) & 0xc0) | (((version) << 3) & 0x38) | ((mode) & 0x07))
+
+/* Special NTP reference IDs */
+#define NTP_REFID_UNSYNC 0x0UL
+#define NTP_REFID_LOCAL 0x7F7F0101UL /* 127.127.1.1 */
+#define NTP_REFID_SMOOTH 0x7F7F01FFUL /* 127.127.1.255 */
+
+/* Non-authentication extension fields and corresponding internal flags */
+
+#define NTP_EF_EXP_MONO_ROOT 0xF323
+#define NTP_EF_EXP_NET_CORRECTION 0xF324
+
+#define NTP_EF_FLAG_EXP_MONO_ROOT 0x1
+#define NTP_EF_FLAG_EXP_NET_CORRECTION 0x2
+
+/* Pre-NTPv5 experimental extension field */
+typedef struct {
+ uint32_t magic;
+ NTP_int32 root_delay;
+ NTP_int32 root_dispersion;
+ NTP_int64 mono_receive_ts;
+ uint32_t mono_epoch;
+} NTP_EFExpMonoRoot;
+
+#define NTP_EF_EXP_MONO_ROOT_MAGIC 0xF5BEDD9AU
+
+/* Experimental extension field to provide PTP corrections */
+typedef struct {
+ uint32_t magic;
+ NTP_int64 correction;
+ uint32_t reserved[3];
+} NTP_EFExpNetCorrection;
+
+#define NTP_EF_EXP_NET_CORRECTION_MAGIC 0x07AC2CEBU
+
+/* Authentication extension fields */
+
+#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
+
+/* Enumeration for authentication modes of NTP packets */
+typedef enum {
+ NTP_AUTH_NONE = 0, /* No authentication */
+ NTP_AUTH_SYMMETRIC, /* NTP MAC or CMAC using a symmetric key
+ (RFC 1305, RFC 5905, RFC 8573) */
+ NTP_AUTH_MSSNTP, /* MS-SNTP authenticator field */
+ NTP_AUTH_MSSNTP_EXT, /* MS-SNTP extended authenticator field */
+ NTP_AUTH_NTS, /* Network Time Security (RFC 8915) */
+} NTP_AuthMode;
+
+/* Structure describing an NTP packet */
+typedef struct {
+ int length;
+ int version;
+ NTP_Mode mode;
+
+ int ext_fields;
+ int ext_field_flags;
+
+ 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
+ source). root_delay/root_dispersion include peer_delay/peer_dispersion. */
+typedef struct {
+ struct timespec time;
+ double offset;
+ double peer_delay;
+ double peer_dispersion;
+ double root_delay;
+ double root_dispersion;
+} NTP_Sample;
+
+/* Possible sources of timestamps */
+typedef enum {
+ NTP_TS_DAEMON = 0,
+ NTP_TS_KERNEL,
+ NTP_TS_HARDWARE
+} NTP_Timestamp_Source;
+
+#endif /* GOT_NTP_H */
diff --git a/ntp_auth.c b/ntp_auth.c
new file mode 100644
index 0000000..58374c5
--- /dev/null
+++ b/ntp_auth.c
@@ -0,0 +1,386 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2019-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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ NTP authentication
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "keys.h"
+#include "logging.h"
+#include "memory.h"
+#include "ntp_auth.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;
+
+ if (info->length + NTP_MIN_MAC_LENGTH > sizeof (*packet)) {
+ DEBUG_LOG("Packet too long");
+ return 0;
+ }
+
+ /* 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 (*packet) - info->length - 4);
+
+ auth_len = KEY_GenerateAuth(key_id, packet, info->length,
+ (unsigned char *)packet + info->length + 4, max_auth_len);
+ if (auth_len < NTP_MIN_MAC_LENGTH - 4) {
+ 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->auth.mac.start = info->length;
+ info->auth.mac.length = 4 + auth_len;
+ info->auth.mac.key_id = key_id;
+ info->length += info->auth.mac.length;
+
+ 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, 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 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, uint32_t cert_set,
+ uint16_t ntp_port)
+{
+ NAU_Instance instance = create_instance(NTP_AUTH_NTS);
+
+ instance->nts = NNC_CreateInstance(nts_address, name, cert_set, ntp_port);
+
+ return instance;
+}
+
+/* ================================================== */
+
+void
+NAU_DestroyInstance(NAU_Instance instance)
+{
+ if (instance->mode == NTP_AUTH_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;
+}
+
+/* ================================================== */
+
+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);
+ }
+
+ info->auth.mode = instance->mode;
+
+ return 1;
+}
+
+/* ================================================== */
+
+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_MSSNTP_EXT:
+ /* Not supported yet */
+ return 0;
+ case NTP_AUTH_NTS:
+ if (!NNS_CheckRequestAuth(request, info, kod))
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+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;
+ }
+
+ response_info->auth.mode = request_info->auth.mode;
+
+ 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;
+ }
+}
+
+/* ================================================== */
+
+void
+NAU_GetReport(NAU_Instance instance, RPT_AuthReport *report)
+{
+ memset(report, 0, sizeof (*report));
+
+ report->mode = instance->mode;
+ report->last_ke_ago = -1;
+
+ switch (instance->mode) {
+ case NTP_AUTH_NONE:
+ break;
+ case NTP_AUTH_SYMMETRIC:
+ report->key_id = instance->key_id;
+ KEY_GetKeyInfo(instance->key_id, &report->key_type, &report->key_length);
+ break;
+ case NTP_AUTH_NTS:
+ NNC_GetReport(instance->nts, report);
+ break;
+ default:
+ assert(0);
+ }
+}
diff --git a/ntp_auth.h b/ntp_auth.h
new file mode 100644
index 0000000..0b8a825
--- /dev/null
+++ b/ntp_auth.h
@@ -0,0 +1,84 @@
+/*
+ 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"
+#include "reports.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,
+ uint32_t cert_set, uint16_t ntp_port);
+
+/* 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);
+
+/* Extend a request with data required by the authentication mode */
+extern int NAU_GenerateRequestAuth(NAU_Instance instance, NTP_Packet *request,
+ 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);
+
+/* 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);
+
+/* Provide a report about the current authentication state */
+extern void NAU_GetReport(NAU_Instance instance, RPT_AuthReport *report);
+
+#endif
diff --git a/ntp_core.c b/ntp_core.c
new file mode 100644
index 0000000..023e60b
--- /dev/null
+++ b/ntp_core.c
@@ -0,0 +1,3267 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009-2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Core NTP protocol engine
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "ntp_auth.h"
+#include "ntp_core.h"
+#include "ntp_ext.h"
+#include "ntp_io.h"
+#include "memory.h"
+#include "quantiles.h"
+#include "sched.h"
+#include "reference.h"
+#include "local.h"
+#include "samplefilt.h"
+#include "smooth.h"
+#include "sources.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "addrfilt.h"
+#include "clientlog.h"
+
+/* ================================================== */
+
+static LOG_FileID logfileid;
+static int log_raw_measurements;
+
+/* ================================================== */
+/* Enumeration used for remembering the operating mode of one of the
+ sources */
+
+typedef enum {
+ MD_OFFLINE, /* No sampling at all */
+ MD_ONLINE, /* Normal sampling based on sampling interval */
+ MD_BURST_WAS_OFFLINE, /* Burst sampling, return to offline afterwards */
+ MD_BURST_WAS_ONLINE, /* Burst sampling, return to online afterwards */
+} OperatingMode;
+
+/* Structure holding a response and other data waiting to be processed when
+ a late HW transmit timestamp of the request is available, or a timeout is
+ reached */
+struct SavedResponse {
+ NTP_Local_Address local_addr;
+ NTP_Local_Timestamp rx_ts;
+ NTP_Packet message;
+ NTP_PacketInfo info;
+ SCH_TimeoutID timeout_id;
+};
+
+/* ================================================== */
+/* Structure used for holding a single peer/server's
+ protocol machine */
+
+struct NCR_Instance_Record {
+ NTP_Remote_Address remote_addr; /* Needed for routing transmit packets */
+ NTP_Local_Address local_addr; /* Local address/socket used to send packets */
+ NTP_Mode mode; /* The source's NTP mode
+ (client/server or symmetric active peer) */
+ int interleaved; /* Boolean enabling interleaved NTP mode */
+ OperatingMode opmode; /* Whether we are sampling this source
+ or not and in what way */
+ SCH_TimeoutID rx_timeout_id; /* Timeout ID for latest received response */
+ SCH_TimeoutID tx_timeout_id; /* Timeout ID for next transmission */
+ int tx_suspended; /* Boolean indicating we can't transmit yet */
+
+ int auto_iburst; /* If 1, initiate a burst when going online */
+ int auto_burst; /* If 1, initiate a burst on each poll */
+ int auto_offline; /* If 1, automatically go offline when requests
+ cannot be sent */
+
+ int local_poll; /* Log2 of polling interval at our end */
+ int remote_poll; /* Log2 of server/peer's polling interval (recovered
+ from received packets) */
+ int remote_stratum; /* Stratum of the server/peer (recovered from
+ received packets) */
+ double remote_root_delay; /* Root delay from last valid packet */
+ double remote_root_dispersion;/* Root dispersion from last valid packet */
+
+ int presend_minpoll; /* If the current polling interval is
+ at least this, an extra client packet
+ will be send some time before normal
+ transmit. This ensures that both
+ us and the server/peer have an ARP
+ entry for each other ready, which
+ means our measurement is not
+ botched by an ARP round-trip on one
+ side or the other. */
+
+ int presend_done; /* The presend packet has been sent */
+
+ int minpoll; /* Log2 of minimum defined polling interval */
+ int maxpoll; /* Log2 of maximum defined polling interval */
+
+ int min_stratum; /* Increase stratum in received packets to the
+ minimum */
+
+ int copy; /* Boolean suppressing own refid and stratum */
+
+ int poll_target; /* Target number of sourcestats samples */
+
+ int version; /* Version set in packets for server/peer */
+
+ double poll_score; /* Score of current local poll */
+
+ double max_delay; /* Maximum round-trip delay to the
+ peer that we can tolerate and still
+ use the sample for generating
+ statistics from */
+
+ double max_delay_ratio; /* Largest ratio of delay /
+ min_delay_in_register that we can
+ tolerate. */
+
+ double max_delay_dev_ratio; /* Maximum ratio of increase in delay / stddev */
+
+ double offset_correction; /* Correction applied to measured offset
+ (e.g. for asymmetry in network delay) */
+
+ int ext_field_flags; /* Enabled extension fields */
+
+ uint32_t remote_mono_epoch; /* ID of the source's monotonic scale */
+ double mono_doffset; /* Accumulated offset between source's
+ real-time and monotonic scales */
+
+ NAU_Instance auth; /* Authentication */
+
+ /* Count of transmitted packets since last valid response */
+ unsigned int tx_count;
+
+ /* Flag indicating a valid response was received since last request */
+ int valid_rx;
+
+ /* Flag indicating the timestamps below are from a valid packet and may
+ be used for synchronisation */
+ int valid_timestamps;
+
+ /* Receive and transmit timestamps from the last valid response */
+ NTP_int64 remote_ntp_monorx;
+ NTP_int64 remote_ntp_rx;
+ NTP_int64 remote_ntp_tx;
+
+ /* Local timestamp when the last valid response was received from the
+ source. We have to be prepared to tinker with this if the local
+ clock has its frequency adjusted before we repond. The value we
+ store here is what our own local time was when the same arrived.
+ Before replying, we have to correct this to fit with the
+ parameters for the current reference. (It must be stored
+ relative to local time to permit frequency and offset adjustments
+ to be made when we trim the local clock). */
+ NTP_int64 local_ntp_rx;
+ NTP_Local_Timestamp local_rx;
+
+ /* Local timestamp when we last transmitted a packet to the source.
+ We store two versions. The first is in NTP format, and is used
+ to validate the next received packet from the source.
+ Additionally, this is corrected to bring it into line with the
+ current reference. The second is in timespec format, and is kept
+ relative to the local clock. We modify this in accordance with
+ local clock frequency/offset changes, and use this for computing
+ statistics about the source when a return packet arrives. */
+ NTP_int64 local_ntp_tx;
+ NTP_Local_Timestamp local_tx;
+
+ /* Previous values of some variables needed in interleaved mode */
+ NTP_Local_Timestamp prev_local_tx;
+ int prev_local_poll;
+ unsigned int prev_tx_count;
+
+ /* Flag indicating the two timestamps below were updated since the
+ last transmission */
+ int updated_init_timestamps;
+
+ /* Timestamps used for (re)starting the symmetric protocol, when we
+ need to respond to a packet which is not a valid response */
+ NTP_int64 init_remote_ntp_tx;
+ NTP_Local_Timestamp init_local_rx;
+
+ /* The instance record in the main source management module. This
+ performs the statistical analysis on the samples we generate */
+
+ SRC_Instance source;
+
+ /* Optional long-term quantile estimate of peer delay */
+ QNT_Instance delay_quant;
+
+ /* Optional median filter for NTP measurements */
+ SPF_Instance filter;
+ int filter_count;
+
+ /* Response waiting for a HW transmit timestamp of the request */
+ struct SavedResponse *saved_response;
+
+ int burst_good_samples_to_go;
+ int burst_total_samples_to_go;
+
+ /* Report from last valid response */
+ RPT_NTPReport report;
+};
+
+typedef struct {
+ NTP_Remote_Address addr;
+ NTP_Local_Address local_addr;
+ NAU_Instance auth;
+ int interval;
+} BroadcastDestination;
+
+/* Array of BroadcastDestination */
+static ARR_Instance broadcasts;
+
+/* ================================================== */
+/* Initial delay period before first packet is transmitted (in seconds) */
+#define INITIAL_DELAY 0.2
+
+/* Spacing required between samples for any two servers/peers (to
+ minimise risk of network collisions) (in seconds) */
+#define MIN_SAMPLING_SEPARATION 0.002
+#define MAX_SAMPLING_SEPARATION 0.2
+
+/* Randomness added to spacing between samples for one server/peer */
+#define SAMPLING_RANDOMNESS 0.02
+
+/* Adjustment of the peer polling interval */
+#define PEER_SAMPLING_ADJ 1.1
+
+/* Maximum spacing between samples in the burst mode as an absolute
+ value and ratio to the normal polling interval */
+#define MAX_BURST_INTERVAL 2.0
+#define MAX_BURST_POLL_RATIO 0.25
+
+/* Number of samples in initial burst */
+#define IBURST_GOOD_SAMPLES 4
+#define IBURST_TOTAL_SAMPLES SOURCE_REACH_BITS
+
+/* Number of samples in automatic burst */
+#define BURST_GOOD_SAMPLES 1
+#define MAX_BURST_TOTAL_SAMPLES 4
+
+/* Time to wait after sending packet to 'warm up' link */
+#define WARM_UP_DELAY 2.0
+
+/* Compatible NTP protocol versions */
+#define NTP_MAX_COMPAT_VERSION NTP_VERSION
+#define NTP_MIN_COMPAT_VERSION 1
+
+/* Maximum allowed dispersion - as defined in RFC 5905 (16 seconds) */
+#define NTP_MAX_DISPERSION 16.0
+
+/* Maximum allowed time for server to process client packet */
+#define MAX_SERVER_INTERVAL 4.0
+
+/* Maximum acceptable delay in transmission for timestamp correction */
+#define MAX_TX_DELAY 1.0
+
+/* Maximum allowed values of maxdelay parameters */
+#define MAX_MAXDELAY 1.0e3
+#define MAX_MAXDELAYRATIO 1.0e6
+#define MAX_MAXDELAYDEVRATIO 1.0e6
+
+/* Parameters for the peer delay quantile */
+#define DELAY_QUANT_Q 100
+#define DELAY_QUANT_REPEAT 7
+
+/* Minimum and maximum allowed poll interval */
+#define MIN_POLL -7
+#define MAX_POLL 24
+
+/* Enable sub-second polling intervals only when the peer delay is not
+ longer than 10 milliseconds to restrict them to local networks */
+#define MIN_NONLAN_POLL 0
+#define MAX_LAN_PEER_DELAY 0.01
+
+/* Kiss-o'-Death codes */
+#define KOD_RATE 0x52415445UL /* RATE */
+
+/* Maximum poll interval set by KoD RATE */
+#define MAX_KOD_RATE_POLL SRC_DEFAULT_MAXPOLL
+
+/* Maximum number of missed responses to accept samples using old timestamps
+ in the interleaved client/server mode */
+#define MAX_CLIENT_INTERLEAVED_TX 4
+
+/* Maximum ratio of local intervals in the timestamp selection of the
+ interleaved mode to prefer a sample using previous timestamps */
+#define MAX_INTERLEAVED_L2L_RATIO 0.1
+
+/* Maximum acceptable change in server mono<->real offset */
+#define MAX_MONO_DOFFSET 16.0
+
+/* Maximum assumed frequency error in network corrections */
+#define MAX_NET_CORRECTION_FREQ 100.0e-6
+
+/* Invalid socket, different from the one in ntp_io.c */
+#define INVALID_SOCK_FD -2
+
+/* ================================================== */
+
+/* Server IPv4/IPv6 sockets */
+static int server_sock_fd4;
+static int server_sock_fd6;
+
+static ADF_AuthTable access_auth_table;
+
+/* Current offset between monotonic and cooked time, and its epoch ID
+ which is reset on clock steps */
+static double server_mono_offset;
+static uint32_t server_mono_epoch;
+
+/* Characters for printing synchronisation status and timestamping source */
+static const char leap_chars[4] = {'N', '+', '-', '?'};
+static const char tss_chars[3] = {'D', 'K', 'H'};
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void transmit_timeout(void *arg);
+static double get_transmit_delay(NCR_Instance inst, int on_tx);
+static double get_separation(int poll);
+static int parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info);
+static void process_sample(NCR_Instance inst, NTP_Sample *sample);
+static int has_saved_response(NCR_Instance inst);
+static void process_saved_response(NCR_Instance inst);
+static int process_response(NCR_Instance inst, int saved, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *rx_ts, NTP_Packet *message,
+ NTP_PacketInfo *info);
+static void set_connectivity(NCR_Instance inst, SRC_Connectivity connectivity);
+
+/* ================================================== */
+
+static void
+do_size_checks(void)
+{
+ /* Assertions to check the sizes of certain data types
+ and the positions of certain record fields */
+
+ /* Check that certain invariants are true */
+ assert(sizeof(NTP_int32) == 4);
+ assert(sizeof(NTP_int64) == 8);
+
+ /* Check offsets of all fields in the NTP packet format */
+ assert(offsetof(NTP_Packet, lvm) == 0);
+ assert(offsetof(NTP_Packet, stratum) == 1);
+ assert(offsetof(NTP_Packet, poll) == 2);
+ assert(offsetof(NTP_Packet, precision) == 3);
+ assert(offsetof(NTP_Packet, root_delay) == 4);
+ assert(offsetof(NTP_Packet, root_dispersion) == 8);
+ assert(offsetof(NTP_Packet, reference_id) == 12);
+ assert(offsetof(NTP_Packet, reference_ts) == 16);
+ assert(offsetof(NTP_Packet, originate_ts) == 24);
+ assert(offsetof(NTP_Packet, receive_ts) == 32);
+ assert(offsetof(NTP_Packet, transmit_ts) == 40);
+
+ assert(sizeof (NTP_EFExpMonoRoot) == 24);
+ assert(sizeof (NTP_EFExpNetCorrection) == 24);
+}
+
+/* ================================================== */
+
+static void
+do_time_checks(void)
+{
+ struct timespec now;
+ time_t warning_advance = 3600 * 24 * 365 * 10; /* 10 years */
+
+#ifdef HAVE_LONG_TIME_T
+ /* Check that time before NTP_ERA_SPLIT underflows correctly */
+
+ struct timespec ts1 = {NTP_ERA_SPLIT, 1}, ts2 = {NTP_ERA_SPLIT - 1, 1};
+ NTP_int64 nts1, nts2;
+ int r;
+
+ UTI_TimespecToNtp64(&ts1, &nts1, NULL);
+ UTI_TimespecToNtp64(&ts2, &nts2, NULL);
+ UTI_Ntp64ToTimespec(&nts1, &ts1);
+ UTI_Ntp64ToTimespec(&nts2, &ts2);
+
+ r = ts1.tv_sec == NTP_ERA_SPLIT &&
+ ts1.tv_sec + (1ULL << 32) - 1 == ts2.tv_sec;
+
+ assert(r);
+
+ LCL_ReadRawTime(&now);
+ if (ts2.tv_sec - now.tv_sec < warning_advance)
+ LOG(LOGS_WARN, "Assumed NTP time ends at %s!", UTI_TimeToLogForm(ts2.tv_sec));
+#else
+ LCL_ReadRawTime(&now);
+ if (now.tv_sec > 0x7fffffff - warning_advance)
+ LOG(LOGS_WARN, "System time ends at %s!", UTI_TimeToLogForm(0x7fffffff));
+#endif
+}
+
+/* ================================================== */
+
+static void
+zero_local_timestamp(NTP_Local_Timestamp *ts)
+{
+ UTI_ZeroTimespec(&ts->ts);
+ ts->err = 0.0;
+ ts->source = NTP_TS_DAEMON;
+ ts->rx_duration = 0.0;
+ ts->net_correction = 0.0;
+}
+
+/* ================================================== */
+
+static void
+handle_slew(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything)
+{
+ if (change_type == LCL_ChangeAdjust) {
+ server_mono_offset += doffset;
+ } else {
+ UTI_GetRandomBytes(&server_mono_epoch, sizeof (server_mono_epoch));
+ server_mono_offset = 0.0;
+ }
+}
+
+/* ================================================== */
+
+void
+NCR_Initialise(void)
+{
+ do_size_checks();
+ do_time_checks();
+
+ logfileid = CNF_GetLogMeasurements(&log_raw_measurements) ? LOG_FileOpen("measurements",
+ " Date (UTC) Time IP Address L St 123 567 ABCD LP RP Score Offset Peer del. Peer disp. Root del. Root disp. Refid MTxRx")
+ : -1;
+
+ access_auth_table = ADF_CreateTable();
+ broadcasts = ARR_CreateInstance(sizeof (BroadcastDestination));
+
+ /* Server socket will be opened when access is allowed */
+ server_sock_fd4 = INVALID_SOCK_FD;
+ server_sock_fd6 = INVALID_SOCK_FD;
+
+ LCL_AddParameterChangeHandler(handle_slew, NULL);
+ handle_slew(NULL, NULL, 0.0, 0.0, LCL_ChangeUnknownStep, NULL);
+}
+
+/* ================================================== */
+
+void
+NCR_Finalise(void)
+{
+ unsigned int i;
+
+ LCL_RemoveParameterChangeHandler(handle_slew, NULL);
+
+ if (server_sock_fd4 != INVALID_SOCK_FD)
+ NIO_CloseServerSocket(server_sock_fd4);
+ if (server_sock_fd6 != INVALID_SOCK_FD)
+ NIO_CloseServerSocket(server_sock_fd6);
+
+ 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);
+}
+
+/* ================================================== */
+
+static void
+restart_timeout(NCR_Instance inst, double delay)
+{
+ /* Check if we can transmit */
+ if (inst->tx_suspended) {
+ assert(!inst->tx_timeout_id);
+ return;
+ }
+
+ /* Stop both rx and tx timers if running */
+ SCH_RemoveTimeout(inst->rx_timeout_id);
+ inst->rx_timeout_id = 0;
+ SCH_RemoveTimeout(inst->tx_timeout_id);
+
+ /* Start new timer for transmission */
+ inst->tx_timeout_id = SCH_AddTimeoutInClass(delay, get_separation(inst->local_poll),
+ SAMPLING_RANDOMNESS,
+ inst->mode == MODE_CLIENT ?
+ SCH_NtpClientClass : SCH_NtpPeerClass,
+ transmit_timeout, (void *)inst);
+}
+
+/* ================================================== */
+
+static void
+start_initial_timeout(NCR_Instance inst)
+{
+ double delay;
+
+ if (!inst->tx_timeout_id) {
+ /* This will be the first transmission after mode change */
+
+ /* Mark source active */
+ SRC_SetActive(inst->source);
+ }
+
+ /* In case the offline period was too short, adjust the delay to keep
+ the interval between packets at least as long as the current polling
+ interval */
+ if (!UTI_IsZeroTimespec(&inst->local_tx.ts)) {
+ delay = get_transmit_delay(inst, 0);
+ } else {
+ delay = 0.0;
+ }
+
+ if (delay < INITIAL_DELAY)
+ delay = INITIAL_DELAY;
+
+ restart_timeout(inst, delay);
+}
+
+/* ================================================== */
+
+static void
+close_client_socket(NCR_Instance inst)
+{
+ if (inst->mode == MODE_CLIENT && inst->local_addr.sock_fd != INVALID_SOCK_FD) {
+ NIO_CloseClientSocket(inst->local_addr.sock_fd);
+ inst->local_addr.sock_fd = INVALID_SOCK_FD;
+ }
+
+ SCH_RemoveTimeout(inst->rx_timeout_id);
+ inst->rx_timeout_id = 0;
+
+ if (has_saved_response(inst)) {
+ SCH_RemoveTimeout(inst->saved_response->timeout_id);
+ inst->saved_response->timeout_id = 0;
+ }
+}
+
+/* ================================================== */
+
+static void
+take_offline(NCR_Instance inst)
+{
+ inst->opmode = MD_OFFLINE;
+
+ SCH_RemoveTimeout(inst->tx_timeout_id);
+ inst->tx_timeout_id = 0;
+
+ /* Mark source unreachable */
+ SRC_ResetReachability(inst->source);
+
+ /* And inactive */
+ SRC_UnsetActive(inst->source);
+
+ close_client_socket(inst);
+
+ NCR_ResetInstance(inst);
+}
+
+/* ================================================== */
+
+static void
+reset_report(NCR_Instance inst)
+{
+ memset(&inst->report, 0, sizeof (inst->report));
+ inst->report.remote_addr = inst->remote_addr.ip_addr;
+ inst->report.remote_port = inst->remote_addr.port;
+}
+
+/* ================================================== */
+
+NCR_Instance
+NCR_CreateInstance(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
+ SourceParameters *params, const char *name)
+{
+ NCR_Instance result;
+
+ result = MallocNew(struct NCR_Instance_Record);
+
+ result->remote_addr = *remote_addr;
+ result->local_addr.ip_addr.family = IPADDR_UNSPEC;
+ result->local_addr.if_index = INVALID_IF_INDEX;
+
+ switch (type) {
+ case NTP_SERVER:
+ /* Client socket will be obtained when sending request */
+ result->local_addr.sock_fd = INVALID_SOCK_FD;
+ result->mode = MODE_CLIENT;
+ break;
+ case NTP_PEER:
+ result->local_addr.sock_fd = NIO_OpenServerSocket(remote_addr);
+ result->mode = MODE_ACTIVE;
+ break;
+ default:
+ assert(0);
+ }
+
+ result->interleaved = params->interleaved;
+
+ result->minpoll = params->minpoll;
+ if (result->minpoll < MIN_POLL)
+ result->minpoll = SRC_DEFAULT_MINPOLL;
+ else if (result->minpoll > MAX_POLL)
+ result->minpoll = MAX_POLL;
+
+ result->maxpoll = params->maxpoll;
+ if (result->maxpoll < MIN_POLL)
+ result->maxpoll = SRC_DEFAULT_MAXPOLL;
+ else if (result->maxpoll > MAX_POLL)
+ result->maxpoll = MAX_POLL;
+ if (result->maxpoll < result->minpoll)
+ result->maxpoll = result->minpoll;
+
+ result->min_stratum = params->min_stratum;
+ if (result->min_stratum >= NTP_MAX_STRATUM)
+ result->min_stratum = NTP_MAX_STRATUM - 1;
+
+ /* Presend doesn't work in symmetric mode */
+ result->presend_minpoll = params->presend_minpoll;
+ if (result->presend_minpoll <= MAX_POLL && result->mode != MODE_CLIENT)
+ result->presend_minpoll = MAX_POLL + 1;
+
+ result->max_delay = CLAMP(0.0, params->max_delay, MAX_MAXDELAY);
+ result->max_delay_ratio = CLAMP(0.0, params->max_delay_ratio, MAX_MAXDELAYRATIO);
+ result->max_delay_dev_ratio = CLAMP(0.0, params->max_delay_dev_ratio, MAX_MAXDELAYDEVRATIO);
+ result->offset_correction = params->offset;
+ result->auto_iburst = params->iburst;
+ result->auto_burst = params->burst;
+ result->auto_offline = params->auto_offline;
+ result->copy = params->copy && result->mode == MODE_CLIENT;
+ result->poll_target = MAX(1, params->poll_target);
+ result->ext_field_flags = params->ext_fields;
+
+ if (params->nts) {
+ IPSockAddr nts_address;
+
+ 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, params->cert_set,
+ result->remote_addr.port);
+ } else if (params->authkey != INACTIVE_AUTHKEY) {
+ result->auth = NAU_CreateSymmetricInstance(params->authkey);
+ } else {
+ result->auth = NAU_CreateNoneInstance();
+ }
+
+ if (result->ext_field_flags || result->interleaved)
+ result->version = NTP_VERSION;
+ else
+ result->version = NAU_GetSuggestedNtpVersion(result->auth);
+
+ if (params->version)
+ result->version = CLAMP(NTP_MIN_COMPAT_VERSION, params->version, NTP_VERSION);
+
+ /* Create a source instance for this NTP source */
+ result->source = SRC_CreateNewInstance(UTI_IPToRefid(&remote_addr->ip_addr),
+ SRC_NTP, NAU_IsAuthEnabled(result->auth),
+ params->sel_options, &result->remote_addr.ip_addr,
+ params->min_samples, params->max_samples,
+ params->min_delay, params->asymmetry);
+
+ if (params->max_delay_quant > 0.0) {
+ int k = round(CLAMP(0.05, params->max_delay_quant, 0.95) * DELAY_QUANT_Q);
+ result->delay_quant = QNT_CreateInstance(k, k, DELAY_QUANT_Q, DELAY_QUANT_REPEAT,
+ LCL_GetSysPrecisionAsQuantum() / 2.0);
+ } else {
+ result->delay_quant = NULL;
+ }
+
+ if (params->filter_length >= 1)
+ result->filter = SPF_CreateInstance(1, params->filter_length, NTP_MAX_DISPERSION, 0.0);
+ else
+ result->filter = NULL;
+
+ result->saved_response = NULL;
+
+ result->rx_timeout_id = 0;
+ result->tx_timeout_id = 0;
+ result->tx_suspended = 1;
+ result->opmode = MD_OFFLINE;
+ result->local_poll = MAX(result->minpoll, MIN_NONLAN_POLL);
+ result->poll_score = 0.0;
+ zero_local_timestamp(&result->local_tx);
+ result->burst_good_samples_to_go = 0;
+ result->burst_total_samples_to_go = 0;
+
+ NCR_ResetInstance(result);
+
+ set_connectivity(result, params->connectivity);
+
+ reset_report(result);
+
+ return result;
+}
+
+/* ================================================== */
+
+/* Destroy an instance */
+void
+NCR_DestroyInstance(NCR_Instance instance)
+{
+ if (instance->opmode != MD_OFFLINE)
+ take_offline(instance);
+
+ if (instance->mode == MODE_ACTIVE)
+ NIO_CloseServerSocket(instance->local_addr.sock_fd);
+
+ if (instance->delay_quant)
+ QNT_DestroyInstance(instance->delay_quant);
+ if (instance->filter)
+ SPF_DestroyInstance(instance->filter);
+
+ if (instance->saved_response)
+ Free(instance->saved_response);
+
+ NAU_DestroyInstance(instance->auth);
+
+ /* This will destroy the source instance inside the
+ structure, which will cause reselection if this was the
+ synchronising source etc. */
+ SRC_DestroyInstance(instance->source);
+
+ /* Free the data structure */
+ Free(instance);
+}
+
+/* ================================================== */
+
+void
+NCR_StartInstance(NCR_Instance instance)
+{
+ instance->tx_suspended = 0;
+ if (instance->opmode != MD_OFFLINE)
+ start_initial_timeout(instance);
+}
+
+/* ================================================== */
+
+void
+NCR_ResetInstance(NCR_Instance instance)
+{
+ instance->tx_count = 0;
+ instance->presend_done = 0;
+
+ instance->remote_poll = 0;
+ instance->remote_stratum = 0;
+ instance->remote_root_delay = 0.0;
+ instance->remote_root_dispersion = 0.0;
+ instance->remote_mono_epoch = 0;
+ instance->mono_doffset = 0.0;
+
+ instance->valid_rx = 0;
+ instance->valid_timestamps = 0;
+ UTI_ZeroNtp64(&instance->remote_ntp_monorx);
+ UTI_ZeroNtp64(&instance->remote_ntp_rx);
+ UTI_ZeroNtp64(&instance->remote_ntp_tx);
+ UTI_ZeroNtp64(&instance->local_ntp_rx);
+ UTI_ZeroNtp64(&instance->local_ntp_tx);
+ zero_local_timestamp(&instance->local_rx);
+
+ zero_local_timestamp(&instance->prev_local_tx);
+ instance->prev_local_poll = 0;
+ instance->prev_tx_count = 0;
+
+ instance->updated_init_timestamps = 0;
+ UTI_ZeroNtp64(&instance->init_remote_ntp_tx);
+ zero_local_timestamp(&instance->init_local_rx);
+
+ if (instance->delay_quant)
+ QNT_Reset(instance->delay_quant);
+ if (instance->filter)
+ SPF_DropSamples(instance->filter);
+ instance->filter_count = 0;
+}
+
+/* ================================================== */
+
+void
+NCR_ResetPoll(NCR_Instance instance)
+{
+ instance->poll_score = 0.0;
+
+ if (instance->local_poll != instance->minpoll) {
+ instance->local_poll = instance->minpoll;
+
+ /* The timer was set with a longer poll interval, restart it */
+ if (instance->tx_timeout_id)
+ restart_timeout(instance, get_transmit_delay(instance, 0));
+ }
+}
+
+/* ================================================== */
+
+void
+NCR_ChangeRemoteAddress(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only)
+{
+ NCR_ResetInstance(inst);
+
+ if (!ntp_only)
+ NAU_ChangeAddress(inst->auth, &remote_addr->ip_addr);
+
+ inst->remote_addr = *remote_addr;
+
+ if (inst->mode == MODE_CLIENT)
+ close_client_socket(inst);
+ else {
+ NIO_CloseServerSocket(inst->local_addr.sock_fd);
+ inst->local_addr.ip_addr.family = IPADDR_UNSPEC;
+ inst->local_addr.if_index = INVALID_IF_INDEX;
+ inst->local_addr.sock_fd = NIO_OpenServerSocket(remote_addr);
+ }
+
+ /* Reset the polling interval only if the source wasn't unreachable to
+ avoid increasing server/network load in case that is what caused
+ the source to be unreachable */
+ if (SRC_IsReachable(inst->source))
+ NCR_ResetPoll(inst);
+
+ /* Update the reference ID and reset the source/sourcestats instances */
+ SRC_SetRefid(inst->source, UTI_IPToRefid(&remote_addr->ip_addr),
+ &inst->remote_addr.ip_addr);
+ SRC_ResetInstance(inst->source);
+
+ reset_report(inst);
+}
+
+/* ================================================== */
+
+static void
+adjust_poll(NCR_Instance inst, double adj)
+{
+ NTP_Sample last_sample;
+
+ inst->poll_score += adj;
+
+ if (inst->poll_score >= 1.0) {
+ inst->local_poll += (int)inst->poll_score;
+ inst->poll_score -= (int)inst->poll_score;
+ }
+
+ if (inst->poll_score < 0.0) {
+ inst->local_poll += (int)(inst->poll_score - 1.0);
+ inst->poll_score -= (int)(inst->poll_score - 1.0);
+ }
+
+ /* Clamp polling interval to defined range */
+ if (inst->local_poll < inst->minpoll) {
+ inst->local_poll = inst->minpoll;
+ inst->poll_score = 0;
+ } else if (inst->local_poll > inst->maxpoll) {
+ inst->local_poll = inst->maxpoll;
+ inst->poll_score = 1.0;
+ }
+
+ /* Don't allow a sub-second polling interval if the source is not reachable
+ or it is not in a local network according to the measured delay */
+ if (inst->local_poll < MIN_NONLAN_POLL &&
+ (!SRC_IsReachable(inst->source) ||
+ (SST_MinRoundTripDelay(SRC_GetSourcestats(inst->source)) > MAX_LAN_PEER_DELAY &&
+ (!inst->filter || !SPF_GetLastSample(inst->filter, &last_sample) ||
+ last_sample.peer_delay > MAX_LAN_PEER_DELAY))))
+ inst->local_poll = MIN_NONLAN_POLL;
+}
+
+/* ================================================== */
+
+static double
+get_poll_adj(NCR_Instance inst, double error_in_estimate, double peer_distance)
+{
+ double poll_adj;
+ int samples;
+
+ if (error_in_estimate > peer_distance) {
+ /* If the prediction is not even within +/- the peer distance of the peer,
+ we are clearly not tracking the peer at all well, so we back off the
+ sampling rate depending on just how bad the situation is */
+ poll_adj = -log(error_in_estimate / peer_distance) / log(2.0);
+ } else {
+ samples = SST_Samples(SRC_GetSourcestats(inst->source));
+
+ /* Adjust polling interval so that the number of sourcestats samples
+ remains close to the target value */
+ poll_adj = ((double)samples / inst->poll_target - 1.0) / inst->poll_target;
+
+ /* Make interval shortening quicker */
+ if (samples < inst->poll_target) {
+ poll_adj *= 2.0;
+ }
+ }
+
+ return poll_adj;
+}
+
+/* ================================================== */
+
+static int
+get_transmit_poll(NCR_Instance inst)
+{
+ int poll;
+
+ poll = inst->local_poll;
+
+ /* In symmetric mode, if the peer is responding, use shorter of the local
+ and remote poll interval, but not shorter than the minimum */
+ if (inst->mode == MODE_ACTIVE && poll > inst->remote_poll &&
+ SRC_IsReachable(inst->source))
+ poll = MAX(inst->remote_poll, inst->minpoll);
+
+ return poll;
+}
+
+/* ================================================== */
+
+static double
+get_transmit_delay(NCR_Instance inst, int on_tx)
+{
+ int poll_to_use, stratum_diff;
+ double delay_time, last_tx;
+ struct timespec now;
+
+ /* Calculate the interval since last transmission if known */
+ if (!on_tx && !UTI_IsZeroTimespec(&inst->local_tx.ts)) {
+ SCH_GetLastEventTime(&now, NULL, NULL);
+ last_tx = UTI_DiffTimespecsToDouble(&now, &inst->local_tx.ts);
+ } else {
+ last_tx = 0;
+ }
+
+ /* If we're in burst mode, queue for immediate dispatch.
+
+ If we're operating in client/server mode, queue the timeout for
+ the poll interval hence. The fact that a timeout has been queued
+ in the transmit handler is immaterial - that is only done so that
+ we at least send something, if no reply is heard.
+
+ If we're in symmetric mode, we have to take account of the peer's
+ wishes, otherwise his sampling regime will fall to pieces. If
+ we're in client/server mode, we don't care what poll interval the
+ server responded with last time. */
+
+ poll_to_use = get_transmit_poll(inst);
+ delay_time = UTI_Log2ToDouble(poll_to_use);
+
+ switch (inst->opmode) {
+ case MD_OFFLINE:
+ assert(0);
+ break;
+ case MD_ONLINE:
+ switch(inst->mode) {
+ case MODE_CLIENT:
+ if (inst->presend_done)
+ delay_time = WARM_UP_DELAY;
+ break;
+
+ case MODE_ACTIVE:
+ /* If the remote stratum is higher than ours, wait a bit for the next
+ packet before responding in order to minimize the delay of the
+ measurement and its error for the peer which has higher stratum.
+ If the remote stratum is equal to ours, try to interleave packets
+ evenly with the peer. */
+ stratum_diff = inst->remote_stratum - REF_GetOurStratum();
+ if ((stratum_diff > 0 && last_tx * PEER_SAMPLING_ADJ < delay_time) ||
+ (!on_tx && !stratum_diff &&
+ last_tx / delay_time > PEER_SAMPLING_ADJ - 0.5))
+ delay_time *= PEER_SAMPLING_ADJ;
+
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case MD_BURST_WAS_ONLINE:
+ case MD_BURST_WAS_OFFLINE:
+ /* Burst modes */
+ delay_time = MIN(MAX_BURST_INTERVAL, MAX_BURST_POLL_RATIO * delay_time);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+ /* Subtract elapsed time */
+ if (last_tx > 0.0)
+ delay_time -= last_tx;
+ if (delay_time < 0.0)
+ delay_time = 0.0;
+
+ return delay_time;
+}
+
+/* ================================================== */
+/* Calculate sampling separation for given polling interval */
+
+static double
+get_separation(int poll)
+{
+ double separation;
+
+ assert(poll >= MIN_POLL && poll <= MAX_POLL);
+
+ /* Allow up to 8 sources using the same short interval to not be limited
+ by the separation */
+ separation = UTI_Log2ToDouble(poll - 3);
+
+ return CLAMP(MIN_SAMPLING_SEPARATION, separation, MAX_SAMPLING_SEPARATION);
+}
+
+/* ================================================== */
+/* Timeout handler for closing the client socket when no acceptable
+ reply can be received from the server */
+
+static void
+receive_timeout(void *arg)
+{
+ NCR_Instance inst = (NCR_Instance)arg;
+
+ DEBUG_LOG("Receive timeout for %s", UTI_IPSockAddrToString(&inst->remote_addr));
+
+ inst->rx_timeout_id = 0;
+ close_client_socket(inst);
+}
+
+/* ================================================== */
+
+static int
+add_ef_mono_root(NTP_Packet *message, NTP_PacketInfo *info, struct timespec *rx,
+ double root_delay, double root_dispersion)
+{
+ struct timespec mono_rx;
+ NTP_EFExpMonoRoot ef;
+ NTP_int64 ts_fuzz;
+
+ memset(&ef, 0, sizeof (ef));
+ ef.magic = htonl(NTP_EF_EXP_MONO_ROOT_MAGIC);
+
+ if (info->mode != MODE_CLIENT) {
+ ef.root_delay = UTI_DoubleToNtp32f28(root_delay);
+ ef.root_dispersion = UTI_DoubleToNtp32f28(root_dispersion);
+ if (rx)
+ UTI_AddDoubleToTimespec(rx, server_mono_offset, &mono_rx);
+ else
+ UTI_ZeroTimespec(&mono_rx);
+ UTI_GetNtp64Fuzz(&ts_fuzz, message->precision);
+ UTI_TimespecToNtp64(&mono_rx, &ef.mono_receive_ts, &ts_fuzz);
+ ef.mono_epoch = htonl(server_mono_epoch);
+ }
+
+ if (!NEF_AddField(message, info, NTP_EF_EXP_MONO_ROOT, &ef, sizeof (ef))) {
+ DEBUG_LOG("Could not add EF");
+ return 0;
+ }
+
+ info->ext_field_flags |= NTP_EF_FLAG_EXP_MONO_ROOT;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+add_ef_net_correction(NTP_Packet *message, NTP_PacketInfo *info,
+ NTP_Local_Timestamp *local_rx)
+{
+ NTP_EFExpNetCorrection ef;
+
+ if (CNF_GetPtpPort() == 0) {
+ DEBUG_LOG("ptpport disabled");
+ return 1;
+ }
+
+ memset(&ef, 0, sizeof (ef));
+ ef.magic = htonl(NTP_EF_EXP_NET_CORRECTION_MAGIC);
+
+ if (info->mode != MODE_CLIENT && local_rx->net_correction > local_rx->rx_duration) {
+ UTI_DoubleToNtp64(local_rx->net_correction, &ef.correction);
+ }
+
+ if (!NEF_AddField(message, info, NTP_EF_EXP_NET_CORRECTION, &ef, sizeof (ef))) {
+ DEBUG_LOG("Could not add EF");
+ return 0;
+ }
+
+ info->ext_field_flags |= NTP_EF_FLAG_EXP_NET_CORRECTION;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+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 */
+ uint32_t kod, /* KoD code - 0 disabled */
+ int ext_field_flags, /* Extension fields to be included in the packet */
+ 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 */
+ NTP_Local_Timestamp *local_tx, /* The TX time of the previous packet
+ RESULT : TX time of this packet */
+ NTP_int64 *local_ntp_rx, /* The receive timestamp from the previous packet
+ RESULT : receive timestamp from this packet */
+ 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_Packet *request, /* The received packet if responding */
+ NTP_PacketInfo *request_info /* and its info */
+ )
+{
+ NTP_PacketInfo info;
+ NTP_Packet message;
+ struct timespec local_receive, local_transmit;
+ double smooth_offset, local_transmit_err;
+ int ret, precision;
+ NTP_int64 ts_fuzz;
+
+ /* Parameters read from reference module */
+ int are_we_synchronised, our_stratum, smooth_time;
+ NTP_Leap leap_status;
+ uint32_t our_ref_id;
+ 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;
+ }
+
+ /* Check if the packet can be formed in the interleaved mode */
+ if (interleaved && (!remote_ntp_rx || !local_tx || UTI_IsZeroTimespec(&local_tx->ts)))
+ interleaved = 0;
+
+ smooth_time = 0;
+ smooth_offset = 0.0;
+
+ /* Get an initial transmit timestamp. A more accurate timestamp will be
+ taken later in this function. */
+ SCH_GetLastEventTime(&local_transmit, NULL, NULL);
+
+ if (my_mode == MODE_CLIENT) {
+ /* Don't reveal local time or state of the clock in client packets */
+ precision = 32;
+ leap_status = our_stratum = our_ref_id = 0;
+ our_root_delay = our_root_dispersion = 0.0;
+ UTI_ZeroTimespec(&our_ref_time);
+ } else {
+ REF_GetReferenceParams(&local_transmit,
+ &are_we_synchronised, &leap_status,
+ &our_stratum,
+ &our_ref_id, &our_ref_time,
+ &our_root_delay, &our_root_dispersion);
+
+ /* Get current smoothing offset when sending packet to a client */
+ if (SMT_IsEnabled() && (my_mode == MODE_SERVER || my_mode == MODE_BROADCAST)) {
+ smooth_offset = SMT_GetOffset(&local_transmit);
+ smooth_time = fabs(smooth_offset) > LCL_GetSysPrecisionAsQuantum();
+
+ /* Suppress leap second when smoothing and slew mode are enabled */
+ if (REF_GetLeapMode() == REF_LeapModeSlew &&
+ (leap_status == LEAP_InsertSecond || leap_status == LEAP_DeleteSecond))
+ leap_status = LEAP_Normal;
+ }
+
+ precision = LCL_GetSysPrecisionAsLog();
+ }
+
+ if (smooth_time && !UTI_IsZeroTimespec(&local_rx->ts)) {
+ our_ref_id = NTP_REFID_SMOOTH;
+ UTI_AddDoubleToTimespec(&our_ref_time, smooth_offset, &our_ref_time);
+ UTI_AddDoubleToTimespec(&local_rx->ts, smooth_offset, &local_receive);
+ } else {
+ 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 */
+ if (our_stratum < NTP_MAX_STRATUM) {
+ message.stratum = our_stratum;
+ } else {
+ message.stratum = NTP_INVALID_STRATUM;
+ }
+
+ message.poll = my_poll;
+ message.precision = precision;
+ message.root_delay = UTI_DoubleToNtp32(our_root_delay);
+ message.root_dispersion = UTI_DoubleToNtp32(our_root_dispersion);
+ message.reference_id = htonl(our_ref_id);
+
+ /* Now fill in timestamps */
+
+ UTI_TimespecToNtp64(&our_ref_time, &message.reference_ts, NULL);
+
+ /* Don't reveal timestamps which are not necessary for the protocol */
+
+ if (my_mode != MODE_CLIENT || interleaved) {
+ /* Originate - this comes from the last packet the source sent us */
+ message.originate_ts = interleaved ? *remote_ntp_rx : *remote_ntp_tx;
+
+ do {
+ /* Prepare random bits which will be added to the receive timestamp */
+ UTI_GetNtp64Fuzz(&ts_fuzz, precision);
+
+ /* Receive - this is when we received the last packet from the source.
+ This timestamp will have been adjusted so that it will now look to
+ the source like we have been running on our latest estimate of
+ frequency all along */
+ UTI_TimespecToNtp64(&local_receive, &message.receive_ts, &ts_fuzz);
+
+ /* Do not send a packet with a non-zero receive timestamp equal to the
+ originate timestamp or previous receive timestamp */
+ } while (!UTI_IsZeroNtp64(&message.receive_ts) &&
+ UTI_IsEqualAnyNtp64(&message.receive_ts, &message.originate_ts,
+ local_ntp_rx, NULL));
+ } else {
+ UTI_ZeroNtp64(&message.originate_ts);
+ UTI_ZeroNtp64(&message.receive_ts);
+ }
+
+ if (!parse_packet(&message, NTP_HEADER_LENGTH, &info))
+ return 0;
+
+ if (ext_field_flags) {
+ if (ext_field_flags & NTP_EF_FLAG_EXP_MONO_ROOT) {
+ if (!add_ef_mono_root(&message, &info, smooth_time ? NULL : &local_receive,
+ our_root_delay, our_root_dispersion))
+ return 0;
+ }
+ if (ext_field_flags & NTP_EF_FLAG_EXP_NET_CORRECTION) {
+ if (!add_ef_net_correction(&message, &info, local_rx))
+ return 0;
+ }
+ }
+
+ do {
+ /* Prepare random bits which will be added to the transmit timestamp */
+ UTI_GetNtp64Fuzz(&ts_fuzz, precision);
+
+ /* Get a more accurate transmit timestamp if it needs to be saved in the
+ packet (i.e. in the server, symmetric, and broadcast basic modes) */
+ if (!interleaved && precision < 32) {
+ LCL_ReadCookedTime(&local_transmit, &local_transmit_err);
+ if (smooth_time)
+ UTI_AddDoubleToTimespec(&local_transmit, smooth_offset, &local_transmit);
+ }
+
+ UTI_TimespecToNtp64(interleaved ? &local_tx->ts : &local_transmit,
+ &message.transmit_ts, &ts_fuzz);
+
+ /* Do not send a packet with a non-zero transmit timestamp which is
+ equal to any of the following timestamps:
+ - receive (to allow reliable detection of the interleaved mode)
+ - originate (to prevent the packet from being its own valid response
+ in the symmetric mode)
+ - previous transmit (to invalidate responses to the previous packet)
+ (the precision must be at least -30 to prevent an infinite loop!) */
+ } while (!UTI_IsZeroNtp64(&message.transmit_ts) &&
+ UTI_IsEqualAnyNtp64(&message.transmit_ts, &message.receive_ts,
+ &message.originate_ts, local_ntp_tx));
+
+ /* Encode in server timestamps a flag indicating RX timestamp to avoid
+ saving all RX timestamps for detection of interleaved requests */
+ if (my_mode == MODE_SERVER || my_mode == MODE_PASSIVE) {
+ message.receive_ts.lo |= htonl(1);
+ message.transmit_ts.lo &= ~htonl(1);
+ }
+
+ /* Generate the authentication data */
+ if (auth) {
+ if (!NAU_GenerateRequestAuth(auth, &message, &info)) {
+ DEBUG_LOG("Could not generate request auth");
+ return 0;
+ }
+ } else {
+ if (!NAU_GenerateResponseAuth(request, request_info, &message, &info,
+ where_to, from, kod)) {
+ DEBUG_LOG("Could not generate response auth");
+ return 0;
+ }
+ }
+
+ 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;
+ }
+
+ /* If the transmit timestamp will be saved, get an even more
+ accurate daemon timestamp closer to the transmission */
+ if (local_tx)
+ LCL_ReadCookedTime(&local_transmit, &local_transmit_err);
+
+ ret = NIO_SendPacket(&message, where_to, from, info.length, local_tx != NULL);
+
+ if (local_tx) {
+ if (smooth_time)
+ UTI_AddDoubleToTimespec(&local_transmit, smooth_offset, &local_transmit);
+ local_tx->ts = local_transmit;
+ local_tx->err = local_transmit_err;
+ local_tx->source = NTP_TS_DAEMON;
+ local_tx->rx_duration = 0.0;
+ local_tx->net_correction = 0.0;
+ }
+
+ if (local_ntp_rx)
+ *local_ntp_rx = message.receive_ts;
+ if (local_ntp_tx)
+ *local_ntp_tx = message.transmit_ts;
+
+ return ret;
+}
+
+/* ================================================== */
+/* Timeout handler for transmitting to a source. */
+
+static void
+transmit_timeout(void *arg)
+{
+ NCR_Instance inst = (NCR_Instance) arg;
+ NTP_Local_Address local_addr;
+ int interleaved, initial, sent;
+
+ inst->tx_timeout_id = 0;
+
+ if (has_saved_response(inst)) {
+ process_saved_response(inst);
+
+ /* Wait for the new transmission timeout (if the response was still
+ valid and it did not cause switch to offline) */
+ if (inst->tx_timeout_id != 0)
+ return;
+ }
+
+ switch (inst->opmode) {
+ case MD_BURST_WAS_ONLINE:
+ /* With online burst switch to online before last packet */
+ if (inst->burst_total_samples_to_go <= 1)
+ inst->opmode = MD_ONLINE;
+ break;
+ case MD_BURST_WAS_OFFLINE:
+ if (inst->burst_total_samples_to_go <= 0)
+ take_offline(inst);
+ break;
+ case MD_ONLINE:
+ /* Start a new burst if the burst option is enabled and the average
+ polling interval including the burst will not fall below the
+ minimum polling interval */
+ if (inst->auto_burst && inst->local_poll > inst->minpoll)
+ NCR_InitiateSampleBurst(inst, BURST_GOOD_SAMPLES,
+ MIN(1 << (inst->local_poll - inst->minpoll),
+ MAX_BURST_TOTAL_SAMPLES));
+ break;
+ default:
+ break;
+ }
+
+ if (inst->opmode == MD_OFFLINE) {
+ return;
+ }
+
+ DEBUG_LOG("Transmit timeout for %s", UTI_IPSockAddrToString(&inst->remote_addr));
+
+ /* Prepare authentication */
+ if (!NAU_PrepareRequestAuth(inst->auth)) {
+ SRC_UpdateReachability(inst->source, 0);
+ restart_timeout(inst, get_transmit_delay(inst, 1));
+ /* Count missing samples for the sample filter */
+ process_sample(inst, NULL);
+ return;
+ }
+
+ /* Open new client socket */
+ if (inst->mode == MODE_CLIENT) {
+ close_client_socket(inst);
+ assert(inst->local_addr.sock_fd == INVALID_SOCK_FD);
+ inst->local_addr.sock_fd = NIO_OpenClientSocket(&inst->remote_addr);
+ }
+
+ /* Don't require the packet to be sent from the same address as before */
+ local_addr.ip_addr.family = IPADDR_UNSPEC;
+ local_addr.if_index = INVALID_IF_INDEX;
+ local_addr.sock_fd = inst->local_addr.sock_fd;
+
+ /* In symmetric mode, don't send a packet in interleaved mode unless it
+ is the first response to the last valid request received from the peer
+ and there was just one response to the previous valid request. This
+ prevents the peer from matching the transmit timestamp with an older
+ response if it can't detect missed responses. In client mode, which has
+ at most one response per request, check how many responses are missing to
+ prevent the server from responding with a very old transmit timestamp. */
+ interleaved = inst->interleaved &&
+ ((inst->mode == MODE_CLIENT &&
+ inst->tx_count < MAX_CLIENT_INTERLEAVED_TX) ||
+ (inst->mode == MODE_ACTIVE &&
+ inst->prev_tx_count == 1 && inst->tx_count == 0));
+
+ /* In symmetric mode, if no valid response was received since the previous
+ transmission, respond to the last received packet even if it failed some
+ specific NTP tests. This is necessary for starting and restarting the
+ protocol, e.g. when a packet was lost. */
+ initial = inst->mode == MODE_ACTIVE && !inst->valid_rx &&
+ !UTI_IsZeroNtp64(&inst->init_remote_ntp_tx);
+
+ /* Prepare for the response */
+ inst->valid_rx = 0;
+ inst->updated_init_timestamps = 0;
+ if (initial)
+ inst->valid_timestamps = 0;
+
+ /* Check whether we need to 'warm up' the link to the other end by
+ sending an NTP exchange to ensure both ends' ARP caches are
+ primed or whether we need to send two packets first to ensure a
+ server in the interleaved mode has a fresh timestamp for us. */
+ if (inst->presend_minpoll <= inst->local_poll && !inst->presend_done &&
+ !inst->burst_total_samples_to_go) {
+ inst->presend_done = interleaved ? 2 : 1;
+ } else if (inst->presend_done > 0) {
+ inst->presend_done--;
+ }
+
+ /* Send the request (which may also be a response in the symmetric mode) */
+ sent = transmit_packet(inst->mode, interleaved, inst->local_poll, inst->version, 0,
+ inst->ext_field_flags, 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, NULL, NULL);
+
+ ++inst->tx_count;
+ if (sent)
+ inst->report.total_tx_count++;
+
+ /* If the source loses connectivity and our packets are still being sent,
+ back off the sampling rate to reduce the network traffic. If it's the
+ source to which we are currently locked, back off slowly. */
+
+ if (inst->tx_count >= 2) {
+ /* Implies we have missed at least one transmission */
+
+ if (sent) {
+ adjust_poll(inst, SRC_IsSyncPeer(inst->source) ? 0.1 : 0.25);
+ }
+
+ SRC_UpdateReachability(inst->source, 0);
+
+ /* Count missing samples for the sample filter */
+ process_sample(inst, NULL);
+ }
+
+ /* With auto_offline take the source offline if sending failed */
+ if (!sent && inst->auto_offline)
+ NCR_SetConnectivity(inst, SRC_OFFLINE);
+
+ switch (inst->opmode) {
+ case MD_BURST_WAS_ONLINE:
+ /* When not reachable, don't stop online burst until sending succeeds */
+ if (!sent && !SRC_IsReachable(inst->source))
+ break;
+ /* Fall through */
+ case MD_BURST_WAS_OFFLINE:
+ --inst->burst_total_samples_to_go;
+ break;
+ case MD_OFFLINE:
+ return;
+ default:
+ break;
+ }
+
+ /* Restart timer for this message */
+ restart_timeout(inst, get_transmit_delay(inst, 1));
+
+ /* If a client packet was just sent, schedule a timeout to close the socket
+ at the time when all server replies would fail the delay test, so the
+ socket is not open for longer than necessary */
+ if (inst->mode == MODE_CLIENT)
+ inst->rx_timeout_id = SCH_AddTimeoutByDelay(inst->max_delay + MAX_SERVER_INTERVAL,
+ receive_timeout, (void *)inst);
+}
+
+/* ================================================== */
+
+static int
+is_zero_data(unsigned char *data, int length)
+{
+ int i;
+
+ for (i = 0; i < length; i++)
+ if (data[i] != 0)
+ return 0;
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+is_exp_ef(void *body, int body_length, int expected_body_length, uint32_t magic)
+{
+ return body_length == expected_body_length && *(uint32_t *)body == htonl(magic);
+}
+
+/* ================================================== */
+
+static int
+parse_packet(NTP_Packet *packet, int length, NTP_PacketInfo *info)
+{
+ int parsed, remainder, ef_length, ef_type, ef_body_length;
+ unsigned char *data;
+ void *ef_body;
+
+ if (length < NTP_HEADER_LENGTH || length % 4U != 0) {
+ DEBUG_LOG("NTP packet has invalid length %d", length);
+ return 0;
+ }
+
+ info->length = length;
+ info->version = NTP_LVM_TO_VERSION(packet->lvm);
+ info->mode = NTP_LVM_TO_MODE(packet->lvm);
+ info->ext_fields = 0;
+ info->ext_field_flags = 0;
+ info->auth.mode = NTP_AUTH_NONE;
+
+ 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;
+ }
+
+ data = (void *)packet;
+ parsed = NTP_HEADER_LENGTH;
+ remainder = info->length - parsed;
+
+ /* Check if this is a plain NTP packet with no extension fields or MAC */
+ if (remainder <= 0)
+ return 1;
+
+ assert(remainder % 4 == 0);
+
+ /* 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 != 0) {
+ 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;
+
+ /* Check if this is a valid NTPv4 extension field and skip it */
+ if (!NEF_ParseField(packet, info->length, parsed,
+ &ef_length, &ef_type, &ef_body, &ef_body_length)) {
+ DEBUG_LOG("Invalid format");
+ return 0;
+ }
+
+ assert(ef_length > 0 && ef_length % 4 == 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;
+ case NTP_EF_EXP_MONO_ROOT:
+ if (is_exp_ef(ef_body, ef_body_length, sizeof (NTP_EFExpMonoRoot),
+ NTP_EF_EXP_MONO_ROOT_MAGIC))
+ info->ext_field_flags |= NTP_EF_FLAG_EXP_MONO_ROOT;
+ break;
+ case NTP_EF_EXP_NET_CORRECTION:
+ if (is_exp_ef(ef_body, ef_body_length, sizeof (NTP_EFExpNetCorrection),
+ NTP_EF_EXP_NET_CORRECTION_MAGIC))
+ info->ext_field_flags |= NTP_EF_FLAG_EXP_NET_CORRECTION;
+ break;
+ default:
+ DEBUG_LOG("Unknown extension field type=%x", (unsigned int)ef_type);
+ }
+
+ info->ext_fields++;
+ parsed += ef_length;
+ remainder = info->length - parsed;
+ }
+
+ if (remainder == 0) {
+ /* No MAC */
+ return 1;
+ } else if (remainder >= NTP_MIN_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;
+}
+
+/* ================================================== */
+
+static void
+apply_net_correction(NTP_Sample *sample, NTP_Local_Timestamp *rx, NTP_Local_Timestamp *tx,
+ double precision)
+{
+ double rx_correction, tx_correction, low_delay_correction;
+
+ /* Require some correction from transparent clocks to be present
+ in both directions (not just the local RX timestamp correction) */
+ if (rx->net_correction <= rx->rx_duration || tx->net_correction <= 0.0)
+ return;
+
+ /* With perfect corrections from PTP transparent clocks and short cables
+ the peer delay would be close to zero, or even negative if the server or
+ transparent clocks were running faster than client, which would invert the
+ sample weighting. Adjust the correction to get a delay corresponding to
+ a direct connection to the server. For simplicity, assume the TX and RX
+ link speeds are equal. If not, the reported delay will be wrong, but it
+ will not cause an error in the offset. */
+ rx_correction = rx->net_correction - rx->rx_duration;
+ tx_correction = tx->net_correction - rx->rx_duration;
+
+ /* Use a slightly smaller value in the correction of delay to not overcorrect
+ if the transparent clocks run up to 100 ppm fast and keep a part of the
+ uncorrected delay for the sample weighting */
+ low_delay_correction = (rx_correction + tx_correction) *
+ (1.0 - MAX_NET_CORRECTION_FREQ);
+
+ /* Make sure the correction is sane. The values are not authenticated! */
+ if (low_delay_correction < 0.0 || low_delay_correction > sample->peer_delay) {
+ DEBUG_LOG("Invalid correction %.9f peer_delay=%.9f",
+ low_delay_correction, sample->peer_delay);
+ return;
+ }
+
+ /* Correct the offset and peer delay, but not the root delay to not
+ change the estimated maximum error */
+ sample->offset += (rx_correction - tx_correction) / 2.0;
+ sample->peer_delay -= low_delay_correction;
+ if (sample->peer_delay < precision)
+ sample->peer_delay = precision;
+
+ DEBUG_LOG("Applied correction rx=%.9f tx=%.9f dur=%.9f",
+ rx->net_correction, tx->net_correction, rx->rx_duration);
+}
+
+/* ================================================== */
+
+static int
+check_delay_ratio(NCR_Instance inst, SST_Stats stats,
+ struct timespec *sample_time, double delay)
+{
+ double last_sample_ago, predicted_offset, min_delay, skew, std_dev;
+ double max_delay;
+
+ if (inst->max_delay_ratio < 1.0 ||
+ !SST_GetDelayTestData(stats, sample_time, &last_sample_ago,
+ &predicted_offset, &min_delay, &skew, &std_dev))
+ return 1;
+
+ max_delay = min_delay * inst->max_delay_ratio +
+ last_sample_ago * (skew + LCL_GetMaxClockError());
+
+ if (delay <= max_delay)
+ return 1;
+
+ DEBUG_LOG("maxdelayratio: delay=%e max_delay=%e", delay, max_delay);
+ return 0;
+}
+
+/* ================================================== */
+
+static int
+check_delay_quant(NCR_Instance inst, double delay)
+{
+ double quant;
+
+ quant = QNT_GetQuantile(inst->delay_quant, QNT_GetMinK(inst->delay_quant));
+
+ if (delay <= quant)
+ return 1;
+
+ DEBUG_LOG("maxdelayquant: delay=%e quant=%e", delay, quant);
+ return 0;
+}
+
+/* ================================================== */
+
+static int
+check_delay_dev_ratio(NCR_Instance inst, SST_Stats stats,
+ struct timespec *sample_time, double offset, double delay)
+{
+ double last_sample_ago, predicted_offset, min_delay, skew, std_dev;
+ double delta, max_delta, error_in_estimate;
+
+ if (!SST_GetDelayTestData(stats, sample_time, &last_sample_ago,
+ &predicted_offset, &min_delay, &skew, &std_dev))
+ return 1;
+
+ /* Require that the ratio of the increase in delay from the minimum to the
+ standard deviation is less than max_delay_dev_ratio. In the allowed
+ increase in delay include also dispersion. */
+
+ max_delta = std_dev * inst->max_delay_dev_ratio +
+ last_sample_ago * (skew + LCL_GetMaxClockError());
+ delta = (delay - min_delay) / 2.0;
+
+ if (delta <= max_delta)
+ return 1;
+
+ error_in_estimate = offset + predicted_offset;
+
+ /* Before we decide to drop the sample, make sure the difference between
+ measured offset and predicted offset is not significantly larger than
+ the increase in delay */
+ if (fabs(error_in_estimate) - delta > max_delta)
+ return 1;
+
+ DEBUG_LOG("maxdelaydevratio: error=%e delay=%e delta=%e max_delta=%e",
+ error_in_estimate, delay, delta, max_delta);
+ return 0;
+}
+
+/* ================================================== */
+
+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 client or peer can be synchronised to us */
+ if (!NIO_IsServerSocketOpen() || REF_GetMode() != REF_ModeNormal)
+ 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)
+{
+ double estimated_offset, error_in_estimate;
+ NTP_Sample filtered_sample;
+
+ /* Accumulate the sample to the median filter if enabled and wait for
+ the configured number of samples before processing (NULL indicates
+ a missing sample) */
+ if (inst->filter) {
+ if (sample)
+ SPF_AccumulateSample(inst->filter, sample);
+
+ if (++inst->filter_count < SPF_GetMaxSamples(inst->filter))
+ return;
+
+ if (!SPF_GetFilteredSample(inst->filter, &filtered_sample))
+ return;
+
+ sample = &filtered_sample;
+ inst->filter_count = 0;
+ }
+
+ if (!sample)
+ return;
+
+ /* Get the estimated offset predicted from previous samples. The
+ convention here is that positive means local clock FAST of
+ reference, i.e. backwards to the way that 'offset' is defined. */
+ estimated_offset = SST_PredictOffset(SRC_GetSourcestats(inst->source), &sample->time);
+
+ error_in_estimate = fabs(-sample->offset - estimated_offset);
+
+ if (inst->mono_doffset != 0.0 && fabs(inst->mono_doffset) <= MAX_MONO_DOFFSET) {
+ DEBUG_LOG("Monotonic correction offset=%.9f", inst->mono_doffset);
+ SST_CorrectOffset(SRC_GetSourcestats(inst->source), inst->mono_doffset);
+ }
+ inst->mono_doffset = 0.0;
+
+ SRC_AccumulateSample(inst->source, sample);
+ SRC_SelectSource(inst->source);
+
+ adjust_poll(inst, get_poll_adj(inst, error_in_estimate,
+ sample->peer_dispersion + 0.5 * sample->peer_delay));
+}
+
+/* ================================================== */
+
+static int
+has_saved_response(NCR_Instance inst)
+{
+ return inst->saved_response && inst->saved_response->timeout_id > 0;
+}
+
+/* ================================================== */
+
+static void
+process_saved_response(NCR_Instance inst)
+{
+ SCH_RemoveTimeout(inst->saved_response->timeout_id);
+ inst->saved_response->timeout_id = 0;
+
+ DEBUG_LOG("Processing saved response from %s", UTI_IPToString(&inst->remote_addr.ip_addr));
+ process_response(inst, 1, &inst->saved_response->local_addr, &inst->saved_response->rx_ts,
+ &inst->saved_response->message, &inst->saved_response->info);
+}
+
+/* ================================================== */
+
+static void
+saved_response_timeout(void *arg)
+{
+ NCR_Instance inst = arg;
+
+ inst->saved_response->timeout_id = 0;
+ process_saved_response(inst);
+}
+
+/* ================================================== */
+
+static int
+save_response(NCR_Instance inst, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *rx_ts, NTP_Packet *message, NTP_PacketInfo *info)
+{
+ double timeout = CNF_GetHwTsTimeout();
+
+ if (timeout <= 0.0)
+ return 0;
+
+ /* If another message is already saved, process both immediately */
+ if (has_saved_response(inst)) {
+ process_saved_response(inst);
+ return 0;
+ }
+
+ if (!inst->saved_response)
+ inst->saved_response = MallocNew(struct SavedResponse);
+ inst->saved_response->local_addr = *local_addr;
+ inst->saved_response->rx_ts = *rx_ts;
+ inst->saved_response->message = *message;
+ inst->saved_response->info = *info;
+ inst->saved_response->timeout_id = SCH_AddTimeoutByDelay(timeout, saved_response_timeout,
+ inst);
+ DEBUG_LOG("Saved valid response for later processing");
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_response(NCR_Instance inst, int saved, 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;
+ double pkt_root_delay;
+ double pkt_root_dispersion;
+
+ /* The skew and estimated frequency offset relative to the remote source */
+ double skew, source_freq_lo, source_freq_hi;
+
+ /* RFC 5905 packet tests */
+ int test1, test2n, test2i, test2, test3, test5, test6, test7;
+ int interleaved_packet, valid_packet, synced_packet;
+
+ /* Additional tests */
+ int testA, testB, testC, testD;
+ int good_packet;
+
+ /* Kiss-o'-Death codes */
+ int kod_rate;
+
+ /* Extension fields */
+ int parsed, ef_length, ef_type, ef_body_length;
+ void *ef_body;
+ NTP_EFExpMonoRoot *ef_mono_root;
+ NTP_EFExpNetCorrection *ef_net_correction;
+
+ NTP_Local_Timestamp local_receive, local_transmit;
+ double remote_interval, local_interval, response_time;
+ double delay_time, precision, mono_doffset, net_correction;
+ int updated_timestamps;
+
+ /* ==================== */
+
+ stats = SRC_GetSourcestats(inst->source);
+
+ ef_mono_root = NULL;
+ ef_net_correction = NULL;
+
+ /* Find requested non-authentication extension fields */
+ if (inst->ext_field_flags & info->ext_field_flags) {
+ for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) {
+ if (!NEF_ParseField(message, info->length, parsed,
+ &ef_length, &ef_type, &ef_body, &ef_body_length))
+ break;
+
+ switch (ef_type) {
+ case NTP_EF_EXP_MONO_ROOT:
+ if (inst->ext_field_flags & NTP_EF_FLAG_EXP_MONO_ROOT &&
+ is_exp_ef(ef_body, ef_body_length, sizeof (*ef_mono_root),
+ NTP_EF_EXP_MONO_ROOT_MAGIC))
+ ef_mono_root = ef_body;
+ break;
+ case NTP_EF_EXP_NET_CORRECTION:
+ if (inst->ext_field_flags & NTP_EF_FLAG_EXP_NET_CORRECTION &&
+ is_exp_ef(ef_body, ef_body_length, sizeof (*ef_net_correction),
+ NTP_EF_EXP_NET_CORRECTION_MAGIC))
+ ef_net_correction = ef_body;
+ break;
+ }
+ }
+ }
+
+ pkt_leap = NTP_LVM_TO_LEAP(message->lvm);
+ pkt_version = NTP_LVM_TO_VERSION(message->lvm);
+ pkt_refid = ntohl(message->reference_id);
+ if (ef_mono_root) {
+ pkt_root_delay = UTI_Ntp32f28ToDouble(ef_mono_root->root_delay);
+ pkt_root_dispersion = UTI_Ntp32f28ToDouble(ef_mono_root->root_dispersion);
+ } else {
+ pkt_root_delay = UTI_Ntp32ToDouble(message->root_delay);
+ pkt_root_dispersion = UTI_Ntp32ToDouble(message->root_dispersion);
+ }
+
+ /* Check if the packet is valid per RFC 5905, section 8.
+ The test values are 1 when passed and 0 when failed. */
+
+ /* Test 1 checks for duplicate packet */
+ test1 = UTI_CompareNtp64(&message->receive_ts, &inst->remote_ntp_rx) ||
+ UTI_CompareNtp64(&message->transmit_ts, &inst->remote_ntp_tx);
+
+ /* Test 2 checks for bogus packet in the basic and interleaved modes. This
+ ensures the source is responding to the latest packet we sent to it. */
+ test2n = !UTI_CompareNtp64(&message->originate_ts, &inst->local_ntp_tx);
+ test2i = inst->interleaved &&
+ !UTI_CompareNtp64(&message->originate_ts, &inst->local_ntp_rx);
+ test2 = test2n || test2i;
+ interleaved_packet = !test2n && test2i;
+
+ /* Test 3 checks for invalid timestamps. This can happen when the
+ association if not properly 'up'. */
+ test3 = !UTI_IsZeroNtp64(&message->originate_ts) &&
+ !UTI_IsZeroNtp64(&message->receive_ts) &&
+ !UTI_IsZeroNtp64(&message->transmit_ts);
+
+ /* 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 it is a saved message,
+ which had to pass all these tests before, avoid authenticating it for
+ the second time (that is not allowed in the NTS code). */
+ test5 = saved || NAU_CheckResponseAuth(inst->auth, message, info);
+
+ /* Test 6 checks for unsynchronised server */
+ test6 = pkt_leap != LEAP_Unsynchronised &&
+ message->stratum < NTP_MAX_STRATUM &&
+ message->stratum != NTP_INVALID_STRATUM;
+
+ /* Test 7 checks for bad data. The root distance must be smaller than a
+ defined maximum. */
+ test7 = pkt_root_delay / 2.0 + pkt_root_dispersion < NTP_MAX_DISPERSION;
+
+ /* The packet is considered valid if the tests 1-5 passed. The timestamps
+ can be used for synchronisation if the tests 6 and 7 passed too. */
+ valid_packet = test1 && test2 && test3 && test5;
+ synced_packet = valid_packet && test6 && test7;
+
+ /* If the server is very close and/or the NIC hardware/driver is slow, it
+ is possible that a response from the server is received before the HW
+ transmit timestamp of the request. To avoid getting a less accurate
+ offset or failing one of the later tests, save the response and wait for
+ the transmit timestamp or timeout. Allow this only for the first valid
+ response to the request, when at least one good response has already been
+ accepted to avoid incorrectly confirming a tentative source. */
+ if (valid_packet && synced_packet && !saved && !inst->valid_rx &&
+ NIO_IsHwTsEnabled() && inst->local_tx.source != NTP_TS_HARDWARE &&
+ inst->report.total_good_count > 0) {
+ if (save_response(inst, local_addr, rx_ts, message, info))
+ return 1;
+ }
+
+ /* Check for Kiss-o'-Death codes */
+ kod_rate = 0;
+ if (test1 && test2 && test5 && pkt_leap == LEAP_Unsynchronised &&
+ message->stratum == NTP_INVALID_STRATUM) {
+ if (pkt_refid == KOD_RATE)
+ kod_rate = 1;
+ }
+
+ if (synced_packet && (!interleaved_packet || inst->valid_timestamps)) {
+ /* These are the timespec equivalents of the remote and local epochs */
+ struct timespec remote_receive, remote_transmit, remote_request_receive;
+ struct timespec local_average, remote_average, prev_remote_transmit;
+ double prev_remote_poll_interval, root_delay, root_dispersion;
+
+ /* If the remote monotonic timestamps are available and are from the same
+ epoch, calculate the change in the offset between the monotonic and
+ real-time clocks, i.e. separate the source's time corrections from
+ frequency corrections. The offset is accumulated between measurements.
+ It will correct old measurements kept in sourcestats before accumulating
+ the new sample. In the interleaved mode, cancel the correction out in
+ remote timestamps of the previous request and response, which were
+ captured before the source accumulated the new time corrections. */
+ if (ef_mono_root && inst->remote_mono_epoch == ntohl(ef_mono_root->mono_epoch) &&
+ !UTI_IsZeroNtp64(&ef_mono_root->mono_receive_ts) &&
+ !UTI_IsZeroNtp64(&inst->remote_ntp_monorx)) {
+ mono_doffset =
+ UTI_DiffNtp64ToDouble(&ef_mono_root->mono_receive_ts, &inst->remote_ntp_monorx) -
+ UTI_DiffNtp64ToDouble(&message->receive_ts, &inst->remote_ntp_rx);
+ if (fabs(mono_doffset) > MAX_MONO_DOFFSET)
+ mono_doffset = 0.0;
+ } else {
+ mono_doffset = 0.0;
+ }
+
+ if (ef_net_correction) {
+ net_correction = UTI_Ntp64ToDouble(&ef_net_correction->correction);
+ } else {
+ net_correction = 0.0;
+ }
+
+ /* Select remote and local timestamps for the new sample */
+ if (interleaved_packet) {
+ /* Prefer previous local TX and remote RX timestamps if it will make
+ the intervals significantly shorter in order to improve the accuracy
+ of the measured delay */
+ if (!UTI_IsZeroTimespec(&inst->prev_local_tx.ts) &&
+ MAX_INTERLEAVED_L2L_RATIO *
+ UTI_DiffTimespecsToDouble(&inst->local_tx.ts, &inst->local_rx.ts) >
+ UTI_DiffTimespecsToDouble(&inst->local_rx.ts, &inst->prev_local_tx.ts)) {
+ UTI_Ntp64ToTimespec(&inst->remote_ntp_rx, &remote_receive);
+ UTI_AddDoubleToTimespec(&remote_receive, -mono_doffset, &remote_receive);
+ remote_request_receive = remote_receive;
+ local_transmit = inst->prev_local_tx;
+ root_delay = inst->remote_root_delay;
+ root_dispersion = inst->remote_root_dispersion;
+ } else {
+ UTI_Ntp64ToTimespec(&message->receive_ts, &remote_receive);
+ UTI_Ntp64ToTimespec(&inst->remote_ntp_rx, &remote_request_receive);
+ local_transmit = inst->local_tx;
+ local_transmit.net_correction = net_correction;
+ root_delay = MAX(pkt_root_delay, inst->remote_root_delay);
+ root_dispersion = MAX(pkt_root_dispersion, inst->remote_root_dispersion);
+ }
+ UTI_Ntp64ToTimespec(&message->transmit_ts, &remote_transmit);
+ UTI_AddDoubleToTimespec(&remote_transmit, -mono_doffset, &remote_transmit);
+ UTI_Ntp64ToTimespec(&inst->remote_ntp_tx, &prev_remote_transmit);
+ local_receive = inst->local_rx;
+ } else {
+ UTI_Ntp64ToTimespec(&message->receive_ts, &remote_receive);
+ UTI_Ntp64ToTimespec(&message->transmit_ts, &remote_transmit);
+ UTI_ZeroTimespec(&prev_remote_transmit);
+ remote_request_receive = remote_receive;
+ local_receive = *rx_ts;
+ local_transmit = inst->local_tx;
+ local_transmit.net_correction = net_correction;
+ root_delay = pkt_root_delay;
+ root_dispersion = pkt_root_dispersion;
+ }
+
+ /* Calculate intervals between remote and local timestamps */
+ UTI_AverageDiffTimespecs(&remote_receive, &remote_transmit,
+ &remote_average, &remote_interval);
+ UTI_AverageDiffTimespecs(&local_transmit.ts, &local_receive.ts,
+ &local_average, &local_interval);
+ response_time = fabs(UTI_DiffTimespecsToDouble(&remote_transmit,
+ &remote_request_receive));
+
+ precision = LCL_GetSysPrecisionAsQuantum() + UTI_Log2ToDouble(message->precision);
+
+ /* Calculate delay */
+ sample.peer_delay = fabs(local_interval - remote_interval);
+ if (sample.peer_delay < precision)
+ sample.peer_delay = precision;
+
+ /* Calculate offset. Following the NTP definition, this is negative
+ if we are fast of the remote source. */
+ sample.offset = UTI_DiffTimespecsToDouble(&remote_average, &local_average);
+
+ /* Apply configured correction */
+ sample.offset += inst->offset_correction;
+
+ /* We treat the time of the sample as being midway through the local
+ measurement period. An analysis assuming constant relative
+ frequency and zero network delay shows this is the only possible
+ choice to estimate the frequency difference correctly for every
+ sample pair. */
+ sample.time = local_average;
+
+ SST_GetFrequencyRange(stats, &source_freq_lo, &source_freq_hi);
+
+ /* Calculate skew */
+ skew = (source_freq_hi - source_freq_lo) / 2.0;
+
+ /* and then calculate peer dispersion and the rest of the sample */
+ sample.peer_dispersion = MAX(precision, MAX(local_transmit.err, local_receive.err)) +
+ skew * fabs(local_interval);
+ sample.root_delay = root_delay + sample.peer_delay;
+ sample.root_dispersion = root_dispersion + sample.peer_dispersion;
+
+ /* Apply corrections from PTP transparent clocks if available and sane */
+ apply_net_correction(&sample, &local_receive, &local_transmit, precision);
+
+ /* If the source is an active peer, this is the minimum assumed interval
+ between previous two transmissions (if not constrained by minpoll) */
+ prev_remote_poll_interval = UTI_Log2ToDouble(MIN(inst->remote_poll,
+ inst->prev_local_poll));
+
+ /* Additional tests required to pass before accumulating the sample */
+
+ /* Test A combines multiple tests to avoid changing the measurements log
+ format and ntpdata report. It requires that the minimum estimate of the
+ peer delay is not larger than the configured maximum, it is not a
+ response in the 'warm up' exchange, in both client modes that the server
+ processing time is sane, in interleaved client/server mode that the
+ previous response was not in basic mode (which prevents using timestamps
+ that minimise delay error), and in interleaved symmetric mode that the
+ measured delay and intervals between remote timestamps don't indicate
+ a missed response */
+ testA = sample.peer_delay - sample.peer_dispersion <= inst->max_delay &&
+ precision <= inst->max_delay &&
+ inst->presend_done <= 0 &&
+ !(inst->mode == MODE_CLIENT && response_time > MAX_SERVER_INTERVAL) &&
+ !(inst->mode == MODE_CLIENT && interleaved_packet &&
+ UTI_IsZeroTimespec(&inst->prev_local_tx.ts) &&
+ UTI_CompareTimespecs(&local_transmit.ts, &inst->local_tx.ts) == 0) &&
+ !(inst->mode == MODE_ACTIVE && interleaved_packet &&
+ (sample.peer_delay > 0.5 * prev_remote_poll_interval ||
+ UTI_CompareNtp64(&message->receive_ts, &message->transmit_ts) <= 0 ||
+ (inst->remote_poll <= inst->prev_local_poll &&
+ UTI_DiffTimespecsToDouble(&remote_transmit, &prev_remote_transmit) >
+ 1.5 * prev_remote_poll_interval)));
+
+ /* Test B requires in client mode that the ratio of the round trip delay
+ to the minimum one currently in the stats data register is less than an
+ administrator-defined value */
+ testB = check_delay_ratio(inst, stats, &sample.time, sample.peer_delay);
+
+ /* Test C either requires that the delay is less than an estimate of an
+ administrator-defined quantile, or (if the quantile is not specified)
+ it requires that the ratio of the increase in delay from the minimum
+ one in the stats data register to the standard deviation of the offsets
+ in the register is less than an administrator-defined value or the
+ difference between measured offset and predicted offset is larger than
+ the increase in delay */
+ if (inst->delay_quant)
+ testC = check_delay_quant(inst, sample.peer_delay);
+ else
+ testC = check_delay_dev_ratio(inst, stats, &sample.time, sample.offset,
+ sample.peer_delay);
+
+ /* 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;
+ sample.root_delay = sample.root_dispersion = 0.0;
+ sample.time = rx_ts->ts;
+ mono_doffset = 0.0;
+ net_correction = 0.0;
+ local_receive = *rx_ts;
+ local_transmit = inst->local_tx;
+ testA = testB = testC = testD = 0;
+ }
+
+ /* The packet is considered good for synchronisation if
+ the additional tests passed */
+ good_packet = testA && testB && testC && testD;
+
+ /* 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
+ interleaved mode. Protect the timestamps against replay attacks in client
+ mode, and also in symmetric mode as long as the peers use the same polling
+ interval and never start with clocks in future or very distant past.
+ The authentication test (test5) is required to prevent DoS attacks using
+ unauthenticated packets on authenticated symmetric associations. */
+ if ((inst->mode == MODE_CLIENT && valid_packet && !inst->valid_rx) ||
+ (inst->mode == MODE_ACTIVE && valid_packet &&
+ (!inst->valid_rx ||
+ UTI_CompareNtp64(&inst->remote_ntp_tx, &message->transmit_ts) < 0))) {
+ inst->remote_ntp_rx = message->receive_ts;
+ inst->remote_ntp_tx = message->transmit_ts;
+ inst->local_rx = *rx_ts;
+ inst->valid_timestamps = synced_packet;
+
+ UTI_ZeroNtp64(&inst->init_remote_ntp_tx);
+ zero_local_timestamp(&inst->init_local_rx);
+ inst->updated_init_timestamps = 0;
+ updated_timestamps = 2;
+
+ /* If available, update the monotonic timestamp and accumulate the offset.
+ This needs to be done here to not lose changes in remote_ntp_rx in
+ symmetric mode when there are multiple responses per request. */
+ if (ef_mono_root && !UTI_IsZeroNtp64(&ef_mono_root->mono_receive_ts)) {
+ inst->remote_mono_epoch = ntohl(ef_mono_root->mono_epoch);
+ inst->remote_ntp_monorx = ef_mono_root->mono_receive_ts;
+ inst->mono_doffset += mono_doffset;
+ } else {
+ inst->remote_mono_epoch = 0;
+ UTI_ZeroNtp64(&inst->remote_ntp_monorx);
+ inst->mono_doffset = 0.0;
+ }
+
+ inst->local_tx.net_correction = net_correction;
+
+ /* Avoid reusing timestamps of an accumulated sample when switching
+ from basic mode to interleaved mode */
+ if (interleaved_packet || !good_packet)
+ inst->prev_local_tx = inst->local_tx;
+ else
+ zero_local_timestamp(&inst->prev_local_tx);
+ } else if (inst->mode == MODE_ACTIVE &&
+ test1 && !UTI_IsZeroNtp64(&message->transmit_ts) && test5 &&
+ (!inst->updated_init_timestamps ||
+ UTI_CompareNtp64(&inst->init_remote_ntp_tx, &message->transmit_ts) < 0)) {
+ inst->init_remote_ntp_tx = message->transmit_ts;
+ inst->init_local_rx = *rx_ts;
+ inst->updated_init_timestamps = 1;
+ updated_timestamps = 1;
+ } else {
+ updated_timestamps = 0;
+ }
+
+ /* Accept at most one response per request. The NTP specification recommends
+ resetting local_ntp_tx to make the following packets fail test2 or test3,
+ but that would not allow the code above to make multiple updates of the
+ timestamps in symmetric mode. */
+ if (inst->valid_rx) {
+ test2 = test3 = 0;
+ valid_packet = synced_packet = good_packet = 0;
+ } else if (valid_packet) {
+ inst->valid_rx = 1;
+ }
+
+ if ((unsigned int)local_receive.source >= sizeof (tss_chars) ||
+ (unsigned int)local_transmit.source >= sizeof (tss_chars))
+ assert(0);
+
+ DEBUG_LOG("NTP packet lvm=%o stratum=%d poll=%d prec=%d root_delay=%.9f root_disp=%.9f refid=%"PRIx32" [%s]",
+ message->lvm, message->stratum, message->poll, message->precision,
+ pkt_root_delay, pkt_root_dispersion, pkt_refid,
+ message->stratum == NTP_INVALID_STRATUM || message->stratum == 1 ?
+ UTI_RefidToString(pkt_refid) : "");
+ DEBUG_LOG("reference=%s origin=%s receive=%s transmit=%s",
+ UTI_Ntp64ToString(&message->reference_ts),
+ UTI_Ntp64ToString(&message->originate_ts),
+ UTI_Ntp64ToString(&message->receive_ts),
+ UTI_Ntp64ToString(&message->transmit_ts));
+ DEBUG_LOG("offset=%.9f delay=%.9f dispersion=%f root_delay=%f root_dispersion=%f",
+ sample.offset, sample.peer_delay, sample.peer_dispersion,
+ sample.root_delay, sample.root_dispersion);
+ DEBUG_LOG("remote_interval=%.9f local_interval=%.9f response_time=%.9f mono_doffset=%.9f txs=%c rxs=%c",
+ remote_interval, local_interval, response_time, mono_doffset,
+ tss_chars[local_transmit.source], tss_chars[local_receive.source]);
+ DEBUG_LOG("test123=%d%d%d test567=%d%d%d testABCD=%d%d%d%d kod_rate=%d interleaved=%d"
+ " presend=%d valid=%d good=%d updated=%d",
+ test1, test2, test3, test5, test6, test7, testA, testB, testC, testD,
+ kod_rate, interleaved_packet, inst->presend_done, valid_packet, good_packet,
+ updated_timestamps);
+
+ if (valid_packet) {
+ inst->remote_poll = message->poll;
+ inst->remote_stratum = message->stratum != NTP_INVALID_STRATUM ?
+ MIN(message->stratum, NTP_MAX_STRATUM) : NTP_MAX_STRATUM;
+ inst->remote_root_delay = pkt_root_delay;
+ inst->remote_root_dispersion = pkt_root_dispersion;
+
+ inst->prev_local_poll = inst->local_poll;
+ inst->prev_tx_count = inst->tx_count;
+ inst->tx_count = 0;
+
+ SRC_UpdateReachability(inst->source, synced_packet);
+
+ if (synced_packet) {
+ if (inst->copy && inst->remote_stratum > 0) {
+ /* Assume the reference ID and stratum of the server */
+ inst->remote_stratum--;
+ SRC_SetRefid(inst->source, ntohl(message->reference_id), &inst->remote_addr.ip_addr);
+ }
+
+ SRC_UpdateStatus(inst->source, MAX(inst->remote_stratum, inst->min_stratum), pkt_leap);
+
+ if (inst->delay_quant)
+ QNT_Accumulate(inst->delay_quant, sample.peer_delay);
+ }
+
+ if (good_packet) {
+ /* Adjust the polling interval, accumulate the sample, etc. */
+ process_sample(inst, &sample);
+
+ /* If we're in burst mode, check whether the burst is completed and
+ revert to the previous mode */
+ switch (inst->opmode) {
+ case MD_BURST_WAS_ONLINE:
+ case MD_BURST_WAS_OFFLINE:
+ --inst->burst_good_samples_to_go;
+ if (inst->burst_good_samples_to_go <= 0) {
+ if (inst->opmode == MD_BURST_WAS_ONLINE)
+ inst->opmode = MD_ONLINE;
+ else
+ take_offline(inst);
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ /* Slowly increase the polling interval if we can't get a good response */
+ adjust_poll(inst, testD ? 0.02 : 0.1);
+
+ /* Count missing samples for the sample filter */
+ process_sample(inst, NULL);
+ }
+
+ /* If in client mode, no more packets are expected to be coming from the
+ server and the socket can be closed */
+ close_client_socket(inst);
+
+ /* Update the local address and interface */
+ inst->local_addr.ip_addr = local_addr->ip_addr;
+ inst->local_addr.if_index = local_addr->if_index;
+
+ /* And now, requeue the timer */
+ if (inst->opmode != MD_OFFLINE) {
+ delay_time = get_transmit_delay(inst, 0);
+
+ if (kod_rate) {
+ LOG(LOGS_WARN, "Received KoD RATE from %s",
+ UTI_IPToString(&inst->remote_addr.ip_addr));
+
+ /* Back off for a while and stop ongoing burst */
+ delay_time += 4 * UTI_Log2ToDouble(inst->local_poll);
+
+ if (inst->opmode == MD_BURST_WAS_OFFLINE || inst->opmode == MD_BURST_WAS_ONLINE) {
+ inst->burst_good_samples_to_go = 0;
+ }
+ }
+
+ /* Get rid of old timeout and start a new one */
+ if (!saved)
+ assert(inst->tx_timeout_id);
+ restart_timeout(inst, delay_time);
+ }
+
+ /* Update the NTP report */
+ inst->report.local_addr = inst->local_addr.ip_addr;
+ inst->report.leap = pkt_leap;
+ inst->report.version = pkt_version;
+ inst->report.mode = NTP_LVM_TO_MODE(message->lvm);
+ inst->report.stratum = message->stratum;
+ inst->report.poll = message->poll;
+ inst->report.precision = message->precision;
+ inst->report.root_delay = pkt_root_delay;
+ inst->report.root_dispersion = pkt_root_dispersion;
+ inst->report.ref_id = pkt_refid;
+ UTI_Ntp64ToTimespec(&message->reference_ts, &inst->report.ref_time);
+ inst->report.offset = sample.offset;
+ inst->report.peer_delay = sample.peer_delay;
+ inst->report.peer_dispersion = sample.peer_dispersion;
+ inst->report.response_time = response_time;
+ inst->report.jitter_asymmetry = SST_GetJitterAsymmetry(stats);
+ inst->report.tests = ((((((((test1 << 1 | test2) << 1 | test3) << 1 |
+ test5) << 1 | test6) << 1 | test7) << 1 |
+ testA) << 1 | testB) << 1 | testC) << 1 | testD;
+ inst->report.interleaved = interleaved_packet;
+ 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];
+
+ inst->report.total_valid_count++;
+ if (good_packet)
+ inst->report.total_good_count++;
+ }
+
+ /* Do measurement logging */
+ if (logfileid != -1 && (log_raw_measurements || synced_packet)) {
+ LOG_FileWrite(logfileid, "%s %-15s %1c %2d %1d%1d%1d %1d%1d%1d %1d%1d%1d%d %2d %2d %4.2f %10.3e %10.3e %10.3e %10.3e %10.3e %08"PRIX32" %1d%1c %1c %1c",
+ UTI_TimeToLogForm(sample.time.tv_sec),
+ UTI_IPToString(&inst->remote_addr.ip_addr),
+ leap_chars[pkt_leap],
+ message->stratum,
+ test1, test2, test3, test5, test6, test7, testA, testB, testC, testD,
+ inst->local_poll, message->poll,
+ inst->poll_score,
+ sample.offset, sample.peer_delay, sample.peer_dispersion,
+ pkt_root_delay, pkt_root_dispersion, pkt_refid,
+ NTP_LVM_TO_MODE(message->lvm), interleaved_packet ? 'I' : 'B',
+ tss_chars[local_transmit.source],
+ tss_chars[local_receive.source]);
+ }
+
+ return good_packet;
+}
+
+/* ================================================== */
+/* From RFC 5905, the standard handling of received packets, depending
+ on the mode of the packet and of the source, is :
+
+ +------------------+---------------------------------------+
+ | | Packet Mode |
+ +------------------+-------+-------+-------+-------+-------+
+ | Association Mode | 1 | 2 | 3 | 4 | 5 |
+ +------------------+-------+-------+-------+-------+-------+
+ | No Association 0 | NEWPS | DSCRD | FXMIT | MANY | NEWBC |
+ | Symm. Active 1 | PROC | PROC | DSCRD | DSCRD | DSCRD |
+ | Symm. Passive 2 | PROC | ERR | DSCRD | DSCRD | DSCRD |
+ | Client 3 | DSCRD | DSCRD | DSCRD | PROC | DSCRD |
+ | Server 4 | DSCRD | DSCRD | DSCRD | DSCRD | DSCRD |
+ | Broadcast 5 | DSCRD | DSCRD | DSCRD | DSCRD | DSCRD |
+ | Bcast Client 6 | DSCRD | DSCRD | DSCRD | DSCRD | PROC |
+ +------------------+-------+-------+-------+-------+-------+
+
+ Association mode 0 is implemented in NCR_ProcessRxUnknown(), other modes
+ in NCR_ProcessRxKnown().
+
+ Broadcast, manycast and ephemeral symmetric passive associations are not
+ supported yet.
+ */
+
+/* ================================================== */
+/* 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 */
+
+int
+NCR_ProcessRxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length)
+{
+ int proc_packet, proc_as_unknown;
+ NTP_PacketInfo info;
+
+ inst->report.total_rx_count++;
+
+ if (!parse_packet(message, length, &info))
+ return 0;
+
+ proc_packet = 0;
+ proc_as_unknown = 0;
+
+ /* Now, depending on the mode we decide what to do */
+ switch (info.mode) {
+ case MODE_ACTIVE:
+ switch (inst->mode) {
+ case MODE_ACTIVE:
+ /* Ordinary symmetric peering */
+ proc_packet = 1;
+ break;
+ case MODE_PASSIVE:
+ /* In this software this case should not arise, we don't
+ support unconfigured peers */
+ break;
+ case MODE_CLIENT:
+ /* This is where we have the remote configured as a server and he has
+ us configured as a peer, process as from an unknown source */
+ proc_as_unknown = 1;
+ break;
+ default:
+ /* Discard */
+ break;
+ }
+ break;
+
+ case MODE_PASSIVE:
+ switch (inst->mode) {
+ case MODE_ACTIVE:
+ /* This would arise if we have the remote configured as a peer and
+ he does not have us configured */
+ proc_packet = 1;
+ break;
+ case MODE_PASSIVE:
+ /* Error condition in RFC 5905 */
+ break;
+ default:
+ /* Discard */
+ break;
+ }
+ break;
+
+ case MODE_CLIENT:
+ /* If message is client mode, we just respond with a server mode
+ packet, regardless of what we think the remote machine is
+ supposed to be. However, even though this is a configured
+ peer or server, we still implement access restrictions on
+ client mode operation.
+
+ This copes with the case for an isolated network where one
+ machine is set by eye and is used as the primary server, with
+ the other machines pointed at it. If the server goes down, we
+ want to be able to reset its time at startup by relying on
+ one of the secondaries to flywheel it. The behaviour coded here
+ is required in the secondaries to make this possible. */
+
+ proc_as_unknown = 1;
+ break;
+
+ case MODE_SERVER:
+ switch (inst->mode) {
+ case MODE_CLIENT:
+ /* Standard case where he's a server and we're the client */
+ proc_packet = 1;
+ break;
+ default:
+ /* Discard */
+ break;
+ }
+ break;
+
+ case MODE_BROADCAST:
+ /* Just ignore these */
+ break;
+
+ default:
+ /* Obviously ignore */
+ break;
+ }
+
+ if (proc_packet) {
+ /* Check if the reply was received by the socket that sent the request */
+ if (local_addr->sock_fd != inst->local_addr.sock_fd) {
+ DEBUG_LOG("Packet received by wrong socket %d (expected %d)",
+ local_addr->sock_fd, inst->local_addr.sock_fd);
+ return 0;
+ }
+
+ /* Ignore packets from offline sources */
+ if (inst->opmode == MD_OFFLINE || inst->tx_suspended) {
+ DEBUG_LOG("Packet from offline source");
+ return 0;
+ }
+
+ return process_response(inst, 0, 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 mode=%d our_mode=%u", (int)info.mode, inst->mode);
+ return 0;
+ }
+}
+
+/* ================================================== */
+/* This routine is called when a new packet arrives off the network,
+ and it relates to a source we don't know (not our server or peer) */
+
+void
+NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length)
+{
+ NTP_PacketInfo info;
+ NTP_Mode my_mode;
+ NTP_Local_Timestamp local_tx, *tx_ts;
+ NTP_int64 ntp_rx, *local_ntp_rx;
+ 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)) {
+ DEBUG_LOG("NTP request packet received by client socket %d", local_addr->sock_fd);
+ return;
+ }
+
+ 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",
+ UTI_IPToString(&remote_addr->ip_addr));
+ return;
+ }
+
+ switch (info.mode) {
+ case MODE_ACTIVE:
+ /* We are symmetric passive, even though we don't ever lock to him */
+ my_mode = MODE_PASSIVE;
+ break;
+ case MODE_CLIENT:
+ /* Reply with server packet */
+ my_mode = MODE_SERVER;
+ break;
+ case MODE_UNDEFINED:
+ /* 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 (info.version == 1 && remote_addr->port != NTP_PORT) {
+ my_mode = MODE_SERVER;
+ break;
+ }
+ /* Fall through */
+ default:
+ /* Discard */
+ DEBUG_LOG("NTP packet discarded mode=%d", (int)info.mode);
+ return;
+ }
+
+ kod = 0;
+ log_index = CLG_LogServiceAccess(CLG_NTP, &remote_addr->ip_addr, &rx_ts->ts);
+
+ /* Don't reply to all requests if the rate is excessive */
+ if (log_index >= 0 && CLG_LimitServiceRate(CLG_NTP, log_index)) {
+ DEBUG_LOG("NTP packet discarded to limit response rate");
+ return;
+ }
+
+ /* Check authentication */
+ if (!NAU_CheckRequestAuth(message, &info, &kod)) {
+ DEBUG_LOG("NTP packet failed auth mode=%d kod=%"PRIx32, (int)info.auth.mode, kod);
+
+ /* Don't respond unless a non-zero KoD was returned */
+ if (kod == 0)
+ return;
+ }
+
+ local_ntp_rx = NULL;
+ tx_ts = NULL;
+ interleaved = 0;
+
+ /* Handle requests formed in the interleaved mode. As an optimisation to
+ avoid saving all receive timestamps, require that the origin timestamp
+ has the lowest bit equal to 1, which indicates it was set to one of our
+ receive timestamps instead of transmit timestamps or zero. Respond in the
+ interleaved mode if the receive timestamp is found and it has a non-zero
+ transmit timestamp (this is verified in transmit_packet()). For a new
+ client starting with a zero origin timestamp, the third response is the
+ earliest one that can be interleaved. */
+ if (kod == 0 && log_index >= 0 && info.version == 4 &&
+ message->originate_ts.lo & htonl(1) &&
+ UTI_CompareNtp64(&message->receive_ts, &message->transmit_ts) != 0) {
+ ntp_rx = message->originate_ts;
+ local_ntp_rx = &ntp_rx;
+ zero_local_timestamp(&local_tx);
+ interleaved = CLG_GetNtpTxTimestamp(&ntp_rx, &local_tx.ts, &local_tx.source);
+
+ tx_ts = &local_tx;
+ if (interleaved)
+ CLG_DisableNtpTimestamps(&ntp_rx);
+ }
+
+ CLG_UpdateNtpStats(kod != 0 && info.auth.mode != NTP_AUTH_NONE &&
+ info.auth.mode != NTP_AUTH_MSSNTP,
+ rx_ts->source, interleaved ? tx_ts->source : NTP_TS_DAEMON);
+
+ /* Suggest the client to increase its polling interval if it indicates
+ the interval is shorter than the rate limiting interval */
+ poll = CLG_GetNtpMinPoll();
+ poll = MAX(poll, message->poll);
+
+ /* Respond with the same version */
+ version = info.version;
+
+ /* Send a reply */
+ if (!transmit_packet(my_mode, interleaved, poll, version, kod, info.ext_field_flags, NULL,
+ &message->receive_ts, &message->transmit_ts,
+ rx_ts, tx_ts, local_ntp_rx, NULL, remote_addr, local_addr,
+ message, &info))
+ return;
+
+ if (local_ntp_rx)
+ CLG_SaveNtpTimestamps(local_ntp_rx, &tx_ts->ts, tx_ts->source);
+}
+
+/* ================================================== */
+
+static void
+update_tx_timestamp(NTP_Local_Timestamp *tx_ts, NTP_Local_Timestamp *new_tx_ts,
+ NTP_int64 *local_ntp_rx, NTP_int64 *local_ntp_tx, NTP_Packet *message)
+{
+ double delay;
+
+ if (UTI_IsZeroTimespec(&tx_ts->ts)) {
+ DEBUG_LOG("Unexpected TX update");
+ return;
+ }
+
+ /* Check if this is the last packet that was sent */
+ if ((local_ntp_rx && UTI_CompareNtp64(&message->receive_ts, local_ntp_rx)) ||
+ (local_ntp_tx && UTI_CompareNtp64(&message->transmit_ts, local_ntp_tx))) {
+ DEBUG_LOG("RX/TX timestamp mismatch");
+ return;
+ }
+
+ delay = UTI_DiffTimespecsToDouble(&new_tx_ts->ts, &tx_ts->ts);
+
+ if (delay < 0.0 || delay > MAX_TX_DELAY) {
+ DEBUG_LOG("Unacceptable TX delay %.9f", delay);
+ return;
+ }
+
+ *tx_ts = *new_tx_ts;
+
+ DEBUG_LOG("Updated TX timestamp delay=%.9f", delay);
+}
+
+/* ================================================== */
+
+void
+NCR_ProcessTxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length)
+{
+ NTP_PacketInfo info;
+
+ if (!parse_packet(message, length, &info))
+ return;
+
+ /* Server and passive mode packets are responses to unknown sources */
+ if (info.mode != MODE_CLIENT && info.mode != MODE_ACTIVE) {
+ NCR_ProcessTxUnknown(&inst->remote_addr, local_addr, tx_ts, message, length);
+ return;
+ }
+
+ update_tx_timestamp(&inst->local_tx, tx_ts, &inst->local_ntp_rx, &inst->local_ntp_tx,
+ message);
+
+ if (tx_ts->source == NTP_TS_HARDWARE) {
+ if (has_saved_response(inst))
+ process_saved_response(inst);
+ }
+}
+
+/* ================================================== */
+
+void
+NCR_ProcessTxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length)
+{
+ NTP_Local_Timestamp old_tx, new_tx;
+ NTP_int64 *local_ntp_rx;
+ NTP_PacketInfo info;
+
+ if (!parse_packet(message, length, &info))
+ return;
+
+ if (info.mode == MODE_BROADCAST)
+ return;
+
+ if (SMT_IsEnabled() && info.mode == MODE_SERVER)
+ UTI_AddDoubleToTimespec(&tx_ts->ts, SMT_GetOffset(&tx_ts->ts), &tx_ts->ts);
+
+ local_ntp_rx = &message->receive_ts;
+ new_tx = *tx_ts;
+
+ if (!CLG_GetNtpTxTimestamp(local_ntp_rx, &old_tx.ts, &old_tx.source))
+ return;
+
+ /* Undo a clock adjustment between the RX and TX timestamps to minimise error
+ in the delay measured by the client */
+ CLG_UndoNtpTxTimestampSlew(local_ntp_rx, &new_tx.ts);
+
+ update_tx_timestamp(&old_tx, &new_tx, local_ntp_rx, NULL, message);
+
+ CLG_UpdateNtpTxTimestamp(local_ntp_rx, &new_tx.ts, new_tx.source);
+}
+
+/* ================================================== */
+
+void
+NCR_SlewTimes(NCR_Instance inst, struct timespec *when, double dfreq, double doffset)
+{
+ double delta;
+
+ if (!UTI_IsZeroTimespec(&inst->local_rx.ts))
+ UTI_AdjustTimespec(&inst->local_rx.ts, when, &inst->local_rx.ts, &delta, dfreq, doffset);
+ if (!UTI_IsZeroTimespec(&inst->local_tx.ts))
+ UTI_AdjustTimespec(&inst->local_tx.ts, when, &inst->local_tx.ts, &delta, dfreq, doffset);
+ if (!UTI_IsZeroTimespec(&inst->prev_local_tx.ts))
+ UTI_AdjustTimespec(&inst->prev_local_tx.ts, when, &inst->prev_local_tx.ts, &delta, dfreq,
+ doffset);
+ if (!UTI_IsZeroTimespec(&inst->init_local_rx.ts))
+ UTI_AdjustTimespec(&inst->init_local_rx.ts, when, &inst->init_local_rx.ts, &delta, dfreq,
+ doffset);
+
+ if (inst->filter)
+ SPF_SlewSamples(inst->filter, when, dfreq, doffset);
+
+ if (has_saved_response(inst))
+ UTI_AdjustTimespec(&inst->saved_response->rx_ts.ts, when, &inst->saved_response->rx_ts.ts,
+ &delta, dfreq, doffset);
+}
+
+/* ================================================== */
+
+static void
+set_connectivity(NCR_Instance inst, SRC_Connectivity connectivity)
+{
+ if (connectivity == SRC_MAYBE_ONLINE)
+ connectivity = NIO_IsServerConnectable(&inst->remote_addr) ? SRC_ONLINE : SRC_OFFLINE;
+
+ switch (connectivity) {
+ case SRC_ONLINE:
+ switch (inst->opmode) {
+ case MD_ONLINE:
+ /* Nothing to do */
+ break;
+ case MD_OFFLINE:
+ inst->opmode = MD_ONLINE;
+ NCR_ResetInstance(inst);
+ start_initial_timeout(inst);
+ if (inst->auto_iburst)
+ NCR_InitiateSampleBurst(inst, IBURST_GOOD_SAMPLES, IBURST_TOTAL_SAMPLES);
+ break;
+ case MD_BURST_WAS_ONLINE:
+ /* Will revert */
+ break;
+ case MD_BURST_WAS_OFFLINE:
+ inst->opmode = MD_BURST_WAS_ONLINE;
+ break;
+ default:
+ assert(0);
+ }
+ break;
+ case SRC_OFFLINE:
+ switch (inst->opmode) {
+ case MD_ONLINE:
+ take_offline(inst);
+ break;
+ case MD_OFFLINE:
+ break;
+ case MD_BURST_WAS_ONLINE:
+ inst->opmode = MD_BURST_WAS_OFFLINE;
+ break;
+ case MD_BURST_WAS_OFFLINE:
+ break;
+ default:
+ assert(0);
+ }
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+void
+NCR_SetConnectivity(NCR_Instance inst, SRC_Connectivity connectivity)
+{
+ OperatingMode prev_opmode;
+ int was_online, is_online;
+
+ prev_opmode = inst->opmode;
+
+ set_connectivity(inst, connectivity);
+
+ /* Report an important change */
+ was_online = prev_opmode == MD_ONLINE || prev_opmode == MD_BURST_WAS_ONLINE;
+ is_online = inst->opmode == MD_ONLINE || inst->opmode == MD_BURST_WAS_ONLINE;
+ if (was_online != is_online)
+ LOG(LOGS_INFO, "Source %s %s",
+ UTI_IPToString(&inst->remote_addr.ip_addr), is_online ? "online" : "offline");
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMinpoll(NCR_Instance inst, int new_minpoll)
+{
+ if (new_minpoll < MIN_POLL || new_minpoll > MAX_POLL)
+ return;
+ inst->minpoll = new_minpoll;
+ LOG(LOGS_INFO, "Source %s new minpoll %d", UTI_IPToString(&inst->remote_addr.ip_addr), new_minpoll);
+ if (inst->maxpoll < inst->minpoll)
+ NCR_ModifyMaxpoll(inst, inst->minpoll);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMaxpoll(NCR_Instance inst, int new_maxpoll)
+{
+ if (new_maxpoll < MIN_POLL || new_maxpoll > MAX_POLL)
+ return;
+ inst->maxpoll = new_maxpoll;
+ LOG(LOGS_INFO, "Source %s new maxpoll %d", UTI_IPToString(&inst->remote_addr.ip_addr), new_maxpoll);
+ if (inst->minpoll > inst->maxpoll)
+ NCR_ModifyMinpoll(inst, inst->maxpoll);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMaxdelay(NCR_Instance inst, double new_max_delay)
+{
+ inst->max_delay = CLAMP(0.0, new_max_delay, MAX_MAXDELAY);
+ LOG(LOGS_INFO, "Source %s new maxdelay %f",
+ UTI_IPToString(&inst->remote_addr.ip_addr), inst->max_delay);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMaxdelayratio(NCR_Instance inst, double new_max_delay_ratio)
+{
+ inst->max_delay_ratio = CLAMP(0.0, new_max_delay_ratio, MAX_MAXDELAYRATIO);
+ LOG(LOGS_INFO, "Source %s new maxdelayratio %f",
+ UTI_IPToString(&inst->remote_addr.ip_addr), inst->max_delay_ratio);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMaxdelaydevratio(NCR_Instance inst, double new_max_delay_dev_ratio)
+{
+ inst->max_delay_dev_ratio = CLAMP(0.0, new_max_delay_dev_ratio, MAX_MAXDELAYDEVRATIO);
+ LOG(LOGS_INFO, "Source %s new maxdelaydevratio %f",
+ UTI_IPToString(&inst->remote_addr.ip_addr), inst->max_delay_dev_ratio);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyMinstratum(NCR_Instance inst, int new_min_stratum)
+{
+ inst->min_stratum = new_min_stratum;
+ LOG(LOGS_INFO, "Source %s new minstratum %d",
+ UTI_IPToString(&inst->remote_addr.ip_addr), new_min_stratum);
+}
+
+/* ================================================== */
+
+void
+NCR_ModifyPolltarget(NCR_Instance inst, int new_poll_target)
+{
+ inst->poll_target = MAX(1, new_poll_target);
+ LOG(LOGS_INFO, "Source %s new polltarget %d",
+ UTI_IPToString(&inst->remote_addr.ip_addr), new_poll_target);
+}
+
+/* ================================================== */
+
+void
+NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples)
+{
+
+ if (inst->mode == MODE_CLIENT) {
+
+ /* We want to prevent burst mode being used on symmetric active
+ associations - it will play havoc with the peer's sampling
+ strategy. (This obviously relies on us having the peer
+ configured that way if he has us configured symmetric active -
+ but there's not much else we can do.) */
+
+ switch (inst->opmode) {
+ case MD_BURST_WAS_OFFLINE:
+ case MD_BURST_WAS_ONLINE:
+ /* If already burst sampling, don't start again */
+ break;
+
+ case MD_ONLINE:
+ case MD_OFFLINE:
+ inst->opmode = inst->opmode == MD_ONLINE ?
+ MD_BURST_WAS_ONLINE : MD_BURST_WAS_OFFLINE;
+ inst->burst_good_samples_to_go = n_good_samples;
+ inst->burst_total_samples_to_go = n_total_samples;
+ start_initial_timeout(inst);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+
+}
+
+/* ================================================== */
+
+void
+NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timespec *now)
+{
+ report->poll = get_transmit_poll(inst);
+
+ switch (inst->mode) {
+ case MODE_CLIENT:
+ report->mode = RPT_NTP_CLIENT;
+ break;
+ case MODE_ACTIVE:
+ report->mode = RPT_NTP_PEER;
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+void
+NCR_GetAuthReport(NCR_Instance inst, RPT_AuthReport *report)
+{
+ NAU_GetReport(inst->auth, report);
+}
+
+/* ================================================== */
+
+void
+NCR_GetNTPReport(NCR_Instance inst, RPT_NTPReport *report)
+{
+ *report = inst->report;
+}
+
+/* ================================================== */
+
+int
+NCR_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all)
+ {
+ ADF_Status status;
+
+ if (allow) {
+ if (all) {
+ status = ADF_AllowAll(access_auth_table, ip_addr, subnet_bits);
+ } else {
+ status = ADF_Allow(access_auth_table, ip_addr, subnet_bits);
+ }
+ } else {
+ if (all) {
+ status = ADF_DenyAll(access_auth_table, ip_addr, subnet_bits);
+ } else {
+ status = ADF_Deny(access_auth_table, ip_addr, subnet_bits);
+ }
+ }
+
+ if (status != ADF_SUCCESS)
+ return 0;
+
+ LOG(LOG_GetContextSeverity(LOGC_Command), "%s%s %s access from %s",
+ allow ? "Allowed" : "Denied", all ? " all" : "", "NTP",
+ UTI_IPSubnetToString(ip_addr, subnet_bits));
+
+ /* Keep server sockets open only when an address allowed */
+ if (allow) {
+ NTP_Remote_Address remote_addr;
+
+ if (server_sock_fd4 == INVALID_SOCK_FD &&
+ ADF_IsAnyAllowed(access_auth_table, IPADDR_INET4)) {
+ remote_addr.ip_addr.family = IPADDR_INET4;
+ remote_addr.port = 0;
+ server_sock_fd4 = NIO_OpenServerSocket(&remote_addr);
+ }
+ if (server_sock_fd6 == INVALID_SOCK_FD &&
+ ADF_IsAnyAllowed(access_auth_table, IPADDR_INET6)) {
+ remote_addr.ip_addr.family = IPADDR_INET6;
+ remote_addr.port = 0;
+ server_sock_fd6 = NIO_OpenServerSocket(&remote_addr);
+ }
+ } else {
+ if (server_sock_fd4 != INVALID_SOCK_FD &&
+ !ADF_IsAnyAllowed(access_auth_table, IPADDR_INET4)) {
+ NIO_CloseServerSocket(server_sock_fd4);
+ server_sock_fd4 = INVALID_SOCK_FD;
+ }
+ if (server_sock_fd6 != INVALID_SOCK_FD &&
+ !ADF_IsAnyAllowed(access_auth_table, IPADDR_INET6)) {
+ NIO_CloseServerSocket(server_sock_fd6);
+ server_sock_fd6 = INVALID_SOCK_FD;
+ }
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NCR_CheckAccessRestriction(IPAddr *ip_addr)
+{
+ return ADF_IsAllowed(access_auth_table, ip_addr);
+}
+
+/* ================================================== */
+
+void
+NCR_IncrementActivityCounters(NCR_Instance inst, int *online, int *offline,
+ int *burst_online, int *burst_offline)
+{
+ switch (inst->opmode) {
+ case MD_BURST_WAS_OFFLINE:
+ ++*burst_offline;
+ break;
+ case MD_BURST_WAS_ONLINE:
+ ++*burst_online;
+ break;
+ case MD_ONLINE:
+ ++*online;
+ break;
+ case MD_OFFLINE:
+ ++*offline;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+}
+
+/* ================================================== */
+
+NTP_Remote_Address *
+NCR_GetRemoteAddress(NCR_Instance inst)
+{
+ return &inst->remote_addr;
+}
+
+/* ================================================== */
+
+uint32_t
+NCR_GetLocalRefid(NCR_Instance inst)
+{
+ return UTI_IPToRefid(&inst->local_addr.ip_addr);
+}
+
+/* ================================================== */
+
+int NCR_IsSyncPeer(NCR_Instance inst)
+{
+ return SRC_IsSyncPeer(inst->source);
+}
+
+/* ================================================== */
+
+void
+NCR_DumpAuthData(NCR_Instance inst)
+{
+ NAU_DumpData(inst->auth);
+}
+
+/* ================================================== */
+
+static void
+broadcast_timeout(void *arg)
+{
+ BroadcastDestination *destination;
+ NTP_int64 orig_ts;
+ NTP_Local_Timestamp recv_ts;
+ int poll;
+
+ destination = ARR_GetElement(broadcasts, (long)arg);
+ poll = round(log(destination->interval) / log(2.0));
+
+ UTI_ZeroNtp64(&orig_ts);
+ zero_local_timestamp(&recv_ts);
+
+ transmit_packet(MODE_BROADCAST, 0, poll, NTP_VERSION, 0, 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,
+ SCH_NtpBroadcastClass, broadcast_timeout, arg);
+}
+
+/* ================================================== */
+
+void
+NCR_AddBroadcastDestination(NTP_Remote_Address *addr, int interval)
+{
+ BroadcastDestination *destination;
+
+ destination = (BroadcastDestination *)ARR_GetNewElement(broadcasts);
+
+ destination->addr = *addr;
+ 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,
+ SCH_NtpBroadcastClass, broadcast_timeout,
+ (void *)(long)(ARR_GetSize(broadcasts) - 1));
+}
diff --git a/ntp_core.h b/ntp_core.h
new file mode 100644
index 0000000..5c5a614
--- /dev/null
+++ b/ntp_core.h
@@ -0,0 +1,140 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 main NTP protocol engine
+ */
+
+#ifndef GOT_NTP_CORE_H
+#define GOT_NTP_CORE_H
+
+#include "sysincl.h"
+
+#include "addressing.h"
+#include "srcparams.h"
+#include "ntp.h"
+#include "reports.h"
+
+typedef enum {
+ NTP_SERVER, NTP_PEER
+} NTP_Source_Type;
+
+typedef struct {
+ struct timespec ts;
+ double err;
+ NTP_Timestamp_Source source;
+ double rx_duration;
+ double net_correction;
+} NTP_Local_Timestamp;
+
+/* This is a private data type used for storing the instance record for
+ each source that we are chiming with */
+typedef struct NCR_Instance_Record *NCR_Instance;
+
+/* Init and fini functions */
+extern void NCR_Initialise(void);
+extern void NCR_Finalise(void);
+
+/* Get a new instance for a server or peer */
+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);
+
+/* Start an instance */
+extern void NCR_StartInstance(NCR_Instance instance);
+
+/* Reset an instance */
+extern void NCR_ResetInstance(NCR_Instance inst);
+
+/* Reset polling interval of an instance */
+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,
+ 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 */
+extern int NCR_ProcessRxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length);
+
+/* This routine is called when a new packet arrives off the network,
+ and we do not recognize its source */
+extern void NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length);
+
+/* This routine is called when a packet is sent to a source we have
+ an ongoing protocol exchange with */
+extern void NCR_ProcessTxKnown(NCR_Instance inst, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length);
+
+/* This routine is called when a packet is sent to a destination we
+ do not recognize */
+extern void NCR_ProcessTxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length);
+
+/* Slew receive and transmit times in instance records */
+extern void NCR_SlewTimes(NCR_Instance inst, struct timespec *when, double dfreq, double doffset);
+
+/* Take a particular source online (i.e. start sampling it) or offline
+ (i.e. stop sampling it) */
+extern void NCR_SetConnectivity(NCR_Instance inst, SRC_Connectivity connectivity);
+
+extern void NCR_ModifyMinpoll(NCR_Instance inst, int new_minpoll);
+
+extern void NCR_ModifyMaxpoll(NCR_Instance inst, int new_maxpoll);
+
+extern void NCR_ModifyMaxdelay(NCR_Instance inst, double new_max_delay);
+
+extern void NCR_ModifyMaxdelayratio(NCR_Instance inst, double new_max_delay_ratio);
+
+extern void NCR_ModifyMaxdelaydevratio(NCR_Instance inst, double new_max_delay_dev_ratio);
+
+extern void NCR_ModifyMinstratum(NCR_Instance inst, int new_min_stratum);
+
+extern void NCR_ModifyPolltarget(NCR_Instance inst, int new_poll_target);
+
+extern void NCR_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples);
+
+extern void NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timespec *now);
+extern void NCR_GetAuthReport(NCR_Instance inst, RPT_AuthReport *report);
+extern void NCR_GetNTPReport(NCR_Instance inst, RPT_NTPReport *report);
+
+extern int NCR_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all);
+extern int NCR_CheckAccessRestriction(IPAddr *ip_addr);
+
+extern void NCR_IncrementActivityCounters(NCR_Instance inst, int *online, int *offline,
+ int *burst_online, int *burst_offline);
+
+extern NTP_Remote_Address *NCR_GetRemoteAddress(NCR_Instance instance);
+
+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(NTP_Remote_Address *addr, int interval);
+
+#endif /* GOT_NTP_CORE_H */
diff --git a/ntp_ext.c b/ntp_ext.c
new file mode 100644
index 0000000..d2babb1
--- /dev/null
+++ b/ntp_ext.c
@@ -0,0 +1,192 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2019-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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ 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;
+}
diff --git a/ntp_ext.h b/ntp_ext.h
new file mode 100644
index 0000000..7405be8
--- /dev/null
+++ b/ntp_ext.h
@@ -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
diff --git a/ntp_io.c b/ntp_io.c
new file mode 100644
index 0000000..ec2fd7b
--- /dev/null
+++ b/ntp_io.c
@@ -0,0 +1,634 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Timo Teras 2009
+ * Copyright (C) Miroslav Lichvar 2009, 2013-2016, 2018-2021
+ *
+ * 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 file deals with the IO aspects of reading and writing NTP packets
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "memory.h"
+#include "ntp_io.h"
+#include "ntp_core.h"
+#include "ntp_sources.h"
+#include "ptp.h"
+#include "sched.h"
+#include "socket.h"
+#include "local.h"
+#include "logging.h"
+#include "conf.h"
+#include "privops.h"
+#include "util.h"
+
+#ifdef HAVE_LINUX_TIMESTAMPING
+#include "ntp_io_linux.h"
+#endif
+
+#define INVALID_SOCK_FD -1
+
+/* The server/peer and client sockets for IPv4 and IPv6 */
+static int server_sock_fd4;
+static int server_sock_fd6;
+static int client_sock_fd4;
+static int client_sock_fd6;
+
+/* Reference counters for server sockets to keep them open only when needed */
+static int server_sock_ref4;
+static int server_sock_ref6;
+
+/* Flag indicating we create a new connected client socket for each
+ server instead of sharing client_sock_fd4 and client_sock_fd6 */
+static int separate_client_sockets;
+
+/* Flag indicating the server sockets are not created dynamically when needed,
+ either to have a socket for client requests when separate client sockets
+ are disabled and client port is equal to server port, or the server port is
+ disabled */
+static int permanent_server_sockets;
+
+/* Flag indicating the server IPv4 socket is bound to an address */
+static int bound_server_sock_fd4;
+
+/* PTP event port, or 0 if disabled */
+static int ptp_port;
+
+/* Shared server/client sockets for NTP-over-PTP */
+static int ptp_sock_fd4;
+static int ptp_sock_fd6;
+
+/* Buffer for transmitted NTP-over-PTP messages */
+static PTP_NtpMessage *ptp_message;
+
+/* Flag indicating that we have been initialised */
+static int initialised=0;
+
+/* ================================================== */
+
+/* Forward prototypes */
+static void read_from_socket(int sock_fd, int event, void *anything);
+
+/* ================================================== */
+
+static int
+open_socket(int family, int local_port, int client_only, IPSockAddr *remote_addr)
+{
+ int sock_fd, sock_flags, dscp, events = SCH_FILE_INPUT;
+ IPSockAddr local_addr;
+ char *iface;
+
+ if (!SCK_IsIpFamilyEnabled(family))
+ return INVALID_SOCK_FD;
+
+ if (!client_only) {
+ CNF_GetBindAddress(family, &local_addr.ip_addr);
+ iface = CNF_GetBindNtpInterface();
+ } else {
+ CNF_GetBindAcquisitionAddress(family, &local_addr.ip_addr);
+ iface = CNF_GetBindAcquisitionInterface();
+ }
+
+ 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, iface, sock_flags);
+ if (sock_fd < 0) {
+ if (!client_only)
+ LOG(LOGS_ERR, "Could not open NTP socket on %s", UTI_IPSockAddrToString(&local_addr));
+ return INVALID_SOCK_FD;
+ }
+
+ dscp = CNF_GetNtpDscp();
+ if (dscp > 0 && dscp < 64) {
+#ifdef IP_TOS
+ if (family == IPADDR_INET4)
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_TOS, dscp << 2))
+ ;
+#endif
+#if defined(FEAT_IPV6) && defined(IPV6_TCLASS)
+ if (family == IPADDR_INET6)
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, dscp << 2))
+ ;
+#endif
+ }
+
+ 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
+ if (!SCK_EnableKernelRxTimestamping(sock_fd))
+ ;
+
+ /* Register handler for read and possibly exception events on the socket */
+ SCH_AddFileHandler(sock_fd, events, read_from_socket, NULL);
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+static int
+open_separate_client_socket(IPSockAddr *remote_addr)
+{
+ return open_socket(remote_addr->ip_addr.family, 0, 1, remote_addr);
+}
+
+/* ================================================== */
+
+static void
+close_socket(int sock_fd)
+{
+ if (sock_fd == INVALID_SOCK_FD)
+ return;
+
+ SCH_RemoveFileHandler(sock_fd);
+ SCK_CloseSocket(sock_fd);
+}
+
+/* ================================================== */
+
+void
+NIO_Initialise(void)
+{
+ int server_port, client_port;
+
+ assert(!initialised);
+ initialised = 1;
+
+#ifdef PRIVOPS_BINDSOCKET
+ SCK_SetPrivBind(PRV_BindSocket);
+#endif
+
+#ifdef HAVE_LINUX_TIMESTAMPING
+ NIO_Linux_Initialise();
+#else
+ if (1) {
+ CNF_HwTsInterface *conf_iface;
+ if (CNF_GetHwTsInterface(0, &conf_iface))
+ LOG_FATAL("HW timestamping not supported");
+ }
+#endif
+
+ server_port = CNF_GetNTPPort();
+ client_port = CNF_GetAcquisitionPort();
+
+ /* Use separate connected sockets if client port is negative */
+ separate_client_sockets = client_port < 0;
+ if (client_port < 0)
+ client_port = 0;
+
+ permanent_server_sockets = !server_port || (!separate_client_sockets &&
+ client_port == server_port);
+
+ server_sock_fd4 = INVALID_SOCK_FD;
+ 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;
+
+ if (permanent_server_sockets && server_port) {
+ server_sock_fd4 = open_socket(IPADDR_INET4, server_port, 0, NULL);
+ server_sock_fd6 = open_socket(IPADDR_INET6, server_port, 0, NULL);
+ }
+
+ if (!separate_client_sockets) {
+ if (client_port != server_port || !server_port) {
+ client_sock_fd4 = open_socket(IPADDR_INET4, client_port, 1, NULL);
+ client_sock_fd6 = open_socket(IPADDR_INET6, client_port, 1, NULL);
+ } else {
+ client_sock_fd4 = server_sock_fd4;
+ client_sock_fd6 = server_sock_fd6;
+ }
+ }
+
+ 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");
+ }
+
+ ptp_port = CNF_GetPtpPort();
+ ptp_sock_fd4 = INVALID_SOCK_FD;
+ ptp_sock_fd6 = INVALID_SOCK_FD;
+ ptp_message = NULL;
+
+ if (ptp_port > 0) {
+ ptp_sock_fd4 = open_socket(IPADDR_INET4, ptp_port, 0, NULL);
+ ptp_sock_fd6 = open_socket(IPADDR_INET6, ptp_port, 0, NULL);
+ ptp_message = MallocNew(PTP_NtpMessage);
+ }
+}
+
+/* ================================================== */
+
+void
+NIO_Finalise(void)
+{
+ if (server_sock_fd4 != client_sock_fd4)
+ close_socket(client_sock_fd4);
+ close_socket(server_sock_fd4);
+ server_sock_fd4 = client_sock_fd4 = INVALID_SOCK_FD;
+
+ 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;
+
+ close_socket(ptp_sock_fd4);
+ close_socket(ptp_sock_fd6);
+ ptp_sock_fd4 = ptp_sock_fd6 = INVALID_SOCK_FD;
+ Free(ptp_message);
+
+#ifdef HAVE_LINUX_TIMESTAMPING
+ NIO_Linux_Finalise();
+#endif
+
+ initialised = 0;
+}
+
+/* ================================================== */
+
+int
+NIO_IsHwTsEnabled(void)
+{
+#ifdef HAVE_LINUX_TIMESTAMPING
+ return NIO_Linux_IsHwTsEnabled();
+#else
+ return 0;
+#endif
+}
+
+/* ================================================== */
+
+int
+NIO_OpenClientSocket(NTP_Remote_Address *remote_addr)
+{
+ switch (remote_addr->ip_addr.family) {
+ case IPADDR_INET4:
+ if (ptp_port > 0 && remote_addr->port == ptp_port)
+ return ptp_sock_fd4;
+ if (separate_client_sockets)
+ return open_separate_client_socket(remote_addr);
+ return client_sock_fd4;
+ case IPADDR_INET6:
+ if (ptp_port > 0 && remote_addr->port == ptp_port)
+ return ptp_sock_fd6;
+ if (separate_client_sockets)
+ return open_separate_client_socket(remote_addr);
+ return client_sock_fd6;
+ default:
+ return INVALID_SOCK_FD;
+ }
+}
+
+/* ================================================== */
+
+int
+NIO_OpenServerSocket(NTP_Remote_Address *remote_addr)
+{
+ switch (remote_addr->ip_addr.family) {
+ case IPADDR_INET4:
+ if (ptp_port > 0 && remote_addr->port == ptp_port)
+ return ptp_sock_fd4;
+ if (permanent_server_sockets)
+ return server_sock_fd4;
+ if (server_sock_fd4 == INVALID_SOCK_FD)
+ 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;
+ case IPADDR_INET6:
+ if (ptp_port > 0 && remote_addr->port == ptp_port)
+ return ptp_sock_fd6;
+ if (permanent_server_sockets)
+ return server_sock_fd6;
+ if (server_sock_fd6 == INVALID_SOCK_FD)
+ 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;
+ default:
+ return INVALID_SOCK_FD;
+ }
+}
+
+/* ================================================== */
+
+static int
+is_ptp_socket(int sock_fd)
+{
+ return ptp_port > 0 && sock_fd != INVALID_SOCK_FD &&
+ (sock_fd == ptp_sock_fd4 || sock_fd == ptp_sock_fd6);
+}
+
+/* ================================================== */
+
+void
+NIO_CloseClientSocket(int sock_fd)
+{
+ if (is_ptp_socket(sock_fd))
+ return;
+
+ if (separate_client_sockets)
+ close_socket(sock_fd);
+}
+
+/* ================================================== */
+
+void
+NIO_CloseServerSocket(int sock_fd)
+{
+ if (permanent_server_sockets || sock_fd == INVALID_SOCK_FD || is_ptp_socket(sock_fd))
+ return;
+
+ if (sock_fd == server_sock_fd4) {
+ if (--server_sock_ref4 <= 0) {
+ close_socket(server_sock_fd4);
+ server_sock_fd4 = INVALID_SOCK_FD;
+ }
+ } else if (sock_fd == server_sock_fd6) {
+ if (--server_sock_ref6 <= 0) {
+ close_socket(server_sock_fd6);
+ server_sock_fd6 = INVALID_SOCK_FD;
+ }
+ } else {
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+int
+NIO_IsServerSocket(int sock_fd)
+{
+ return sock_fd != INVALID_SOCK_FD &&
+ (sock_fd == server_sock_fd4 || sock_fd == server_sock_fd6 || is_ptp_socket(sock_fd));
+}
+
+/* ================================================== */
+
+int
+NIO_IsServerSocketOpen(void)
+{
+ return server_sock_fd4 != INVALID_SOCK_FD || server_sock_fd6 != INVALID_SOCK_FD ||
+ ptp_sock_fd4 != INVALID_SOCK_FD || ptp_sock_fd6 != INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+int
+NIO_IsServerConnectable(NTP_Remote_Address *remote_addr)
+{
+ int sock_fd;
+
+ sock_fd = open_separate_client_socket(remote_addr);
+ if (sock_fd == INVALID_SOCK_FD)
+ return 0;
+
+ close_socket(sock_fd);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+process_message(SCK_Message *message, int sock_fd, int event)
+{
+ NTP_Local_Address local_addr;
+ NTP_Local_Timestamp local_ts;
+ struct timespec sched_ts;
+
+ SCH_GetLastEventTime(&local_ts.ts, &local_ts.err, NULL);
+ local_ts.source = NTP_TS_DAEMON;
+ local_ts.rx_duration = 0.0;
+ local_ts.net_correction = 0.0;
+
+ sched_ts = local_ts.ts;
+
+ if (message->addr_type != SCK_ADDR_IP) {
+ DEBUG_LOG("Unexpected address type");
+ return;
+ }
+
+ local_addr.ip_addr = message->local_addr.ip;
+ local_addr.if_index = message->if_index;;
+ local_addr.sock_fd = sock_fd;
+
+#ifdef HAVE_LINUX_TIMESTAMPING
+ 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
+
+ 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);
+
+ if (!NIO_UnwrapMessage(message, sock_fd, &local_ts.net_correction))
+ return;
+
+ /* Just ignore the packet if it's not of a recognized length */
+ if (message->length < NTP_HEADER_LENGTH || message->length > sizeof (NTP_Packet)) {
+ DEBUG_LOG("Unexpected length");
+ return;
+ }
+
+ NSR_ProcessRx(&message->remote_addr.ip, &local_addr, &local_ts, message->data, message->length);
+}
+
+/* ================================================== */
+
+static void
+read_from_socket(int sock_fd, int event, void *anything)
+{
+ SCK_Message *messages;
+ int i, received, flags = 0;
+
+ if (event == SCH_FILE_EXCEPTION) {
+#ifdef HAVE_LINUX_TIMESTAMPING
+ flags |= SCK_FLAG_MSG_ERRQUEUE;
+#else
+ assert(0);
+#endif
+ }
+
+ messages = SCK_ReceiveMessages(sock_fd, flags, &received);
+ if (!messages)
+ return;
+
+ for (i = 0; i < received; i++)
+ process_message(&messages[i], sock_fd, event);
+}
+
+/* ================================================== */
+
+int
+NIO_UnwrapMessage(SCK_Message *message, int sock_fd, double *net_correction)
+{
+ double ptp_correction;
+ PTP_NtpMessage *msg;
+
+ if (!is_ptp_socket(sock_fd))
+ return 1;
+
+ if (message->length <= PTP_NTP_PREFIX_LENGTH) {
+ DEBUG_LOG("Unexpected length");
+ return 0;
+ }
+
+ msg = message->data;
+
+ if (msg->header.type != PTP_TYPE_DELAY_REQ || msg->header.version != PTP_VERSION ||
+ ntohs(msg->header.length) != message->length ||
+ msg->header.domain != PTP_DOMAIN_NTP ||
+ ntohs(msg->header.flags) != PTP_FLAG_UNICAST ||
+ ntohs(msg->tlv_header.type) != PTP_TLV_NTP ||
+ ntohs(msg->tlv_header.length) != message->length - PTP_NTP_PREFIX_LENGTH) {
+ DEBUG_LOG("Unexpected PTP message");
+ return 0;
+ }
+
+ message->data = (char *)message->data + PTP_NTP_PREFIX_LENGTH;
+ message->length -= PTP_NTP_PREFIX_LENGTH;
+
+ ptp_correction = UTI_Integer64NetworkToHost(*(Integer64 *)msg->header.correction) /
+ ((1 << 16) * 1.0e9);
+
+ /* Use the correction only if the RX duration is known (i.e. HW timestamp) */
+ if (*net_correction > 0.0)
+ *net_correction += ptp_correction;
+
+ DEBUG_LOG("Unwrapped PTP->NTP len=%d corr=%.9f", message->length, ptp_correction);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+wrap_message(SCK_Message *message, int sock_fd)
+{
+ static uint16_t sequence_id = 0;
+
+ assert(PTP_NTP_PREFIX_LENGTH == 48);
+
+ if (!is_ptp_socket(sock_fd))
+ return 1;
+
+ if (!ptp_message)
+ return 0;
+
+ if (message->length < NTP_HEADER_LENGTH ||
+ message->length + PTP_NTP_PREFIX_LENGTH > sizeof (*ptp_message)) {
+ DEBUG_LOG("Unexpected length");
+ return 0;
+ }
+
+ memset(ptp_message, 0, PTP_NTP_PREFIX_LENGTH);
+ ptp_message->header.type = PTP_TYPE_DELAY_REQ;
+ ptp_message->header.version = PTP_VERSION;
+ ptp_message->header.length = htons(PTP_NTP_PREFIX_LENGTH + message->length);
+ ptp_message->header.domain = PTP_DOMAIN_NTP;
+ ptp_message->header.flags = htons(PTP_FLAG_UNICAST);
+ ptp_message->header.sequence_id = htons(sequence_id++);
+ ptp_message->tlv_header.type = htons(PTP_TLV_NTP);
+ ptp_message->tlv_header.length = htons(message->length);
+ memcpy((char *)ptp_message + PTP_NTP_PREFIX_LENGTH, message->data, message->length);
+
+ message->data = ptp_message;
+ message->length += PTP_NTP_PREFIX_LENGTH;
+
+ DEBUG_LOG("Wrapped NTP->PTP len=%d", message->length - PTP_NTP_PREFIX_LENGTH);
+
+ return 1;
+}
+
+/* ================================================== */
+/* Send a packet to remote address from local address */
+
+int
+NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr,
+ NTP_Local_Address *local_addr, int length, int process_tx)
+{
+ SCK_Message message;
+
+ assert(initialised);
+
+ if (local_addr->sock_fd == INVALID_SOCK_FD) {
+ DEBUG_LOG("No socket to send to %s", UTI_IPSockAddrToString(remote_addr));
+ return 0;
+ }
+
+ SCK_InitMessage(&message, SCK_ADDR_IP);
+
+ message.data = packet;
+ message.length = length;
+
+ if (!wrap_message(&message, local_addr->sock_fd))
+ return 0;
+
+ /* Specify remote address if the socket is not connected */
+ if (NIO_IsServerSocket(local_addr->sock_fd) || !separate_client_sockets) {
+ message.remote_addr.ip.ip_addr = remote_addr->ip_addr;
+ message.remote_addr.ip.port = remote_addr->port;
+ }
+
+ message.local_addr.ip = local_addr->ip_addr;
+
+ /* Don't require responses to non-link-local addresses to use the same
+ interface */
+ message.if_index = SCK_IsLinkLocalIPAddress(&message.remote_addr.ip.ip_addr) ?
+ local_addr->if_index : INVALID_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 &&
+ (bound_server_sock_fd4 || !NIO_IsServerSocket(local_addr->sock_fd)))
+ message.local_addr.ip.family = IPADDR_UNSPEC;
+#endif
+
+#ifdef HAVE_LINUX_TIMESTAMPING
+ if (process_tx)
+ NIO_Linux_RequestTxTimestamp(&message, local_addr->sock_fd);
+#endif
+
+ if (!SCK_SendMessage(local_addr->sock_fd, &message, 0))
+ return 0;
+
+ return 1;
+}
diff --git a/ntp_io.h b/ntp_io.h
new file mode 100644
index 0000000..30f4992
--- /dev/null
+++ b/ntp_io.h
@@ -0,0 +1,73 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 the NTP socket I/O bits.
+
+ */
+
+#ifndef GOT_NTP_IO_H
+#define GOT_NTP_IO_H
+
+#include "ntp.h"
+#include "addressing.h"
+#include "socket.h"
+
+/* Function to initialise the module. */
+extern void NIO_Initialise(void);
+
+/* Function to finalise the module */
+extern void NIO_Finalise(void);
+
+/* Function to check if HW timestamping is enabled on any interface */
+extern int NIO_IsHwTsEnabled(void);
+
+/* Function to obtain a socket for sending client packets */
+extern int NIO_OpenClientSocket(NTP_Remote_Address *remote_addr);
+
+/* Function to obtain a socket for sending server/peer packets */
+extern int NIO_OpenServerSocket(NTP_Remote_Address *remote_addr);
+
+/* Function to close a socket returned by NIO_OpenClientSocket() */
+extern void NIO_CloseClientSocket(int sock_fd);
+
+/* Function to close a socket returned by NIO_OpenServerSocket() */
+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);
+
+/* Function to unwrap an NTP message from non-native transport (e.g. PTP) */
+extern int NIO_UnwrapMessage(SCK_Message *message, int sock_fd, double *net_correction);
+
+/* Function to transmit a packet */
+extern int NIO_SendPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr,
+ NTP_Local_Address *local_addr, int length, int process_tx);
+
+#endif /* GOT_NTP_IO_H */
diff --git a/ntp_io_linux.c b/ntp_io_linux.c
new file mode 100644
index 0000000..3b6874e
--- /dev/null
+++ b/ntp_io_linux.c
@@ -0,0 +1,815 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016-2019, 2021-2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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 NTP I/O specific to Linux
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <ifaddrs.h>
+#include <linux/ethtool.h>
+#include <linux/net_tstamp.h>
+#include <linux/sockios.h>
+#include <net/if.h>
+
+#include "array.h"
+#include "conf.h"
+#include "hwclock.h"
+#include "local.h"
+#include "logging.h"
+#include "memory.h"
+#include "ntp_core.h"
+#include "ntp_io.h"
+#include "ntp_io_linux.h"
+#include "ntp_sources.h"
+#include "sched.h"
+#include "socket.h"
+#include "sys_linux.h"
+#include "util.h"
+
+struct Interface {
+ char name[IF_NAMESIZE];
+ int if_index;
+ int phc_fd;
+ int phc_mode;
+ int phc_nocrossts;
+ /* Link speed in mbit/s */
+ int link_speed;
+ /* Start of UDP data at layer 2 for IPv4 and IPv6 */
+ int l2_udp4_ntp_start;
+ int l2_udp6_ntp_start;
+ /* Compensation of errors in TX and RX timestamping */
+ double tx_comp;
+ double rx_comp;
+ HCL_Instance clock;
+ int maxpoll;
+ SCH_TimeoutID poll_timeout_id;
+};
+
+/* Number of PHC readings per HW clock sample */
+#define PHC_READINGS 25
+
+/* Minimum and maximum interval between PHC readings */
+#define MIN_PHC_POLL -6
+#define MAX_PHC_POLL 20
+
+/* Maximum acceptable offset between SW/HW and daemon timestamp */
+#define MAX_TS_DELAY 1.0
+
+/* Array of Interfaces */
+static ARR_Instance interfaces;
+
+/* RX/TX and TX-specific timestamping socket options */
+static int ts_flags;
+static int ts_tx_flags;
+
+/* Flag indicating the socket options can't be changed in control messages */
+static int permanent_ts_options;
+
+/* Unbound socket keeping the kernel RX timestamping permanently enabled
+ in order to avoid a race condition between receiving a server response
+ and the kernel actually starting to timestamp received packets after
+ enabling the timestamping and sending a request */
+static int dummy_rxts_socket;
+
+#define INVALID_SOCK_FD -3
+
+/* ================================================== */
+
+static void poll_phc(struct Interface *iface, struct timespec *now);
+
+/* ================================================== */
+
+static int
+add_interface(CNF_HwTsInterface *conf_iface)
+{
+ int sock_fd, if_index, minpoll, phc_fd, req_hwts_flags, rx_filter;
+ struct ethtool_ts_info ts_info;
+ struct hwtstamp_config ts_config;
+ struct ifreq req;
+ unsigned int i;
+ struct Interface *iface;
+
+ /* Check if the interface was not already added */
+ for (i = 0; i < ARR_GetSize(interfaces); i++) {
+ if (!strcmp(conf_iface->name, ((struct Interface *)ARR_GetElement(interfaces, i))->name))
+ return 1;
+ }
+
+ sock_fd = SCK_OpenUdpSocket(NULL, NULL, NULL, 0);
+ if (sock_fd < 0)
+ return 0;
+
+ memset(&req, 0, sizeof (req));
+ memset(&ts_info, 0, sizeof (ts_info));
+
+ if (snprintf(req.ifr_name, sizeof (req.ifr_name), "%s", conf_iface->name) >=
+ sizeof (req.ifr_name)) {
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+
+ if (ioctl(sock_fd, SIOCGIFINDEX, &req)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "SIOCGIFINDEX", strerror(errno));
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+
+ if_index = req.ifr_ifindex;
+
+ ts_info.cmd = ETHTOOL_GET_TS_INFO;
+ req.ifr_data = (char *)&ts_info;
+
+ if (ioctl(sock_fd, SIOCETHTOOL, &req)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "SIOCETHTOOL", strerror(errno));
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+
+ req_hwts_flags = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_TX_HARDWARE |
+ 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);
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+
+ if (ts_info.phc_index < 0) {
+ DEBUG_LOG("PHC missing on %s", req.ifr_name);
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+
+ switch (conf_iface->rxfilter) {
+ case CNF_HWTS_RXFILTER_ANY:
+#ifdef HAVE_LINUX_TIMESTAMPING_RXFILTER_NTP
+ if (ts_info.rx_filters & (1 << HWTSTAMP_FILTER_NTP_ALL))
+ rx_filter = HWTSTAMP_FILTER_NTP_ALL;
+ else
+#endif
+ if (ts_info.rx_filters & (1 << HWTSTAMP_FILTER_ALL))
+ rx_filter = HWTSTAMP_FILTER_ALL;
+ else
+ rx_filter = HWTSTAMP_FILTER_NONE;
+ break;
+ case CNF_HWTS_RXFILTER_NONE:
+ rx_filter = HWTSTAMP_FILTER_NONE;
+ break;
+#ifdef HAVE_LINUX_TIMESTAMPING_RXFILTER_NTP
+ case CNF_HWTS_RXFILTER_NTP:
+ rx_filter = HWTSTAMP_FILTER_NTP_ALL;
+ break;
+#endif
+ case CNF_HWTS_RXFILTER_PTP:
+ if (ts_info.rx_filters & (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT))
+ rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
+ else if (ts_info.rx_filters & (1 << HWTSTAMP_FILTER_PTP_V2_EVENT))
+ rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+ else
+ rx_filter = HWTSTAMP_FILTER_NONE;
+ break;
+ default:
+ rx_filter = HWTSTAMP_FILTER_ALL;
+ break;
+ }
+
+ ts_config.flags = 0;
+ ts_config.tx_type = HWTSTAMP_TX_ON;
+ ts_config.rx_filter = rx_filter;
+ req.ifr_data = (char *)&ts_config;
+
+ if (ioctl(sock_fd, SIOCSHWTSTAMP, &req)) {
+ LOG(errno == EPERM ? LOGS_ERR : LOGS_DEBUG,
+ "ioctl(%s) failed : %s", "SIOCSHWTSTAMP", strerror(errno));
+
+ /* Check the current timestamping configuration in case this interface
+ allows only reading of the configuration and it was already configured
+ as requested */
+ req.ifr_data = (char *)&ts_config;
+#ifdef SIOCGHWTSTAMP
+ if (ioctl(sock_fd, SIOCGHWTSTAMP, &req) ||
+ ts_config.tx_type != HWTSTAMP_TX_ON || ts_config.rx_filter != rx_filter)
+#endif
+ {
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+ }
+
+ SCK_CloseSocket(sock_fd);
+
+ phc_fd = SYS_Linux_OpenPHC(NULL, ts_info.phc_index);
+ if (phc_fd < 0)
+ return 0;
+
+ iface = ARR_GetNewElement(interfaces);
+
+ snprintf(iface->name, sizeof (iface->name), "%s", conf_iface->name);
+ iface->if_index = if_index;
+ iface->phc_fd = phc_fd;
+ iface->phc_mode = 0;
+ iface->phc_nocrossts = conf_iface->nocrossts;
+
+ /* Start with 1 gbit and no VLANs or IPv4/IPv6 options */
+ iface->link_speed = 1000;
+ iface->l2_udp4_ntp_start = 42;
+ iface->l2_udp6_ntp_start = 62;
+
+ iface->tx_comp = conf_iface->tx_comp;
+ iface->rx_comp = conf_iface->rx_comp;
+
+ minpoll = CLAMP(MIN_PHC_POLL, conf_iface->minpoll, MAX_PHC_POLL);
+ iface->clock = HCL_CreateInstance(conf_iface->min_samples, conf_iface->max_samples,
+ UTI_Log2ToDouble(minpoll), conf_iface->precision);
+
+ iface->maxpoll = CLAMP(minpoll, conf_iface->maxpoll, MAX_PHC_POLL);
+
+ /* Do not schedule the first poll timeout here! The argument (interface) can
+ move until all interfaces are added. Wait for the first HW timestamp. */
+ iface->poll_timeout_id = 0;
+
+ LOG(LOGS_INFO, "Enabled HW timestamping %son %s",
+ ts_config.rx_filter == HWTSTAMP_FILTER_NONE ? "(TX only) " : "", iface->name);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+add_all_interfaces(CNF_HwTsInterface *conf_iface_all)
+{
+ CNF_HwTsInterface conf_iface;
+ struct ifaddrs *ifaddr, *ifa;
+ int r;
+
+ conf_iface = *conf_iface_all;
+
+ if (getifaddrs(&ifaddr)) {
+ DEBUG_LOG("getifaddrs() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ for (r = 0, ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
+ conf_iface.name = ifa->ifa_name;
+ if (add_interface(&conf_iface))
+ r = 1;
+ }
+
+ freeifaddrs(ifaddr);
+
+ /* Return success if at least one interface was added */
+ return r;
+}
+
+/* ================================================== */
+
+static void
+update_interface_speed(struct Interface *iface)
+{
+ struct ethtool_cmd cmd;
+ struct ifreq req;
+ int sock_fd, link_speed;
+
+ sock_fd = SCK_OpenUdpSocket(NULL, NULL, NULL, 0);
+ if (sock_fd < 0)
+ return;
+
+ memset(&req, 0, sizeof (req));
+ memset(&cmd, 0, sizeof (cmd));
+
+ snprintf(req.ifr_name, sizeof (req.ifr_name), "%s", iface->name);
+ cmd.cmd = ETHTOOL_GSET;
+ req.ifr_data = (char *)&cmd;
+
+ if (ioctl(sock_fd, SIOCETHTOOL, &req)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "SIOCETHTOOL", strerror(errno));
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+
+ SCK_CloseSocket(sock_fd);
+
+ link_speed = ethtool_cmd_speed(&cmd);
+
+ if (iface->link_speed != link_speed) {
+ iface->link_speed = link_speed;
+ DEBUG_LOG("Updated speed of %s to %d Mb/s", iface->name, link_speed);
+ }
+}
+
+/* ================================================== */
+
+#if defined(HAVE_LINUX_TIMESTAMPING_OPT_PKTINFO) || defined(HAVE_LINUX_TIMESTAMPING_OPT_TX_SWHW)
+static int
+check_timestamping_option(int option)
+{
+ int sock_fd;
+
+ sock_fd = SCK_OpenUdpSocket(NULL, NULL, NULL, 0);
+ if (sock_fd < 0)
+ return 0;
+
+ if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMPING, option)) {
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+
+ SCK_CloseSocket(sock_fd);
+ return 1;
+}
+#endif
+
+/* ================================================== */
+
+static int
+open_dummy_socket(void)
+{
+ int sock_fd, events = 0;
+
+ sock_fd = SCK_OpenUdpSocket(NULL, NULL, NULL, 0);
+ if (sock_fd < 0)
+ return INVALID_SOCK_FD;
+
+ if (!NIO_Linux_SetTimestampSocketOptions(sock_fd, 1, &events)) {
+ SCK_CloseSocket(sock_fd);
+ return INVALID_SOCK_FD;
+ }
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+void
+NIO_Linux_Initialise(void)
+{
+ CNF_HwTsInterface *conf_iface;
+ unsigned int i;
+ int hwts;
+
+ interfaces = ARR_CreateInstance(sizeof (struct Interface));
+
+ /* Enable HW timestamping on specified interfaces. If "*" was specified, try
+ all interfaces. If no interface was specified, enable SW timestamping. */
+
+ for (i = hwts = 0; CNF_GetHwTsInterface(i, &conf_iface); i++) {
+ if (!strcmp("*", conf_iface->name))
+ continue;
+ if (!add_interface(conf_iface))
+ LOG_FATAL("Could not enable HW timestamping on %s", conf_iface->name);
+ hwts = 1;
+ }
+
+ for (i = 0; CNF_GetHwTsInterface(i, &conf_iface); i++) {
+ if (strcmp("*", conf_iface->name))
+ continue;
+ if (add_all_interfaces(conf_iface))
+ hwts = 1;
+ break;
+ }
+
+ ts_flags = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE;
+ ts_tx_flags = SOF_TIMESTAMPING_TX_SOFTWARE;
+
+ if (hwts) {
+ ts_flags |= SOF_TIMESTAMPING_RAW_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE;
+ ts_tx_flags |= SOF_TIMESTAMPING_TX_HARDWARE;
+#ifdef HAVE_LINUX_TIMESTAMPING_OPT_PKTINFO
+ if (check_timestamping_option(SOF_TIMESTAMPING_OPT_PKTINFO))
+ ts_flags |= SOF_TIMESTAMPING_OPT_PKTINFO;
+#endif
+#ifdef HAVE_LINUX_TIMESTAMPING_OPT_TX_SWHW
+ if (check_timestamping_option(SOF_TIMESTAMPING_OPT_TX_SWHW))
+ ts_flags |= SOF_TIMESTAMPING_OPT_TX_SWHW;
+#endif
+ }
+
+ /* Enable IP_PKTINFO in messages looped back to the error queue */
+ ts_flags |= SOF_TIMESTAMPING_OPT_CMSG;
+
+ /* Kernels before 4.7 ignore timestamping flags set in control messages */
+ permanent_ts_options = !SYS_Linux_CheckKernelVersion(4, 7);
+
+ dummy_rxts_socket = INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+void
+NIO_Linux_Finalise(void)
+{
+ struct Interface *iface;
+ unsigned int i;
+
+ if (dummy_rxts_socket != INVALID_SOCK_FD)
+ SCK_CloseSocket(dummy_rxts_socket);
+
+ for (i = 0; i < ARR_GetSize(interfaces); i++) {
+ iface = ARR_GetElement(interfaces, i);
+ SCH_RemoveTimeout(iface->poll_timeout_id);
+ HCL_DestroyInstance(iface->clock);
+ close(iface->phc_fd);
+ }
+
+ ARR_DestroyInstance(interfaces);
+}
+
+/* ================================================== */
+
+int
+NIO_Linux_IsHwTsEnabled(void)
+{
+ return ARR_GetSize(interfaces) > 0;
+}
+
+/* ================================================== */
+
+int
+NIO_Linux_SetTimestampSocketOptions(int sock_fd, int client_only, int *events)
+{
+ int val, flags;
+
+ if (!ts_flags)
+ return 0;
+
+ /* Enable SCM_TIMESTAMPING control messages and the socket's error queue in
+ order to receive our transmitted packets with more accurate timestamps */
+
+ val = 1;
+ flags = ts_flags;
+
+ if (client_only || permanent_ts_options)
+ flags |= ts_tx_flags;
+
+ if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_SELECT_ERR_QUEUE, val)) {
+ ts_flags = 0;
+ return 0;
+ }
+
+ if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMPING, flags)) {
+ ts_flags = 0;
+ return 0;
+ }
+
+ *events |= SCH_FILE_EXCEPTION;
+ return 1;
+}
+
+/* ================================================== */
+
+static struct Interface *
+get_interface(int if_index)
+{
+ struct Interface *iface;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(interfaces); i++) {
+ iface = ARR_GetElement(interfaces, i);
+ if (iface->if_index != if_index)
+ continue;
+
+ return iface;
+ }
+
+ return NULL;
+}
+
+/* ================================================== */
+
+static void
+poll_timeout(void *arg)
+{
+ struct Interface *iface = arg;
+ struct timespec now;
+
+ iface->poll_timeout_id = 0;
+
+ SCH_GetLastEventTime(&now, NULL, NULL);
+ poll_phc(iface, &now);
+}
+
+/* ================================================== */
+
+static void
+poll_phc(struct Interface *iface, struct timespec *now)
+{
+ struct timespec sample_phc_ts, sample_sys_ts, sample_local_ts;
+ struct timespec phc_readings[PHC_READINGS][3];
+ double phc_err, local_err, interval;
+ int n_readings;
+
+ if (!HCL_NeedsNewSample(iface->clock, now))
+ return;
+
+ DEBUG_LOG("Polling PHC on %s%s",
+ iface->name, iface->poll_timeout_id != 0 ? " before timeout" : "");
+
+ n_readings = SYS_Linux_GetPHCReadings(iface->phc_fd, iface->phc_nocrossts,
+ &iface->phc_mode, PHC_READINGS, phc_readings);
+
+ /* Add timeout for the next poll in case no HW timestamp will be captured
+ between the minpoll and maxpoll. Separate reading of different PHCs to
+ avoid long intervals between handling I/O events. */
+ SCH_RemoveTimeout(iface->poll_timeout_id);
+ interval = UTI_Log2ToDouble(iface->maxpoll);
+ iface->poll_timeout_id = SCH_AddTimeoutInClass(interval, interval /
+ ARR_GetSize(interfaces) / 4, 0.1,
+ SCH_PhcPollClass, poll_timeout, iface);
+
+ if (n_readings <= 0)
+ return;
+
+ if (!HCL_ProcessReadings(iface->clock, n_readings, phc_readings,
+ &sample_phc_ts, &sample_sys_ts, &phc_err))
+ return;
+
+ LCL_CookTime(&sample_sys_ts, &sample_local_ts, &local_err);
+ HCL_AccumulateSample(iface->clock, &sample_phc_ts, &sample_local_ts, phc_err + local_err);
+
+ update_interface_speed(iface);
+}
+
+/* ================================================== */
+
+static void
+process_hw_timestamp(struct Interface *iface, struct timespec *hw_ts,
+ NTP_Local_Timestamp *local_ts, int rx_ntp_length, int family,
+ int l2_length)
+{
+ double rx_correction = 0.0, ts_delay, local_err;
+ struct timespec ts;
+
+ poll_phc(iface, &local_ts->ts);
+
+ /* We need to transpose RX timestamps as hardware timestamps are normally
+ preamble timestamps and RX timestamps in NTP are supposed to be trailer
+ timestamps. If we don't know the length of the packet at layer 2, we
+ make an assumption that UDP data start at the same position as in the
+ last transmitted packet which had a HW TX timestamp. */
+ if (rx_ntp_length && iface->link_speed) {
+ if (!l2_length)
+ l2_length = (family == IPADDR_INET4 ? iface->l2_udp4_ntp_start :
+ iface->l2_udp6_ntp_start) + rx_ntp_length;
+
+ /* Include the frame check sequence (FCS) */
+ l2_length += 4;
+
+ rx_correction = l2_length / (1.0e6 / 8 * iface->link_speed);
+
+ UTI_AddDoubleToTimespec(hw_ts, rx_correction, hw_ts);
+ }
+
+ if (!HCL_CookTime(iface->clock, hw_ts, &ts, &local_err))
+ return;
+
+ if (!rx_ntp_length && iface->tx_comp)
+ UTI_AddDoubleToTimespec(&ts, iface->tx_comp, &ts);
+ else if (rx_ntp_length && iface->rx_comp)
+ UTI_AddDoubleToTimespec(&ts, -iface->rx_comp, &ts);
+
+ ts_delay = UTI_DiffTimespecsToDouble(&local_ts->ts, &ts);
+
+ if (fabs(ts_delay) > MAX_TS_DELAY) {
+ DEBUG_LOG("Unacceptable timestamp delay %.9f", ts_delay);
+ return;
+ }
+
+ local_ts->ts = ts;
+ local_ts->err = local_err;
+ local_ts->source = NTP_TS_HARDWARE;
+ local_ts->rx_duration = rx_correction;
+ /* Network correction needs to include the RX duration to avoid
+ asymmetric correction with asymmetric link speeds */
+ local_ts->net_correction = rx_correction;
+}
+
+/* ================================================== */
+
+static void
+process_sw_timestamp(struct timespec *sw_ts, NTP_Local_Timestamp *local_ts)
+{
+ double ts_delay, local_err;
+ struct timespec ts;
+
+ LCL_CookTime(sw_ts, &ts, &local_err);
+
+ ts_delay = UTI_DiffTimespecsToDouble(&local_ts->ts, &ts);
+
+ if (fabs(ts_delay) > MAX_TS_DELAY) {
+ DEBUG_LOG("Unacceptable timestamp delay %.9f", ts_delay);
+ return;
+ }
+
+ local_ts->ts = ts;
+ local_ts->err = local_err;
+ local_ts->source = NTP_TS_KERNEL;
+}
+
+/* ================================================== */
+/* Extract UDP data from a layer 2 message. Supported is Ethernet
+ with optional VLAN tags. */
+
+static int
+extract_udp_data(unsigned char *msg, NTP_Remote_Address *remote_addr, int len)
+{
+ unsigned char *msg_start = msg;
+
+ remote_addr->ip_addr.family = IPADDR_UNSPEC;
+ remote_addr->port = 0;
+
+ /* Skip MACs */
+ if (len < 12)
+ return 0;
+ len -= 12, msg += 12;
+
+ /* Skip VLAN tag(s) if present */
+ while (len >= 4 && msg[0] == 0x81 && msg[1] == 0x00)
+ len -= 4, msg += 4;
+
+ /* Skip IPv4 or IPv6 ethertype */
+ if (len < 2 || !((msg[0] == 0x08 && msg[1] == 0x00) ||
+ (msg[0] == 0x86 && msg[1] == 0xdd)))
+ return 0;
+ len -= 2, msg += 2;
+
+ /* 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, 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(&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 */
+ while (next_header != 17) {
+ switch (next_header) {
+ case 44: /* Fragment Header */
+ /* Process only the first fragment */
+ if (ntohs(*(uint16_t *)(msg + 2)) >> 3 != 0)
+ return 0;
+ eh_len = 8;
+ break;
+ case 0: /* Hop-by-Hop Options */
+ case 43: /* Routing Header */
+ case 60: /* Destination Options */
+ case 135: /* Mobility Header */
+ eh_len = 8 * (msg[1] + 1);
+ break;
+ case 51: /* Authentication Header */
+ eh_len = 4 * (msg[1] + 2);
+ break;
+ default:
+ return 0;
+ }
+
+ if (eh_len < 8 || len < eh_len + 8)
+ return 0;
+
+ next_header = msg[0];
+ len -= eh_len, msg += eh_len;
+ }
+
+ remote_addr->port = ntohs(*(uint16_t *)(msg + 2));
+ remote_addr->ip_addr.family = IPADDR_INET6;
+ len -= 8, msg += 8;
+#endif
+ } else {
+ return 0;
+ }
+
+ /* Move the message to fix alignment of its fields */
+ if (len > 0)
+ memmove(msg_start, msg, len);
+
+ return len;
+}
+
+/* ================================================== */
+
+int
+NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *local_ts, int event)
+{
+ struct Interface *iface;
+ int is_tx, ts_if_index, l2_length;
+ double c = 0.0;
+
+ is_tx = event == SCH_FILE_EXCEPTION;
+ iface = NULL;
+
+ 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;
+
+ 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 (local_ts->source == NTP_TS_DAEMON && !UTI_IsZeroTimespec(&message->timestamp.kernel) &&
+ (!is_tx || UTI_IsZeroTimespec(&message->timestamp.hw))) {
+ process_sw_timestamp(&message->timestamp.kernel, local_ts);
+ }
+
+ /* If the kernel is slow with enabling RX timestamping, open a dummy
+ socket to keep the kernel RX timestamping permanently enabled */
+ if (!is_tx && local_ts->source == NTP_TS_DAEMON && ts_flags) {
+ DEBUG_LOG("Missing kernel RX timestamp");
+ if (dummy_rxts_socket == INVALID_SOCK_FD)
+ dummy_rxts_socket = open_dummy_socket();
+ }
+
+ /* Return the message if it's not received from the error queue */
+ if (!is_tx)
+ return 0;
+
+ /* 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 = message->length;
+ message->length = extract_udp_data(message->data, &message->remote_addr.ip, message->length);
+
+ DEBUG_LOG("Extracted message for %s fd=%d len=%d",
+ 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 && 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 */
+ if (local_ts->source == NTP_TS_DAEMON) {
+ DEBUG_LOG("Missing TX timestamp");
+ return 1;
+ }
+
+ if (!NIO_UnwrapMessage(message, local_addr->sock_fd, &c))
+ return 1;
+
+ if (message->length < NTP_HEADER_LENGTH || message->length > sizeof (NTP_Packet))
+ return 1;
+
+ NSR_ProcessTx(&message->remote_addr.ip, local_addr, local_ts, message->data, message->length);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+NIO_Linux_RequestTxTimestamp(SCK_Message *message, int sock_fd)
+{
+ if (!ts_flags)
+ return;
+
+ /* Check if TX timestamping is disabled on this socket */
+ if (permanent_ts_options || !NIO_IsServerSocket(sock_fd))
+ return;
+
+ message->timestamp.tx_flags = ts_tx_flags;
+}
diff --git a/ntp_io_linux.h b/ntp_io_linux.h
new file mode 100644
index 0000000..7978889
--- /dev/null
+++ b/ntp_io_linux.h
@@ -0,0 +1,45 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * 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 the Linux-specific NTP socket I/O bits.
+ */
+
+#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);
+
+extern int NIO_Linux_IsHwTsEnabled(void);
+
+extern int NIO_Linux_SetTimestampSocketOptions(int sock_fd, int client_only, int *events);
+
+extern int NIO_Linux_ProcessMessage(SCK_Message *message, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *local_ts, int event);
+
+extern void NIO_Linux_RequestTxTimestamp(SCK_Message *message, int sock_fd);
+
+#endif
diff --git a/ntp_signd.c b/ntp_signd.c
new file mode 100644
index 0000000..77b3249
--- /dev/null
+++ b/ntp_signd.c
@@ -0,0 +1,341 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * 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 MS-SNTP authentication in Samba (ntp_signd)
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "conf.h"
+#include "logging.h"
+#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 */
+
+#define SIGND_VERSION 0
+
+typedef enum {
+ SIGN_TO_CLIENT = 0,
+ ASK_SERVER_TO_SIGN = 1,
+ CHECK_SERVER_SIGNATURE = 2,
+ SIGNING_SUCCESS = 3,
+ SIGNING_FAILURE = 4,
+} SigndOp;
+
+typedef struct {
+ uint32_t length;
+ uint32_t version;
+ uint32_t op;
+ uint16_t packet_id;
+ uint16_t _pad;
+ uint32_t key_id;
+ NTP_Packet packet_to_sign;
+} SigndRequest;
+
+typedef struct {
+ uint32_t length;
+ uint32_t version;
+ uint32_t op;
+ uint32_t packet_id;
+ NTP_Packet signed_packet;
+} SigndResponse;
+
+typedef struct {
+ NTP_Remote_Address remote_addr;
+ NTP_Local_Address local_addr;
+
+ int sent;
+ int received;
+ int request_length;
+ struct timespec request_ts;
+ SigndRequest request;
+ SigndResponse response;
+} SignInstance;
+
+/* As the communication with ntp_signd is asynchronous, incoming packets are
+ saved in a queue in order to avoid loss when they come in bursts */
+
+#define MAX_QUEUE_LENGTH 16U
+#define NEXT_QUEUE_INDEX(index) (((index) + 1) % MAX_QUEUE_LENGTH)
+#define IS_QUEUE_EMPTY() (queue_head == queue_tail)
+
+/* Fixed-size array of SignInstance */
+static ARR_Instance queue;
+static unsigned int queue_head;
+static unsigned int queue_tail;
+
+#define INVALID_SOCK_FD (-6)
+
+/* Unix domain socket connected to ntp_signd */
+static int sock_fd;
+
+/* Flag indicating if the MS-SNTP authentication is enabled */
+static int enabled;
+
+/* ================================================== */
+
+static void read_write_socket(int sock_fd, int event, void *anything);
+
+/* ================================================== */
+
+static void
+close_socket(void)
+{
+ SCH_RemoveFileHandler(sock_fd);
+ SCK_CloseSocket(sock_fd);
+ sock_fd = INVALID_SOCK_FD;
+
+ /* Empty the queue */
+ queue_head = queue_tail = 0;
+}
+
+/* ================================================== */
+
+static int
+open_socket(void)
+{
+ char path[PATH_MAX];
+
+ if (sock_fd != INVALID_SOCK_FD)
+ return 1;
+
+ if (snprintf(path, sizeof (path), "%s/socket", CNF_GetNtpSigndSocket()) >= sizeof (path)) {
+ DEBUG_LOG("signd socket path too long");
+ return 0;
+ }
+
+ sock_fd = SCK_OpenUnixStreamSocket(path, NULL, 0);
+ if (sock_fd < 0) {
+ sock_fd = INVALID_SOCK_FD;
+ return 0;
+ }
+
+ SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, NULL);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+process_response(SignInstance *inst)
+{
+ struct timespec ts;
+ double delay;
+
+ if (ntohs(inst->request.packet_id) != ntohl(inst->response.packet_id)) {
+ DEBUG_LOG("Invalid response ID");
+ return;
+ }
+
+ if (ntohl(inst->response.op) != SIGNING_SUCCESS) {
+ DEBUG_LOG("Signing failed");
+ return;
+ }
+
+ /* Check if the file descriptor is still valid */
+ if (!NIO_IsServerSocket(inst->local_addr.sock_fd)) {
+ DEBUG_LOG("Invalid NTP socket");
+ return;
+ }
+
+ SCH_GetLastEventTime(NULL, NULL, &ts);
+ delay = UTI_DiffTimespecsToDouble(&ts, &inst->request_ts);
+
+ DEBUG_LOG("Signing succeeded (delay %f)", delay);
+
+ /* Send the signed NTP packet */
+ NIO_SendPacket(&inst->response.signed_packet, &inst->remote_addr, &inst->local_addr,
+ ntohl(inst->response.length) + sizeof (inst->response.length) -
+ offsetof(SigndResponse, signed_packet), 0);
+}
+
+/* ================================================== */
+
+static void
+read_write_socket(int sock_fd, int event, void *anything)
+{
+ SignInstance *inst;
+ uint32_t response_length;
+ int s;
+
+ inst = ARR_GetElement(queue, queue_head);
+
+ if (event == SCH_FILE_OUTPUT) {
+ assert(!IS_QUEUE_EMPTY());
+ assert(inst->sent < inst->request_length);
+
+ if (!inst->sent)
+ SCH_GetLastEventTime(NULL, NULL, &inst->request_ts);
+
+ s = SCK_Send(sock_fd, (char *)&inst->request + inst->sent,
+ inst->request_length - inst->sent, 0);
+
+ if (s < 0) {
+ close_socket();
+ return;
+ }
+
+ inst->sent += s;
+
+ /* Try again later if the request is not complete yet */
+ if (inst->sent < inst->request_length)
+ return;
+
+ /* Disable output and wait for a response */
+ SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 0);
+ }
+
+ if (event == SCH_FILE_INPUT) {
+ if (IS_QUEUE_EMPTY()) {
+ DEBUG_LOG("Unexpected signd response");
+ close_socket();
+ return;
+ }
+
+ assert(inst->received < sizeof (inst->response));
+ s = SCK_Receive(sock_fd, (char *)&inst->response + inst->received,
+ sizeof (inst->response) - inst->received, 0);
+
+ if (s <= 0) {
+ close_socket();
+ return;
+ }
+
+ inst->received += s;
+
+ if (inst->received < sizeof (inst->response.length))
+ return;
+
+ response_length = ntohl(inst->response.length) + sizeof (inst->response.length);
+
+ if (response_length < offsetof(SigndResponse, signed_packet) ||
+ response_length > sizeof (SigndResponse)) {
+ DEBUG_LOG("Invalid response length");
+ close_socket();
+ return;
+ }
+
+ /* Wait for more data if not complete yet */
+ if (inst->received < response_length)
+ return;
+
+ process_response(inst);
+
+ /* Move the head and enable output for the next packet */
+ queue_head = NEXT_QUEUE_INDEX(queue_head);
+ if (!IS_QUEUE_EMPTY())
+ SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 1);
+ }
+}
+
+/* ================================================== */
+
+void
+NSD_Initialise()
+{
+ sock_fd = INVALID_SOCK_FD;
+ enabled = CNF_GetNtpSigndSocket() && CNF_GetNtpSigndSocket()[0];
+
+ if (!enabled)
+ return;
+
+ queue = ARR_CreateInstance(sizeof (SignInstance));
+ ARR_SetSize(queue, MAX_QUEUE_LENGTH);
+ queue_head = queue_tail = 0;
+
+ LOG(LOGS_INFO, "MS-SNTP authentication enabled");
+}
+
+/* ================================================== */
+
+void
+NSD_Finalise()
+{
+ if (!enabled)
+ return;
+ if (sock_fd != INVALID_SOCK_FD)
+ close_socket();
+ ARR_DestroyInstance(queue);
+}
+
+/* ================================================== */
+
+int
+NSD_SignAndSendPacket(uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info,
+ NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr)
+{
+ SignInstance *inst;
+
+ if (!enabled) {
+ DEBUG_LOG("signd disabled");
+ return 0;
+ }
+
+ if (queue_head == NEXT_QUEUE_INDEX(queue_tail)) {
+ DEBUG_LOG("signd queue full");
+ return 0;
+ }
+
+ if (info->length != NTP_HEADER_LENGTH) {
+ DEBUG_LOG("Invalid packet length");
+ return 0;
+ }
+
+ if (!open_socket())
+ return 0;
+
+ inst = ARR_GetElement(queue, queue_tail);
+ inst->remote_addr = *remote_addr;
+ inst->local_addr = *local_addr;
+ inst->sent = 0;
+ inst->received = 0;
+ 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));
+ inst->request.version = htonl(SIGND_VERSION);
+ inst->request.op = htonl(SIGN_TO_CLIENT);
+ inst->request.packet_id = htons(queue_tail);
+ inst->request._pad = 0;
+ inst->request.key_id = htonl(key_id);
+
+ memcpy(&inst->request.packet_to_sign, packet, info->length);
+
+ /* Enable output if there was no pending request */
+ if (IS_QUEUE_EMPTY())
+ SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_OUTPUT, 1);
+
+ queue_tail = NEXT_QUEUE_INDEX(queue_tail);
+
+ DEBUG_LOG("Packet added to signd queue (%u:%u)", queue_head, queue_tail);
+
+ return 1;
+}
diff --git a/ntp_signd.h b/ntp_signd.h
new file mode 100644
index 0000000..d333c9a
--- /dev/null
+++ b/ntp_signd.h
@@ -0,0 +1,42 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * 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 MS-SNTP authentication via Samba (ntp_signd) */
+
+#ifndef GOT_NTP_SIGND_H
+#define GOT_NTP_SIGND_H
+
+#include "addressing.h"
+#include "ntp.h"
+
+/* Initialisation function */
+extern void NSD_Initialise(void);
+
+/* Finalisation function */
+extern void NSD_Finalise(void);
+
+/* Function to sign an NTP packet and send it */
+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
diff --git a/ntp_sources.c b/ntp_sources.c
new file mode 100644
index 0000000..d8bd2d8
--- /dev/null
+++ b/ntp_sources.c
@@ -0,0 +1,1561 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2011-2012, 2014, 2016, 2020-2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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 which manage the pool of NTP sources that we are currently
+ a client of or peering with.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "conf.h"
+#include "ntp_sources.h"
+#include "ntp_core.h"
+#include "ntp_io.h"
+#include "util.h"
+#include "logging.h"
+#include "local.h"
+#include "memory.h"
+#include "nameserv_async.h"
+#include "privops.h"
+#include "sched.h"
+
+/* ================================================== */
+
+/* Maximum number of sources */
+#define MAX_SOURCES 65536
+
+/* Record type private to this file, used to store information about
+ particular sources */
+typedef struct {
+ NTP_Remote_Address *remote_addr; /* The address of this source, non-NULL
+ 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 as it was specified
+ (may be an IP address) */
+ IPAddr resolved_addr; /* Address resolved from the name, which can be
+ different from remote_addr (e.g. NTS-KE) */
+ int pool_id; /* ID of the pool from which was this source
+ added or INVALID_POOL */
+ int tentative; /* Flag indicating there was no valid response
+ received from the source yet */
+ uint32_t conf_id; /* Configuration ID, which can be shared with
+ different sources in case of a pool */
+ double last_resolving; /* Time of last name resolving (monotonic) */
+} SourceRecord;
+
+/* Hash table of SourceRecord, its size is a power of two and it's never
+ more than half full */
+static ARR_Instance records;
+
+/* Number of sources in the hash table */
+static int n_sources;
+
+/* Flag indicating new sources will be started automatically when added */
+static int auto_start_sources = 0;
+
+/* Flag indicating a record is currently being modified */
+static int record_lock;
+
+/* Last assigned address ID */
+static uint32_t last_address_id = 0;
+
+/* Last assigned configuration ID */
+static uint32_t last_conf_id = 0;
+
+/* Source scheduled for name resolving (first resolving or replacement) */
+struct UnresolvedSource {
+ /* Current address of the source (IPADDR_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_id;
+ /* Name to be resolved */
+ char *name;
+ /* Flag indicating addresses should be used in a random order */
+ int random_order;
+ /* Flag indicating current address should be replaced only if it is
+ no longer returned by the resolver */
+ int refreshment;
+ /* Next unresolved source in the list */
+ struct UnresolvedSource *next;
+};
+
+#define RESOLVE_INTERVAL_UNIT 7
+#define MIN_RESOLVE_INTERVAL 2
+#define MAX_RESOLVE_INTERVAL 9
+#define MAX_REPLACEMENT_INTERVAL 9
+
+static struct UnresolvedSource *unresolved_sources = NULL;
+static int resolving_interval = 0;
+static int resolving_restart = 0;
+static SCH_TimeoutID resolving_id;
+static struct UnresolvedSource *resolving_source = NULL;
+static NSR_SourceResolvingEndHandler resolving_end_handler = NULL;
+
+#define MAX_POOL_SOURCES 16
+#define INVALID_POOL (-1)
+
+/* Pool of sources with the same name */
+struct SourcePool {
+ /* Number of all sources from the pool */
+ int 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;
+};
+
+/* Array of SourcePool (indexed by their ID) */
+static ARR_Instance pools;
+
+/* Requested update of a source's address */
+struct AddressUpdate {
+ NTP_Remote_Address old_address;
+ NTP_Remote_Address new_address;
+};
+
+/* Update saved when record_lock is true */
+static struct AddressUpdate saved_address_update;
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void resolve_sources(void);
+static void rehash_records(void);
+static void handle_saved_address_update(void);
+static void clean_source_record(SourceRecord *record);
+static void remove_pool_sources(int pool_id, int tentative, int unresolved);
+static void remove_unresolved_source(struct UnresolvedSource *us);
+
+static void
+slew_sources(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything);
+
+/* ================================================== */
+
+/* Flag indicating whether module is initialised */
+static int initialised = 0;
+
+/* ================================================== */
+
+static SourceRecord *
+get_record(unsigned index)
+{
+ return (SourceRecord *)ARR_GetElement(records, index);
+}
+
+/* ================================================== */
+
+static struct SourcePool *
+get_pool(unsigned index)
+{
+ return (struct SourcePool *)ARR_GetElement(pools, index);
+}
+
+/* ================================================== */
+
+void
+NSR_Initialise(void)
+{
+ n_sources = 0;
+ resolving_id = 0;
+ initialised = 1;
+
+ records = ARR_CreateInstance(sizeof (SourceRecord));
+ rehash_records();
+
+ pools = ARR_CreateInstance(sizeof (struct SourcePool));
+
+ LCL_AddParameterChangeHandler(slew_sources, NULL);
+}
+
+/* ================================================== */
+
+void
+NSR_Finalise(void)
+{
+ NSR_RemoveAllSources();
+
+ LCL_RemoveParameterChangeHandler(slew_sources, NULL);
+
+ ARR_DestroyInstance(records);
+ ARR_DestroyInstance(pools);
+
+ SCH_RemoveTimeout(resolving_id);
+ while (unresolved_sources)
+ remove_unresolved_source(unresolved_sources);
+
+ initialised = 0;
+}
+
+/* ================================================== */
+/* Find a slot matching an IP address. It is assumed that there can
+ only ever be one record for a particular IP address. */
+
+static int
+find_slot(IPAddr *ip_addr, int *slot)
+{
+ SourceRecord *record;
+ uint32_t hash;
+ unsigned int i, size;
+
+ size = ARR_GetSize(records);
+
+ *slot = 0;
+
+ switch (ip_addr->family) {
+ case IPADDR_INET4:
+ case IPADDR_INET6:
+ case IPADDR_ID:
+ break;
+ default:
+ return 0;
+ }
+
+ hash = UTI_IPToHash(ip_addr);
+
+ for (i = 0; i < size / 2; i++) {
+ /* Use quadratic probing */
+ *slot = (hash + (i + i * i) / 2) % size;
+ record = get_record(*slot);
+
+ if (!record->remote_addr)
+ break;
+
+ if (UTI_CompareIPs(&record->remote_addr->ip_addr, ip_addr, NULL) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ================================================== */
+/* Find a slot matching an IP address and port. The function returns:
+ 0 => IP not matched, empty slot returned if a valid address was provided
+ 1 => Only IP matched, port doesn't match
+ 2 => Both IP and port matched. */
+
+static int
+find_slot2(NTP_Remote_Address *remote_addr, int *slot)
+{
+ if (!find_slot(&remote_addr->ip_addr, slot))
+ return 0;
+
+ return get_record(*slot)->remote_addr->port == remote_addr->port ? 2 : 1;
+}
+
+/* ================================================== */
+/* Check if hash table of given size is sufficient to contain sources */
+
+static int
+check_hashtable_size(unsigned int sources, unsigned int size)
+{
+ return sources * 2 <= size;
+}
+
+/* ================================================== */
+
+static void
+rehash_records(void)
+{
+ SourceRecord *temp_records;
+ unsigned int i, old_size, new_size;
+ int slot;
+
+ assert(!record_lock);
+
+ old_size = ARR_GetSize(records);
+
+ temp_records = MallocArray(SourceRecord, old_size);
+ memcpy(temp_records, ARR_GetElements(records), old_size * sizeof (SourceRecord));
+
+ /* The size of the hash table is always a power of two */
+ for (new_size = 1; !check_hashtable_size(n_sources, new_size); new_size *= 2)
+ ;
+
+ ARR_SetSize(records, new_size);
+
+ for (i = 0; i < new_size; i++)
+ get_record(i)->remote_addr = NULL;
+
+ for (i = 0; i < old_size; i++) {
+ if (!temp_records[i].remote_addr)
+ continue;
+
+ if (find_slot2(temp_records[i].remote_addr, &slot) != 0)
+ assert(0);
+
+ *get_record(slot) = temp_records[i];
+ }
+
+ Free(temp_records);
+}
+
+/* ================================================== */
+
+static void
+log_source(SourceRecord *record, int addition, int once_per_pool)
+{
+ int pool, log_addr;
+ char *ip_str;
+
+ if (once_per_pool && record->pool_id != INVALID_POOL) {
+ if (get_pool(record->pool_id)->sources > 1)
+ return;
+ pool = 1;
+ log_addr = 0;
+ } else {
+ ip_str = UTI_IPToString(&record->remote_addr->ip_addr);
+ pool = 0;
+ log_addr = strcmp(record->name, ip_str) != 0;
+ }
+
+ LOG(LOG_GetContextSeverity(LOGC_Command | LOGC_SourceFile), "%s %s %s%s%s%s",
+ addition ? "Added" : "Removed", pool ? "pool" : "source",
+ log_addr ? ip_str : record->name,
+ log_addr ? " (" : "", log_addr ? record->name : "", log_addr ? ")" : "");
+}
+
+/* ================================================== */
+
+/* Procedure to add a new source */
+static NSR_Status
+add_source(NTP_Remote_Address *remote_addr, char *name, NTP_Source_Type type,
+ SourceParameters *params, int pool_id, uint32_t conf_id)
+{
+ SourceRecord *record;
+ int slot;
+
+ assert(initialised);
+
+ /* Find empty bin & check that we don't have the address already */
+ if (find_slot2(remote_addr, &slot) != 0) {
+ return NSR_AlreadyInUse;
+ } else if (!name && !UTI_IsIPReal(&remote_addr->ip_addr)) {
+ /* Name is required for non-real addresses */
+ return NSR_InvalidName;
+ } else if (n_sources >= MAX_SOURCES) {
+ return NSR_TooManySources;
+ } else {
+ if (remote_addr->ip_addr.family != IPADDR_INET4 &&
+ remote_addr->ip_addr.family != IPADDR_INET6 &&
+ remote_addr->ip_addr.family != IPADDR_ID) {
+ return NSR_InvalidAF;
+ } else {
+ n_sources++;
+
+ if (!check_hashtable_size(n_sources, ARR_GetSize(records))) {
+ rehash_records();
+ if (find_slot2(remote_addr, &slot) != 0)
+ assert(0);
+ }
+
+ assert(!record_lock);
+ record_lock = 1;
+
+ record = get_record(slot);
+ record->name = Strdup(name ? name : UTI_IPToString(&remote_addr->ip_addr));
+ record->data = NCR_CreateInstance(remote_addr, type, params, record->name);
+ record->remote_addr = NCR_GetRemoteAddress(record->data);
+ record->resolved_addr = remote_addr->ip_addr;
+ record->pool_id = pool_id;
+ record->tentative = 1;
+ record->conf_id = conf_id;
+ record->last_resolving = SCH_GetLastEventMonoTime();
+
+ record_lock = 0;
+
+ if (record->pool_id != INVALID_POOL) {
+ get_pool(record->pool_id)->sources++;
+ if (!UTI_IsIPReal(&remote_addr->ip_addr))
+ get_pool(record->pool_id)->unresolved_sources++;
+ }
+
+ if (auto_start_sources && UTI_IsIPReal(&remote_addr->ip_addr))
+ NCR_StartInstance(record->data);
+
+ log_source(record, 1, 1);
+
+ /* The new instance is allowed to change its address immediately */
+ handle_saved_address_update();
+
+ return NSR_Success;
+ }
+ }
+}
+
+/* ================================================== */
+
+static NSR_Status
+change_source_address(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr,
+ int replacement)
+{
+ int slot1, slot2, found;
+ SourceRecord *record;
+ LOG_Severity severity;
+ char *name;
+
+ found = find_slot2(old_addr, &slot1);
+ if (found != 2)
+ 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 */
+ found = find_slot2(new_addr, &slot2);
+ if (found == 2 || (found != 0 && slot1 != slot2))
+ return NSR_AlreadyInUse;
+
+ assert(!record_lock);
+ record_lock = 1;
+
+ record = get_record(slot1);
+ NCR_ChangeRemoteAddress(record->data, new_addr, !replacement);
+ if (replacement)
+ record->resolved_addr = new_addr->ip_addr;
+
+ if (record->remote_addr != NCR_GetRemoteAddress(record->data) ||
+ UTI_CompareIPs(&record->remote_addr->ip_addr, &new_addr->ip_addr, NULL) != 0)
+ assert(0);
+
+ if (!UTI_IsIPReal(&old_addr->ip_addr) && UTI_IsIPReal(&new_addr->ip_addr)) {
+ if (auto_start_sources)
+ NCR_StartInstance(record->data);
+ if (record->pool_id != INVALID_POOL)
+ get_pool(record->pool_id)->unresolved_sources--;
+ }
+
+ if (!record->tentative) {
+ record->tentative = 1;
+
+ if (record->pool_id != INVALID_POOL)
+ get_pool(record->pool_id)->confirmed_sources--;
+ }
+
+ record_lock = 0;
+
+ name = record->name;
+ severity = UTI_IsIPReal(&old_addr->ip_addr) ? LOGS_INFO : LOGS_DEBUG;
+
+ 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);
+ } else {
+ LOG(severity, "Source %s (%s) changed port to %d",
+ UTI_IPToString(&new_addr->ip_addr), name, new_addr->port);
+ }
+
+ return NSR_Success;
+}
+
+/* ================================================== */
+
+static void
+handle_saved_address_update(void)
+{
+ if (!UTI_IsIPReal(&saved_address_update.old_address.ip_addr))
+ return;
+
+ if (change_source_address(&saved_address_update.old_address,
+ &saved_address_update.new_address, 0) != NSR_Success)
+ /* This is expected to happen only if the old address is wrong */
+ LOG(LOGS_ERR, "Could not change %s to %s",
+ UTI_IPSockAddrToString(&saved_address_update.old_address),
+ UTI_IPSockAddrToString(&saved_address_update.new_address));
+
+ saved_address_update.old_address.ip_addr.family = IPADDR_UNSPEC;
+}
+
+/* ================================================== */
+
+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;
+
+ handle_saved_address_update();
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+process_resolved_name(struct UnresolvedSource *us, IPAddr *ip_addrs, int n_addrs)
+{
+ NTP_Remote_Address old_addr, new_addr;
+ SourceRecord *record;
+ unsigned short first = 0;
+ int i, j, slot;
+
+ /* Keep using the current address if it is being refreshed and it is
+ still included in the resolved addresses */
+ if (us->refreshment) {
+ assert(us->pool_id == INVALID_POOL);
+
+ for (i = 0; i < n_addrs; i++) {
+ if (find_slot2(&us->address, &slot) == 2 &&
+ UTI_CompareIPs(&get_record(slot)->resolved_addr, &ip_addrs[i], NULL) == 0) {
+ DEBUG_LOG("%s still fresh", UTI_IPToString(&us->address.ip_addr));
+ return;
+ }
+ }
+ }
+
+ if (us->random_order)
+ UTI_GetRandomBytes(&first, sizeof (first));
+
+ 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(&new_addr.ip_addr));
+
+ if (us->pool_id != INVALID_POOL) {
+ /* In the pool resolving mode, try to replace a source from
+ the pool which does not have a real address yet */
+ for (j = 0; j < ARR_GetSize(records); j++) {
+ record = get_record(j);
+ if (!record->remote_addr || record->pool_id != us->pool_id ||
+ 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 {
+ new_addr.port = us->address.port;
+ if (replace_source_connectable(&us->address, &new_addr))
+ break;
+ }
+ }
+}
+
+/* ================================================== */
+
+static int
+is_resolved(struct UnresolvedSource *us)
+{
+ int slot;
+
+ if (us->pool_id != INVALID_POOL) {
+ return get_pool(us->pool_id)->unresolved_sources <= 0;
+ } else {
+ /* If the address is no longer present, it was removed or replaced
+ (i.e. resolved) */
+ return find_slot2(&us->address, &slot) == 0;
+ }
+}
+
+/* ================================================== */
+
+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, *next;
+
+ us = (struct UnresolvedSource *)anything;
+
+ assert(us == resolving_source);
+ assert(resolving_id == 0);
+
+ DEBUG_LOG("%s resolved to %d addrs", us->name, n_addrs);
+
+ switch (status) {
+ case DNS_TryAgain:
+ break;
+ case DNS_Success:
+ process_resolved_name(us, ip_addrs, n_addrs);
+ break;
+ case DNS_Failure:
+ LOG(LOGS_WARN, "Invalid host %s", us->name);
+ break;
+ default:
+ assert(0);
+ }
+
+ next = us->next;
+
+ /* Don't repeat the resolving if it (permanently) failed, it was a
+ replacement of a real address, or all addresses are already resolved */
+ if (status == DNS_Failure || UTI_IsIPReal(&us->address.ip_addr) || is_resolved(us))
+ remove_unresolved_source(us);
+
+ /* If a restart was requested and this was the last source in the list,
+ start with the first source again (if there still is one) */
+ if (!next && resolving_restart) {
+ next = unresolved_sources;
+ resolving_restart = 0;
+ }
+
+ resolving_source = next;
+
+ if (next) {
+ /* Continue with the next source in the list */
+ DEBUG_LOG("resolving %s", next->name);
+ DNS_Name2IPAddressAsync(next->name, name_resolve_handler, next);
+ } else {
+ /* This was the last source in the list. If some sources couldn't
+ be resolved, try again in exponentially increasing interval. */
+ if (unresolved_sources) {
+ 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;
+ }
+
+ /* This round of resolving is done */
+ if (resolving_end_handler)
+ (resolving_end_handler)();
+ }
+}
+
+/* ================================================== */
+
+static void
+resolve_sources(void)
+{
+ 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
+ will iterate over the rest */
+ us = unresolved_sources;
+
+ resolving_source = us;
+ DEBUG_LOG("resolving %s", us->name);
+ DNS_Name2IPAddressAsync(us->name, name_resolve_handler, us);
+}
+
+/* ================================================== */
+
+static void
+append_unresolved_source(struct UnresolvedSource *us)
+{
+ struct UnresolvedSource **i;
+
+ for (i = &unresolved_sources; *i; i = &(*i)->next)
+ ;
+ *i = us;
+ us->next = NULL;
+}
+
+/* ================================================== */
+
+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;
+ }
+ }
+}
+
+/* ================================================== */
+
+static int get_unused_pool_id(void)
+{
+ struct UnresolvedSource *us;
+ int i;
+
+ for (i = 0; i < ARR_GetSize(pools); i++) {
+ if (get_pool(i)->sources > 0)
+ continue;
+
+ /* Make sure there is no name waiting to be resolved using this pool */
+ for (us = unresolved_sources; us; us = us->next) {
+ if (us->pool_id == i)
+ break;
+ }
+ if (us)
+ continue;
+
+ return i;
+ }
+
+ return INVALID_POOL;
+}
+
+/* ================================================== */
+
+static uint32_t
+get_next_conf_id(uint32_t *conf_id)
+{
+ last_conf_id++;
+
+ if (conf_id)
+ *conf_id = last_conf_id;
+
+ return last_conf_id;
+}
+
+/* ================================================== */
+
+NSR_Status
+NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
+ SourceParameters *params, uint32_t *conf_id)
+{
+ return add_source(remote_addr, NULL, type, params, INVALID_POOL,
+ get_next_conf_id(conf_id));
+}
+
+/* ================================================== */
+
+NSR_Status
+NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
+ SourceParameters *params, uint32_t *conf_id)
+{
+ struct UnresolvedSource *us;
+ struct SourcePool *sp;
+ NTP_Remote_Address remote_addr;
+ int i, new_sources, pool_id;
+ uint32_t cid;
+
+ /* If the name is an IP address, add the source with the address directly */
+ if (UTI_StringToIP(name, &remote_addr.ip_addr)) {
+ remote_addr.port = port;
+ return add_source(&remote_addr, name, type, params, INVALID_POOL,
+ get_next_conf_id(conf_id));
+ }
+
+ /* Make sure the name is at least printable and has no spaces */
+ for (i = 0; name[i] != '\0'; i++) {
+ if (!isgraph((unsigned char)name[i]))
+ return NSR_InvalidName;
+ }
+
+ us = MallocNew(struct UnresolvedSource);
+ us->name = Strdup(name);
+ us->random_order = 0;
+ us->refreshment = 0;
+
+ remote_addr.ip_addr.family = IPADDR_ID;
+ remote_addr.ip_addr.addr.id = ++last_address_id;
+ remote_addr.port = port;
+
+ if (!pool) {
+ us->pool_id = INVALID_POOL;
+ us->address = remote_addr;
+ new_sources = 1;
+ } else {
+ pool_id = get_unused_pool_id();
+ if (pool_id != INVALID_POOL) {
+ sp = get_pool(pool_id);
+ } else {
+ sp = ARR_GetNewElement(pools);
+ pool_id = ARR_GetSize(pools) - 1;
+ }
+
+ sp->sources = 0;
+ sp->unresolved_sources = 0;
+ sp->confirmed_sources = 0;
+ sp->max_sources = CLAMP(1, params->max_sources, MAX_POOL_SOURCES);
+ us->pool_id = pool_id;
+ us->address.ip_addr.family = IPADDR_UNSPEC;
+ new_sources = MIN(2 * sp->max_sources, MAX_POOL_SOURCES);
+ }
+
+ append_unresolved_source(us);
+
+ cid = get_next_conf_id(conf_id);
+
+ for (i = 0; i < new_sources; i++) {
+ if (i > 0)
+ remote_addr.ip_addr.addr.id = ++last_address_id;
+ if (add_source(&remote_addr, name, type, params, us->pool_id, cid) != NSR_Success)
+ return NSR_TooManySources;
+ }
+
+ return NSR_UnresolvedName;
+}
+
+/* ================================================== */
+
+const char *
+NSR_StatusToString(NSR_Status status)
+{
+ switch (status) {
+ case NSR_Success:
+ return "Success";
+ case NSR_NoSuchSource:
+ return "No such source";
+ case NSR_AlreadyInUse:
+ return "Already in use";
+ case NSR_TooManySources:
+ return "Too many sources";
+ case NSR_InvalidAF:
+ return "Invalid address";
+ case NSR_InvalidName:
+ return "Invalid name";
+ case NSR_UnresolvedName:
+ return "Unresolved name";
+ default:
+ return "?";
+ }
+}
+
+/* ================================================== */
+
+void
+NSR_SetSourceResolvingEndHandler(NSR_SourceResolvingEndHandler handler)
+{
+ resolving_end_handler = handler;
+}
+
+/* ================================================== */
+
+void
+NSR_ResolveSources(void)
+{
+ /* Try to resolve unresolved sources now */
+ if (unresolved_sources) {
+ /* Allow only one resolving to be running at a time */
+ if (!resolving_source) {
+ if (resolving_id != 0) {
+ SCH_RemoveTimeout(resolving_id);
+ resolving_id = 0;
+ resolving_interval--;
+ }
+ resolve_sources();
+ } else {
+ /* Try again as soon as the current resolving ends */
+ resolving_restart = 1;
+ }
+ } else {
+ /* No unresolved sources, we are done */
+ if (resolving_end_handler)
+ (resolving_end_handler)();
+ }
+}
+
+/* ================================================== */
+
+void NSR_StartSources(void)
+{
+ NTP_Remote_Address *addr;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(records); i++) {
+ addr = get_record(i)->remote_addr;
+ if (!addr || !UTI_IsIPReal(&addr->ip_addr))
+ continue;
+ NCR_StartInstance(get_record(i)->data);
+ }
+}
+
+/* ================================================== */
+
+void NSR_AutoStartSources(void)
+{
+ auto_start_sources = 1;
+}
+
+/* ================================================== */
+
+static void
+clean_source_record(SourceRecord *record)
+{
+ assert(record->remote_addr);
+
+ if (record->pool_id != INVALID_POOL) {
+ struct SourcePool *pool = get_pool(record->pool_id);
+
+ 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);
+ Free(record->name);
+
+ n_sources--;
+}
+
+/* ================================================== */
+
+/* Procedure to remove a source. We don't bother whether the port
+ address is matched - we're only interested in removing a record for
+ the right IP address. */
+NSR_Status
+NSR_RemoveSource(IPAddr *address)
+{
+ int slot;
+
+ assert(initialised);
+
+ if (find_slot(address, &slot) == 0)
+ return NSR_NoSuchSource;
+
+ log_source(get_record(slot), 0, 0);
+ clean_source_record(get_record(slot));
+
+ /* Rehash the table to make sure there are no broken probe sequences.
+ This is costly, but it's not expected to happen frequently. */
+
+ rehash_records();
+
+ return NSR_Success;
+}
+
+/* ================================================== */
+
+void
+NSR_RemoveSourcesById(uint32_t conf_id)
+{
+ SourceRecord *record;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+ if (!record->remote_addr || record->conf_id != conf_id)
+ continue;
+ log_source(record, 0, 1);
+ clean_source_record(record);
+ }
+
+ rehash_records();
+}
+
+/* ================================================== */
+
+void
+NSR_RemoveAllSources(void)
+{
+ SourceRecord *record;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+ if (!record->remote_addr)
+ continue;
+ clean_source_record(record);
+ }
+
+ rehash_records();
+}
+
+/* ================================================== */
+
+static void
+resolve_source_replacement(SourceRecord *record, int refreshment)
+{
+ struct UnresolvedSource *us;
+
+ DEBUG_LOG("%s %s (%s)", refreshment ? "refreshing" : "trying to replace",
+ UTI_IPToString(&record->remote_addr->ip_addr), record->name);
+
+ record->last_resolving = SCH_GetLastEventMonoTime();
+
+ us = MallocNew(struct UnresolvedSource);
+ us->name = Strdup(record->name);
+ /* Ignore the order of addresses from the resolver to not get
+ stuck with a pair of unreachable or otherwise unusable servers
+ (e.g. falsetickers) in case the order doesn't change, or a group
+ of servers if they are ordered by IP family */
+ us->random_order = 1;
+ us->refreshment = refreshment;
+ us->pool_id = INVALID_POOL;
+ us->address = *record->remote_addr;
+
+ append_unresolved_source(us);
+ NSR_ResolveSources();
+}
+
+/* ================================================== */
+
+void
+NSR_HandleBadSource(IPAddr *address)
+{
+ static double next_replacement = 0.0;
+ SourceRecord *record;
+ IPAddr ip_addr;
+ uint32_t rnd;
+ double now;
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return;
+
+ record = get_record(slot);
+
+ /* Don't try to replace a source specified by an IP address unless the
+ address changed since the source was added (e.g. by NTS-KE) */
+ if (UTI_StringToIP(record->name, &ip_addr) &&
+ UTI_CompareIPs(&record->remote_addr->ip_addr, &ip_addr, NULL) == 0)
+ return;
+
+ /* Don't resolve names too frequently */
+ now = SCH_GetLastEventMonoTime();
+ if (now < next_replacement) {
+ DEBUG_LOG("replacement postponed");
+ return;
+ }
+
+ UTI_GetRandomBytes(&rnd, sizeof (rnd));
+ next_replacement = now + ((double)rnd / (uint32_t)-1) *
+ (RESOLVE_INTERVAL_UNIT * (1 << MAX_REPLACEMENT_INTERVAL));
+
+ resolve_source_replacement(record, 0);
+}
+
+/* ================================================== */
+
+static void
+maybe_refresh_source(void)
+{
+ static double last_refreshment = 0.0;
+ SourceRecord *record, *oldest_record;
+ int i, min_interval;
+ double now;
+
+ min_interval = CNF_GetRefresh();
+
+ now = SCH_GetLastEventMonoTime();
+ if (min_interval <= 0 || now < last_refreshment + min_interval)
+ return;
+
+ last_refreshment = now;
+
+ for (i = 0, oldest_record = NULL; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+ if (!record->remote_addr || UTI_IsStringIP(record->name))
+ continue;
+
+ if (!oldest_record || oldest_record->last_resolving > record->last_resolving)
+ oldest_record = record;
+ }
+
+ if (!oldest_record)
+ return;
+
+ /* Check if the name wasn't already resolved in the last interval */
+ if (now < oldest_record->last_resolving + min_interval) {
+ last_refreshment = oldest_record->last_resolving;
+ return;
+ }
+
+ resolve_source_replacement(oldest_record, 1);
+}
+
+/* ================================================== */
+
+void
+NSR_RefreshAddresses(void)
+{
+ SourceRecord *record;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+ if (!record->remote_addr)
+ continue;
+
+ resolve_source_replacement(record, 1);
+ }
+}
+
+/* ================================================== */
+
+NSR_Status
+NSR_UpdateSourceNtpAddress(NTP_Remote_Address *old_addr, NTP_Remote_Address *new_addr)
+{
+ int slot;
+
+ if (!UTI_IsIPReal(&old_addr->ip_addr) || !UTI_IsIPReal(&new_addr->ip_addr))
+ return NSR_InvalidAF;
+
+ if (UTI_CompareIPs(&old_addr->ip_addr, &new_addr->ip_addr, NULL) != 0 &&
+ find_slot(&new_addr->ip_addr, &slot))
+ return NSR_AlreadyInUse;
+
+ /* If a record is being modified (e.g. by change_source_address(), or the
+ source is just being created), postpone the change to avoid corruption */
+
+ if (!record_lock)
+ return change_source_address(old_addr, new_addr, 0);
+
+ if (UTI_IsIPReal(&saved_address_update.old_address.ip_addr))
+ return NSR_TooManySources;
+
+ saved_address_update.old_address = *old_addr;
+ saved_address_update.new_address = *new_addr;
+
+ return NSR_Success;
+}
+
+/* ================================================== */
+
+static void remove_pool_sources(int pool_id, int tentative, int unresolved)
+{
+ SourceRecord *record;
+ unsigned int i, removed;
+
+ for (i = removed = 0; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+
+ if (!record->remote_addr || record->pool_id != pool_id)
+ continue;
+
+ 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);
+ removed++;
+ }
+
+ if (removed)
+ rehash_records();
+}
+
+/* ================================================== */
+
+uint32_t
+NSR_GetLocalRefid(IPAddr *address)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ return NCR_GetLocalRefid(get_record(slot)->data);
+}
+
+/* ================================================== */
+
+char *
+NSR_GetName(IPAddr *address)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return NULL;
+
+ return get_record(slot)->name;
+}
+
+/* ================================================== */
+
+/* This routine is called by ntp_io when a new packet arrives off the network,
+ possibly with an authentication tail */
+void
+NSR_ProcessRx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *rx_ts, NTP_Packet *message, int length)
+{
+ SourceRecord *record;
+ struct SourcePool *pool;
+ int slot;
+
+ assert(initialised);
+
+ /* Avoid unnecessary lookup if the packet cannot be a response from our
+ source. Otherwise, it must match both IP address and port number. */
+ if (NTP_LVM_TO_MODE(message->lvm) != MODE_CLIENT &&
+ find_slot2(remote_addr, &slot) == 2) {
+ record = get_record(slot);
+
+ if (!NCR_ProcessRxKnown(record->data, local_addr, rx_ts, message, length))
+ return;
+
+ if (record->tentative) {
+ /* This was the first good reply from the source */
+ record->tentative = 0;
+
+ if (record->pool_id != INVALID_POOL) {
+ pool = get_pool(record->pool_id);
+ pool->confirmed_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->confirmed_sources >= pool->max_sources)
+ remove_pool_sources(record->pool_id, 1, 0);
+ }
+ }
+
+ maybe_refresh_source();
+ } else {
+ NCR_ProcessRxUnknown(remote_addr, local_addr, rx_ts, message, length);
+ }
+}
+
+/* ================================================== */
+
+void
+NSR_ProcessTx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length)
+{
+ SourceRecord *record;
+ int slot;
+
+ /* Avoid unnecessary lookup if the packet cannot be a request to our
+ source. Otherwise, it must match both IP address and port number. */
+ if (NTP_LVM_TO_MODE(message->lvm) != MODE_SERVER &&
+ find_slot2(remote_addr, &slot) == 2) {
+ record = get_record(slot);
+ NCR_ProcessTxKnown(record->data, local_addr, tx_ts, message, length);
+ } else {
+ NCR_ProcessTxUnknown(remote_addr, local_addr, tx_ts, message, length);
+ }
+}
+
+/* ================================================== */
+
+static void
+slew_sources(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything)
+{
+ SourceRecord *record;
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+ if (record->remote_addr) {
+ if (change_type == LCL_ChangeUnknownStep) {
+ NCR_ResetInstance(record->data);
+ NCR_ResetPoll(record->data);
+ } else {
+ NCR_SlewTimes(record->data, cooked, dfreq, doffset);
+ }
+ }
+ }
+}
+
+/* ================================================== */
+
+int
+NSR_SetConnectivity(IPAddr *mask, IPAddr *address, SRC_Connectivity connectivity)
+{
+ SourceRecord *record, *syncpeer;
+ unsigned int i, any;
+
+ if (connectivity != SRC_OFFLINE)
+ NSR_ResolveSources();
+
+ any = 0;
+ syncpeer = NULL;
+ for (i = 0; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+ if (record->remote_addr) {
+ /* 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)) {
+ syncpeer = record;
+ continue;
+ }
+ NCR_SetConnectivity(record->data, connectivity);
+ }
+ }
+ }
+
+ /* Set the sync peer last to avoid unnecessary reference switching */
+ if (syncpeer)
+ NCR_SetConnectivity(syncpeer->data, connectivity);
+
+ return any;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMinpoll(IPAddr *address, int new_minpoll)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_ModifyMinpoll(get_record(slot)->data, new_minpoll);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMaxpoll(IPAddr *address, int new_maxpoll)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_ModifyMaxpoll(get_record(slot)->data, new_maxpoll);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMaxdelay(IPAddr *address, double new_max_delay)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_ModifyMaxdelay(get_record(slot)->data, new_max_delay);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMaxdelayratio(IPAddr *address, double new_max_delay_ratio)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_ModifyMaxdelayratio(get_record(slot)->data, new_max_delay_ratio);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMaxdelaydevratio(IPAddr *address, double new_max_delay_dev_ratio)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_ModifyMaxdelaydevratio(get_record(slot)->data, new_max_delay_dev_ratio);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyMinstratum(IPAddr *address, int new_min_stratum)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_ModifyMinstratum(get_record(slot)->data, new_min_stratum);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NSR_ModifyPolltarget(IPAddr *address, int new_poll_target)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_ModifyPolltarget(get_record(slot)->data, new_poll_target);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples,
+ IPAddr *mask, IPAddr *address)
+{
+ SourceRecord *record;
+ unsigned int i;
+ int any;
+
+ any = 0;
+ for (i = 0; i < ARR_GetSize(records); i++) {
+ record = get_record(i);
+ if (record->remote_addr) {
+ if (address->family == IPADDR_UNSPEC ||
+ !UTI_CompareIPs(&record->remote_addr->ip_addr, address, mask)) {
+ any = 1;
+ NCR_InitiateSampleBurst(record->data, n_good_samples, n_total_samples);
+ }
+ }
+ }
+
+ return any;
+
+}
+
+/* ================================================== */
+/* The ip address is assumed to be completed on input, that is how we
+ identify the source record. */
+
+void
+NSR_ReportSource(RPT_SourceReport *report, struct timespec *now)
+{
+ int slot;
+
+ if (find_slot(&report->ip_addr, &slot)) {
+ NCR_ReportSource(get_record(slot)->data, report, now);
+ } else {
+ report->poll = 0;
+ report->latest_meas_ago = 0;
+ }
+}
+
+/* ================================================== */
+
+int
+NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report)
+{
+ int slot;
+
+ if (!find_slot(address, &slot))
+ return 0;
+
+ NCR_GetAuthReport(get_record(slot)->data, report);
+ return 1;
+}
+
+/* ================================================== */
+/* The ip address is assumed to be completed on input, that is how we
+ identify the source record. */
+
+int
+NSR_GetNTPReport(RPT_NTPReport *report)
+{
+ int slot;
+
+ if (!find_slot(&report->remote_addr, &slot))
+ return 0;
+
+ NCR_GetNTPReport(get_record(slot)->data, report);
+ return 1;
+}
+
+/* ================================================== */
+
+void
+NSR_GetActivityReport(RPT_ActivityReport *report)
+{
+ SourceRecord *record;
+ unsigned int i;
+
+ 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)
+ 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);
+ }
+ }
+}
+
+/* ================================================== */
+
+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);
+ }
+}
diff --git a/ntp_sources.h b/ntp_sources.h
new file mode 100644
index 0000000..5aeb1a3
--- /dev/null
+++ b/ntp_sources.h
@@ -0,0 +1,154 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 the part of the software that deals with the set of
+ current NTP servers and peers, which can resolve an IP address into
+ a source record for further processing.
+
+ */
+
+#ifndef GOT_NTP_SOURCES_H
+#define GOT_NTP_SOURCES_H
+
+#include "ntp.h"
+#include "addressing.h"
+#include "srcparams.h"
+#include "ntp_core.h"
+#include "reports.h"
+
+/* Status values returned by operations that indirectly result from user
+ input. */
+typedef enum {
+ NSR_Success, /* Operation successful */
+ 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_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. */
+extern NSR_Status NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
+ SourceParameters *params, uint32_t *conf_id);
+
+/* Procedure to add a new server, peer source, or pool of servers specified by
+ name instead of address. The name is resolved in exponentially increasing
+ intervals until it succeeds or fails with a non-temporary error. If the
+ name is an address, it is equivalent to NSR_AddSource(). */
+extern NSR_Status NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
+ SourceParameters *params, uint32_t *conf_id);
+
+extern const char *NSR_StatusToString(NSR_Status status);
+
+/* Function type for handlers to be called back when an attempt
+ * (possibly unsuccessful) to resolve unresolved sources ends */
+typedef void (*NSR_SourceResolvingEndHandler)(void);
+
+/* Set the handler, or NULL to disable the notification */
+extern void NSR_SetSourceResolvingEndHandler(NSR_SourceResolvingEndHandler handler);
+
+/* Procedure to start resolving unresolved sources */
+extern void NSR_ResolveSources(void);
+
+/* Procedure to start all sources */
+extern void NSR_StartSources(void);
+
+/* Start new sources automatically */
+extern void NSR_AutoStartSources(void);
+
+/* Procedure to remove a source */
+extern NSR_Status NSR_RemoveSource(IPAddr *address);
+
+/* Procedure to remove all sources matching a configuration ID */
+extern void NSR_RemoveSourcesById(uint32_t conf_id);
+
+/* Procedure to remove all sources */
+extern void NSR_RemoveAllSources(void);
+
+/* Procedure to try to find a replacement for a bad source */
+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. The update may be
+ postponed. */
+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 as it was specified (it may be
+ an IP 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);
+
+/* This routine is called by ntp_io when a packet was sent to the network and
+ an accurate transmit timestamp was captured */
+extern void NSR_ProcessTx(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
+ NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length);
+
+/* Initialisation function */
+extern void NSR_Initialise(void);
+
+/* Finalisation function */
+extern void NSR_Finalise(void);
+
+/* This routine is used to indicate that sources whose IP addresses
+ match a particular subnet should be set online or offline. It returns
+ a flag indicating whether any hosts matched the address. */
+extern int NSR_SetConnectivity(IPAddr *mask, IPAddr *address, SRC_Connectivity connectivity);
+
+extern int NSR_ModifyMinpoll(IPAddr *address, int new_minpoll);
+
+extern int NSR_ModifyMaxpoll(IPAddr *address, int new_maxpoll);
+
+extern int NSR_ModifyMaxdelay(IPAddr *address, double new_max_delay);
+
+extern int NSR_ModifyMaxdelayratio(IPAddr *address, double new_max_delay_ratio);
+
+extern int NSR_ModifyMaxdelaydevratio(IPAddr *address, double new_max_delay_ratio);
+
+extern int NSR_ModifyMinstratum(IPAddr *address, int new_min_stratum);
+
+extern int NSR_ModifyPolltarget(IPAddr *address, int new_poll_target);
+
+extern int NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples, IPAddr *mask, IPAddr *address);
+
+extern void NSR_ReportSource(RPT_SourceReport *report, struct timespec *now);
+
+extern int NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report);
+
+extern int NSR_GetNTPReport(RPT_NTPReport *report);
+
+extern void NSR_GetActivityReport(RPT_ActivityReport *report);
+
+extern void NSR_DumpAuthData(void);
+
+#endif /* GOT_NTP_SOURCES_H */
diff --git a/nts_ke.h b/nts_ke.h
new file mode 100644
index 0000000..e7497af
--- /dev/null
+++ b/nts_ke.h
@@ -0,0 +1,81 @@
+/*
+ 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_PORT 4460
+
+#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
diff --git a/nts_ke_client.c b/nts_ke_client.c
new file mode 100644
index 0000000..3891f71
--- /dev/null
+++ b/nts_ke_client.c
@@ -0,0 +1,457 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020-2021
+ *
+ * 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_Credentials credentials;
+ 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 + 2];
+ IPSockAddr ntp_address;
+};
+
+/* ================================================== */
+
+static NKSN_Credentials default_credentials = NULL;
+static int default_credentials_refs = 0;
+
+/* ================================================== */
+
+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) {
+ Free(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 in the same family as the NTS-KE server */
+ 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 data[2];
+ int length;
+
+ NKSN_BeginMessage(session);
+
+ data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, sizeof (data[0])))
+ return 0;
+
+ length = 0;
+ if (SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0)
+ data[length++] = htons(AEAD_AES_128_GCM_SIV);
+ if (SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0)
+ data[length++] = htons(AEAD_AES_SIV_CMAC_256);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data,
+ length * sizeof (data[0])))
+ 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_RECORD_BODY_LENGTH / sizeof (uint16_t)];
+
+ assert(NKE_MAX_COOKIE_LENGTH <= NKE_MAX_RECORD_BODY_LENGTH);
+ assert(sizeof (data) % 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;
+
+ if (length > sizeof (data)) {
+ DEBUG_LOG("Record too long type=%d length=%d critical=%d", type, length, critical);
+ if (critical)
+ error = 1;
+ continue;
+ }
+
+ 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 &&
+ ntohs(data[0]) != AEAD_AES_128_GCM_SIV) ||
+ SIV_GetKeyLength(ntohs(data[0])) <= 0) {
+ DEBUG_LOG("Unexpected NTS-KE AEAD algorithm");
+ error = 1;
+ break;
+ }
+ aead_algorithm = ntohs(data[0]);
+ 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 length=%d", length);
+
+ if (length < 1 || length > NKE_MAX_COOKIE_LENGTH || length % 4 != 0 ||
+ inst->num_cookies >= NKE_MAX_COOKIES) {
+ DEBUG_LOG("Unexpected length/cookie");
+ break;
+ }
+
+ assert(NKE_MAX_COOKIE_LENGTH == sizeof (inst->cookies[inst->num_cookies].cookie));
+ assert(NKE_MAX_COOKIES == sizeof (inst->cookies) /
+ sizeof (inst->cookies[inst->num_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((unsigned char)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 < 0)
+ 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)) {
+ int length = strlen(inst->server_name);
+
+ /* Add a trailing dot if not present to force the name to be
+ resolved as a fully qualified domain name */
+ if (length < 1 || length + 1 >= sizeof (inst->server_name))
+ return 0;
+ if (inst->server_name[length - 1] != '.') {
+ inst->server_name[length] = '.';
+ inst->server_name[length + 1] = '\0';
+ }
+
+ DNS_Name2IPAddressAsync(inst->server_name, name_resolve_handler, inst);
+ inst->resolving_name = 1;
+ }
+ }
+
+ inst->got_response = 1;
+
+ return 1;
+}
+
+/* ================================================== */
+
+NKC_Instance
+NKC_CreateInstance(IPSockAddr *address, const char *name, uint32_t cert_set)
+{
+ const char **trusted_certs;
+ uint32_t *certs_ids;
+ NKC_Instance inst;
+ int n_certs;
+
+ 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;
+
+ n_certs = CNF_GetNtsTrustedCertsPaths(&trusted_certs, &certs_ids);
+
+ /* Share the credentials among clients using the default set of trusted
+ certificates, which likely contains most certificates */
+ if (cert_set == 0) {
+ if (!default_credentials)
+ default_credentials = NKSN_CreateClientCertCredentials(trusted_certs, certs_ids,
+ n_certs, cert_set);
+ inst->credentials = default_credentials;
+ if (default_credentials)
+ default_credentials_refs++;
+ } else {
+ inst->credentials = NKSN_CreateClientCertCredentials(trusted_certs, certs_ids,
+ n_certs, cert_set);
+ }
+
+ return inst;
+}
+
+/* ================================================== */
+
+void
+NKC_DestroyInstance(NKC_Instance inst)
+{
+ NKSN_DestroyInstance(inst->session);
+
+ Free(inst->name);
+
+ if (inst->credentials) {
+ if (inst->credentials == default_credentials) {
+ default_credentials_refs--;
+ if (default_credentials_refs <= 0) {
+ NKSN_DestroyCertCredentials(default_credentials);
+ default_credentials = NULL;
+ }
+ } else {
+ NKSN_DestroyCertCredentials(inst->credentials);
+ }
+ }
+
+ /* If the asynchronous resolver is running, let the handler free
+ the instance later */
+ if (inst->resolving_name) {
+ inst->destroying = 1;
+ return;
+ }
+
+ Free(inst);
+}
+
+/* ================================================== */
+
+int
+NKC_Start(NKC_Instance inst)
+{
+ IPSockAddr local_addr;
+ char label[512], *iface;
+ int sock_fd;
+
+ assert(!NKC_IsActive(inst));
+
+ inst->got_response = 0;
+
+ if (!inst->credentials) {
+ DEBUG_LOG("Missing client credentials");
+ return 0;
+ }
+
+ /* Don't try to connect if missing the algorithm which all servers
+ are required to support */
+ if (SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) <= 0) {
+ LOG(LOGS_ERR, "Missing AES-SIV-CMAC-256");
+ return 0;
+ }
+
+ /* Follow the bindacqaddress and bindacqdevice settings */
+ CNF_GetBindAcquisitionAddress(inst->address.ip_addr.family, &local_addr.ip_addr);
+ local_addr.port = 0;
+ iface = CNF_GetBindAcquisitionInterface();
+
+ /* 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))
+ ;
+
+ sock_fd = SCK_OpenTcpSocket(&inst->address, &local_addr, iface, 0);
+ if (sock_fd < 0) {
+ LOG(LOGS_ERR, "Could not connect to %s", label);
+ return 0;
+ }
+
+ /* Start an NTS-KE session */
+ if (!NKSN_StartSession(inst->session, sock_fd, label, inst->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 1;
+}
+
+/* ================================================== */
+
+int
+NKC_GetRetryFactor(NKC_Instance inst)
+{
+ return NKSN_GetRetryFactor(inst->session);
+}
diff --git a/nts_ke_client.h b/nts_ke_client.h
new file mode 100644
index 0000000..a1bedb6
--- /dev/null
+++ b/nts_ke_client.h
@@ -0,0 +1,56 @@
+/*
+ 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;
+
+/* Create a client NTS-KE instance */
+extern NKC_Instance NKC_CreateInstance(IPSockAddr *address, const char *name, uint32_t cert_set);
+
+/* 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
diff --git a/nts_ke_server.c b/nts_ke_server.c
new file mode 100644
index 0000000..3fe99db
--- /dev/null
+++ b/nts_ke_server.c
@@ -0,0 +1,1036 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020, 2022
+ *
+ * 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 "local.h"
+#include "logging.h"
+#include "memory.h"
+#include "ntp_core.h"
+#include "nts_ke_session.h"
+#include "privops.h"
+#include "siv.h"
+#include "socket.h"
+#include "sched.h"
+#include "sys.h"
+#include "util.h"
+
+#define SERVER_TIMEOUT 2.0
+
+#define MAX_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 "NKS1\n"
+#define OLD_DUMP_IDENTIFIER "NKS0\n"
+
+#define INVALID_SOCK_FD (-7)
+
+typedef struct {
+ uint32_t key_id;
+} ServerCookieHeader;
+
+typedef struct {
+ uint32_t id;
+ unsigned char key[SIV_MAX_KEY_LENGTH];
+ SIV_Algorithm siv_algorithm;
+ SIV_Instance siv;
+ int nonce_length;
+} ServerKey;
+
+typedef struct {
+ uint32_t key_id;
+ uint32_t siv_algorithm;
+ 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 is_helper;
+
+static int initialised = 0;
+
+/* Array of NKSN instances */
+static ARR_Instance sessions;
+static NKSN_Credentials server_credentials;
+
+/* ================================================== */
+
+static int handle_message(void *arg);
+
+/* ================================================== */
+
+static int
+handle_client(int sock_fd, IPSockAddr *addr)
+{
+ NKSN_Instance inst, *instp;
+ int i;
+
+ /* Leave at least half of the descriptors which can handled by select()
+ to other use */
+ if (sock_fd > FD_SETSIZE / 2) {
+ DEBUG_LOG("Rejected connection from %s (%s)",
+ UTI_IPSockAddrToString(addr), "too many descriptors");
+ return 0;
+ }
+
+ /* Find an unused server slot or one with an already 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;
+ }
+
+ assert(server_credentials);
+
+ if (!NKSN_StartSession(inst, sock_fd, UTI_IPSockAddrToString(addr),
+ server_credentials, SERVER_TIMEOUT))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+update_key_siv(ServerKey *key, SIV_Algorithm algorithm)
+{
+ if (!key->siv || key->siv_algorithm != algorithm) {
+ if (key->siv)
+ SIV_DestroyInstance(key->siv);
+ key->siv_algorithm = algorithm;
+ key->siv = SIV_CreateInstance(algorithm);
+ key->nonce_length = MIN(SIV_GetMaxNonceLength(key->siv), MAX_COOKIE_NONCE_LENGTH);
+ }
+
+ if (!key->siv || !SIV_SetKey(key->siv, key->key, SIV_GetKeyLength(key->siv_algorithm)))
+ LOG_FATAL("Could not set SIV key");
+}
+
+/* ================================================== */
+
+static void
+handle_helper_request(int fd, int event, void *arg)
+{
+ SCK_Message *message;
+ HelperRequest *req;
+ IPSockAddr client_addr;
+ ServerKey *key;
+ int sock_fd;
+
+ /* Receive the helper request with the NTS-KE session socket.
+ With multiple helpers EAGAIN errors are expected here. */
+ 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 (!initialised) {
+ DEBUG_LOG("Uninitialised helper");
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+
+ if (message->length != sizeof (HelperRequest))
+ LOG_FATAL("Invalid helper request");
+
+ req = message->data;
+
+ /* Extract the current server key and client address from the request */
+ key = &server_keys[current_server_key];
+ key->id = ntohl(req->key_id);
+ assert(sizeof (key->key) == sizeof (req->key));
+ memcpy(key->key, req->key, sizeof (key->key));
+ UTI_IPNetworkToHost(&req->client_addr, &client_addr.ip_addr);
+ client_addr.port = ntohs(req->client_port);
+
+ update_key_siv(key, ntohl(req->siv_algorithm));
+
+ 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 listening_fd, int event, void *arg)
+{
+ SCK_Message message;
+ IPSockAddr addr;
+ int log_index, sock_fd;
+ struct timespec now;
+
+ sock_fd = SCK_AcceptConnection(listening_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_LogServiceAccess(CLG_NTSKE, &addr.ip_addr, &now);
+ if (log_index >= 0 && CLG_LimitServiceRate(CLG_NTSKE, 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;
+
+ memset(&req, 0, sizeof (req));
+
+ /* Include the current server key and client address in the request */
+ req.key_id = htonl(server_keys[current_server_key].id);
+ req.siv_algorithm = htonl(server_keys[current_server_key].siv_algorithm);
+ assert(sizeof (req.key) == sizeof (server_keys[current_server_key].key));
+ 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;
+
+ errno = 0;
+ if (!SCK_SendMessage(helper_sock_fd, &message, SCK_FLAG_MSG_DESCRIPTOR)) {
+ /* If sending failed with EPIPE, it means all helpers closed their end of
+ the socket (e.g. due to a fatal error) */
+ if (errno == EPIPE)
+ LOG_FATAL("NTS-KE helpers failed");
+ 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)
+{
+ IPSockAddr local_addr;
+ int backlog, sock_fd;
+ char *iface;
+
+ if (!SCK_IsIpFamilyEnabled(family))
+ return INVALID_SOCK_FD;
+
+ CNF_GetBindAddress(family, &local_addr.ip_addr);
+ local_addr.port = CNF_GetNtsServerPort();
+ iface = CNF_GetBindNtpInterface();
+
+ sock_fd = SCK_OpenTcpSocket(NULL, &local_addr, iface, 0);
+ if (sock_fd < 0) {
+ LOG(LOGS_ERR, "Could not open NTS-KE socket on %s", UTI_IPSockAddrToString(&local_addr));
+ return INVALID_SOCK_FD;
+ }
+
+ /* Set the maximum number of waiting connections on the socket to the maximum
+ number of concurrent sessions */
+ backlog = MAX(CNF_GetNtsServerProcesses(), 1) * CNF_GetNtsServerConnections();
+
+ if (!SCK_ListenOnSocket(sock_fd, backlog)) {
+ 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 if (next_protocol < 0) {
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, NULL, 0))
+ return 0;
+ } else if (aead_algorithm < 0) {
+ datum = htons(next_protocol);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, &datum, sizeof (datum)))
+ return 0;
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, NULL, 0))
+ 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_records = 0, aead_algorithm_records = 0;
+ int next_protocol_values = 0, aead_algorithm_values = 0;
+ int next_protocol = -1, aead_algorithm = -1, error = -1;
+ int i, critical, type, length;
+ 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 < 0) {
+ 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;
+ }
+
+ next_protocol_records++;
+
+ for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) {
+ next_protocol_values++;
+ 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;
+ }
+
+ aead_algorithm_records++;
+
+ for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) {
+ aead_algorithm_values++;
+ /* Use the first supported algorithm */
+ if (aead_algorithm < 0 && SIV_GetKeyLength(ntohs(data[i])) > 0)
+ aead_algorithm = ntohs(data[i]);
+ }
+ 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 (error < 0) {
+ if (next_protocol_records != 1 || next_protocol_values < 1 ||
+ (next_protocol == NKE_NEXT_PROTOCOL_NTPV4 &&
+ (aead_algorithm_records != 1 || aead_algorithm_values < 1)))
+ 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)
+{
+ SIV_Algorithm algorithm;
+ ServerKey *key;
+ int key_length;
+
+ if (index < 0 || index >= MAX_SERVER_KEYS)
+ assert(0);
+
+ /* Prefer AES-128-GCM-SIV if available. Note that if older keys loaded
+ from ntsdumpdir use a different algorithm, responding to NTP requests
+ with cookies encrypted with those keys will not work if the new algorithm
+ produces longer cookies (i.e. response would be longer than request).
+ Switching from AES-SIV-CMAC-256 to AES-128-GCM-SIV is ok. */
+ algorithm = SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ?
+ AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256;
+
+ key = &server_keys[index];
+
+ key_length = SIV_GetKeyLength(algorithm);
+ if (key_length > sizeof (key->key))
+ assert(0);
+
+ UTI_GetRandomBytesUrandom(key->key, key_length);
+ memset(key->key + key_length, 0, sizeof (key->key) - key_length);
+ UTI_GetRandomBytes(&key->id, sizeof (key->id));
+
+ /* Encode the index in the lowest bits of the ID */
+ key->id &= -1U << KEY_ID_INDEX_BITS;
+ key->id |= index;
+
+ update_key_siv(key, algorithm);
+
+ DEBUG_LOG("Generated key %08"PRIX32" (%d)", key->id, (int)key->siv_algorithm);
+
+ 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;
+
+ last_key_age = SCH_GetLastEventMonoTime() - last_server_key_ts;
+
+ if (fprintf(f, "%s%.1f\n", DUMP_IDENTIFIER, 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;
+ key_length = SIV_GetKeyLength(server_keys[index].siv_algorithm);
+
+ if (key_length > sizeof (server_keys[index].key) ||
+ !UTI_BytesToHex(server_keys[index].key, key_length, buf, sizeof (buf)) ||
+ fprintf(f, "%08"PRIX32" %s %d\n", server_keys[index].id, buf,
+ (int)server_keys[index].siv_algorithm) < 0)
+ goto error;
+ }
+
+ fclose(f);
+
+ /* Rename the temporary file, or remove it if that fails */
+ if (!UTI_RenameTempFile(dump_dir, DUMP_FILENAME, ".tmp", NULL)) {
+ if (!UTI_RemoveFile(dump_dir, DUMP_FILENAME, ".tmp"))
+ ;
+ }
+
+ return;
+
+error:
+ LOG(LOGS_ERR, "Could not %s %s", "save", "server NTS keys");
+ fclose(f);
+
+ if (!UTI_RemoveFile(dump_dir, DUMP_FILENAME, NULL))
+ ;
+}
+
+/* ================================================== */
+
+#define MAX_WORDS 3
+
+static int
+load_keys(void)
+{
+ int i, index, key_length, algorithm = 0, old_ver;
+ char *dump_dir, line[1024], *words[MAX_WORDS];
+ ServerKey new_keys[MAX_SERVER_KEYS];
+ double key_age;
+ FILE *f;
+
+ dump_dir = CNF_GetNtsDumpDir();
+ if (!dump_dir)
+ return 0;
+
+ f = UTI_OpenFile(dump_dir, DUMP_FILENAME, NULL, 'r', 0);
+ if (!f)
+ return 0;
+
+ if (!fgets(line, sizeof (line), f) ||
+ (strcmp(line, DUMP_IDENTIFIER) != 0 && strcmp(line, OLD_DUMP_IDENTIFIER) != 0))
+ goto error;
+
+ old_ver = strcmp(line, DUMP_IDENTIFIER) != 0;
+
+ if (!fgets(line, sizeof (line), f) ||
+ UTI_SplitString(line, words, MAX_WORDS) != (old_ver ? 2 : 1) ||
+ (old_ver && sscanf(words[0], "%d", &algorithm) != 1) ||
+ sscanf(words[old_ver ? 1 : 0], "%lf", &key_age) != 1)
+ goto error;
+
+ for (i = 0; i < MAX_SERVER_KEYS && fgets(line, sizeof (line), f); i++) {
+ if (UTI_SplitString(line, words, MAX_WORDS) != (old_ver ? 2 : 3) ||
+ sscanf(words[0], "%"PRIX32, &new_keys[i].id) != 1 ||
+ (!old_ver && sscanf(words[2], "%d", &algorithm) != 1))
+ goto error;
+
+ new_keys[i].siv_algorithm = algorithm;
+ key_length = SIV_GetKeyLength(algorithm);
+
+ if ((i > 0 && (new_keys[i].id - new_keys[i - 1].id) % MAX_SERVER_KEYS != 1) ||
+ key_length <= 0 ||
+ UTI_HexToBytes(words[1], new_keys[i].key, sizeof (new_keys[i].key)) != key_length)
+ goto error;
+ memset(new_keys[i].key + key_length, 0, sizeof (new_keys[i].key) - key_length);
+ }
+
+ if (i < MAX_SERVER_KEYS)
+ goto error;
+
+ for (i = 0; i < MAX_SERVER_KEYS; i++) {
+ index = new_keys[i].id % MAX_SERVER_KEYS;
+ server_keys[index].id = new_keys[i].id;
+ memcpy(server_keys[index].key, new_keys[i].key, sizeof (server_keys[index].key));
+
+ update_key_siv(&server_keys[index], new_keys[i].siv_algorithm);
+
+ DEBUG_LOG("Loaded key %08"PRIX32" (%d)",
+ server_keys[index].id, (int)server_keys[index].siv_algorithm);
+ }
+
+ current_server_key = (index + MAX_SERVER_KEYS - FUTURE_KEYS) % MAX_SERVER_KEYS;
+ last_server_key_ts = SCH_GetLastEventMonoTime() - MAX(key_age, 0.0);
+
+ fclose(f);
+
+ LOG(LOGS_INFO, "Loaded %s", "server NTS keys");
+ return 1;
+
+error:
+ LOG(LOGS_ERR, "Could not %s %s", "load", "server NTS keys");
+ fclose(f);
+
+ return 0;
+}
+
+/* ================================================== */
+
+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
+run_helper(uid_t uid, gid_t gid, int scfilter_level)
+{
+ LOG_Severity log_severity;
+
+ /* Finish minimal initialisation and run using the scheduler loop
+ similarly to the main process */
+
+ DEBUG_LOG("Helper started");
+
+ SCK_CloseReusableSockets();
+
+ /* Suppress a log message about disabled clock control */
+ log_severity = LOG_GetMinSeverity();
+ LOG_SetMinSeverity(LOGS_ERR);
+
+ SYS_Initialise(0);
+ LOG_SetMinSeverity(log_severity);
+
+ if (!geteuid() && (uid || gid))
+ SYS_DropRoot(uid, gid, SYS_NTSKE_HELPER);
+
+ NKS_Initialise();
+
+ UTI_SetQuitSignalsHandler(helper_signal, 1);
+ if (scfilter_level != 0)
+ SYS_EnableSystemCallFilter(scfilter_level, SYS_NTSKE_HELPER);
+
+ SCH_MainLoop();
+
+ DEBUG_LOG("Helper exiting");
+
+ NKS_Finalise();
+ SCK_Finalise();
+ SYS_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ PRV_Finalise();
+ CNF_Finalise();
+ LOG_Finalise();
+
+ UTI_ResetGetRandomFunctions();
+
+ exit(0);
+}
+
+/* ================================================== */
+
+void
+NKS_PreInitialise(uid_t uid, gid_t gid, int scfilter_level)
+{
+ int i, processes, sock_fd1, sock_fd2;
+ const char **certs, **keys;
+ char prefix[16];
+ pid_t pid;
+
+ helper_sock_fd = INVALID_SOCK_FD;
+ is_helper = 0;
+
+ if (CNF_GetNtsServerCertAndKeyFiles(&certs, &keys) <= 0)
+ return;
+
+ processes = CNF_GetNtsServerProcesses();
+ if (processes <= 0)
+ return;
+
+ /* Start helper processes to perform (computationally expensive) NTS-KE
+ sessions with clients on sockets forwarded from the main process */
+
+ sock_fd1 = SCK_OpenUnixSocketPair(0, &sock_fd2);
+ if (sock_fd1 < 0)
+ LOG_FATAL("Could not open socket pair");
+
+ for (i = 0; i < processes; i++) {
+ pid = fork();
+
+ if (pid < 0)
+ LOG_FATAL("fork() failed : %s", strerror(errno));
+
+ if (pid > 0)
+ continue;
+
+ is_helper = 1;
+
+ UTI_ResetGetRandomFunctions();
+
+ snprintf(prefix, sizeof (prefix), "nks#%d:", i + 1);
+ LOG_SetDebugPrefix(prefix);
+ LOG_CloseParentFd();
+
+ SCK_CloseSocket(sock_fd1);
+ SCH_AddFileHandler(sock_fd2, SCH_FILE_INPUT, handle_helper_request, NULL);
+
+ run_helper(uid, gid, scfilter_level);
+ }
+
+ SCK_CloseSocket(sock_fd2);
+ helper_sock_fd = sock_fd1;
+}
+
+/* ================================================== */
+
+void
+NKS_Initialise(void)
+{
+ const char **certs, **keys;
+ int i, n_certs_keys;
+ double key_delay;
+
+ server_sock_fd4 = INVALID_SOCK_FD;
+ server_sock_fd6 = INVALID_SOCK_FD;
+
+ n_certs_keys = CNF_GetNtsServerCertAndKeyFiles(&certs, &keys);
+ if (n_certs_keys <= 0)
+ return;
+
+ if (helper_sock_fd == INVALID_SOCK_FD) {
+ server_credentials = NKSN_CreateServerCertCredentials(certs, keys, n_certs_keys);
+ if (!server_credentials)
+ return;
+ } else {
+ server_credentials = NULL;
+ }
+
+ sessions = ARR_CreateInstance(sizeof (NKSN_Instance));
+ for (i = 0; i < CNF_GetNtsServerConnections(); i++)
+ *(NKSN_Instance *)ARR_GetNewElement(sessions) = NULL;
+
+ /* Generate random keys, even if they will be replaced by reloaded keys,
+ or unused (in the helper) */
+ for (i = 0; i < MAX_SERVER_KEYS; i++) {
+ server_keys[i].siv = NULL;
+ generate_key(i);
+ }
+
+ current_server_key = MAX_SERVER_KEYS - 1;
+
+ if (!is_helper) {
+ server_sock_fd4 = open_socket(IPADDR_INET4);
+ server_sock_fd6 = open_socket(IPADDR_INET6);
+
+ key_rotation_interval = MAX(CNF_GetNtsRotate(), 0);
+
+ /* Reload saved keys, or save the new keys */
+ if (!load_keys())
+ save_keys();
+
+ 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);
+ }
+
+ /* Warn if keys are not saved, which can cause a flood of requests
+ after server restart */
+ if (!CNF_GetNtsDumpDir())
+ LOG(LOGS_WARN, "No ntsdumpdir to save server keys");
+ }
+
+ initialised = 1;
+}
+
+/* ================================================== */
+
+void
+NKS_Finalise(void)
+{
+ int i;
+
+ if (!initialised)
+ return;
+
+ if (helper_sock_fd != INVALID_SOCK_FD) {
+ /* Send the helpers a request to exit */
+ 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);
+
+ if (!is_helper)
+ save_keys();
+
+ for (i = 0; i < MAX_SERVER_KEYS; i++)
+ 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);
+
+ if (server_credentials)
+ 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 *nonce, 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 AEAD ID is not encoded in the cookie. It is implied from the key
+ length (as long as only algorithms with different key lengths are
+ supported). */
+
+ if (context->c2s.length < 0 || context->c2s.length > NKE_MAX_KEY_LENGTH ||
+ context->s2c.length != context->c2s.length) {
+ DEBUG_LOG("Invalid key length");
+ return 0;
+ }
+
+ key = &server_keys[current_server_key];
+
+ header = (ServerCookieHeader *)cookie->cookie;
+
+ header->key_id = htonl(key->id);
+
+ nonce = cookie->cookie + sizeof (*header);
+ if (key->nonce_length > sizeof (cookie->cookie) - sizeof (*header))
+ assert(0);
+ UTI_GetRandomBytes(nonce, key->nonce_length);
+
+ 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) + key->nonce_length + plaintext_length + tag_length;
+ assert(cookie->length <= sizeof (cookie->cookie));
+ ciphertext = cookie->cookie + sizeof (*header) + key->nonce_length;
+
+ if (!SIV_Encrypt(key->siv, nonce, key->nonce_length,
+ "", 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 *nonce, 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 <= (int)sizeof (*header)) {
+ DEBUG_LOG("Invalid cookie length");
+ return 0;
+ }
+
+ header = (ServerCookieHeader *)cookie->cookie;
+
+ 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 (cookie->length <= (int)sizeof (*header) + key->nonce_length + tag_length) {
+ DEBUG_LOG("Invalid cookie length");
+ return 0;
+ }
+
+ nonce = cookie->cookie + sizeof (*header);
+ ciphertext = cookie->cookie + sizeof (*header) + key->nonce_length;
+ ciphertext_length = cookie->length - sizeof (*header) - key->nonce_length;
+ 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, nonce, key->nonce_length,
+ "", 0,
+ ciphertext, ciphertext_length,
+ plaintext, plaintext_length)) {
+ DEBUG_LOG("Could not decrypt cookie");
+ return 0;
+ }
+
+ /* Select a supported algorithm corresponding to the key length, avoiding
+ potentially slow SIV_GetKeyLength() */
+ switch (plaintext_length / 2) {
+ case 16:
+ context->algorithm = AEAD_AES_128_GCM_SIV;
+ break;
+ case 32:
+ context->algorithm = AEAD_AES_SIV_CMAC_256;
+ break;
+ default:
+ DEBUG_LOG("Unknown key length");
+ return 0;
+ }
+
+ 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;
+}
diff --git a/nts_ke_server.h b/nts_ke_server.h
new file mode 100644
index 0000000..4d8a92a
--- /dev/null
+++ b/nts_ke_server.h
@@ -0,0 +1,49 @@
+/*
+ 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_PreInitialise(uid_t uid, gid_t gid, int scfilter_level);
+extern void NKS_Initialise(void);
+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
diff --git a/nts_ke_session.c b/nts_ke_session.c
new file mode 100644
index 0000000..2ae1e91
--- /dev/null
+++ b/nts_ke_session.c
@@ -0,0 +1,929 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020-2021
+ *
+ * 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;
+};
+
+/* ================================================== */
+
+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;
+
+ assert(message->length <= sizeof (message->data));
+
+ 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;
+ assert(blen >= 0 && rlen > 0);
+
+ 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 | GNUTLS_NO_TICKETS |
+ (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) {
+ assert(server_name);
+
+ if (!UTI_IsStringIP(server_name)) {
+ 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
+check_alpn(NKSN_Instance inst)
+{
+ gnutls_datum_t alpn;
+
+ if (gnutls_alpn_get_selected_protocol(inst->tls_session, &alpn) < 0 ||
+ alpn.size != sizeof (NKE_ALPN_NAME) - 1 ||
+ memcmp(alpn.data, NKE_ALPN_NAME, sizeof (NKE_ALPN_NAME) - 1) != 0)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+set_input_output(NKSN_Instance inst, int output)
+{
+ SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_INPUT, !output);
+ SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT, output);
+}
+
+/* ================================================== */
+
+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);
+ }
+
+ set_input_output(inst, 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;
+
+ /* Get the socket error */
+ if (!SCK_GetIntOption(inst->sock_fd, SOL_SOCKET, SO_ERROR, &r))
+ r = EINVAL;
+
+ if (r != 0) {
+ 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)) {
+ gnutls_datum_t cert_error;
+
+ /* Get a description of verification errors */
+ if (r != GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR ||
+ gnutls_certificate_verification_status_print(
+ gnutls_session_get_verify_cert_status(inst->tls_session),
+ gnutls_certificate_type_get(inst->tls_session), &cert_error, 0) < 0)
+ cert_error.data = NULL;
+
+ LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
+ "TLS handshake with %s failed : %s%s%s", inst->label, gnutls_strerror(r),
+ cert_error.data ? " " : "", cert_error.data ? (const char *)cert_error.data : "");
+
+ if (cert_error.data)
+ gnutls_free(cert_error.data);
+
+ 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 */
+ set_input_output(inst, 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);
+ assert(message->length <= sizeof (message->data) && message->length > message->sent);
+
+ 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);
+
+ /* Return success to process the received message */
+ return 1;
+
+ 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 */
+ set_input_output(inst, 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 0;
+ }
+}
+
+/* ================================================== */
+
+static void
+read_write_socket(int fd, int event, void *arg)
+{
+ NKSN_Instance inst = arg;
+
+ if (!handle_event(inst, event))
+ return;
+
+ /* A valid message was received. Call the handler to process the message,
+ and prepare a response if it is a server. */
+
+ 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 int
+init_gnutls(void)
+{
+ int r;
+
+ if (gnutls_initialised)
+ return 1;
+
+ r = gnutls_global_init();
+ if (r < 0)
+ LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r));
+
+ /* Prepare a priority cache for server and client NTS-KE sessions
+ (the 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:-VERS-DTLS-ALL",
+ NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND);
+ if (r < 0) {
+ LOG(LOGS_ERR, "Could not initialise %s : %s",
+ "priority cache for TLS", gnutls_strerror(r));
+ gnutls_global_deinit();
+ return 0;
+ }
+
+ /* Use our clock instead of the system clock in certificate verification */
+ gnutls_global_set_time_function(get_time);
+
+ gnutls_initialised = 1;
+ DEBUG_LOG("Initialised");
+
+ LCL_AddParameterChangeHandler(handle_step, NULL);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+deinit_gnutls(void)
+{
+ if (!gnutls_initialised || credentials_counter > 0)
+ return;
+
+ LCL_RemoveParameterChangeHandler(handle_step, NULL);
+
+ gnutls_priority_deinit(priority_cache);
+ gnutls_global_deinit();
+ gnutls_initialised = 0;
+ DEBUG_LOG("Deinitialised");
+}
+
+/* ================================================== */
+
+static NKSN_Credentials
+create_credentials(const char **certs, const char **keys, int n_certs_keys,
+ const char **trusted_certs, uint32_t *trusted_certs_ids,
+ int n_trusted_certs, uint32_t trusted_cert_set)
+{
+ gnutls_certificate_credentials_t credentials = NULL;
+ int i, r;
+
+ if (!init_gnutls())
+ return NULL;
+
+ r = gnutls_certificate_allocate_credentials(&credentials);
+ if (r < 0)
+ goto error;
+
+ if (certs && keys) {
+ if (trusted_certs || trusted_certs_ids)
+ assert(0);
+
+ for (i = 0; i < n_certs_keys; i++) {
+ if (!UTI_CheckFilePermissions(keys[i], 0771))
+ ;
+ r = gnutls_certificate_set_x509_key_file(credentials, certs[i], keys[i],
+ GNUTLS_X509_FMT_PEM);
+ if (r < 0)
+ goto error;
+ }
+ } else {
+ if (certs || keys || n_certs_keys > 0)
+ assert(0);
+
+ if (trusted_cert_set == 0 && !CNF_GetNoSystemCert()) {
+ r = gnutls_certificate_set_x509_system_trust(credentials);
+ if (r < 0)
+ goto error;
+ }
+
+ if (trusted_certs && trusted_certs_ids) {
+ for (i = 0; i < n_trusted_certs; i++) {
+ struct stat buf;
+
+ if (trusted_certs_ids[i] != trusted_cert_set)
+ continue;
+
+ if (stat(trusted_certs[i], &buf) == 0 && S_ISDIR(buf.st_mode))
+ r = gnutls_certificate_set_x509_trust_dir(credentials, trusted_certs[i],
+ GNUTLS_X509_FMT_PEM);
+ else
+ r = gnutls_certificate_set_x509_trust_file(credentials, trusted_certs[i],
+ GNUTLS_X509_FMT_PEM);
+ if (r < 0)
+ goto error;
+
+ DEBUG_LOG("Added %d trusted certs from %s", r, trusted_certs[i]);
+ }
+ }
+ }
+
+ credentials_counter++;
+
+ return (NKSN_Credentials)credentials;
+
+error:
+ LOG(LOGS_ERR, "Could not set credentials : %s", gnutls_strerror(r));
+ if (credentials)
+ gnutls_certificate_free_credentials(credentials);
+ deinit_gnutls();
+ return NULL;
+}
+
+/* ================================================== */
+
+NKSN_Credentials
+NKSN_CreateServerCertCredentials(const char **certs, const char **keys, int n_certs_keys)
+{
+ return create_credentials(certs, keys, n_certs_keys, NULL, NULL, 0, 0);
+}
+
+/* ================================================== */
+
+NKSN_Credentials
+NKSN_CreateClientCertCredentials(const char **certs, uint32_t *ids,
+ int n_certs_ids, uint32_t trusted_cert_set)
+{
+ return create_credentials(NULL, NULL, 0, certs, ids, n_certs_ids, trusted_cert_set);
+}
+
+/* ================================================== */
+
+void
+NKSN_DestroyCertCredentials(NKSN_Credentials credentials)
+{
+ gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)credentials);
+ credentials_counter--;
+ 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 a NULL argument 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,
+ NKSN_Credentials credentials, double timeout)
+{
+ assert(inst->state == KE_STOPPED);
+
+ inst->tls_session = create_tls_session(inst->server, sock_fd, inst->server_name,
+ (gnutls_certificate_credentials_t)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;
+
+ 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);
+
+ /* Terminate the message */
+ 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 (body_length)
+ *body_length = 0;
+
+ if (!get_record(&inst->message, critical, &type2, body_length, body, buffer_length))
+ return 0;
+
+ /* Hide the end-of-message record */
+ 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)
+{
+ int length = SIV_GetKeyLength(siv);
+
+ if (length <= 0 || length > sizeof (c2s->key) || length > sizeof (s2c->key)) {
+ DEBUG_LOG("Invalid algorithm");
+ return 0;
+ }
+
+ if (gnutls_prf_rfc5705(inst->tls_session,
+ sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
+ sizeof (NKE_EXPORTER_CONTEXT_C2S) - 1, NKE_EXPORTER_CONTEXT_C2S,
+ length, (char *)c2s->key) < 0 ||
+ gnutls_prf_rfc5705(inst->tls_session,
+ sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
+ sizeof (NKE_EXPORTER_CONTEXT_S2C) - 1, NKE_EXPORTER_CONTEXT_S2C,
+ length, (char *)s2c->key) < 0) {
+ DEBUG_LOG("Could not export key");
+ return 0;
+ }
+
+ c2s->length = length;
+ s2c->length = length;
+
+ 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;
+}
diff --git a/nts_ke_session.h b/nts_ke_session.h
new file mode 100644
index 0000000..2735e04
--- /dev/null
+++ b/nts_ke_session.h
@@ -0,0 +1,93 @@
+/*
+ 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_Credentials_Record *NKSN_Credentials;
+
+typedef struct NKSN_Instance_Record *NKSN_Instance;
+
+/* Handler for received NTS-KE messages. A zero return code stops
+ the session. */
+typedef int (*NKSN_MessageHandler)(void *arg);
+
+/* Get server or client credentials using a server certificate and key,
+ or certificates of trusted CAs. The credentials may be shared between
+ different clients or servers. */
+extern NKSN_Credentials NKSN_CreateServerCertCredentials(const char **certs, const char **keys,
+ int n_certs_keys);
+extern NKSN_Credentials NKSN_CreateClientCertCredentials(const char **certs, uint32_t *ids,
+ int n_certs_ids,
+ uint32_t trusted_cert_set);
+
+/* Destroy the credentials */
+extern void NKSN_DestroyCertCredentials(NKSN_Credentials 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,
+ NKSN_Credentials 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
diff --git a/nts_ntp.h b/nts_ntp.h
new file mode 100644
index 0000000..e39def2
--- /dev/null
+++ b/nts_ntp.h
@@ -0,0 +1,36 @@
+/*
+ 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_KOD_NTS_NAK 0x4e54534e
+
+#define NTS_MIN_UNIQ_ID_LENGTH 32
+#define NTS_MIN_UNPADDED_NONCE_LENGTH 16
+#define NTS_MAX_COOKIES 8
+
+#endif
diff --git a/nts_ntp_auth.c b/nts_ntp_auth.c
new file mode 100644
index 0000000..b92c406
--- /dev/null
+++ b/nts_ntp_auth.c
@@ -0,0 +1,187 @@
+/*
+ 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 max_nonce_length,
+ const unsigned char *plaintext, int plaintext_length,
+ int min_ef_length)
+{
+ int auth_length, ciphertext_length, assoc_length, nonce_length, max_siv_nonce_length;
+ int nonce_padding, ciphertext_padding, additional_padding;
+ unsigned char *ciphertext, *body;
+ struct AuthHeader *header;
+
+ assert(sizeof (*header) == 4);
+
+ if (max_nonce_length <= 0 || plaintext_length < 0) {
+ DEBUG_LOG("Invalid nonce/plaintext length");
+ return 0;
+ }
+
+ assoc_length = info->length;
+ max_siv_nonce_length = SIV_GetMaxNonceLength(siv);
+ nonce_length = MIN(max_nonce_length, max_siv_nonce_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(MIN(NTS_MIN_UNPADDED_NONCE_LENGTH, max_siv_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;
+
+ if ((unsigned char *)header + auth_length !=
+ ciphertext + ciphertext_length + ciphertext_padding + additional_padding)
+ assert(0);
+
+ 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");
+ info->length = assoc_length;
+ info->ext_fields--;
+ 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)
+{
+ int siv_tag_length, max_siv_nonce_length, nonce_length, ciphertext_length;
+ unsigned char *nonce, *ciphertext;
+ int ef_type, ef_body_length;
+ void *ef_body;
+ struct AuthHeader *header;
+
+ if (buffer_length < 0)
+ return 0;
+
+ 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 || ef_body_length < sizeof (*header))
+ 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 = nonce + get_padded_length(nonce_length);
+
+ max_siv_nonce_length = SIV_GetMaxNonceLength(siv);
+ 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 (sizeof (*header) + MIN(NTS_MIN_UNPADDED_NONCE_LENGTH, max_siv_nonce_length) +
+ get_padded_length(ciphertext_length) > ef_body_length) {
+ DEBUG_LOG("Missing padding");
+ return 0;
+ }
+
+ *plaintext_length = ciphertext_length - siv_tag_length;
+ assert(*plaintext_length >= 0);
+
+ if (!SIV_Decrypt(siv, nonce, nonce_length, packet, ef_start,
+ ciphertext, ciphertext_length, plaintext, *plaintext_length)) {
+ DEBUG_LOG("SIV decrypt failed");
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/nts_ntp_auth.h b/nts_ntp_auth.h
new file mode 100644
index 0000000..19d04bf
--- /dev/null
+++ b/nts_ntp_auth.h
@@ -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 max_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
diff --git a/nts_ntp_client.c b/nts_ntp_client.c
new file mode 100644
index 0000000..2f4b728
--- /dev/null
+++ b/nts_ntp_client.c
@@ -0,0 +1,717 @@
+/*
+ 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"
+
+/* Maximum length of all cookies to avoid IP fragmentation */
+#define MAX_TOTAL_COOKIE_LENGTH (8 * 108)
+
+/* Retry interval for NTS-KE start (which doesn't generate network traffic) */
+#define RETRY_INTERVAL_KE_START 2.0
+
+/* Magic string of files containing keys and cookies */
+#define DUMP_IDENTIFIER "NNC0\n"
+
+struct NNC_Instance_Record {
+ /* Address of NTS-KE server */
+ IPSockAddr nts_address;
+ /* Hostname or IP address for certificate verification */
+ char *name;
+ /* ID of trusted certificates */
+ uint32_t cert_set;
+ /* Configured NTP port */
+ uint16_t default_ntp_port;
+ /* Address of NTP server (can be negotiated in NTS-KE) */
+ IPSockAddr ntp_address;
+
+ NKC_Instance nke;
+ SIV_Instance siv;
+
+ int nke_attempts;
+ double next_nke_attempt;
+ double last_nke_success;
+
+ NKE_Context context;
+ unsigned int context_id;
+ NKE_Cookie cookies[NTS_MAX_COOKIES];
+ int num_cookies;
+ int cookie_index;
+ int auth_ready;
+ 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)
+{
+ if (inst->nke)
+ NKC_DestroyInstance(inst->nke);
+ inst->nke = NULL;
+ if (inst->siv)
+ SIV_DestroyInstance(inst->siv);
+ inst->siv = NULL;
+
+ inst->nke_attempts = 0;
+ inst->next_nke_attempt = 0.0;
+ inst->last_nke_success = 0.0;
+
+ memset(&inst->context, 0, sizeof (inst->context));
+ inst->context_id = 0;
+ memset(inst->cookies, 0, sizeof (inst->cookies));
+ inst->num_cookies = 0;
+ inst->cookie_index = 0;
+ inst->auth_ready = 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, uint32_t cert_set, uint16_t ntp_port)
+{
+ NNC_Instance inst;
+
+ inst = MallocNew(struct NNC_Instance_Record);
+
+ inst->nts_address = *nts_address;
+ inst->name = Strdup(name);
+ inst->cert_set = cert_set;
+ inst->default_ntp_port = ntp_port;
+ inst->ntp_address.ip_addr = nts_address->ip_addr;
+ inst->ntp_address.port = ntp_port;
+ inst->siv = NULL;
+ inst->nke = NULL;
+
+ reset_instance(inst);
+
+ /* Try to reload saved keys and cookies */
+ load_cookies(inst);
+
+ return inst;
+}
+
+/* ================================================== */
+
+void
+NNC_DestroyInstance(NNC_Instance inst)
+{
+ save_cookies(inst);
+
+ reset_instance(inst);
+
+ Free(inst->name);
+ Free(inst);
+}
+
+/* ================================================== */
+
+static int
+check_cookies(NNC_Instance inst)
+{
+ /* Force a new NTS-KE session if a NAK was received without a valid response,
+ or the keys encrypting the cookies need to be refreshed */
+ if (inst->num_cookies > 0 &&
+ ((inst->nak_response && !inst->ok_response) ||
+ SCH_GetLastEventMonoTime() - inst->last_nke_success > CNF_GetNtsRefresh())) {
+ inst->num_cookies = 0;
+ DEBUG_LOG("Dropped cookies");
+ }
+
+ 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 = inst->nts_address.ip_addr;
+ if (new_address.port == 0)
+ new_address.port = inst->default_ntp_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;
+ }
+
+ inst->ntp_address = new_address;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+update_next_nke_attempt(NNC_Instance inst, int failed_start, double now)
+{
+ int factor, interval;
+
+ if (failed_start) {
+ inst->next_nke_attempt = now + RETRY_INTERVAL_KE_START;
+ return;
+ }
+
+ 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;
+ int got_data, failed_start = 0;
+ double now;
+
+ assert(inst->num_cookies == 0);
+
+ now = SCH_GetLastEventMonoTime();
+
+ /* Create and start a new NTS-KE session if not already present */
+ 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;
+ }
+
+ inst->nke = NKC_CreateInstance(&inst->nts_address, inst->name, inst->cert_set);
+
+ inst->nke_attempts++;
+
+ if (!NKC_Start(inst->nke))
+ failed_start = 1;
+ }
+
+ update_next_nke_attempt(inst, failed_start, now);
+
+ /* Wait until the session stops */
+ if (NKC_IsActive(inst->nke))
+ return 0;
+
+ assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == NTS_MAX_COOKIES);
+
+ /* Get the new keys, cookies and NTP address if the session was successful */
+ got_data = NKC_GetNtsData(inst->nke, &inst->context,
+ 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;
+
+ inst->context_id++;
+
+ /* Force a new session if the NTP address is used by another source, with
+ an expectation that it will eventually get a non-conflicting address */
+ if (!set_ntp_address(inst, &ntp_address)) {
+ inst->num_cookies = 0;
+ return 0;
+ }
+
+ inst->last_nke_success = now;
+ inst->cookie_index = 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NNC_PrepareForAuth(NNC_Instance inst)
+{
+ inst->auth_ready = 0;
+
+ /* Prepare data for the next request and invalidate any responses to the
+ previous request */
+ UTI_GetRandomBytes(inst->uniq_id, sizeof (inst->uniq_id));
+ UTI_GetRandomBytes(inst->nonce, sizeof (inst->nonce));
+
+ /* Get new cookies if there are not any, or they are no longer usable */
+ if (!check_cookies(inst)) {
+ if (!get_cookies(inst))
+ return 0;
+ }
+
+ inst->nak_response = 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;
+ }
+
+ inst->auth_ready = 1;
+
+ 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->auth_ready)
+ return 0;
+
+ inst->auth_ready = 0;
+
+ if (inst->num_cookies <= 0 || !inst->siv)
+ return 0;
+
+ if (info->mode != MODE_CLIENT)
+ return 0;
+
+ cookie = &inst->cookies[inst->cookie_index];
+ inst->num_cookies--;
+ inst->cookie_index = (inst->cookie_index + 1) % NTS_MAX_COOKIES;
+
+ req_cookies = MIN(NTS_MAX_COOKIES - inst->num_cookies,
+ 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->ok_response = 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+parse_encrypted_efs(NNC_Instance inst, unsigned char *plaintext, int length)
+{
+ int ef_length, parsed;
+
+ for (parsed = 0; parsed < length; parsed += ef_length) {
+ if (!NEF_ParseSingleField(plaintext, length, parsed, &ef_length, NULL, NULL, NULL)) {
+ DEBUG_LOG("Could not parse encrypted EF");
+ return 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))
+ return 0;
+
+ 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;
+ assert(index >= 0 && index < NTS_MAX_COOKIES);
+ assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == 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 at most one response per request */
+ if (inst->ok_response || inst->auth_ready)
+ 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))
+ /* This is not expected as the packet already passed parsing */
+ return 0;
+
+ 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;
+
+ if (!parse_encrypted_efs(inst, 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);
+
+ inst->nts_address.ip_addr = *address;
+ inst->ntp_address.ip_addr = *address;
+
+ reset_instance(inst);
+
+ DEBUG_LOG("NTS reset");
+
+ load_cookies(inst);
+}
+
+/* ================================================== */
+
+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%s\n%.1f\n%s %d\n%u %d ",
+ DUMP_IDENTIFIER, inst->name, context_time,
+ UTI_IPToString(&inst->ntp_address.ip_addr), inst->ntp_address.port,
+ inst->context_id, (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 4
+
+static void
+load_cookies(NNC_Instance inst)
+{
+ char line[2 * NKE_MAX_COOKIE_LENGTH + 2], *dump_dir, *filename, *words[MAX_WORDS];
+ unsigned int context_id;
+ 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 ||
+ strcmp(words[0], inst->name) != 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) != 4 ||
+ sscanf(words[0], "%u", &context_id) != 1 || sscanf(words[1], "%d", &algorithm) != 1)
+ goto error;
+
+ inst->context.algorithm = algorithm;
+ inst->context.s2c.length = UTI_HexToBytes(words[2], inst->context.s2c.key,
+ sizeof (inst->context.s2c.key));
+ inst->context.c2s.length = UTI_HexToBytes(words[3], inst->context.c2s.key,
+ sizeof (inst->context.c2s.key));
+
+ if (inst->context.s2c.length != SIV_GetKeyLength(algorithm) ||
+ inst->context.s2c.length <= 0 ||
+ 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();
+ inst->context_id = context_id;
+
+ fclose(f);
+
+ 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);
+}
+
+/* ================================================== */
+
+void
+NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report)
+{
+ report->key_id = inst->context_id;
+ report->key_type = inst->context.algorithm;
+ report->key_length = 8 * inst->context.s2c.length;
+ report->ke_attempts = inst->nke_attempts;
+ if (report->key_length > 0)
+ report->last_ke_ago = SCH_GetLastEventMonoTime() - inst->last_nke_success;
+ else
+ report->last_ke_ago = -1;
+ report->cookies = inst->num_cookies;
+ report->cookie_length = inst->num_cookies > 0 ? inst->cookies[inst->cookie_index].length : 0;
+ report->nak = inst->nak_response;
+}
diff --git a/nts_ntp_client.h b/nts_ntp_client.h
new file mode 100644
index 0000000..2c314cc
--- /dev/null
+++ b/nts_ntp_client.h
@@ -0,0 +1,51 @@
+/*
+ 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"
+#include "reports.h"
+
+typedef struct NNC_Instance_Record *NNC_Instance;
+
+extern NNC_Instance NNC_CreateInstance(IPSockAddr *nts_address, const char *name,
+ uint32_t cert_set, uint16_t ntp_port);
+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);
+
+extern void NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report);
+
+#endif
diff --git a/nts_ntp_server.c b/nts_ntp_server.c
new file mode 100644
index 0000000..be69a2b
--- /dev/null
+++ b/nts_ntp_server.c
@@ -0,0 +1,309 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020, 2022
+ *
+ * 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 MAX_SERVER_SIVS 2
+
+struct NtsServer {
+ SIV_Instance sivs[MAX_SERVER_SIVS];
+ SIV_Algorithm siv_algorithms[MAX_SERVER_SIVS];
+ unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH];
+ NKE_Cookie cookies[NTS_MAX_COOKIES];
+ int num_cookies;
+ int siv_index;
+ NTP_int64 req_tx;
+};
+
+/* The server instance handling all requests */
+struct NtsServer *server;
+
+/* ================================================== */
+
+void
+NNS_Initialise(void)
+{
+ const char **certs, **keys;
+ int i;
+
+ /* Create an NTS-NTP server instance only if NTS-KE server is enabled */
+ if (CNF_GetNtsServerCertAndKeyFiles(&certs, &keys) <= 0) {
+ server = NULL;
+ return;
+ }
+
+ server = Malloc(sizeof (struct NtsServer));
+
+ server->siv_algorithms[0] = AEAD_AES_SIV_CMAC_256;
+ server->siv_algorithms[1] = AEAD_AES_128_GCM_SIV;
+ assert(MAX_SERVER_SIVS == 2);
+
+ for (i = 0; i < 2; i++)
+ server->sivs[i] = SIV_CreateInstance(server->siv_algorithms[i]);
+
+ /* AES-SIV-CMAC-256 is required on servers */
+ if (!server->sivs[0])
+ LOG_FATAL("Missing AES-SIV-CMAC-256");
+}
+
+/* ================================================== */
+
+void
+NNS_Finalise(void)
+{
+ int i;
+
+ if (!server)
+ return;
+
+ for (i = 0; i < MAX_SERVER_SIVS; i++) {
+ if (server->sivs[i])
+ SIV_DestroyInstance(server->sivs[i]);
+ }
+ 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;
+ SIV_Instance siv;
+ void *ef_body;
+
+ *kod = 0;
+
+ if (!server)
+ return 0;
+
+ server->num_cookies = 0;
+ server->siv_index = -1;
+ 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))
+ /* This is not expected as the packet already passed NAU_ParsePacket() */
+ return 0;
+
+ 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)) {
+ DEBUG_LOG("Unexpected cookie/length");
+ 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:
+ if (parsed + ef_length != info->length) {
+ DEBUG_LOG("Auth not last EF");
+ return 0;
+ }
+
+ 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;
+ }
+
+ /* Find the SIV instance needed for authentication */
+ for (i = 0; i < MAX_SERVER_SIVS && context.algorithm != server->siv_algorithms[i]; i++)
+ ;
+ if (i == MAX_SERVER_SIVS || !server->sivs[i]) {
+ DEBUG_LOG("Unexpected SIV");
+ return 0;
+ }
+ server->siv_index = i;
+ siv = server->sivs[i];
+
+ if (!SIV_SetKey(siv, context.c2s.key, context.c2s.length)) {
+ DEBUG_LOG("Could not set C2S key");
+ return 0;
+ }
+
+ if (!NNA_DecryptAuthEF(packet, info, 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)) {
+ DEBUG_LOG("Could not parse encrypted EF");
+ return 0;
+ }
+
+ 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(siv, context.s2c.key, context.s2c.length)) {
+ DEBUG_LOG("Could not set S2C key");
+ return 0;
+ }
+
+ /* Prepare data for NNS_GenerateResponseAuth() to minimise the time spent
+ there (when the TX timestamp is already set) */
+
+ UTI_GetRandomBytes(server->nonce, sizeof (server->nonce));
+
+ assert(sizeof (server->cookies) / sizeof (server->cookies[0]) == NTS_MAX_COOKIES);
+ for (i = 0; i < NTS_MAX_COOKIES && i < requested_cookies; i++)
+ if (!NKS_GenerateCookie(&context, &server->cookies[i]))
+ return 0;
+
+ server->num_cookies = i;
+
+ 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 request from the last call
+ of NNS_CheckRequestAuth() */
+ 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))
+ /* This is not expected as the packet already passed parsing */
+ return 0;
+
+ 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 (server->siv_index < 0)
+ return 0;
+
+ /* Generate an authenticator field which will make the length
+ of the response equal to the length of the request */
+ if (!NNA_GenerateAuthEF(response, res_info, server->sivs[server->siv_index],
+ server->nonce, sizeof (server->nonce),
+ plaintext, plaintext_length,
+ req_info->length - res_info->length))
+ return 0;
+
+ return 1;
+}
diff --git a/nts_ntp_server.h b/nts_ntp_server.h
new file mode 100644
index 0000000..fea28f2
--- /dev/null
+++ b/nts_ntp_server.h
@@ -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
diff --git a/pktlength.c b/pktlength.c
new file mode 100644
index 0000000..d7ed272
--- /dev/null
+++ b/pktlength.c
@@ -0,0 +1,222 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ * Copyright (C) Miroslav Lichvar 2014-2016
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines to compute the expected length of a command or reply packet.
+ These operate on the RAW NETWORK packets, from the point of view of
+ integer endianness within the structures.
+
+ */
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "util.h"
+#include "pktlength.h"
+
+#define PADDING_LENGTH_(request_length, reply_length) \
+ (uint16_t)((request_length) < (reply_length) ? (reply_length) - (request_length) : 0)
+
+#define PADDING_LENGTH(request_data, reply_data) \
+ PADDING_LENGTH_(offsetof(CMD_Request, request_data), offsetof(CMD_Reply, reply_data))
+
+#define REQ_LENGTH_ENTRY(request_data_field, reply_data_field) \
+ { offsetof(CMD_Request, data.request_data_field.EOR), \
+ PADDING_LENGTH(data.request_data_field.EOR, data.reply_data_field.EOR) }
+
+#define RPY_LENGTH_ENTRY(reply_data_field) \
+ offsetof(CMD_Reply, data.reply_data_field.EOR)
+
+/* ================================================== */
+
+struct request_length {
+ uint16_t command;
+ uint16_t padding;
+};
+
+static const struct request_length request_lengths[] = {
+ REQ_LENGTH_ENTRY(null, null), /* NULL */
+ REQ_LENGTH_ENTRY(online, null), /* ONLINE */
+ REQ_LENGTH_ENTRY(offline, null), /* OFFLINE */
+ REQ_LENGTH_ENTRY(burst, null), /* BURST */
+ REQ_LENGTH_ENTRY(modify_minpoll, null), /* MODIFY_MINPOLL */
+ REQ_LENGTH_ENTRY(modify_maxpoll, null), /* MODIFY_MAXPOLL */
+ REQ_LENGTH_ENTRY(dump, null), /* DUMP */
+ REQ_LENGTH_ENTRY(modify_maxdelay, null), /* MODIFY_MAXDELAY */
+ REQ_LENGTH_ENTRY(modify_maxdelayratio, null), /* MODIFY_MAXDELAYRATIO */
+ REQ_LENGTH_ENTRY(modify_maxupdateskew, null), /* MODIFY_MAXUPDATESKEW */
+ REQ_LENGTH_ENTRY(logon, null), /* LOGON */
+ REQ_LENGTH_ENTRY(settime, manual_timestamp), /* SETTIME */
+ { 0, 0 }, /* LOCAL */
+ REQ_LENGTH_ENTRY(manual, null), /* MANUAL */
+ REQ_LENGTH_ENTRY(null, n_sources), /* N_SOURCES */
+ REQ_LENGTH_ENTRY(source_data, source_data), /* SOURCE_DATA */
+ REQ_LENGTH_ENTRY(null, null), /* REKEY */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* ALLOW */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* ALLOWALL */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* DENY */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* DENYALL */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* CMDALLOW */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* CMDALLOWALL */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* CMDDENY */
+ REQ_LENGTH_ENTRY(allow_deny, null), /* CMDDENYALL */
+ REQ_LENGTH_ENTRY(ac_check, null), /* ACCHECK */
+ REQ_LENGTH_ENTRY(ac_check, null), /* CMDACCHECK */
+ { 0, 0 }, /* ADD_SERVER */
+ { 0, 0 }, /* ADD_PEER */
+ REQ_LENGTH_ENTRY(del_source, null), /* DEL_SOURCE */
+ REQ_LENGTH_ENTRY(null, null), /* WRITERTC */
+ REQ_LENGTH_ENTRY(dfreq, null), /* DFREQ */
+ { 0, 0 }, /* DOFFSET - not supported */
+ REQ_LENGTH_ENTRY(null, tracking), /* TRACKING */
+ REQ_LENGTH_ENTRY(sourcestats, sourcestats), /* SOURCESTATS */
+ REQ_LENGTH_ENTRY(null, rtc), /* RTCREPORT */
+ REQ_LENGTH_ENTRY(null, null), /* TRIMRTC */
+ REQ_LENGTH_ENTRY(null, null), /* CYCLELOGS */
+ { 0, 0 }, /* SUBNETS_ACCESSED - not supported */
+ { 0, 0 }, /* CLIENT_ACCESSES - not supported */
+ { 0, 0 }, /* CLIENT_ACCESSES_BY_INDEX - not supported */
+ REQ_LENGTH_ENTRY(null, manual_list), /* MANUAL_LIST */
+ REQ_LENGTH_ENTRY(manual_delete, null), /* MANUAL_DELETE */
+ REQ_LENGTH_ENTRY(null, null), /* MAKESTEP */
+ REQ_LENGTH_ENTRY(null, activity), /* ACTIVITY */
+ REQ_LENGTH_ENTRY(modify_minstratum, null), /* MODIFY_MINSTRATUM */
+ REQ_LENGTH_ENTRY(modify_polltarget, null), /* MODIFY_POLLTARGET */
+ REQ_LENGTH_ENTRY(modify_maxdelaydevratio, null), /* MODIFY_MAXDELAYDEVRATIO */
+ REQ_LENGTH_ENTRY(null, null), /* RESELECT */
+ REQ_LENGTH_ENTRY(reselect_distance, null), /* RESELECTDISTANCE */
+ REQ_LENGTH_ENTRY(modify_makestep, null), /* MODIFY_MAKESTEP */
+ REQ_LENGTH_ENTRY(null, smoothing), /* SMOOTHING */
+ REQ_LENGTH_ENTRY(smoothtime, null), /* SMOOTHTIME */
+ REQ_LENGTH_ENTRY(null, null), /* REFRESH */
+ REQ_LENGTH_ENTRY(null, server_stats), /* SERVER_STATS */
+ { 0, 0 }, /* CLIENT_ACCESSES_BY_INDEX2 - not supported */
+ REQ_LENGTH_ENTRY(local, null), /* LOCAL2 */
+ REQ_LENGTH_ENTRY(ntp_data, ntp_data), /* NTP_DATA */
+ { 0, 0 }, /* ADD_SERVER2 */
+ { 0, 0 }, /* ADD_PEER2 */
+ { 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_SOURCES */
+ REQ_LENGTH_ENTRY(auth_data, auth_data), /* AUTH_DATA */
+ REQ_LENGTH_ENTRY(client_accesses_by_index,
+ client_accesses_by_index), /* CLIENT_ACCESSES_BY_INDEX3 */
+ REQ_LENGTH_ENTRY(select_data, select_data), /* SELECT_DATA */
+ REQ_LENGTH_ENTRY(null, null), /* RELOAD_SOURCES */
+ REQ_LENGTH_ENTRY(doffset, null), /* DOFFSET2 */
+ REQ_LENGTH_ENTRY(modify_select_opts, null), /* MODIFY_SELECTOPTS */
+};
+
+static const uint16_t reply_lengths[] = {
+ 0, /* empty slot */
+ RPY_LENGTH_ENTRY(null), /* NULL */
+ RPY_LENGTH_ENTRY(n_sources), /* N_SOURCES */
+ RPY_LENGTH_ENTRY(source_data), /* SOURCE_DATA */
+ 0, /* MANUAL_TIMESTAMP */
+ RPY_LENGTH_ENTRY(tracking), /* TRACKING */
+ RPY_LENGTH_ENTRY(sourcestats), /* SOURCESTATS */
+ RPY_LENGTH_ENTRY(rtc), /* RTC */
+ 0, /* SUBNETS_ACCESSED - not supported */
+ 0, /* CLIENT_ACCESSES - not supported */
+ 0, /* CLIENT_ACCESSES_BY_INDEX - not supported */
+ 0, /* MANUAL_LIST - not supported */
+ RPY_LENGTH_ENTRY(activity), /* ACTIVITY */
+ RPY_LENGTH_ENTRY(smoothing), /* SMOOTHING */
+ 0, /* SERVER_STATS - not supported */
+ 0, /* CLIENT_ACCESSES_BY_INDEX2 - not supported */
+ 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 */
+ RPY_LENGTH_ENTRY(auth_data), /* AUTH_DATA */
+ RPY_LENGTH_ENTRY(client_accesses_by_index), /* CLIENT_ACCESSES_BY_INDEX3 */
+ 0, /* SERVER_STATS2 - not supported */
+ RPY_LENGTH_ENTRY(select_data), /* SELECT_DATA */
+ 0, /* SERVER_STATS3 - not supported */
+ RPY_LENGTH_ENTRY(server_stats), /* SERVER_STATS4 */
+};
+
+/* ================================================== */
+
+int
+PKL_CommandLength(CMD_Request *r)
+{
+ uint32_t type;
+ int command_length;
+
+ assert(sizeof (request_lengths) / sizeof (request_lengths[0]) == N_REQUEST_TYPES);
+
+ type = ntohs(r->command);
+ if (type >= N_REQUEST_TYPES)
+ return 0;
+
+ command_length = request_lengths[type].command;
+ if (!command_length)
+ return 0;
+
+ return command_length + PKL_CommandPaddingLength(r);
+}
+
+/* ================================================== */
+
+int
+PKL_CommandPaddingLength(CMD_Request *r)
+{
+ uint32_t type;
+
+ if (r->version < PROTO_VERSION_PADDING)
+ return 0;
+
+ type = ntohs(r->command);
+
+ if (type >= N_REQUEST_TYPES)
+ return 0;
+
+ return request_lengths[ntohs(r->command)].padding;
+}
+
+/* ================================================== */
+
+int
+PKL_ReplyLength(CMD_Reply *r)
+{
+ uint32_t type;
+
+ assert(sizeof (reply_lengths) / sizeof (reply_lengths[0]) == N_REPLY_TYPES);
+
+ type = ntohs(r->reply);
+
+ /* Note that reply type codes start from 1, not 0 */
+ if (type < 1 || type >= N_REPLY_TYPES)
+ return 0;
+
+ return reply_lengths[type];
+}
+
+/* ================================================== */
+
diff --git a/pktlength.h b/pktlength.h
new file mode 100644
index 0000000..fad4c30
--- /dev/null
+++ b/pktlength.h
@@ -0,0 +1,40 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 pktlength.c, routines for working out the expected length
+ of a network command/reply packet.
+
+ */
+
+#ifndef GOT_PKTLENGTH_H
+#define GOT_PKTLENGTH_H
+
+#include "candm.h"
+
+extern int PKL_CommandLength(CMD_Request *r);
+
+extern int PKL_CommandPaddingLength(CMD_Request *r);
+
+extern int PKL_ReplyLength(CMD_Reply *r);
+
+#endif /* GOT_PKTLENGTH_H */
diff --git a/privops.c b/privops.c
new file mode 100644
index 0000000..3fb5cbd
--- /dev/null
+++ b/privops.c
@@ -0,0 +1,696 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Bryan Christianson 2015
+ * Copyright (C) Miroslav Lichvar 2017
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Perform privileged operations over a unix socket to a privileged fork.
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "conf.h"
+#include "nameserv.h"
+#include "logging.h"
+#include "privops.h"
+#include "socket.h"
+#include "util.h"
+
+#define OP_ADJUSTTIME 1024
+#define OP_ADJUSTTIMEX 1025
+#define OP_SETTIME 1026
+#define OP_BINDSOCKET 1027
+#define OP_NAME2IPADDRESS 1028
+#define OP_RELOADDNS 1029
+#define OP_QUIT 1099
+
+union sockaddr_in46 {
+ struct sockaddr_in in4;
+#ifdef FEAT_IPV6
+ struct sockaddr_in6 in6;
+#endif
+ struct sockaddr u;
+};
+
+/* daemon request structs */
+
+typedef struct {
+ struct timeval tv;
+} ReqAdjustTime;
+
+#ifdef PRIVOPS_ADJUSTTIMEX
+typedef struct {
+ struct timex tmx;
+} ReqAdjustTimex;
+#endif
+
+typedef struct {
+ struct timeval tv;
+} ReqSetTime;
+
+typedef struct {
+ int sock;
+ socklen_t sa_len;
+ union sockaddr_in46 sa;
+} ReqBindSocket;
+
+typedef struct {
+ char name[256];
+} ReqName2IPAddress;
+
+typedef struct {
+ int op;
+ union {
+ ReqAdjustTime adjust_time;
+#ifdef PRIVOPS_ADJUSTTIMEX
+ ReqAdjustTimex adjust_timex;
+#endif
+ ReqSetTime set_time;
+ ReqBindSocket bind_socket;
+#ifdef PRIVOPS_NAME2IPADDRESS
+ ReqName2IPAddress name_to_ipaddress;
+#endif
+ } data;
+} PrvRequest;
+
+/* helper response structs */
+
+typedef struct {
+ struct timeval tv;
+} ResAdjustTime;
+
+#ifdef PRIVOPS_ADJUSTTIMEX
+typedef struct {
+ struct timex tmx;
+} ResAdjustTimex;
+#endif
+
+typedef struct {
+ IPAddr addresses[DNS_MAX_ADDRESSES];
+} ResName2IPAddress;
+
+typedef struct {
+ char msg[256];
+} ResFatalMsg;
+
+typedef struct {
+ int fatal_error;
+ int rc;
+ int res_errno;
+ union {
+ ResFatalMsg fatal_msg;
+ ResAdjustTime adjust_time;
+#ifdef PRIVOPS_ADJUSTTIMEX
+ ResAdjustTimex adjust_timex;
+#endif
+#ifdef PRIVOPS_NAME2IPADDRESS
+ ResName2IPAddress name_to_ipaddress;
+#endif
+ } data;
+} PrvResponse;
+
+static int helper_fd;
+static pid_t helper_pid;
+
+static int
+have_helper(void)
+{
+ return helper_fd >= 0;
+}
+
+/* ======================================================================= */
+
+/* HELPER - prepare fatal error for daemon */
+static void
+res_fatal(PrvResponse *res, const char *fmt, ...)
+{
+ va_list ap;
+
+ res->fatal_error = 1;
+ va_start(ap, fmt);
+ vsnprintf(res->data.fatal_msg.msg, sizeof (res->data.fatal_msg.msg), fmt, ap);
+ va_end(ap);
+}
+
+/* ======================================================================= */
+
+/* HELPER - send response to the fd */
+
+static int
+send_response(int fd, const PrvResponse *res)
+{
+ if (SCK_Send(fd, res, sizeof (*res), 0) != sizeof (*res))
+ return 0;
+
+ return 1;
+}
+
+/* ======================================================================= */
+/* receive daemon request plus optional file descriptor over a unix socket */
+
+static int
+receive_from_daemon(int fd, PrvRequest *req)
+{
+ SCK_Message *message;
+
+ 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) {
+ 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;
+}
+
+/* ======================================================================= */
+
+/* HELPER - perform adjtime() */
+
+#ifdef PRIVOPS_ADJUSTTIME
+static void
+do_adjust_time(const ReqAdjustTime *req, PrvResponse *res)
+{
+ res->rc = adjtime(&req->tv, &res->data.adjust_time.tv);
+ if (res->rc)
+ res->res_errno = errno;
+}
+#endif
+
+/* ======================================================================= */
+
+/* HELPER - perform ntp_adjtime() */
+
+#ifdef PRIVOPS_ADJUSTTIMEX
+static void
+do_adjust_timex(const ReqAdjustTimex *req, PrvResponse *res)
+{
+ res->data.adjust_timex.tmx = req->tmx;
+ res->rc = ntp_adjtime(&res->data.adjust_timex.tmx);
+ if (res->rc < 0)
+ res->res_errno = errno;
+}
+#endif
+
+/* ======================================================================= */
+
+/* HELPER - perform settimeofday() */
+
+#ifdef PRIVOPS_SETTIME
+static void
+do_set_time(const ReqSetTime *req, PrvResponse *res)
+{
+ res->rc = settimeofday(&req->tv, NULL);
+ if (res->rc)
+ res->res_errno = errno;
+}
+#endif
+
+/* ======================================================================= */
+
+/* HELPER - perform bind() */
+
+#ifdef PRIVOPS_BINDSOCKET
+static void
+do_bind_socket(ReqBindSocket *req, PrvResponse *res)
+{
+ IPSockAddr ip_saddr;
+ int sock_fd;
+ struct sockaddr *sa;
+ socklen_t sa_len;
+
+ sa = &req->sa.u;
+ sa_len = req->sa_len;
+ sock_fd = req->sock;
+
+ SCK_SockaddrToIPSockAddr(sa, sa_len, &ip_saddr);
+ if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() &&
+ ip_saddr.port != CNF_GetAcquisitionPort() && ip_saddr.port != CNF_GetPtpPort()) {
+ SCK_CloseSocket(sock_fd);
+ res_fatal(res, "Invalid port %d", ip_saddr.port);
+ return;
+ }
+
+ res->rc = bind(sock_fd, sa, sa_len);
+ if (res->rc)
+ res->res_errno = errno;
+
+ /* sock is still open on daemon side, but we're done with it in the helper */
+ SCK_CloseSocket(sock_fd);
+}
+#endif
+
+/* ======================================================================= */
+
+/* HELPER - perform DNS_Name2IPAddress() */
+
+#ifdef PRIVOPS_NAME2IPADDRESS
+static void
+do_name_to_ipaddress(ReqName2IPAddress *req, PrvResponse *res)
+{
+ /* make sure the string is terminated */
+ req->name[sizeof (req->name) - 1] = '\0';
+
+ res->rc = DNS_Name2IPAddress(req->name, res->data.name_to_ipaddress.addresses,
+ DNS_MAX_ADDRESSES);
+}
+#endif
+
+/* ======================================================================= */
+
+/* HELPER - perform DNS_Reload() */
+
+#ifdef PRIVOPS_RELOADDNS
+static void
+do_reload_dns(PrvResponse *res)
+{
+ DNS_Reload();
+ res->rc = 0;
+}
+#endif
+
+/* ======================================================================= */
+
+/* HELPER - main loop - action requests from the daemon */
+
+static void
+helper_main(int fd)
+{
+ PrvRequest req;
+ PrvResponse res;
+ int quit = 0;
+
+ while (!quit) {
+ if (!receive_from_daemon(fd, &req))
+ /* read error or closed input - we cannot recover - give up */
+ break;
+
+ memset(&res, 0, sizeof (res));
+
+ switch (req.op) {
+#ifdef PRIVOPS_ADJUSTTIME
+ case OP_ADJUSTTIME:
+ do_adjust_time(&req.data.adjust_time, &res);
+ break;
+#endif
+#ifdef PRIVOPS_ADJUSTTIMEX
+ case OP_ADJUSTTIMEX:
+ do_adjust_timex(&req.data.adjust_timex, &res);
+ break;
+#endif
+#ifdef PRIVOPS_SETTIME
+ case OP_SETTIME:
+ do_set_time(&req.data.set_time, &res);
+ break;
+#endif
+#ifdef PRIVOPS_BINDSOCKET
+ case OP_BINDSOCKET:
+ do_bind_socket(&req.data.bind_socket, &res);
+ break;
+#endif
+#ifdef PRIVOPS_NAME2IPADDRESS
+ case OP_NAME2IPADDRESS:
+ do_name_to_ipaddress(&req.data.name_to_ipaddress, &res);
+ break;
+#endif
+#ifdef PRIVOPS_RELOADDNS
+ case OP_RELOADDNS:
+ do_reload_dns(&res);
+ break;
+#endif
+ case OP_QUIT:
+ quit = 1;
+ continue;
+
+ default:
+ res_fatal(&res, "Unexpected operator %d", req.op);
+ break;
+ }
+
+ send_response(fd, &res);
+ }
+
+ SCK_CloseSocket(fd);
+ exit(0);
+}
+
+/* ======================================================================= */
+
+/* DAEMON - receive helper response */
+
+static void
+receive_response(PrvResponse *res)
+{
+ int resp_len;
+
+ 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))
+ LOG_FATAL("Invalid helper response");
+
+ if (res->fatal_error)
+ LOG_FATAL("Error in helper : %s", res->data.fatal_msg.msg);
+
+ DEBUG_LOG("Received response rc=%d", res->rc);
+
+ /* if operation failed in the helper, set errno so daemon can print log message */
+ if (res->res_errno)
+ errno = res->res_errno;
+}
+
+/* ======================================================================= */
+
+/* DAEMON - send daemon request to the helper */
+
+static void
+send_request(PrvRequest *req)
+{
+ SCK_Message message;
+ int flags;
+
+ SCK_InitMessage(&message, SCK_ADDR_UNSPEC);
+
+ message.data = req;
+ message.length = sizeof (*req);
+ flags = 0;
+
+ if (req->op == OP_BINDSOCKET) {
+ /* send file descriptor as a control message */
+ message.descriptor = req->data.bind_socket.sock;
+ flags |= SCK_FLAG_MSG_DESCRIPTOR;
+ }
+
+ 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));
+ }
+
+ DEBUG_LOG("Sent request op=%d", req->op);
+}
+
+/* ======================================================================= */
+
+/* DAEMON - send daemon request and wait for response */
+
+static void
+submit_request(PrvRequest *req, PrvResponse *res)
+{
+ send_request(req);
+ receive_response(res);
+}
+
+/* ======================================================================= */
+
+/* DAEMON - send the helper a request to exit and wait until it exits */
+
+static void
+stop_helper(void)
+{
+ PrvRequest req;
+ int status;
+
+ if (!have_helper())
+ return;
+
+ memset(&req, 0, sizeof (req));
+ req.op = OP_QUIT;
+ send_request(&req);
+
+ waitpid(helper_pid, &status, 0);
+}
+
+/* ======================================================================= */
+
+/* DAEMON - request adjtime() */
+
+#ifdef PRIVOPS_ADJUSTTIME
+int
+PRV_AdjustTime(const struct timeval *delta, struct timeval *olddelta)
+{
+ PrvRequest req;
+ PrvResponse res;
+
+ if (!have_helper() || delta == NULL)
+ /* helper is not running or read adjustment call */
+ return adjtime(delta, olddelta);
+
+ memset(&req, 0, sizeof (req));
+ req.op = OP_ADJUSTTIME;
+ req.data.adjust_time.tv = *delta;
+
+ submit_request(&req, &res);
+
+ if (olddelta)
+ *olddelta = res.data.adjust_time.tv;
+
+ return res.rc;
+}
+#endif
+
+/* ======================================================================= */
+
+/* DAEMON - request ntp_adjtime() */
+
+#ifdef PRIVOPS_ADJUSTTIMEX
+int
+PRV_AdjustTimex(struct timex *tmx)
+{
+ PrvRequest req;
+ PrvResponse res;
+
+ if (!have_helper())
+ return ntp_adjtime(tmx);
+
+ memset(&req, 0, sizeof (req));
+ req.op = OP_ADJUSTTIMEX;
+ req.data.adjust_timex.tmx = *tmx;
+
+ submit_request(&req, &res);
+
+ *tmx = res.data.adjust_timex.tmx;
+
+ return res.rc;
+}
+#endif
+
+/* ======================================================================= */
+
+/* DAEMON - request settimeofday() */
+
+#ifdef PRIVOPS_SETTIME
+int
+PRV_SetTime(const struct timeval *tp, const struct timezone *tzp)
+{
+ PrvRequest req;
+ PrvResponse res;
+
+ /* only support setting the time */
+ assert(tp != NULL);
+ assert(tzp == NULL);
+
+ if (!have_helper())
+ return settimeofday(tp, NULL);
+
+ memset(&req, 0, sizeof (req));
+ req.op = OP_SETTIME;
+ req.data.set_time.tv = *tp;
+
+ submit_request(&req, &res);
+
+ return res.rc;
+}
+#endif
+
+/* ======================================================================= */
+
+/* DAEMON - request bind() */
+
+#ifdef PRIVOPS_BINDSOCKET
+int
+PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len)
+{
+ IPSockAddr ip_saddr;
+ PrvRequest req;
+ PrvResponse res;
+
+ SCK_SockaddrToIPSockAddr(address, address_len, &ip_saddr);
+ if (ip_saddr.port != 0 && ip_saddr.port != CNF_GetNTPPort() &&
+ ip_saddr.port != CNF_GetAcquisitionPort() && ip_saddr.port != CNF_GetPtpPort())
+ assert(0);
+
+ if (!have_helper())
+ return bind(sock, address, address_len);
+
+ memset(&req, 0, sizeof (req));
+ 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);
+
+ return res.rc;
+}
+#endif
+
+/* ======================================================================= */
+
+/* DAEMON - request DNS_Name2IPAddress() */
+
+#ifdef PRIVOPS_NAME2IPADDRESS
+int
+PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs)
+{
+ PrvRequest req;
+ PrvResponse res;
+ int i;
+
+ if (!have_helper())
+ return DNS_Name2IPAddress(name, ip_addrs, max_addrs);
+
+ memset(&req, 0, sizeof (req));
+ 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)) {
+ return DNS_Failure;
+ }
+
+ submit_request(&req, &res);
+
+ for (i = 0; i < max_addrs && i < DNS_MAX_ADDRESSES; i++)
+ ip_addrs[i] = res.data.name_to_ipaddress.addresses[i];
+
+ return res.rc;
+}
+#endif
+
+/* ======================================================================= */
+
+/* DAEMON - request res_init() */
+
+#ifdef PRIVOPS_RELOADDNS
+void
+PRV_ReloadDNS(void)
+{
+ PrvRequest req;
+ PrvResponse res;
+
+ if (!have_helper()) {
+ DNS_Reload();
+ return;
+ }
+
+ memset(&req, 0, sizeof (req));
+ req.op = OP_RELOADDNS;
+
+ submit_request(&req, &res);
+ assert(!res.rc);
+}
+#endif
+
+/* ======================================================================= */
+
+void
+PRV_Initialise(void)
+{
+ helper_fd = -1;
+}
+
+/* ======================================================================= */
+
+/* DAEMON - setup socket(s) then fork to run the helper */
+/* must be called before privileges are dropped */
+
+void
+PRV_StartHelper(void)
+{
+ pid_t pid;
+ int fd, sock_fd1, sock_fd2;
+
+ if (have_helper())
+ LOG_FATAL("Helper already running");
+
+ 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)
+ LOG_FATAL("fork() failed : %s", strerror(errno));
+
+ if (pid == 0) {
+ /* child process */
+ SCK_CloseSocket(sock_fd1);
+
+ /* 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);
+ }
+
+ UTI_ResetGetRandomFunctions();
+
+ /* ignore signals, the process will exit on OP_QUIT request */
+ UTI_SetQuitSignalsHandler(SIG_IGN, 1);
+
+ helper_main(sock_fd2);
+
+ } else {
+ /* parent process */
+ SCK_CloseSocket(sock_fd2);
+ helper_fd = sock_fd1;
+ helper_pid = pid;
+
+ /* stop the helper even when not exiting cleanly from the main function */
+ atexit(stop_helper);
+ }
+}
+
+/* ======================================================================= */
+
+/* DAEMON - graceful shutdown of the helper */
+
+void
+PRV_Finalise(void)
+{
+ if (!have_helper())
+ return;
+
+ stop_helper();
+ close(helper_fd);
+ helper_fd = -1;
+}
diff --git a/privops.h b/privops.h
new file mode 100644
index 0000000..146580b
--- /dev/null
+++ b/privops.h
@@ -0,0 +1,77 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Bryan Christianson 2015
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Perform privileged operations over a unix socket to a privileged fork.
+
+*/
+
+#ifndef GOT_PRIVOPS_H
+#define GOT_PRIVOPS_H
+
+#ifdef PRIVOPS_ADJUSTTIME
+int PRV_AdjustTime(const struct timeval *delta, struct timeval *olddelta);
+#else
+#define PRV_AdjustTime adjtime
+#endif
+
+#ifdef PRIVOPS_ADJUSTTIMEX
+int PRV_AdjustTimex(struct timex *txc);
+#else
+#define PRV_AdjustTimex ntp_adjtime
+#endif
+
+#ifdef PRIVOPS_SETTIME
+int PRV_SetTime(const struct timeval *tp, const struct timezone *tzp);
+#else
+#define PRV_SetTime settimeofday
+#endif
+
+#ifdef PRIVOPS_BINDSOCKET
+int PRV_BindSocket(int sock, struct sockaddr *address, socklen_t address_len);
+#else
+#define PRV_BindSocket bind
+#endif
+
+#ifdef PRIVOPS_NAME2IPADDRESS
+int PRV_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs);
+#else
+#define PRV_Name2IPAddress DNS_Name2IPAddress
+#endif
+
+#ifdef PRIVOPS_RELOADDNS
+void PRV_ReloadDNS(void);
+#else
+#define PRV_ReloadDNS DNS_Reload
+#endif
+
+#ifdef PRIVOPS_HELPER
+void PRV_Initialise(void);
+void PRV_StartHelper(void);
+void PRV_Finalise(void);
+#else
+#define PRV_Initialise()
+#define PRV_StartHelper()
+#define PRV_Finalise()
+#endif
+
+#endif
diff --git a/ptp.h b/ptp.h
new file mode 100644
index 0000000..8034a2c
--- /dev/null
+++ b/ptp.h
@@ -0,0 +1,69 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2021
+ *
+ * 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 the Precision Time Protocol (PTP).
+
+ */
+#ifndef GOT_PTP_H
+#define GOT_PTP_H
+
+#include "sysincl.h"
+
+#include "ntp.h"
+
+#define PTP_VERSION 2
+#define PTP_TYPE_DELAY_REQ 1
+#define PTP_DOMAIN_NTP 123
+#define PTP_FLAG_UNICAST (1 << (2 + 8))
+#define PTP_TLV_NTP 0x2023
+
+typedef struct {
+ uint8_t type;
+ uint8_t version;
+ uint16_t length;
+ uint8_t domain;
+ uint8_t min_sdoid;
+ uint16_t flags;
+ uint8_t correction[8];
+ uint8_t msg_specific[4];
+ uint8_t port_id[10];
+ uint16_t sequence_id;
+ uint8_t control;
+ int8_t interval;
+} PTP_Header;
+
+typedef struct {
+ uint16_t type;
+ uint16_t length;
+} PTP_TlvHeader;
+
+typedef struct {
+ PTP_Header header;
+ uint8_t origin_ts[10];
+ PTP_TlvHeader tlv_header;
+ NTP_Packet ntp_msg;
+} PTP_NtpMessage;
+
+#define PTP_NTP_PREFIX_LENGTH (int)offsetof(PTP_NtpMessage, ntp_msg)
+
+#endif
diff --git a/quantiles.c b/quantiles.c
new file mode 100644
index 0000000..52953db
--- /dev/null
+++ b/quantiles.c
@@ -0,0 +1,209 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2022
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Estimation of quantiles using the Frugal-2U streaming algorithm
+ (https://arxiv.org/pdf/1407.1121v1.pdf)
+ */
+
+#include "config.h"
+
+#include "logging.h"
+#include "memory.h"
+#include "quantiles.h"
+#include "regress.h"
+#include "util.h"
+
+/* Maximum number of repeated estimates for stabilisation */
+#define MAX_REPEAT 64
+
+struct Quantile {
+ double est;
+ double step;
+ int sign;
+};
+
+struct QNT_Instance_Record {
+ struct Quantile *quants;
+ int n_quants;
+ int repeat;
+ int q;
+ int min_k;
+ double min_step;
+ int n_set;
+};
+
+/* ================================================== */
+
+QNT_Instance
+QNT_CreateInstance(int min_k, int max_k, int q, int repeat, double min_step)
+{
+ QNT_Instance inst;
+ long seed;
+
+ if (q < 2 || min_k > max_k || min_k < 1 || max_k >= q ||
+ repeat < 1 || repeat > MAX_REPEAT || min_step <= 0.0)
+ assert(0);
+
+ inst = MallocNew(struct QNT_Instance_Record);
+ inst->n_quants = (max_k - min_k + 1) * repeat;
+ inst->quants = MallocArray(struct Quantile, inst->n_quants);
+ inst->repeat = repeat;
+ inst->q = q;
+ inst->min_k = min_k;
+ inst->min_step = min_step;
+
+ QNT_Reset(inst);
+
+ /* Seed the random number generator, which will not be isolated from
+ other instances and other random() users */
+ UTI_GetRandomBytes(&seed, sizeof (seed));
+ srandom(seed);
+
+ return inst;
+}
+
+/* ================================================== */
+
+void
+QNT_DestroyInstance(QNT_Instance inst)
+{
+ Free(inst->quants);
+ Free(inst);
+}
+
+/* ================================================== */
+
+void
+QNT_Reset(QNT_Instance inst)
+{
+ int i;
+
+ inst->n_set = 0;
+
+ for (i = 0; i < inst->n_quants; i++) {
+ inst->quants[i].est = 0.0;
+ inst->quants[i].step = inst->min_step;
+ inst->quants[i].sign = 1;
+ }
+}
+
+/* ================================================== */
+
+static void
+insert_initial_value(QNT_Instance inst, double value)
+{
+ int i, j, r = inst->repeat;
+
+ if (inst->n_set * r >= inst->n_quants)
+ assert(0);
+
+ /* Keep the initial estimates repeated and ordered */
+ for (i = inst->n_set; i > 0 && inst->quants[(i - 1) * r].est > value; i--) {
+ for (j = 0; j < r; j++)
+ inst->quants[i * r + j].est = inst->quants[(i - 1) * r].est;
+ }
+
+ for (j = 0; j < r; j++)
+ inst->quants[i * r + j].est = value;
+ inst->n_set++;
+
+ /* Duplicate the largest value in unset quantiles */
+ for (i = inst->n_set * r; i < inst->n_quants; i++)
+ inst->quants[i].est = inst->quants[i - 1].est;
+}
+
+/* ================================================== */
+
+static void
+update_estimate(struct Quantile *quantile, double value, double p, double rand,
+ double min_step)
+{
+ if (value > quantile->est && rand > (1.0 - p)) {
+ quantile->step += quantile->sign > 0 ? min_step : -min_step;
+ quantile->est += quantile->step > 0.0 ? fabs(quantile->step) : min_step;
+ if (quantile->est > value) {
+ quantile->step += value - quantile->est;
+ quantile->est = value;
+ }
+ if (quantile->sign < 0 && quantile->step > min_step)
+ quantile->step = min_step;
+ quantile->sign = 1;
+ } else if (value < quantile->est && rand > p) {
+ quantile->step += quantile->sign < 0 ? min_step : -min_step;
+ quantile->est -= quantile->step > 0.0 ? fabs(quantile->step) : min_step;
+ if (quantile->est < value) {
+ quantile->step += quantile->est - value;
+ quantile->est = value;
+ }
+ if (quantile->sign > 0 && quantile->step > min_step)
+ quantile->step = min_step;
+ quantile->sign = -1;
+ }
+}
+
+/* ================================================== */
+
+void
+QNT_Accumulate(QNT_Instance inst, double value)
+{
+ double p, rand;
+ int i;
+
+ /* Initialise the estimates with first received values */
+ if (inst->n_set * inst->repeat < inst->n_quants) {
+ insert_initial_value(inst, value);
+ return;
+ }
+
+ for (i = 0; i < inst->n_quants; i++) {
+ p = (double)(i / inst->repeat + inst->min_k) / inst->q;
+ rand = (double)random() / ((1U << 31) - 1);
+
+ update_estimate(&inst->quants[i], value, p, rand, inst->min_step);
+ }
+}
+
+/* ================================================== */
+
+int
+QNT_GetMinK(QNT_Instance inst)
+{
+ return inst->min_k;
+}
+
+/* ================================================== */
+
+double
+QNT_GetQuantile(QNT_Instance inst, int k)
+{
+ double estimates[MAX_REPEAT];
+ int i;
+
+ if (k < inst->min_k || k - inst->min_k >= inst->n_quants)
+ assert(0);
+
+ for (i = 0; i < inst->repeat; i++)
+ estimates[i] = inst->quants[(k - inst->min_k) * inst->repeat + i].est;
+
+ return RGR_FindMedian(estimates, inst->repeat);
+}
diff --git a/quantiles.h b/quantiles.h
new file mode 100644
index 0000000..1788544
--- /dev/null
+++ b/quantiles.h
@@ -0,0 +1,41 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2022
+ *
+ * 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 estimation of quantiles.
+
+ */
+
+#ifndef GOT_QUANTILES_H
+#define GOT_QUANTILES_H
+
+typedef struct QNT_Instance_Record *QNT_Instance;
+
+extern QNT_Instance QNT_CreateInstance(int min_k, int max_k, int q, int repeat, double min_step);
+extern void QNT_DestroyInstance(QNT_Instance inst);
+
+extern void QNT_Reset(QNT_Instance inst);
+extern void QNT_Accumulate(QNT_Instance inst, double value);
+extern int QNT_GetMinK(QNT_Instance inst);
+extern double QNT_GetQuantile(QNT_Instance inst, int k);
+
+#endif
diff --git a/refclock.c b/refclock.c
new file mode 100644
index 0000000..84f7439
--- /dev/null
+++ b/refclock.c
@@ -0,0 +1,862 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2009-2011, 2013-2014, 2016-2019, 2022
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing reference clocks.
+
+ */
+
+#include "config.h"
+
+#include "array.h"
+#include "refclock.h"
+#include "reference.h"
+#include "conf.h"
+#include "local.h"
+#include "memory.h"
+#include "util.h"
+#include "sources.h"
+#include "logging.h"
+#include "regress.h"
+#include "samplefilt.h"
+#include "sched.h"
+
+/* Maximum offset of locked reference as a fraction of the PPS interval */
+#define PPS_LOCK_LIMIT 0.4
+
+/* list of refclock drivers */
+extern RefclockDriver RCL_SHM_driver;
+extern RefclockDriver RCL_SOCK_driver;
+extern RefclockDriver RCL_PPS_driver;
+extern RefclockDriver RCL_PHC_driver;
+
+struct FilterSample {
+ double offset;
+ double dispersion;
+ struct timespec sample_time;
+};
+
+struct RCL_Instance_Record {
+ RefclockDriver *driver;
+ void *data;
+ char *driver_parameter;
+ int driver_parameter_length;
+ int driver_poll;
+ int driver_polled;
+ int poll;
+ int leap_status;
+ int local;
+ int pps_forced;
+ int pps_rate;
+ int pps_active;
+ int max_lock_age;
+ int stratum;
+ int tai;
+ uint32_t ref_id;
+ uint32_t lock_ref;
+ double offset;
+ double delay;
+ double precision;
+ double pulse_width;
+ SPF_Instance filter;
+ SCH_TimeoutID timeout_id;
+ SRC_Instance source;
+};
+
+/* Array of pointers to RCL_Instance_Record */
+static ARR_Instance refclocks;
+
+static LOG_FileID logfileid;
+
+static int valid_sample_time(RCL_Instance instance, struct timespec *sample_time);
+static int pps_stratum(RCL_Instance instance, struct timespec *ts);
+static void poll_timeout(void *arg);
+static void slew_samples(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything);
+static void add_dispersion(double dispersion, void *anything);
+static void log_sample(RCL_Instance instance, struct timespec *sample_time, int filtered, int pulse, double raw_offset, double cooked_offset, double dispersion);
+
+static RCL_Instance
+get_refclock(unsigned int index)
+{
+ return *(RCL_Instance *)ARR_GetElement(refclocks, index);
+}
+
+void
+RCL_Initialise(void)
+{
+ refclocks = ARR_CreateInstance(sizeof (RCL_Instance));
+
+ CNF_AddRefclocks();
+
+ if (ARR_GetSize(refclocks) > 0) {
+ LCL_AddParameterChangeHandler(slew_samples, NULL);
+ LCL_AddDispersionNotifyHandler(add_dispersion, NULL);
+ }
+
+ logfileid = CNF_GetLogRefclocks() ? LOG_FileOpen("refclocks",
+ " Date (UTC) Time Refid DP L P Raw offset Cooked offset Disp.")
+ : -1;
+}
+
+void
+RCL_Finalise(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(refclocks); i++) {
+ RCL_Instance inst = get_refclock(i);
+
+ if (inst->driver->fini)
+ inst->driver->fini(inst);
+
+ SPF_DestroyInstance(inst->filter);
+ Free(inst->driver_parameter);
+ SRC_DestroyInstance(inst->source);
+ Free(inst);
+ }
+
+ if (ARR_GetSize(refclocks) > 0) {
+ LCL_RemoveParameterChangeHandler(slew_samples, NULL);
+ LCL_RemoveDispersionNotifyHandler(add_dispersion, NULL);
+ }
+
+ ARR_DestroyInstance(refclocks);
+}
+
+int
+RCL_AddRefclock(RefclockParameters *params)
+{
+ RCL_Instance inst;
+
+ inst = MallocNew(struct RCL_Instance_Record);
+ *(RCL_Instance *)ARR_GetNewElement(refclocks) = inst;
+
+ if (strcmp(params->driver_name, "SHM") == 0) {
+ inst->driver = &RCL_SHM_driver;
+ } else if (strcmp(params->driver_name, "SOCK") == 0) {
+ inst->driver = &RCL_SOCK_driver;
+ } else if (strcmp(params->driver_name, "PPS") == 0) {
+ inst->driver = &RCL_PPS_driver;
+ } else if (strcmp(params->driver_name, "PHC") == 0) {
+ inst->driver = &RCL_PHC_driver;
+ } else {
+ LOG_FATAL("unknown refclock driver %s", params->driver_name);
+ }
+
+ if (!inst->driver->init && !inst->driver->poll)
+ LOG_FATAL("refclock driver %s is not compiled in", params->driver_name);
+
+ if (params->tai && !CNF_GetLeapSecTimezone())
+ LOG_FATAL("refclock tai option requires leapsectz");
+
+ inst->data = NULL;
+ inst->driver_parameter = Strdup(params->driver_parameter);
+ inst->driver_parameter_length = 0;
+ inst->driver_poll = params->driver_poll;
+ inst->poll = params->poll;
+ inst->driver_polled = 0;
+ inst->leap_status = LEAP_Normal;
+ inst->local = params->local;
+ inst->pps_forced = params->pps_forced;
+ inst->pps_rate = params->pps_rate;
+ inst->pps_active = 0;
+ inst->max_lock_age = params->max_lock_age;
+ inst->stratum = params->stratum;
+ inst->tai = params->tai;
+ inst->lock_ref = params->lock_ref_id;
+ inst->offset = params->offset;
+ inst->delay = params->delay;
+ inst->precision = LCL_GetSysPrecisionAsQuantum();
+ inst->precision = MAX(inst->precision, params->precision);
+ inst->pulse_width = params->pulse_width;
+ inst->timeout_id = -1;
+ inst->source = NULL;
+
+ if (inst->driver_parameter) {
+ int i;
+
+ inst->driver_parameter_length = strlen(inst->driver_parameter);
+ for (i = 0; i < inst->driver_parameter_length; i++)
+ if (inst->driver_parameter[i] == ':')
+ inst->driver_parameter[i] = '\0';
+ }
+
+ if (inst->pps_rate < 1)
+ inst->pps_rate = 1;
+
+ if (params->ref_id)
+ inst->ref_id = params->ref_id;
+ else {
+ unsigned char ref[5] = { 0, 0, 0, 0, 0 };
+ unsigned int index = ARR_GetSize(refclocks) - 1;
+
+ snprintf((char *)ref, sizeof (ref), "%3.3s", params->driver_name);
+ ref[3] = index % 10 + '0';
+ if (index >= 10)
+ ref[2] = (index / 10) % 10 + '0';
+
+ inst->ref_id = (uint32_t)ref[0] << 24 | ref[1] << 16 | ref[2] << 8 | ref[3];
+ }
+
+ if (inst->local) {
+ inst->pps_forced = 1;
+ inst->lock_ref = inst->ref_id;
+ inst->leap_status = LEAP_Unsynchronised;
+ inst->max_lock_age = MAX(inst->max_lock_age, 3);
+ }
+
+ if (inst->driver->poll) {
+ int max_samples;
+
+ if (inst->driver_poll > inst->poll)
+ inst->driver_poll = inst->poll;
+
+ max_samples = 1 << (inst->poll - inst->driver_poll);
+ if (max_samples < params->filter_length) {
+ if (max_samples < 4) {
+ LOG(LOGS_WARN, "Setting filter length for %s to %d",
+ UTI_RefidToString(inst->ref_id), max_samples);
+ }
+ params->filter_length = max_samples;
+ }
+ }
+
+ if (inst->driver->init && !inst->driver->init(inst))
+ LOG_FATAL("refclock %s initialisation failed", params->driver_name);
+
+ /* Require the filter to have at least 4 samples to produce a filtered
+ sample, or be full for shorter lengths, and combine 60% of samples
+ closest to the median */
+ inst->filter = SPF_CreateInstance(MIN(params->filter_length, 4), params->filter_length,
+ params->max_dispersion, 0.6);
+
+ inst->source = SRC_CreateNewInstance(inst->ref_id, SRC_REFCLOCK, 0, params->sel_options,
+ NULL, params->min_samples, params->max_samples,
+ 0.0, 0.0);
+
+ DEBUG_LOG("refclock %s refid=%s poll=%d dpoll=%d filter=%d",
+ params->driver_name, UTI_RefidToString(inst->ref_id),
+ inst->poll, inst->driver_poll, params->filter_length);
+
+ return 1;
+}
+
+void
+RCL_StartRefclocks(void)
+{
+ unsigned int i, j, n, lock_index;
+
+ n = ARR_GetSize(refclocks);
+
+ for (i = 0; i < n; i++) {
+ RCL_Instance inst = get_refclock(i);
+
+ SRC_SetActive(inst->source);
+ inst->timeout_id = SCH_AddTimeoutByDelay(0.0, poll_timeout, (void *)inst);
+
+ /* Replace lock refid with the refclock's index, or -1 if not valid */
+
+ lock_index = -1;
+
+ if (inst->lock_ref != 0) {
+ for (j = 0; j < n; j++) {
+ RCL_Instance inst2 = get_refclock(j);
+
+ if (inst->lock_ref != inst2->ref_id)
+ continue;
+
+ if (inst->driver->poll && inst2->driver->poll &&
+ (double)inst->max_lock_age / inst->pps_rate < UTI_Log2ToDouble(inst2->driver_poll))
+ LOG(LOGS_WARN, "%s maxlockage too small for %s",
+ UTI_RefidToString(inst->ref_id), UTI_RefidToString(inst2->ref_id));
+
+ lock_index = j;
+ break;
+ }
+
+ if (lock_index == -1 || (lock_index == i && !inst->local))
+ LOG(LOGS_WARN, "Invalid lock refid %s", UTI_RefidToString(inst->lock_ref));
+ }
+
+ inst->lock_ref = lock_index;
+ }
+}
+
+void
+RCL_ReportSource(RPT_SourceReport *report, struct timespec *now)
+{
+ unsigned int i;
+ uint32_t ref_id;
+
+ assert(report->ip_addr.family == IPADDR_INET4);
+ ref_id = report->ip_addr.addr.in4;
+
+ for (i = 0; i < ARR_GetSize(refclocks); i++) {
+ RCL_Instance inst = get_refclock(i);
+ if (inst->ref_id == ref_id) {
+ report->poll = inst->poll;
+ report->mode = RPT_LOCAL_REFERENCE;
+ break;
+ }
+ }
+}
+
+void
+RCL_SetDriverData(RCL_Instance instance, void *data)
+{
+ instance->data = data;
+}
+
+void *
+RCL_GetDriverData(RCL_Instance instance)
+{
+ return instance->data;
+}
+
+char *
+RCL_GetDriverParameter(RCL_Instance instance)
+{
+ return instance->driver_parameter;
+}
+
+static char *
+get_next_driver_option(RCL_Instance instance, char *option)
+{
+ if (option == NULL)
+ option = instance->driver_parameter;
+
+ option += strlen(option) + 1;
+
+ if (option >= instance->driver_parameter + instance->driver_parameter_length)
+ return NULL;
+
+ return option;
+}
+
+void
+RCL_CheckDriverOptions(RCL_Instance instance, const char **options)
+{
+ char *option;
+ int i, len;
+
+ for (option = get_next_driver_option(instance, NULL);
+ option;
+ option = get_next_driver_option(instance, option)) {
+ for (i = 0; options && options[i]; i++) {
+ len = strlen(options[i]);
+ if (!strncmp(options[i], option, len) &&
+ (option[len] == '=' || option[len] == '\0'))
+ break;
+ }
+
+ if (!options || !options[i])
+ LOG_FATAL("Invalid refclock driver option %s", option);
+ }
+}
+
+char *
+RCL_GetDriverOption(RCL_Instance instance, char *name)
+{
+ char *option;
+ int len;
+
+ len = strlen(name);
+
+ for (option = get_next_driver_option(instance, NULL);
+ option;
+ option = get_next_driver_option(instance, option)) {
+ if (!strncmp(name, option, len)) {
+ if (option[len] == '=')
+ return option + len + 1;
+ if (option[len] == '\0')
+ return option + len;
+ }
+ }
+
+ return NULL;
+}
+
+static int
+convert_tai_offset(struct timespec *sample_time, double *offset)
+{
+ struct timespec tai_ts, utc_ts;
+ int tai_offset;
+
+ /* Get approximate TAI-UTC offset for the reference time in TAI */
+ UTI_AddDoubleToTimespec(sample_time, *offset, &tai_ts);
+ tai_offset = REF_GetTaiOffset(&tai_ts);
+
+ /* Get TAI-UTC offset for the reference time in UTC +/- 1 second */
+ UTI_AddDoubleToTimespec(&tai_ts, -tai_offset, &utc_ts);
+ tai_offset = REF_GetTaiOffset(&utc_ts);
+
+ if (!tai_offset)
+ return 0;
+
+ *offset -= tai_offset;
+
+ return 1;
+}
+
+static int
+accumulate_sample(RCL_Instance instance, struct timespec *sample_time, double offset, double dispersion)
+{
+ NTP_Sample sample;
+
+ sample.time = *sample_time;
+ sample.offset = offset;
+ sample.peer_delay = instance->delay;
+ sample.root_delay = instance->delay;
+ sample.peer_dispersion = dispersion;
+ sample.root_dispersion = dispersion;
+
+ return SPF_AccumulateSample(instance->filter, &sample);
+}
+
+int
+RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
+ struct timespec *ref_time, int leap)
+{
+ double correction, dispersion, raw_offset, offset;
+ struct timespec cooked_time;
+
+ if (instance->pps_forced)
+ return RCL_AddPulse(instance, sample_time,
+ 1.0e-9 * (sample_time->tv_nsec - ref_time->tv_nsec));
+
+ raw_offset = UTI_DiffTimespecsToDouble(ref_time, sample_time);
+
+ LCL_GetOffsetCorrection(sample_time, &correction, &dispersion);
+ UTI_AddDoubleToTimespec(sample_time, correction, &cooked_time);
+ dispersion += instance->precision;
+
+ /* Make sure the timestamp and offset provided by the driver are sane */
+ if (!UTI_IsTimeOffsetSane(sample_time, raw_offset) ||
+ !valid_sample_time(instance, &cooked_time))
+ return 0;
+
+ switch (leap) {
+ case LEAP_Normal:
+ case LEAP_InsertSecond:
+ case LEAP_DeleteSecond:
+ instance->leap_status = leap;
+ break;
+ default:
+ DEBUG_LOG("refclock sample ignored bad leap %d", leap);
+ return 0;
+ }
+
+ /* Calculate offset = raw_offset - correction + instance->offset
+ in parts to avoid loss of precision if there are large differences */
+ offset = ref_time->tv_sec - sample_time->tv_sec -
+ (time_t)correction + (time_t)instance->offset;
+ offset += 1.0e-9 * (ref_time->tv_nsec - sample_time->tv_nsec) -
+ (correction - (time_t)correction) + (instance->offset - (time_t)instance->offset);
+
+ if (instance->tai && !convert_tai_offset(sample_time, &offset)) {
+ DEBUG_LOG("refclock sample ignored unknown TAI offset");
+ return 0;
+ }
+
+ if (!accumulate_sample(instance, &cooked_time, offset, dispersion))
+ return 0;
+
+ instance->pps_active = 0;
+
+ log_sample(instance, &cooked_time, 0, 0, raw_offset, offset, dispersion);
+
+ /* for logging purposes */
+ if (!instance->driver->poll)
+ instance->driver_polled++;
+
+ return 1;
+}
+
+int
+RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second)
+{
+ double correction, dispersion;
+ struct timespec cooked_time;
+
+ LCL_GetOffsetCorrection(pulse_time, &correction, &dispersion);
+ UTI_AddDoubleToTimespec(pulse_time, correction, &cooked_time);
+ second += correction;
+
+ if (!UTI_IsTimeOffsetSane(pulse_time, 0.0))
+ return 0;
+
+ return RCL_AddCookedPulse(instance, &cooked_time, second, dispersion, correction);
+}
+
+static int
+check_pulse_edge(RCL_Instance instance, double offset, double distance)
+{
+ double max_error;
+
+ if (instance->pulse_width <= 0.0)
+ return 1;
+
+ max_error = 1.0 / instance->pps_rate - instance->pulse_width;
+ max_error = MIN(instance->pulse_width, max_error);
+ max_error *= 0.5;
+
+ if (fabs(offset) > max_error || distance > max_error) {
+ DEBUG_LOG("refclock pulse ignored offset=%.9f distance=%.9f max_error=%.9f",
+ offset, distance, max_error);
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
+ double second, double dispersion, double raw_correction)
+{
+ double offset;
+ int rate;
+ NTP_Leap leap;
+
+ if (!UTI_IsTimeOffsetSane(cooked_time, second) ||
+ !valid_sample_time(instance, cooked_time))
+ return 0;
+
+ leap = LEAP_Normal;
+ dispersion += instance->precision;
+ rate = instance->pps_rate;
+
+ offset = -second + instance->offset;
+
+ /* Adjust the offset to [-0.5/rate, 0.5/rate) interval */
+ offset -= (long)(offset * rate) / (double)rate;
+ if (offset < -0.5 / rate)
+ offset += 1.0 / rate;
+ else if (offset >= 0.5 / rate)
+ offset -= 1.0 / rate;
+
+ if (instance->lock_ref != -1) {
+ RCL_Instance lock_refclock;
+ NTP_Sample ref_sample;
+ double sample_diff, shift;
+
+ lock_refclock = get_refclock(instance->lock_ref);
+
+ if (!SPF_GetLastSample(lock_refclock->filter, &ref_sample)) {
+ if (instance->local) {
+ /* Make the first sample in order to lock to itself */
+ ref_sample.time = *cooked_time;
+ ref_sample.offset = offset;
+ ref_sample.peer_delay = ref_sample.peer_dispersion = 0;
+ ref_sample.root_delay = ref_sample.root_dispersion = 0;
+ } else {
+ DEBUG_LOG("refclock pulse ignored no ref sample");
+ return 0;
+ }
+ }
+
+ ref_sample.root_dispersion += SPF_GetAvgSampleDispersion(lock_refclock->filter);
+
+ sample_diff = UTI_DiffTimespecsToDouble(cooked_time, &ref_sample.time);
+ if (fabs(sample_diff) >= (double)instance->max_lock_age / rate) {
+ DEBUG_LOG("refclock pulse ignored samplediff=%.9f", sample_diff);
+
+ /* Restart the local mode */
+ if (instance->local) {
+ LOG(LOGS_WARN, "Local refclock lost lock");
+ SPF_DropSamples(instance->filter);
+ SRC_ResetInstance(instance->source);
+ }
+ return 0;
+ }
+
+ /* Align the offset to the reference sample */
+ shift = round((ref_sample.offset - offset) * rate) / rate;
+
+ offset += shift;
+
+ if (fabs(ref_sample.offset - offset) +
+ ref_sample.root_dispersion + dispersion > PPS_LOCK_LIMIT / rate) {
+ DEBUG_LOG("refclock pulse ignored offdiff=%.9f refdisp=%.9f disp=%.9f",
+ ref_sample.offset - offset, ref_sample.root_dispersion, dispersion);
+ return 0;
+ }
+
+ if (!check_pulse_edge(instance, ref_sample.offset - offset, 0.0))
+ return 0;
+
+ leap = lock_refclock->leap_status;
+
+ DEBUG_LOG("refclock pulse offset=%.9f offdiff=%.9f samplediff=%.9f",
+ offset, ref_sample.offset - offset, sample_diff);
+ } else {
+ struct timespec ref_time;
+ int is_synchronised, stratum;
+ double root_delay, root_dispersion, distance;
+ uint32_t ref_id;
+
+ /* Ignore the pulse if we are not well synchronized and the local
+ reference is not active */
+
+ REF_GetReferenceParams(cooked_time, &is_synchronised, &leap, &stratum,
+ &ref_id, &ref_time, &root_delay, &root_dispersion);
+ distance = fabs(root_delay) / 2 + root_dispersion;
+
+ if (leap == LEAP_Unsynchronised || distance >= 0.5 / rate) {
+ DEBUG_LOG("refclock pulse ignored offset=%.9f sync=%d dist=%.9f",
+ offset, leap != LEAP_Unsynchronised, distance);
+ /* Drop also all stored samples */
+ SPF_DropSamples(instance->filter);
+ return 0;
+ }
+
+ if (!check_pulse_edge(instance, offset, distance))
+ return 0;
+ }
+
+ if (!accumulate_sample(instance, cooked_time, offset, dispersion))
+ return 0;
+
+ instance->leap_status = leap;
+ instance->pps_active = 1;
+
+ log_sample(instance, cooked_time, 0, 1, offset + raw_correction - instance->offset,
+ offset, dispersion);
+
+ /* for logging purposes */
+ if (!instance->driver->poll)
+ instance->driver_polled++;
+
+ return 1;
+}
+
+double
+RCL_GetPrecision(RCL_Instance instance)
+{
+ return instance->precision;
+}
+
+int
+RCL_GetDriverPoll(RCL_Instance instance)
+{
+ return instance->driver_poll;
+}
+
+static int
+valid_sample_time(RCL_Instance instance, struct timespec *sample_time)
+{
+ struct timespec now;
+ double diff;
+
+ LCL_ReadCookedTime(&now, NULL);
+ diff = UTI_DiffTimespecsToDouble(&now, sample_time);
+
+ if (diff < 0.0 || diff > UTI_Log2ToDouble(instance->poll + 1)) {
+ DEBUG_LOG("%s refclock sample time %s not valid age=%.6f",
+ UTI_RefidToString(instance->ref_id),
+ UTI_TimespecToString(sample_time), diff);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+pps_stratum(RCL_Instance instance, struct timespec *ts)
+{
+ struct timespec ref_time;
+ int is_synchronised, stratum;
+ unsigned int i;
+ double root_delay, root_dispersion;
+ NTP_Leap leap;
+ uint32_t ref_id;
+ RCL_Instance refclock;
+
+ REF_GetReferenceParams(ts, &is_synchronised, &leap, &stratum,
+ &ref_id, &ref_time, &root_delay, &root_dispersion);
+
+ /* Don't change our stratum if the local reference is active
+ or this is the current source */
+ if (ref_id == instance->ref_id ||
+ (!is_synchronised && leap != LEAP_Unsynchronised))
+ return stratum - 1;
+
+ /* Or the current source is another PPS refclock */
+ for (i = 0; i < ARR_GetSize(refclocks); i++) {
+ refclock = get_refclock(i);
+ if (refclock->ref_id == ref_id &&
+ refclock->pps_active && refclock->lock_ref == -1)
+ return stratum - 1;
+ }
+
+ return 0;
+}
+
+static void
+get_local_stats(RCL_Instance inst, struct timespec *ref, double *freq, double *offset)
+{
+ double offset_sd, freq_sd, skew, root_delay, root_disp;
+ SST_Stats stats = SRC_GetSourcestats(inst->source);
+
+ if (SST_Samples(stats) < SST_GetMinSamples(stats)) {
+ UTI_ZeroTimespec(ref);
+ return;
+ }
+
+ SST_GetTrackingData(stats, ref, offset, &offset_sd, freq, &freq_sd,
+ &skew, &root_delay, &root_disp);
+}
+
+static void
+follow_local(RCL_Instance inst, struct timespec *prev_ref_time, double prev_freq,
+ double prev_offset)
+{
+ SST_Stats stats = SRC_GetSourcestats(inst->source);
+ double freq, dfreq, offset, doffset, elapsed;
+ struct timespec now, ref_time;
+
+ get_local_stats(inst, &ref_time, &freq, &offset);
+
+ if (UTI_IsZeroTimespec(prev_ref_time) || UTI_IsZeroTimespec(&ref_time))
+ return;
+
+ dfreq = (freq - prev_freq) / (1.0 - prev_freq);
+ elapsed = UTI_DiffTimespecsToDouble(&ref_time, prev_ref_time);
+ doffset = offset - elapsed * prev_freq - prev_offset;
+
+ if (!REF_AdjustReference(doffset, dfreq))
+ return;
+
+ LCL_ReadCookedTime(&now, NULL);
+ SST_SlewSamples(stats, &now, dfreq, doffset);
+ SPF_SlewSamples(inst->filter, &now, dfreq, doffset);
+
+ /* Keep the offset close to zero to not lose precision */
+ if (fabs(offset) >= 1.0) {
+ SST_CorrectOffset(stats, -round(offset));
+ SPF_CorrectOffset(inst->filter, -round(offset));
+ }
+}
+
+static void
+poll_timeout(void *arg)
+{
+ NTP_Sample sample;
+ int poll, stratum;
+
+ RCL_Instance inst = (RCL_Instance)arg;
+
+ poll = inst->poll;
+
+ if (inst->driver->poll) {
+ poll = inst->driver_poll;
+ inst->driver->poll(inst);
+ inst->driver_polled++;
+ }
+
+ if (!(inst->driver->poll && inst->driver_polled < (1 << (inst->poll - inst->driver_poll)))) {
+ inst->driver_polled = 0;
+
+ if (SPF_GetFilteredSample(inst->filter, &sample)) {
+ double local_freq, local_offset;
+ struct timespec local_ref_time;
+
+ /* Handle special case when PPS is used with the local reference */
+ if (inst->pps_active && inst->lock_ref == -1)
+ stratum = pps_stratum(inst, &sample.time);
+ else
+ stratum = inst->stratum;
+
+ if (inst->local) {
+ get_local_stats(inst, &local_ref_time, &local_freq, &local_offset);
+ inst->leap_status = LEAP_Unsynchronised;
+ }
+
+ SRC_UpdateReachability(inst->source, 1);
+ SRC_UpdateStatus(inst->source, stratum, inst->leap_status);
+ SRC_AccumulateSample(inst->source, &sample);
+ SRC_SelectSource(inst->source);
+
+ if (inst->local)
+ follow_local(inst, &local_ref_time, local_freq, local_offset);
+
+ log_sample(inst, &sample.time, 1, 0, 0.0, sample.offset, sample.peer_dispersion);
+ } else {
+ SRC_UpdateReachability(inst->source, 0);
+ }
+ }
+
+ inst->timeout_id = SCH_AddTimeoutByDelay(UTI_Log2ToDouble(poll), poll_timeout, arg);
+}
+
+static void
+slew_samples(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(refclocks); i++) {
+ if (change_type == LCL_ChangeUnknownStep)
+ SPF_DropSamples(get_refclock(i)->filter);
+ else
+ SPF_SlewSamples(get_refclock(i)->filter, cooked, dfreq, doffset);
+ }
+}
+
+static void
+add_dispersion(double dispersion, void *anything)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARR_GetSize(refclocks); i++)
+ SPF_AddDispersion(get_refclock(i)->filter, dispersion);
+}
+
+static void
+log_sample(RCL_Instance instance, struct timespec *sample_time, int filtered, int pulse, double raw_offset, double cooked_offset, double dispersion)
+{
+ char sync_stats[4] = {'N', '+', '-', '?'};
+
+ if (logfileid == -1)
+ return;
+
+ if (!filtered) {
+ LOG_FileWrite(logfileid, "%s.%06d %-5s %3d %1c %1d %13.6e %13.6e %10.3e",
+ UTI_TimeToLogForm(sample_time->tv_sec),
+ (int)sample_time->tv_nsec / 1000,
+ UTI_RefidToString(instance->ref_id),
+ instance->driver_polled,
+ sync_stats[instance->leap_status],
+ pulse,
+ raw_offset,
+ cooked_offset,
+ dispersion);
+ } else {
+ LOG_FileWrite(logfileid, "%s.%06d %-5s - %1c - - %13.6e %10.3e",
+ UTI_TimeToLogForm(sample_time->tv_sec),
+ (int)sample_time->tv_nsec / 1000,
+ UTI_RefidToString(instance->ref_id),
+ sync_stats[instance->leap_status],
+ cooked_offset,
+ dispersion);
+ }
+}
diff --git a/refclock.h b/refclock.h
new file mode 100644
index 0000000..7947b62
--- /dev/null
+++ b/refclock.h
@@ -0,0 +1,86 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2009
+ *
+ * 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 refclocks.
+
+ */
+
+#ifndef GOT_REFCLOCK_H
+#define GOT_REFCLOCK_H
+
+#include "srcparams.h"
+#include "sources.h"
+
+typedef struct {
+ char *driver_name;
+ char *driver_parameter;
+ int driver_poll;
+ int poll;
+ int filter_length;
+ int local;
+ int pps_forced;
+ int pps_rate;
+ int min_samples;
+ int max_samples;
+ int sel_options;
+ int max_lock_age;
+ int stratum;
+ int tai;
+ uint32_t ref_id;
+ uint32_t lock_ref_id;
+ double offset;
+ double delay;
+ double precision;
+ double max_dispersion;
+ double pulse_width;
+} RefclockParameters;
+
+typedef struct RCL_Instance_Record *RCL_Instance;
+
+typedef struct {
+ int (*init)(RCL_Instance instance);
+ void (*fini)(RCL_Instance instance);
+ int (*poll)(RCL_Instance instance);
+} RefclockDriver;
+
+extern void RCL_Initialise(void);
+extern void RCL_Finalise(void);
+extern int RCL_AddRefclock(RefclockParameters *params);
+extern void RCL_StartRefclocks(void);
+extern void RCL_ReportSource(RPT_SourceReport *report, struct timespec *now);
+
+/* functions used by drivers */
+extern void RCL_SetDriverData(RCL_Instance instance, void *data);
+extern void *RCL_GetDriverData(RCL_Instance instance);
+extern char *RCL_GetDriverParameter(RCL_Instance instance);
+extern void RCL_CheckDriverOptions(RCL_Instance instance, const char **options);
+extern char *RCL_GetDriverOption(RCL_Instance instance, char *name);
+extern int RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
+ struct timespec *ref_time, int leap);
+extern int RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second);
+extern int RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
+ double second, double dispersion, double raw_correction);
+extern double RCL_GetPrecision(RCL_Instance instance);
+extern int RCL_GetDriverPoll(RCL_Instance instance);
+
+#endif
diff --git a/refclock_phc.c b/refclock_phc.c
new file mode 100644
index 0000000..abda2e8
--- /dev/null
+++ b/refclock_phc.c
@@ -0,0 +1,232 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2013, 2017, 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ PTP hardware clock (PHC) refclock driver.
+
+ */
+
+#include "config.h"
+
+#include "refclock.h"
+
+#ifdef FEAT_PHC
+
+#include "sysincl.h"
+
+#include <sys/sysmacros.h>
+
+#include "array.h"
+#include "refclock.h"
+#include "hwclock.h"
+#include "local.h"
+#include "logging.h"
+#include "memory.h"
+#include "util.h"
+#include "sched.h"
+#include "sys_linux.h"
+
+struct phc_instance {
+ int fd;
+ int dev_index;
+ int mode;
+ int nocrossts;
+ int extpps;
+ int pin;
+ int channel;
+ struct timespec last_extts;
+ HCL_Instance clock;
+};
+
+/* Array of RCL_Instance with enabled extpps */
+static ARR_Instance extts_phcs = NULL;
+
+static void read_ext_pulse(int sockfd, int event, void *anything);
+
+static int phc_initialise(RCL_Instance instance)
+{
+ const char *options[] = {"nocrossts", "extpps", "pin", "channel", "clear", NULL};
+ struct phc_instance *phc;
+ int phc_fd, rising_edge;
+ struct stat st;
+ char *path, *s;
+
+ RCL_CheckDriverOptions(instance, options);
+
+ path = RCL_GetDriverParameter(instance);
+
+ phc_fd = SYS_Linux_OpenPHC(path, 0);
+ if (phc_fd < 0)
+ LOG_FATAL("Could not open PHC");
+
+ phc = MallocNew(struct phc_instance);
+ phc->fd = phc_fd;
+ if (fstat(phc_fd, &st) < 0 || !S_ISCHR(st.st_mode))
+ LOG_FATAL("Could not get PHC index");
+ phc->dev_index = minor(st.st_rdev);
+ phc->mode = 0;
+ phc->nocrossts = RCL_GetDriverOption(instance, "nocrossts") ? 1 : 0;
+ phc->extpps = RCL_GetDriverOption(instance, "extpps") ? 1 : 0;
+ UTI_ZeroTimespec(&phc->last_extts);
+ phc->clock = HCL_CreateInstance(0, 16, UTI_Log2ToDouble(RCL_GetDriverPoll(instance)),
+ RCL_GetPrecision(instance));
+
+ if (phc->extpps) {
+ s = RCL_GetDriverOption(instance, "pin");
+ phc->pin = s ? atoi(s) : 0;
+ s = RCL_GetDriverOption(instance, "channel");
+ phc->channel = s ? atoi(s) : 0;
+ rising_edge = RCL_GetDriverOption(instance, "clear") ? 0 : 1;
+
+ if (!SYS_Linux_SetPHCExtTimestamping(phc->fd, phc->pin, phc->channel,
+ rising_edge, !rising_edge, 1))
+ LOG_FATAL("Could not enable external PHC timestamping");
+
+ SCH_AddFileHandler(phc->fd, SCH_FILE_INPUT, read_ext_pulse, instance);
+
+ if (!extts_phcs)
+ extts_phcs = ARR_CreateInstance(sizeof (RCL_Instance));
+ ARR_AppendElement(extts_phcs, &instance);
+ } else {
+ phc->pin = phc->channel = 0;
+ }
+
+ RCL_SetDriverData(instance, phc);
+ return 1;
+}
+
+static void phc_finalise(RCL_Instance instance)
+{
+ struct phc_instance *phc;
+ unsigned int i;
+
+ phc = (struct phc_instance *)RCL_GetDriverData(instance);
+
+ if (phc->extpps) {
+ SCH_RemoveFileHandler(phc->fd);
+ SYS_Linux_SetPHCExtTimestamping(phc->fd, phc->pin, phc->channel, 0, 0, 0);
+
+ for (i = 0; i < ARR_GetSize(extts_phcs); i++) {
+ if ((*(RCL_Instance *)ARR_GetElement(extts_phcs, i)) == instance)
+ ARR_RemoveElement(extts_phcs, i--);
+ }
+ if (ARR_GetSize(extts_phcs) == 0) {
+ ARR_DestroyInstance(extts_phcs);
+ extts_phcs = NULL;
+ }
+ }
+
+ HCL_DestroyInstance(phc->clock);
+ close(phc->fd);
+ Free(phc);
+}
+
+static void process_ext_pulse(RCL_Instance instance, struct timespec *phc_ts)
+{
+ struct phc_instance *phc;
+ struct timespec local_ts;
+ double local_err;
+
+ phc = RCL_GetDriverData(instance);
+
+ if (UTI_CompareTimespecs(&phc->last_extts, phc_ts) == 0) {
+ DEBUG_LOG("Ignoring duplicated PHC timestamp");
+ return;
+ }
+ phc->last_extts = *phc_ts;
+
+ if (!HCL_CookTime(phc->clock, phc_ts, &local_ts, &local_err))
+ return;
+
+ RCL_AddCookedPulse(instance, &local_ts, 1.0e-9 * local_ts.tv_nsec, local_err,
+ UTI_DiffTimespecsToDouble(phc_ts, &local_ts));
+}
+
+static void read_ext_pulse(int fd, int event, void *anything)
+{
+ RCL_Instance instance;
+ struct phc_instance *phc1, *phc2;
+ struct timespec phc_ts;
+ unsigned int i;
+ int channel;
+
+ if (!SYS_Linux_ReadPHCExtTimestamp(fd, &phc_ts, &channel))
+ return;
+
+ instance = anything;
+ phc1 = RCL_GetDriverData(instance);
+
+ /* The Linux kernel (as of 6.2) has one shared queue of timestamps for all
+ descriptors of the same PHC. Search for all refclocks that expect
+ the timestamp. */
+
+ for (i = 0; i < ARR_GetSize(extts_phcs); i++) {
+ instance = *(RCL_Instance *)ARR_GetElement(extts_phcs, i);
+ phc2 = RCL_GetDriverData(instance);
+ if (!phc2->extpps || phc2->dev_index != phc1->dev_index || phc2->channel != channel)
+ continue;
+ process_ext_pulse(instance, &phc_ts);
+ }
+}
+
+#define PHC_READINGS 25
+
+static int phc_poll(RCL_Instance instance)
+{
+ struct timespec phc_ts, sys_ts, local_ts, readings[PHC_READINGS][3];
+ struct phc_instance *phc;
+ double phc_err, local_err;
+ int n_readings;
+
+ phc = (struct phc_instance *)RCL_GetDriverData(instance);
+
+ n_readings = SYS_Linux_GetPHCReadings(phc->fd, phc->nocrossts, &phc->mode,
+ PHC_READINGS, readings);
+ if (n_readings < 1)
+ return 0;
+
+ if (!HCL_ProcessReadings(phc->clock, n_readings, readings, &phc_ts, &sys_ts, &phc_err))
+ return 0;
+
+ LCL_CookTime(&sys_ts, &local_ts, &local_err);
+ HCL_AccumulateSample(phc->clock, &phc_ts, &local_ts, phc_err + local_err);
+
+ if (phc->extpps)
+ return 0;
+
+ DEBUG_LOG("PHC offset: %+.9f err: %.9f",
+ UTI_DiffTimespecsToDouble(&phc_ts, &sys_ts), phc_err);
+
+ return RCL_AddSample(instance, &sys_ts, &phc_ts, LEAP_Normal);
+}
+
+RefclockDriver RCL_PHC_driver = {
+ phc_initialise,
+ phc_finalise,
+ phc_poll
+};
+
+#else
+
+RefclockDriver RCL_PHC_driver = { NULL, NULL, NULL };
+
+#endif
diff --git a/refclock_pps.c b/refclock_pps.c
new file mode 100644
index 0000000..880c13f
--- /dev/null
+++ b/refclock_pps.c
@@ -0,0 +1,159 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2009
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ PPSAPI refclock driver.
+
+ */
+
+#include "config.h"
+
+#include "refclock.h"
+
+#if FEAT_PPS
+
+#if defined(HAVE_SYS_TIMEPPS_H)
+#include <sys/timepps.h>
+#elif defined(HAVE_TIMEPPS_H)
+#include <timepps.h>
+#endif
+
+#include "logging.h"
+#include "memory.h"
+#include "util.h"
+
+struct pps_instance {
+ pps_handle_t handle;
+ pps_seq_t last_seq;
+ int edge_clear;
+};
+
+static int pps_initialise(RCL_Instance instance) {
+ const char *options[] = {"clear", NULL};
+ pps_handle_t handle;
+ pps_params_t params;
+ struct pps_instance *pps;
+ int fd, edge_clear, mode;
+ char *path;
+
+ RCL_CheckDriverOptions(instance, options);
+
+ path = RCL_GetDriverParameter(instance);
+ edge_clear = RCL_GetDriverOption(instance, "clear") ? 1 : 0;
+
+ fd = open(path, O_RDWR);
+ if (fd < 0)
+ LOG_FATAL("Could not open %s : %s", path, strerror(errno));
+
+ UTI_FdSetCloexec(fd);
+
+ if (time_pps_create(fd, &handle) < 0)
+ LOG_FATAL("time_pps_create() failed on %s : %s", path, strerror(errno));
+
+ if (time_pps_getcap(handle, &mode) < 0)
+ LOG_FATAL("time_pps_getcap() failed on %s : %s", path, strerror(errno));
+
+ if (time_pps_getparams(handle, &params) < 0)
+ LOG_FATAL("time_pps_getparams() failed on %s : %s", path, strerror(errno));
+
+ if (!edge_clear) {
+ if (!(mode & PPS_CAPTUREASSERT))
+ LOG_FATAL("CAPTUREASSERT not supported on %s", path);
+
+ params.mode |= PPS_CAPTUREASSERT;
+ params.mode &= ~PPS_CAPTURECLEAR;
+ } else {
+ if (!(mode & PPS_CAPTURECLEAR))
+ LOG_FATAL("CAPTURECLEAR not supported on %s", path);
+
+ params.mode |= PPS_CAPTURECLEAR;
+ params.mode &= ~PPS_CAPTUREASSERT;
+ }
+
+ if (time_pps_setparams(handle, &params) < 0)
+ LOG_FATAL("time_pps_setparams() failed on %s : %s", path, strerror(errno));
+
+ pps = MallocNew(struct pps_instance);
+ pps->handle = handle;
+ pps->last_seq = 0;
+ pps->edge_clear = edge_clear;
+
+ RCL_SetDriverData(instance, pps);
+ return 1;
+}
+
+static void pps_finalise(RCL_Instance instance)
+{
+ struct pps_instance *pps;
+
+ pps = (struct pps_instance *)RCL_GetDriverData(instance);
+ time_pps_destroy(pps->handle);
+ Free(pps);
+}
+
+static int pps_poll(RCL_Instance instance)
+{
+ struct pps_instance *pps;
+ struct timespec ts;
+ pps_info_t pps_info;
+ pps_seq_t seq;
+
+ pps = (struct pps_instance *)RCL_GetDriverData(instance);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ if (time_pps_fetch(pps->handle, PPS_TSFMT_TSPEC, &pps_info, &ts) < 0) {
+ LOG(LOGS_ERR, "time_pps_fetch() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ if (!pps->edge_clear) {
+ seq = pps_info.assert_sequence;
+ ts = pps_info.assert_timestamp;
+ } else {
+ seq = pps_info.clear_sequence;
+ ts = pps_info.clear_timestamp;
+ }
+
+ if (seq == pps->last_seq || UTI_IsZeroTimespec(&ts)) {
+ DEBUG_LOG("PPS sample ignored seq=%lu ts=%s",
+ (unsigned long)seq, UTI_TimespecToString(&ts));
+ return 0;
+ }
+
+ pps->last_seq = seq;
+
+ return RCL_AddPulse(instance, &ts, 1.0e-9 * ts.tv_nsec);
+}
+
+RefclockDriver RCL_PPS_driver = {
+ pps_initialise,
+ pps_finalise,
+ pps_poll
+};
+
+#else
+
+RefclockDriver RCL_PPS_driver = { NULL, NULL, NULL };
+
+#endif
diff --git a/refclock_shm.c b/refclock_shm.c
new file mode 100644
index 0000000..ee13e87
--- /dev/null
+++ b/refclock_shm.c
@@ -0,0 +1,134 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2009
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ SHM refclock driver.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "refclock.h"
+#include "logging.h"
+#include "util.h"
+
+#define SHMKEY 0x4e545030
+
+struct shmTime {
+ int mode; /* 0 - if valid set
+ * use values,
+ * clear valid
+ * 1 - if valid set
+ * if count before and after read of values is equal,
+ * use values
+ * clear valid
+ */
+ volatile int count;
+ time_t clockTimeStampSec;
+ int clockTimeStampUSec;
+ time_t receiveTimeStampSec;
+ int receiveTimeStampUSec;
+ int leap;
+ int precision;
+ int nsamples;
+ volatile int valid;
+ int clockTimeStampNSec;
+ int receiveTimeStampNSec;
+ int dummy[8];
+};
+
+static int shm_initialise(RCL_Instance instance) {
+ const char *options[] = {"perm", NULL};
+ int id, param, perm;
+ char *s;
+ struct shmTime *shm;
+
+ RCL_CheckDriverOptions(instance, options);
+
+ param = atoi(RCL_GetDriverParameter(instance));
+ s = RCL_GetDriverOption(instance, "perm");
+ perm = s ? strtol(s, NULL, 8) & 0777 : 0600;
+
+ id = shmget(SHMKEY + param, sizeof (struct shmTime), IPC_CREAT | perm);
+ if (id == -1) {
+ LOG_FATAL("shmget() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ shm = (struct shmTime *)shmat(id, 0, 0);
+ if ((long)shm == -1) {
+ LOG_FATAL("shmat() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ RCL_SetDriverData(instance, shm);
+ return 1;
+}
+
+static void shm_finalise(RCL_Instance instance)
+{
+ shmdt(RCL_GetDriverData(instance));
+}
+
+static int shm_poll(RCL_Instance instance)
+{
+ struct timespec receive_ts, clock_ts;
+ struct shmTime t, *shm;
+
+ shm = (struct shmTime *)RCL_GetDriverData(instance);
+
+ t = *shm;
+
+ if ((t.mode == 1 && t.count != shm->count) ||
+ !(t.mode == 0 || t.mode == 1) || !t.valid) {
+ DEBUG_LOG("SHM sample ignored mode=%d count=%d valid=%d",
+ t.mode, t.count, t.valid);
+ return 0;
+ }
+
+ shm->valid = 0;
+
+ receive_ts.tv_sec = t.receiveTimeStampSec;
+ clock_ts.tv_sec = t.clockTimeStampSec;
+
+ if (t.clockTimeStampNSec / 1000 == t.clockTimeStampUSec &&
+ t.receiveTimeStampNSec / 1000 == t.receiveTimeStampUSec) {
+ receive_ts.tv_nsec = t.receiveTimeStampNSec;
+ clock_ts.tv_nsec = t.clockTimeStampNSec;
+ } else {
+ receive_ts.tv_nsec = 1000 * t.receiveTimeStampUSec;
+ clock_ts.tv_nsec = 1000 * t.clockTimeStampUSec;
+ }
+
+ UTI_NormaliseTimespec(&clock_ts);
+ UTI_NormaliseTimespec(&receive_ts);
+
+ return RCL_AddSample(instance, &receive_ts, &clock_ts, t.leap);
+}
+
+RefclockDriver RCL_SHM_driver = {
+ shm_initialise,
+ shm_finalise,
+ shm_poll
+};
diff --git a/refclock_sock.c b/refclock_sock.c
new file mode 100644
index 0000000..2da57ef
--- /dev/null
+++ b/refclock_sock.c
@@ -0,0 +1,176 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2009
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Unix domain socket refclock driver.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "refclock.h"
+#include "logging.h"
+#include "util.h"
+#include "sched.h"
+#include "socket.h"
+
+#define SOCK_MAGIC 0x534f434b
+
+struct sock_sample {
+ /* Time of the measurement (system time) */
+ struct timeval tv;
+
+ /* Offset between the true time and the system time (in seconds) */
+ double offset;
+
+ /* Non-zero if the sample is from a PPS signal, i.e. another source
+ is needed to obtain seconds */
+ int pulse;
+
+ /* 0 - normal, 1 - insert leap second, 2 - delete leap second */
+ int leap;
+
+ /* Padding, ignored */
+ int _pad;
+
+ /* Protocol identifier (0x534f434b) */
+ int magic;
+};
+
+/* On 32-bit glibc-based systems enable conversion between timevals using
+ 32-bit and 64-bit time_t to support SOCK clients compiled with different
+ time_t size than chrony */
+#ifdef __GLIBC_PREREQ
+#if __GLIBC_PREREQ(2, 34) && __TIMESIZE == 32
+#define CONVERT_TIMEVAL 1
+#if defined(_TIME_BITS) && _TIME_BITS == 64
+typedef int32_t alt_time_t;
+typedef int32_t alt_suseconds_t;
+#else
+typedef int64_t alt_time_t;
+typedef int64_t alt_suseconds_t;
+#endif
+struct alt_timeval {
+ alt_time_t tv_sec;
+ alt_suseconds_t tv_usec;
+};
+#endif
+#endif
+
+static void read_sample(int sockfd, int event, void *anything)
+{
+ char buf[sizeof (struct sock_sample) + 16];
+ struct timespec sys_ts, ref_ts;
+ struct sock_sample sample;
+ RCL_Instance instance;
+ int s;
+
+ instance = (RCL_Instance)anything;
+
+ s = recv(sockfd, buf, sizeof (buf), 0);
+
+ if (s < 0) {
+ DEBUG_LOG("Could not read SOCK sample : %s", strerror(errno));
+ return;
+ }
+
+ if (s == sizeof (sample)) {
+ memcpy(&sample, buf, sizeof (sample));
+#ifdef CONVERT_TIMEVAL
+ } else if (s == sizeof (sample) - sizeof (struct timeval) + sizeof (struct alt_timeval)) {
+ struct alt_timeval atv;
+ memcpy(&atv, buf, sizeof (atv));
+#ifndef HAVE_LONG_TIME_T
+ if (atv.tv_sec > INT32_MAX || atv.tv_sec < INT32_MIN ||
+ atv.tv_usec > INT32_MAX || atv.tv_usec < INT32_MIN) {
+ DEBUG_LOG("Could not convert 64-bit timeval");
+ return;
+ }
+#endif
+ sample.tv.tv_sec = atv.tv_sec;
+ sample.tv.tv_usec = atv.tv_usec;
+ DEBUG_LOG("Converted %d-bit timeval", 8 * (int)sizeof (alt_time_t));
+ memcpy((char *)&sample + sizeof (struct timeval), buf + sizeof (struct alt_timeval),
+ sizeof (sample) - sizeof (struct timeval));
+#endif
+ } else {
+ DEBUG_LOG("Unexpected length of SOCK sample : %d != %ld",
+ s, (long)sizeof (sample));
+ return;
+ }
+
+ if (sample.magic != SOCK_MAGIC) {
+ DEBUG_LOG("Unexpected magic number in SOCK sample : %x != %x",
+ (unsigned int)sample.magic, (unsigned int)SOCK_MAGIC);
+ return;
+ }
+
+ UTI_TimevalToTimespec(&sample.tv, &sys_ts);
+ UTI_NormaliseTimespec(&sys_ts);
+
+ if (!UTI_IsTimeOffsetSane(&sys_ts, sample.offset))
+ return;
+
+ UTI_AddDoubleToTimespec(&sys_ts, sample.offset, &ref_ts);
+
+ if (sample.pulse) {
+ RCL_AddPulse(instance, &sys_ts, sample.offset);
+ } else {
+ RCL_AddSample(instance, &sys_ts, &ref_ts, sample.leap);
+ }
+}
+
+static int sock_initialise(RCL_Instance instance)
+{
+ int sockfd;
+ char *path;
+
+ RCL_CheckDriverOptions(instance, NULL);
+
+ path = RCL_GetDriverParameter(instance);
+
+ 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);
+ return 1;
+}
+
+static void sock_finalise(RCL_Instance instance)
+{
+ int sockfd;
+
+ sockfd = (long)RCL_GetDriverData(instance);
+ SCH_RemoveFileHandler(sockfd);
+ SCK_RemoveSocket(sockfd);
+ SCK_CloseSocket(sockfd);
+}
+
+RefclockDriver RCL_SOCK_driver = {
+ sock_initialise,
+ sock_finalise,
+ NULL
+};
diff --git a/reference.c b/reference.c
new file mode 100644
index 0000000..97dfbe9
--- /dev/null
+++ b/reference.c
@@ -0,0 +1,1441 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009-2018, 2020, 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ This module keeps track of the source which we are claiming to be
+ our reference, for the purposes of generating outgoing NTP packets */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "memory.h"
+#include "reference.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "local.h"
+#include "sched.h"
+
+/* ================================================== */
+
+/* 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;
+static int our_stratum;
+static uint32_t our_ref_id;
+static IPAddr our_ref_ip;
+static struct timespec our_ref_time;
+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;
+
+static double last_offset;
+static double avg2_offset;
+static int avg2_moving;
+
+static double correction_time_ratio;
+
+/* Flag indicating that we are initialised */
+static int initialised = 0;
+
+/* Current operating mode */
+static REF_Mode mode;
+
+/* Threshold and update limit for stepping clock */
+static int make_step_limit;
+static double make_step_threshold;
+
+/* Number of updates before offset checking, number of ignored updates
+ before exiting and the maximum allowed offset */
+static int max_offset_delay;
+static int max_offset_ignore;
+static double max_offset;
+
+/* Threshold for logging clock changes to syslog */
+static double log_change_threshold;
+
+/* Flag, threshold and user for sending mail notification on large clock changes */
+static int do_mail_change;
+static double mail_change_threshold;
+static char *mail_change_user;
+
+/* Handler for mode ending */
+static REF_ModeEndHandler mode_end_handler = NULL;
+
+/* Filename of the drift file. */
+static char *drift_file=NULL;
+static double drift_file_age;
+
+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;
+
+/* Timer for the leap second handler */
+static SCH_TimeoutID leap_timeout_id;
+
+/* Name of a system timezone containing leap seconds occuring at midnight */
+static char *leap_tzname;
+
+/* ================================================== */
+
+static LOG_FileID logfileid;
+
+/* ================================================== */
+
+/* Exponential moving averages of absolute clock frequencies
+ used as a fallback when synchronisation is lost. */
+
+struct fb_drift {
+ double freq;
+ double secs;
+};
+
+static int fb_drift_min;
+static int fb_drift_max;
+
+static struct fb_drift *fb_drifts = NULL;
+static int next_fb_drift;
+static SCH_TimeoutID fb_drift_timeout_id;
+
+/* Monotonic timestamp of the last reference update */
+static double last_ref_update;
+static double last_ref_update_interval;
+
+static double last_ref_adjustment;
+static int ref_adjustments;
+
+/* ================================================== */
+
+static NTP_Leap get_tz_leap(time_t when, int *tai_offset);
+static void update_leap_status(NTP_Leap leap, time_t now, int reset);
+
+/* ================================================== */
+
+static void
+handle_slew(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything)
+{
+ double delta;
+ struct timespec now;
+
+ if (!UTI_IsZeroTimespec(&our_ref_time))
+ UTI_AdjustTimespec(&our_ref_time, cooked, &our_ref_time, &delta, dfreq, doffset);
+
+ if (change_type == LCL_ChangeUnknownStep) {
+ last_ref_update = 0.0;
+ REF_SetUnsynchronised();
+ }
+
+ /* When the clock was stepped, check if that doesn't change our leap status
+ and also reset the leap timeout to undo the shift in the scheduler */
+ if (change_type != LCL_ChangeAdjust && our_leap_sec && !leap_in_progress) {
+ LCL_ReadRawTime(&now);
+ update_leap_status(our_leap_status, now.tv_sec, 1);
+ }
+}
+
+/* ================================================== */
+
+void
+REF_Initialise(void)
+{
+ FILE *in;
+ double file_freq_ppm, file_skew_ppm;
+ double our_frequency_ppm;
+ int tai_offset;
+
+ mode = REF_ModeNormal;
+ are_we_synchronised = 0;
+ our_leap_status = LEAP_Unsynchronised;
+ our_leap_sec = 0;
+ our_tai_offset = 0;
+ initialised = 1;
+ our_root_dispersion = 1.0;
+ our_root_delay = 1.0;
+ 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 = 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 */
+ our_frequency_ppm = file_freq_ppm;
+ our_skew = 1.0e-6 * file_skew_ppm;
+ if (our_skew < MIN_SKEW)
+ our_skew = MIN_SKEW;
+ LOG(LOGS_INFO, "Frequency %.3f +/- %.3f ppm read from %s",
+ file_freq_ppm, file_skew_ppm, drift_file);
+ LCL_SetAbsoluteFrequency(our_frequency_ppm);
+ } else {
+ LOG(LOGS_WARN, "Could not read valid frequency and skew from driftfile %s",
+ drift_file);
+ }
+ fclose(in);
+ }
+ }
+
+ if (our_frequency_ppm == 0.0) {
+ our_frequency_ppm = LCL_ReadAbsoluteFrequency();
+ if (our_frequency_ppm != 0.0) {
+ LOG(LOGS_INFO, "Initial frequency %.3f ppm", our_frequency_ppm);
+ }
+ }
+
+ logfileid = CNF_GetLogTracking() ? LOG_FileOpen("tracking",
+ " Date (UTC) Time IP Address St Freq ppm Skew ppm Offset L Co Offset sd Rem. corr. Root delay Root disp. Max. error")
+ : -1;
+
+ max_update_skew = fabs(CNF_GetMaxUpdateSkew()) * 1.0e-6;
+
+ 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();
+ /* Switch to step mode if the system driver doesn't support leap */
+ if (leap_mode == REF_LeapModeSystem && !LCL_CanSystemLeap())
+ leap_mode = REF_LeapModeStep;
+
+ leap_tzname = CNF_GetLeapSecTimezone();
+ if (leap_tzname) {
+ /* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */
+ if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 &&
+ get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) {
+ LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname);
+ } else {
+ LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname);
+ leap_tzname = NULL;
+ }
+ }
+
+ CNF_GetMakeStep(&make_step_limit, &make_step_threshold);
+ CNF_GetMaxChange(&max_offset_delay, &max_offset_ignore, &max_offset);
+ CNF_GetMailOnChange(&do_mail_change, &mail_change_threshold, &mail_change_user);
+ log_change_threshold = CNF_GetLogChange();
+
+ CNF_GetFallbackDrifts(&fb_drift_min, &fb_drift_max);
+
+ if (fb_drift_max >= fb_drift_min && fb_drift_min > 0) {
+ fb_drifts = MallocArray(struct fb_drift, fb_drift_max - fb_drift_min + 1);
+ memset(fb_drifts, 0, sizeof (struct fb_drift) * (fb_drift_max - fb_drift_min + 1));
+ next_fb_drift = 0;
+ fb_drift_timeout_id = 0;
+ }
+
+ UTI_ZeroTimespec(&our_ref_time);
+ last_ref_update = 0.0;
+ last_ref_update_interval = 0.0;
+ last_ref_adjustment = 0.0;
+ ref_adjustments = 0;
+
+ LCL_AddParameterChangeHandler(handle_slew, NULL);
+
+ /* Make first entry in tracking log */
+ REF_SetUnsynchronised();
+}
+
+/* ================================================== */
+
+void
+REF_Finalise(void)
+{
+ update_leap_status(LEAP_Unsynchronised, 0, 0);
+
+ if (drift_file) {
+ update_drift_file(LCL_ReadAbsoluteFrequency(), our_skew);
+ }
+
+ LCL_RemoveParameterChangeHandler(handle_slew, NULL);
+
+ Free(fb_drifts);
+
+ initialised = 0;
+}
+
+/* ================================================== */
+
+void REF_SetMode(REF_Mode new_mode)
+{
+ mode = new_mode;
+}
+
+/* ================================================== */
+
+REF_Mode
+REF_GetMode(void)
+{
+ return mode;
+}
+
+/* ================================================== */
+
+void
+REF_SetModeEndHandler(REF_ModeEndHandler handler)
+{
+ mode_end_handler = handler;
+}
+
+/* ================================================== */
+
+REF_LeapMode
+REF_GetLeapMode(void)
+{
+ return leap_mode;
+}
+
+/* ================================================== */
+/* Update the drift coefficients to the file. */
+
+static void
+update_drift_file(double freq_ppm, double skew)
+{
+ FILE *out;
+
+ /* Create a temporary file with a '.tmp' extension. */
+ out = UTI_OpenFile(NULL, drift_file, ".tmp", 'w', 0644);
+ if (!out)
+ return;
+
+ /* Write the frequency and skew parameters in ppm */
+ fprintf(out, "%20.6f %20.6f\n", freq_ppm, 1.0e6 * skew);
+ fclose(out);
+
+ /* Rename the temporary file to the correct location */
+ if (!UTI_RenameTempFile(NULL, drift_file, ".tmp", NULL))
+ ;
+}
+
+/* ================================================== */
+
+static void
+update_fb_drifts(double freq_ppm, double update_interval)
+{
+ int i, secs;
+
+ assert(are_we_synchronised);
+
+ if (next_fb_drift > 0) {
+#if 0
+ /* Reset drifts that were used when we were unsynchronised */
+ for (i = 0; i < next_fb_drift - fb_drift_min; i++)
+ fb_drifts[i].secs = 0.0;
+#endif
+ next_fb_drift = 0;
+ }
+
+ SCH_RemoveTimeout(fb_drift_timeout_id);
+ fb_drift_timeout_id = 0;
+
+ if (update_interval < 1.0 || update_interval > last_ref_update_interval * 4.0)
+ return;
+
+ for (i = 0; i < fb_drift_max - fb_drift_min + 1; i++) {
+ secs = 1 << (i + fb_drift_min);
+ if (fb_drifts[i].secs < secs) {
+ /* Calculate average over 2 * secs interval before switching to
+ exponential updating */
+ fb_drifts[i].freq = (fb_drifts[i].freq * fb_drifts[i].secs +
+ update_interval * 0.5 * freq_ppm) / (update_interval * 0.5 + fb_drifts[i].secs);
+ fb_drifts[i].secs += update_interval * 0.5;
+ } else {
+ /* Update exponential moving average. The smoothing factor for update
+ interval equal to secs is about 0.63, for half interval about 0.39,
+ for double interval about 0.86. */
+ fb_drifts[i].freq += (1 - 1.0 / exp(update_interval / secs)) *
+ (freq_ppm - fb_drifts[i].freq);
+ }
+
+ DEBUG_LOG("Fallback drift %d updated: %f ppm %f seconds",
+ i + fb_drift_min, fb_drifts[i].freq, fb_drifts[i].secs);
+ }
+}
+
+/* ================================================== */
+
+static void
+fb_drift_timeout(void *arg)
+{
+ assert(next_fb_drift >= fb_drift_min && next_fb_drift <= fb_drift_max);
+
+ fb_drift_timeout_id = 0;
+
+ DEBUG_LOG("Fallback drift %d active: %f ppm",
+ next_fb_drift, fb_drifts[next_fb_drift - fb_drift_min].freq);
+ LCL_SetAbsoluteFrequency(fb_drifts[next_fb_drift - fb_drift_min].freq);
+ REF_SetUnsynchronised();
+}
+
+/* ================================================== */
+
+static void
+schedule_fb_drift(void)
+{
+ int i, c, secs;
+ double unsynchronised, now;
+
+ if (fb_drift_timeout_id)
+ return; /* already scheduled */
+
+ now = SCH_GetLastEventMonoTime();
+ unsynchronised = now - last_ref_update;
+
+ for (c = secs = 0, i = fb_drift_min; i <= fb_drift_max; i++) {
+ secs = 1 << i;
+
+ if (fb_drifts[i - fb_drift_min].secs < secs)
+ continue;
+
+ if (unsynchronised < secs && i > next_fb_drift)
+ break;
+
+ c = i;
+ }
+
+ if (c > next_fb_drift) {
+ LCL_SetAbsoluteFrequency(fb_drifts[c - fb_drift_min].freq);
+ next_fb_drift = c;
+ DEBUG_LOG("Fallback drift %d set", c);
+ }
+
+ if (i <= fb_drift_max) {
+ next_fb_drift = i;
+ fb_drift_timeout_id = SCH_AddTimeoutByDelay(secs - unsynchronised, fb_drift_timeout, NULL);
+ DEBUG_LOG("Fallback drift %d scheduled", i);
+ }
+}
+
+/* ================================================== */
+
+static void
+end_ref_mode(int result)
+{
+ mode = REF_ModeIgnore;
+
+ /* Dispatch the handler */
+ if (mode_end_handler)
+ (mode_end_handler)(result);
+}
+
+/* ================================================== */
+
+#define BUFLEN 255
+#define S_MAX_USER_LEN "128"
+
+static void
+maybe_log_offset(double offset, time_t now)
+{
+ double abs_offset;
+ FILE *p;
+ char buffer[BUFLEN], host[BUFLEN];
+ struct tm *tm;
+
+ abs_offset = fabs(offset);
+
+ if (abs_offset > log_change_threshold) {
+ LOG(LOGS_WARN, "System clock wrong by %.6f seconds", -offset);
+ }
+
+ if (do_mail_change &&
+ (abs_offset > mail_change_threshold)) {
+ snprintf(buffer, sizeof (buffer), "%s -t", MAIL_PROGRAM);
+ p = popen(buffer, "w");
+ if (p) {
+ if (gethostname(host, sizeof(host)) < 0) {
+ strcpy(host, "<UNKNOWN>");
+ }
+ host[sizeof (host) - 1] = '\0';
+
+ fprintf(p, "To: %s\n", mail_change_user);
+ fprintf(p, "Subject: chronyd reports change to system clock on node [%s]\n", host);
+ fputs("\n", p);
+
+ tm = localtime(&now);
+ if (tm) {
+ strftime(buffer, sizeof (buffer),
+ "On %A, %d %B %Y\n with the system clock reading %H:%M:%S (%Z)", tm);
+ fputs(buffer, p);
+ }
+
+ /* If offset < 0 the local clock is slow, so we are applying a
+ positive change to it to bring it into line, hence the
+ negation of 'offset' in the next statement (and earlier) */
+ fprintf(p,
+ "\n\nchronyd started to apply an adjustment of %.3f seconds to it,\n"
+ " which exceeded the reporting threshold of %.3f seconds\n\n",
+ -offset, mail_change_threshold);
+ pclose(p);
+ } else {
+ LOG(LOGS_ERR, "Could not send mail notification to user %s\n",
+ mail_change_user);
+ }
+ }
+
+}
+
+/* ================================================== */
+
+static int
+is_step_limit_reached(double offset, double offset_correction)
+{
+ if (make_step_limit == 0) {
+ return 0;
+ } else if (make_step_limit > 0) {
+ make_step_limit--;
+ }
+ return fabs(offset - offset_correction) > make_step_threshold;
+}
+
+/* ================================================== */
+
+static int
+is_offset_ok(double offset)
+{
+ if (max_offset_delay < 0)
+ return 1;
+
+ if (max_offset_delay > 0) {
+ max_offset_delay--;
+ return 1;
+ }
+
+ if (fabs(offset) > max_offset) {
+ LOG(LOGS_WARN,
+ "Adjustment of %.3f seconds exceeds the allowed maximum of %.3f seconds (%s) ",
+ -offset, max_offset, !max_offset_ignore ? "exiting" : "ignored");
+ if (!max_offset_ignore)
+ end_ref_mode(0);
+ else if (max_offset_ignore > 0)
+ max_offset_ignore--;
+ return 0;
+ }
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+is_leap_second_day(time_t when)
+{
+ struct tm *stm;
+
+ stm = gmtime(&when);
+ if (!stm)
+ return 0;
+
+ /* Allow leap second only on the last day of June and December */
+ return (stm->tm_mon == 5 && stm->tm_mday == 30) ||
+ (stm->tm_mon == 11 && stm->tm_mday == 31);
+}
+
+/* ================================================== */
+
+static NTP_Leap
+get_tz_leap(time_t when, int *tai_offset)
+{
+ static time_t last_tz_leap_check;
+ static NTP_Leap tz_leap;
+ static int tz_tai_offset;
+
+ struct tm stm, *tm;
+ time_t t;
+ char *tz_env, tz_orig[128];
+
+ *tai_offset = tz_tai_offset;
+
+ /* Do this check at most twice a day */
+ when = when / (12 * 3600) * (12 * 3600);
+ if (last_tz_leap_check == when)
+ return tz_leap;
+
+ last_tz_leap_check = when;
+ tz_leap = LEAP_Normal;
+ tz_tai_offset = 0;
+
+ tm = gmtime(&when);
+ if (!tm)
+ return tz_leap;
+
+ stm = *tm;
+
+ /* Temporarily switch to the timezone containing leap seconds */
+ tz_env = getenv("TZ");
+ if (tz_env) {
+ if (strlen(tz_env) >= sizeof (tz_orig))
+ return tz_leap;
+ strcpy(tz_orig, tz_env);
+ }
+ setenv("TZ", leap_tzname, 1);
+ tzset();
+
+ /* Get the TAI-UTC offset, which started at the epoch at 10 seconds */
+ t = mktime(&stm);
+ if (t != -1)
+ tz_tai_offset = t - when + 10;
+
+ /* Set the time to 23:59:60 and see how it overflows in mktime() */
+ stm.tm_sec = 60;
+ stm.tm_min = 59;
+ stm.tm_hour = 23;
+
+ t = mktime(&stm);
+
+ if (tz_env)
+ setenv("TZ", tz_orig, 1);
+ else
+ unsetenv("TZ");
+ tzset();
+
+ if (t == -1)
+ return tz_leap;
+
+ if (stm.tm_sec == 60)
+ tz_leap = LEAP_InsertSecond;
+ else if (stm.tm_sec == 1)
+ tz_leap = LEAP_DeleteSecond;
+
+ *tai_offset = tz_tai_offset;
+
+ return tz_leap;
+}
+
+/* ================================================== */
+
+static void
+leap_end_timeout(void *arg)
+{
+ leap_timeout_id = 0;
+ leap_in_progress = 0;
+
+ if (our_tai_offset)
+ our_tai_offset += our_leap_sec;
+ our_leap_sec = 0;
+
+ if (leap_mode == REF_LeapModeSystem)
+ LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
+
+ if (our_leap_status == LEAP_InsertSecond ||
+ our_leap_status == LEAP_DeleteSecond)
+ our_leap_status = LEAP_Normal;
+}
+
+/* ================================================== */
+
+static void
+leap_start_timeout(void *arg)
+{
+ leap_in_progress = 1;
+
+ switch (leap_mode) {
+ case REF_LeapModeSystem:
+ DEBUG_LOG("Waiting for system clock leap second correction");
+ break;
+ case REF_LeapModeSlew:
+ LCL_NotifyLeap(our_leap_sec);
+ LCL_AccumulateOffset(our_leap_sec, 0.0);
+ LOG(LOGS_WARN, "Adjusting system clock for leap second");
+ break;
+ case REF_LeapModeStep:
+ LCL_NotifyLeap(our_leap_sec);
+ LCL_ApplyStepOffset(our_leap_sec);
+ LOG(LOGS_WARN, "System clock was stepped for leap second");
+ break;
+ case REF_LeapModeIgnore:
+ LOG(LOGS_WARN, "Ignoring leap second");
+ break;
+ default:
+ break;
+ }
+
+ /* Wait until the leap second is over with some extra room to be safe */
+ leap_timeout_id = SCH_AddTimeoutByDelay(2.0, leap_end_timeout, NULL);
+}
+
+/* ================================================== */
+
+static void
+set_leap_timeout(time_t now)
+{
+ struct timespec when;
+
+ /* Stop old timer if there is one */
+ SCH_RemoveTimeout(leap_timeout_id);
+ leap_timeout_id = 0;
+ leap_in_progress = 0;
+
+ 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 = leap_when;
+ when.tv_nsec = 0;
+ if (our_leap_sec < 0)
+ when.tv_sec--;
+ if (leap_mode == REF_LeapModeSystem) {
+ when.tv_sec--;
+ when.tv_nsec = 500000000;
+ }
+
+ leap_timeout_id = SCH_AddTimeout(&when, leap_start_timeout, NULL);
+}
+
+/* ================================================== */
+
+static void
+update_leap_status(NTP_Leap leap, time_t now, int reset)
+{
+ NTP_Leap tz_leap;
+ int leap_sec, tai_offset;
+
+ leap_sec = 0;
+ tai_offset = 0;
+
+ if (leap_tzname && now) {
+ tz_leap = get_tz_leap(now, &tai_offset);
+ if (leap == LEAP_Normal)
+ leap = tz_leap;
+ }
+
+ if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) {
+ /* Check that leap second is allowed today */
+
+ if (is_leap_second_day(now)) {
+ if (leap == LEAP_InsertSecond) {
+ leap_sec = 1;
+ } else {
+ leap_sec = -1;
+ }
+ } else {
+ leap = LEAP_Normal;
+ }
+ }
+
+ if ((leap_sec != our_leap_sec || tai_offset != our_tai_offset)
+ && !REF_IsLeapSecondClose(NULL, 0.0)) {
+ our_leap_sec = leap_sec;
+ our_tai_offset = tai_offset;
+
+ switch (leap_mode) {
+ case REF_LeapModeSystem:
+ LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
+ /* Fall through */
+ case REF_LeapModeSlew:
+ case REF_LeapModeStep:
+ case REF_LeapModeIgnore:
+ set_leap_timeout(now);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ } else if (reset) {
+ set_leap_timeout(now);
+ }
+
+ our_leap_status = leap;
+}
+
+/* ================================================== */
+
+static double
+get_root_dispersion(struct timespec *ts)
+{
+ if (UTI_IsZeroTimespec(&our_ref_time))
+ return 1.0;
+
+ return our_root_dispersion +
+ fabs(UTI_DiffTimespecsToDouble(ts, &our_ref_time)) *
+ (our_skew + fabs(our_residual_freq) + LCL_GetMaxClockError());
+}
+
+/* ================================================== */
+
+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,
+ double orig_root_distance)
+{
+ const char leap_codes[4] = {'N', '+', '-', '?'};
+ double root_dispersion, max_error;
+ static double last_sys_offset = 0.0;
+
+ if (logfileid == -1)
+ return;
+
+ max_error = orig_root_distance + fabs(last_sys_offset);
+ root_dispersion = get_root_dispersion(now);
+ last_sys_offset = offset - uncorrected_offset;
+
+ LOG_FileWrite(logfileid,
+ "%s %-15s %2d %10.3f %10.3f %10.3e %1c %2d %10.3e %10.3e %10.3e %10.3e %10.3e",
+ UTI_TimeToLogForm(now->tv_sec),
+ our_ref_ip.family != IPADDR_UNSPEC ?
+ UTI_IPToString(&our_ref_ip) : UTI_RefidToString(our_ref_id),
+ our_stratum, freq, 1.0e6 * our_skew, offset,
+ leap_codes[our_leap_status], combined_sources, offset_sd,
+ uncorrected_offset, our_root_delay, root_dispersion, max_error);
+}
+
+/* ================================================== */
+
+static void
+special_mode_sync(int valid, double offset)
+{
+ int step;
+
+ switch (mode) {
+ case REF_ModeInitStepSlew:
+ if (!valid) {
+ LOG(LOGS_WARN, "No suitable source for initstepslew");
+ end_ref_mode(0);
+ break;
+ }
+
+ step = fabs(offset) >= CNF_GetInitStepThreshold();
+
+ LOG(LOGS_INFO, "System's initial offset : %.6f seconds %s of true (%s)",
+ fabs(offset), offset >= 0 ? "fast" : "slow", step ? "step" : "slew");
+
+ if (step)
+ LCL_ApplyStepOffset(offset);
+ else
+ LCL_AccumulateOffset(offset, 0.0);
+
+ end_ref_mode(1);
+
+ break;
+ case REF_ModeUpdateOnce:
+ case REF_ModePrintOnce:
+ if (!valid) {
+ LOG(LOGS_WARN, "No suitable source for synchronisation");
+ end_ref_mode(0);
+ break;
+ }
+
+ step = mode == REF_ModeUpdateOnce;
+
+ LOG(LOGS_INFO, "System clock wrong by %.6f seconds (%s)",
+ -offset, step ? "step" : "ignored");
+
+ if (step)
+ LCL_ApplyStepOffset(offset);
+
+ end_ref_mode(1);
+
+ break;
+ case REF_ModeIgnore:
+ /* Do nothing until the mode is changed */
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+static void
+get_clock_estimates(int manual,
+ double measured_freq, double measured_skew,
+ double *estimated_freq, double *estimated_skew,
+ double *residual_freq)
+{
+ double gain, expected_freq, expected_skew, extra_skew;
+
+ /* We assume that the local clock is running according to our previously
+ determined value */
+ expected_freq = 0.0;
+ expected_skew = our_skew;
+
+ /* Set new frequency based on weighted average of the expected and measured
+ skew. Disable updates that are based on totally unreliable frequency
+ information unless it is a manual reference. */
+ if (manual) {
+ gain = 1.0;
+ } else if (fabs(measured_skew) > max_update_skew) {
+ DEBUG_LOG("Skew %f too large to track", measured_skew);
+ gain = 0.0;
+ } else {
+ gain = 3.0 * SQUARE(expected_skew) /
+ (3.0 * SQUARE(expected_skew) + SQUARE(measured_skew));
+ }
+
+ gain = CLAMP(0.0, gain, 1.0);
+
+ *estimated_freq = expected_freq + gain * (measured_freq - expected_freq);
+ *residual_freq = measured_freq - *estimated_freq;
+
+ extra_skew = sqrt(SQUARE(expected_freq - *estimated_freq) * (1.0 - gain) +
+ SQUARE(measured_freq - *estimated_freq) * gain);
+
+ *estimated_skew = expected_skew + gain * (measured_skew - expected_skew) + extra_skew;
+}
+
+/* ================================================== */
+
+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);
+}
+
+/* ================================================== */
+
+static double
+get_correction_rate(double offset_sd, double update_interval)
+{
+ /* We want to correct the offset quickly, but we also want to keep the
+ frequency error caused by the correction itself low.
+
+ Define correction rate as the area of the region bounded by the graph of
+ offset corrected in time. Set the rate so that the time needed to correct
+ an offset equal to the current sourcestats stddev will be equal to the
+ update interval multiplied by the correction time ratio (assuming linear
+ adjustment). The offset and the time needed to make the correction are
+ inversely proportional.
+
+ This is only a suggestion and it's up to the system driver how the
+ adjustment will be executed. */
+
+ return correction_time_ratio * 0.5 * offset_sd * update_interval;
+}
+
+/* ================================================== */
+
+void
+REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
+ uint32_t ref_id, IPAddr *ref_ip, struct timespec *ref_time,
+ double offset, double offset_sd,
+ double frequency, double frequency_sd, double skew,
+ double root_delay, double root_dispersion)
+{
+ double uncorrected_offset, accumulate_offset, step_offset;
+ double residual_frequency, local_abs_frequency;
+ double elapsed, mono_now, update_interval, orig_root_distance;
+ struct timespec now, raw_now;
+ int manual;
+
+ assert(initialised);
+
+ /* Special modes are implemented elsewhere */
+ if (mode != REF_ModeNormal) {
+ special_mode_sync(1, offset);
+ return;
+ }
+
+ 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;
+
+ if (last_ref_update != 0.0) {
+ update_interval = mono_now - last_ref_update;
+ } else {
+ update_interval = 0.0;
+ }
+
+ /* Get new estimates of the frequency and skew including the new data */
+ get_clock_estimates(manual, frequency, skew,
+ &frequency, &skew, &residual_frequency);
+
+ if (!is_offset_ok(offset))
+ return;
+
+ orig_root_distance = our_root_delay / 2.0 + get_root_dispersion(&now);
+
+ are_we_synchronised = leap != LEAP_Unsynchronised;
+ our_stratum = stratum + 1;
+ our_ref_id = ref_id;
+ if (ref_ip)
+ our_ref_ip = *ref_ip;
+ else
+ our_ref_ip.family = IPADDR_UNSPEC;
+ our_ref_time = *ref_time;
+ our_skew = skew;
+ our_residual_freq = residual_frequency;
+ our_root_delay = root_delay;
+ our_root_dispersion = root_dispersion;
+ our_frequency_sd = frequency_sd;
+ our_offset_sd = offset_sd;
+ last_ref_update = mono_now;
+ last_ref_update_interval = update_interval;
+ last_offset = offset;
+
+ /* Check if the clock should be stepped */
+ if (is_step_limit_reached(offset, uncorrected_offset)) {
+ /* Cancel the uncorrected offset and correct the total offset by step */
+ accumulate_offset = uncorrected_offset;
+ step_offset = offset - uncorrected_offset;
+ } else {
+ accumulate_offset = offset;
+ step_offset = 0.0;
+ }
+
+ /* Adjust the clock */
+ LCL_AccumulateFrequencyAndOffset(frequency, accumulate_offset,
+ get_correction_rate(offset_sd, update_interval));
+
+ maybe_log_offset(offset, raw_now.tv_sec);
+
+ if (step_offset != 0.0) {
+ if (LCL_ApplyStepOffset(step_offset))
+ LOG(LOGS_WARN, "System clock was stepped by %.6f seconds", -step_offset);
+ }
+
+ 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 */
+ fuzz_ref_time(&our_ref_time);
+
+ local_abs_frequency = LCL_ReadAbsoluteFrequency();
+
+ write_log(&now, combined_sources, local_abs_frequency,
+ offset, offset_sd, uncorrected_offset, orig_root_distance);
+
+ if (drift_file) {
+ /* Update drift file at most once per hour */
+ drift_file_age += update_interval;
+ if (drift_file_age >= MAX_DRIFTFILE_AGE) {
+ update_drift_file(local_abs_frequency, our_skew);
+ drift_file_age = 0.0;
+ }
+ }
+
+ /* Update fallback drifts */
+ if (fb_drifts && are_we_synchronised) {
+ update_fb_drifts(local_abs_frequency, update_interval);
+ schedule_fb_drift();
+ }
+
+ /* Update the moving average of squares of offset, quickly on start */
+ if (avg2_moving) {
+ avg2_offset += 0.1 * (SQUARE(offset) - avg2_offset);
+ } else {
+ if (avg2_offset > 0.0 && avg2_offset < SQUARE(offset))
+ avg2_moving = 1;
+ avg2_offset = SQUARE(offset);
+ }
+
+ ref_adjustments = 0;
+}
+
+/* ================================================== */
+
+int
+REF_AdjustReference(double offset, double frequency)
+{
+ double adj_corr_rate, ref_corr_rate, mono_now;
+
+ mono_now = SCH_GetLastEventMonoTime();
+ ref_adjustments++;
+
+ adj_corr_rate = get_correction_rate(fabs(offset), mono_now - last_ref_adjustment);
+ ref_corr_rate = get_correction_rate(our_offset_sd, last_ref_update_interval) /
+ ref_adjustments;
+ last_ref_adjustment = mono_now;
+
+ return LCL_AccumulateFrequencyAndOffsetNoHandlers(frequency, offset,
+ MAX(adj_corr_rate, ref_corr_rate));
+}
+
+/* ================================================== */
+
+void
+REF_SetManualReference
+(
+ struct timespec *ref_time,
+ double offset,
+ double frequency,
+ double skew
+)
+{
+ /* We are not synchronised to an external source, as such. This is
+ only supposed to be used with the local source option, really.
+ Log as MANU in the tracking log, packets will have NTP_REFID_LOCAL. */
+ REF_SetReference(0, LEAP_Unsynchronised, 1, 0x4D414E55UL, NULL,
+ ref_time, offset, 0.0, frequency, skew, skew, 0.0, 0.0);
+}
+
+/* ================================================== */
+
+void
+REF_SetUnsynchronised(void)
+{
+ /* Variables required for logging to statistics log */
+ struct timespec now, now_raw;
+ double uncorrected_offset;
+
+ assert(initialised);
+
+ /* Special modes are implemented elsewhere */
+ if (mode != REF_ModeNormal) {
+ special_mode_sync(0, 0.0);
+ return;
+ }
+
+ LCL_ReadRawTime(&now_raw);
+ LCL_GetOffsetCorrection(&now_raw, &uncorrected_offset, NULL);
+ UTI_AddDoubleToTimespec(&now_raw, uncorrected_offset, &now);
+
+ if (fb_drifts) {
+ schedule_fb_drift();
+ }
+
+ update_leap_status(LEAP_Unsynchronised, 0, 0);
+ our_ref_ip.family = IPADDR_INET4;
+ our_ref_ip.addr.in4 = 0;
+ our_stratum = 0;
+ are_we_synchronised = 0;
+
+ LCL_SetSyncStatus(0, 0.0, 0.0);
+
+ write_log(&now, 0, LCL_ReadAbsoluteFrequency(), 0.0, 0.0, uncorrected_offset,
+ our_root_delay / 2.0 + get_root_dispersion(&now));
+}
+
+/* ================================================== */
+
+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
+(
+ struct timespec *local_time,
+ int *is_synchronised,
+ NTP_Leap *leap_status,
+ int *stratum,
+ uint32_t *ref_id,
+ struct timespec *ref_time,
+ double *root_delay,
+ double *root_dispersion
+)
+{
+ double dispersion, delta;
+
+ assert(initialised);
+
+ if (are_we_synchronised) {
+ dispersion = get_root_dispersion(local_time);
+ } else {
+ dispersion = 0.0;
+ }
+
+ /* Local reference is active when enabled and the clock is not synchronised
+ or the root distance exceeds the threshold */
+
+ if (are_we_synchronised &&
+ !(enable_local_stratum && our_root_delay / 2 + dispersion > local_distance)) {
+
+ *is_synchronised = 1;
+
+ *stratum = our_stratum;
+
+ *leap_status = !leap_in_progress ? our_leap_status : LEAP_Unsynchronised;
+ *ref_id = our_ref_id;
+ *ref_time = our_ref_time;
+ *root_delay = our_root_delay;
+ *root_dispersion = dispersion;
+
+ } else if (enable_local_stratum) {
+
+ *is_synchronised = 0;
+
+ *stratum = local_stratum;
+ *ref_id = NTP_REFID_LOCAL;
+
+ /* 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 */
+ *leap_status = LEAP_Normal;
+
+ *root_delay = 0.0;
+ *root_dispersion = 0.0;
+
+ } else {
+
+ *is_synchronised = 0;
+
+ *leap_status = LEAP_Unsynchronised;
+ *stratum = NTP_MAX_STRATUM;
+ *ref_id = NTP_REFID_UNSYNC;
+ UTI_ZeroTimespec(ref_time);
+ /* These values seem to be standard for a client, and
+ any peer or client of ours will ignore them anyway because
+ we don't claim to be synchronised */
+ *root_dispersion = 1.0;
+ *root_delay = 1.0;
+
+ }
+}
+
+/* ================================================== */
+
+int
+REF_GetOurStratum(void)
+{
+ struct timespec now_cooked, ref_time;
+ int synchronised, stratum;
+ NTP_Leap leap_status;
+ uint32_t ref_id;
+ double root_delay, root_dispersion;
+
+ SCH_GetLastEventTime(&now_cooked, NULL, NULL);
+ REF_GetReferenceParams(&now_cooked, &synchronised, &leap_status, &stratum,
+ &ref_id, &ref_time, &root_delay, &root_dispersion);
+
+ return stratum;
+}
+
+/* ================================================== */
+
+int
+REF_GetOrphanStratum(void)
+{
+ if (!enable_local_stratum || !local_orphan || mode != REF_ModeNormal)
+ return NTP_MAX_STRATUM;
+ return local_stratum;
+}
+
+/* ================================================== */
+
+double
+REF_GetSkew(void)
+{
+ return our_skew;
+}
+
+/* ================================================== */
+
+void
+REF_ModifyMaxupdateskew(double new_max_update_skew)
+{
+ max_update_skew = new_max_update_skew * 1.0e-6;
+ LOG(LOGS_INFO, "New maxupdateskew %f ppm", new_max_update_skew);
+}
+
+/* ================================================== */
+
+void
+REF_ModifyMakestep(int limit, double threshold)
+{
+ make_step_limit = limit;
+ make_step_threshold = threshold;
+ LOG(LOGS_INFO, "New makestep %f %d", threshold, limit);
+}
+
+/* ================================================== */
+
+void
+REF_EnableLocal(int stratum, double distance, int orphan)
+{
+ enable_local_stratum = 1;
+ local_stratum = CLAMP(1, stratum, NTP_MAX_STRATUM - 1);
+ local_distance = distance;
+ local_orphan = !!orphan;
+ LOG(LOGS_INFO, "%s local reference mode", "Enabled");
+}
+
+/* ================================================== */
+
+void
+REF_DisableLocal(void)
+{
+ enable_local_stratum = 0;
+ LOG(LOGS_INFO, "%s local reference mode", "Disabled");
+}
+
+/* ================================================== */
+
+#define LEAP_SECOND_CLOSE 5
+
+static int
+is_leap_close(time_t t)
+{
+ return leap_when != 0 &&
+ 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;
+
+ SCH_GetLastEventTime(&now, NULL, &now_raw);
+
+ if (is_leap_close(now.tv_sec) || is_leap_close(now_raw.tv_sec))
+ return 1;
+
+ if (ts && (is_leap_close(ts->tv_sec) || is_leap_close(ts->tv_sec + offset)))
+ return 1;
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+REF_GetTaiOffset(struct timespec *ts)
+{
+ int tai_offset;
+
+ get_tz_leap(ts->tv_sec, &tai_offset);
+
+ return tai_offset;
+}
+
+/* ================================================== */
+
+void
+REF_GetTrackingReport(RPT_TrackingReport *rep)
+{
+ struct timespec now_raw, now_cooked;
+ double correction;
+ int synchronised;
+
+ LCL_ReadRawTime(&now_raw);
+ LCL_GetOffsetCorrection(&now_raw, &correction, NULL);
+ UTI_AddDoubleToTimespec(&now_raw, correction, &now_cooked);
+
+ REF_GetReferenceParams(&now_cooked, &synchronised,
+ &rep->leap_status, &rep->stratum,
+ &rep->ref_id, &rep->ref_time,
+ &rep->root_delay, &rep->root_dispersion);
+
+ if (rep->stratum == NTP_MAX_STRATUM && !synchronised)
+ rep->stratum = 0;
+
+ rep->ip_addr.family = IPADDR_UNSPEC;
+ rep->current_correction = correction;
+ rep->freq_ppm = LCL_ReadAbsoluteFrequency();
+ rep->resid_freq_ppm = 0.0;
+ rep->skew_ppm = 0.0;
+ rep->last_update_interval = last_ref_update_interval;
+ rep->last_offset = last_offset;
+ rep->rms_offset = sqrt(avg2_offset);
+
+ if (synchronised) {
+ rep->ip_addr = our_ref_ip;
+ rep->resid_freq_ppm = 1.0e6 * our_residual_freq;
+ rep->skew_ppm = 1.0e6 * our_skew;
+ }
+}
diff --git a/reference.h b/reference.h
new file mode 100644
index 0000000..73454d4
--- /dev/null
+++ b/reference.h
@@ -0,0 +1,200 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 the module that keeps track of the current
+ reference.
+
+ */
+
+#ifndef GOT_REFERENCE_H
+#define GOT_REFERENCE_H
+
+#include "sysincl.h"
+
+#include "ntp.h"
+#include "reports.h"
+
+/* Leap second handling modes */
+typedef enum {
+ REF_LeapModeSystem,
+ REF_LeapModeSlew,
+ REF_LeapModeStep,
+ REF_LeapModeIgnore,
+} REF_LeapMode;
+
+/* Init function */
+extern void REF_Initialise(void);
+
+/* Fini function */
+extern void REF_Finalise(void);
+
+typedef enum {
+ REF_ModeNormal,
+ REF_ModeInitStepSlew,
+ REF_ModeUpdateOnce,
+ REF_ModePrintOnce,
+ REF_ModeIgnore,
+} REF_Mode;
+
+/* Set reference update mode */
+extern void REF_SetMode(REF_Mode mode);
+
+/* Get reference update mode */
+extern REF_Mode REF_GetMode(void);
+
+/* Function type for handlers to be called back when mode ends */
+typedef void (*REF_ModeEndHandler)(int result);
+
+/* Set the handler for being notified of mode ending */
+extern void REF_SetModeEndHandler(REF_ModeEndHandler handler);
+
+/* Get leap second handling mode */
+extern REF_LeapMode REF_GetLeapMode(void);
+
+/* Function which takes a local cooked time and returns the estimated
+ time of the reference. It also returns the other parameters
+ required for forming the outgoing NTP packet.
+
+ local_time is the cooked local time returned by the LCL module
+
+ is_synchronised indicates whether we are synchronised to anything
+ at the moment.
+
+ leap indicates the current leap status
+
+ stratum is the stratum of this machine, when considered to be sync'd to the
+ reference
+
+ ref_id is the reference_id of the source
+
+ ref_time is the time at which the we last set the reference source up
+
+ root_delay is the root delay of the sample we are using
+
+ root_dispersion is the root dispersion of the sample we are using, with all the
+ skew etc added on.
+
+ */
+
+extern void REF_GetReferenceParams
+(
+ struct timespec *local_time,
+ int *is_synchronised,
+ NTP_Leap *leap,
+ int *stratum,
+ uint32_t *ref_id,
+ struct timespec *ref_time,
+ double *root_delay,
+ double *root_dispersion
+);
+
+/* Function called by the clock selection process to register a new
+ reference source and its parameters
+
+ stratum is the stratum of the reference
+
+ leap is the leap status read from the source
+
+ ref_id is the reference id of the reference
+
+ ref_time is the time at which the parameters are assumed to be
+ correct, in terms of local time
+
+ frequency is the amount of local clock gain relative to the
+ reference per unit time interval of the local clock
+
+ skew is the maximum estimated frequency error (so we are within
+ [frequency+-skew])
+
+ root_delay is the root delay of the sample we are using
+
+ root_dispersion is the root dispersion of the sample we are using
+
+ */
+
+extern void REF_SetReference
+(
+ int stratum,
+ NTP_Leap leap,
+ int combined_sources,
+ uint32_t ref_id,
+ IPAddr *ref_ip,
+ struct timespec *ref_time,
+ double offset,
+ double offset_sd,
+ double frequency,
+ double frequency_sd,
+ double skew,
+ double root_delay,
+ double root_dispersion
+);
+
+extern void REF_SetManualReference
+(
+ struct timespec *ref_time,
+ double offset,
+ double frequency,
+ double skew
+);
+
+/* Mark the local clock as unsynchronised */
+extern void
+REF_SetUnsynchronised(void);
+
+/* Make a small correction of the clock without updating the reference
+ parameters and calling the clock change handlers */
+extern int REF_AdjustReference(double offset, double frequency);
+
+/* 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);
+
+/* Return stratum of the local reference if orphan mode is enabled */
+extern int REF_GetOrphanStratum(void);
+
+/* Return the current skew */
+extern double REF_GetSkew(void);
+
+/* Modify the setting for the maximum skew we are prepared to allow updates on (in ppm). */
+extern void REF_ModifyMaxupdateskew(double new_max_update_skew);
+
+/* Modify makestep settings */
+extern void REF_ModifyMakestep(int limit, double threshold);
+
+extern void REF_EnableLocal(int stratum, double distance, int orphan);
+extern void REF_DisableLocal(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);
+
+extern void REF_GetTrackingReport(RPT_TrackingReport *rep);
+
+#endif /* GOT_REFERENCE_H */
diff --git a/regress.c b/regress.c
new file mode 100644
index 0000000..e767e2f
--- /dev/null
+++ b/regress.c
@@ -0,0 +1,704 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2011, 2016-2017
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Regression algorithms.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "regress.h"
+#include "logging.h"
+#include "util.h"
+
+#define MAX_POINTS 64
+
+void
+RGR_WeightedRegression
+(double *x, /* independent variable */
+ double *y, /* measured data */
+ double *w, /* weightings (large => data
+ less reliable) */
+
+ int n, /* number of data points */
+
+ /* And now the results */
+
+ double *b0, /* estimated y axis intercept */
+ double *b1, /* estimated slope */
+ double *s2, /* estimated variance of data points */
+
+ double *sb0, /* estimated standard deviation of
+ intercept */
+ double *sb1 /* estimated standard deviation of
+ slope */
+
+ /* Could add correlation stuff later if required */
+)
+{
+ double P, Q, U, V, W;
+ double diff;
+ double u, ui, aa;
+ int i;
+
+ assert(n >= 3);
+
+ W = U = 0;
+ for (i=0; i<n; i++) {
+ U += x[i] / w[i];
+ W += 1.0 / w[i];
+ }
+
+ u = U / W;
+
+ /* Calculate statistics from data */
+ P = Q = V = 0.0;
+ for (i=0; i<n; i++) {
+ ui = x[i] - u;
+ P += y[i] / w[i];
+ Q += y[i] * ui / w[i];
+ V += ui * ui / w[i];
+ }
+
+ *b1 = Q / V;
+ *b0 = (P / W) - (*b1) * u;
+
+ *s2 = 0.0;
+ for (i=0; i<n; i++) {
+ diff = y[i] - *b0 - *b1*x[i];
+ *s2 += diff*diff / w[i];
+ }
+
+ *s2 /= (double)(n-2);
+
+ *sb1 = sqrt(*s2 / V);
+ aa = u * (*sb1);
+ *sb0 = sqrt(*s2 / W + aa * aa);
+
+ *s2 *= (n / W); /* Giving weighted average of variances */
+}
+
+/* ================================================== */
+/* Get the coefficient to multiply the standard deviation by, to get a
+ particular size of confidence interval (assuming a t-distribution) */
+
+double
+RGR_GetTCoef(int dof)
+{
+ /* Assuming now the 99.95% quantile */
+ static const float coefs[] =
+ { 636.6, 31.6, 12.92, 8.61, 6.869,
+ 5.959, 5.408, 5.041, 4.781, 4.587,
+ 4.437, 4.318, 4.221, 4.140, 4.073,
+ 4.015, 3.965, 3.922, 3.883, 3.850,
+ 3.819, 3.792, 3.768, 3.745, 3.725,
+ 3.707, 3.690, 3.674, 3.659, 3.646,
+ 3.633, 3.622, 3.611, 3.601, 3.591,
+ 3.582, 3.574, 3.566, 3.558, 3.551};
+
+ if (dof <= 40) {
+ return coefs[dof-1];
+ } else {
+ return 3.5; /* Until I can be bothered to do something better */
+ }
+}
+
+/* ================================================== */
+/* Get 90% quantile of chi-square distribution */
+
+double
+RGR_GetChi2Coef(int dof)
+{
+ static const float coefs[] = {
+ 2.706, 4.605, 6.251, 7.779, 9.236, 10.645, 12.017, 13.362,
+ 14.684, 15.987, 17.275, 18.549, 19.812, 21.064, 22.307, 23.542,
+ 24.769, 25.989, 27.204, 28.412, 29.615, 30.813, 32.007, 33.196,
+ 34.382, 35.563, 36.741, 37.916, 39.087, 40.256, 41.422, 42.585,
+ 43.745, 44.903, 46.059, 47.212, 48.363, 49.513, 50.660, 51.805,
+ 52.949, 54.090, 55.230, 56.369, 57.505, 58.641, 59.774, 60.907,
+ 62.038, 63.167, 64.295, 65.422, 66.548, 67.673, 68.796, 69.919,
+ 71.040, 72.160, 73.279, 74.397, 75.514, 76.630, 77.745, 78.860
+ };
+
+ if (dof <= 64) {
+ return coefs[dof-1];
+ } else {
+ return 1.2 * dof; /* Until I can be bothered to do something better */
+ }
+}
+
+/* ================================================== */
+/* Critical value for number of runs of residuals with same sign.
+ 5% critical region for now. */
+
+static char critical_runs[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 3,
+ 3, 3, 4, 4, 5, 5, 5, 6, 6, 7,
+ 7, 7, 8, 8, 9, 9, 9, 10, 10, 11,
+ 11, 11, 12, 12, 13, 13, 14, 14, 14, 15,
+ 15, 16, 16, 17, 17, 18, 18, 18, 19, 19,
+ 20, 20, 21, 21, 21, 22, 22, 23, 23, 24,
+ 24, 25, 25, 26, 26, 26, 27, 27, 28, 28,
+ 29, 29, 30, 30, 30, 31, 31, 32, 32, 33,
+ 33, 34, 34, 35, 35, 35, 36, 36, 37, 37,
+ 38, 38, 39, 39, 40, 40, 40, 41, 41, 42,
+ 42, 43, 43, 44, 44, 45, 45, 46, 46, 46,
+ 47, 47, 48, 48, 49, 49, 50, 50, 51, 51,
+ 52, 52, 52, 53, 53, 54, 54, 55, 55, 56
+};
+
+/* ================================================== */
+
+static int
+n_runs_from_residuals(double *resid, int n)
+{
+ int nruns;
+ int i;
+
+ nruns = 1;
+ for (i=1; i<n; i++) {
+ if (((resid[i-1] < 0.0) && (resid[i] < 0.0)) ||
+ ((resid[i-1] > 0.0) && (resid[i] > 0.0))) {
+ /* Nothing to do */
+ } else {
+ nruns++;
+ }
+ }
+
+ return nruns;
+}
+
+/* ================================================== */
+/* Return a boolean indicating whether we had enough points for
+ regression */
+
+int
+RGR_FindBestRegression
+(double *x, /* independent variable */
+ double *y, /* measured data */
+ double *w, /* weightings (large => data
+ less reliable) */
+
+ int n, /* number of data points */
+ int m, /* number of extra samples in x and y arrays
+ (negative index) which can be used to
+ extend runs test */
+ int min_samples, /* minimum number of samples to be kept after
+ changing the starting index to pass the runs
+ test */
+
+ /* And now the results */
+
+ double *b0, /* estimated y axis intercept */
+ double *b1, /* estimated slope */
+ double *s2, /* estimated variance of data points */
+
+ double *sb0, /* estimated standard deviation of
+ intercept */
+ double *sb1, /* estimated standard deviation of
+ slope */
+
+ int *new_start, /* the new starting index to make the
+ residuals pass the two tests */
+
+ int *n_runs, /* number of runs amongst the residuals */
+
+ int *dof /* degrees of freedom in statistics (needed
+ to get confidence intervals later) */
+
+)
+{
+ double P, Q, U, V, W; /* total */
+ double resid[MAX_POINTS * REGRESS_RUNS_RATIO];
+ double ss;
+ double a, b, u, ui, aa;
+
+ int start, resid_start, nruns, npoints;
+ int i;
+
+ assert(n <= MAX_POINTS && m >= 0);
+ assert(n * REGRESS_RUNS_RATIO < sizeof (critical_runs) / sizeof (critical_runs[0]));
+
+ if (n < MIN_SAMPLES_FOR_REGRESS) {
+ return 0;
+ }
+
+ start = 0;
+ do {
+
+ W = U = 0;
+ for (i=start; i<n; i++) {
+ U += x[i] / w[i];
+ W += 1.0 / w[i];
+ }
+
+ u = U / W;
+
+ P = Q = V = 0.0;
+ for (i=start; i<n; i++) {
+ ui = x[i] - u;
+ P += y[i] / w[i];
+ Q += y[i] * ui / w[i];
+ V += ui * ui / w[i];
+ }
+
+ b = Q / V;
+ a = (P / W) - (b * u);
+
+ /* Get residuals also for the extra samples before start */
+ resid_start = n - (n - start) * REGRESS_RUNS_RATIO;
+ if (resid_start < -m)
+ resid_start = -m;
+
+ for (i=resid_start; i<n; i++) {
+ resid[i - resid_start] = y[i] - a - b*x[i];
+ }
+
+ /* Count number of runs */
+ nruns = n_runs_from_residuals(resid, n - resid_start);
+
+ if (nruns > critical_runs[n - resid_start] ||
+ n - start <= MIN_SAMPLES_FOR_REGRESS ||
+ n - start <= min_samples) {
+ if (start != resid_start) {
+ /* Ignore extra samples in returned nruns */
+ nruns = n_runs_from_residuals(resid + (start - resid_start), n - start);
+ }
+ break;
+ } else {
+ /* Try dropping one sample at a time until the runs test passes. */
+ ++start;
+ }
+
+ } while (1);
+
+ /* Work out statistics from full dataset */
+ *b1 = b;
+ *b0 = a;
+
+ ss = 0.0;
+ for (i=start; i<n; i++) {
+ ss += resid[i - resid_start]*resid[i - resid_start] / w[i];
+ }
+
+ npoints = n - start;
+ ss /= (double)(npoints - 2);
+ *sb1 = sqrt(ss / V);
+ aa = u * (*sb1);
+ *sb0 = sqrt((ss / W) + (aa * aa));
+ *s2 = ss * (double) npoints / W;
+
+ *new_start = start;
+ *dof = npoints - 2;
+ *n_runs = nruns;
+
+ return 1;
+
+}
+
+/* ================================================== */
+
+#define EXCH(a,b) temp=(a); (a)=(b); (b)=temp
+
+/* ================================================== */
+/* Find the index'th biggest element in the array x of n elements.
+ flags is an array where a 1 indicates that the corresponding entry
+ in x is known to be sorted into its correct position and a 0
+ indicates that the corresponding entry is not sorted. However, if
+ flags[m] = flags[n] = 1 with m<n, then x[m] must be <= x[n] and for
+ all i with m<i<n, x[m] <= x[i] <= x[n]. In practice, this means
+ flags[] has to be the result of a previous call to this routine
+ with the same array x, and is used to remember which parts of the
+ x[] array we have already sorted.
+
+ The approach used is a cut-down quicksort, where we only bother to
+ keep sorting the partition that contains the index we are after.
+ The approach comes from Numerical Recipes in C (ISBN
+ 0-521-43108-5). */
+
+static double
+find_ordered_entry_with_flags(double *x, int n, int index, char *flags)
+{
+ int u, v, l, r;
+ double temp;
+ double piv;
+ int pivind;
+
+ assert(index >= 0);
+
+ /* If this bit of the array is already sorted, simple! */
+ if (flags[index]) {
+ return x[index];
+ }
+
+ /* Find subrange to look at */
+ u = v = index;
+ while (u > 0 && !flags[u]) u--;
+ if (flags[u]) u++;
+
+ while (v < (n-1) && !flags[v]) v++;
+ if (flags[v]) v--;
+
+ do {
+ if (v - u < 2) {
+ if (x[v] < x[u]) {
+ EXCH(x[v], x[u]);
+ }
+ flags[v] = flags[u] = 1;
+ return x[index];
+ } else {
+ pivind = (u + v) >> 1;
+ EXCH(x[u], x[pivind]);
+ piv = x[u]; /* New value */
+ l = u + 1;
+ r = v;
+ do {
+ while (l < v && x[l] < piv) l++;
+ while (x[r] > piv) r--;
+ if (r <= l) break;
+ EXCH(x[l], x[r]);
+ l++;
+ r--;
+ } while (1);
+ EXCH(x[u], x[r]);
+ flags[r] = 1; /* Pivot now in correct place */
+ if (index == r) {
+ return x[r];
+ } else if (index < r) {
+ v = r - 1;
+ } else if (index > r) {
+ u = l;
+ }
+ }
+ } while (1);
+}
+
+/* ================================================== */
+
+#if 0
+/* Not used, but this is how it can be done */
+static double
+find_ordered_entry(double *x, int n, int index)
+{
+ char flags[MAX_POINTS];
+
+ memset(flags, 0, n * sizeof (flags[0]));
+ return find_ordered_entry_with_flags(x, n, index, flags);
+}
+#endif
+
+/* ================================================== */
+/* Find the median entry of an array x[] with n elements. */
+
+static double
+find_median(double *x, int n)
+{
+ int k;
+ char flags[MAX_POINTS];
+
+ memset(flags, 0, n * sizeof (flags[0]));
+ k = n>>1;
+ if (n&1) {
+ return find_ordered_entry_with_flags(x, n, k, flags);
+ } else {
+ return 0.5 * (find_ordered_entry_with_flags(x, n, k, flags) +
+ find_ordered_entry_with_flags(x, n, k-1, flags));
+ }
+}
+
+/* ================================================== */
+
+double
+RGR_FindMedian(double *x, int n)
+{
+ double tmp[MAX_POINTS];
+
+ assert(n > 0 && n <= MAX_POINTS);
+ memcpy(tmp, x, n * sizeof (tmp[0]));
+
+ return find_median(tmp, n);
+}
+
+/* ================================================== */
+/* This function evaluates the equation
+
+ \sum_{i=0}^{n-1} x_i sign(y_i - a - b x_i)
+
+ and chooses the value of a that minimises the absolute value of the
+ result. (See pp703-704 of Numerical Recipes in C). */
+
+static void
+eval_robust_residual
+(double *x, /* The independent points */
+ double *y, /* The dependent points */
+ int n, /* Number of points */
+ double b, /* Slope */
+ double *aa, /* Intercept giving smallest absolute
+ value for the above equation */
+ double *rr /* Corresponding value of equation */
+)
+{
+ int i;
+ double a, res, del;
+ double d[MAX_POINTS];
+
+ for (i=0; i<n; i++) {
+ d[i] = y[i] - b * x[i];
+ }
+
+ a = find_median(d, n);
+
+ res = 0.0;
+ for (i=0; i<n; i++) {
+ del = y[i] - a - b * x[i];
+ if (del > 0.0) {
+ res += x[i];
+ } else if (del < 0.0) {
+ res -= x[i];
+ }
+ }
+
+ *aa = a;
+ *rr = res;
+}
+
+/* ================================================== */
+/* This routine performs a 'robust' regression, i.e. one which has low
+ susceptibility to outliers amongst the data. If one thinks of a
+ normal (least squares) linear regression in 2D being analogous to
+ the arithmetic mean in 1D, this algorithm in 2D is roughly
+ analogous to the median in 1D. This algorithm seems to work quite
+ well until the number of outliers is approximately half the number
+ of data points.
+
+ The return value is a status indicating whether there were enough
+ data points to run the routine or not. */
+
+int
+RGR_FindBestRobustRegression
+(double *x, /* The independent axis points */
+ double *y, /* The dependent axis points (which
+ may contain outliers). */
+ int n, /* The number of points */
+ double tol, /* The tolerance required in
+ determining the value of b1 */
+ double *b0, /* The estimated Y-axis intercept */
+ double *b1, /* The estimated slope */
+ int *n_runs, /* The number of runs of residuals */
+ int *best_start /* The best starting index */
+)
+{
+ int i;
+ int start;
+ int n_points;
+ double a, b;
+ double P, U, V, W, X;
+ double resid, resids[MAX_POINTS];
+ double blo, bhi, bmid, rlo, rhi, rmid;
+ double s2, sb, incr;
+ double mx, dx, my, dy;
+ int nruns = 0;
+
+ assert(n <= MAX_POINTS);
+
+ if (n < 2) {
+ return 0;
+ } else if (n == 2) {
+ /* Just a straight line fit (we need this for the manual mode) */
+ *b1 = (y[1] - y[0]) / (x[1] - x[0]);
+ *b0 = y[0] - (*b1) * x[0];
+ *n_runs = 0;
+ *best_start = 0;
+ return 1;
+ }
+
+ /* else at least 3 points, apply normal algorithm */
+
+ start = 0;
+
+ /* Loop to strip oldest points that cause the regression residuals
+ to fail the number of runs test */
+ do {
+
+ n_points = n - start;
+
+ /* Use standard least squares regression to get starting estimate */
+
+ P = U = 0.0;
+ for (i=start; i<n; i++) {
+ P += y[i];
+ U += x[i];
+ }
+
+ W = (double) n_points;
+
+ my = P/W;
+ mx = U/W;
+
+ X = V = 0.0;
+ for (i=start; i<n; i++) {
+ dy = y[i] - my;
+ dx = x[i] - mx;
+ X += dy * dx;
+ V += dx * dx;
+ }
+
+ b = X / V;
+ a = my - b*mx;
+
+ s2 = 0.0;
+ for (i=start; i<n; i++) {
+ resid = y[i] - a - b * x[i];
+ s2 += resid * resid;
+ }
+
+ /* Need to expand range of b to get a root in the interval.
+ Estimate standard deviation of b and expand range about b based
+ on that. */
+ sb = sqrt(s2 * W/V);
+ incr = MAX(sb, tol);
+
+ do {
+ incr *= 2.0;
+
+ /* Give up if the interval is too large */
+ if (incr > 100.0)
+ return 0;
+
+ blo = b - incr;
+ bhi = b + incr;
+
+ /* We don't want 'a' yet */
+ eval_robust_residual(x + start, y + start, n_points, blo, &a, &rlo);
+ eval_robust_residual(x + start, y + start, n_points, bhi, &a, &rhi);
+
+ } while (rlo * rhi >= 0.0); /* fn vals have same sign or one is zero,
+ i.e. root not in interval (rlo, rhi). */
+
+ /* OK, so the root for b lies in (blo, bhi). Start bisecting */
+ do {
+ bmid = 0.5 * (blo + bhi);
+ if (!(blo < bmid && bmid < bhi))
+ break;
+ eval_robust_residual(x + start, y + start, n_points, bmid, &a, &rmid);
+ if (rmid == 0.0) {
+ break;
+ } else if (rmid * rlo > 0.0) {
+ blo = bmid;
+ rlo = rmid;
+ } else if (rmid * rhi > 0.0) {
+ bhi = bmid;
+ rhi = rmid;
+ } else {
+ assert(0);
+ }
+ } while (bhi - blo > tol);
+
+ *b0 = a;
+ *b1 = bmid;
+
+ /* Number of runs test, but not if we're already down to the
+ minimum number of points */
+ if (n_points == MIN_SAMPLES_FOR_REGRESS) {
+ break;
+ }
+
+ for (i=start; i<n; i++) {
+ resids[i] = y[i] - a - bmid * x[i];
+ }
+
+ nruns = n_runs_from_residuals(resids + start, n_points);
+
+ if (nruns > critical_runs[n_points]) {
+ break;
+ } else {
+ start++;
+ }
+
+ } while (1);
+
+ *n_runs = nruns;
+ *best_start = start;
+
+ return 1;
+
+}
+
+/* ================================================== */
+/* This routine performs linear regression with two independent variables.
+ It returns non-zero status if there were enough data points and there
+ was a solution. */
+
+int
+RGR_MultipleRegress
+(double *x1, /* first independent variable */
+ double *x2, /* second independent variable */
+ double *y, /* measured data */
+
+ int n, /* number of data points */
+
+ /* The results */
+ double *b2 /* estimated second slope */
+ /* other values are not needed yet */
+)
+{
+ double Sx1, Sx2, Sx1x1, Sx1x2, Sx2x2, Sx1y, Sx2y, Sy;
+ double U, V, V1, V2, V3;
+ int i;
+
+ if (n < 4)
+ return 0;
+
+ Sx1 = Sx2 = Sx1x1 = Sx1x2 = Sx2x2 = Sx1y = Sx2y = Sy = 0.0;
+
+ for (i = 0; i < n; i++) {
+ Sx1 += x1[i];
+ Sx2 += x2[i];
+ Sx1x1 += x1[i] * x1[i];
+ Sx1x2 += x1[i] * x2[i];
+ Sx2x2 += x2[i] * x2[i];
+ Sx1y += x1[i] * y[i];
+ Sx2y += x2[i] * y[i];
+ Sy += y[i];
+ }
+
+ U = n * (Sx1x2 * Sx1y - Sx1x1 * Sx2y) +
+ Sx1 * Sx1 * Sx2y - Sx1 * Sx2 * Sx1y +
+ Sy * (Sx2 * Sx1x1 - Sx1 * Sx1x2);
+
+ V1 = n * (Sx1x2 * Sx1x2 - Sx1x1 * Sx2x2);
+ V2 = Sx1 * Sx1 * Sx2x2 + Sx2 * Sx2 * Sx1x1;
+ V3 = -2.0 * Sx1 * Sx2 * Sx1x2;
+ V = V1 + V2 + V3;
+
+ /* Check if there is a (numerically stable) solution */
+ if (fabs(V) * 1.0e10 <= -V1 + V2 + fabs(V3))
+ return 0;
+
+ *b2 = U / V;
+
+ return 1;
+}
diff --git a/regress.h b/regress.h
new file mode 100644
index 0000000..90055da
--- /dev/null
+++ b/regress.h
@@ -0,0 +1,137 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 regression routine(s)
+
+ */
+
+#ifndef GOT_REGRESS_H
+#define GOT_REGRESS_H
+
+extern void
+RGR_WeightedRegression
+(double *x, /* independent variable */
+ double *y, /* measured data */
+ double *w, /* weightings (large => data
+ less reliable) */
+
+ int n, /* number of data points */
+
+ /* And now the results */
+
+ double *b0, /* estimated y axis intercept */
+ double *b1, /* estimated slope */
+ double *s2, /* estimated variance (weighted) of
+ data points */
+
+ double *sb0, /* estimated standard deviation of
+ intercept */
+ double *sb1 /* estimated standard deviation of
+ slope */
+
+ /* Could add correlation stuff later if required */
+);
+
+/* Return the weighting to apply to the standard deviation to get a
+ given size of confidence interval assuming a T distribution */
+
+extern double RGR_GetTCoef(int dof);
+
+/* Return the value to apply to the variance to make an upper one-sided
+ test assuming a chi-square distribution. */
+
+extern double RGR_GetChi2Coef(int dof);
+
+/* Maximum ratio of number of points used for runs test to number of regression
+ points */
+#define REGRESS_RUNS_RATIO 2
+
+/* Minimum number of samples for regression */
+#define MIN_SAMPLES_FOR_REGRESS 3
+
+/* Return a status indicating whether there were enough points to
+ carry out the regression */
+
+extern int
+RGR_FindBestRegression
+(double *x, /* independent variable */
+ double *y, /* measured data */
+ double *w, /* weightings (large => data
+ less reliable) */
+
+ int n, /* number of data points */
+ int m, /* number of extra samples in x and y arrays
+ (negative index) which can be used to
+ extend runs test */
+ int min_samples, /* minimum number of samples to be kept after
+ changing the starting index to pass the runs
+ test */
+
+ /* And now the results */
+
+ double *b0, /* estimated y axis intercept */
+ double *b1, /* estimated slope */
+ double *s2, /* estimated variance of data points */
+
+ double *sb0, /* estimated standard deviation of
+ intercept */
+ double *sb1, /* estimated standard deviation of
+ slope */
+
+ int *new_start, /* the new starting index to make the
+ residuals pass the two tests */
+
+ int *n_runs, /* number of runs amongst the residuals */
+
+ int *dof /* degrees of freedom in statistics (needed
+ to get confidence intervals later) */
+
+);
+
+int
+RGR_FindBestRobustRegression
+(double *x,
+ double *y,
+ int n,
+ double tol,
+ double *b0,
+ double *b1,
+ int *n_runs,
+ int *best_start);
+
+int
+RGR_MultipleRegress
+(double *x1, /* first independent variable */
+ double *x2, /* second independent variable */
+ double *y, /* measured data */
+
+ int n, /* number of data points */
+
+ /* The results */
+ double *b2 /* estimated second slope */
+);
+
+/* Return the median value from an array */
+extern double RGR_FindMedian(double *x, int n);
+
+#endif /* GOT_REGRESS_H */
diff --git a/reports.h b/reports.h
new file mode 100644
index 0000000..0150fc6
--- /dev/null
+++ b/reports.h
@@ -0,0 +1,212 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Data structure definitions within the daemon for various reports that
+ can be generated */
+
+#ifndef GOT_REPORTS_H
+#define GOT_REPORTS_H
+
+#include "sysincl.h"
+#include "addressing.h"
+#include "ntp.h"
+
+typedef struct {
+ IPAddr ip_addr;
+ int stratum;
+ int poll;
+ enum {RPT_NTP_CLIENT, RPT_NTP_PEER, RPT_LOCAL_REFERENCE} mode;
+ enum {
+ RPT_NONSELECTABLE,
+ RPT_FALSETICKER,
+ RPT_JITTERY,
+ RPT_SELECTABLE,
+ RPT_UNSELECTED,
+ RPT_SELECTED,
+ } state;
+
+ int reachability;
+ unsigned long latest_meas_ago; /* seconds */
+ double orig_latest_meas; /* seconds */
+ double latest_meas; /* seconds */
+ double latest_meas_err; /* seconds */
+} RPT_SourceReport ;
+
+typedef struct {
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ int stratum;
+ NTP_Leap leap_status;
+ struct timespec ref_time;
+ double current_correction;
+ double last_offset;
+ double rms_offset;
+ double freq_ppm;
+ double resid_freq_ppm;
+ double skew_ppm;
+ double root_delay;
+ double root_dispersion;
+ double last_update_interval;
+} RPT_TrackingReport;
+
+typedef struct {
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ unsigned long n_samples;
+ unsigned long n_runs;
+ unsigned long span_seconds;
+ double resid_freq_ppm;
+ double skew_ppm;
+ double sd;
+ double est_offset;
+ double est_offset_err;
+} RPT_SourcestatsReport;
+
+typedef struct {
+ struct timespec ref_time;
+ unsigned long n_samples;
+ unsigned long n_runs;
+ unsigned long span_seconds;
+ double rtc_seconds_fast;
+ double rtc_gain_rate_ppm;
+} RPT_RTC_Report;
+
+typedef struct {
+ IPAddr ip_addr;
+ uint32_t ntp_hits;
+ uint32_t nke_hits;
+ uint32_t cmd_hits;
+ uint16_t ntp_drops;
+ uint16_t nke_drops;
+ uint16_t cmd_drops;
+ int8_t ntp_interval;
+ int8_t nke_interval;
+ int8_t cmd_interval;
+ int8_t ntp_timeout_interval;
+ uint32_t last_ntp_hit_ago;
+ uint32_t last_nke_hit_ago;
+ uint32_t last_cmd_hit_ago;
+} RPT_ClientAccessByIndex_Report;
+
+typedef struct {
+ uint64_t ntp_hits;
+ uint64_t nke_hits;
+ uint64_t cmd_hits;
+ uint64_t ntp_drops;
+ uint64_t nke_drops;
+ uint64_t cmd_drops;
+ uint64_t log_drops;
+ uint64_t ntp_auth_hits;
+ uint64_t ntp_interleaved_hits;
+ uint64_t ntp_timestamps;
+ uint64_t ntp_span_seconds;
+ uint64_t ntp_daemon_rx_timestamps;
+ uint64_t ntp_daemon_tx_timestamps;
+ uint64_t ntp_kernel_rx_timestamps;
+ uint64_t ntp_kernel_tx_timestamps;
+ uint64_t ntp_hw_rx_timestamps;
+ uint64_t ntp_hw_tx_timestamps;
+} RPT_ServerStatsReport;
+
+typedef struct {
+ struct timespec when;
+ double slewed_offset;
+ double orig_offset;
+ double residual;
+} RPT_ManualSamplesReport;
+
+typedef struct {
+ int online;
+ int offline;
+ int burst_online;
+ int burst_offline;
+ int unresolved;
+} RPT_ActivityReport;
+
+typedef struct {
+ int active;
+ int leap_only;
+ double offset;
+ double freq_ppm;
+ double wander_ppm;
+ double last_update_ago;
+ double remaining_time;
+} RPT_SmoothingReport;
+
+typedef struct {
+ IPAddr remote_addr;
+ IPAddr local_addr;
+ uint16_t remote_port;
+ uint8_t leap;
+ uint8_t version;
+ uint8_t mode;
+ uint8_t stratum;
+ int8_t poll;
+ int8_t precision;
+ double root_delay;
+ double root_dispersion;
+ uint32_t ref_id;
+ struct timespec ref_time;
+ double offset;
+ double peer_delay;
+ double peer_dispersion;
+ double response_time;
+ double jitter_asymmetry;
+ uint16_t tests;
+ int interleaved;
+ int authenticated;
+ char tx_tss_char;
+ char rx_tss_char;
+ uint32_t total_tx_count;
+ uint32_t total_rx_count;
+ uint32_t total_valid_count;
+ uint32_t total_good_count;
+} RPT_NTPReport;
+
+typedef struct {
+ NTP_AuthMode mode;
+ uint32_t key_id;
+ int key_type;
+ int key_length;
+ int ke_attempts;
+ uint32_t last_ke_ago;
+ int cookies;
+ int cookie_length;
+ int nak;
+} RPT_AuthReport;
+
+typedef struct {
+ uint32_t ref_id;
+ IPAddr ip_addr;
+ char state_char;
+ int authentication;
+ NTP_Leap leap;
+ int conf_options;
+ int eff_options;
+ uint32_t last_sample_ago;
+ double score;
+ double lo_limit;
+ double hi_limit;
+} RPT_SelectReport;
+
+#endif /* GOT_REPORTS_H */
diff --git a/rtc.c b/rtc.c
new file mode 100644
index 0000000..d946541
--- /dev/null
+++ b/rtc.c
@@ -0,0 +1,242 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "rtc.h"
+#include "local.h"
+#include "logging.h"
+#include "conf.h"
+
+#if defined LINUX && defined FEAT_RTC
+#include "rtc_linux.h"
+#endif /* defined LINUX */
+
+/* ================================================== */
+
+static int driver_initialised = 0;
+static int driver_preinit_ok = 0;
+
+static struct {
+ int (*init)(void);
+ void (*fini)(void);
+ int (*time_pre_init)(time_t driftfile_time);
+ void (*time_init)(void (*after_hook)(void*), void *anything);
+ void (*start_measurements)(void);
+ int (*write_parameters)(void);
+ int (*get_report)(RPT_RTC_Report *report);
+ int (*trim)(void);
+} driver =
+{
+#if defined LINUX && defined FEAT_RTC
+ RTC_Linux_Initialise,
+ RTC_Linux_Finalise,
+ RTC_Linux_TimePreInit,
+ RTC_Linux_TimeInit,
+ RTC_Linux_StartMeasurements,
+ RTC_Linux_WriteParameters,
+ RTC_Linux_GetReport,
+ RTC_Linux_Trim
+#else
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+#endif
+};
+
+/* ================================================== */
+/* Get the last modification time of the driftfile */
+
+static time_t
+get_driftfile_time(void)
+{
+ struct stat buf;
+ char *drift_file;
+
+ drift_file = CNF_GetDriftFile();
+ if (!drift_file)
+ return 0;
+
+ if (stat(drift_file, &buf))
+ return 0;
+
+ return buf.st_mtime;
+}
+
+/* ================================================== */
+/* Set the system time to the driftfile time if it's in the future */
+
+static void
+apply_driftfile_time(time_t t)
+{
+ struct timespec now;
+
+ LCL_ReadCookedTime(&now, NULL);
+
+ if (now.tv_sec < t) {
+ if (LCL_ApplyStepOffset(now.tv_sec - t))
+ LOG(LOGS_INFO, "System time restored from driftfile");
+ }
+}
+
+/* ================================================== */
+
+void
+RTC_Initialise(int initial_set)
+{
+ time_t driftfile_time;
+ char *file_name;
+
+ /* If the -s option was specified, try to do an initial read of the RTC and
+ set the system time to it. Also, read the last modification time of the
+ driftfile (i.e. system time when chronyd was previously stopped) and set
+ the system time to it if it's in the future to bring the clock closer to
+ the true time when the RTC is broken (e.g. it has no battery), is missing,
+ or there is no RTC driver. */
+ if (initial_set) {
+ driftfile_time = get_driftfile_time();
+
+ if (driver.time_pre_init && driver.time_pre_init(driftfile_time)) {
+ driver_preinit_ok = 1;
+ } else {
+ driver_preinit_ok = 0;
+ if (driftfile_time)
+ apply_driftfile_time(driftfile_time);
+ }
+ }
+
+ driver_initialised = 0;
+
+ /* This is how we tell whether the user wants to load the RTC
+ driver, if he is on a machine where it is an option. */
+ file_name = CNF_GetRtcFile();
+
+ if (file_name) {
+ if (CNF_GetRtcSync()) {
+ LOG_FATAL("rtcfile directive cannot be used with rtcsync");
+ }
+
+ if (driver.init) {
+ if ((driver.init)()) {
+ driver_initialised = 1;
+ } else {
+ LOG(LOGS_ERR, "RTC driver could not be initialised");
+ }
+ } else {
+ LOG(LOGS_ERR, "RTC not supported on this operating system");
+ }
+ }
+}
+
+/* ================================================== */
+
+void
+RTC_Finalise(void)
+{
+ if (driver_initialised) {
+ (driver.fini)();
+ }
+}
+
+/* ================================================== */
+/* Start the processing to get a single measurement from the real time
+ clock, and use it to trim the system time, based on knowing the
+ drift rate of the RTC and the error the last time we set it. If the
+ TimePreInit routine has succeeded, we can be sure that the trim required
+ is not *too* large.
+
+ We are called with a hook to a function to be called after the
+ initialisation is complete. We also call this if we cannot do the
+ initialisation. */
+
+void
+RTC_TimeInit(void (*after_hook)(void *), void *anything)
+{
+ if (driver_initialised && driver_preinit_ok) {
+ (driver.time_init)(after_hook, anything);
+ } else {
+ (after_hook)(anything);
+ }
+}
+
+/* ================================================== */
+/* Start the RTC measurement process */
+
+void
+RTC_StartMeasurements(void)
+{
+ if (driver_initialised) {
+ (driver.start_measurements)();
+ }
+ /* Benign if driver not present */
+}
+
+/* ================================================== */
+/* Write RTC information out to RTC file. Return 0 for success, 1 if
+ RTC driver not running, or 2 if the file cannot be written. */
+
+int
+RTC_WriteParameters(void)
+{
+ if (driver_initialised) {
+ return (driver.write_parameters)();
+ } else {
+ return RTC_ST_NODRV;
+ }
+}
+
+/* ================================================== */
+
+int
+RTC_GetReport(RPT_RTC_Report *report)
+{
+ if (driver_initialised) {
+ return (driver.get_report)(report);
+ } else {
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+int
+RTC_Trim(void)
+{
+ if (driver_initialised) {
+ return (driver.trim)();
+ } else {
+ return 0;
+ }
+}
+
+/* ================================================== */
+
diff --git a/rtc.h b/rtc.h
new file mode 100644
index 0000000..8fd677b
--- /dev/null
+++ b/rtc.h
@@ -0,0 +1,45 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ */
+
+#ifndef GOT_RTC_H
+#define GOT_RTC_H
+
+#include "reports.h"
+
+extern void RTC_Initialise(int initial_set);
+extern void RTC_Finalise(void);
+extern void RTC_TimeInit(void (*after_hook)(void *), void *anything);
+extern void RTC_StartMeasurements(void);
+extern int RTC_GetReport(RPT_RTC_Report *report);
+
+#define RTC_ST_OK 0
+#define RTC_ST_NODRV 1
+#define RTC_ST_BADFILE 2
+
+extern int RTC_WriteParameters(void);
+
+extern int RTC_Trim(void);
+
+#endif /* GOT_RTC_H */
diff --git a/rtc_linux.c b/rtc_linux.c
new file mode 100644
index 0000000..58b625b
--- /dev/null
+++ b/rtc_linux.c
@@ -0,0 +1,1072 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2012-2014
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Real-time clock driver for linux. This interfaces the program with
+ the clock that keeps time when the machine is turned off.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <linux/rtc.h>
+
+#include "logging.h"
+#include "sched.h"
+#include "local.h"
+#include "util.h"
+#include "sys_linux.h"
+#include "reference.h"
+#include "regress.h"
+#include "rtc.h"
+#include "rtc_linux.h"
+#include "conf.h"
+#include "memory.h"
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void measurement_timeout(void *any);
+
+static void read_from_device(int fd_, int event, void *any);
+
+/* ================================================== */
+
+typedef enum {
+ OM_NORMAL,
+ OM_INITIAL,
+ OM_AFTERTRIM
+} OperatingMode;
+
+static OperatingMode operating_mode = OM_NORMAL;
+
+/* ================================================== */
+
+static int fd;
+
+#define LOWEST_MEASUREMENT_PERIOD 15
+#define HIGHEST_MEASUREMENT_PERIOD 480
+#define N_SAMPLES_PER_REGRESSION 1
+
+static int measurement_period = LOWEST_MEASUREMENT_PERIOD;
+
+static SCH_TimeoutID timeout_id = 0;
+
+static int skip_interrupts;
+
+/* ================================================== */
+
+/* Maximum number of samples held */
+#define MAX_SAMPLES 64
+
+/* Real time clock samples. We store the seconds count as originally
+ measured. */
+static time_t *rtc_sec = NULL;
+
+/* Reference time, against which delta times on the RTC scale are measured */
+static time_t rtc_ref;
+
+/* System clock samples associated with the above samples. */
+static struct timespec *system_times = NULL;
+
+/* Number of samples currently stored. */
+static int n_samples;
+
+/* Number of new samples since last regression */
+static int n_samples_since_regression;
+
+/* Number of runs of residuals in last regression (for logging) */
+static int n_runs;
+
+/* Coefficients */
+/* Whether they are valid */
+static int coefs_valid;
+
+/* Reference time */
+static time_t coef_ref_time;
+/* Number of seconds by which RTC was fast of the system time at coef_ref_time */
+static double coef_seconds_fast;
+
+/* Estimated number of seconds that RTC gains relative to system time
+ for each second of ITS OWN time */
+static double coef_gain_rate;
+
+/* Gain rate saved just before we step the RTC to correct it to the
+ nearest second, so that we can write a useful set of coefs to the
+ RTC data file once we have reacquired its offset after the step */
+static double saved_coef_gain_rate;
+
+/* Threshold for automatic RTC trimming in seconds, zero when disabled */
+static double autotrim_threshold;
+
+/* Filename supplied by config file where RTC coefficients are
+ stored. */
+static char *coefs_file_name;
+
+/* ================================================== */
+/* Coefficients read from file at start of run. */
+
+/* Whether we have tried to load the coefficients */
+static int tried_to_load_coefs = 0;
+
+/* Whether valid coefficients were read */
+static int valid_coefs_from_file = 0;
+
+/* Coefs read in */
+static time_t file_ref_time;
+static double file_ref_offset, file_rate_ppm;
+
+/* ================================================== */
+
+/* Flag to remember whether to assume the RTC is running on UTC */
+static int rtc_on_utc;
+
+/* ================================================== */
+
+static LOG_FileID logfileid;
+
+/* ================================================== */
+
+static void (*after_init_hook)(void *) = NULL;
+static void *after_init_hook_arg = NULL;
+
+/* ================================================== */
+
+static void
+discard_samples(int new_first)
+{
+ int n_to_save;
+
+ assert(new_first >= 0 && new_first < n_samples);
+
+ n_to_save = n_samples - new_first;
+
+ memmove(rtc_sec, rtc_sec + new_first, n_to_save * sizeof(time_t));
+ memmove(system_times, system_times + new_first, n_to_save * sizeof(struct timespec));
+
+ n_samples = n_to_save;
+}
+
+/* ================================================== */
+
+#define NEW_FIRST_WHEN_FULL 4
+
+static void
+accumulate_sample(time_t rtc, struct timespec *sys)
+{
+
+ if (n_samples == MAX_SAMPLES) {
+ /* Discard oldest samples */
+ discard_samples(NEW_FIRST_WHEN_FULL);
+ }
+
+ /* Discard all samples if the RTC was stepped back (not our trim) */
+ if (n_samples > 0 && rtc_sec[n_samples - 1] >= rtc) {
+ DEBUG_LOG("RTC samples discarded");
+ n_samples = 0;
+ }
+
+ /* Always use most recent sample as reference */
+ rtc_ref = rtc;
+ rtc_sec[n_samples] = rtc;
+ system_times[n_samples] = *sys;
+ ++n_samples_since_regression;
+ ++n_samples;
+}
+
+/* ================================================== */
+/* The new_sample flag is to indicate whether to adjust the
+ measurement period depending on the behaviour of the standard
+ deviation. */
+
+static void
+run_regression(int new_sample,
+ int *valid,
+ time_t *ref,
+ double *fast,
+ double *slope)
+{
+ double rtc_rel[MAX_SAMPLES]; /* Relative times on RTC axis */
+ double offsets[MAX_SAMPLES]; /* How much the RTC is fast of the system clock */
+ int i;
+ double est_intercept, est_slope;
+ int best_new_start;
+
+ if (n_samples > 0) {
+
+ for (i=0; i<n_samples; i++) {
+ rtc_rel[i] = (double)(rtc_sec[i] - rtc_ref);
+ offsets[i] = ((double) (rtc_ref - system_times[i].tv_sec) -
+ (1.0e-9 * system_times[i].tv_nsec) +
+ rtc_rel[i]);
+
+ }
+
+ if (RGR_FindBestRobustRegression
+ (rtc_rel, offsets,
+ n_samples, 1.0e-9,
+ &est_intercept, &est_slope,
+ &n_runs,
+ &best_new_start)) {
+
+ /* Calculate and store coefficients. We don't do any error
+ bounds processing on any of these. */
+ *valid = 1;
+ *ref = rtc_ref;
+ *fast = est_intercept;
+ *slope = est_slope;
+
+ if (best_new_start > 0) {
+ discard_samples(best_new_start);
+ }
+
+
+ } else {
+ /* Keep existing coefficients. */
+ }
+ } else {
+ /* Keep existing coefficients. */
+ }
+
+}
+
+/* ================================================== */
+
+static void
+slew_samples
+(struct timespec *raw, struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything)
+{
+ int i;
+ double delta_time;
+ double old_seconds_fast, old_gain_rate;
+
+ if (change_type == LCL_ChangeUnknownStep) {
+ /* Drop all samples. */
+ n_samples = 0;
+ }
+
+ for (i=0; i<n_samples; i++) {
+ UTI_AdjustTimespec(system_times + i, cooked, system_times + i, &delta_time,
+ dfreq, doffset);
+ }
+
+ old_seconds_fast = coef_seconds_fast;
+ old_gain_rate = coef_gain_rate;
+
+ if (coefs_valid) {
+ coef_seconds_fast += doffset;
+ coef_gain_rate += dfreq * (1.0 - coef_gain_rate);
+ }
+
+ DEBUG_LOG("dfreq=%.8f doffset=%.6f old_fast=%.6f old_rate=%.3f new_fast=%.6f new_rate=%.3f",
+ dfreq, doffset,
+ old_seconds_fast, 1.0e6 * old_gain_rate,
+ coef_seconds_fast, 1.0e6 * coef_gain_rate);
+}
+
+/* ================================================== */
+
+/* Function to convert from a time_t value represenging UTC to the
+ corresponding real time clock 'DMY HMS' form, taking account of
+ whether the user runs his RTC on the local time zone or UTC */
+
+static struct tm *
+rtc_from_t(const time_t *t)
+{
+ if (rtc_on_utc) {
+ return gmtime(t);
+ } else {
+ return localtime(t);
+ }
+}
+
+/* ================================================== */
+
+/* Inverse function to get back from RTC 'DMY HMS' form to time_t UTC
+ form. This essentially uses mktime(), but involves some awful
+ complexity to cope with timezones. The problem is that mktime's
+ behaviour with regard to the daylight saving flag in the 'struct
+ tm' does not seem to be reliable across all systems, unless that
+ flag is set to zero.
+
+ tm_isdst = -1 does not seem to work with all libc's - it is treated
+ as meaning there is DST, or fails completely. (It is supposed to
+ use the timezone info to work out whether summer time is active at
+ the specified epoch).
+
+ tm_isdst = 1 fails if the local timezone has no summer time defined.
+
+ The approach taken is as follows. Suppose the RTC is on localtime.
+ We perform all mktime calls with the tm_isdst field set to zero.
+
+ Let y be the RTC reading in 'DMY HMS' form. Let M be the mktime
+ function with tm_isdst=0 and L be the localtime function.
+
+ We seek x such that y = L(x). Now there will exist a value Z(t)
+ such that M(L(t)) = t + Z(t) for all t, where Z(t) depends on
+ whether daylight saving is active at time t.
+
+ We want L(x) = y. Therefore M(L(x)) = x + Z = M(y). But
+ M(L(M(y))) = M(y) + Z. Therefore x = M(y) - Z = M(y) - (M(L(M(y)))
+ - M(y)).
+
+ The case for the RTC running on UTC is identical but without the
+ potential complication that Z depends on t.
+*/
+
+static time_t
+t_from_rtc(struct tm *stm) {
+ struct tm temp1, temp2, *tm;
+ long diff;
+ time_t t1, t2;
+
+ temp1 = *stm;
+ temp1.tm_isdst = 0;
+
+ t1 = mktime(&temp1);
+
+ tm = rtc_on_utc ? gmtime(&t1) : localtime(&t1);
+ if (!tm) {
+ DEBUG_LOG("gmtime()/localtime() failed");
+ return -1;
+ }
+
+ temp2 = *tm;
+ temp2.tm_isdst = 0;
+ t2 = mktime(&temp2);
+ diff = t2 - t1;
+
+ if (t1 - diff == -1)
+ DEBUG_LOG("Could not convert RTC time");
+
+ return t1 - diff;
+}
+
+/* ================================================== */
+
+static void
+read_hwclock_file(const char *hwclock_file)
+{
+ FILE *in;
+ char line[256];
+ int i;
+
+ if (!hwclock_file || !hwclock_file[0])
+ return;
+
+ in = UTI_OpenFile(NULL, hwclock_file, NULL, 'r', 0);
+ if (!in)
+ return;
+
+ /* Read third line from the file. */
+ for (i = 0; i < 3; i++) {
+ if (!fgets(line, sizeof(line), in))
+ break;
+ }
+
+ fclose(in);
+
+ if (i == 3 && !strncmp(line, "LOCAL", 5)) {
+ rtc_on_utc = 0;
+ } else if (i == 3 && !strncmp(line, "UTC", 3)) {
+ rtc_on_utc = 1;
+ } else {
+ LOG(LOGS_WARN, "Could not read RTC LOCAL/UTC setting from %s", hwclock_file);
+ }
+}
+
+/* ================================================== */
+
+static void
+setup_config(void)
+{
+ if (CNF_GetRtcOnUtc()) {
+ rtc_on_utc = 1;
+ } else {
+ rtc_on_utc = 0;
+ }
+
+ read_hwclock_file(CNF_GetHwclockFile());
+
+ autotrim_threshold = CNF_GetRtcAutotrim();
+}
+
+/* ================================================== */
+/* Read the coefficients from the file where they were saved
+ the last time the program was run. */
+
+static void
+read_coefs_from_file(void)
+{
+ double ref_time;
+ FILE *in;
+
+ if (!tried_to_load_coefs) {
+
+ valid_coefs_from_file = 0; /* only gets set true if we succeed */
+
+ tried_to_load_coefs = 1;
+
+ if (coefs_file_name &&
+ (in = UTI_OpenFile(NULL, coefs_file_name, NULL, 'r', 0))) {
+ if (fscanf(in, "%d%lf%lf%lf",
+ &valid_coefs_from_file,
+ &ref_time,
+ &file_ref_offset,
+ &file_rate_ppm) == 4) {
+ file_ref_time = ref_time;
+ } else {
+ LOG(LOGS_WARN, "Could not read coefficients from %s", coefs_file_name);
+ }
+ fclose(in);
+ }
+ }
+}
+
+/* ================================================== */
+/* Write the coefficients to the file where they will be read
+ the next time the program is run. */
+
+static int
+write_coefs_to_file(int valid,time_t ref_time,double offset,double rate)
+{
+ FILE *out;
+
+ /* Create a temporary file with a '.tmp' extension. */
+ out = UTI_OpenFile(NULL, coefs_file_name, ".tmp", 'w', 0644);
+ if (!out)
+ return RTC_ST_BADFILE;
+
+ /* Gain rate is written out in ppm */
+ fprintf(out, "%1d %.0f %.6f %.3f\n", valid, (double)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;
+
+ 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
+ between executions. Return status is whether we could initialise
+ on this version of the system. */
+
+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);
+ system_times = MallocArray(struct timespec, MAX_SAMPLES);
+
+ /* Setup details depending on configuration options */
+ setup_config();
+
+ /* In case it didn't get done by pre-init */
+ coefs_file_name = CNF_GetRtcFile();
+
+ n_samples = 0;
+ n_samples_since_regression = 0;
+ n_runs = 0;
+ coefs_valid = 0;
+
+ measurement_period = LOWEST_MEASUREMENT_PERIOD;
+
+ operating_mode = OM_NORMAL;
+
+ /* Register file handler */
+ SCH_AddFileHandler(fd, SCH_FILE_INPUT, read_from_device, NULL);
+
+ /* Register slew handler */
+ LCL_AddParameterChangeHandler(slew_samples, NULL);
+
+ logfileid = CNF_GetLogRtc() ? LOG_FileOpen("rtc",
+ " Date (UTC) Time RTC fast (s) Val Est fast (s) Slope (ppm) Ns Nr Meas")
+ : -1;
+ return 1;
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_Finalise(void)
+{
+ SCH_RemoveTimeout(timeout_id);
+ timeout_id = 0;
+
+ /* Remove input file handler */
+ if (fd >= 0) {
+ SCH_RemoveFileHandler(fd);
+ switch_interrupts(0);
+ close(fd);
+
+ /* Save the RTC data */
+ (void) RTC_Linux_WriteParameters();
+
+ }
+
+ if (rtc_sec)
+ LCL_RemoveParameterChangeHandler(slew_samples, NULL);
+
+ Free(rtc_sec);
+ Free(system_times);
+}
+
+/* ================================================== */
+
+static void
+measurement_timeout(void *any)
+{
+ timeout_id = 0;
+ switch_interrupts(1);
+}
+
+/* ================================================== */
+
+static void
+set_rtc(time_t new_rtc_time)
+{
+ struct tm rtc_tm;
+ struct rtc_time rtc_raw;
+ int status;
+
+ rtc_tm = *rtc_from_t(&new_rtc_time);
+
+ rtc_raw.tm_sec = rtc_tm.tm_sec;
+ rtc_raw.tm_min = rtc_tm.tm_min;
+ rtc_raw.tm_hour = rtc_tm.tm_hour;
+ rtc_raw.tm_mday = rtc_tm.tm_mday;
+ rtc_raw.tm_mon = rtc_tm.tm_mon;
+ rtc_raw.tm_year = rtc_tm.tm_year;
+ rtc_raw.tm_wday = rtc_tm.tm_wday;
+ rtc_raw.tm_yday = rtc_tm.tm_yday;
+ rtc_raw.tm_isdst = rtc_tm.tm_isdst;
+
+ status = ioctl(fd, RTC_SET_TIME, &rtc_raw);
+ if (status < 0) {
+ LOG(LOGS_ERR, "Could not set RTC time");
+ }
+
+}
+
+/* ================================================== */
+
+static void
+handle_initial_trim(void)
+{
+ double rate;
+ long delta_time;
+ double rtc_error_now, sys_error_now;
+
+ /* The idea is to accumulate some number of samples at 1 second
+ intervals, then do a robust regression fit to this. This
+ should give a good fix on the intercept (=system clock error
+ rel to RTC) at a particular time, removing risk of any
+ particular sample being an outlier. We can then look at the
+ elapsed interval since the epoch recorded in the RTC file,
+ and correct the system time accordingly. */
+
+ run_regression(1, &coefs_valid, &coef_ref_time, &coef_seconds_fast, &coef_gain_rate);
+
+ n_samples_since_regression = 0;
+ n_samples = 0;
+
+ read_coefs_from_file();
+
+ if (valid_coefs_from_file) {
+ /* Can process data */
+ delta_time = coef_ref_time - file_ref_time;
+ rate = 1.0e-6 * file_rate_ppm;
+ rtc_error_now = file_ref_offset + rate * (double) delta_time;
+
+ /* sys_error_now is positive if the system clock is fast */
+ sys_error_now = rtc_error_now - coef_seconds_fast;
+
+ LCL_AccumulateOffset(sys_error_now, 0.0);
+ LOG(LOGS_INFO, "System clock off from RTC by %f seconds (slew)",
+ sys_error_now);
+ } else {
+ LOG(LOGS_WARN, "No valid rtcfile coefficients");
+ }
+
+ coefs_valid = 0;
+
+ (after_init_hook)(after_init_hook_arg);
+
+ operating_mode = OM_NORMAL;
+}
+
+/* ================================================== */
+
+static void
+handle_relock_after_trim(void)
+{
+ int valid;
+ time_t ref;
+ double fast, slope;
+
+ valid = 0;
+ run_regression(1, &valid, &ref, &fast, &slope);
+
+ if (valid) {
+ write_coefs_to_file(1,ref,fast,saved_coef_gain_rate);
+ } else {
+ DEBUG_LOG("Could not do regression after trim");
+ }
+
+ coefs_valid = 0;
+ n_samples = 0;
+ n_samples_since_regression = 0;
+ operating_mode = OM_NORMAL;
+ measurement_period = LOWEST_MEASUREMENT_PERIOD;
+}
+
+/* ================================================== */
+
+static void
+maybe_autotrim(void)
+{
+ /* Trim only when in normal mode, the coefficients are fresh, the current
+ offset is above the threshold and the system clock is synchronized */
+
+ if (operating_mode != OM_NORMAL || !coefs_valid || n_samples_since_regression)
+ return;
+
+ if (autotrim_threshold <= 0.0 || fabs(coef_seconds_fast) < autotrim_threshold)
+ return;
+
+ if (REF_GetOurStratum() >= 16)
+ return;
+
+ RTC_Linux_Trim();
+}
+
+/* ================================================== */
+
+static void
+process_reading(time_t rtc_time, struct timespec *system_time)
+{
+ double rtc_fast;
+
+ accumulate_sample(rtc_time, system_time);
+
+ switch (operating_mode) {
+ case OM_NORMAL:
+
+ if (n_samples_since_regression >= N_SAMPLES_PER_REGRESSION) {
+ run_regression(1, &coefs_valid, &coef_ref_time, &coef_seconds_fast, &coef_gain_rate);
+ n_samples_since_regression = 0;
+ maybe_autotrim();
+ }
+
+ break;
+ case OM_INITIAL:
+ if (n_samples_since_regression >= 8) {
+ handle_initial_trim();
+ }
+ break;
+ case OM_AFTERTRIM:
+ if (n_samples_since_regression >= 8) {
+ handle_relock_after_trim();
+ }
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+
+ if (logfileid != -1) {
+ rtc_fast = (rtc_time - system_time->tv_sec) - 1.0e-9 * system_time->tv_nsec;
+
+ LOG_FileWrite(logfileid, "%s %14.6f %1d %14.6f %12.3f %2d %2d %4d",
+ UTI_TimeToLogForm(system_time->tv_sec),
+ rtc_fast,
+ coefs_valid,
+ coef_seconds_fast, coef_gain_rate * 1.0e6, n_samples, n_runs, measurement_period);
+ }
+
+}
+
+/* ================================================== */
+
+static void
+read_from_device(int fd_, int event, void *any)
+{
+ int status;
+ unsigned long data;
+ struct timespec sys_time;
+ struct rtc_time rtc_raw;
+ struct tm rtc_tm;
+ time_t rtc_t;
+ int error = 0;
+
+ status = read(fd, &data, sizeof(data));
+
+ if (status < 0) {
+ /* This looks like a bad error : the file descriptor was indicating it was
+ * ready to read but we couldn't read anything. Give up. */
+ LOG(LOGS_ERR, "Could not read flags %s : %s", CNF_GetRtcDevice(), strerror(errno));
+ SCH_RemoveFileHandler(fd);
+ switch_interrupts(0); /* Likely to raise error too, but just to be sure... */
+ close(fd);
+ fd = -1;
+ return;
+ }
+
+ if (skip_interrupts > 0) {
+ /* Wait for the next interrupt, this one may be bogus */
+ skip_interrupts--;
+ return;
+ }
+
+ if ((data & RTC_UF) == RTC_UF) {
+ /* Update interrupt detected */
+
+ /* Read RTC time, sandwiched between two polls of the system clock
+ so we can bound any error. */
+
+ SCH_GetLastEventTime(&sys_time, NULL, NULL);
+
+ status = ioctl(fd, RTC_RD_TIME, &rtc_raw);
+ if (status < 0) {
+ LOG(LOGS_ERR, "Could not read time from %s : %s", CNF_GetRtcDevice(), strerror(errno));
+ error = 1;
+ goto turn_off_interrupt;
+ }
+
+ /* Convert RTC time into a struct timespec */
+ rtc_tm.tm_sec = rtc_raw.tm_sec;
+ rtc_tm.tm_min = rtc_raw.tm_min;
+ rtc_tm.tm_hour = rtc_raw.tm_hour;
+ rtc_tm.tm_mday = rtc_raw.tm_mday;
+ rtc_tm.tm_mon = rtc_raw.tm_mon;
+ rtc_tm.tm_year = rtc_raw.tm_year;
+
+ rtc_t = t_from_rtc(&rtc_tm);
+
+ if (rtc_t == (time_t)(-1)) {
+ error = 1;
+ goto turn_off_interrupt;
+ }
+
+ process_reading(rtc_t, &sys_time);
+
+ if (n_samples < 4) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD;
+ } else if (n_samples < 6) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 1;
+ } else if (n_samples < 10) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 2;
+ } else if (n_samples < 14) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 3;
+ } else {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 4;
+ }
+
+ }
+
+turn_off_interrupt:
+
+ switch (operating_mode) {
+ case OM_INITIAL:
+ if (error) {
+ DEBUG_LOG("Could not complete initial step due to errors");
+ operating_mode = OM_NORMAL;
+ (after_init_hook)(after_init_hook_arg);
+
+ switch_interrupts(0);
+
+ timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+ }
+
+ break;
+
+ case OM_AFTERTRIM:
+ if (error) {
+ DEBUG_LOG("Could not complete after trim relock due to errors");
+ operating_mode = OM_NORMAL;
+
+ switch_interrupts(0);
+
+ timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+ }
+
+ break;
+
+ case OM_NORMAL:
+ switch_interrupts(0);
+
+ timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_TimeInit(void (*after_hook)(void *), void *anything)
+{
+ after_init_hook = after_hook;
+ after_init_hook_arg = anything;
+
+ operating_mode = OM_INITIAL;
+ timeout_id = 0;
+ switch_interrupts(1);
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_StartMeasurements(void)
+{
+ measurement_timeout(NULL);
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_WriteParameters(void)
+{
+ int retval;
+
+ if (fd < 0) {
+ return RTC_ST_NODRV;
+ }
+
+ if (coefs_valid) {
+ retval = write_coefs_to_file(1,coef_ref_time, coef_seconds_fast, coef_gain_rate);
+ } else {
+ /* Don't change the existing file, it may not be 100% valid but is our
+ current best guess. */
+ retval = RTC_ST_OK; /*write_coefs_to_file(0,0,0.0,0.0); */
+ }
+
+ return(retval);
+}
+
+/* ================================================== */
+/* Try to set the system clock from the RTC, in the same manner as
+ /sbin/hwclock -s would do. We're not as picky about OS version
+ etc in this case, since we have fewer requirements regarding the
+ RTC behaviour than we do for the rest of the module. */
+
+int
+RTC_Linux_TimePreInit(time_t driftfile_time)
+{
+ int fd, status;
+ struct rtc_time rtc_raw, rtc_raw_retry;
+ struct tm rtc_tm;
+ time_t rtc_t;
+ double accumulated_error, sys_offset;
+ struct timespec new_sys_time, old_sys_time;
+
+ coefs_file_name = CNF_GetRtcFile();
+
+ setup_config();
+ read_coefs_from_file();
+
+ fd = open(CNF_GetRtcDevice(), O_RDONLY);
+
+ if (fd < 0) {
+ return 0; /* Can't open it, and won't be able to later */
+ }
+
+ /* Retry reading the rtc until both read attempts give the same sec value.
+ This way the race condition is prevented that the RTC has updated itself
+ during the first read operation. */
+ do {
+ status = ioctl(fd, RTC_RD_TIME, &rtc_raw);
+ if (status >= 0) {
+ status = ioctl(fd, RTC_RD_TIME, &rtc_raw_retry);
+ }
+ } while (status >= 0 && rtc_raw.tm_sec != rtc_raw_retry.tm_sec);
+
+ /* Read system clock */
+ LCL_ReadCookedTime(&old_sys_time, NULL);
+
+ close(fd);
+
+ if (status >= 0) {
+ /* Convert to seconds since 1970 */
+ rtc_tm.tm_sec = rtc_raw.tm_sec;
+ rtc_tm.tm_min = rtc_raw.tm_min;
+ rtc_tm.tm_hour = rtc_raw.tm_hour;
+ rtc_tm.tm_mday = rtc_raw.tm_mday;
+ rtc_tm.tm_mon = rtc_raw.tm_mon;
+ rtc_tm.tm_year = rtc_raw.tm_year;
+
+ rtc_t = t_from_rtc(&rtc_tm);
+
+ if (rtc_t != (time_t)(-1)) {
+
+ /* Work out approximatation to correct time (to about the
+ nearest second) */
+ if (valid_coefs_from_file) {
+ accumulated_error = file_ref_offset +
+ (rtc_t - file_ref_time) * 1.0e-6 * file_rate_ppm;
+ } else {
+ accumulated_error = 0.0;
+ }
+
+ /* Correct time */
+
+ new_sys_time.tv_sec = rtc_t;
+ /* Average error in the RTC reading */
+ new_sys_time.tv_nsec = 500000000;
+
+ UTI_AddDoubleToTimespec(&new_sys_time, -accumulated_error, &new_sys_time);
+
+ if (new_sys_time.tv_sec < driftfile_time) {
+ LOG(LOGS_WARN, "RTC time before last driftfile modification (ignored)");
+ return 0;
+ }
+
+ sys_offset = UTI_DiffTimespecsToDouble(&old_sys_time, &new_sys_time);
+
+ /* Set system time only if the step is larger than 1 second */
+ if (fabs(sys_offset) >= 1.0) {
+ if (LCL_ApplyStepOffset(sys_offset))
+ LOG(LOGS_INFO, "System time set from RTC");
+ }
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_GetReport(RPT_RTC_Report *report)
+{
+ report->ref_time.tv_sec = coef_ref_time;
+ report->ref_time.tv_nsec = 0;
+ report->n_samples = n_samples;
+ report->n_runs = n_runs;
+ if (n_samples > 1) {
+ report->span_seconds = rtc_sec[n_samples - 1] - rtc_sec[0];
+ } else {
+ report->span_seconds = 0;
+ }
+ report->rtc_seconds_fast = coef_seconds_fast;
+ report->rtc_gain_rate_ppm = 1.0e6 * coef_gain_rate;
+ return 1;
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_Trim(void)
+{
+ struct timespec now;
+
+ /* Remember the slope coefficient - we won't be able to determine a
+ good one in a few seconds when we determine the new offset! */
+ saved_coef_gain_rate = coef_gain_rate;
+
+ if (fabs(coef_seconds_fast) > 1.0) {
+
+ LOG(LOGS_INFO, "RTC wrong by %.3f seconds (step)",
+ coef_seconds_fast);
+
+ /* Do processing to set clock. Let R be the value we set the
+ RTC to, then in 500ms the RTC ticks (R+1) (see comments in
+ arch/i386/kernel/time.c about the behaviour of the real time
+ clock chip). If S is the system time now, the error at the
+ next RTC tick is given by E = (R+1) - (S+0.5). Ideally we
+ want |E| <= 0.5, which implies R <= S <= R+1, i.e. R is just
+ the rounded down part of S, i.e. the seconds part. */
+
+ LCL_ReadCookedTime(&now, NULL);
+
+ set_rtc(now.tv_sec);
+
+ /* All old samples will now look bogus under the new
+ regime. */
+ n_samples = 0;
+ operating_mode = OM_AFTERTRIM;
+
+ /* Estimate the offset in case writertc is called or chronyd
+ is terminated during rapid sampling */
+ coef_seconds_fast = -now.tv_nsec / 1.0e9 + 0.5;
+ coef_ref_time = now.tv_sec;
+
+ /* And start rapid sampling, interrupts on now */
+ SCH_RemoveTimeout(timeout_id);
+ timeout_id = 0;
+ switch_interrupts(1);
+ }
+
+ return 1;
+
+}
diff --git a/rtc_linux.h b/rtc_linux.h
new file mode 100644
index 0000000..fa33ef1
--- /dev/null
+++ b/rtc_linux.h
@@ -0,0 +1,45 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ ======================================================================
+
+ */
+
+#ifndef _GOT_RTC_LINUX_H
+#define _GOT_RTC_LINUX_H
+
+#include "reports.h"
+
+extern int RTC_Linux_Initialise(void);
+extern void RTC_Linux_Finalise(void);
+extern int RTC_Linux_TimePreInit(time_t driftile_time);
+extern void RTC_Linux_TimeInit(void (*after_hook)(void *), void *anything);
+extern void RTC_Linux_StartMeasurements(void);
+
+/* 0=success, 1=no driver, 2=can't write file */
+extern int RTC_Linux_WriteParameters(void);
+
+extern int RTC_Linux_GetReport(RPT_RTC_Report *report);
+extern int RTC_Linux_Trim(void);
+
+extern void RTC_Linux_CycleLogFile(void);
+
+#endif /* _GOT_RTC_LINUX_H */
diff --git a/samplefilt.c b/samplefilt.c
new file mode 100644
index 0000000..9b81a76
--- /dev/null
+++ b/samplefilt.c
@@ -0,0 +1,497 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2009-2011, 2014, 2016, 2018
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing a median sample filter.
+
+ */
+
+#include "config.h"
+
+#include "local.h"
+#include "logging.h"
+#include "memory.h"
+#include "regress.h"
+#include "samplefilt.h"
+#include "util.h"
+
+#define MIN_SAMPLES 1
+#define MAX_SAMPLES 256
+
+struct SPF_Instance_Record {
+ int min_samples;
+ int max_samples;
+ int index;
+ int used;
+ int last;
+ int avg_var_n;
+ double avg_var;
+ double max_var;
+ double combine_ratio;
+ NTP_Sample *samples;
+ int *selected;
+ double *x_data;
+ double *y_data;
+ double *w_data;
+};
+
+/* ================================================== */
+
+SPF_Instance
+SPF_CreateInstance(int min_samples, int max_samples, double max_dispersion, double combine_ratio)
+{
+ SPF_Instance filter;
+
+ filter = MallocNew(struct SPF_Instance_Record);
+
+ min_samples = CLAMP(MIN_SAMPLES, min_samples, MAX_SAMPLES);
+ max_samples = CLAMP(MIN_SAMPLES, max_samples, MAX_SAMPLES);
+ max_samples = MAX(min_samples, max_samples);
+ combine_ratio = CLAMP(0.0, combine_ratio, 1.0);
+
+ filter->min_samples = min_samples;
+ filter->max_samples = max_samples;
+ filter->index = -1;
+ filter->used = 0;
+ filter->last = -1;
+ /* Set the first estimate to the system precision */
+ filter->avg_var_n = 0;
+ filter->avg_var = SQUARE(LCL_GetSysPrecisionAsQuantum());
+ filter->max_var = SQUARE(max_dispersion);
+ filter->combine_ratio = combine_ratio;
+ filter->samples = MallocArray(NTP_Sample, filter->max_samples);
+ filter->selected = MallocArray(int, filter->max_samples);
+ filter->x_data = MallocArray(double, filter->max_samples);
+ filter->y_data = MallocArray(double, filter->max_samples);
+ filter->w_data = MallocArray(double, filter->max_samples);
+
+ return filter;
+}
+
+/* ================================================== */
+
+void
+SPF_DestroyInstance(SPF_Instance filter)
+{
+ Free(filter->samples);
+ Free(filter->selected);
+ Free(filter->x_data);
+ Free(filter->y_data);
+ Free(filter->w_data);
+ Free(filter);
+}
+
+/* ================================================== */
+
+/* Check that samples times are strictly increasing */
+
+static int
+check_sample(SPF_Instance filter, NTP_Sample *sample)
+{
+ if (filter->used <= 0)
+ return 1;
+
+ if (UTI_CompareTimespecs(&filter->samples[filter->last].time, &sample->time) >= 0) {
+ DEBUG_LOG("filter non-increasing sample time %s", UTI_TimespecToString(&sample->time));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SPF_AccumulateSample(SPF_Instance filter, NTP_Sample *sample)
+{
+ if (!check_sample(filter, sample))
+ return 0;
+
+ filter->index++;
+ filter->index %= filter->max_samples;
+ filter->last = filter->index;
+ if (filter->used < filter->max_samples)
+ filter->used++;
+
+ filter->samples[filter->index] = *sample;
+
+ DEBUG_LOG("filter sample %d t=%s offset=%.9f peer_disp=%.9f",
+ filter->index, UTI_TimespecToString(&sample->time),
+ sample->offset, sample->peer_dispersion);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SPF_GetLastSample(SPF_Instance filter, NTP_Sample *sample)
+{
+ if (filter->last < 0)
+ return 0;
+
+ *sample = filter->samples[filter->last];
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SPF_GetNumberOfSamples(SPF_Instance filter)
+{
+ return filter->used;
+}
+
+/* ================================================== */
+
+int
+SPF_GetMaxSamples(SPF_Instance filter)
+{
+ return filter->max_samples;
+}
+
+/* ================================================== */
+
+double
+SPF_GetAvgSampleDispersion(SPF_Instance filter)
+{
+ return sqrt(filter->avg_var);
+}
+
+/* ================================================== */
+
+static void
+drop_samples(SPF_Instance filter, int keep_last)
+{
+ filter->index = -1;
+ filter->used = 0;
+ if (!keep_last)
+ filter->last = -1;
+}
+
+/* ================================================== */
+
+void
+SPF_DropSamples(SPF_Instance filter)
+{
+ drop_samples(filter, 0);
+}
+
+/* ================================================== */
+
+static const NTP_Sample *tmp_sort_samples;
+
+static int
+compare_samples(const void *a, const void *b)
+{
+ const NTP_Sample *s1, *s2;
+
+ s1 = &tmp_sort_samples[*(int *)a];
+ s2 = &tmp_sort_samples[*(int *)b];
+
+ if (s1->offset < s2->offset)
+ return -1;
+ else if (s1->offset > s2->offset)
+ return 1;
+ return 0;
+}
+
+/* ================================================== */
+
+static int
+select_samples(SPF_Instance filter)
+{
+ int i, j, k, o, from, to, *selected;
+ double min_dispersion;
+
+ if (filter->used < filter->min_samples)
+ return 0;
+
+ selected = filter->selected;
+
+ /* With 4 or more samples, select those that have peer dispersion smaller
+ than 1.5x of the minimum dispersion */
+ if (filter->used > 4) {
+ for (i = 1, min_dispersion = filter->samples[0].peer_dispersion; i < filter->used; i++) {
+ if (min_dispersion > filter->samples[i].peer_dispersion)
+ min_dispersion = filter->samples[i].peer_dispersion;
+ }
+
+ for (i = j = 0; i < filter->used; i++) {
+ if (filter->samples[i].peer_dispersion <= 1.5 * min_dispersion)
+ selected[j++] = i;
+ }
+ } else {
+ j = 0;
+ }
+
+ if (j < 4) {
+ /* Select all samples */
+
+ for (j = 0; j < filter->used; j++)
+ selected[j] = j;
+ }
+
+ /* And sort their indices by offset */
+ tmp_sort_samples = filter->samples;
+ qsort(selected, j, sizeof (int), compare_samples);
+
+ /* Select samples closest to the median */
+ if (j > 2) {
+ from = j * (1.0 - filter->combine_ratio) / 2.0;
+ from = CLAMP(1, from, (j - 1) / 2);
+ } else {
+ from = 0;
+ }
+
+ to = j - from;
+
+ /* Mark unused samples and sort the rest by their time */
+
+ o = filter->used - filter->index - 1;
+
+ for (i = 0; i < from; i++)
+ selected[i] = -1;
+ for (; i < to; i++)
+ selected[i] = (selected[i] + o) % filter->used;
+ for (; i < filter->used; i++)
+ selected[i] = -1;
+
+ for (i = from; i < to; i++) {
+ j = selected[i];
+ selected[i] = -1;
+ while (j != -1 && selected[j] != j) {
+ k = selected[j];
+ selected[j] = j;
+ j = k;
+ }
+ }
+
+ for (i = j = 0; i < filter->used; i++) {
+ if (selected[i] != -1)
+ selected[j++] = (selected[i] + filter->used - o) % filter->used;
+ }
+
+ assert(j > 0 && j <= filter->max_samples);
+
+ return j;
+}
+
+/* ================================================== */
+
+static int
+combine_selected_samples(SPF_Instance filter, int n, NTP_Sample *result)
+{
+ double mean_peer_dispersion, mean_root_dispersion, mean_peer_delay, mean_root_delay;
+ double mean_x, mean_y, disp, var, prev_avg_var;
+ NTP_Sample *sample, *last_sample;
+ int i, dof;
+
+ last_sample = &filter->samples[filter->selected[n - 1]];
+
+ /* Prepare data */
+ for (i = 0; i < n; i++) {
+ sample = &filter->samples[filter->selected[i]];
+
+ filter->x_data[i] = UTI_DiffTimespecsToDouble(&sample->time, &last_sample->time);
+ filter->y_data[i] = sample->offset;
+ filter->w_data[i] = sample->peer_dispersion;
+ }
+
+ /* Calculate mean offset and interval since the last sample */
+ for (i = 0, mean_x = mean_y = 0.0; i < n; i++) {
+ mean_x += filter->x_data[i];
+ mean_y += filter->y_data[i];
+ }
+ mean_x /= n;
+ mean_y /= n;
+
+ if (n >= 4) {
+ double b0, b1, s2, sb0, sb1;
+
+ /* Set y axis to the mean sample time */
+ for (i = 0; i < n; i++)
+ filter->x_data[i] -= mean_x;
+
+ /* Make a linear fit and use the estimated standard deviation of the
+ intercept as dispersion */
+ RGR_WeightedRegression(filter->x_data, filter->y_data, filter->w_data, n,
+ &b0, &b1, &s2, &sb0, &sb1);
+ var = s2;
+ disp = sb0;
+ dof = n - 2;
+ } else if (n >= 2) {
+ for (i = 0, disp = 0.0; i < n; i++)
+ disp += (filter->y_data[i] - mean_y) * (filter->y_data[i] - mean_y);
+ var = disp / (n - 1);
+ disp = sqrt(var);
+ dof = n - 1;
+ } else {
+ var = filter->avg_var;
+ disp = sqrt(var);
+ dof = 1;
+ }
+
+ /* Avoid working with zero dispersion */
+ if (var < 1e-20) {
+ var = 1e-20;
+ disp = sqrt(var);
+ }
+
+ /* Drop the sample if the variance is larger than the maximum */
+ if (filter->max_var > 0.0 && var > filter->max_var) {
+ DEBUG_LOG("filter dispersion too large disp=%.9f max=%.9f",
+ sqrt(var), sqrt(filter->max_var));
+ return 0;
+ }
+
+ prev_avg_var = filter->avg_var;
+
+ /* Update the exponential moving average of the variance */
+ if (filter->avg_var_n > 50) {
+ filter->avg_var += dof / (dof + 50.0) * (var - filter->avg_var);
+ } else {
+ filter->avg_var = (filter->avg_var * filter->avg_var_n + var * dof) /
+ (dof + filter->avg_var_n);
+ if (filter->avg_var_n == 0)
+ prev_avg_var = filter->avg_var;
+ filter->avg_var_n += dof;
+ }
+
+ /* Use the long-term average of variance instead of the estimated value
+ unless it is significantly smaller in order to reduce the noise in
+ sourcestats weights */
+ if (var * dof / RGR_GetChi2Coef(dof) < prev_avg_var)
+ disp = sqrt(filter->avg_var) * disp / sqrt(var);
+
+ mean_peer_dispersion = mean_root_dispersion = mean_peer_delay = mean_root_delay = 0.0;
+
+ for (i = 0; i < n; i++) {
+ sample = &filter->samples[filter->selected[i]];
+
+ mean_peer_dispersion += sample->peer_dispersion;
+ mean_root_dispersion += sample->root_dispersion;
+ mean_peer_delay += sample->peer_delay;
+ mean_root_delay += sample->root_delay;
+ }
+
+ mean_peer_dispersion /= n;
+ mean_root_dispersion /= n;
+ mean_peer_delay /= n;
+ mean_root_delay /= n;
+
+ UTI_AddDoubleToTimespec(&last_sample->time, mean_x, &result->time);
+ result->offset = mean_y;
+ result->peer_dispersion = MAX(disp, mean_peer_dispersion);
+ result->root_dispersion = MAX(disp, mean_root_dispersion);
+ result->peer_delay = mean_peer_delay;
+ result->root_delay = mean_root_delay;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SPF_GetFilteredSample(SPF_Instance filter, NTP_Sample *sample)
+{
+ int n;
+
+ n = select_samples(filter);
+
+ DEBUG_LOG("selected %d from %d samples", n, filter->used);
+
+ if (n < 1)
+ return 0;
+
+ if (!combine_selected_samples(filter, n, sample))
+ return 0;
+
+ drop_samples(filter, 1);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+get_first_last(SPF_Instance filter, int *first, int *last)
+{
+ if (filter->last < 0)
+ return 0;
+
+ /* Always slew the last sample as it may be returned even if no new
+ samples were accumulated */
+ if (filter->used > 0) {
+ *first = 0;
+ *last = filter->used - 1;
+ } else {
+ *first = *last = filter->last;
+ }
+
+ return 1;
+}
+
+
+/* ================================================== */
+
+void
+SPF_SlewSamples(SPF_Instance filter, struct timespec *when, double dfreq, double doffset)
+{
+ int i, first, last;
+ double delta_time;
+
+ if (!get_first_last(filter, &first, &last))
+ return;
+
+ for (i = first; i <= last; i++) {
+ UTI_AdjustTimespec(&filter->samples[i].time, when, &filter->samples[i].time,
+ &delta_time, dfreq, doffset);
+ filter->samples[i].offset -= delta_time;
+ }
+}
+
+/* ================================================== */
+
+void
+SPF_CorrectOffset(SPF_Instance filter, double doffset)
+{
+ int i, first, last;
+
+ if (!get_first_last(filter, &first, &last))
+ return;
+
+ for (i = first; i <= last; i++)
+ filter->samples[i].offset -= doffset;
+}
+
+/* ================================================== */
+
+void
+SPF_AddDispersion(SPF_Instance filter, double dispersion)
+{
+ int i;
+
+ for (i = 0; i < filter->used; i++) {
+ filter->samples[i].peer_dispersion += dispersion;
+ filter->samples[i].root_dispersion += dispersion;
+ }
+}
diff --git a/samplefilt.h b/samplefilt.h
new file mode 100644
index 0000000..8d72b85
--- /dev/null
+++ b/samplefilt.h
@@ -0,0 +1,51 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2018
+ *
+ * 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 sample filter.
+
+ */
+
+#ifndef GOT_SAMPLEFILT_H
+#define GOT_SAMPLEFILT_H
+
+#include "ntp.h"
+
+typedef struct SPF_Instance_Record *SPF_Instance;
+
+extern SPF_Instance SPF_CreateInstance(int min_samples, int max_samples,
+ double max_dispersion, double combine_ratio);
+extern void SPF_DestroyInstance(SPF_Instance filter);
+
+extern int SPF_AccumulateSample(SPF_Instance filter, NTP_Sample *sample);
+extern int SPF_GetLastSample(SPF_Instance filter, NTP_Sample *sample);
+extern int SPF_GetNumberOfSamples(SPF_Instance filter);
+extern int SPF_GetMaxSamples(SPF_Instance filter);
+extern double SPF_GetAvgSampleDispersion(SPF_Instance filter);
+extern void SPF_DropSamples(SPF_Instance filter);
+extern int SPF_GetFilteredSample(SPF_Instance filter, NTP_Sample *sample);
+extern void SPF_SlewSamples(SPF_Instance filter, struct timespec *when,
+ double dfreq, double doffset);
+extern void SPF_CorrectOffset(SPF_Instance filter, double doffset);
+extern void SPF_AddDispersion(SPF_Instance filter, double dispersion);
+
+#endif
diff --git a/sched.c b/sched.c
new file mode 100644
index 0000000..676866e
--- /dev/null
+++ b/sched.c
@@ -0,0 +1,866 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2011, 2013-2016
+ *
+ * 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 file contains the scheduling loop and the timeout queue.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "array.h"
+#include "sched.h"
+#include "memory.h"
+#include "util.h"
+#include "local.h"
+#include "logging.h"
+
+/* ================================================== */
+
+/* Flag indicating that we are initialised */
+static int initialised = 0;
+
+/* ================================================== */
+
+/* One more than the highest file descriptor that is registered */
+static unsigned int one_highest_fd;
+
+#ifndef FD_SETSIZE
+/* If FD_SETSIZE is not defined, assume that fd_set is implemented
+ as a fixed size array of bits, possibly embedded inside a record */
+#define FD_SETSIZE (sizeof(fd_set) * 8)
+#endif
+
+typedef struct {
+ SCH_FileHandler handler;
+ SCH_ArbitraryArgument arg;
+ int events;
+} FileHandlerEntry;
+
+static ARR_Instance file_handlers;
+
+/* Timestamp when last select() returned */
+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 */
+
+typedef struct _TimerQueueEntry
+{
+ struct _TimerQueueEntry *next; /* Forward and back links in the list */
+ struct _TimerQueueEntry *prev;
+ struct timespec ts; /* Local system time at which the
+ timeout is to expire. Clearly this
+ must be in terms of what the
+ operating system thinks of as
+ system time, because it will be an
+ argument to select(). Therefore,
+ any fudges etc that our local time
+ driver module would apply to time
+ that we pass to clients etc doesn't
+ apply to this. */
+ SCH_TimeoutID id; /* ID to allow client to delete
+ timeout */
+ SCH_TimeoutClass class; /* The class that the epoch is in */
+ SCH_TimeoutHandler handler; /* The handler routine to use */
+ SCH_ArbitraryArgument arg; /* The argument to pass to the handler */
+
+} TimerQueueEntry;
+
+/* The timer queue. We only use the next and prev entries of this
+ record, these chain to the real entries. */
+static TimerQueueEntry timer_queue;
+static unsigned long n_timer_queue_entries;
+static SCH_TimeoutID next_tqe_id;
+
+/* Pointer to head of free list */
+static TimerQueueEntry *tqe_free_list;
+
+/* Array of all allocated tqe blocks to be freed in finalisation */
+static ARR_Instance tqe_blocks;
+
+/* Timestamp when was last timeout dispatched for each class */
+static struct timespec last_class_dispatch[SCH_NumberOfClasses];
+
+/* ================================================== */
+
+/* Flag terminating the main loop, which can be set from a signal handler */
+static volatile int need_to_exit;
+
+/* ================================================== */
+
+static void
+handle_slew(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything);
+
+/* ================================================== */
+
+void
+SCH_Initialise(void)
+{
+ file_handlers = ARR_CreateInstance(sizeof (FileHandlerEntry));
+
+ n_timer_queue_entries = 0;
+ next_tqe_id = 0;
+ tqe_free_list = NULL;
+ tqe_blocks = ARR_CreateInstance(sizeof (TimerQueueEntry *));
+
+ timer_queue.next = &timer_queue;
+ timer_queue.prev = &timer_queue;
+
+ need_to_exit = 0;
+
+ LCL_AddParameterChangeHandler(handle_slew, NULL);
+
+ 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;
+}
+
+
+/* ================================================== */
+
+void
+SCH_Finalise(void) {
+ unsigned int i;
+
+ ARR_DestroyInstance(file_handlers);
+
+ timer_queue.next = &timer_queue;
+ timer_queue.prev = &timer_queue;
+ for (i = 0; i < ARR_GetSize(tqe_blocks); i++)
+ Free(*(TimerQueueEntry **)ARR_GetElement(tqe_blocks, i));
+ ARR_DestroyInstance(tqe_blocks);
+
+ LCL_RemoveParameterChangeHandler(handle_slew, NULL);
+
+ initialised = 0;
+}
+
+/* ================================================== */
+
+void
+SCH_AddFileHandler
+(int fd, int events, SCH_FileHandler handler, SCH_ArbitraryArgument arg)
+{
+ FileHandlerEntry *ptr;
+
+ assert(initialised);
+ assert(events);
+ assert(fd >= 0);
+
+ if (fd >= FD_SETSIZE)
+ LOG_FATAL("Too many file descriptors");
+
+ /* Resize the array if the descriptor is highest so far */
+ while (ARR_GetSize(file_handlers) <= fd) {
+ ptr = ARR_GetNewElement(file_handlers);
+ ptr->handler = NULL;
+ ptr->arg = NULL;
+ ptr->events = 0;
+ }
+
+ ptr = ARR_GetElement(file_handlers, fd);
+
+ /* Don't want to allow the same fd to register a handler more than
+ once without deleting a previous association - this suggests
+ a bug somewhere else in the program. */
+ assert(!ptr->handler);
+
+ ptr->handler = handler;
+ ptr->arg = arg;
+ ptr->events = events;
+
+ if (one_highest_fd < fd + 1)
+ one_highest_fd = fd + 1;
+}
+
+
+/* ================================================== */
+
+void
+SCH_RemoveFileHandler(int fd)
+{
+ FileHandlerEntry *ptr;
+
+ assert(initialised);
+
+ ptr = ARR_GetElement(file_handlers, fd);
+
+ /* Check that a handler was registered for the fd in question */
+ assert(ptr->handler);
+
+ ptr->handler = NULL;
+ ptr->arg = NULL;
+ ptr->events = 0;
+
+ /* Find new highest file descriptor */
+ while (one_highest_fd > 0) {
+ ptr = ARR_GetElement(file_handlers, one_highest_fd - 1);
+ if (ptr->handler)
+ break;
+ one_highest_fd--;
+ }
+}
+
+/* ================================================== */
+
+void
+SCH_SetFileHandlerEvent(int fd, int event, int enable)
+{
+ FileHandlerEntry *ptr;
+
+ ptr = ARR_GetElement(file_handlers, fd);
+
+ if (enable)
+ ptr->events |= event;
+ else
+ ptr->events &= ~event;
+}
+
+/* ================================================== */
+
+void
+SCH_GetLastEventTime(struct timespec *cooked, double *err, struct timespec *raw)
+{
+ if (cooked) {
+ *cooked = last_select_ts;
+ if (err)
+ *err = last_select_ts_err;
+ }
+ if (raw)
+ *raw = last_select_ts_raw;
+}
+
+/* ================================================== */
+
+double
+SCH_GetLastEventMonoTime(void)
+{
+ return last_select_ts_mono;
+}
+
+/* ================================================== */
+
+#define TQE_ALLOC_QUANTUM 32
+
+static TimerQueueEntry *
+allocate_tqe(void)
+{
+ TimerQueueEntry *new_block;
+ TimerQueueEntry *result;
+ int i;
+ if (tqe_free_list == NULL) {
+ new_block = MallocArray(TimerQueueEntry, TQE_ALLOC_QUANTUM);
+ for (i=1; i<TQE_ALLOC_QUANTUM; i++) {
+ new_block[i].next = &(new_block[i-1]);
+ }
+ new_block[0].next = NULL;
+ tqe_free_list = &(new_block[TQE_ALLOC_QUANTUM - 1]);
+ ARR_AppendElement(tqe_blocks, &new_block);
+ }
+
+ result = tqe_free_list;
+ tqe_free_list = tqe_free_list->next;
+ return result;
+}
+
+/* ================================================== */
+
+static void
+release_tqe(TimerQueueEntry *node)
+{
+ node->next = tqe_free_list;
+ tqe_free_list = node;
+}
+
+/* ================================================== */
+
+static SCH_TimeoutID
+get_new_tqe_id(void)
+{
+ TimerQueueEntry *ptr;
+
+try_again:
+ next_tqe_id++;
+ if (!next_tqe_id)
+ goto try_again;
+
+ /* Make sure the ID isn't already used */
+ for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next)
+ if (ptr->id == next_tqe_id)
+ goto try_again;
+
+ return next_tqe_id;
+}
+
+/* ================================================== */
+
+SCH_TimeoutID
+SCH_AddTimeout(struct timespec *ts, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg)
+{
+ TimerQueueEntry *new_tqe;
+ TimerQueueEntry *ptr;
+
+ assert(initialised);
+
+ new_tqe = allocate_tqe();
+
+ new_tqe->id = get_new_tqe_id();
+ new_tqe->handler = handler;
+ new_tqe->arg = arg;
+ new_tqe->ts = *ts;
+ new_tqe->class = SCH_ReservedTimeoutValue;
+
+ /* Now work out where to insert the new entry in the list */
+ for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+ if (UTI_CompareTimespecs(&new_tqe->ts, &ptr->ts) == -1) {
+ /* If the new entry comes before the current pointer location in
+ the list, we want to insert the new entry just before ptr. */
+ break;
+ }
+ }
+
+ /* At this stage, we want to insert the new entry immediately before
+ the entry identified by 'ptr' */
+
+ new_tqe->next = ptr;
+ new_tqe->prev = ptr->prev;
+ ptr->prev->next = new_tqe;
+ ptr->prev = new_tqe;
+
+ n_timer_queue_entries++;
+
+ return new_tqe->id;
+}
+
+/* ================================================== */
+/* This queues a timeout to elapse at a given delta time relative to
+ the current (raw) time */
+
+SCH_TimeoutID
+SCH_AddTimeoutByDelay(double delay, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg)
+{
+ struct timespec now, then;
+
+ assert(initialised);
+ assert(delay >= 0.0);
+
+ LCL_ReadRawTime(&now);
+ UTI_AddDoubleToTimespec(&now, delay, &then);
+ if (UTI_CompareTimespecs(&now, &then) > 0) {
+ LOG_FATAL("Timeout overflow");
+ }
+
+ return SCH_AddTimeout(&then, handler, arg);
+
+}
+
+/* ================================================== */
+
+SCH_TimeoutID
+SCH_AddTimeoutInClass(double min_delay, double separation, double randomness,
+ SCH_TimeoutClass class,
+ SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg)
+{
+ TimerQueueEntry *new_tqe;
+ TimerQueueEntry *ptr;
+ struct timespec now;
+ double diff, r;
+ double new_min_delay;
+
+ assert(initialised);
+ assert(min_delay >= 0.0);
+ assert(class < SCH_NumberOfClasses);
+
+ if (randomness > 0.0) {
+ uint32_t rnd;
+
+ UTI_GetRandomBytes(&rnd, sizeof (rnd));
+ r = rnd * (randomness / (uint32_t)-1) + 1.0;
+ min_delay *= r;
+ separation *= r;
+ }
+
+ LCL_ReadRawTime(&now);
+ new_min_delay = min_delay;
+
+ /* Check the separation from the last dispatched timeout */
+ diff = UTI_DiffTimespecsToDouble(&now, &last_class_dispatch[class]);
+ if (diff < separation && diff >= 0.0 && diff + new_min_delay < separation) {
+ new_min_delay = separation - diff;
+ }
+
+ /* Scan through list for entries in the same class and increase min_delay
+ if necessary to keep at least the separation away */
+ for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+ if (ptr->class == class) {
+ diff = UTI_DiffTimespecsToDouble(&ptr->ts, &now);
+ if (new_min_delay > diff) {
+ if (new_min_delay - diff < separation) {
+ new_min_delay = diff + separation;
+ }
+ } else {
+ if (diff - new_min_delay < separation) {
+ new_min_delay = diff + separation;
+ }
+ }
+ }
+ }
+
+ for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+ diff = UTI_DiffTimespecsToDouble(&ptr->ts, &now);
+ if (diff > new_min_delay) {
+ break;
+ }
+ }
+
+ /* We have located the insertion point */
+ new_tqe = allocate_tqe();
+
+ new_tqe->id = get_new_tqe_id();
+ new_tqe->handler = handler;
+ new_tqe->arg = arg;
+ UTI_AddDoubleToTimespec(&now, new_min_delay, &new_tqe->ts);
+ new_tqe->class = class;
+
+ new_tqe->next = ptr;
+ new_tqe->prev = ptr->prev;
+ ptr->prev->next = new_tqe;
+ ptr->prev = new_tqe;
+ n_timer_queue_entries++;
+
+ return new_tqe->id;
+}
+
+/* ================================================== */
+
+void
+SCH_RemoveTimeout(SCH_TimeoutID id)
+{
+ TimerQueueEntry *ptr;
+
+ assert(initialised);
+
+ if (!id)
+ return;
+
+ for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+
+ if (ptr->id == id) {
+ /* Found the required entry */
+
+ /* Unlink from the queue */
+ ptr->next->prev = ptr->prev;
+ ptr->prev->next = ptr->next;
+
+ /* Decrement entry count */
+ --n_timer_queue_entries;
+
+ /* Release memory back to the operating system */
+ release_tqe(ptr);
+
+ return;
+ }
+ }
+
+ /* Catch calls with invalid non-zero ID */
+ assert(0);
+}
+
+/* ================================================== */
+/* Try to dispatch any timeouts that have already gone by, and
+ keep going until all are done. (The earlier ones may take so
+ long to do that the later ones come around by the time they are
+ completed). */
+
+static void
+dispatch_timeouts(struct timespec *now) {
+ unsigned long n_done, n_entries_on_start;
+ TimerQueueEntry *ptr;
+ SCH_TimeoutHandler handler;
+ SCH_ArbitraryArgument arg;
+
+ n_entries_on_start = n_timer_queue_entries;
+ n_done = 0;
+
+ do {
+ LCL_ReadRawTime(now);
+
+ if (!(n_timer_queue_entries > 0 &&
+ UTI_CompareTimespecs(now, &timer_queue.next->ts) >= 0)) {
+ break;
+ }
+
+ ptr = timer_queue.next;
+
+ last_class_dispatch[ptr->class] = *now;
+
+ handler = ptr->handler;
+ arg = ptr->arg;
+
+ SCH_RemoveTimeout(ptr->id);
+
+ /* Dispatch the handler */
+ (handler)(arg);
+
+ /* Increment count of timeouts handled */
+ ++n_done;
+
+ /* If the number of dispatched timeouts is significantly larger than the
+ length of the queue on start and now, assume there is a bug causing
+ an infinite loop by constantly adding a timeout with a zero or negative
+ delay. Check the actual rate of timeouts to avoid false positives in
+ case the execution slowed down so much (e.g. due to memory thrashing)
+ that it repeatedly takes more time to handle the timeout than is its
+ delay. This is a safety mechanism intended to stop a full-speed flood
+ of NTP requests due to a bug in the NTP polling. */
+
+ if (n_done > 20 &&
+ n_done > 4 * MAX(n_timer_queue_entries, n_entries_on_start) &&
+ fabs(UTI_DiffTimespecsToDouble(now, &last_select_ts_raw)) / n_done < 0.01)
+ LOG_FATAL("Possible infinite loop in scheduling");
+
+ } while (!need_to_exit);
+}
+
+/* ================================================== */
+
+/* nfd is the number of bits set in all fd_sets */
+
+static void
+dispatch_filehandlers(int nfd, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds)
+{
+ FileHandlerEntry *ptr;
+ int fd;
+
+ for (fd = 0; nfd && fd < one_highest_fd; fd++) {
+ if (except_fds && FD_ISSET(fd, except_fds)) {
+ /* This descriptor has an exception, dispatch its handler */
+ ptr = (FileHandlerEntry *)ARR_GetElement(file_handlers, fd);
+ if (ptr->handler)
+ (ptr->handler)(fd, SCH_FILE_EXCEPTION, ptr->arg);
+ nfd--;
+
+ /* Don't try to read from it now */
+ if (read_fds && FD_ISSET(fd, read_fds)) {
+ FD_CLR(fd, read_fds);
+ nfd--;
+ }
+ }
+
+ if (read_fds && FD_ISSET(fd, read_fds)) {
+ /* This descriptor can be read from, dispatch its handler */
+ ptr = (FileHandlerEntry *)ARR_GetElement(file_handlers, fd);
+ if (ptr->handler)
+ (ptr->handler)(fd, SCH_FILE_INPUT, ptr->arg);
+ nfd--;
+ }
+
+ if (write_fds && FD_ISSET(fd, write_fds)) {
+ /* This descriptor can be written to, dispatch its handler */
+ ptr = (FileHandlerEntry *)ARR_GetElement(file_handlers, fd);
+ if (ptr->handler)
+ (ptr->handler)(fd, SCH_FILE_OUTPUT, ptr->arg);
+ nfd--;
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+handle_slew(struct timespec *raw,
+ struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything)
+{
+ TimerQueueEntry *ptr;
+ double delta;
+ int i;
+
+ if (change_type != LCL_ChangeAdjust) {
+ /* Make sure this handler is invoked first in order to not shift new timers
+ added from other handlers */
+ assert(LCL_IsFirstParameterChangeHandler(handle_slew));
+
+ /* If a step change occurs, just shift all raw time stamps by the offset */
+
+ for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) {
+ UTI_AddDoubleToTimespec(&ptr->ts, -doffset, &ptr->ts);
+ }
+
+ for (i = 0; i < SCH_NumberOfClasses; i++) {
+ UTI_AddDoubleToTimespec(&last_class_dispatch[i], -doffset, &last_class_dispatch[i]);
+ }
+
+ UTI_AddDoubleToTimespec(&last_select_ts_raw, -doffset, &last_select_ts_raw);
+ }
+
+ UTI_AdjustTimespec(&last_select_ts, cooked, &last_select_ts, &delta, dfreq, doffset);
+}
+
+/* ================================================== */
+
+static void
+fill_fd_sets(fd_set **read_fds, fd_set **write_fds, fd_set **except_fds)
+{
+ FileHandlerEntry *handlers;
+ fd_set *rd, *wr, *ex;
+ int i, n, events;
+
+ n = ARR_GetSize(file_handlers);
+ handlers = ARR_GetElements(file_handlers);
+ rd = wr = ex = NULL;
+
+ for (i = 0; i < n; i++) {
+ events = handlers[i].events;
+
+ if (!events)
+ continue;
+
+ if (events & SCH_FILE_INPUT) {
+ if (!rd) {
+ rd = *read_fds;
+ FD_ZERO(rd);
+ }
+ FD_SET(i, rd);
+ }
+
+ if (events & SCH_FILE_OUTPUT) {
+ if (!wr) {
+ wr = *write_fds;
+ FD_ZERO(wr);
+ }
+ FD_SET(i, wr);
+ }
+
+ if (events & SCH_FILE_EXCEPTION) {
+ if (!ex) {
+ ex = *except_fds;
+ FD_ZERO(ex);
+ }
+ FD_SET(i, ex);
+ }
+ }
+
+ if (!rd)
+ *read_fds = NULL;
+ if (!wr)
+ *write_fds = NULL;
+ if (!ex)
+ *except_fds = NULL;
+}
+
+/* ================================================== */
+
+#define JUMP_DETECT_THRESHOLD 10
+
+static int
+check_current_time(struct timespec *prev_raw, struct timespec *raw, int timeout,
+ struct timeval *orig_select_tv,
+ struct timeval *rem_select_tv)
+{
+ struct timespec elapsed_min, elapsed_max, orig_select_ts, rem_select_ts;
+ double step, elapsed;
+
+ UTI_TimevalToTimespec(orig_select_tv, &orig_select_ts);
+
+ /* Get an estimate of the time spent waiting in the select() call. On some
+ systems (e.g. Linux) the timeout timeval is modified to return the
+ remaining time, use that information. */
+ if (timeout) {
+ elapsed_max = elapsed_min = orig_select_ts;
+ } else if (rem_select_tv && rem_select_tv->tv_sec >= 0 &&
+ rem_select_tv->tv_sec <= orig_select_tv->tv_sec &&
+ (rem_select_tv->tv_sec != orig_select_tv->tv_sec ||
+ rem_select_tv->tv_usec != orig_select_tv->tv_usec)) {
+ UTI_TimevalToTimespec(rem_select_tv, &rem_select_ts);
+ UTI_DiffTimespecs(&elapsed_min, &orig_select_ts, &rem_select_ts);
+ elapsed_max = elapsed_min;
+ } else {
+ if (rem_select_tv)
+ elapsed_max = orig_select_ts;
+ else
+ UTI_DiffTimespecs(&elapsed_max, raw, prev_raw);
+ UTI_ZeroTimespec(&elapsed_min);
+ }
+
+ if (last_select_ts_raw.tv_sec + elapsed_min.tv_sec >
+ raw->tv_sec + JUMP_DETECT_THRESHOLD) {
+ LOG(LOGS_WARN, "Backward time jump detected!");
+ } else if (prev_raw->tv_sec + elapsed_max.tv_sec + JUMP_DETECT_THRESHOLD <
+ raw->tv_sec) {
+ LOG(LOGS_WARN, "Forward time jump detected!");
+ } else {
+ return 1;
+ }
+
+ step = UTI_DiffTimespecsToDouble(&last_select_ts_raw, raw);
+ elapsed = UTI_TimespecToDouble(&elapsed_min);
+ step += elapsed;
+
+ /* Cooked time may no longer be valid after dispatching the handlers */
+ LCL_NotifyExternalTimeStep(raw, raw, step, fabs(step));
+
+ return 0;
+}
+
+/* ================================================== */
+
+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)
+{
+ fd_set read_fds, write_fds, except_fds;
+ fd_set *p_read_fds, *p_write_fds, *p_except_fds;
+ int status, errsv;
+ struct timeval tv, saved_tv, *ptv;
+ struct timespec ts, now, saved_now, cooked;
+ double err;
+
+ assert(initialised);
+
+ while (!need_to_exit) {
+ /* Dispatch timeouts and fill now with current raw time */
+ dispatch_timeouts(&now);
+ saved_now = now;
+
+ /* The timeout handlers may request quit */
+ if (need_to_exit)
+ break;
+
+ /* Check whether there is a timeout and set it up */
+ if (n_timer_queue_entries > 0) {
+ UTI_DiffTimespecs(&ts, &timer_queue.next->ts, &now);
+ assert(ts.tv_sec > 0 || ts.tv_nsec > 0);
+
+ UTI_TimespecToTimeval(&ts, &tv);
+ ptv = &tv;
+ saved_tv = tv;
+ } else {
+ ptv = NULL;
+ saved_tv.tv_sec = saved_tv.tv_usec = 0;
+ }
+
+ p_read_fds = &read_fds;
+ p_write_fds = &write_fds;
+ p_except_fds = &except_fds;
+ fill_fd_sets(&p_read_fds, &p_write_fds, &p_except_fds);
+
+ /* if there are no file descriptors being waited on and no
+ timeout set, this is clearly ridiculous, so stop the run */
+ if (!ptv && !p_read_fds && !p_write_fds)
+ LOG_FATAL("Nothing to do");
+
+ status = select(one_highest_fd, p_read_fds, p_write_fds, p_except_fds, ptv);
+ errsv = errno;
+
+ LCL_ReadRawTime(&now);
+ LCL_CookTime(&now, &cooked, &err);
+
+ update_monotonic_time(&now, &last_select_ts_raw);
+
+ /* Check if the time didn't jump unexpectedly */
+ if (!check_current_time(&saved_now, &now, status == 0, &saved_tv, ptv)) {
+ /* Cook the time again after handling the step */
+ LCL_CookTime(&now, &cooked, &err);
+ }
+
+ last_select_ts_raw = now;
+ last_select_ts = cooked;
+ last_select_ts_err = err;
+
+ if (status < 0) {
+ if (!need_to_exit && errsv != EINTR) {
+ LOG_FATAL("select() failed : %s", strerror(errsv));
+ }
+ } else if (status > 0) {
+ /* A file descriptor is ready for input or output */
+ dispatch_filehandlers(status, p_read_fds, p_write_fds, p_except_fds);
+ } else {
+ /* No descriptors readable, timeout must have elapsed.
+ Therefore, tv must be non-null */
+ assert(ptv);
+
+ /* There's nothing to do here, since the timeouts
+ will be dispatched at the top of the next loop
+ cycle */
+
+ }
+ }
+}
+
+/* ================================================== */
+
+void
+SCH_QuitProgram(void)
+{
+ need_to_exit = 1;
+}
+
+/* ================================================== */
+
diff --git a/sched.h b/sched.h
new file mode 100644
index 0000000..90d757f
--- /dev/null
+++ b/sched.h
@@ -0,0 +1,93 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Exported header file for sched.c
+ */
+
+#ifndef GOT_SCHED_H
+#define GOT_SCHED_H
+
+#include "sysincl.h"
+
+/* Type for timeout IDs, valid IDs are always greater than zero */
+typedef unsigned int SCH_TimeoutID;
+
+typedef enum {
+ SCH_ReservedTimeoutValue = 0,
+ SCH_NtpClientClass,
+ SCH_NtpPeerClass,
+ SCH_NtpBroadcastClass,
+ SCH_PhcPollClass,
+ SCH_NumberOfClasses /* needs to be last */
+} SCH_TimeoutClass;
+
+typedef void* SCH_ArbitraryArgument;
+typedef void (*SCH_FileHandler)(int fd, int event, SCH_ArbitraryArgument);
+typedef void (*SCH_TimeoutHandler)(SCH_ArbitraryArgument);
+
+/* Exported functions */
+
+/* Initialisation function for the module */
+extern void SCH_Initialise(void);
+
+/* Finalisation function for the module */
+extern void SCH_Finalise(void);
+
+/* File events */
+#define SCH_FILE_INPUT 1
+#define SCH_FILE_OUTPUT 2
+#define SCH_FILE_EXCEPTION 4
+
+/* Register a handler for when select goes true on a file descriptor */
+extern void SCH_AddFileHandler(int fd, int events, SCH_FileHandler handler, SCH_ArbitraryArgument arg);
+extern void SCH_RemoveFileHandler(int fd);
+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);
+
+/* This queues a timeout to elapse at a given delta time relative to the current (raw) time */
+extern SCH_TimeoutID SCH_AddTimeoutByDelay(double delay, SCH_TimeoutHandler, SCH_ArbitraryArgument);
+
+/* This queues a timeout in a particular class, ensuring that the
+ expiry time is at least a given separation away from any other
+ timeout in the same class, given randomness is added to the delay
+ and separation */
+extern SCH_TimeoutID SCH_AddTimeoutInClass(double min_delay, double separation, double randomness,
+ SCH_TimeoutClass class,
+ SCH_TimeoutHandler handler, SCH_ArbitraryArgument);
+
+/* The next one probably ought to return a status code */
+extern void SCH_RemoveTimeout(SCH_TimeoutID);
+
+extern void SCH_MainLoop(void);
+
+extern void SCH_QuitProgram(void);
+
+#endif /* GOT_SCHED_H */
diff --git a/siv.h b/siv.h
new file mode 100644
index 0000000..868edbd
--- /dev/null
+++ b/siv.h
@@ -0,0 +1,74 @@
+/*
+ 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_GetMinNonceLength(SIV_Instance instance);
+
+extern int SIV_GetMaxNonceLength(SIV_Instance instance);
+
+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
diff --git a/siv_gnutls.c b/siv_gnutls.c
new file mode 100644
index 0000000..a0a712c
--- /dev/null
+++ b/siv_gnutls.c
@@ -0,0 +1,310 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020, 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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 GnuTLS library
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <gnutls/crypto.h>
+
+#include "logging.h"
+#include "memory.h"
+#include "siv.h"
+
+struct SIV_Instance_Record {
+ gnutls_cipher_algorithm_t algorithm;
+ gnutls_aead_cipher_hd_t cipher;
+ int min_nonce_length;
+ int max_nonce_length;
+};
+
+/* ================================================== */
+
+static int instance_counter = 0;
+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));
+
+ DEBUG_LOG("Initialised");
+ gnutls_initialised = 1;
+}
+
+/* ================================================== */
+
+static void
+deinit_gnutls(void)
+{
+ assert(gnutls_initialised);
+ gnutls_global_deinit();
+ gnutls_initialised = 0;
+ DEBUG_LOG("Deinitialised");
+}
+
+/* ================================================== */
+
+static gnutls_cipher_algorithm_t
+get_cipher_algorithm(SIV_Algorithm algorithm)
+{
+ switch (algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ return GNUTLS_CIPHER_AES_128_SIV;
+#if HAVE_GNUTLS_SIV_GCM
+ case AEAD_AES_128_GCM_SIV:
+ return GNUTLS_CIPHER_AES_128_SIV_GCM;
+#endif
+ default:
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+SIV_Instance
+SIV_CreateInstance(SIV_Algorithm algorithm)
+{
+ gnutls_cipher_algorithm_t calgo;
+ SIV_Instance instance;
+
+ calgo = get_cipher_algorithm(algorithm);
+ if (calgo == 0)
+ return NULL;
+
+ if (instance_counter == 0)
+ init_gnutls();
+
+ /* Check if the cipher is actually supported */
+ if (gnutls_cipher_get_tag_size(calgo) == 0) {
+ if (instance_counter == 0)
+ deinit_gnutls();
+ return NULL;
+ }
+
+ instance = MallocNew(struct SIV_Instance_Record);
+ instance->algorithm = calgo;
+ instance->cipher = NULL;
+
+ switch (algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ instance->min_nonce_length = 1;
+ instance->max_nonce_length = INT_MAX;
+ break;
+ case AEAD_AES_128_GCM_SIV:
+ instance->min_nonce_length = 12;
+ instance->max_nonce_length = 12;
+ break;
+ default:
+ assert(0);
+ }
+
+ instance_counter++;
+
+ return instance;
+}
+
+/* ================================================== */
+
+void
+SIV_DestroyInstance(SIV_Instance instance)
+{
+ if (instance->cipher)
+ gnutls_aead_cipher_deinit(instance->cipher);
+ Free(instance);
+
+ instance_counter--;
+ if (instance_counter == 0)
+ deinit_gnutls();
+}
+
+/* ================================================== */
+
+int
+SIV_GetKeyLength(SIV_Algorithm algorithm)
+{
+ gnutls_cipher_algorithm_t calgo = get_cipher_algorithm(algorithm);
+ int len;
+
+ if (calgo == 0)
+ return 0;
+
+ len = gnutls_cipher_get_key_size(calgo);
+ if (len == 0)
+ return 0;
+
+ if (len < 1 || len > SIV_MAX_KEY_LENGTH)
+ LOG_FATAL("Invalid key length");
+
+ return len;
+}
+
+/* ================================================== */
+
+int
+SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length)
+{
+ gnutls_aead_cipher_hd_t cipher;
+ gnutls_datum_t datum;
+ int r;
+
+ if (length <= 0 || length != gnutls_cipher_get_key_size(instance->algorithm))
+ return 0;
+
+ datum.data = (unsigned char *)key;
+ datum.size = length;
+
+#ifdef HAVE_GNUTLS_AEAD_CIPHER_SET_KEY
+ if (instance->cipher) {
+ r = gnutls_aead_cipher_set_key(instance->cipher, &datum);
+ if (r < 0) {
+ DEBUG_LOG("Could not set cipher key : %s", gnutls_strerror(r));
+ return 0;
+ }
+
+ return 1;
+ }
+#endif
+
+ /* Initialise a new cipher with the provided key */
+ r = gnutls_aead_cipher_init(&cipher, instance->algorithm, &datum);
+ if (r < 0) {
+ DEBUG_LOG("Could not initialise %s : %s", "cipher", gnutls_strerror(r));
+ return 0;
+ }
+
+ /* Destroy the previous cipher (if its key could not be changed directly) */
+ if (instance->cipher)
+ gnutls_aead_cipher_deinit(instance->cipher);
+
+ instance->cipher = cipher;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SIV_GetMinNonceLength(SIV_Instance instance)
+{
+ return instance->min_nonce_length;
+}
+
+/* ================================================== */
+
+int
+SIV_GetMaxNonceLength(SIV_Instance instance)
+{
+ return instance->max_nonce_length;
+}
+
+/* ================================================== */
+
+int
+SIV_GetTagLength(SIV_Instance instance)
+{
+ int len;
+
+ len = gnutls_cipher_get_tag_size(instance->algorithm);
+
+ if (len < 1 || len > SIV_MAX_TAG_LENGTH)
+ LOG_FATAL("Invalid tag length");
+
+ return len;
+}
+
+/* ================================================== */
+
+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)
+{
+ size_t clen = ciphertext_length;
+
+ if (!instance->cipher)
+ return 0;
+
+ if (nonce_length < instance->min_nonce_length ||
+ nonce_length > instance->max_nonce_length || assoc_length < 0 ||
+ plaintext_length < 0 || ciphertext_length < 0)
+ return 0;
+
+ assert(assoc && plaintext);
+
+ if (gnutls_aead_cipher_encrypt(instance->cipher,
+ nonce, nonce_length, assoc, assoc_length, 0,
+ plaintext, plaintext_length, ciphertext, &clen) < 0)
+ return 0;
+
+ if (clen != ciphertext_length)
+ return 0;
+
+ 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)
+{
+ size_t plen = plaintext_length;
+
+ if (!instance->cipher)
+ return 0;
+
+ if (nonce_length < instance->min_nonce_length ||
+ nonce_length > instance->max_nonce_length || assoc_length < 0 ||
+ plaintext_length < 0 || ciphertext_length < 0)
+ return 0;
+
+ assert(assoc && plaintext);
+
+ if (gnutls_aead_cipher_decrypt(instance->cipher,
+ nonce, nonce_length, assoc, assoc_length, 0,
+ ciphertext, ciphertext_length, plaintext, &plen) < 0)
+ return 0;
+
+ if (plen != plaintext_length)
+ return 0;
+
+ return 1;
+}
diff --git a/siv_nettle.c b/siv_nettle.c
new file mode 100644
index 0000000..a8cba49
--- /dev/null
+++ b/siv_nettle.c
@@ -0,0 +1,251 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2019, 2022
+ *
+ * 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
+
+#ifdef HAVE_NETTLE_SIV_GCM
+#include <nettle/siv-gcm.h>
+#endif
+
+#include "memory.h"
+#include "siv.h"
+
+struct SIV_Instance_Record {
+ SIV_Algorithm algorithm;
+ int key_set;
+ int min_nonce_length;
+ int max_nonce_length;
+ int tag_length;
+ union {
+ struct siv_cmac_aes128_ctx cmac_aes128;
+#ifdef HAVE_NETTLE_SIV_GCM
+ struct aes128_ctx aes128;
+#endif
+ } ctx;
+};
+
+/* ================================================== */
+
+SIV_Instance
+SIV_CreateInstance(SIV_Algorithm algorithm)
+{
+ SIV_Instance instance;
+
+ if (SIV_GetKeyLength(algorithm) <= 0)
+ return NULL;
+
+ instance = MallocNew(struct SIV_Instance_Record);
+ instance->algorithm = algorithm;
+ instance->key_set = 0;
+
+ switch (algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ instance->min_nonce_length = SIV_MIN_NONCE_SIZE;
+ instance->max_nonce_length = INT_MAX;
+ instance->tag_length = SIV_DIGEST_SIZE;
+ break;
+#ifdef HAVE_NETTLE_SIV_GCM
+ case AEAD_AES_128_GCM_SIV:
+ instance->min_nonce_length = SIV_GCM_NONCE_SIZE;
+ instance->max_nonce_length = SIV_GCM_NONCE_SIZE;
+ instance->tag_length = SIV_GCM_DIGEST_SIZE;
+ break;
+#endif
+ default:
+ assert(0);
+ }
+
+ return instance;
+}
+
+/* ================================================== */
+
+void
+SIV_DestroyInstance(SIV_Instance instance)
+{
+ Free(instance);
+}
+
+/* ================================================== */
+
+int
+SIV_GetKeyLength(SIV_Algorithm algorithm)
+{
+ assert(2 * AES128_KEY_SIZE <= SIV_MAX_KEY_LENGTH);
+
+ switch (algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ return 2 * AES128_KEY_SIZE;
+#ifdef HAVE_NETTLE_SIV_GCM
+ case AEAD_AES_128_GCM_SIV:
+ return AES128_KEY_SIZE;
+#endif
+ default:
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+int
+SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length)
+{
+ if (length != SIV_GetKeyLength(instance->algorithm))
+ return 0;
+
+ switch (instance->algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ siv_cmac_aes128_set_key(&instance->ctx.cmac_aes128, key);
+ break;
+#ifdef HAVE_NETTLE_SIV_GCM
+ case AEAD_AES_128_GCM_SIV:
+ aes128_set_encrypt_key(&instance->ctx.aes128, key);
+ break;
+#endif
+ default:
+ assert(0);
+ }
+
+ instance->key_set = 1;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SIV_GetMinNonceLength(SIV_Instance instance)
+{
+ return instance->min_nonce_length;
+}
+
+/* ================================================== */
+
+int
+SIV_GetMaxNonceLength(SIV_Instance instance)
+{
+ return instance->max_nonce_length;
+}
+
+/* ================================================== */
+
+int
+SIV_GetTagLength(SIV_Instance instance)
+{
+ if (instance->tag_length < 1 || instance->tag_length > SIV_MAX_TAG_LENGTH)
+ assert(0);
+ return instance->tag_length;
+}
+
+/* ================================================== */
+
+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 (!instance->key_set)
+ return 0;
+
+ if (nonce_length < instance->min_nonce_length ||
+ nonce_length > instance->max_nonce_length || assoc_length < 0 ||
+ plaintext_length < 0 || plaintext_length > ciphertext_length ||
+ plaintext_length + SIV_GetTagLength(instance) != ciphertext_length)
+ return 0;
+
+ assert(assoc && plaintext);
+
+ switch (instance->algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ siv_cmac_aes128_encrypt_message(&instance->ctx.cmac_aes128,
+ nonce_length, nonce, assoc_length, assoc,
+ ciphertext_length, ciphertext, plaintext);
+ break;
+#ifdef HAVE_NETTLE_SIV_GCM
+ case AEAD_AES_128_GCM_SIV:
+ siv_gcm_aes128_encrypt_message(&instance->ctx.aes128,
+ nonce_length, nonce, assoc_length, assoc,
+ ciphertext_length, ciphertext, plaintext);
+ break;
+#endif
+ default:
+ assert(0);
+ }
+
+ 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 (!instance->key_set)
+ return 0;
+
+ if (nonce_length < instance->min_nonce_length ||
+ nonce_length > instance->max_nonce_length || assoc_length < 0 ||
+ plaintext_length < 0 || plaintext_length > ciphertext_length ||
+ plaintext_length + SIV_GetTagLength(instance) != ciphertext_length)
+ return 0;
+
+ assert(assoc && plaintext);
+
+ switch (instance->algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ if (!siv_cmac_aes128_decrypt_message(&instance->ctx.cmac_aes128,
+ nonce_length, nonce, assoc_length, assoc,
+ plaintext_length, plaintext, ciphertext))
+ return 0;
+ break;
+#ifdef HAVE_NETTLE_SIV_GCM
+ case AEAD_AES_128_GCM_SIV:
+ if (!siv_gcm_aes128_decrypt_message(&instance->ctx.aes128,
+ nonce_length, nonce, assoc_length, assoc,
+ plaintext_length, plaintext, ciphertext))
+ return 0;
+ break;
+#endif
+ default:
+ assert(0);
+ }
+
+ return 1;
+}
diff --git a/siv_nettle_int.c b/siv_nettle_int.c
new file mode 100644
index 0000000..714eff6
--- /dev/null
+++ b/siv_nettle_int.c
@@ -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);
+}
+
diff --git a/smooth.c b/smooth.c
new file mode 100644
index 0000000..ee7094e
--- /dev/null
+++ b/smooth.c
@@ -0,0 +1,370 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2015
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing time smoothing.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "conf.h"
+#include "local.h"
+#include "logging.h"
+#include "reference.h"
+#include "smooth.h"
+#include "util.h"
+
+/*
+ Time smoothing determines an offset that needs to be applied to the cooked
+ time to make it smooth for external observers. Observed offset and frequency
+ change slowly and there are no discontinuities. This can be used on an NTP
+ server to make it easier for the clients to track the time and keep their
+ clocks close together even when large offset or frequency corrections are
+ applied to the server's clock (e.g. after being offline for longer time).
+
+ Accumulated offset and frequency are smoothed out in three stages. In the
+ first stage, the frequency is changed at a constant rate (wander) up to a
+ maximum, in the second stage the frequency stays at the maximum for as long
+ as needed and in the third stage the frequency is brought back to zero.
+
+ |
+ max_freq +-------/--------\-------------
+ | /| |\
+ freq | / | | \
+ | / | | \
+ | / | | \
+ 0 +--/----+--------+----\--------
+ | / | | | time
+ |/ | | |
+
+ stage 1 2 3
+
+ Integral of this function is the smoothed out offset. It's a continuous
+ piecewise polynomial with two quadratic parts and one linear.
+*/
+
+struct stage {
+ double wander;
+ double length;
+};
+
+#define NUM_STAGES 3
+
+static struct stage stages[NUM_STAGES];
+
+/* Enabled/disabled smoothing */
+static int enabled;
+
+/* Enabled/disabled mode where only leap seconds are smoothed out and normal
+ offset/frequency changes are ignored */
+static int leap_only_mode;
+
+/* Maximum skew/max_wander ratio to start updating offset and frequency */
+#define UNLOCK_SKEW_WANDER_RATIO 10000
+
+static int locked;
+
+/* Maximum wander and frequency offset */
+static double max_wander;
+static double max_freq;
+
+/* Frequency offset, time offset and the time of the last smoothing update */
+static double smooth_freq;
+static double smooth_offset;
+static struct timespec last_update;
+
+
+static void
+get_smoothing(struct timespec *now, double *poffset, double *pfreq,
+ double *pwander)
+{
+ double elapsed, length, offset, freq, wander;
+ int i;
+
+ elapsed = UTI_DiffTimespecsToDouble(now, &last_update);
+
+ offset = smooth_offset;
+ freq = smooth_freq;
+ wander = 0.0;
+
+ for (i = 0; i < NUM_STAGES; i++) {
+ if (elapsed <= 0.0)
+ break;
+
+ length = stages[i].length;
+ if (length >= elapsed)
+ length = elapsed;
+
+ wander = stages[i].wander;
+ offset -= length * (2.0 * freq + wander * length) / 2.0;
+ freq += wander * length;
+ elapsed -= length;
+ }
+
+ if (elapsed > 0.0) {
+ wander = 0.0;
+ offset -= elapsed * freq;
+ }
+
+ *poffset = offset;
+ *pfreq = freq;
+ if (pwander)
+ *pwander = wander;
+}
+
+static void
+update_stages(void)
+{
+ double s1, s2, s, l1, l2, l3, lc, f, f2, l1t[2], l3t[2], err[2];
+ int i, dir;
+
+ /* Prepare the three stages so that the integral of the frequency offset
+ is equal to the offset that should be smoothed out */
+
+ s1 = smooth_offset / max_wander;
+ s2 = SQUARE(smooth_freq) / (2.0 * SQUARE(max_wander));
+
+ /* Calculate the lengths of the 1st and 3rd stage assuming there is no
+ frequency limit. The direction of the 1st stage is selected so that
+ the lengths will not be negative. With extremely small offsets both
+ directions may give a negative length due to numerical errors, so select
+ the one which gives a smaller error. */
+
+ for (i = 0, dir = -1; i <= 1; i++, dir += 2) {
+ err[i] = 0.0;
+ s = dir * s1 + s2;
+
+ if (s < 0.0) {
+ err[i] += -s;
+ s = 0.0;
+ }
+
+ l3t[i] = sqrt(s);
+ l1t[i] = l3t[i] - dir * smooth_freq / max_wander;
+
+ if (l1t[i] < 0.0) {
+ err[i] += l1t[i] * l1t[i];
+ l1t[i] = 0.0;
+ }
+ }
+
+ if (err[0] < err[1]) {
+ l1 = l1t[0];
+ l3 = l3t[0];
+ dir = -1;
+ } else {
+ l1 = l1t[1];
+ l3 = l3t[1];
+ dir = 1;
+ }
+
+ l2 = 0.0;
+
+ /* If the limit was reached, shorten 1st+3rd stages and set a 2nd stage */
+ f = dir * smooth_freq + l1 * max_wander - max_freq;
+ if (f > 0.0) {
+ lc = f / max_wander;
+
+ /* No 1st stage if the frequency is already above the maximum */
+ if (lc > l1) {
+ lc = l1;
+ f2 = dir * smooth_freq;
+ } else {
+ f2 = max_freq;
+ }
+
+ l2 = lc * (2.0 + f / f2);
+ l1 -= lc;
+ l3 -= lc;
+ }
+
+ stages[0].wander = dir * max_wander;
+ stages[0].length = l1;
+ stages[1].wander = 0.0;
+ stages[1].length = l2;
+ stages[2].wander = -dir * max_wander;
+ stages[2].length = l3;
+
+ for (i = 0; i < NUM_STAGES; i++) {
+ DEBUG_LOG("Smooth stage %d wander %e length %f",
+ i + 1, stages[i].wander, stages[i].length);
+ }
+}
+
+static void
+update_smoothing(struct timespec *now, double offset, double freq)
+{
+ /* Don't accept offset/frequency until the clock has stabilized */
+ if (locked) {
+ if (REF_GetSkew() / max_wander < UNLOCK_SKEW_WANDER_RATIO || leap_only_mode)
+ SMT_Activate(now);
+ return;
+ }
+
+ get_smoothing(now, &smooth_offset, &smooth_freq, NULL);
+ smooth_offset += offset;
+ smooth_freq = (smooth_freq - freq) / (1.0 - freq);
+ last_update = *now;
+
+ update_stages();
+
+ DEBUG_LOG("Smooth offset %e freq %e", smooth_offset, smooth_freq);
+}
+
+static void
+handle_slew(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything)
+{
+ double delta;
+
+ if (change_type == LCL_ChangeAdjust) {
+ if (leap_only_mode)
+ update_smoothing(cooked, 0.0, 0.0);
+ else
+ update_smoothing(cooked, doffset, dfreq);
+ }
+
+ if (!UTI_IsZeroTimespec(&last_update))
+ UTI_AdjustTimespec(&last_update, cooked, &last_update, &delta, dfreq, doffset);
+}
+
+void SMT_Initialise(void)
+{
+ CNF_GetSmooth(&max_freq, &max_wander, &leap_only_mode);
+ if (max_freq <= 0.0 || max_wander <= 0.0) {
+ enabled = 0;
+ return;
+ }
+
+ enabled = 1;
+ locked = 1;
+
+ /* Convert from ppm */
+ max_freq *= 1e-6;
+ max_wander *= 1e-6;
+
+ UTI_ZeroTimespec(&last_update);
+
+ LCL_AddParameterChangeHandler(handle_slew, NULL);
+}
+
+void SMT_Finalise(void)
+{
+ if (!enabled)
+ return;
+
+ LCL_RemoveParameterChangeHandler(handle_slew, NULL);
+}
+
+int SMT_IsEnabled(void)
+{
+ return enabled;
+}
+
+double
+SMT_GetOffset(struct timespec *now)
+{
+ double offset, freq;
+
+ if (!enabled)
+ return 0.0;
+
+ get_smoothing(now, &offset, &freq, NULL);
+
+ return offset;
+}
+
+void
+SMT_Activate(struct timespec *now)
+{
+ if (!enabled || !locked)
+ return;
+
+ LOG(LOGS_INFO, "Activated %s%s", "time smoothing", leap_only_mode ?
+ " (leap seconds only)" : "");
+ locked = 0;
+ last_update = *now;
+}
+
+void
+SMT_Reset(struct timespec *now)
+{
+ int i;
+
+ if (!enabled)
+ return;
+
+ smooth_offset = 0.0;
+ smooth_freq = 0.0;
+ last_update = *now;
+
+ for (i = 0; i < NUM_STAGES; i++)
+ stages[i].wander = stages[i].length = 0.0;
+
+ LOG(LOGS_INFO, "Reset %s", "time smoothing");
+}
+
+void
+SMT_Leap(struct timespec *now, int leap)
+{
+ /* When the leap-only mode is disabled, the leap second will be accumulated
+ in handle_slew() as a normal offset */
+ if (!enabled || !leap_only_mode)
+ return;
+
+ update_smoothing(now, leap, 0.0);
+}
+
+int
+SMT_GetSmoothingReport(RPT_SmoothingReport *report, struct timespec *now)
+{
+ double length, elapsed;
+ int i;
+
+ if (!enabled)
+ return 0;
+
+ report->active = !locked;
+ report->leap_only = leap_only_mode;
+
+ get_smoothing(now, &report->offset, &report->freq_ppm, &report->wander_ppm);
+
+ /* Convert to ppm and negate (positive values mean faster/speeding up) */
+ report->freq_ppm *= -1.0e6;
+ report->wander_ppm *= -1.0e6;
+
+ elapsed = UTI_DiffTimespecsToDouble(now, &last_update);
+ if (!locked && elapsed >= 0.0) {
+ for (i = 0, length = 0.0; i < NUM_STAGES; i++)
+ length += stages[i].length;
+ report->last_update_ago = elapsed;
+ report->remaining_time = elapsed < length ? length - elapsed : 0.0;
+ } else {
+ report->last_update_ago = 0.0;
+ report->remaining_time = 0.0;
+ }
+
+ return 1;
+}
diff --git a/smooth.h b/smooth.h
new file mode 100644
index 0000000..4e84504
--- /dev/null
+++ b/smooth.h
@@ -0,0 +1,48 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ This module implements time smoothing.
+ */
+
+#ifndef GOT_SMOOTH_H
+#define GOT_SMOOTH_H
+
+#include "reports.h"
+
+extern void SMT_Initialise(void);
+
+extern void SMT_Finalise(void);
+
+extern int SMT_IsEnabled(void);
+
+extern double SMT_GetOffset(struct timespec *now);
+
+extern void SMT_Activate(struct timespec *now);
+
+extern void SMT_Reset(struct timespec *now);
+
+extern void SMT_Leap(struct timespec *now, int leap);
+
+extern int SMT_GetSmoothingReport(RPT_SmoothingReport *report, struct timespec *now);
+
+#endif
diff --git a/socket.c b/socket.c
new file mode 100644
index 0000000..5b22db5
--- /dev/null
+++ b/socket.c
@@ -0,0 +1,1803 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Timo Teras 2009
+ * Copyright (C) Miroslav Lichvar 2009, 2013-2020
+ * Copyright (C) Luke Valenta 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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 file implements socket operations.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#ifdef HAVE_LINUX_TIMESTAMPING
+#include <linux/errqueue.h>
+#include <linux/net_tstamp.h>
+#endif
+
+#include "socket.h"
+#include "array.h"
+#include "logging.h"
+#include "privops.h"
+#include "ptp.h"
+#include "util.h"
+
+#define INVALID_SOCK_FD (-4)
+#define CMSG_BUF_SIZE 256
+
+union sockaddr_all {
+ struct sockaddr_in in4;
+#ifdef FEAT_IPV6
+ struct sockaddr_in6 in6;
+#endif
+ struct sockaddr_un un;
+ struct sockaddr sa;
+};
+
+struct Message {
+ union sockaddr_all name;
+ struct iovec iov;
+ /* Buffer of sufficient length for all expected messages */
+ struct {
+ /* Extra space for Ethernet, IPv4/IPv6, and UDP headers in
+ timestamped messages received from the Linux error queue */
+ uint8_t l234_headers[64];
+ union {
+ NTP_Packet ntp_msg;
+ PTP_NtpMessage ptp_msg;
+ CMD_Request cmd_request;
+ CMD_Reply cmd_reply;
+ } msg;
+ } msg_buf;
+ /* Aligned buffer for control messages */
+ struct cmsghdr cmsg_buf[CMSG_BUF_SIZE / sizeof (struct cmsghdr)];
+};
+
+#ifdef HAVE_RECVMMSG
+#define MAX_RECV_MESSAGES 16
+#define MessageHeader mmsghdr
+#else
+/* Compatible with mmsghdr */
+struct MessageHeader {
+ struct msghdr msg_hdr;
+ unsigned int msg_len;
+};
+
+#define MAX_RECV_MESSAGES 1
+#endif
+
+static int initialised;
+
+static int first_reusable_fd;
+static int reusable_fds;
+
+/* Flags indicating in which IP families sockets can be requested */
+static int ip4_enabled;
+static int ip6_enabled;
+
+/* Flags supported by socket() */
+static int supported_socket_flags;
+
+/* Arrays of Message, MessageHeader, and SCK_Message */
+static ARR_Instance recv_messages;
+static ARR_Instance recv_headers;
+static ARR_Instance recv_sck_messages;
+
+static unsigned int received_messages;
+
+static int (*priv_bind_function)(int sock_fd, struct sockaddr *address,
+ socklen_t address_len);
+
+/* ================================================== */
+
+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->msg_buf;
+ msg->iov.iov_len = sizeof (msg->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->cmsg_buf;
+ hdr->msg_hdr.msg_controllen = sizeof (msg->cmsg_buf);
+ hdr->msg_hdr.msg_flags = 0;
+ hdr->msg_len = 0;
+ }
+}
+
+/* ================================================== */
+
+static const char *
+domain_to_string(int domain)
+{
+ switch (domain) {
+ case AF_INET:
+ return "IPv4";
+#ifdef AF_INET6
+ case AF_INET6:
+ return "IPv6";
+#endif
+ case AF_UNIX:
+ return "Unix";
+ case AF_UNSPEC:
+ return "UNSPEC";
+ default:
+ return "?";
+ }
+}
+
+/* ================================================== */
+
+static int
+get_reusable_socket(int type, IPSockAddr *spec)
+{
+#ifdef LINUX
+ union sockaddr_all sa;
+ IPSockAddr ip_sa;
+ int sock_fd, opt;
+ socklen_t l;
+
+ /* Abort early if not an IPv4/IPv6 server socket */
+ if (!spec || spec->ip_addr.family == IPADDR_UNSPEC || spec->port == 0)
+ return INVALID_SOCK_FD;
+
+ /* Loop over available reusable sockets */
+ for (sock_fd = first_reusable_fd; sock_fd < first_reusable_fd + reusable_fds; sock_fd++) {
+
+ /* Check that types match */
+ l = sizeof (opt);
+ if (getsockopt(sock_fd, SOL_SOCKET, SO_TYPE, &opt, &l) < 0 ||
+ l != sizeof (opt) || opt != type)
+ continue;
+
+ /* Get sockaddr for reusable socket */
+ l = sizeof (sa);
+ if (getsockname(sock_fd, &sa.sa, &l) < 0 || l < sizeof (sa_family_t))
+ continue;
+ SCK_SockaddrToIPSockAddr(&sa.sa, l, &ip_sa);
+
+ /* Check that reusable socket matches specification */
+ if (ip_sa.port != spec->port || UTI_CompareIPs(&ip_sa.ip_addr, &spec->ip_addr, NULL) != 0)
+ continue;
+
+ /* Check that STREAM socket is listening */
+ l = sizeof (opt);
+ if (type == SOCK_STREAM && (getsockopt(sock_fd, SOL_SOCKET, SO_ACCEPTCONN, &opt, &l) < 0 ||
+ l != sizeof (opt) || opt == 0))
+ continue;
+
+#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
+ if (spec->ip_addr.family == IPADDR_INET6 &&
+ (!SCK_GetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt) || opt != 1))
+ LOG(LOGS_WARN, "Reusable IPv6 socket missing IPV6_V6ONLY option");
+#endif
+
+ return sock_fd;
+ }
+#endif
+
+ return INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+#if defined(SOCK_CLOEXEC) || defined(SOCK_NONBLOCK)
+static int
+check_socket_flag(int sock_flag, int fd_flag, int fs_flag)
+{
+ int sock_fd, fd_flags, fs_flags;
+
+ sock_fd = socket(AF_INET, SOCK_DGRAM | sock_flag, 0);
+ if (sock_fd < 0)
+ return 0;
+
+ fd_flags = fcntl(sock_fd, F_GETFD);
+ fs_flags = fcntl(sock_fd, F_GETFL);
+
+ close(sock_fd);
+
+ if (fd_flags == -1 || (fd_flags & fd_flag) != fd_flag ||
+ fs_flags == -1 || (fs_flags & fs_flag) != fs_flag)
+ return 0;
+
+ return 1;
+}
+#endif
+
+/* ================================================== */
+
+static int
+set_socket_nonblock(int sock_fd)
+{
+ if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) < 0) {
+ DEBUG_LOG("Could not set O_NONBLOCK : %s", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+get_open_flags(int flags)
+{
+ int r = supported_socket_flags;
+
+#ifdef SOCK_NONBLOCK
+ if (flags & SCK_FLAG_BLOCK)
+ r &= ~SOCK_NONBLOCK;
+#endif
+
+ return r;
+}
+
+/* ================================================== */
+
+static int
+set_socket_flags(int sock_fd, int flags)
+{
+ /* Close the socket automatically on exec */
+ if (!SCK_IsReusable(sock_fd) &&
+#ifdef SOCK_CLOEXEC
+ (supported_socket_flags & SOCK_CLOEXEC) == 0 &&
+#endif
+ !UTI_FdSetCloexec(sock_fd))
+ return 0;
+
+ /* Enable non-blocking mode */
+ if ((flags & SCK_FLAG_BLOCK) == 0 &&
+#ifdef SOCK_NONBLOCK
+ (SCK_IsReusable(sock_fd) || (supported_socket_flags & SOCK_NONBLOCK) == 0) &&
+#endif
+ !set_socket_nonblock(sock_fd))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+open_socket(int domain, int type, int flags)
+{
+ int sock_fd;
+
+ sock_fd = socket(domain, type | get_open_flags(flags), 0);
+
+ if (sock_fd < 0) {
+ DEBUG_LOG("Could not open %s socket : %s",
+ domain_to_string(domain), strerror(errno));
+ return INVALID_SOCK_FD;
+ }
+
+ if (!set_socket_flags(sock_fd, flags)) {
+ close(sock_fd);
+ return INVALID_SOCK_FD;
+ }
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+static int
+open_socket_pair(int domain, int type, int flags, int *other_fd)
+{
+ int sock_fds[2];
+
+ if (socketpair(domain, type | get_open_flags(flags), 0, sock_fds) < 0) {
+ DEBUG_LOG("Could not open %s socket : %s",
+ domain_to_string(domain), strerror(errno));
+ return INVALID_SOCK_FD;
+ }
+
+ if (!set_socket_flags(sock_fds[0], flags) || !set_socket_flags(sock_fds[1], flags)) {
+ close(sock_fds[0]);
+ close(sock_fds[1]);
+ return INVALID_SOCK_FD;
+ }
+
+ *other_fd = sock_fds[1];
+
+ return sock_fds[0];
+}
+
+/* ================================================== */
+
+static int
+get_ip_socket(int domain, int type, int flags, IPSockAddr *ip_sa)
+{
+ int sock_fd;
+
+ /* Check if there is a matching reusable socket */
+ sock_fd = get_reusable_socket(type, ip_sa);
+
+ if (sock_fd < 0) {
+ sock_fd = open_socket(domain, type, flags);
+
+ /* Unexpected, but make sure the new socket is not in the reusable range */
+ if (SCK_IsReusable(sock_fd))
+ LOG_FATAL("Could not open %s socket : file descriptor in reusable range",
+ domain_to_string(domain));
+ } else {
+ /* Set socket flags on reusable socket */
+ if (!set_socket_flags(sock_fd, flags))
+ return INVALID_SOCK_FD;
+ }
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+static int
+set_socket_options(int sock_fd, int flags)
+{
+ /* Make the socket capable of sending broadcast packets if requested */
+ if (flags & SCK_FLAG_BROADCAST && !SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_BROADCAST, 1))
+ ;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+set_ip_options(int sock_fd, int family, int flags)
+{
+#if defined(FEAT_IPV6) && defined(IPV6_V6ONLY)
+ /* Receive only IPv6 packets on an IPv6 socket, but do not attempt
+ to set this option on pre-initialised reuseable sockets */
+ if (family == IPADDR_INET6 && !SCK_IsReusable(sock_fd) &&
+ !SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, 1))
+ return 0;
+#endif
+
+ /* Provide destination address of received packets if requested */
+ if (flags & SCK_FLAG_RX_DEST_ADDR) {
+ if (family == IPADDR_INET4) {
+#ifdef HAVE_IN_PKTINFO
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_PKTINFO, 1))
+ ;
+#elif defined(IP_RECVDSTADDR)
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_RECVDSTADDR, 1))
+ ;
+#endif
+ }
+#ifdef FEAT_IPV6
+ else if (family == IPADDR_INET6) {
+#ifdef HAVE_IN6_PKTINFO
+#ifdef IPV6_RECVPKTINFO
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1))
+ ;
+#else
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IPV6, IPV6_PKTINFO, 1))
+ ;
+#endif
+#endif
+ }
+#endif
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+is_any_address(IPAddr *addr)
+{
+ IPAddr any_addr;
+
+ SCK_GetAnyLocalIPAddress(addr->family, &any_addr);
+
+ return UTI_CompareIPs(&any_addr, addr, NULL) == 0;
+}
+
+/* ================================================== */
+
+static int
+bind_device(int sock_fd, const char *iface)
+{
+#ifdef SO_BINDTODEVICE
+ if (setsockopt(sock_fd, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) < 0) {
+ DEBUG_LOG("Could not bind socket to %s : %s", iface, strerror(errno));
+ return 0;
+ }
+ return 1;
+#else
+ DEBUG_LOG("Could not bind socket to %s : %s", iface, "Not supported");
+ return 0;
+#endif
+}
+
+/* ================================================== */
+
+static int
+bind_ip_address(int sock_fd, IPSockAddr *addr, int flags)
+{
+ union sockaddr_all saddr;
+ socklen_t saddr_len;
+ int s;
+
+ /* Make the socket capable of re-using an old address if binding to a specific port */
+ if (addr->port > 0 && !SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_REUSEADDR, 1))
+ ;
+
+#if defined(LINUX) && defined(SO_REUSEPORT)
+ /* Allow multiple instances to bind to the same port in order to enable load
+ balancing. Don't enable this option on non-Linux systems as it has
+ a slightly different meaning there (with some important implications). */
+ if (addr->port > 0 && !SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_REUSEPORT, 1))
+ ;
+#endif
+
+#ifdef IP_FREEBIND
+ /* Allow binding to an address that doesn't exist yet */
+ if (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_FREEBIND, 1))
+ ;
+#endif
+
+ /* Do not attempt to bind pre-initialised reusable socket */
+ if (SCK_IsReusable(sock_fd))
+ return 1;
+
+ saddr_len = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr, sizeof (saddr));
+ if (saddr_len == 0)
+ return 0;
+
+ if (flags & SCK_FLAG_PRIV_BIND && priv_bind_function)
+ s = priv_bind_function(sock_fd, &saddr.sa, saddr_len);
+ else
+ s = bind(sock_fd, &saddr.sa, saddr_len);
+
+ if (s < 0) {
+ DEBUG_LOG("Could not bind socket to %s : %s",
+ UTI_IPSockAddrToString(addr), strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+connect_ip_address(int sock_fd, IPSockAddr *addr)
+{
+ union sockaddr_all saddr;
+ socklen_t saddr_len;
+
+ saddr_len = SCK_IPSockAddrToSockaddr(addr, (struct sockaddr *)&saddr, sizeof (saddr));
+ if (saddr_len == 0)
+ return 0;
+
+ if (connect(sock_fd, &saddr.sa, saddr_len) < 0 && errno != EINPROGRESS) {
+ DEBUG_LOG("Could not connect socket to %s : %s",
+ UTI_IPSockAddrToString(addr), strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+open_ip_socket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *iface,
+ int type, int flags)
+{
+ int domain, family, sock_fd;
+
+ if (local_addr)
+ family = local_addr->ip_addr.family;
+ else if (remote_addr)
+ family = remote_addr->ip_addr.family;
+ else
+ family = IPADDR_INET4;
+
+ switch (family) {
+ case IPADDR_INET4:
+ if (!ip4_enabled)
+ return INVALID_SOCK_FD;
+ domain = AF_INET;
+ break;
+#ifdef FEAT_IPV6
+ case IPADDR_INET6:
+ if (!ip6_enabled)
+ return INVALID_SOCK_FD;
+ domain = AF_INET6;
+ break;
+#endif
+ default:
+ DEBUG_LOG("Unspecified family");
+ return INVALID_SOCK_FD;
+ }
+
+ sock_fd = get_ip_socket(domain, type, flags, local_addr);
+ if (sock_fd < 0)
+ return INVALID_SOCK_FD;
+
+ if (!set_socket_options(sock_fd, flags))
+ goto error;
+
+ if (!set_ip_options(sock_fd, family, flags))
+ goto error;
+
+ if (iface && !bind_device(sock_fd, iface))
+ goto error;
+
+ /* Bind the socket if a non-any local address/port was specified */
+ if (local_addr && local_addr->ip_addr.family != IPADDR_UNSPEC &&
+ (local_addr->port != 0 || !is_any_address(&local_addr->ip_addr)) &&
+ !bind_ip_address(sock_fd, local_addr, flags))
+ goto error;
+
+ /* Connect the socket if a remote address was specified */
+ if (remote_addr && remote_addr->ip_addr.family != IPADDR_UNSPEC &&
+ !connect_ip_address(sock_fd, remote_addr))
+ goto error;
+
+ if (remote_addr || local_addr)
+ DEBUG_LOG("%s %s%s socket fd=%d%s%s%s%s",
+ SCK_IsReusable(sock_fd) ? "Reusing" : "Opened",
+ type == SOCK_DGRAM ? "UDP" : type == SOCK_STREAM ? "TCP" : "?",
+ family == IPADDR_INET4 ? "v4" : "v6",
+ sock_fd,
+ remote_addr ? " remote=" : "",
+ remote_addr ? UTI_IPSockAddrToString(remote_addr) : "",
+ local_addr ? " local=" : "",
+ local_addr ? UTI_IPSockAddrToString(local_addr) : "");
+
+ return sock_fd;
+
+error:
+ SCK_CloseSocket(sock_fd);
+ return INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+static int
+bind_unix_address(int sock_fd, const char *addr, int flags)
+{
+ union sockaddr_all saddr;
+
+ memset(&saddr, 0, sizeof (saddr));
+
+ if (snprintf(saddr.un.sun_path, sizeof (saddr.un.sun_path), "%s", addr) >=
+ sizeof (saddr.un.sun_path)) {
+ DEBUG_LOG("Unix socket path %s too long", addr);
+ return 0;
+ }
+ saddr.un.sun_family = AF_UNIX;
+
+ if (unlink(addr) < 0)
+ DEBUG_LOG("Could not remove %s : %s", addr, strerror(errno));
+
+ /* PRV_BindSocket() doesn't support Unix sockets yet */
+ if (bind(sock_fd, &saddr.sa, sizeof (saddr.un)) < 0) {
+ DEBUG_LOG("Could not bind Unix socket to %s : %s", addr, strerror(errno));
+ return 0;
+ }
+
+ /* Allow access to everyone with access to the directory if requested */
+ if (flags & SCK_FLAG_ALL_PERMISSIONS && chmod(addr, 0666) < 0) {
+ DEBUG_LOG("Could not change permissions of %s : %s", addr, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+connect_unix_address(int sock_fd, const char *addr)
+{
+ union sockaddr_all saddr;
+
+ memset(&saddr, 0, sizeof (saddr));
+
+ if (snprintf(saddr.un.sun_path, sizeof (saddr.un.sun_path), "%s", addr) >=
+ sizeof (saddr.un.sun_path)) {
+ DEBUG_LOG("Unix socket path %s too long", addr);
+ return 0;
+ }
+ saddr.un.sun_family = AF_UNIX;
+
+ if (connect(sock_fd, &saddr.sa, sizeof (saddr.un)) < 0) {
+ DEBUG_LOG("Could not connect Unix socket to %s : %s", addr, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+open_unix_socket(const char *remote_addr, const char *local_addr, int type, int flags)
+{
+ int sock_fd;
+
+ sock_fd = open_socket(AF_UNIX, type, flags);
+ if (sock_fd < 0)
+ return INVALID_SOCK_FD;
+
+ if (!set_socket_options(sock_fd, flags))
+ goto error;
+
+ /* Bind the socket if a local address was specified */
+ if (local_addr && !bind_unix_address(sock_fd, local_addr, flags))
+ goto error;
+
+ /* Connect the socket if a remote address was specified */
+ if (remote_addr && !connect_unix_address(sock_fd, remote_addr))
+ goto error;
+
+ DEBUG_LOG("Opened Unix socket fd=%d%s%s%s%s",
+ sock_fd,
+ remote_addr ? " remote=" : "", remote_addr ? remote_addr : "",
+ local_addr ? " local=" : "", local_addr ? local_addr : "");
+
+ return sock_fd;
+
+error:
+ SCK_RemoveSocket(sock_fd);
+ SCK_CloseSocket(sock_fd);
+ return INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+static int
+open_unix_socket_pair(int type, int flags, int *other_fd)
+{
+ int sock_fd;
+
+ sock_fd = open_socket_pair(AF_UNIX, type, flags, other_fd);
+ if (sock_fd < 0)
+ return INVALID_SOCK_FD;
+
+ DEBUG_LOG("Opened Unix socket pair fd1=%d fd2=%d", sock_fd, *other_fd);
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+static int
+get_recv_flags(int flags)
+{
+ int recv_flags = 0;
+
+ if (flags & SCK_FLAG_MSG_ERRQUEUE) {
+#ifdef MSG_ERRQUEUE
+ recv_flags |= MSG_ERRQUEUE;
+#else
+ assert(0);
+#endif
+ }
+
+ return recv_flags;
+}
+
+/* ================================================== */
+
+static void
+handle_recv_error(int sock_fd, int flags)
+{
+#ifdef MSG_ERRQUEUE
+ /* If reading from the error queue failed, the select() exception should
+ be for a socket error. Clear the error to avoid a busy loop. */
+ if (flags & SCK_FLAG_MSG_ERRQUEUE) {
+ int error = 0;
+
+ if (SCK_GetIntOption(sock_fd, SOL_SOCKET, SO_ERROR, &error))
+ errno = error;
+ }
+#endif
+
+ DEBUG_LOG("Could not receive message fd=%d : %s", sock_fd, strerror(errno));
+}
+
+/* ================================================== */
+
+static void
+log_message(int sock_fd, int direction, SCK_Message *message, const char *prefix,
+ const char *error)
+{
+ const char *local_addr, *remote_addr;
+ char if_index[20], tss[10], tsif[20], tslen[20];
+
+ if (DEBUG <= 0 || log_min_severity > LOGS_DEBUG)
+ return;
+
+ remote_addr = NULL;
+ local_addr = NULL;
+ if_index[0] = '\0';
+ tss[0] = '\0';
+ tsif[0] = '\0';
+ tslen[0] = '\0';
+
+ switch (message->addr_type) {
+ case SCK_ADDR_IP:
+ if (message->remote_addr.ip.ip_addr.family != IPADDR_UNSPEC)
+ remote_addr = UTI_IPSockAddrToString(&message->remote_addr.ip);
+ if (message->local_addr.ip.family != IPADDR_UNSPEC)
+ local_addr = UTI_IPToString(&message->local_addr.ip);
+ break;
+ case SCK_ADDR_UNIX:
+ remote_addr = message->remote_addr.path;
+ break;
+ default:
+ break;
+ }
+
+ if (message->if_index != INVALID_IF_INDEX)
+ snprintf(if_index, sizeof (if_index), " if=%d", message->if_index);
+
+ if (direction > 0) {
+ if (!UTI_IsZeroTimespec(&message->timestamp.kernel) ||
+ !UTI_IsZeroTimespec(&message->timestamp.hw))
+ snprintf(tss, sizeof (tss), " tss=%s%s",
+ !UTI_IsZeroTimespec(&message->timestamp.kernel) ? "K" : "",
+ !UTI_IsZeroTimespec(&message->timestamp.hw) ? "H" : "");
+
+ if (message->timestamp.if_index != INVALID_IF_INDEX)
+ snprintf(tsif, sizeof (tsif), " tsif=%d", message->timestamp.if_index);
+
+ if (message->timestamp.l2_length != 0)
+ snprintf(tslen, sizeof (tslen), " tslen=%d", message->timestamp.l2_length);
+ }
+
+ DEBUG_LOG("%s message%s%s%s%s fd=%d len=%d%s%s%s%s%s%s",
+ prefix,
+ remote_addr ? (direction > 0 ? " from " : " to ") : "",
+ remote_addr ? remote_addr : "",
+ local_addr ? (direction > 0 ? " to " : " from ") : "",
+ local_addr ? local_addr : "",
+ sock_fd, message->length, if_index,
+ tss, tsif, tslen,
+ error ? " : " : "", error ? error : "");
+}
+
+/* ================================================== */
+
+static void
+init_message_addresses(SCK_Message *message, SCK_AddressType addr_type)
+{
+ message->addr_type = addr_type;
+
+ switch (addr_type) {
+ case SCK_ADDR_UNSPEC:
+ break;
+ case SCK_ADDR_IP:
+ message->remote_addr.ip.ip_addr.family = IPADDR_UNSPEC;
+ message->remote_addr.ip.port = 0;
+ message->local_addr.ip.family = IPADDR_UNSPEC;
+ break;
+ case SCK_ADDR_UNIX:
+ message->remote_addr.path = NULL;
+ break;
+ default:
+ assert(0);
+ }
+}
+
+/* ================================================== */
+
+static void
+init_message_nonaddress(SCK_Message *message)
+{
+ message->data = NULL;
+ message->length = 0;
+ message->if_index = INVALID_IF_INDEX;
+
+ UTI_ZeroTimespec(&message->timestamp.kernel);
+ UTI_ZeroTimespec(&message->timestamp.hw);
+ message->timestamp.if_index = INVALID_IF_INDEX;
+ message->timestamp.l2_length = 0;
+ message->timestamp.tx_flags = 0;
+
+ message->descriptor = INVALID_SOCK_FD;
+}
+
+/* ================================================== */
+
+static int
+match_cmsg(struct cmsghdr *cmsg, int level, int type, size_t length)
+{
+ if (cmsg->cmsg_type == type && cmsg->cmsg_level == level &&
+ (length == 0 || cmsg->cmsg_len == CMSG_LEN(length)))
+ return 1;
+ return 0;
+}
+
+/* ================================================== */
+
+static int
+process_header(struct msghdr *msg, int msg_length, int sock_fd, int flags,
+ SCK_Message *message)
+{
+ struct cmsghdr *cmsg;
+ int r = 1;
+
+ if (msg->msg_namelen <= sizeof (union sockaddr_all) &&
+ msg->msg_namelen > sizeof (((struct sockaddr *)msg->msg_name)->sa_family)) {
+ switch (((struct sockaddr *)msg->msg_name)->sa_family) {
+ case AF_INET:
+#ifdef FEAT_IPV6
+ case AF_INET6:
+#endif
+ init_message_addresses(message, SCK_ADDR_IP);
+ SCK_SockaddrToIPSockAddr(msg->msg_name, msg->msg_namelen, &message->remote_addr.ip);
+ break;
+ case AF_UNIX:
+ init_message_addresses(message, SCK_ADDR_UNIX);
+ message->remote_addr.path = ((struct sockaddr_un *)msg->msg_name)->sun_path;
+ break;
+ default:
+ init_message_addresses(message, SCK_ADDR_UNSPEC);
+ DEBUG_LOG("Unexpected address");
+ r = 0;
+ break;
+ }
+ } else {
+ init_message_addresses(message, SCK_ADDR_UNSPEC);
+
+ if (msg->msg_namelen > sizeof (union sockaddr_all)) {
+ DEBUG_LOG("Truncated source address");
+ r = 0;
+ }
+ }
+
+ init_message_nonaddress(message);
+
+ if (msg->msg_iovlen == 1) {
+ message->data = msg->msg_iov[0].iov_base;
+ message->length = msg_length;
+ } else {
+ DEBUG_LOG("Unexpected iovlen");
+ r = 0;
+ }
+
+ if (msg->msg_flags & MSG_TRUNC) {
+ log_message(sock_fd, 1, message, "Truncated", NULL);
+ r = 0;
+ }
+
+ if (msg->msg_flags & MSG_CTRUNC) {
+ log_message(sock_fd, 1, message, "Truncated cmsg in", NULL);
+ r = 0;
+ }
+
+ for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (0) {
+ }
+#ifdef HAVE_IN_PKTINFO
+ else if (match_cmsg(cmsg, IPPROTO_IP, IP_PKTINFO, sizeof (struct in_pktinfo))) {
+ struct in_pktinfo ipi;
+
+ if (message->addr_type != SCK_ADDR_IP)
+ init_message_addresses(message, SCK_ADDR_IP);
+
+ memcpy(&ipi, CMSG_DATA(cmsg), sizeof (ipi));
+ message->local_addr.ip.addr.in4 = ntohl(ipi.ipi_addr.s_addr);
+ message->local_addr.ip.family = IPADDR_INET4;
+ message->if_index = ipi.ipi_ifindex;
+ }
+#elif defined(IP_RECVDSTADDR)
+ else if (match_cmsg(cmsg, IPPROTO_IP, IP_RECVDSTADDR, sizeof (struct in_addr))) {
+ struct in_addr addr;
+
+ if (message->addr_type != SCK_ADDR_IP)
+ init_message_addresses(message, SCK_ADDR_IP);
+
+ memcpy(&addr, CMSG_DATA(cmsg), sizeof (addr));
+ message->local_addr.ip.addr.in4 = ntohl(addr.s_addr);
+ message->local_addr.ip.family = IPADDR_INET4;
+ }
+#endif
+#ifdef HAVE_IN6_PKTINFO
+ else if (match_cmsg(cmsg, IPPROTO_IPV6, IPV6_PKTINFO, sizeof (struct in6_pktinfo))) {
+ struct in6_pktinfo ipi;
+
+ if (message->addr_type != SCK_ADDR_IP)
+ init_message_addresses(message, SCK_ADDR_IP);
+
+ memcpy(&ipi, CMSG_DATA(cmsg), sizeof (ipi));
+ memcpy(&message->local_addr.ip.addr.in6, &ipi.ipi6_addr.s6_addr,
+ sizeof (message->local_addr.ip.addr.in6));
+ message->local_addr.ip.family = IPADDR_INET6;
+ message->if_index = ipi.ipi6_ifindex;
+ }
+#endif
+#ifdef SCM_TIMESTAMP
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMP, sizeof (struct timeval))) {
+ struct timeval tv;
+
+ memcpy(&tv, CMSG_DATA(cmsg), sizeof (tv));
+ UTI_TimevalToTimespec(&tv, &message->timestamp.kernel);
+ }
+#endif
+#ifdef SCM_TIMESTAMPNS
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMPNS, sizeof (message->timestamp.kernel))) {
+ memcpy(&message->timestamp.kernel, CMSG_DATA(cmsg), sizeof (message->timestamp.kernel));
+ }
+#endif
+#ifdef SCM_REALTIME
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_REALTIME, sizeof (message->timestamp.kernel))) {
+ memcpy(&message->timestamp.kernel, CMSG_DATA(cmsg), sizeof (message->timestamp.kernel));
+ }
+#endif
+#ifdef HAVE_LINUX_TIMESTAMPING
+#ifdef HAVE_LINUX_TIMESTAMPING_OPT_PKTINFO
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMPING_PKTINFO,
+ sizeof (struct scm_ts_pktinfo))) {
+ struct scm_ts_pktinfo ts_pktinfo;
+
+ memcpy(&ts_pktinfo, CMSG_DATA(cmsg), sizeof (ts_pktinfo));
+ message->timestamp.if_index = ts_pktinfo.if_index;
+ message->timestamp.l2_length = ts_pktinfo.pkt_length;
+ }
+#endif
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_TIMESTAMPING,
+ sizeof (struct scm_timestamping))) {
+ struct scm_timestamping ts3;
+
+ memcpy(&ts3, CMSG_DATA(cmsg), sizeof (ts3));
+ message->timestamp.kernel = ts3.ts[0];
+ message->timestamp.hw = ts3.ts[2];
+ }
+ else if ((match_cmsg(cmsg, SOL_IP, IP_RECVERR, 0) ||
+ match_cmsg(cmsg, SOL_IPV6, IPV6_RECVERR, 0)) &&
+ cmsg->cmsg_len >= CMSG_LEN(sizeof (struct sock_extended_err))) {
+ struct sock_extended_err err;
+
+ 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) {
+ log_message(sock_fd, 1, message, "Unexpected extended error in", NULL);
+ r = 0;
+ }
+ }
+#endif
+ else if (match_cmsg(cmsg, SOL_SOCKET, SCM_RIGHTS, 0)) {
+ if (!(flags & SCK_FLAG_MSG_DESCRIPTOR) || cmsg->cmsg_len != CMSG_LEN(sizeof (int))) {
+ int i, fd;
+
+ DEBUG_LOG("Unexpected SCM_RIGHTS");
+ for (i = 0; CMSG_LEN((i + 1) * sizeof (int)) <= cmsg->cmsg_len; i++) {
+ memcpy(&fd, (char *)CMSG_DATA(cmsg) + i * sizeof (int), sizeof (fd));
+ close(fd);
+ }
+ r = 0;
+ } else {
+ memcpy(&message->descriptor, CMSG_DATA(cmsg), sizeof (message->descriptor));
+ }
+ }
+ else {
+ DEBUG_LOG("Unexpected control message level=%d type=%d len=%d",
+ cmsg->cmsg_level, cmsg->cmsg_type, (int)cmsg->cmsg_len);
+ }
+ }
+
+ if (!r && message->descriptor != INVALID_SOCK_FD)
+ close(message->descriptor);
+
+ return r;
+}
+
+/* ================================================== */
+
+static SCK_Message *
+receive_messages(int sock_fd, int flags, int max_messages, int *num_messages)
+{
+ struct MessageHeader *hdr;
+ SCK_Message *messages;
+ unsigned int i, n, n_ok;
+ int ret, recv_flags = 0;
+
+ assert(initialised);
+
+ *num_messages = 0;
+
+ if (max_messages < 1)
+ return NULL;
+
+ /* Prepare used buffers for new messages */
+ prepare_buffers(received_messages);
+ received_messages = 0;
+
+ messages = ARR_GetElements(recv_sck_messages);
+
+ hdr = ARR_GetElements(recv_headers);
+ n = ARR_GetSize(recv_headers);
+ n = MIN(n, max_messages);
+
+ if (n < 1 || n > MAX_RECV_MESSAGES ||
+ n > ARR_GetSize(recv_messages) || n > ARR_GetSize(recv_sck_messages))
+ assert(0);
+
+ recv_flags = get_recv_flags(flags);
+
+#ifdef HAVE_RECVMMSG
+ ret = recvmmsg(sock_fd, hdr, n, recv_flags, NULL);
+ if (ret >= 0)
+ n = ret;
+#else
+ n = 1;
+ ret = recvmsg(sock_fd, &hdr[0].msg_hdr, recv_flags);
+ if (ret >= 0)
+ hdr[0].msg_len = ret;
+#endif
+
+ if (ret < 0) {
+ handle_recv_error(sock_fd, flags);
+ return NULL;
+ }
+
+ received_messages = n;
+
+ for (i = n_ok = 0; i < n; i++) {
+ hdr = ARR_GetElement(recv_headers, i);
+ if (!process_header(&hdr->msg_hdr, hdr->msg_len, sock_fd, flags, &messages[n_ok]))
+ continue;
+
+ log_message(sock_fd, 1, &messages[n_ok],
+ flags & SCK_FLAG_MSG_ERRQUEUE ? "Received error" : "Received", NULL);
+
+ n_ok++;
+ }
+
+ *num_messages = n_ok;
+
+ return n_ok > 0 ? messages : NULL;
+}
+
+/* ================================================== */
+
+static void *
+add_control_message(struct msghdr *msg, int level, int type, size_t length, size_t buf_length)
+{
+ struct cmsghdr *cmsg;
+ size_t cmsg_space;
+
+ /* Avoid using CMSG_NXTHDR as the one in glibc does not support adding
+ control messages: https://sourceware.org/bugzilla/show_bug.cgi?id=13500 */
+
+ cmsg = msg->msg_control;
+ cmsg_space = CMSG_SPACE(length);
+
+ if (!cmsg || length > buf_length || msg->msg_controllen + cmsg_space > buf_length) {
+ DEBUG_LOG("Could not add control message level=%d type=%d", level, type);
+ return NULL;
+ }
+
+ cmsg = (struct cmsghdr *)((char *)cmsg + msg->msg_controllen);
+
+ memset(cmsg, 0, cmsg_space);
+
+ cmsg->cmsg_level = level;
+ cmsg->cmsg_type = type;
+ cmsg->cmsg_len = CMSG_LEN(length);
+
+ msg->msg_controllen += cmsg_space;
+
+ return CMSG_DATA(cmsg);
+}
+
+/* ================================================== */
+
+static int
+send_message(int sock_fd, SCK_Message *message, int flags)
+{
+ struct cmsghdr cmsg_buf[CMSG_BUF_SIZE / sizeof (struct cmsghdr)];
+ union sockaddr_all saddr;
+ socklen_t saddr_len;
+ struct msghdr msg;
+ struct iovec iov;
+
+ switch (message->addr_type) {
+ case SCK_ADDR_UNSPEC:
+ saddr_len = 0;
+ break;
+ case SCK_ADDR_IP:
+ saddr_len = SCK_IPSockAddrToSockaddr(&message->remote_addr.ip,
+ (struct sockaddr *)&saddr, sizeof (saddr));
+ break;
+ case SCK_ADDR_UNIX:
+ memset(&saddr, 0, sizeof (saddr));
+ if (snprintf(saddr.un.sun_path, sizeof (saddr.un.sun_path), "%s",
+ message->remote_addr.path) >= sizeof (saddr.un.sun_path)) {
+ DEBUG_LOG("Unix socket path %s too long", message->remote_addr.path);
+ return 0;
+ }
+ saddr.un.sun_family = AF_UNIX;
+ saddr_len = sizeof (saddr.un);
+ break;
+ default:
+ assert(0);
+ }
+
+ if (saddr_len) {
+ msg.msg_name = &saddr.un;
+ msg.msg_namelen = saddr_len;
+ } else {
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ }
+
+ if (message->length < 0) {
+ DEBUG_LOG("Invalid length %d", message->length);
+ return 0;
+ }
+
+ iov.iov_base = message->data;
+ iov.iov_len = message->length;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ if (message->addr_type == SCK_ADDR_IP) {
+ if (message->local_addr.ip.family == IPADDR_INET4) {
+#ifdef HAVE_IN_PKTINFO
+ struct in_pktinfo *ipi;
+
+ ipi = add_control_message(&msg, IPPROTO_IP, IP_PKTINFO, sizeof (*ipi),
+ sizeof (cmsg_buf));
+ if (!ipi)
+ return 0;
+
+ ipi->ipi_spec_dst.s_addr = htonl(message->local_addr.ip.addr.in4);
+ if (message->if_index != INVALID_IF_INDEX)
+ ipi->ipi_ifindex = message->if_index;
+
+#elif defined(IP_SENDSRCADDR)
+ struct in_addr *addr;
+
+ addr = add_control_message(&msg, IPPROTO_IP, IP_SENDSRCADDR, sizeof (*addr),
+ sizeof (cmsg_buf));
+ if (!addr)
+ return 0;
+
+ addr->s_addr = htonl(message->local_addr.ip.addr.in4);
+#endif
+ }
+
+#ifdef HAVE_IN6_PKTINFO
+ if (message->local_addr.ip.family == IPADDR_INET6) {
+ struct in6_pktinfo *ipi;
+
+ ipi = add_control_message(&msg, IPPROTO_IPV6, IPV6_PKTINFO, sizeof (*ipi),
+ sizeof (cmsg_buf));
+ if (!ipi)
+ return 0;
+
+ memcpy(&ipi->ipi6_addr.s6_addr, &message->local_addr.ip.addr.in6,
+ sizeof(ipi->ipi6_addr.s6_addr));
+ if (message->if_index != INVALID_IF_INDEX)
+ ipi->ipi6_ifindex = message->if_index;
+ }
+#endif
+ }
+
+#ifdef HAVE_LINUX_TIMESTAMPING
+ if (message->timestamp.tx_flags) {
+ int *ts_tx_flags;
+
+ /* Set timestamping flags for this message */
+
+ ts_tx_flags = add_control_message(&msg, SOL_SOCKET, SO_TIMESTAMPING,
+ sizeof (*ts_tx_flags), sizeof (cmsg_buf));
+ if (!ts_tx_flags)
+ return 0;
+
+ *ts_tx_flags = message->timestamp.tx_flags;
+ }
+#endif
+
+ if (flags & SCK_FLAG_MSG_DESCRIPTOR) {
+ int *fd;
+
+ fd = add_control_message(&msg, SOL_SOCKET, SCM_RIGHTS, sizeof (*fd), sizeof (cmsg_buf));
+ if (!fd)
+ return 0;
+
+ *fd = message->descriptor;
+ }
+
+ /* This is apparently required on some systems */
+ if (msg.msg_controllen == 0)
+ msg.msg_control = NULL;
+
+ if (sendmsg(sock_fd, &msg, 0) < 0) {
+ log_message(sock_fd, -1, message, "Could not send", strerror(errno));
+ return 0;
+ }
+
+ log_message(sock_fd, -1, message, "Sent", NULL);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+SCK_PreInitialise(void)
+{
+#ifdef LINUX
+ char *s, *ptr;
+
+ /* On Linux systems, the systemd service manager may pass file descriptors
+ for pre-initialised sockets to the chronyd daemon. The service manager
+ allocates and binds the file descriptors, and passes a copy to each
+ spawned instance of the service. This allows for zero-downtime service
+ restarts as the sockets buffer client requests until the service is able
+ to handle them. The service manager sets the LISTEN_FDS environment
+ variable to the number of passed file descriptors, and the integer file
+ descriptors start at 3 (see SD_LISTEN_FDS_START in
+ https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html). */
+ first_reusable_fd = 3;
+ reusable_fds = 0;
+
+ s = getenv("LISTEN_FDS");
+ if (s) {
+ errno = 0;
+ reusable_fds = strtol(s, &ptr, 10);
+ if (errno != 0 || *ptr != '\0' || reusable_fds < 0)
+ reusable_fds = 0;
+ }
+#else
+ first_reusable_fd = 0;
+ reusable_fds = 0;
+#endif
+}
+
+/* ================================================== */
+
+void
+SCK_Initialise(int family)
+{
+ int fd;
+
+ ip4_enabled = family == IPADDR_INET4 || family == IPADDR_UNSPEC;
+#ifdef FEAT_IPV6
+ ip6_enabled = family == IPADDR_INET6 || family == IPADDR_UNSPEC;
+#else
+ ip6_enabled = 0;
+#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);
+ recv_sck_messages = ARR_CreateInstance(sizeof (SCK_Message));
+ ARR_SetSize(recv_sck_messages, MAX_RECV_MESSAGES);
+
+ received_messages = MAX_RECV_MESSAGES;
+
+ priv_bind_function = NULL;
+
+ supported_socket_flags = 0;
+#ifdef SOCK_CLOEXEC
+ if (check_socket_flag(SOCK_CLOEXEC, FD_CLOEXEC, 0))
+ supported_socket_flags |= SOCK_CLOEXEC;
+#endif
+#ifdef SOCK_NONBLOCK
+ if (check_socket_flag(SOCK_NONBLOCK, 0, O_NONBLOCK))
+ supported_socket_flags |= SOCK_NONBLOCK;
+#endif
+
+ for (fd = first_reusable_fd; fd < first_reusable_fd + reusable_fds; fd++)
+ UTI_FdSetCloexec(fd);
+
+ initialised = 1;
+}
+
+/* ================================================== */
+
+void
+SCK_Finalise(void)
+{
+ ARR_DestroyInstance(recv_sck_messages);
+ ARR_DestroyInstance(recv_headers);
+ ARR_DestroyInstance(recv_messages);
+
+ SCK_CloseReusableSockets();
+
+ initialised = 0;
+}
+
+/* ================================================== */
+
+int
+SCK_IsIpFamilyEnabled(int family)
+{
+ switch (family) {
+ case IPADDR_INET4:
+ return ip4_enabled;
+ case IPADDR_INET6:
+ return ip6_enabled;
+ default:
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+void
+SCK_GetAnyLocalIPAddress(int family, IPAddr *local_addr)
+{
+ local_addr->family = family;
+
+ switch (family) {
+ case IPADDR_INET4:
+ local_addr->addr.in4 = INADDR_ANY;
+ break;
+ case IPADDR_INET6:
+#ifdef FEAT_IPV6
+ memcpy(&local_addr->addr.in6, &in6addr_any, sizeof (local_addr->addr.in6));
+#else
+ memset(&local_addr->addr.in6, 0, sizeof (local_addr->addr.in6));
+#endif
+ break;
+ }
+}
+
+/* ================================================== */
+
+void
+SCK_GetLoopbackIPAddress(int family, IPAddr *local_addr)
+{
+ local_addr->family = family;
+
+ switch (family) {
+ case IPADDR_INET4:
+ local_addr->addr.in4 = INADDR_LOOPBACK;
+ break;
+ case IPADDR_INET6:
+#ifdef FEAT_IPV6
+ memcpy(&local_addr->addr.in6, &in6addr_loopback, sizeof (local_addr->addr.in6));
+#else
+ memset(&local_addr->addr.in6, 0, sizeof (local_addr->addr.in6));
+ local_addr->addr.in6[15] = 1;
+#endif
+ break;
+ }
+}
+
+/* ================================================== */
+
+int
+SCK_IsLinkLocalIPAddress(IPAddr *addr)
+{
+ switch (addr->family) {
+ case IPADDR_INET4:
+ /* 169.254.0.0/16 */
+ return (addr->addr.in4 & 0xffff0000) == 0xa9fe0000;
+ case IPADDR_INET6:
+ /* fe80::/10 */
+ return addr->addr.in6[0] == 0xfe && (addr->addr.in6[1] & 0xc0) == 0x80;
+ default:
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+void
+SCK_SetPrivBind(int (*function)(int sock_fd, struct sockaddr *address,
+ socklen_t address_len))
+{
+ priv_bind_function = function;
+}
+
+/* ================================================== */
+
+int
+SCK_OpenUdpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *iface, int flags)
+{
+ return open_ip_socket(remote_addr, local_addr, iface, SOCK_DGRAM, flags);
+}
+
+/* ================================================== */
+
+int
+SCK_OpenTcpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr, const char *iface, int flags)
+{
+ return open_ip_socket(remote_addr, local_addr, iface, SOCK_STREAM, flags);
+}
+
+/* ================================================== */
+
+int
+SCK_OpenUnixDatagramSocket(const char *remote_addr, const char *local_addr, int flags)
+{
+ return open_unix_socket(remote_addr, local_addr, SOCK_DGRAM, flags);
+}
+
+/* ================================================== */
+
+int
+SCK_OpenUnixStreamSocket(const char *remote_addr, const char *local_addr, int flags)
+{
+ return open_unix_socket(remote_addr, local_addr, SOCK_STREAM, flags);
+}
+
+/* ================================================== */
+
+int
+SCK_OpenUnixSocketPair(int flags, int *other_fd)
+{
+ int sock_fd;
+
+ /* Prefer SEQPACKET sockets over DGRAM in order to receive a zero-length
+ message (end of file) when the other end is unexpectedly closed */
+ if (
+#ifdef SOCK_SEQPACKET
+ (sock_fd = open_unix_socket_pair(SOCK_SEQPACKET, flags, other_fd)) < 0 &&
+#endif
+ (sock_fd = open_unix_socket_pair(SOCK_DGRAM, flags, other_fd)) < 0)
+ return INVALID_SOCK_FD;
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+int
+SCK_IsReusable(int fd)
+{
+ return fd >= first_reusable_fd && fd < first_reusable_fd + reusable_fds;
+}
+
+/* ================================================== */
+
+void
+SCK_CloseReusableSockets(void)
+{
+ int fd;
+
+ for (fd = first_reusable_fd; fd < first_reusable_fd + reusable_fds; fd++)
+ close(fd);
+ reusable_fds = 0;
+ first_reusable_fd = 0;
+}
+
+/* ================================================== */
+
+int
+SCK_SetIntOption(int sock_fd, int level, int name, int value)
+{
+ if (setsockopt(sock_fd, level, name, &value, sizeof (value)) < 0) {
+ DEBUG_LOG("setsockopt() failed fd=%d level=%d name=%d value=%d : %s",
+ sock_fd, level, name, value, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SCK_GetIntOption(int sock_fd, int level, int name, int *value)
+{
+ socklen_t len = sizeof (*value);
+
+ if (getsockopt(sock_fd, level, name, value, &len) < 0) {
+ DEBUG_LOG("getsockopt() failed fd=%d level=%d name=%d : %s",
+ sock_fd, level, name, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SCK_EnableKernelRxTimestamping(int sock_fd)
+{
+#ifdef SO_TIMESTAMPNS
+ if (SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMPNS, 1))
+ return 1;
+#endif
+#ifdef SO_TIMESTAMP
+ if (SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TIMESTAMP, 1)) {
+#if defined(SO_TS_CLOCK) && defined(SO_TS_REALTIME)
+ /* We don't care about the return value - we'll get either a
+ SCM_REALTIME (if we succeded) or a SCM_TIMESTAMP (if we failed) */
+ if (!SCK_SetIntOption(sock_fd, SOL_SOCKET, SO_TS_CLOCK, SO_TS_REALTIME))
+ ;
+#endif
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+SCK_ListenOnSocket(int sock_fd, int backlog)
+{
+ if (!SCK_IsReusable(sock_fd) && listen(sock_fd, backlog) < 0) {
+ DEBUG_LOG("listen() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SCK_AcceptConnection(int sock_fd, IPSockAddr *remote_addr)
+{
+ union sockaddr_all saddr;
+ socklen_t saddr_len = sizeof (saddr);
+ int conn_fd;
+
+ conn_fd = accept(sock_fd, &saddr.sa, &saddr_len);
+ if (conn_fd < 0) {
+ DEBUG_LOG("accept() failed : %s", strerror(errno));
+ return INVALID_SOCK_FD;
+ }
+
+ if (!UTI_FdSetCloexec(conn_fd) || !set_socket_nonblock(conn_fd)) {
+ close(conn_fd);
+ return INVALID_SOCK_FD;
+ }
+
+ SCK_SockaddrToIPSockAddr(&saddr.sa, saddr_len, remote_addr);
+
+ return conn_fd;
+}
+
+/* ================================================== */
+
+int
+SCK_ShutdownConnection(int sock_fd)
+{
+ if (shutdown(sock_fd, SHUT_RDWR) < 0) {
+ DEBUG_LOG("shutdown() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SCK_Receive(int sock_fd, void *buffer, int length, int flags)
+{
+ int r;
+
+ if (length < 0) {
+ DEBUG_LOG("Invalid length %d", length);
+ return -1;
+ }
+
+ r = recv(sock_fd, buffer, length, get_recv_flags(flags));
+
+ if (r < 0) {
+ handle_recv_error(sock_fd, flags);
+ return r;
+ }
+
+ DEBUG_LOG("Received data fd=%d len=%d", sock_fd, r);
+
+ return r;
+}
+
+/* ================================================== */
+
+int
+SCK_Send(int sock_fd, const void *buffer, int length, int flags)
+{
+ int r;
+
+ assert(flags == 0);
+
+ if (length < 0) {
+ DEBUG_LOG("Invalid length %d", length);
+ return -1;
+ }
+
+ r = send(sock_fd, buffer, length, 0);
+
+ if (r < 0) {
+ DEBUG_LOG("Could not send data fd=%d len=%d : %s", sock_fd, length, strerror(errno));
+ return r;
+ }
+
+ DEBUG_LOG("Sent data fd=%d len=%d", sock_fd, r);
+
+ return r;
+}
+
+/* ================================================== */
+
+SCK_Message *
+SCK_ReceiveMessage(int sock_fd, int flags)
+{
+ int num_messages;
+
+ return receive_messages(sock_fd, flags, 1, &num_messages);
+}
+
+/* ================================================== */
+
+SCK_Message *
+SCK_ReceiveMessages(int sock_fd, int flags, int *num_messages)
+{
+ return receive_messages(sock_fd, flags, MAX_RECV_MESSAGES, num_messages);
+}
+
+/* ================================================== */
+
+void
+SCK_InitMessage(SCK_Message *message, SCK_AddressType addr_type)
+{
+ init_message_addresses(message, addr_type);
+ init_message_nonaddress(message);
+}
+
+/* ================================================== */
+
+int
+SCK_SendMessage(int sock_fd, SCK_Message *message, int flags)
+{
+ return send_message(sock_fd, message, flags);
+}
+
+/* ================================================== */
+
+int
+SCK_RemoveSocket(int sock_fd)
+{
+ union sockaddr_all saddr;
+ socklen_t saddr_len;
+
+ saddr_len = sizeof (saddr);
+
+ if (getsockname(sock_fd, &saddr.sa, &saddr_len) < 0) {
+ DEBUG_LOG("getsockname() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ if (saddr_len > sizeof (saddr) || saddr_len <= sizeof (saddr.sa.sa_family) ||
+ saddr.sa.sa_family != AF_UNIX)
+ return 0;
+
+ if (unlink(saddr.un.sun_path) < 0) {
+ DEBUG_LOG("Could not remove %s : %s", saddr.un.sun_path, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+SCK_CloseSocket(int sock_fd)
+{
+ /* Reusable sockets are closed in finalisation */
+ if (SCK_IsReusable(sock_fd))
+ return;
+
+ close(sock_fd);
+}
+
+/* ================================================== */
+
+void
+SCK_SockaddrToIPSockAddr(struct sockaddr *sa, int sa_length, IPSockAddr *ip_sa)
+{
+ ip_sa->ip_addr.family = IPADDR_UNSPEC;
+ ip_sa->port = 0;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (sa_length < (int)sizeof (struct sockaddr_in))
+ return;
+ ip_sa->ip_addr.family = IPADDR_INET4;
+ ip_sa->ip_addr.addr.in4 = ntohl(((struct sockaddr_in *)sa)->sin_addr.s_addr);
+ ip_sa->port = ntohs(((struct sockaddr_in *)sa)->sin_port);
+ break;
+#ifdef FEAT_IPV6
+ case AF_INET6:
+ if (sa_length < (int)sizeof (struct sockaddr_in6))
+ return;
+ ip_sa->ip_addr.family = IPADDR_INET6;
+ memcpy(&ip_sa->ip_addr.addr.in6, ((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr,
+ sizeof (ip_sa->ip_addr.addr.in6));
+ ip_sa->port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+/* ================================================== */
+
+int
+SCK_IPSockAddrToSockaddr(IPSockAddr *ip_sa, struct sockaddr *sa, int sa_length)
+{
+ switch (ip_sa->ip_addr.family) {
+ case IPADDR_INET4:
+ if (sa_length < (int)sizeof (struct sockaddr_in))
+ return 0;
+ memset(sa, 0, sizeof (struct sockaddr_in));
+ sa->sa_family = AF_INET;
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr = htonl(ip_sa->ip_addr.addr.in4);
+ ((struct sockaddr_in *)sa)->sin_port = htons(ip_sa->port);
+#ifdef SIN6_LEN
+ ((struct sockaddr_in *)sa)->sin_len = sizeof (struct sockaddr_in);
+#endif
+ return sizeof (struct sockaddr_in);
+#ifdef FEAT_IPV6
+ case IPADDR_INET6:
+ if (sa_length < (int)sizeof (struct sockaddr_in6))
+ return 0;
+ memset(sa, 0, sizeof (struct sockaddr_in6));
+ sa->sa_family = AF_INET6;
+ memcpy(&((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr, ip_sa->ip_addr.addr.in6,
+ sizeof (((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr));
+ ((struct sockaddr_in6 *)sa)->sin6_port = htons(ip_sa->port);
+#ifdef SIN6_LEN
+ ((struct sockaddr_in6 *)sa)->sin6_len = sizeof (struct sockaddr_in6);
+#endif
+ return sizeof (struct sockaddr_in6);
+#endif
+ default:
+ if (sa_length < (int)sizeof (struct sockaddr))
+ return 0;
+ memset(sa, 0, sizeof (struct sockaddr));
+ sa->sa_family = AF_UNSPEC;
+ return 0;
+ }
+}
diff --git a/socket.h b/socket.h
new file mode 100644
index 0000000..8b178e2
--- /dev/null
+++ b/socket.h
@@ -0,0 +1,156 @@
+/*
+ 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;
+ 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;
+
+/* Pre-initialisation function */
+extern void SCK_PreInitialise(void);
+
+/* Initialisation function (the specified IP family is enabled,
+ or all if IPADDR_UNSPEC) */
+extern void SCK_Initialise(int family);
+
+/* Finalisation function */
+extern void SCK_Finalise(void);
+
+/* Check if support for the IP family is enabled */
+extern int SCK_IsIpFamilyEnabled(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);
+
+/* Check if an IP address is a link-local address */
+extern int SCK_IsLinkLocalIPAddress(IPAddr *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 a socket (addresses and iface may be NULL) */
+extern int SCK_OpenUdpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr,
+ const char *iface, int flags);
+extern int SCK_OpenTcpSocket(IPSockAddr *remote_addr, IPSockAddr *local_addr,
+ const char *iface, 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);
+
+/* Check if a file descriptor was passed from the service manager */
+extern int SCK_IsReusable(int sock_fd);
+
+/* Close all reusable sockets before finalisation (e.g. in a helper process) */
+extern void SCK_CloseReusableSockets(void);
+
+/* Set and get a socket option of int size */
+extern int SCK_SetIntOption(int sock_fd, int level, int name, int value);
+extern int SCK_GetIntOption(int sock_fd, int level, int name, int *value);
+
+/* 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, int length, int flags);
+extern int SCK_Send(int sock_fd, const void *buffer, 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
diff --git a/sources.c b/sources.c
new file mode 100644
index 0000000..e7ec4b8
--- /dev/null
+++ b/sources.c
@@ -0,0 +1,1876 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2011-2016, 2018, 2020-2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ The routines in this file manage the complete pool of sources that
+ we might be synchronizing to. This includes NTP sources and others
+ (e.g. local reference clocks, eyeball + wristwatch etc).
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "sources.h"
+#include "sourcestats.h"
+#include "memory.h"
+#include "ntp.h" /* For NTP_Leap */
+#include "ntp_sources.h"
+#include "local.h"
+#include "reference.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "reports.h"
+#include "nameserv.h"
+#include "sched.h"
+#include "regress.h"
+
+/* ================================================== */
+/* Flag indicating that we are initialised */
+static int initialised = 0;
+
+/* ================================================== */
+/* Structure used to hold info for selecting between sources */
+struct SelectInfo {
+ int select_ok;
+ double std_dev;
+ double root_distance;
+ double lo_limit;
+ double hi_limit;
+ double last_sample_ago;
+};
+
+/* ================================================== */
+/* This enum contains the flag values that are used to label
+ each source */
+typedef enum {
+ SRC_OK, /* OK so far, not a final status! */
+ SRC_UNSELECTABLE, /* Has noselect option set */
+ SRC_UNSYNCHRONISED, /* Provides samples but not unsynchronised */
+ SRC_BAD_STATS, /* Doesn't have valid stats data */
+ SRC_BAD_DISTANCE, /* Has root distance longer than allowed maximum */
+ SRC_JITTERY, /* Had std dev larger than allowed maximum */
+ SRC_WAITS_STATS, /* Others have bad stats, selection postponed */
+ SRC_STALE, /* Has older samples than others */
+ SRC_ORPHAN, /* Has stratum equal or larger than orphan stratum */
+ SRC_UNTRUSTED, /* Overlaps trusted sources */
+ SRC_FALSETICKER, /* Doesn't agree with others */
+ SRC_WAITS_SOURCES, /* Not enough sources, selection postponed */
+ SRC_NONPREFERRED, /* Others have prefer option */
+ SRC_WAITS_UPDATE, /* No updates, selection postponed */
+ SRC_DISTANT, /* Others have shorter root distance */
+ SRC_OUTLIER, /* Outlier in clustering (not used yet) */
+ SRC_UNSELECTED, /* Used for synchronisation, not system peer */
+ SRC_SELECTED, /* Used for synchronisation, selected as system peer */
+} SRC_Status;
+
+/* ================================================== */
+/* Define the instance structure used to hold information about each
+ source */
+struct SRC_Instance_Record {
+ SST_Stats stats;
+ int index; /* Index back into the array of source */
+ uint32_t ref_id; /* The reference ID of this source
+ (i.e. from its IP address, NOT the
+ reference _it_ is sync'd to) */
+ IPAddr *ip_addr; /* Its IP address if NTP source */
+
+ /* Flag indicating that the source is updating reachability */
+ int active;
+
+ /* Reachability register */
+ int reachability;
+
+ /* Number of set bits in the reachability register */
+ int reachability_size;
+
+ /* Updates since last reference update */
+ int updates;
+
+ /* Updates left before allowing combining */
+ int distant;
+
+ /* Updates with a status requiring source replacement */
+ int bad;
+
+ /* Flag indicating the status of the source */
+ SRC_Status status;
+
+ /* Type of the source */
+ SRC_Type type;
+
+ /* Flag indicating that the source is authenticated */
+ int authenticated;
+
+ /* Configured selection options */
+ int conf_sel_options;
+
+ /* Effective selection options */
+ int sel_options;
+
+ /* Score against currently selected source */
+ double sel_score;
+
+ struct SelectInfo sel_info;
+
+ /* Current stratum */
+ int stratum;
+
+ /* Current leap status */
+ NTP_Leap leap;
+
+ /* Flag indicating the source has a leap second vote */
+ int leap_vote;
+
+ /* Flag indicating the source was already reported as
+ a falseticker since the last selection change */
+ int reported_falseticker;
+};
+
+/* ================================================== */
+/* Structure used to build the sort list for finding falsetickers */
+struct Sort_Element {
+ int index;
+ double offset;
+ enum {
+ LOW = -1,
+ HIGH = 1
+ } tag;
+};
+
+/* ================================================== */
+/* Table of sources */
+static struct SRC_Instance_Record **sources;
+static struct Sort_Element *sort_list;
+static int *sel_sources;
+static int n_sources; /* Number of sources currently in the table */
+static int max_n_sources; /* Capacity of the table */
+
+#define INVALID_SOURCE (-1)
+static int selected_source_index; /* Which source index is currently
+ selected (set to INVALID_SOURCE
+ if no current valid reference) */
+static int reported_no_majority; /* Flag to avoid repeated log message
+ about no majority */
+static int report_selection_loss; /* Flag to force logging a message if
+ selection is lost in a transient state
+ (SRC_WAITS_STATS, SRC_WAITS_UPDATE) */
+
+/* Score needed to replace the currently selected source */
+#define SCORE_LIMIT 10.0
+
+/* Number of updates needed to reset the distant status */
+#define DISTANT_PENALTY 32
+
+/* Number of updates needed to trigger handling of bad sources */
+#define BAD_HANDLE_THRESHOLD 4
+
+static double max_distance;
+static double max_jitter;
+static double reselect_distance;
+static double stratum_weight;
+static double combine_limit;
+
+static SRC_Instance last_updated_inst;
+
+static LOG_FileID logfileid;
+
+/* Identifier of the dump file */
+#define DUMP_IDENTIFIER "SRC0\n"
+
+/* ================================================== */
+/* Forward prototype */
+
+static void update_sel_options(void);
+static void unselect_selected_source(LOG_Severity severity, const char *format,
+ const char *arg);
+static void slew_sources(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything);
+static void add_dispersion(double dispersion, void *anything);
+static char *source_to_string(SRC_Instance inst);
+static char get_status_char(SRC_Status status);
+
+/* ================================================== */
+/* Initialisation function */
+void SRC_Initialise(void) {
+ sources = NULL;
+ sort_list = NULL;
+ sel_sources = NULL;
+ n_sources = 0;
+ max_n_sources = 0;
+ selected_source_index = INVALID_SOURCE;
+ max_distance = CNF_GetMaxDistance();
+ max_jitter = CNF_GetMaxJitter();
+ reselect_distance = CNF_GetReselectDistance();
+ stratum_weight = CNF_GetStratumWeight();
+ combine_limit = CNF_GetCombineLimit();
+ initialised = 1;
+
+ LCL_AddParameterChangeHandler(slew_sources, NULL);
+ LCL_AddDispersionNotifyHandler(add_dispersion, NULL);
+
+ last_updated_inst = NULL;
+
+ logfileid = CNF_GetLogSelection() ? LOG_FileOpen("selection",
+ " Date (UTC) Time IP Address S EOpts Reach Score Last sample Low end High end")
+ : -1;
+}
+
+/* ================================================== */
+/* Finalisation function */
+void SRC_Finalise(void)
+{
+ LCL_RemoveParameterChangeHandler(slew_sources, NULL);
+ LCL_RemoveDispersionNotifyHandler(add_dispersion, NULL);
+
+ Free(sources);
+ Free(sort_list);
+ Free(sel_sources);
+
+ initialised = 0;
+}
+
+/* ================================================== */
+/* Function to create a new instance. This would be called by one of
+ the individual source-type instance creation routines. */
+
+SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authenticated,
+ int sel_options, IPAddr *addr, int min_samples,
+ int max_samples, double min_delay, double asymmetry)
+{
+ SRC_Instance result;
+
+ assert(initialised);
+
+ if (min_samples == SRC_DEFAULT_MINSAMPLES)
+ min_samples = CNF_GetMinSamples();
+ if (max_samples == SRC_DEFAULT_MAXSAMPLES)
+ max_samples = CNF_GetMaxSamples();
+
+ result = MallocNew(struct SRC_Instance_Record);
+ result->stats = SST_CreateInstance(ref_id, addr, min_samples, max_samples,
+ min_delay, asymmetry);
+
+ if (n_sources == max_n_sources) {
+ /* Reallocate memory */
+ max_n_sources = max_n_sources > 0 ? 2 * max_n_sources : 4;
+ if (sources) {
+ sources = ReallocArray(struct SRC_Instance_Record *, max_n_sources, sources);
+ sort_list = ReallocArray(struct Sort_Element, 3*max_n_sources, sort_list);
+ sel_sources = ReallocArray(int, max_n_sources, sel_sources);
+ } else {
+ sources = MallocArray(struct SRC_Instance_Record *, max_n_sources);
+ sort_list = MallocArray(struct Sort_Element, 3*max_n_sources);
+ sel_sources = MallocArray(int, max_n_sources);
+ }
+ }
+
+ sources[n_sources] = result;
+
+ result->index = n_sources;
+ result->type = type;
+ result->authenticated = authenticated;
+ result->conf_sel_options = sel_options;
+ result->sel_options = sel_options;
+ result->active = 0;
+
+ SRC_SetRefid(result, ref_id, addr);
+ SRC_ResetInstance(result);
+
+ n_sources++;
+
+ update_sel_options();
+
+ return result;
+}
+
+/* ================================================== */
+/* Function to get rid of a source when it is being unconfigured.
+ This may cause the current reference source to be reselected, if this
+ was the reference source or contributed significantly to a
+ falseticker decision. */
+
+void SRC_DestroyInstance(SRC_Instance instance)
+{
+ int dead_index, i;
+
+ if (last_updated_inst == instance)
+ last_updated_inst = NULL;
+
+ assert(initialised);
+ if (instance->index < 0 || instance->index >= n_sources ||
+ instance != sources[instance->index])
+ assert(0);
+
+ SST_DeleteInstance(instance->stats);
+ dead_index = instance->index;
+ for (i=dead_index; i<n_sources-1; i++) {
+ sources[i] = sources[i+1];
+ sources[i]->index = i;
+ }
+ --n_sources;
+ Free(instance);
+
+ update_sel_options();
+
+ if (selected_source_index > dead_index)
+ --selected_source_index;
+ else if (selected_source_index == dead_index)
+ unselect_selected_source(LOGS_INFO, NULL, NULL);
+
+ SRC_SelectSource(NULL);
+}
+
+/* ================================================== */
+
+void
+SRC_ResetInstance(SRC_Instance instance)
+{
+ instance->updates = 0;
+ instance->reachability = 0;
+ instance->reachability_size = 0;
+ instance->distant = 0;
+ instance->bad = 0;
+ instance->status = SRC_BAD_STATS;
+ instance->sel_score = 1.0;
+ instance->stratum = 0;
+ instance->leap = LEAP_Unsynchronised;
+ instance->leap_vote = 0;
+ instance->reported_falseticker = 0;
+
+ memset(&instance->sel_info, 0, sizeof (instance->sel_info));
+
+ SST_ResetInstance(instance->stats);
+
+ if (selected_source_index == instance->index)
+ SRC_SelectSource(NULL);
+}
+
+/* ================================================== */
+
+void
+SRC_SetRefid(SRC_Instance instance, uint32_t ref_id, IPAddr *addr)
+{
+ instance->ref_id = ref_id;
+ instance->ip_addr = addr;
+ SST_SetRefid(instance->stats, ref_id, addr);
+}
+
+/* ================================================== */
+
+SST_Stats
+SRC_GetSourcestats(SRC_Instance instance)
+{
+ assert(initialised);
+ return instance->stats;
+}
+
+/* ================================================== */
+
+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_UpdateStatus(SRC_Instance inst, int stratum, NTP_Leap leap)
+{
+ inst->stratum = stratum;
+
+ 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.
+
+ This function causes the frequency estimation to be re-run for the
+ designated source, and the clock selection procedure to be re-run
+ afterwards.
+ */
+
+void
+SRC_AccumulateSample(SRC_Instance inst, NTP_Sample *sample)
+{
+
+ assert(initialised);
+
+ DEBUG_LOG("src=%s ts=%s offset=%e delay=%e disp=%e",
+ source_to_string(inst), UTI_TimespecToString(&sample->time), -sample->offset,
+ sample->root_delay, sample->root_dispersion);
+
+ if (REF_IsLeapSecondClose(&sample->time, sample->offset)) {
+ LOG(LOGS_INFO, "Dropping sample around leap second");
+ return;
+ }
+
+ SST_AccumulateSample(inst->stats, sample);
+ SST_DoNewRegression(inst->stats);
+}
+
+/* ================================================== */
+
+void
+SRC_SetActive(SRC_Instance inst)
+{
+ inst->active = 1;
+}
+
+/* ================================================== */
+
+void
+SRC_UnsetActive(SRC_Instance inst)
+{
+ inst->active = 0;
+}
+
+/* ================================================== */
+
+static int
+special_mode_end(void)
+{
+ int i;
+
+ for (i = 0; i < n_sources; i++) {
+ /* No updates from inactive sources */
+ if (!sources[i]->active)
+ continue;
+
+ /* Don't expect more updates than the initial burst of an NTP source */
+ if (sources[i]->reachability_size >= SOURCE_REACH_BITS - 1)
+ continue;
+
+ /* Check if the source could still have enough samples to be selectable */
+ if (SOURCE_REACH_BITS - 1 - sources[i]->reachability_size +
+ SST_Samples(sources[i]->stats) >= MIN_SAMPLES_FOR_REGRESS)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+handle_bad_source(SRC_Instance inst)
+{
+ if (inst->type == SRC_NTP) {
+ DEBUG_LOG("Bad source status=%c", get_status_char(inst->status));
+ NSR_HandleBadSource(inst->ip_addr);
+ }
+}
+
+/* ================================================== */
+
+void
+SRC_UpdateReachability(SRC_Instance inst, int reachable)
+{
+ inst->reachability <<= 1;
+ inst->reachability |= !!reachable;
+ inst->reachability %= 1U << SOURCE_REACH_BITS;
+
+ if (inst->reachability_size < SOURCE_REACH_BITS)
+ inst->reachability_size++;
+
+ if (!reachable && inst->index == selected_source_index) {
+ /* Try to select a better source */
+ SRC_SelectSource(NULL);
+ }
+
+ /* Check if special reference update mode failed */
+ if (REF_GetMode() != REF_ModeNormal && special_mode_end()) {
+ REF_SetUnsynchronised();
+ }
+
+ /* Try to replace unreachable NTP sources */
+ if (inst->reachability == 0 && inst->reachability_size == SOURCE_REACH_BITS)
+ handle_bad_source(inst);
+}
+
+/* ================================================== */
+
+void
+SRC_ResetReachability(SRC_Instance inst)
+{
+ inst->reachability = 0;
+ inst->reachability_size = 0;
+ SRC_UpdateReachability(inst, 0);
+}
+
+/* ================================================== */
+
+static void
+update_sel_options(void)
+{
+ int options, auth_ntp_options, unauth_ntp_options, refclk_options;
+ int i, auth_ntp_sources, unauth_ntp_sources;
+
+ auth_ntp_sources = unauth_ntp_sources = 0;
+
+ for (i = 0; i < n_sources; i++) {
+ if (sources[i]->conf_sel_options & SRC_SELECT_NOSELECT)
+ continue;
+ if (sources[i]->type != SRC_NTP)
+ continue;
+ if (sources[i]->authenticated)
+ auth_ntp_sources++;
+ else
+ unauth_ntp_sources++;
+ }
+
+ auth_ntp_options = unauth_ntp_options = refclk_options = 0;
+
+ /* Determine which selection options need to be added to authenticated NTP
+ sources, unauthenticated NTP sources, and refclocks, to follow the
+ configured selection mode */
+ switch (CNF_GetAuthSelectMode()) {
+ case SRC_AUTHSELECT_IGNORE:
+ break;
+ case SRC_AUTHSELECT_MIX:
+ if (auth_ntp_sources > 0 && unauth_ntp_sources > 0)
+ auth_ntp_options = refclk_options = SRC_SELECT_REQUIRE | SRC_SELECT_TRUST;
+ break;
+ case SRC_AUTHSELECT_PREFER:
+ if (auth_ntp_sources > 0)
+ unauth_ntp_options = SRC_SELECT_NOSELECT;
+ break;
+ case SRC_AUTHSELECT_REQUIRE:
+ unauth_ntp_options = SRC_SELECT_NOSELECT;
+ break;
+ default:
+ assert(0);
+ }
+
+ for (i = 0; i < n_sources; i++) {
+ options = sources[i]->conf_sel_options;
+
+ if (!(options & SRC_SELECT_NOSELECT)) {
+ switch (sources[i]->type) {
+ case SRC_NTP:
+ options |= sources[i]->authenticated ? auth_ntp_options : unauth_ntp_options;
+ break;
+ case SRC_REFCLOCK:
+ options |= refclk_options;
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ if (sources[i]->sel_options != options) {
+ DEBUG_LOG("changing %s from %x to %x", source_to_string(sources[i]),
+ (unsigned int)sources[i]->sel_options, (unsigned int)options);
+ sources[i]->sel_options = options;
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+log_selection_message(LOG_Severity severity, const char *format, const char *arg)
+{
+ if (REF_GetMode() != REF_ModeNormal)
+ return;
+ LOG(severity, format, arg);
+}
+
+/* ================================================== */
+
+static void
+log_selection_source(LOG_Severity severity, const char *format, SRC_Instance inst)
+{
+ char buf[320], *name, *ntp_name;
+
+ name = source_to_string(inst);
+ ntp_name = inst->type == SRC_NTP ? NSR_GetName(inst->ip_addr) : NULL;
+
+ if (ntp_name && strcmp(name, ntp_name) != 0)
+ snprintf(buf, sizeof (buf), "%s (%s)", name, ntp_name);
+ else
+ snprintf(buf, sizeof (buf), "%s", name);
+
+ log_selection_message(severity, format, buf);
+}
+
+/* ================================================== */
+
+static int
+compare_sort_elements(const void *a, const void *b)
+{
+ const struct Sort_Element *u = (const struct Sort_Element *) a;
+ const struct Sort_Element *v = (const struct Sort_Element *) b;
+
+ if (u->offset < v->offset) {
+ return -1;
+ } else if (u->offset > v->offset) {
+ return +1;
+ } else if (u->tag < v->tag) {
+ return -1;
+ } else if (u->tag > v->tag) {
+ return +1;
+ } else {
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+static char *
+source_to_string(SRC_Instance inst)
+{
+ switch (inst->type) {
+ case SRC_NTP:
+ return UTI_IPToString(inst->ip_addr);
+ case SRC_REFCLOCK:
+ return UTI_RefidToString(inst->ref_id);
+ default:
+ assert(0);
+ }
+ return NULL;
+}
+
+/* ================================================== */
+
+static void
+mark_source(SRC_Instance inst, SRC_Status status)
+{
+ struct timespec now;
+
+ inst->status = status;
+
+ /* Try to replace NTP sources that are falsetickers, or have a root
+ distance or jitter larger than the allowed maximums */
+ if (inst == last_updated_inst) {
+ if (inst->bad < INT_MAX &&
+ (status == SRC_FALSETICKER || status == SRC_BAD_DISTANCE || status == SRC_JITTERY))
+ inst->bad++;
+ else
+ inst->bad = 0;
+ if (inst->bad >= BAD_HANDLE_THRESHOLD)
+ handle_bad_source(inst);
+ }
+
+ DEBUG_LOG("%s status=%c options=%x reach=%o/%d updates=%d distant=%d bad=%d leap=%d vote=%d lo=%f hi=%f",
+ source_to_string(inst), get_status_char(inst->status),
+ (unsigned int)inst->sel_options, (unsigned int)inst->reachability,
+ inst->reachability_size, inst->updates,
+ inst->distant, inst->bad, (int)inst->leap, inst->leap_vote,
+ inst->sel_info.lo_limit, inst->sel_info.hi_limit);
+
+ if (logfileid == -1)
+ return;
+
+ SCH_GetLastEventTime(&now, NULL, NULL);
+
+ LOG_FileWrite(logfileid,
+ "%s %-15s %c -%c%c%c%c %4o %5.2f %10.3e %10.3e %10.3e",
+ UTI_TimeToLogForm(now.tv_sec), source_to_string(inst),
+ get_status_char(inst->status),
+ inst->sel_options & SRC_SELECT_NOSELECT ? 'N' : '-',
+ inst->sel_options & SRC_SELECT_PREFER ? 'P' : '-',
+ inst->sel_options & SRC_SELECT_TRUST ? 'T' : '-',
+ inst->sel_options & SRC_SELECT_REQUIRE ? 'R' : '-',
+ (unsigned int)inst->reachability, inst->sel_score,
+ inst->sel_info.last_sample_ago,
+ inst->sel_info.lo_limit, inst->sel_info.hi_limit);
+}
+
+/* ================================================== */
+
+static void
+mark_ok_sources(SRC_Status status)
+{
+ int i;
+
+ for (i = 0; i < n_sources; i++) {
+ if (sources[i]->status != SRC_OK)
+ continue;
+ mark_source(sources[i], status);
+ }
+}
+
+/* ================================================== */
+/* Reset the index of selected source and report the selection loss. If no
+ message is provided, assume it is a transient state and wait for another
+ call providing a message or selection of another source, which resets the
+ report_selection_loss flag. */
+
+static void
+unselect_selected_source(LOG_Severity severity, const char *format, const char *arg)
+{
+ if (selected_source_index != INVALID_SOURCE) {
+ selected_source_index = INVALID_SOURCE;
+ report_selection_loss = 1;
+ }
+
+ if (report_selection_loss && format) {
+ log_selection_message(severity, format, arg);
+ report_selection_loss = 0;
+ }
+}
+
+/* ================================================== */
+
+static int
+combine_sources(int n_sel_sources, struct timespec *ref_time, double *offset,
+ double *offset_sd, double *frequency, double *frequency_sd, double *skew)
+{
+ struct timespec src_ref_time;
+ double src_offset, src_offset_sd, src_frequency, src_frequency_sd, src_skew;
+ double src_root_delay, src_root_dispersion, sel_src_distance, elapsed;
+ double offset_weight, sum_offset_weight, sum_offset, sum2_offset_sd;
+ double frequency_weight, sum_frequency_weight, sum_frequency;
+ double inv_sum2_frequency_sd, inv_sum2_skew;
+ int i, index, combined;
+
+ if (n_sel_sources == 1)
+ return 1;
+
+ sum_offset_weight = sum_offset = sum2_offset_sd = 0.0;
+ sum_frequency_weight = sum_frequency = inv_sum2_frequency_sd = inv_sum2_skew = 0.0;
+
+ sel_src_distance = sources[selected_source_index]->sel_info.root_distance;
+ if (sources[selected_source_index]->type == SRC_NTP)
+ sel_src_distance += reselect_distance;
+
+ for (i = combined = 0; i < n_sel_sources; i++) {
+ index = sel_sources[i];
+ SST_GetTrackingData(sources[index]->stats, &src_ref_time,
+ &src_offset, &src_offset_sd,
+ &src_frequency, &src_frequency_sd, &src_skew,
+ &src_root_delay, &src_root_dispersion);
+
+ /* Don't include this source if its distance is longer than the distance of
+ the selected source multiplied by the limit, their estimated frequencies
+ are not close, or it was recently marked as distant */
+
+ if (index != selected_source_index &&
+ (sources[index]->sel_info.root_distance > combine_limit * sel_src_distance ||
+ fabs(*frequency - src_frequency) >
+ combine_limit * (*skew + src_skew + LCL_GetMaxClockError()))) {
+ /* Use a smaller penalty in first few updates */
+ sources[index]->distant = sources[index]->reachability_size >= SOURCE_REACH_BITS ?
+ DISTANT_PENALTY : 1;
+ } else if (sources[index]->distant) {
+ sources[index]->distant--;
+ }
+
+ if (sources[index]->distant) {
+ mark_source(sources[index], SRC_DISTANT);
+ continue;
+ }
+
+ if (sources[index]->status == SRC_OK)
+ mark_source(sources[index], SRC_UNSELECTED);
+
+ elapsed = UTI_DiffTimespecsToDouble(ref_time, &src_ref_time);
+ src_offset += elapsed * src_frequency;
+ src_offset_sd += elapsed * src_frequency_sd;
+ offset_weight = 1.0 / sources[index]->sel_info.root_distance;
+ frequency_weight = 1.0 / SQUARE(src_frequency_sd);
+
+ DEBUG_LOG("combining %s oweight=%e offset=%e osd=%e fweight=%e freq=%e fsd=%e skew=%e",
+ source_to_string(sources[index]), offset_weight, src_offset, src_offset_sd,
+ frequency_weight, src_frequency, src_frequency_sd, src_skew);
+
+ sum_offset_weight += offset_weight;
+ sum_offset += offset_weight * src_offset;
+ sum2_offset_sd += offset_weight * (SQUARE(src_offset_sd) +
+ SQUARE(src_offset - *offset));
+
+ sum_frequency_weight += frequency_weight;
+ sum_frequency += frequency_weight * src_frequency;
+ inv_sum2_frequency_sd += 1.0 / SQUARE(src_frequency_sd);
+ inv_sum2_skew += 1.0 / SQUARE(src_skew);
+
+ combined++;
+ }
+
+ assert(combined);
+ *offset = sum_offset / sum_offset_weight;
+ *offset_sd = sqrt(sum2_offset_sd / sum_offset_weight);
+ *frequency = sum_frequency / sum_frequency_weight;
+ *frequency_sd = 1.0 / sqrt(inv_sum2_frequency_sd);
+ *skew = 1.0 / sqrt(inv_sum2_skew);
+
+ DEBUG_LOG("combined result offset=%e osd=%e freq=%e fsd=%e skew=%e",
+ *offset, *offset_sd, *frequency, *frequency_sd, *skew);
+
+ return combined;
+}
+
+/* ================================================== */
+/* This function selects the current reference from amongst the pool
+ of sources we are holding and updates the local reference */
+
+void
+SRC_SelectSource(SRC_Instance updated_inst)
+{
+ struct SelectInfo *si;
+ struct timespec now, ref_time;
+ int i, j, j1, j2, index, sel_prefer, n_endpoints, n_sel_sources, sel_req_source;
+ int n_badstats_sources, max_sel_reach, max_sel_reach_size, max_badstat_reach;
+ int depth, best_depth, trust_depth, best_trust_depth, n_sel_trust_sources;
+ int combined, stratum, min_stratum, max_score_index;
+ int orphan_stratum, orphan_source;
+ 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;
+ double best_trust_lo, best_trust_hi;
+ double first_sample_ago, max_reach_sample_ago;
+ NTP_Leap leap_status;
+
+ if (updated_inst) {
+ updated_inst->updates++;
+ last_updated_inst = updated_inst;
+ }
+
+ if (n_sources == 0) {
+ unselect_selected_source(LOGS_INFO, "Can't synchronise: no sources", NULL);
+ return;
+ }
+
+ /* This is accurate enough and cheaper than calling LCL_ReadCookedTime */
+ SCH_GetLastEventTime(&now, NULL, NULL);
+
+ /* Step 1 - build intervals about each source */
+
+ n_endpoints = 0;
+ n_sel_sources = n_sel_trust_sources = 0;
+ n_badstats_sources = 0;
+ sel_req_source = 0;
+ max_sel_reach = max_badstat_reach = 0;
+ max_sel_reach_size = 0;
+ max_reach_sample_ago = 0.0;
+
+ for (i = 0; i < n_sources; i++) {
+ assert(sources[i]->status != SRC_OK);
+
+ /* Don't allow the source to vote on leap seconds unless it's selectable */
+ sources[i]->leap_vote = 0;
+
+ /* If some sources are specified with the require option, at least one
+ of them will have to be selectable in order to update the clock */
+ if (sources[i]->sel_options & SRC_SELECT_REQUIRE)
+ sel_req_source = 1;
+
+ /* Ignore sources which were added with the noselect option */
+ if (sources[i]->sel_options & SRC_SELECT_NOSELECT) {
+ mark_source(sources[i], SRC_UNSELECTABLE);
+ continue;
+ }
+
+ /* Ignore sources which are not synchronised */
+ if (sources[i]->leap == LEAP_Unsynchronised) {
+ mark_source(sources[i], SRC_UNSYNCHRONISED);
+ continue;
+ }
+
+ si = &sources[i]->sel_info;
+ SST_GetSelectionData(sources[i]->stats, &now,
+ &si->lo_limit, &si->hi_limit, &si->root_distance,
+ &si->std_dev, &first_sample_ago,
+ &si->last_sample_ago, &si->select_ok);
+
+ if (!si->select_ok) {
+ ++n_badstats_sources;
+ mark_source(sources[i], SRC_BAD_STATS);
+ if (max_badstat_reach < sources[i]->reachability)
+ max_badstat_reach = sources[i]->reachability;
+ continue;
+ }
+
+ /* Include extra dispersion in the root distance of sources that don't
+ have new samples (the last sample is older than span of all samples) */
+ if (first_sample_ago < 2.0 * si->last_sample_ago) {
+ double extra_disp = LCL_GetMaxClockError() *
+ (2.0 * si->last_sample_ago - first_sample_ago);
+ si->root_distance += extra_disp;
+ si->lo_limit -= extra_disp;
+ si->hi_limit += extra_disp;
+ }
+
+ /* Require the root distance to be below the allowed maximum and the
+ endpoints to be in the right order (i.e. a non-negative distance) */
+ if (!(si->root_distance <= max_distance && si->lo_limit <= si->hi_limit)) {
+ mark_source(sources[i], SRC_BAD_DISTANCE);
+ continue;
+ }
+
+ /* And the same applies for the estimated standard deviation */
+ if (si->std_dev > max_jitter) {
+ mark_source(sources[i], SRC_JITTERY);
+ continue;
+ }
+
+ sources[i]->status = SRC_OK; /* For now */
+
+ if (sources[i]->reachability && max_reach_sample_ago < first_sample_ago)
+ max_reach_sample_ago = first_sample_ago;
+
+ if (max_sel_reach < sources[i]->reachability)
+ max_sel_reach = sources[i]->reachability;
+
+ if (max_sel_reach_size < sources[i]->reachability_size)
+ max_sel_reach_size = sources[i]->reachability_size;
+ }
+
+ orphan_stratum = REF_GetOrphanStratum();
+ orphan_source = INVALID_SOURCE;
+
+ for (i = 0; i < n_sources; i++) {
+ if (sources[i]->status != SRC_OK)
+ continue;
+
+ si = &sources[i]->sel_info;
+
+ /* Reachability is not a requirement for selection. An unreachable source
+ can still be selected if its newest sample is not older than the oldest
+ sample from reachable sources. */
+ if (!sources[i]->reachability && max_reach_sample_ago < si->last_sample_ago) {
+ mark_source(sources[i], SRC_STALE);
+ continue;
+ }
+
+ /* When the local reference is configured with the orphan option, NTP
+ sources that have stratum equal to the configured local stratum are
+ considered to be orphans (i.e. serving local time while not being
+ synchronised with real time) and are excluded from the normal source
+ selection. Sources with stratum larger than the local stratum are
+ considered to be directly on indirectly synchronised to an orphan and
+ are always ignored.
+
+ If no selectable source is available and all orphan sources have
+ reference IDs larger than the local ID, no source will be selected and
+ the local reference mode will be activated at some point, i.e. this host
+ will become an orphan. Otherwise, the orphan source with the smallest
+ reference ID will be selected. This ensures a group of servers polling
+ each other (with the same orphan configuration) which have no external
+ source can settle down to a state where only one server is serving its
+ local unsychronised time and others are synchronised to it. */
+
+ if (sources[i]->stratum >= orphan_stratum && sources[i]->type == SRC_NTP) {
+ mark_source(sources[i], SRC_ORPHAN);
+
+ if (sources[i]->stratum == orphan_stratum && sources[i]->reachability &&
+ (orphan_source == INVALID_SOURCE ||
+ sources[i]->ref_id < sources[orphan_source]->ref_id))
+ orphan_source = i;
+
+ continue;
+ }
+
+ ++n_sel_sources;
+ }
+
+ /* If no selectable source is available, consider the orphan source */
+ if (!n_sel_sources && orphan_source != INVALID_SOURCE) {
+ uint32_t local_ref_id = NSR_GetLocalRefid(sources[orphan_source]->ip_addr);
+
+ if (!local_ref_id) {
+ LOG(LOGS_ERR, "Unknown local refid in orphan mode");
+ } else if (sources[orphan_source]->ref_id < local_ref_id) {
+ sources[orphan_source]->status = SRC_OK;
+ n_sel_sources = 1;
+ DEBUG_LOG("selecting orphan refid=%"PRIx32, sources[orphan_source]->ref_id);
+ }
+ }
+
+ for (i = 0; i < n_sources; i++) {
+ if (sources[i]->status != SRC_OK)
+ continue;
+
+ if (sources[i]->sel_options & SRC_SELECT_TRUST)
+ n_sel_trust_sources++;
+
+ si = &sources[i]->sel_info;
+
+ j1 = n_endpoints;
+ j2 = j1 + 1;
+
+ sort_list[j1].index = i;
+ sort_list[j1].offset = si->lo_limit;
+ sort_list[j1].tag = LOW;
+
+ sort_list[j2].index = i;
+ sort_list[j2].offset = si->hi_limit;
+ sort_list[j2].tag = HIGH;
+
+ n_endpoints += 2;
+ }
+
+ DEBUG_LOG("badstat=%d sel=%d badstat_reach=%o sel_reach=%o size=%d max_reach_ago=%f",
+ n_badstats_sources, n_sel_sources, (unsigned int)max_badstat_reach,
+ (unsigned int)max_sel_reach, max_sel_reach_size, max_reach_sample_ago);
+
+ /* Wait for the next call if we have no source selected and there is
+ a source with bad stats (has less than 3 samples) with reachability
+ equal to shifted maximum reachability of sources with valid stats.
+ This delays selecting source on start with servers using the same
+ polling interval until they all have valid stats. */
+ if (n_badstats_sources && n_sel_sources && selected_source_index == INVALID_SOURCE &&
+ max_sel_reach_size < SOURCE_REACH_BITS && max_sel_reach >> 1 == max_badstat_reach) {
+ mark_ok_sources(SRC_WAITS_STATS);
+ unselect_selected_source(LOGS_INFO, NULL, NULL);
+ return;
+ }
+
+ if (n_endpoints == 0) {
+ /* No sources provided valid endpoints */
+ unselect_selected_source(LOGS_INFO, "Can't synchronise: no selectable sources", NULL);
+ return;
+ }
+
+ /* Now sort the endpoint list */
+ qsort((void *) sort_list, n_endpoints, sizeof(struct Sort_Element), compare_sort_elements);
+
+ /* Now search for the interval which is contained in the most
+ individual source intervals. Any source which overlaps this
+ will be a candidate.
+
+ If we get a case like
+
+ <----------------------->
+ <-->
+ <-->
+ <===========>
+
+ we will build the interval as shown with '=', whereas with an extra source we get
+
+ <----------------------->
+ <------->
+ <-->
+ <-->
+ <==>
+
+ The first case is just bad luck - we need extra sources to
+ detect the falseticker, so just make an arbitrary choice based
+ on stratum & stability etc.
+
+ Intervals from sources specified with the trust option have higher
+ priority in the search.
+ */
+
+ trust_depth = best_trust_depth = 0;
+ depth = best_depth = 0;
+ best_lo = best_hi = best_trust_lo = best_trust_hi = 0.0;
+
+ for (i = 0; i < n_endpoints; i++) {
+ switch (sort_list[i].tag) {
+ case LOW:
+ depth++;
+ if (sources[sort_list[i].index]->sel_options & SRC_SELECT_TRUST)
+ trust_depth++;
+ if (trust_depth > best_trust_depth ||
+ (trust_depth == best_trust_depth && depth > best_depth)) {
+ if (trust_depth > best_trust_depth) {
+ best_trust_depth = trust_depth;
+ best_trust_lo = sort_list[i].offset;
+ }
+ best_depth = depth;
+ best_lo = sort_list[i].offset;
+ }
+ break;
+ case HIGH:
+ if (trust_depth == best_trust_depth) {
+ if (depth == best_depth)
+ best_hi = sort_list[i].offset;
+ best_trust_hi = sort_list[i].offset;
+ }
+ if (sources[sort_list[i].index]->sel_options & SRC_SELECT_TRUST)
+ trust_depth--;
+ depth--;
+ break;
+ default:
+ assert(0);
+ }
+ assert(trust_depth <= depth);
+ assert(trust_depth >= 0);
+ }
+
+ assert(depth == 0 && trust_depth == 0);
+ assert(2 * n_sel_sources == n_endpoints);
+
+ if ((best_trust_depth == 0 && best_depth <= n_sel_sources / 2) ||
+ (best_trust_depth > 0 && best_trust_depth <= n_sel_trust_sources / 2)) {
+ /* Could not even get half the reachable (trusted) sources to agree */
+
+ if (!reported_no_majority) {
+ log_selection_message(LOGS_WARN, "Can't synchronise: no majority", NULL);
+ reported_no_majority = 1;
+ report_selection_loss = 0;
+ }
+
+ if (selected_source_index != INVALID_SOURCE) {
+ REF_SetUnsynchronised();
+ selected_source_index = INVALID_SOURCE;
+ }
+
+ /* .. and mark all sources as falsetickers (so they appear thus
+ on the outputs from the command client) */
+ mark_ok_sources(SRC_FALSETICKER);
+
+ return;
+ }
+
+ /* We have our interval, now work out which source are in it,
+ i.e. build list of admissible sources. */
+
+ n_sel_sources = 0;
+
+ for (i = 0; i < n_sources; i++) {
+ /* This should be the same condition to get into the endpoint
+ list */
+ if (sources[i]->status != SRC_OK)
+ continue;
+
+ /* Check if source's interval contains the best interval, or is wholly
+ contained within it. If there are any trusted sources, other sources
+ are required to be wholly contained within the best interval of the
+ trusted sources to not allow non-trusted sources to move the final
+ offset outside the trusted interval. */
+ if ((sources[i]->sel_info.lo_limit <= best_lo &&
+ sources[i]->sel_info.hi_limit >= best_hi) ||
+ (sources[i]->sel_info.lo_limit >= best_lo &&
+ sources[i]->sel_info.hi_limit <= best_hi)) {
+
+ if (!(best_trust_depth == 0 || (sources[i]->sel_options & SRC_SELECT_TRUST) ||
+ (sources[i]->sel_info.lo_limit >= best_trust_lo &&
+ sources[i]->sel_info.hi_limit <= best_trust_hi))) {
+ mark_source(sources[i], SRC_UNTRUSTED);
+ continue;
+ }
+
+ sel_sources[n_sel_sources++] = i;
+
+ if (sources[i]->sel_options & SRC_SELECT_REQUIRE)
+ sel_req_source = 0;
+ } else {
+ mark_source(sources[i], SRC_FALSETICKER);
+ if (!sources[i]->reported_falseticker) {
+ log_selection_source(LOGS_WARN, "Detected falseticker %s", sources[i]);
+ sources[i]->reported_falseticker = 1;
+ }
+ }
+ }
+
+ if (!n_sel_sources || sel_req_source || n_sel_sources < CNF_GetMinSources()) {
+ unselect_selected_source(LOGS_INFO, "Can't synchronise: %s selectable sources",
+ !n_sel_sources ? "no" :
+ sel_req_source ? "no required source in" : "not enough");
+ mark_ok_sources(SRC_WAITS_SOURCES);
+ return;
+ }
+
+ /* 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;
+ sources[index]->leap_vote = 1;
+ }
+
+ /* 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++) {
+ if (sources[sel_sources[i]]->sel_options & SRC_SELECT_PREFER)
+ break;
+ }
+ if (i < n_sel_sources) {
+ for (i = j = 0; i < n_sel_sources; i++) {
+ if (!(sources[sel_sources[i]]->sel_options & SRC_SELECT_PREFER))
+ mark_source(sources[sel_sources[i]], SRC_NONPREFERRED);
+ else
+ sel_sources[j++] = sel_sources[i];
+ }
+ assert(j > 0);
+ n_sel_sources = j;
+ sel_prefer = 1;
+ } else {
+ sel_prefer = 0;
+ }
+
+ /* Find minimum stratum */
+
+ index = sel_sources[0];
+ min_stratum = sources[index]->stratum;
+ for (i = 1; i < n_sel_sources; i++) {
+ index = sel_sources[i];
+ stratum = sources[index]->stratum;
+ if (stratum < min_stratum)
+ min_stratum = stratum;
+ }
+
+ /* Update scores and find the source with maximum score */
+
+ max_score_index = INVALID_SOURCE;
+ max_score = 0.0;
+ sel_src_distance = 0.0;
+
+ if (selected_source_index != INVALID_SOURCE)
+ sel_src_distance = sources[selected_source_index]->sel_info.root_distance +
+ (sources[selected_source_index]->stratum - min_stratum) * stratum_weight;
+
+ for (i = 0; i < n_sources; i++) {
+ /* Reset score for non-selectable sources */
+ if (sources[i]->status != SRC_OK ||
+ (sel_prefer && !(sources[i]->sel_options & SRC_SELECT_PREFER))) {
+ sources[i]->sel_score = 1.0;
+ sources[i]->distant = DISTANT_PENALTY;
+ continue;
+ }
+
+ distance = sources[i]->sel_info.root_distance +
+ (sources[i]->stratum - min_stratum) * stratum_weight;
+ if (sources[i]->type == SRC_NTP)
+ distance += reselect_distance;
+
+ if (selected_source_index != INVALID_SOURCE) {
+ /* Update score, but only for source pairs where one source
+ has a new sample */
+ if (sources[i] == updated_inst ||
+ sources[selected_source_index] == updated_inst) {
+
+ sources[i]->sel_score *= sel_src_distance / distance;
+
+ if (sources[i]->sel_score < 1.0)
+ sources[i]->sel_score = 1.0;
+ }
+ } else {
+ /* When there is no selected source yet, assign scores so that the
+ source with minimum distance will have maximum score. The scores
+ will be reset when the source is selected later in this function. */
+ sources[i]->sel_score = 1.0 / distance;
+ }
+
+ DEBUG_LOG("%s score=%f dist=%f",
+ source_to_string(sources[i]), sources[i]->sel_score, distance);
+
+ if (max_score < sources[i]->sel_score) {
+ max_score = sources[i]->sel_score;
+ max_score_index = i;
+ }
+ }
+
+ assert(max_score_index != INVALID_SOURCE);
+
+ /* Is the current source still a survivor and no other source has reached
+ the score limit? */
+ if (selected_source_index == INVALID_SOURCE ||
+ sources[selected_source_index]->status != SRC_OK ||
+ (max_score_index != selected_source_index && max_score > SCORE_LIMIT)) {
+
+ /* Before selecting the new synchronisation source wait until the reference
+ can be updated */
+ if (sources[max_score_index]->updates == 0) {
+ unselect_selected_source(LOGS_INFO, NULL, NULL);
+ mark_ok_sources(SRC_WAITS_UPDATE);
+ return;
+ }
+
+ selected_source_index = max_score_index;
+ log_selection_source(LOGS_INFO, "Selected source %s", sources[selected_source_index]);
+
+ /* New source has been selected, reset all scores */
+ for (i = 0; i < n_sources; i++) {
+ sources[i]->sel_score = 1.0;
+ sources[i]->distant = 0;
+ sources[i]->reported_falseticker = 0;
+ }
+
+ reported_no_majority = 0;
+ report_selection_loss = 0;
+ }
+
+ mark_source(sources[selected_source_index], SRC_SELECTED);
+
+ /* Don't update reference when the selected source has no new samples */
+
+ if (sources[selected_source_index]->updates == 0) {
+ /* Mark the remaining sources as last combine_sources() call */
+
+ for (i = 0; i < n_sel_sources; i++) {
+ index = sel_sources[i];
+ if (sources[index]->status == SRC_OK)
+ mark_source(sources[index], sources[index]->distant ? SRC_DISTANT : SRC_UNSELECTED);
+ }
+ return;
+ }
+
+ 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 */
+
+ SST_GetTrackingData(sources[selected_source_index]->stats, &ref_time,
+ &src_offset, &src_offset_sd,
+ &src_frequency, &src_frequency_sd, &src_skew,
+ &src_root_delay, &src_root_dispersion);
+
+ combined = combine_sources(n_sel_sources, &ref_time, &src_offset, &src_offset_sd,
+ &src_frequency, &src_frequency_sd, &src_skew);
+
+ REF_SetReference(sources[selected_source_index]->stratum,
+ leap_status, combined,
+ sources[selected_source_index]->ref_id,
+ sources[selected_source_index]->ip_addr,
+ &ref_time, src_offset, src_offset_sd,
+ src_frequency, src_frequency_sd, src_skew,
+ src_root_delay, src_root_dispersion);
+}
+
+/* ================================================== */
+/* Force reselecting the best source */
+
+void
+SRC_ReselectSource(void)
+{
+ selected_source_index = INVALID_SOURCE;
+ SRC_SelectSource(NULL);
+}
+
+/* ================================================== */
+
+void
+SRC_SetReselectDistance(double distance)
+{
+ if (reselect_distance != distance) {
+ reselect_distance = distance;
+ LOG(LOGS_INFO, "New reselect distance %f", distance);
+ }
+}
+
+/* ================================================== */
+/* This routine is registered as a callback with the local clock
+ module, to be called whenever the local clock changes frequency or
+ is slewed. It runs through all the existing source statistics, and
+ adjusts them to make them look as though they were sampled under
+ the new regime. */
+
+static void
+slew_sources(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything)
+{
+ int i;
+
+ for (i=0; i<n_sources; i++) {
+ if (change_type == LCL_ChangeUnknownStep) {
+ SST_ResetInstance(sources[i]->stats);
+ } else {
+ SST_SlewSamples(sources[i]->stats, cooked, dfreq, doffset);
+ }
+ }
+
+ if (change_type == LCL_ChangeUnknownStep) {
+ /* Update selection status */
+ SRC_SelectSource(NULL);
+ }
+}
+
+/* ================================================== */
+/* This routine is called when an indeterminate offset is introduced
+ into the local time. */
+
+static void
+add_dispersion(double dispersion, void *anything)
+{
+ int i;
+
+ for (i = 0; i < n_sources; i++) {
+ SST_AddDispersion(sources[i]->stats, dispersion);
+ }
+}
+
+/* ================================================== */
+
+static int
+get_dumpfile(SRC_Instance inst, char *filename, size_t len)
+{
+ /* Use the IP address, or reference ID with reference clocks */
+ switch (inst->type) {
+ case SRC_NTP:
+ if (!UTI_IsIPReal(inst->ip_addr) ||
+ snprintf(filename, len, "%s", source_to_string(inst)) >= len)
+ return 0;
+ break;
+ case SRC_REFCLOCK:
+ if (snprintf(filename, len, "refid:%08"PRIx32, inst->ref_id) >= len)
+ return 0;
+ break;
+ default:
+ assert(0);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+save_source(SRC_Instance inst)
+{
+ char filename[64], *dumpdir, *ntp_name;
+ FILE *f;
+
+ dumpdir = CNF_GetDumpDir();
+ if (!dumpdir)
+ return;
+
+ if (!get_dumpfile(inst, filename, sizeof (filename)))
+ return;
+
+ f = UTI_OpenFile(dumpdir, filename, ".dat", 'w', 0644);
+ if (!f)
+ return;
+
+ ntp_name = inst->type == SRC_NTP ? NSR_GetName(inst->ip_addr) : ".";
+
+ if (fprintf(f, "%s%s\n%d %o %d %d %d\n",
+ DUMP_IDENTIFIER, ntp_name, inst->authenticated,
+ (unsigned int)inst->reachability, inst->reachability_size,
+ inst->stratum, (int)inst->leap) < 0 ||
+ !SST_SaveToFile(inst->stats, f)) {
+ fclose(f);
+ if (!UTI_RemoveFile(dumpdir, filename, ".dat"))
+ ;
+ return;
+ }
+
+ fclose(f);
+}
+
+/* ================================================== */
+/* This is called to dump out the source measurement registers */
+
+void
+SRC_DumpSources(void)
+{
+ int i;
+
+ for (i = 0; i < n_sources; i++)
+ save_source(sources[i]);
+}
+
+/* ================================================== */
+
+#define MAX_WORDS 1
+
+static void
+load_source(SRC_Instance inst)
+{
+ char filename[64], line[256], *dumpdir, *ntp_name, *words[MAX_WORDS];
+ int auth, leap, reach_size, stratum;
+ unsigned int reach;
+ FILE *f;
+
+ dumpdir = CNF_GetDumpDir();
+ if (!dumpdir)
+ return;
+
+ if (!get_dumpfile(inst, filename, sizeof (filename)))
+ return;
+
+ f = UTI_OpenFile(dumpdir, filename, ".dat", 'r', 0);
+ if (!f)
+ return;
+
+ ntp_name = inst->type == SRC_NTP ? NSR_GetName(inst->ip_addr) : NULL;
+
+ if (!fgets(line, sizeof (line), f) || strcmp(line, DUMP_IDENTIFIER) != 0 ||
+ !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 1 ||
+ (inst->type == SRC_NTP && (!ntp_name || strcmp(words[0], ntp_name) != 0)) ||
+ !fgets(line, sizeof (line), f) ||
+ sscanf(words[0], "%d %o %d %d %d",
+ &auth, &reach, &reach_size, &stratum, &leap) != 5 ||
+ (!auth && inst->authenticated) ||
+ stratum < 0 || stratum >= NTP_MAX_STRATUM ||
+ leap < LEAP_Normal || leap >= LEAP_Unsynchronised ||
+ !SST_LoadFromFile(inst->stats, f)) {
+ LOG(LOGS_WARN, "Could not load dump file for %s", source_to_string(inst));
+ fclose(f);
+ return;
+ }
+
+ inst->reachability = reach & ((1U << SOURCE_REACH_BITS) - 1);
+ inst->reachability_size = CLAMP(0, reach_size, SOURCE_REACH_BITS);
+ inst->stratum = stratum;
+ inst->leap = leap;
+
+ LOG(LOGS_INFO, "Loaded dump file for %s", source_to_string(inst));
+
+ fclose(f);
+}
+
+/* ================================================== */
+
+void
+SRC_ReloadSources(void)
+{
+ int i;
+
+ for (i = 0; i < n_sources; i++) {
+ load_source(sources[i]);
+
+ /* Allow an immediate update of the reference */
+ sources[i]->updates++;
+ }
+
+ /* Select sources and set the reference */
+ SRC_SelectSource(NULL);
+}
+
+/* ================================================== */
+
+void
+SRC_RemoveDumpFiles(void)
+{
+ char pattern[PATH_MAX], name[64], *dumpdir, *s;
+ IPAddr ip_addr;
+ glob_t gl;
+ size_t i;
+
+ dumpdir = CNF_GetDumpDir();
+ if (!dumpdir ||
+ snprintf(pattern, sizeof (pattern), "%s/*.dat", dumpdir) >= sizeof (pattern))
+ return;
+
+ if (glob(pattern, 0, NULL, &gl))
+ return;
+
+ for (i = 0; i < gl.gl_pathc; i++) {
+ s = strrchr(gl.gl_pathv[i], '/');
+ if (!s || snprintf(name, sizeof (name), "%s", s + 1) >= sizeof (name))
+ continue;
+
+ /* Remove .dat extension */
+ if (strlen(name) < 4)
+ continue;
+ name[strlen(name) - 4] = '\0';
+
+ /* Check if it looks like name of an actual dump file */
+ if (strncmp(name, "refid:", 6) && !UTI_StringToIP(name, &ip_addr))
+ continue;
+
+ if (!UTI_RemoveFile(NULL, gl.gl_pathv[i], NULL))
+ ;
+ }
+
+ globfree(&gl);
+}
+
+/* ================================================== */
+
+void
+SRC_ResetSources(void)
+{
+ int i;
+
+ for (i = 0; i < n_sources; i++)
+ SRC_ResetInstance(sources[i]);
+
+ LOG(LOGS_INFO, "Reset all sources");
+}
+
+/* ================================================== */
+
+int
+SRC_IsSyncPeer(SRC_Instance inst)
+{
+ if (inst->index == selected_source_index) {
+ return 1;
+ } else {
+ return 0;
+ }
+
+}
+
+/* ================================================== */
+
+int
+SRC_IsReachable(SRC_Instance inst)
+{
+ return inst->reachability != 0;
+}
+
+/* ================================================== */
+
+int
+SRC_ReadNumberOfSources(void)
+{
+ return n_sources;
+}
+
+/* ================================================== */
+
+int
+SRC_ActiveSources(void)
+{
+ int i, r;
+
+ for (i = r = 0; i < n_sources; i++)
+ if (sources[i]->active)
+ r++;
+
+ return r;
+}
+
+/* ================================================== */
+
+static SRC_Instance
+find_source(IPAddr *ip, uint32_t ref_id)
+{
+ int i;
+
+ for (i = 0; i < n_sources; i++) {
+ if ((ip->family != IPADDR_UNSPEC && sources[i]->type == SRC_NTP &&
+ UTI_CompareIPs(ip, sources[i]->ip_addr, NULL) == 0) ||
+ (ip->family == IPADDR_UNSPEC && sources[i]->type == SRC_REFCLOCK &&
+ ref_id == sources[i]->ref_id))
+ return sources[i];
+ }
+
+ return NULL;
+}
+
+/* ================================================== */
+
+int
+SRC_ModifySelectOptions(IPAddr *ip, uint32_t ref_id, int options, int mask)
+{
+ SRC_Instance inst;
+
+ inst = find_source(ip, ref_id);
+ if (!inst)
+ return 0;
+
+ if ((inst->conf_sel_options & mask) == options)
+ return 1;
+
+ inst->conf_sel_options = (inst->conf_sel_options & ~mask) | options;
+ LOG(LOGS_INFO, "Source %s selection options modified", source_to_string(inst));
+
+ update_sel_options();
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SRC_ReportSource(int index, RPT_SourceReport *report, struct timespec *now)
+{
+ SRC_Instance src;
+ if ((index >= n_sources) || (index < 0)) {
+ return 0;
+ } else {
+ src = sources[index];
+
+ if (src->ip_addr)
+ report->ip_addr = *src->ip_addr;
+ else {
+ /* Use refid as an address */
+ report->ip_addr.addr.in4 = src->ref_id;
+ report->ip_addr.family = IPADDR_INET4;
+ }
+
+ report->stratum = src->stratum;
+
+ switch (src->status) {
+ case SRC_FALSETICKER:
+ report->state = RPT_FALSETICKER;
+ break;
+ case SRC_JITTERY:
+ report->state = RPT_JITTERY;
+ break;
+ case SRC_WAITS_SOURCES:
+ case SRC_NONPREFERRED:
+ case SRC_WAITS_UPDATE:
+ case SRC_DISTANT:
+ case SRC_OUTLIER:
+ report->state = RPT_SELECTABLE;
+ break;
+ case SRC_UNSELECTED:
+ report->state = RPT_UNSELECTED;
+ break;
+ case SRC_SELECTED:
+ report->state = RPT_SELECTED;
+ break;
+ default:
+ report->state = RPT_NONSELECTABLE;
+ break;
+ }
+
+ report->reachability = src->reachability;
+
+ /* Call stats module to fill out estimates */
+ SST_DoSourceReport(src->stats, report, now);
+
+ return 1;
+ }
+
+}
+
+/* ================================================== */
+
+int
+SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report, struct timespec *now)
+{
+ SRC_Instance src;
+
+ if ((index >= n_sources) || (index < 0)) {
+ return 0;
+ } else {
+ src = sources[index];
+ report->ref_id = src->ref_id;
+ if (src->ip_addr)
+ report->ip_addr = *src->ip_addr;
+ else
+ report->ip_addr.family = IPADDR_UNSPEC;
+ SST_DoSourcestatsReport(src->stats, report, now);
+ return 1;
+ }
+}
+
+/* ================================================== */
+
+static char
+get_status_char(SRC_Status status)
+{
+ switch (status) {
+ case SRC_UNSELECTABLE:
+ return 'N';
+ case SRC_UNSYNCHRONISED:
+ return 's';
+ case SRC_BAD_STATS:
+ return 'M';
+ case SRC_BAD_DISTANCE:
+ return 'd';
+ case SRC_JITTERY:
+ return '~';
+ case SRC_WAITS_STATS:
+ return 'w';
+ case SRC_STALE:
+ return 'S';
+ case SRC_ORPHAN:
+ return 'O';
+ case SRC_UNTRUSTED:
+ return 'T';
+ case SRC_FALSETICKER:
+ return 'x';
+ case SRC_WAITS_SOURCES:
+ return 'W';
+ case SRC_NONPREFERRED:
+ return 'P';
+ case SRC_WAITS_UPDATE:
+ return 'U';
+ case SRC_DISTANT:
+ return 'D';
+ case SRC_OUTLIER:
+ return 'L';
+ case SRC_UNSELECTED:
+ return '+';
+ case SRC_SELECTED:
+ return '*';
+ default:
+ return '?';
+ }
+}
+
+/* ================================================== */
+
+int
+SRC_GetSelectReport(int index, RPT_SelectReport *report)
+{
+ SRC_Instance inst;
+
+ if (index >= n_sources || index < 0)
+ return 0;
+
+ inst = sources[index];
+
+ report->ref_id = inst->ref_id;
+ if (inst->ip_addr)
+ report->ip_addr = *inst->ip_addr;
+ else
+ report->ip_addr.family = IPADDR_UNSPEC;
+ report->state_char = get_status_char(inst->status);
+ report->authentication = inst->authenticated;
+ report->leap = inst->leap;
+ report->conf_options = inst->conf_sel_options;
+ report->eff_options = inst->sel_options;
+ report->last_sample_ago = inst->sel_info.last_sample_ago;
+ report->score = inst->sel_score;
+ report->lo_limit = inst->sel_info.lo_limit;
+ report->hi_limit = inst->sel_info.hi_limit;
+
+ return 1;
+}
+
+/* ================================================== */
+
+SRC_Type
+SRC_GetType(int index)
+{
+ if ((index >= n_sources) || (index < 0))
+ return -1;
+ return sources[index]->type;
+}
+
+/* ================================================== */
diff --git a/sources.h b/sources.h
new file mode 100644
index 0000000..da3a533
--- /dev/null
+++ b/sources.h
@@ -0,0 +1,144 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 for the module that manages the collection of all
+ sources that we are making measurements from. This include all NTP
+ servers & peers, locally connected reference sources, eye/wristwatch
+ drivers etc */
+
+#ifndef GOT_SOURCES_H
+#define GOT_SOURCES_H
+
+#include "sysincl.h"
+
+#include "ntp.h"
+#include "reports.h"
+#include "sourcestats.h"
+
+/* Size of the source reachability register */
+#define SOURCE_REACH_BITS 8
+
+/* This datatype is used to hold information about sources. The
+ instance must be passed when calling many of the interface
+ functions */
+
+typedef struct SRC_Instance_Record *SRC_Instance;
+
+/* Initialisation function */
+extern void SRC_Initialise(void);
+
+/* Finalisation function */
+extern void SRC_Finalise(void);
+
+/* Modes for selecting NTP sources based on their authentication status */
+typedef enum {
+ SRC_AUTHSELECT_IGNORE,
+ SRC_AUTHSELECT_MIX,
+ SRC_AUTHSELECT_PREFER,
+ SRC_AUTHSELECT_REQUIRE,
+} SRC_AuthSelectMode;
+
+typedef enum {
+ SRC_NTP, /* NTP client/peer */
+ SRC_REFCLOCK /* Rerefence clock */
+} SRC_Type;
+
+/* Function to create a new instance. This would be called by one of
+ the individual source-type instance creation routines. */
+
+extern SRC_Instance SRC_CreateNewInstance(uint32_t ref_id, SRC_Type type, int authenticated,
+ int sel_options, IPAddr *addr, int min_samples,
+ int max_samples, double min_delay, double asymmetry);
+
+/* Function to get rid of a source when it is being unconfigured.
+ This may cause the current reference source to be reselected, if this
+ was the reference source or contributed significantly to a
+ falseticker decision. */
+
+extern void SRC_DestroyInstance(SRC_Instance instance);
+
+/* Function to reset a source */
+extern void SRC_ResetInstance(SRC_Instance instance);
+
+/* Function to change the sources's reference ID and IP address */
+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);
+
+/* Function to update the stratum and leap status of the source */
+extern void SRC_UpdateStatus(SRC_Instance instance, int stratum, 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 */
+extern void SRC_SetActive(SRC_Instance inst);
+
+/* This routine sets the source as not receiving reachability updates */
+extern void SRC_UnsetActive(SRC_Instance inst);
+
+/* This routine updates the reachability register */
+extern void SRC_UpdateReachability(SRC_Instance inst, int reachable);
+
+/* This routine marks the source unreachable */
+extern void SRC_ResetReachability(SRC_Instance inst);
+
+/* This routine is used to select the best source from amongst those
+ we currently have valid data on, and use it as the tracking base
+ for the local time. Updates are made to the local reference only
+ when the selected source was updated (set as updated_inst) since
+ the last reference update. This avoids updating the frequency
+ tracking for every sample from other sources - only the ones from
+ the selected reference make a difference. */
+extern void SRC_SelectSource(SRC_Instance updated_inst);
+
+/* Force reselecting the best source */
+extern void SRC_ReselectSource(void);
+
+/* Set reselect distance */
+extern void SRC_SetReselectDistance(double distance);
+
+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);
+extern int SRC_ActiveSources(void);
+
+/* Modify selection options of an NTP source specified by address, or
+ refclock specified by its reference ID */
+extern int SRC_ModifySelectOptions(IPAddr *ip, uint32_t ref_id, int options, int mask);
+
+extern int SRC_ReportSource(int index, RPT_SourceReport *report, struct timespec *now);
+extern int SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report, struct timespec *now);
+extern int SRC_GetSelectReport(int index, RPT_SelectReport *report);
+
+extern SRC_Type SRC_GetType(int index);
+
+#endif /* GOT_SOURCES_H */
diff --git a/sourcestats.c b/sourcestats.c
new file mode 100644
index 0000000..ce326e9
--- /dev/null
+++ b/sourcestats.c
@@ -0,0 +1,1041 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2011-2014, 2016-2018, 2021
+ *
+ * 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 file contains the routines that do the statistical
+ analysis on the samples obtained from the sources,
+ to determined frequencies and error bounds. */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "sourcestats.h"
+#include "memory.h"
+#include "regress.h"
+#include "util.h"
+#include "conf.h"
+#include "logging.h"
+#include "local.h"
+
+/* ================================================== */
+/* Define the maxumum number of samples that we want
+ to store per source */
+#define MAX_SAMPLES 64
+
+/* This is the assumed worst case bound on an unknown frequency,
+ 2000ppm, which would be pretty bad */
+#define WORST_CASE_FREQ_BOUND (2000.0/1.0e6)
+
+/* The minimum and maximum assumed skew */
+#define MIN_SKEW 1.0e-12
+#define MAX_SKEW 1.0e+02
+
+/* 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
+
+/* The minimum estimated asymmetry that can activate the offset correction */
+#define MIN_ASYMMETRY 0.45
+
+/* The minimum number of consecutive asymmetries with the same sign needed
+ to activate the offset correction */
+#define MIN_ASYMMETRY_RUN 10
+
+/* The maximum value of the counter */
+#define MAX_ASYMMETRY_RUN 1000
+
+/* ================================================== */
+
+static LOG_FileID logfileid;
+
+/* ================================================== */
+/* This data structure is used to hold the history of data from the
+ source */
+
+struct SST_Stats_Record {
+
+ /* Reference ID and IP address (NULL if not an NTP source) */
+ uint32_t refid;
+ IPAddr *ip_addr;
+
+ /* User defined minimum and maximum number of samples */
+ int min_samples;
+ int max_samples;
+
+ /* User defined minimum delay */
+ double fixed_min_delay;
+
+ /* User defined asymmetry of network jitter */
+ double fixed_asymmetry;
+
+ /* Number of samples currently stored. The samples are stored in circular
+ buffer. */
+ int n_samples;
+
+ /* Number of extra samples stored in sample_times, offsets and peer_delays
+ arrays that are used to extend the runs test */
+ int runs_samples;
+
+ /* The index of the newest sample */
+ int last_sample;
+
+ /* Flag indicating whether last regression was successful */
+ int regression_ok;
+
+ /* The best individual sample that we are holding, in terms of the minimum
+ root distance at the present time */
+ int best_single_sample;
+
+ /* The index of the sample with minimum delay in peer_delays */
+ int min_delay_sample;
+
+ /* This is the estimated offset (+ve => local fast) at a particular time */
+ double estimated_offset;
+ double estimated_offset_sd;
+ struct timespec offset_time;
+
+ /* Number of runs of the same sign amongst the residuals */
+ int nruns;
+
+ /* Number of consecutive estimated asymmetries with the same sign.
+ The sign of the number encodes the sign of the asymmetry. */
+ int asymmetry_run;
+
+ /* This is the latest estimated asymmetry of network jitter */
+ double asymmetry;
+
+ /* This value contains the estimated frequency. This is the number
+ of seconds that the local clock gains relative to the reference
+ source per unit local time. (Positive => local clock fast,
+ negative => local clock slow) */
+ double estimated_frequency;
+ double estimated_frequency_sd;
+
+ /* This is the assumed worst case bounds on the estimated frequency.
+ We assume that the true frequency lies within +/- half this much
+ about estimated_frequency */
+ double skew;
+
+ /* This is the estimated standard deviation of the data points */
+ double std_dev;
+
+ /* This array contains the sample epochs, in terms of the local
+ clock. */
+ struct timespec sample_times[MAX_SAMPLES * REGRESS_RUNS_RATIO];
+
+ /* This is an array of offsets, in seconds, corresponding to the
+ sample times. In this module, we use the convention that
+ positive means the local clock is FAST of the source and negative
+ means it is SLOW. This is contrary to the convention in the NTP
+ stuff. */
+ double offsets[MAX_SAMPLES * REGRESS_RUNS_RATIO];
+
+ /* This is an array of the offsets as originally measured. Local
+ clock fast of real time is indicated by positive values. This
+ array is not slewed to adjust the readings when we apply
+ adjustments to the local clock, as is done for the array
+ 'offset'. */
+ double orig_offsets[MAX_SAMPLES];
+
+ /* This is an array of peer delays, in seconds, being the roundtrip
+ measurement delay to the peer */
+ double peer_delays[MAX_SAMPLES * REGRESS_RUNS_RATIO];
+
+ /* This is an array of peer dispersions, being the skew and local
+ precision dispersion terms from sampling the peer */
+ double peer_dispersions[MAX_SAMPLES];
+
+ /* This array contains the root delays of each sample, in seconds */
+ double root_delays[MAX_SAMPLES];
+
+ /* This array contains the root dispersions of each sample at the
+ time of the measurements */
+ double root_dispersions[MAX_SAMPLES];
+};
+
+/* ================================================== */
+
+static void find_min_delay_sample(SST_Stats inst);
+static int get_buf_index(SST_Stats inst, int i);
+
+/* ================================================== */
+
+void
+SST_Initialise(void)
+{
+ logfileid = CNF_GetLogStatistics() ? LOG_FileOpen("statistics",
+ " Date (UTC) Time IP Address Std dev'n Est offset Offset sd Diff freq Est skew Stress Ns Bs Nr Asym")
+ : -1;
+}
+
+/* ================================================== */
+
+void
+SST_Finalise(void)
+{
+}
+
+/* ================================================== */
+/* This function creates a new instance of the statistics handler */
+
+SST_Stats
+SST_CreateInstance(uint32_t refid, IPAddr *addr, int min_samples, int max_samples,
+ double min_delay, double asymmetry)
+{
+ SST_Stats inst;
+ inst = MallocNew(struct SST_Stats_Record);
+
+ inst->max_samples = max_samples > 0 ? CLAMP(1, max_samples, MAX_SAMPLES) : MAX_SAMPLES;
+ inst->min_samples = CLAMP(1, min_samples, inst->max_samples);
+ inst->fixed_min_delay = min_delay;
+ inst->fixed_asymmetry = asymmetry;
+
+ SST_SetRefid(inst, refid, addr);
+ SST_ResetInstance(inst);
+
+ return inst;
+}
+
+/* ================================================== */
+/* This function deletes an instance of the statistics handler. */
+
+void
+SST_DeleteInstance(SST_Stats inst)
+{
+ Free(inst);
+}
+
+/* ================================================== */
+
+void
+SST_ResetInstance(SST_Stats inst)
+{
+ inst->n_samples = 0;
+ inst->runs_samples = 0;
+ inst->last_sample = 0;
+ inst->regression_ok = 0;
+ inst->best_single_sample = 0;
+ inst->min_delay_sample = 0;
+ inst->estimated_frequency = 0;
+ inst->estimated_frequency_sd = WORST_CASE_FREQ_BOUND;
+ inst->skew = WORST_CASE_FREQ_BOUND;
+ inst->estimated_offset = 0.0;
+ inst->estimated_offset_sd = WORST_CASE_STDDEV_BOUND;
+ UTI_ZeroTimespec(&inst->offset_time);
+ inst->std_dev = WORST_CASE_STDDEV_BOUND;
+ inst->nruns = 0;
+ inst->asymmetry_run = 0;
+ inst->asymmetry = 0.0;
+}
+
+/* ================================================== */
+
+void
+SST_SetRefid(SST_Stats inst, uint32_t refid, IPAddr *addr)
+{
+ inst->refid = refid;
+ inst->ip_addr = addr;
+}
+
+/* ================================================== */
+/* This function is called to prune the register down when it is full.
+ For now, just discard the oldest sample. */
+
+static void
+prune_register(SST_Stats inst, int new_oldest)
+{
+ if (!new_oldest)
+ return;
+
+ assert(inst->n_samples >= new_oldest);
+ inst->n_samples -= new_oldest;
+ inst->runs_samples += new_oldest;
+ if (inst->runs_samples > inst->n_samples * (REGRESS_RUNS_RATIO - 1))
+ inst->runs_samples = inst->n_samples * (REGRESS_RUNS_RATIO - 1);
+
+ assert(inst->n_samples + inst->runs_samples <= MAX_SAMPLES * REGRESS_RUNS_RATIO);
+
+ find_min_delay_sample(inst);
+}
+
+/* ================================================== */
+
+void
+SST_AccumulateSample(SST_Stats inst, NTP_Sample *sample)
+{
+ int n, m;
+
+ /* Make room for the new sample */
+ if (inst->n_samples > 0 &&
+ (inst->n_samples == MAX_SAMPLES || inst->n_samples == inst->max_samples)) {
+ prune_register(inst, 1);
+ }
+
+ /* Make sure it's newer than the last sample */
+ if (inst->n_samples &&
+ UTI_CompareTimespecs(&inst->sample_times[inst->last_sample], &sample->time) >= 0) {
+ LOG(LOGS_WARN, "Out of order sample detected, discarding history for %s",
+ inst->ip_addr ? UTI_IPToString(inst->ip_addr) : UTI_RefidToString(inst->refid));
+ SST_ResetInstance(inst);
+ }
+
+ n = inst->last_sample = (inst->last_sample + 1) %
+ (MAX_SAMPLES * REGRESS_RUNS_RATIO);
+ m = n % MAX_SAMPLES;
+
+ /* WE HAVE TO NEGATE OFFSET IN THIS CALL, IT IS HERE THAT THE SENSE OF OFFSET
+ IS FLIPPED */
+ inst->sample_times[n] = sample->time;
+ inst->offsets[n] = -sample->offset;
+ inst->orig_offsets[m] = -sample->offset;
+ inst->peer_delays[n] = sample->peer_delay;
+ inst->peer_dispersions[m] = sample->peer_dispersion;
+ inst->root_delays[m] = sample->root_delay;
+ inst->root_dispersions[m] = sample->root_dispersion;
+
+ if (inst->peer_delays[n] < inst->fixed_min_delay)
+ inst->peer_delays[n] = 2.0 * inst->fixed_min_delay - inst->peer_delays[n];
+
+ if (!inst->n_samples || inst->peer_delays[n] < inst->peer_delays[inst->min_delay_sample])
+ inst->min_delay_sample = n;
+
+ ++inst->n_samples;
+}
+
+/* ================================================== */
+/* Return index of the i-th sample in the sample_times and offset buffers,
+ i can be negative down to -runs_samples */
+
+static int
+get_runsbuf_index(SST_Stats inst, int i)
+{
+ return (unsigned int)(inst->last_sample + 2 * MAX_SAMPLES * REGRESS_RUNS_RATIO -
+ inst->n_samples + i + 1) % (MAX_SAMPLES * REGRESS_RUNS_RATIO);
+}
+
+/* ================================================== */
+/* Return index of the i-th sample in the other buffers */
+
+static int
+get_buf_index(SST_Stats inst, int i)
+{
+ return (unsigned int)(inst->last_sample + MAX_SAMPLES * REGRESS_RUNS_RATIO -
+ inst->n_samples + i + 1) % MAX_SAMPLES;
+}
+
+/* ================================================== */
+/* This function is used by both the regression routines to find the
+ time interval between each historical sample and the most recent
+ one */
+
+static void
+convert_to_intervals(SST_Stats inst, double *times_back)
+{
+ struct timespec *ts;
+ int i;
+
+ ts = &inst->sample_times[inst->last_sample];
+ for (i = -inst->runs_samples; i < inst->n_samples; i++) {
+ /* The entries in times_back[] should end up negative */
+ times_back[i] = UTI_DiffTimespecsToDouble(&inst->sample_times[get_runsbuf_index(inst, i)], ts);
+ }
+}
+
+/* ================================================== */
+
+static void
+find_best_sample_index(SST_Stats inst, double *times_back)
+{
+ /* With the value of skew that has been computed, see which of the
+ samples offers the tightest bound on root distance */
+
+ double root_distance, best_root_distance;
+ double elapsed;
+ int i, j, best_index;
+
+ if (!inst->n_samples)
+ return;
+
+ best_index = -1;
+ best_root_distance = DBL_MAX;
+
+ for (i = 0; i < inst->n_samples; i++) {
+ j = get_buf_index(inst, i);
+
+ elapsed = -times_back[i];
+ assert(elapsed >= 0.0);
+
+ root_distance = inst->root_dispersions[j] + elapsed * inst->skew + 0.5 * inst->root_delays[j];
+ if (root_distance < best_root_distance) {
+ best_root_distance = root_distance;
+ best_index = i;
+ }
+ }
+
+ assert(best_index >= 0);
+ inst->best_single_sample = best_index;
+}
+
+/* ================================================== */
+
+static void
+find_min_delay_sample(SST_Stats inst)
+{
+ int i, index;
+
+ inst->min_delay_sample = get_runsbuf_index(inst, -inst->runs_samples);
+
+ for (i = -inst->runs_samples + 1; i < inst->n_samples; i++) {
+ index = get_runsbuf_index(inst, i);
+ if (inst->peer_delays[index] < inst->peer_delays[inst->min_delay_sample])
+ inst->min_delay_sample = index;
+ }
+}
+
+/* ================================================== */
+/* This function estimates asymmetry of network jitter on the path to the
+ source as a slope of offset against network delay in multiple linear
+ regression. If the asymmetry is significant and its sign doesn't change
+ frequently, the measured offsets (which are used later to estimate the
+ offset and frequency of the clock) are corrected to correspond to the
+ minimum network delay. This can significantly improve the accuracy and
+ stability of the estimated offset and frequency. */
+
+static int
+estimate_asymmetry(double *times_back, double *offsets, double *delays, int n,
+ double *asymmetry, int *asymmetry_run)
+{
+ double a;
+
+ /* Reset the counter when the regression fails or the sign changes */
+ if (!RGR_MultipleRegress(times_back, delays, offsets, n, &a) ||
+ a * *asymmetry_run < 0.0) {
+ *asymmetry = 0;
+ *asymmetry_run = 0.0;
+ return 0;
+ }
+
+ if (a <= -MIN_ASYMMETRY && *asymmetry_run > -MAX_ASYMMETRY_RUN)
+ (*asymmetry_run)--;
+ else if (a >= MIN_ASYMMETRY && *asymmetry_run < MAX_ASYMMETRY_RUN)
+ (*asymmetry_run)++;
+
+ if (abs(*asymmetry_run) < MIN_ASYMMETRY_RUN)
+ return 0;
+
+ *asymmetry = CLAMP(-MAX_ASYMMETRY, a, MAX_ASYMMETRY);
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+correct_asymmetry(SST_Stats inst, double *times_back, double *offsets)
+{
+ double min_delay, delays[MAX_SAMPLES * REGRESS_RUNS_RATIO];
+ int i, n;
+
+ /* Check if the asymmetry was not specified to be zero */
+ if (inst->fixed_asymmetry == 0.0)
+ return;
+
+ min_delay = SST_MinRoundTripDelay(inst);
+ n = inst->runs_samples + inst->n_samples;
+
+ for (i = 0; i < n; i++)
+ delays[i] = inst->peer_delays[get_runsbuf_index(inst, i - inst->runs_samples)] -
+ min_delay;
+
+ if (fabs(inst->fixed_asymmetry) <= MAX_ASYMMETRY) {
+ inst->asymmetry = inst->fixed_asymmetry;
+ } else {
+ if (!estimate_asymmetry(times_back, offsets, delays, n,
+ &inst->asymmetry, &inst->asymmetry_run))
+ return;
+ }
+
+ /* Correct the offsets */
+ for (i = 0; i < n; i++)
+ offsets[i] -= inst->asymmetry * delays[i];
+}
+
+/* ================================================== */
+
+/* This defines the assumed ratio between the standard deviation of
+ the samples and the peer distance as measured from the round trip
+ time. E.g. a value of 4 means that we think the standard deviation
+ is four times the fluctuation of the peer distance */
+
+#define SD_TO_DIST_RATIO 0.7
+
+/* ================================================== */
+/* This function runs the linear regression operation on the data. It
+ finds the set of most recent samples that give the tightest
+ confidence interval for the frequency, and truncates the register
+ down to that number of samples */
+
+void
+SST_DoNewRegression(SST_Stats inst)
+{
+ double times_back[MAX_SAMPLES * REGRESS_RUNS_RATIO];
+ double offsets[MAX_SAMPLES * REGRESS_RUNS_RATIO];
+ double peer_distances[MAX_SAMPLES];
+ double weights[MAX_SAMPLES];
+
+ int degrees_of_freedom;
+ int best_start, times_back_start;
+ double est_intercept, est_slope, est_var, est_intercept_sd, est_slope_sd;
+ int i, j, nruns;
+ double min_distance, median_distance;
+ double sd_weight, sd;
+ double old_skew, old_freq, stress;
+ double precision;
+
+ convert_to_intervals(inst, times_back + inst->runs_samples);
+
+ if (inst->n_samples > 0) {
+ for (i = -inst->runs_samples; i < inst->n_samples; i++) {
+ offsets[i + inst->runs_samples] = inst->offsets[get_runsbuf_index(inst, i)];
+ }
+
+ for (i = 0, min_distance = DBL_MAX; i < inst->n_samples; i++) {
+ j = get_buf_index(inst, i);
+ peer_distances[i] = 0.5 * inst->peer_delays[get_runsbuf_index(inst, i)] +
+ inst->peer_dispersions[j];
+ if (peer_distances[i] < min_distance) {
+ min_distance = peer_distances[i];
+ }
+ }
+
+ /* And now, work out the weight vector */
+
+ precision = LCL_GetSysPrecisionAsQuantum();
+ median_distance = RGR_FindMedian(peer_distances, inst->n_samples);
+
+ sd = (median_distance - min_distance) / SD_TO_DIST_RATIO;
+ sd = CLAMP(precision, sd, min_distance);
+ min_distance += precision;
+
+ for (i=0; i<inst->n_samples; i++) {
+ sd_weight = 1.0;
+ if (peer_distances[i] > min_distance)
+ sd_weight += (peer_distances[i] - min_distance) / sd;
+ weights[i] = SQUARE(sd_weight);
+ }
+ }
+
+ correct_asymmetry(inst, times_back, offsets);
+
+ inst->regression_ok = RGR_FindBestRegression(times_back + inst->runs_samples,
+ offsets + inst->runs_samples, weights,
+ inst->n_samples, inst->runs_samples,
+ inst->min_samples,
+ &est_intercept, &est_slope, &est_var,
+ &est_intercept_sd, &est_slope_sd,
+ &best_start, &nruns, &degrees_of_freedom);
+
+ if (inst->regression_ok) {
+
+ old_skew = inst->skew;
+ old_freq = inst->estimated_frequency;
+
+ inst->estimated_frequency = est_slope;
+ inst->estimated_frequency_sd = CLAMP(MIN_SKEW, est_slope_sd, MAX_SKEW);
+ inst->skew = est_slope_sd * RGR_GetTCoef(degrees_of_freedom);
+ inst->estimated_offset = est_intercept;
+ inst->offset_time = inst->sample_times[inst->last_sample];
+ inst->estimated_offset_sd = est_intercept_sd;
+ inst->std_dev = MAX(MIN_STDDEV, sqrt(est_var));
+ inst->nruns = nruns;
+
+ inst->skew = CLAMP(MIN_SKEW, inst->skew, MAX_SKEW);
+ stress = fabs(old_freq - inst->estimated_frequency) / old_skew;
+
+ DEBUG_LOG("off=%e freq=%e skew=%e n=%d bs=%d runs=%d asym=%f arun=%d",
+ inst->estimated_offset, inst->estimated_frequency, inst->skew,
+ inst->n_samples, best_start, inst->nruns,
+ inst->asymmetry, inst->asymmetry_run);
+
+ if (logfileid != -1) {
+ LOG_FileWrite(logfileid, "%s %-15s %10.3e %10.3e %10.3e %10.3e %10.3e %7.1e %3d %3d %3d %5.2f",
+ UTI_TimeToLogForm(inst->offset_time.tv_sec),
+ inst->ip_addr ? UTI_IPToString(inst->ip_addr) : UTI_RefidToString(inst->refid),
+ inst->std_dev,
+ inst->estimated_offset, inst->estimated_offset_sd,
+ inst->estimated_frequency, inst->skew, stress,
+ inst->n_samples, best_start, inst->nruns,
+ inst->asymmetry);
+ }
+
+ times_back_start = inst->runs_samples + best_start;
+ prune_register(inst, best_start);
+ } else {
+ 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;
+ }
+
+ find_best_sample_index(inst, times_back + times_back_start);
+
+}
+
+/* ================================================== */
+/* Return the assumed worst case range of values that this source's
+ frequency lies within. Frequency is defined as the amount of time
+ the local clock gains relative to the source per unit local clock
+ time. */
+void
+SST_GetFrequencyRange(SST_Stats inst,
+ double *lo, double *hi)
+{
+ double freq, skew;
+ freq = inst->estimated_frequency;
+ skew = inst->skew;
+ *lo = freq - skew;
+ *hi = freq + skew;
+
+ /* This function is currently used only to determine the values of delta
+ and epsilon in the ntp_core module. Limit the skew to a reasonable maximum
+ to avoid failing the dispersion test too easily. */
+ if (skew > WORST_CASE_FREQ_BOUND) {
+ *lo = -WORST_CASE_FREQ_BOUND;
+ *hi = WORST_CASE_FREQ_BOUND;
+ }
+}
+
+/* ================================================== */
+
+void
+SST_GetSelectionData(SST_Stats inst, struct timespec *now,
+ double *offset_lo_limit,
+ double *offset_hi_limit,
+ double *root_distance,
+ double *std_dev,
+ double *first_sample_ago,
+ double *last_sample_ago,
+ int *select_ok)
+{
+ double offset, sample_elapsed;
+ int i, j;
+
+ if (!inst->n_samples) {
+ *select_ok = 0;
+ return;
+ }
+
+ i = get_runsbuf_index(inst, inst->best_single_sample);
+ j = get_buf_index(inst, inst->best_single_sample);
+
+ *std_dev = inst->std_dev;
+
+ sample_elapsed = fabs(UTI_DiffTimespecsToDouble(now, &inst->sample_times[i]));
+ offset = inst->offsets[i] + sample_elapsed * inst->estimated_frequency;
+ *root_distance = 0.5 * inst->root_delays[j] +
+ inst->root_dispersions[j] + sample_elapsed * inst->skew;
+
+ *offset_lo_limit = offset - *root_distance;
+ *offset_hi_limit = offset + *root_distance;
+
+#if 0
+ double average_offset, elapsed;
+ int average_ok;
+ /* average_ok ignored for now */
+ elapsed = UTI_DiffTimespecsToDouble(now, &inst->offset_time);
+ average_offset = inst->estimated_offset + inst->estimated_frequency * elapsed;
+ if (fabs(average_offset - offset) <=
+ inst->peer_dispersions[j] + 0.5 * inst->peer_delays[i]) {
+ average_ok = 1;
+ } else {
+ average_ok = 0;
+ }
+#endif
+
+ i = get_runsbuf_index(inst, 0);
+ *first_sample_ago = UTI_DiffTimespecsToDouble(now, &inst->sample_times[i]);
+ i = get_runsbuf_index(inst, inst->n_samples - 1);
+ *last_sample_ago = UTI_DiffTimespecsToDouble(now, &inst->sample_times[i]);
+
+ *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 < MIN_SAMPLES_FOR_REGRESS &&
+ 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);
+}
+
+/* ================================================== */
+
+void
+SST_GetTrackingData(SST_Stats inst, struct timespec *ref_time,
+ double *average_offset, double *offset_sd,
+ double *frequency, double *frequency_sd, double *skew,
+ double *root_delay, double *root_dispersion)
+{
+ int i, j;
+ double elapsed_sample;
+
+ assert(inst->n_samples > 0);
+
+ i = get_runsbuf_index(inst, inst->best_single_sample);
+ j = get_buf_index(inst, inst->best_single_sample);
+
+ *ref_time = inst->offset_time;
+ *average_offset = inst->estimated_offset;
+ *offset_sd = inst->estimated_offset_sd;
+ *frequency = inst->estimated_frequency;
+ *frequency_sd = inst->estimated_frequency_sd;
+ *skew = inst->skew;
+ *root_delay = inst->root_delays[j];
+
+ elapsed_sample = UTI_DiffTimespecsToDouble(&inst->offset_time, &inst->sample_times[i]);
+ *root_dispersion = inst->root_dispersions[j] + inst->skew * elapsed_sample + *offset_sd;
+
+ DEBUG_LOG("n=%d off=%f offsd=%f freq=%e freqsd=%e skew=%e delay=%f disp=%f",
+ inst->n_samples, *average_offset, *offset_sd,
+ *frequency, *frequency_sd, *skew, *root_delay, *root_dispersion);
+}
+
+/* ================================================== */
+
+void
+SST_SlewSamples(SST_Stats inst, struct timespec *when, double dfreq, double doffset)
+{
+ int m, i;
+ double delta_time;
+ struct timespec *sample, prev;
+ double prev_offset, prev_freq;
+
+ if (!inst->n_samples)
+ return;
+
+ for (m = -inst->runs_samples; m < inst->n_samples; m++) {
+ i = get_runsbuf_index(inst, m);
+ sample = &inst->sample_times[i];
+ prev = *sample;
+ UTI_AdjustTimespec(sample, when, sample, &delta_time, dfreq, doffset);
+ inst->offsets[i] += delta_time;
+ }
+
+ /* Update the regression estimates */
+ prev = inst->offset_time;
+ prev_offset = inst->estimated_offset;
+ prev_freq = inst->estimated_frequency;
+ UTI_AdjustTimespec(&inst->offset_time, when, &inst->offset_time,
+ &delta_time, dfreq, doffset);
+ inst->estimated_offset += delta_time;
+ inst->estimated_frequency = (inst->estimated_frequency - dfreq) / (1.0 - dfreq);
+
+ DEBUG_LOG("n=%d m=%d old_off_time=%s new=%s old_off=%f new_off=%f old_freq=%.3f new_freq=%.3f",
+ inst->n_samples, inst->runs_samples,
+ UTI_TimespecToString(&prev), UTI_TimespecToString(&inst->offset_time),
+ prev_offset, inst->estimated_offset,
+ 1.0e6 * prev_freq, 1.0e6 * inst->estimated_frequency);
+}
+
+/* ================================================== */
+
+void
+SST_CorrectOffset(SST_Stats inst, double doffset)
+{
+ int i;
+
+ if (!inst->n_samples)
+ return;
+
+ for (i = -inst->runs_samples; i < inst->n_samples; i++)
+ inst->offsets[get_runsbuf_index(inst, i)] += doffset;
+
+ inst->estimated_offset += doffset;
+}
+
+/* ================================================== */
+
+void
+SST_AddDispersion(SST_Stats inst, double dispersion)
+{
+ int m, i;
+
+ for (m = 0; m < inst->n_samples; m++) {
+ i = get_buf_index(inst, m);
+ inst->root_dispersions[i] += dispersion;
+ inst->peer_dispersions[i] += dispersion;
+ }
+}
+
+/* ================================================== */
+
+double
+SST_PredictOffset(SST_Stats inst, struct timespec *when)
+{
+ double elapsed;
+
+ if (inst->n_samples < MIN_SAMPLES_FOR_REGRESS) {
+ /* We don't have any useful statistics, and presumably the poll
+ interval is minimal. We can't do any useful prediction other
+ than use the latest sample or zero if we don't have any samples */
+ if (inst->n_samples > 0) {
+ return inst->offsets[inst->last_sample];
+ } else {
+ return 0.0;
+ }
+ } else {
+ elapsed = UTI_DiffTimespecsToDouble(when, &inst->offset_time);
+ return inst->estimated_offset + elapsed * inst->estimated_frequency;
+ }
+
+}
+
+/* ================================================== */
+
+double
+SST_MinRoundTripDelay(SST_Stats inst)
+{
+ if (inst->fixed_min_delay > 0.0)
+ return inst->fixed_min_delay;
+
+ if (!inst->n_samples)
+ return DBL_MAX;
+
+ return inst->peer_delays[inst->min_delay_sample];
+}
+
+/* ================================================== */
+
+int
+SST_GetDelayTestData(SST_Stats inst, struct timespec *sample_time,
+ double *last_sample_ago, double *predicted_offset,
+ double *min_delay, double *skew, double *std_dev)
+{
+ if (inst->n_samples < 6)
+ return 0;
+
+ *last_sample_ago = UTI_DiffTimespecsToDouble(sample_time, &inst->offset_time);
+ *predicted_offset = inst->estimated_offset +
+ *last_sample_ago * inst->estimated_frequency;
+ *min_delay = SST_MinRoundTripDelay(inst);
+ *skew = inst->skew;
+ *std_dev = inst->std_dev;
+
+ return 1;
+}
+
+/* ================================================== */
+/* This is used to save the register to a file, so that we can reload
+ it after restarting the daemon */
+
+int
+SST_SaveToFile(SST_Stats inst, FILE *out)
+{
+ int m, i, j;
+
+ if (inst->n_samples < 1)
+ return 0;
+
+ if (fprintf(out, "%d %d\n", inst->n_samples, inst->asymmetry_run) < 0)
+ return 0;
+
+ for(m = 0; m < inst->n_samples; m++) {
+ i = get_runsbuf_index(inst, m);
+ j = get_buf_index(inst, m);
+
+ if (fprintf(out, "%s %.6e %.6e %.6e %.6e %.6e %.6e\n",
+ UTI_TimespecToString(&inst->sample_times[i]),
+ inst->offsets[i], inst->orig_offsets[j],
+ inst->peer_delays[i], inst->peer_dispersions[j],
+ inst->root_delays[j], inst->root_dispersions[j]) < 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+/* This is used to reload samples from a file */
+
+int
+SST_LoadFromFile(SST_Stats inst, FILE *in)
+{
+ int i, n_samples, arun;
+ struct timespec now;
+ double sample_time;
+ char line[256];
+
+ if (!fgets(line, sizeof (line), in) ||
+ sscanf(line, "%d %d", &n_samples, &arun) != 2 ||
+ n_samples < 1 || n_samples > MAX_SAMPLES)
+ return 0;
+
+ SST_ResetInstance(inst);
+
+ LCL_ReadCookedTime(&now, NULL);
+
+ for (i = 0; i < n_samples; i++) {
+ if (!fgets(line, sizeof (line), in) ||
+ sscanf(line, "%lf %lf %lf %lf %lf %lf %lf",
+ &sample_time, &inst->offsets[i], &inst->orig_offsets[i],
+ &inst->peer_delays[i], &inst->peer_dispersions[i],
+ &inst->root_delays[i], &inst->root_dispersions[i]) != 7)
+ return 0;
+
+ if (!UTI_IsTimeOffsetSane(&now, sample_time - UTI_TimespecToDouble(&now)))
+ return 0;
+
+ /* Some resolution is lost in the double format, but that's ok */
+ UTI_DoubleToTimespec(sample_time, &inst->sample_times[i]);
+
+ /* Make sure the samples are sane and they are in order */
+ if (!UTI_IsTimeOffsetSane(&inst->sample_times[i], -inst->offsets[i]) ||
+ UTI_CompareTimespecs(&now, &inst->sample_times[i]) < 0 ||
+ !(fabs(inst->peer_delays[i]) < 1.0e6 && fabs(inst->peer_dispersions[i]) < 1.0e6 &&
+ fabs(inst->root_delays[i]) < 1.0e6 && fabs(inst->root_dispersions[i]) < 1.0e6) ||
+ (i > 0 && UTI_CompareTimespecs(&inst->sample_times[i],
+ &inst->sample_times[i - 1]) <= 0))
+ return 0;
+ }
+
+ inst->n_samples = n_samples;
+ inst->last_sample = inst->n_samples - 1;
+ inst->asymmetry_run = CLAMP(-MAX_ASYMMETRY_RUN, arun, MAX_ASYMMETRY_RUN);
+
+ find_min_delay_sample(inst);
+ SST_DoNewRegression(inst);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+SST_DoSourceReport(SST_Stats inst, RPT_SourceReport *report, struct timespec *now)
+{
+ int i, j;
+ struct timespec last_sample_time;
+
+ if (inst->n_samples > 0) {
+ i = get_runsbuf_index(inst, inst->n_samples - 1);
+ j = get_buf_index(inst, inst->n_samples - 1);
+ report->orig_latest_meas = inst->orig_offsets[j];
+ report->latest_meas = inst->offsets[i];
+ report->latest_meas_err = 0.5*inst->root_delays[j] + inst->root_dispersions[j];
+
+ /* Align the sample time to reduce the leak of the NTP receive timestamp */
+ last_sample_time = inst->sample_times[i];
+ if (inst->ip_addr)
+ last_sample_time.tv_nsec = 0;
+ report->latest_meas_ago = UTI_DiffTimespecsToDouble(now, &last_sample_time);
+ } else {
+ report->latest_meas_ago = (uint32_t)-1;
+ report->orig_latest_meas = 0;
+ report->latest_meas = 0;
+ report->latest_meas_err = 0;
+ report->stratum = 0;
+ }
+}
+
+/* ================================================== */
+
+int
+SST_Samples(SST_Stats inst)
+{
+ return inst->n_samples;
+}
+
+/* ================================================== */
+
+int
+SST_GetMinSamples(SST_Stats inst)
+{
+ return inst->min_samples;
+}
+
+/* ================================================== */
+
+void
+SST_DoSourcestatsReport(SST_Stats inst, RPT_SourcestatsReport *report, struct timespec *now)
+{
+ double dspan;
+ double elapsed, sample_elapsed;
+ int bi, bj;
+
+ report->n_samples = inst->n_samples;
+ report->n_runs = inst->nruns;
+
+ if (inst->n_samples > 0) {
+ bi = get_runsbuf_index(inst, inst->best_single_sample);
+ bj = get_buf_index(inst, inst->best_single_sample);
+
+ 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;
+ report->est_offset_err = 0;
+ }
+
+ report->resid_freq_ppm = 1.0e6 * inst->estimated_frequency;
+ report->skew_ppm = 1.0e6 * inst->skew;
+ report->sd = inst->std_dev;
+}
+
+/* ================================================== */
+
+double
+SST_GetJitterAsymmetry(SST_Stats inst)
+{
+ return inst->asymmetry;
+}
+
+/* ================================================== */
diff --git a/sourcestats.h b/sourcestats.h
new file mode 100644
index 0000000..e10f425
--- /dev/null
+++ b/sourcestats.h
@@ -0,0 +1,141 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 module that deals with the measurements and statistics of
+ each of the sources. */
+
+#ifndef GOT_SOURCESTATS_H
+#define GOT_SOURCESTATS_H
+
+#include "sysincl.h"
+
+#include "reports.h"
+
+typedef struct SST_Stats_Record *SST_Stats;
+
+/* Init and fini functions */
+extern void SST_Initialise(void);
+extern void SST_Finalise(void);
+
+/* This function creates a new instance of the statistics handler */
+extern SST_Stats SST_CreateInstance(uint32_t refid, IPAddr *addr,
+ int min_samples, int max_samples,
+ double min_delay, double asymmetry);
+
+/* This function deletes an instance of the statistics handler. */
+extern void SST_DeleteInstance(SST_Stats inst);
+
+/* This function resets an instance */
+extern void SST_ResetInstance(SST_Stats inst);
+
+/* This function changes the reference ID and IP address */
+extern void SST_SetRefid(SST_Stats inst, uint32_t refid, IPAddr *addr);
+
+/* This function accumulates a single sample into the statistics handler */
+extern void SST_AccumulateSample(SST_Stats inst, NTP_Sample *sample);
+
+/* This function runs the linear regression operation on the data. It
+ finds the set of most recent samples that give the tightest
+ confidence interval for the frequency, and truncates the register
+ down to that number of samples. */
+extern void SST_DoNewRegression(SST_Stats inst);
+
+/* Return the assumed worst case range of values that this source's
+ frequency lies within. Frequency is defined as the amount of time
+ the local clock gains relative to the source per unit local clock
+ time. */
+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,
+ double *offset_lo_limit,
+ double *offset_hi_limit,
+ double *root_distance,
+ double *variance,
+ double *first_sample_ago,
+ double *last_sample_ago,
+ int *select_ok);
+
+/* Get data needed when setting up tracking on this source */
+extern void
+SST_GetTrackingData(SST_Stats inst, struct timespec *ref_time,
+ double *average_offset, double *offset_sd,
+ double *frequency, double *frequency_sd, double *skew,
+ double *root_delay, double *root_dispersion);
+
+/* This routine is called when the local machine clock parameters are
+ changed. It adjusts all existing samples that we are holding for
+ each peer so that it looks like they were made under the new clock
+ regime rather than the old one.
+
+ when = cooked local time when the change occurs
+
+ dfreq = delta frequency. positive means the clock has been adjusted
+ because it was previously gaining time relative to the external
+ reference(s).
+
+ doffset = offset slewed onto local clock. positive => local clock
+ has been made fast by that amount.
+
+*/
+
+extern void SST_SlewSamples(SST_Stats inst, struct timespec *when, double dfreq, double doffset);
+
+/* This routine corrects already accumulated samples to improve the
+ frequency estimate when a new sample is accumulated */
+extern void SST_CorrectOffset(SST_Stats inst, double doffset);
+
+/* This routine is called when an indeterminate offset is introduced
+ into the local time. */
+extern void SST_AddDispersion(SST_Stats inst, double dispersion);
+
+/* Predict the offset of the local clock relative to a given source at
+ a given local cooked time. Positive indicates local clock is FAST
+ relative to reference. */
+extern double SST_PredictOffset(SST_Stats inst, struct timespec *when);
+
+/* Find the minimum round trip delay in the register */
+extern double SST_MinRoundTripDelay(SST_Stats inst);
+
+/* Get data needed for testing NTP delay */
+extern int SST_GetDelayTestData(SST_Stats inst, struct timespec *sample_time,
+ double *last_sample_ago, double *predicted_offset,
+ double *min_delay, double *skew, double *std_dev);
+
+extern int SST_SaveToFile(SST_Stats inst, FILE *out);
+
+extern int SST_LoadFromFile(SST_Stats inst, FILE *in);
+
+extern void SST_DoSourceReport(SST_Stats inst, RPT_SourceReport *report, struct timespec *now);
+
+extern void SST_DoSourcestatsReport(SST_Stats inst, RPT_SourcestatsReport *report, struct timespec *now);
+
+extern int SST_Samples(SST_Stats inst);
+
+extern int SST_GetMinSamples(SST_Stats inst);
+
+extern double SST_GetJitterAsymmetry(SST_Stats inst);
+
+#endif /* GOT_SOURCESTATS_H */
+
diff --git a/srcparams.h b/srcparams.h
new file mode 100644
index 0000000..31baed7
--- /dev/null
+++ b/srcparams.h
@@ -0,0 +1,93 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 defining parameters that can be set on a per source basis
+ */
+
+#ifndef GOT_SRCPARAMS_H
+#define GOT_SRCPARAMS_H
+
+#include "sources.h"
+
+typedef enum {
+ SRC_OFFLINE,
+ SRC_ONLINE,
+ SRC_MAYBE_ONLINE,
+} SRC_Connectivity;
+
+typedef struct {
+ int minpoll;
+ int maxpoll;
+ SRC_Connectivity connectivity;
+ int auto_offline;
+ int presend_minpoll;
+ int burst;
+ int iburst;
+ int min_stratum;
+ int poll_target;
+ int version;
+ int max_sources;
+ int min_samples;
+ int max_samples;
+ int filter_length;
+ int interleaved;
+ int sel_options;
+ int nts;
+ int nts_port;
+ int copy;
+ int ext_fields;
+ uint32_t authkey;
+ uint32_t cert_set;
+ double max_delay;
+ double max_delay_ratio;
+ double max_delay_dev_ratio;
+ double max_delay_quant;
+ double min_delay;
+ double asymmetry;
+ double offset;
+} SourceParameters;
+
+#define SRC_DEFAULT_PORT 123
+#define SRC_DEFAULT_MINPOLL 6
+#define SRC_DEFAULT_MAXPOLL 10
+#define SRC_DEFAULT_PRESEND_MINPOLL 100
+#define SRC_DEFAULT_MAXDELAY 3.0
+#define SRC_DEFAULT_MAXDELAYRATIO 0.0
+#define SRC_DEFAULT_MAXDELAYDEVRATIO 10.0
+#define SRC_DEFAULT_MINSTRATUM 0
+#define SRC_DEFAULT_POLLTARGET 8
+#define SRC_DEFAULT_MAXSOURCES 4
+#define SRC_DEFAULT_MINSAMPLES (-1)
+#define SRC_DEFAULT_MAXSAMPLES (-1)
+#define SRC_DEFAULT_ASYMMETRY 1.0
+#define SRC_DEFAULT_NTSPORT 4460
+#define SRC_DEFAULT_CERTSET 0
+#define INACTIVE_AUTHKEY 0
+
+/* Flags for source selection */
+#define SRC_SELECT_NOSELECT 0x1
+#define SRC_SELECT_PREFER 0x2
+#define SRC_SELECT_TRUST 0x4
+#define SRC_SELECT_REQUIRE 0x8
+
+#endif /* GOT_SRCPARAMS_H */
diff --git a/stubs.c b/stubs.c
new file mode 100644
index 0000000..b729c2f
--- /dev/null
+++ b/stubs.c
@@ -0,0 +1,573 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014-2016
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Function replacements needed when optional features are disabled.
+
+ */
+
+#include "config.h"
+
+#include "clientlog.h"
+#include "cmac.h"
+#include "cmdmon.h"
+#include "keys.h"
+#include "logging.h"
+#include "manual.h"
+#include "memory.h"
+#include "nameserv.h"
+#include "nameserv_async.h"
+#include "ntp_core.h"
+#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"
+#include "util.h"
+
+#if defined(FEAT_NTP) && !defined(FEAT_ASYNCDNS)
+
+/* This is a blocking implementation used when asynchronous resolving is not available */
+
+struct DNS_Async_Instance {
+ const char *name;
+ DNS_NameResolveHandler handler;
+ void *arg;
+ int pipe[2];
+};
+
+static void
+resolve_name(int fd, int event, void *anything)
+{
+ struct DNS_Async_Instance *inst;
+ IPAddr addrs[DNS_MAX_ADDRESSES];
+ DNS_Status status;
+ int i;
+
+ inst = (struct DNS_Async_Instance *)anything;
+
+ SCH_RemoveFileHandler(inst->pipe[0]);
+ close(inst->pipe[0]);
+ close(inst->pipe[1]);
+
+ status = PRV_Name2IPAddress(inst->name, addrs, DNS_MAX_ADDRESSES);
+
+ for (i = 0; status == DNS_Success && i < DNS_MAX_ADDRESSES &&
+ addrs[i].family != IPADDR_UNSPEC; i++)
+ ;
+
+ (inst->handler)(status, i, addrs, inst->arg);
+
+ Free(inst);
+}
+
+void
+DNS_Name2IPAddressAsync(const char *name, DNS_NameResolveHandler handler, void *anything)
+{
+ struct DNS_Async_Instance *inst;
+
+ inst = MallocNew(struct DNS_Async_Instance);
+ inst->name = name;
+ inst->handler = handler;
+ inst->arg = anything;
+
+ if (pipe(inst->pipe))
+ LOG_FATAL("pipe() failed");
+
+ UTI_FdSetCloexec(inst->pipe[0]);
+ UTI_FdSetCloexec(inst->pipe[1]);
+
+ SCH_AddFileHandler(inst->pipe[0], SCH_FILE_INPUT, resolve_name, inst);
+
+ if (write(inst->pipe[1], "", 1) < 0)
+ ;
+}
+
+#endif /* !FEAT_ASYNCDNS */
+
+#ifndef FEAT_CMDMON
+
+void
+CAM_Initialise(void)
+{
+}
+
+void
+CAM_Finalise(void)
+{
+}
+
+void
+CAM_OpenUnixSocket(void)
+{
+}
+
+int
+CAM_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all)
+{
+ return 1;
+}
+
+void
+MNL_Initialise(void)
+{
+}
+
+void
+MNL_Finalise(void)
+{
+}
+
+#endif /* !FEAT_CMDMON */
+
+#ifndef FEAT_NTP
+
+void
+NCR_AddBroadcastDestination(NTP_Remote_Address *addr, int interval)
+{
+}
+
+void
+NCR_Initialise(void)
+{
+}
+
+void
+NCR_Finalise(void)
+{
+}
+
+int
+NCR_AddAccessRestriction(IPAddr *ip_addr, int subnet_bits, int allow, int all)
+{
+ return 1;
+}
+
+int
+NCR_CheckAccessRestriction(IPAddr *ip_addr)
+{
+ return 0;
+}
+
+void
+NIO_Initialise(void)
+{
+}
+
+void
+NIO_Finalise(void)
+{
+}
+
+void
+NSR_Initialise(void)
+{
+}
+
+void
+NSR_Finalise(void)
+{
+}
+
+NSR_Status
+NSR_AddSource(NTP_Remote_Address *remote_addr, NTP_Source_Type type,
+ SourceParameters *params, uint32_t *conf_id)
+{
+ return NSR_TooManySources;
+}
+
+NSR_Status
+NSR_AddSourceByName(char *name, int port, int pool, NTP_Source_Type type,
+ SourceParameters *params, uint32_t *conf_id)
+{
+ return NSR_TooManySources;
+}
+
+const char *
+NSR_StatusToString(NSR_Status status)
+{
+ return "NTP not supported";
+}
+
+NSR_Status
+NSR_RemoveSource(IPAddr *address)
+{
+ return NSR_NoSuchSource;
+}
+
+void
+NSR_RemoveSourcesById(uint32_t conf_id)
+{
+}
+
+void
+NSR_RemoveAllSources(void)
+{
+}
+
+void
+NSR_HandleBadSource(IPAddr *address)
+{
+}
+
+void
+NSR_RefreshAddresses(void)
+{
+}
+
+char *
+NSR_GetName(IPAddr *address)
+{
+ return NULL;
+}
+
+void
+NSR_SetSourceResolvingEndHandler(NSR_SourceResolvingEndHandler handler)
+{
+ if (handler)
+ (handler)();
+}
+
+void
+NSR_ResolveSources(void)
+{
+}
+
+void NSR_StartSources(void)
+{
+}
+
+void NSR_AutoStartSources(void)
+{
+}
+
+int
+NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples,
+ IPAddr *mask, IPAddr *address)
+{
+ return 0;
+}
+
+uint32_t
+NSR_GetLocalRefid(IPAddr *address)
+{
+ return 0;
+}
+
+int
+NSR_SetConnectivity(IPAddr *mask, IPAddr *address, SRC_Connectivity connectivity)
+{
+ return 0;
+}
+
+int
+NSR_ModifyMinpoll(IPAddr *address, int new_minpoll)
+{
+ return 0;
+}
+
+int
+NSR_ModifyMaxpoll(IPAddr *address, int new_maxpoll)
+{
+ return 0;
+}
+
+int
+NSR_ModifyMaxdelay(IPAddr *address, double new_max_delay)
+{
+ return 0;
+}
+
+int
+NSR_ModifyMaxdelayratio(IPAddr *address, double new_max_delay_ratio)
+{
+ return 0;
+}
+
+int
+NSR_ModifyMaxdelaydevratio(IPAddr *address, double new_max_delay_dev_ratio)
+{
+ return 0;
+}
+
+int
+NSR_ModifyMinstratum(IPAddr *address, int new_min_stratum)
+{
+ return 0;
+}
+
+int
+NSR_ModifyPolltarget(IPAddr *address, int new_poll_target)
+{
+ return 0;
+}
+
+void
+NSR_ReportSource(RPT_SourceReport *report, struct timespec *now)
+{
+ memset(report, 0, sizeof (*report));
+}
+
+int
+NSR_GetAuthReport(IPAddr *address, RPT_AuthReport *report)
+{
+ return 0;
+}
+
+int
+NSR_GetNTPReport(RPT_NTPReport *report)
+{
+ return 0;
+}
+
+void
+NSR_GetActivityReport(RPT_ActivityReport *report)
+{
+ memset(report, 0, sizeof (*report));
+}
+
+void
+NSR_DumpAuthData(void)
+{
+}
+
+#ifndef FEAT_CMDMON
+
+void
+CLG_Initialise(void)
+{
+}
+
+void
+CLG_Finalise(void)
+{
+}
+
+void
+DNS_SetAddressFamily(int family)
+{
+}
+
+DNS_Status
+DNS_Name2IPAddress(const char *name, IPAddr *ip_addrs, int max_addrs)
+{
+ return DNS_Failure;
+}
+
+void
+KEY_Initialise(void)
+{
+}
+
+void
+KEY_Finalise(void)
+{
+}
+
+#endif /* !FEAT_CMDMON */
+#endif /* !FEAT_NTP */
+
+#ifndef FEAT_REFCLOCK
+void
+RCL_Initialise(void)
+{
+}
+
+void
+RCL_Finalise(void)
+{
+}
+
+int
+RCL_AddRefclock(RefclockParameters *params)
+{
+ return 0;
+}
+
+void
+RCL_StartRefclocks(void)
+{
+}
+
+void
+RCL_ReportSource(RPT_SourceReport *report, struct timespec *now)
+{
+ memset(report, 0, sizeof (*report));
+}
+
+#endif /* !FEAT_REFCLOCK */
+
+#ifndef FEAT_SIGND
+
+void
+NSD_Initialise(void)
+{
+}
+
+void
+NSD_Finalise(void)
+{
+}
+
+int
+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
+
+int
+CMC_GetKeyLength(CMC_Algorithm algorithm)
+{
+ return 0;
+}
+
+CMC_Instance
+CMC_CreateInstance(CMC_Algorithm algorithm, const unsigned char *key, int length)
+{
+ return NULL;
+}
+
+int
+CMC_Hash(CMC_Instance inst, const void *in, int in_len, unsigned char *out, 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, uint32_t cert_set,
+ uint16_t ntp_port)
+{
+ 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)
+{
+ static int logged = 0;
+
+ LOG(logged ? LOGS_DEBUG : LOGS_WARN, "Missing NTS support");
+ logged = 1;
+ return 0;
+}
+
+int
+NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info)
+{
+ return 0;
+}
+
+void
+NNC_ChangeAddress(NNC_Instance inst, IPAddr *address)
+{
+}
+
+void
+NNC_DumpData(NNC_Instance inst)
+{
+}
+
+void
+NNC_GetReport(NNC_Instance inst, RPT_AuthReport *report)
+{
+}
+
+void
+NKS_PreInitialise(uid_t uid, gid_t gid, int scfilter_level)
+{
+}
+
+void
+NKS_Initialise(void)
+{
+}
+
+void
+NKS_Finalise(void)
+{
+}
+
+void
+NKS_DumpKeys(void)
+{
+}
+
+void
+NKS_ReloadKeys(void)
+{
+}
+
+#endif /* !FEAT_NTS */
diff --git a/sys.c b/sys.c
new file mode 100644
index 0000000..1a1a432
--- /dev/null
+++ b/sys.c
@@ -0,0 +1,150 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 file contains all the conditionally compiled bits that pull
+ in the various operating-system specific modules
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "sys.h"
+#include "sys_null.h"
+#include "logging.h"
+
+#if defined(LINUX)
+#include "sys_linux.h"
+#include "sys_posix.h"
+#elif defined(SOLARIS)
+#include "sys_solaris.h"
+#include "sys_posix.h"
+#elif defined(NETBSD) || defined(FREEBSD)
+#include "sys_netbsd.h"
+#include "sys_posix.h"
+#elif defined(MACOSX)
+#include "sys_macosx.h"
+#endif
+
+/* ================================================== */
+
+static int null_driver;
+
+/* ================================================== */
+
+void
+SYS_Initialise(int clock_control)
+{
+ null_driver = !clock_control;
+ if (null_driver) {
+ SYS_Null_Initialise();
+ return;
+ }
+#if defined(LINUX)
+ SYS_Linux_Initialise();
+#elif defined(SOLARIS)
+ SYS_Solaris_Initialise();
+#elif defined(NETBSD) || defined(FREEBSD)
+ SYS_NetBSD_Initialise();
+#elif defined(MACOSX)
+ SYS_MacOSX_Initialise();
+#else
+#error Unknown system
+#endif
+}
+
+/* ================================================== */
+
+void
+SYS_Finalise(void)
+{
+ if (null_driver) {
+ SYS_Null_Finalise();
+ return;
+ }
+#if defined(LINUX)
+ SYS_Linux_Finalise();
+#elif defined(SOLARIS)
+ SYS_Solaris_Finalise();
+#elif defined(NETBSD) || defined(FREEBSD)
+ SYS_NetBSD_Finalise();
+#elif defined(MACOSX)
+ SYS_MacOSX_Finalise();
+#else
+#error Unknown system
+#endif
+}
+
+/* ================================================== */
+
+void SYS_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context)
+{
+#if defined(LINUX) && defined (FEAT_PRIVDROP)
+ SYS_Linux_DropRoot(uid, gid, context, !null_driver);
+#elif defined(SOLARIS) && defined(FEAT_PRIVDROP)
+ SYS_Solaris_DropRoot(uid, gid, context);
+#elif (defined(NETBSD) || defined(FREEBSD)) && defined(FEAT_PRIVDROP)
+ SYS_NetBSD_DropRoot(uid, gid, context, !null_driver);
+#elif defined(MACOSX) && defined(FEAT_PRIVDROP)
+ SYS_MacOSX_DropRoot(uid, gid, context);
+#else
+ LOG_FATAL("dropping root privileges not supported");
+#endif
+}
+
+/* ================================================== */
+
+void SYS_EnableSystemCallFilter(int level, SYS_ProcessContext context)
+{
+#if defined(LINUX) && defined(FEAT_SCFILTER)
+ SYS_Linux_EnableSystemCallFilter(level, context);
+#else
+ LOG_FATAL("system call filter not supported");
+#endif
+}
+
+/* ================================================== */
+
+void SYS_SetScheduler(int SchedPriority)
+{
+#if defined(MACOSX)
+ SYS_MacOSX_SetScheduler(SchedPriority);
+#elif defined(HAVE_PTHREAD_SETSCHEDPARAM)
+ SYS_Posix_SetScheduler(SchedPriority);
+#else
+ LOG_FATAL("scheduler priority setting not supported");
+#endif
+}
+
+/* ================================================== */
+
+void SYS_LockMemory(void)
+{
+#if defined(HAVE_MLOCKALL)
+ SYS_Posix_MemLockAll();
+#else
+ LOG_FATAL("memory locking not supported");
+#endif
+}
+
+/* ================================================== */
diff --git a/sys.h b/sys.h
new file mode 100644
index 0000000..9272daf
--- /dev/null
+++ b/sys.h
@@ -0,0 +1,53 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 for the file that links in the operating system-
+ specific parts of the software
+
+*/
+
+#ifndef GOT_SYS_H
+#define GOT_SYS_H
+
+/* Called at the start of the run to do initialisation */
+extern void SYS_Initialise(int clock_control);
+
+/* Called at the end of the run to do final clean-up */
+extern void SYS_Finalise(void);
+
+typedef enum {
+ SYS_MAIN_PROCESS,
+ SYS_NTSKE_HELPER,
+} SYS_ProcessContext;
+
+/* Switch to the specified user and group in given context */
+extern void SYS_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context);
+
+/* Enable a system call filter to allow only system calls
+ which chronyd normally needs after initialization */
+extern void SYS_EnableSystemCallFilter(int level, SYS_ProcessContext context);
+
+extern void SYS_SetScheduler(int SchedPriority);
+extern void SYS_LockMemory(void);
+
+#endif /* GOT_SYS_H */
diff --git a/sys_generic.c b/sys_generic.c
new file mode 100644
index 0000000..5c42df1
--- /dev/null
+++ b/sys_generic.c
@@ -0,0 +1,449 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014-2015
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Generic driver functions to complete system-specific drivers
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "sys_generic.h"
+
+#include "conf.h"
+#include "local.h"
+#include "localp.h"
+#include "logging.h"
+#include "privops.h"
+#include "sched.h"
+#include "util.h"
+
+/* ================================================== */
+
+/* System clock drivers */
+static lcl_ReadFrequencyDriver drv_read_freq;
+static lcl_SetFrequencyDriver drv_set_freq;
+static lcl_SetSyncStatusDriver drv_set_sync_status;
+static lcl_AccrueOffsetDriver drv_accrue_offset;
+static lcl_OffsetCorrectionDriver drv_get_offset_correction;
+
+/* Current frequency as requested by the local module (in ppm) */
+static double base_freq;
+
+/* Maximum frequency that can be set by drv_set_freq (in ppm) */
+static double max_freq;
+
+/* Maximum expected delay in the actual frequency change (e.g. kernel ticks)
+ in local time */
+static double max_freq_change_delay;
+
+/* Maximum allowed frequency offset relative to the base frequency */
+static double max_corr_freq;
+
+/* Amount of outstanding offset to process */
+static double offset_register;
+
+/* Minimum offset to correct */
+#define MIN_OFFSET_CORRECTION 1.0e-9
+
+/* Current frequency offset between base_freq and the real clock frequency
+ as set by drv_set_freq (not in ppm) */
+static double slew_freq;
+
+/* Time (raw) of last update of slewing frequency and offset */
+static struct timespec slew_start;
+
+/* Limits for the slew length */
+#define MIN_SLEW_DURATION 1.0
+#define MAX_SLEW_DURATION 1.0e4
+
+/* Scheduler timeout ID for ending of the currently running slew */
+static SCH_TimeoutID slew_timeout_id;
+
+/* Scheduled duration of the currently running slew */
+static double slew_duration;
+
+/* Expected delay in ending of the slew due to process scheduling and
+ execution time, tracked as a decaying maximum value */
+static double slew_excess_duration;
+
+/* Maximum accepted excess duration to ignore large jumps after resuming
+ suspended system and other reasons (which should be handled in the
+ scheduler), a constant to determine the minimum slew duration to avoid
+ oscillations due to the excess, and the decay constant */
+#define MAX_SLEW_EXCESS_DURATION 100.0
+#define MIN_SLEW_DURATION_EXCESS_RATIO 5.0
+#define SLEW_EXCESS_DURATION_DECAY 0.9
+
+/* Suggested offset correction rate (correction time * offset) */
+static double correction_rate;
+
+/* Maximum expected offset correction error caused by delayed change in the
+ real frequency of the clock */
+static double slew_error;
+
+/* Minimum offset that the system driver can slew faster than the maximum
+ frequency offset that it allows to be set directly */
+static double fastslew_min_offset;
+
+/* Maximum slew rate of the system driver */
+static double fastslew_max_rate;
+
+/* Flag indicating that the system driver is currently slewing */
+static int fastslew_active;
+
+/* ================================================== */
+
+static void handle_end_of_slew(void *anything);
+static void update_slew(void);
+
+/* ================================================== */
+/* Adjust slew_start on clock step */
+
+static void
+handle_step(struct timespec *raw, struct timespec *cooked, double dfreq,
+ double doffset, LCL_ChangeType change_type, void *anything)
+{
+ if (change_type == LCL_ChangeStep) {
+ UTI_AddDoubleToTimespec(&slew_start, -doffset, &slew_start);
+ }
+}
+
+/* ================================================== */
+
+static void
+start_fastslew(void)
+{
+ if (!drv_accrue_offset)
+ return;
+
+ drv_accrue_offset(offset_register, 0.0);
+
+ DEBUG_LOG("fastslew offset=%e", offset_register);
+
+ offset_register = 0.0;
+ fastslew_active = 1;
+}
+
+/* ================================================== */
+
+static void
+stop_fastslew(struct timespec *now)
+{
+ double corr;
+
+ if (!drv_get_offset_correction || !fastslew_active)
+ return;
+
+ /* Cancel the remaining offset */
+ drv_get_offset_correction(now, &corr, NULL);
+ drv_accrue_offset(corr, 0.0);
+ offset_register -= corr;
+}
+
+/* ================================================== */
+
+static double
+clamp_freq(double freq)
+{
+ if (freq > max_freq)
+ return max_freq;
+ if (freq < -max_freq)
+ return -max_freq;
+ return freq;
+}
+
+/* ================================================== */
+/* End currently running slew and start a new one */
+
+static void
+update_slew(void)
+{
+ double old_slew_freq, total_freq, corr_freq, duration, excess_duration;
+ struct timespec now, end_of_slew;
+
+ /* Remove currently running timeout */
+ SCH_RemoveTimeout(slew_timeout_id);
+
+ LCL_ReadRawTime(&now);
+
+ /* Adjust the offset register by achieved slew */
+ duration = UTI_DiffTimespecsToDouble(&now, &slew_start);
+ offset_register -= slew_freq * duration;
+
+ stop_fastslew(&now);
+
+ /* Update the maximum excess duration, decaying even when the slew did
+ not time out (i.e. frequency was set or offset accrued), but add a small
+ value to avoid denormals */
+ slew_excess_duration = (slew_excess_duration + 1.0e-9) * SLEW_EXCESS_DURATION_DECAY;
+ excess_duration = duration - slew_duration;
+ if (slew_excess_duration < excess_duration &&
+ excess_duration <= MAX_SLEW_EXCESS_DURATION)
+ slew_excess_duration = excess_duration;
+
+ /* Calculate the duration of the new slew, considering the current correction
+ rate and previous delays in stopping of the slew */
+ if (fabs(offset_register) < MIN_OFFSET_CORRECTION) {
+ duration = MAX_SLEW_DURATION;
+ } else {
+ duration = correction_rate / fabs(offset_register);
+ if (duration < MIN_SLEW_DURATION)
+ duration = MIN_SLEW_DURATION;
+ if (duration < MIN_SLEW_DURATION_EXCESS_RATIO * slew_excess_duration)
+ duration = MIN_SLEW_DURATION_EXCESS_RATIO * slew_excess_duration;
+ }
+
+ /* Get frequency offset needed to slew the offset in the duration
+ and clamp it to the allowed maximum */
+ corr_freq = offset_register / duration;
+ if (corr_freq < -max_corr_freq)
+ corr_freq = -max_corr_freq;
+ else if (corr_freq > max_corr_freq)
+ corr_freq = max_corr_freq;
+
+ /* Let the system driver perform the slew if the requested frequency
+ offset is too large for the frequency driver */
+ if (drv_accrue_offset && fabs(corr_freq) >= fastslew_max_rate &&
+ fabs(offset_register) > fastslew_min_offset) {
+ start_fastslew();
+ corr_freq = 0.0;
+ }
+
+ /* Get the new real frequency and clamp it */
+ total_freq = clamp_freq(base_freq + corr_freq * (1.0e6 - base_freq));
+
+ /* Set the new frequency (the actual frequency returned by the call may be
+ slightly different from the requested frequency due to rounding) */
+ total_freq = (*drv_set_freq)(total_freq);
+
+ /* Compute the new slewing frequency, it's relative to the real frequency to
+ make the calculation in offset_convert() cheaper */
+ old_slew_freq = slew_freq;
+ slew_freq = (total_freq - base_freq) / (1.0e6 - total_freq);
+
+ /* Compute the dispersion introduced by changing frequency and add it
+ to all statistics held at higher levels in the system */
+ slew_error = fabs((old_slew_freq - slew_freq) * max_freq_change_delay);
+ if (slew_error >= MIN_OFFSET_CORRECTION)
+ lcl_InvokeDispersionNotifyHandlers(slew_error);
+
+ /* Compute the duration of the slew and clamp it. If the slewing frequency
+ is zero or has wrong sign (e.g. due to rounding in the frequency driver or
+ when base_freq is larger than max_freq, or fast slew is active), use the
+ maximum timeout and try again on the next update. */
+ if (fabs(offset_register) < MIN_OFFSET_CORRECTION ||
+ offset_register * slew_freq <= 0.0) {
+ duration = MAX_SLEW_DURATION;
+ } else {
+ duration = offset_register / slew_freq;
+ if (duration < MIN_SLEW_DURATION)
+ duration = MIN_SLEW_DURATION;
+ else if (duration > MAX_SLEW_DURATION)
+ duration = MAX_SLEW_DURATION;
+ }
+
+ /* Restart timer for the next update */
+ UTI_AddDoubleToTimespec(&now, duration, &end_of_slew);
+ slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
+ slew_start = now;
+ slew_duration = duration;
+
+ DEBUG_LOG("slew offset=%e corr_rate=%e base_freq=%f total_freq=%f slew_freq=%e"
+ " duration=%f excess=%f slew_error=%e",
+ offset_register, correction_rate, base_freq, total_freq, slew_freq,
+ slew_duration, slew_excess_duration, slew_error);
+}
+
+/* ================================================== */
+
+static void
+handle_end_of_slew(void *anything)
+{
+ slew_timeout_id = 0;
+ update_slew();
+}
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+ return base_freq;
+}
+
+/* ================================================== */
+
+static double
+set_frequency(double freq_ppm)
+{
+ base_freq = freq_ppm;
+ update_slew();
+
+ return base_freq;
+}
+
+/* ================================================== */
+
+static void
+accrue_offset(double offset, double corr_rate)
+{
+ offset_register += offset;
+ correction_rate = corr_rate;
+
+ update_slew();
+}
+
+/* ================================================== */
+/* Determine the correction to generate the cooked time for given raw time */
+
+static void
+offset_convert(struct timespec *raw,
+ double *corr, double *err)
+{
+ double duration, fastslew_corr, fastslew_err;
+
+ duration = UTI_DiffTimespecsToDouble(raw, &slew_start);
+
+ if (drv_get_offset_correction && fastslew_active) {
+ drv_get_offset_correction(raw, &fastslew_corr, &fastslew_err);
+ if (fastslew_corr == 0.0 && fastslew_err == 0.0)
+ fastslew_active = 0;
+ } else {
+ fastslew_corr = fastslew_err = 0.0;
+ }
+
+ *corr = slew_freq * duration + fastslew_corr - offset_register;
+
+ if (err) {
+ *err = fastslew_err;
+ if (fabs(duration) <= max_freq_change_delay)
+ *err += slew_error;
+ }
+}
+
+/* ================================================== */
+/* Positive means currently fast of true time, i.e. jump backwards */
+
+static int
+apply_step_offset(double offset)
+{
+ struct timespec old_time, new_time;
+ struct timeval new_time_tv;
+ double err;
+
+ LCL_ReadRawTime(&old_time);
+ UTI_AddDoubleToTimespec(&old_time, -offset, &new_time);
+ UTI_TimespecToTimeval(&new_time, &new_time_tv);
+
+ if (PRV_SetTime(&new_time_tv, NULL) < 0) {
+ DEBUG_LOG("settimeofday() failed");
+ return 0;
+ }
+
+ LCL_ReadRawTime(&old_time);
+ err = UTI_DiffTimespecsToDouble(&old_time, &new_time);
+
+ lcl_InvokeDispersionNotifyHandlers(fabs(err));
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+set_sync_status(int synchronised, double est_error, double max_error)
+{
+ double offset;
+
+ offset = fabs(offset_register);
+ if (est_error < offset)
+ est_error = offset;
+ max_error += offset;
+
+ if (drv_set_sync_status)
+ drv_set_sync_status(synchronised, est_error, max_error);
+}
+
+/* ================================================== */
+
+void
+SYS_Generic_CompleteFreqDriver(double max_set_freq_ppm, double max_set_freq_delay,
+ lcl_ReadFrequencyDriver sys_read_freq,
+ lcl_SetFrequencyDriver sys_set_freq,
+ lcl_ApplyStepOffsetDriver sys_apply_step_offset,
+ double min_fastslew_offset, double max_fastslew_rate,
+ lcl_AccrueOffsetDriver sys_accrue_offset,
+ lcl_OffsetCorrectionDriver sys_get_offset_correction,
+ lcl_SetLeapDriver sys_set_leap,
+ lcl_SetSyncStatusDriver sys_set_sync_status)
+{
+ max_freq = max_set_freq_ppm;
+ max_freq_change_delay = max_set_freq_delay * (1.0 + max_freq / 1.0e6);
+ drv_read_freq = sys_read_freq;
+ drv_set_freq = sys_set_freq;
+ drv_accrue_offset = sys_accrue_offset;
+ drv_get_offset_correction = sys_get_offset_correction;
+ drv_set_sync_status = sys_set_sync_status;
+
+ base_freq = (*drv_read_freq)();
+ slew_freq = 0.0;
+ offset_register = 0.0;
+ slew_excess_duration = 0.0;
+
+ max_corr_freq = CNF_GetMaxSlewRate() / 1.0e6;
+
+ fastslew_min_offset = min_fastslew_offset;
+ fastslew_max_rate = max_fastslew_rate / 1.0e6;
+ fastslew_active = 0;
+
+ lcl_RegisterSystemDrivers(read_frequency, set_frequency,
+ accrue_offset, sys_apply_step_offset ?
+ sys_apply_step_offset : apply_step_offset,
+ offset_convert, sys_set_leap, set_sync_status);
+
+ LCL_AddParameterChangeHandler(handle_step, NULL);
+}
+
+/* ================================================== */
+
+void
+SYS_Generic_Finalise(void)
+{
+ struct timespec now;
+
+ /* Must *NOT* leave a slew running - clock could drift way off
+ if the daemon is not restarted */
+
+ SCH_RemoveTimeout(slew_timeout_id);
+ slew_timeout_id = 0;
+
+ (*drv_set_freq)(clamp_freq(base_freq));
+
+ LCL_ReadRawTime(&now);
+ stop_fastslew(&now);
+
+ LCL_RemoveParameterChangeHandler(handle_step, NULL);
+}
+
+/* ================================================== */
diff --git a/sys_generic.h b/sys_generic.h
new file mode 100644
index 0000000..d9b252e
--- /dev/null
+++ b/sys_generic.h
@@ -0,0 +1,46 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2014
+ *
+ * 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 generic driver
+ */
+
+#ifndef GOT_SYS_GENERIC_H
+#define GOT_SYS_GENERIC_H
+
+#include "localp.h"
+
+/* Register a completed driver that implements offset functions on top of
+ provided frequency functions */
+extern void SYS_Generic_CompleteFreqDriver(double max_set_freq_ppm, double max_set_freq_delay,
+ lcl_ReadFrequencyDriver sys_read_freq,
+ lcl_SetFrequencyDriver sys_set_freq,
+ lcl_ApplyStepOffsetDriver sys_apply_step_offset,
+ double min_fastslew_offset, double max_fastslew_rate,
+ lcl_AccrueOffsetDriver sys_accrue_offset,
+ lcl_OffsetCorrectionDriver sys_get_offset_correction,
+ lcl_SetLeapDriver sys_set_leap,
+ lcl_SetSyncStatusDriver sys_set_sync_status);
+
+extern void SYS_Generic_Finalise(void);
+
+#endif /* GOT_SYS_GENERIC_H */
diff --git a/sys_linux.c b/sys_linux.c
new file mode 100644
index 0000000..6849637
--- /dev/null
+++ b/sys_linux.c
@@ -0,0 +1,1025 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) John G. Hasler 2009
+ * Copyright (C) Miroslav Lichvar 2009-2012, 2014-2018
+ *
+ * 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 module specific to the Linux operating system.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <sys/utsname.h>
+
+#if defined(FEAT_PHC) || defined(HAVE_LINUX_TIMESTAMPING)
+#include <linux/ptp_clock.h>
+#include <poll.h>
+#endif
+
+#ifdef FEAT_SCFILTER
+#include <sys/prctl.h>
+#include <seccomp.h>
+#include <termios.h>
+#ifdef FEAT_PPS
+#include <linux/pps.h>
+#endif
+#ifdef FEAT_RTC
+#include <linux/rtc.h>
+#endif
+#ifdef HAVE_LINUX_TIMESTAMPING
+#include <linux/sockios.h>
+#endif
+#endif
+
+#ifdef FEAT_PRIVDROP
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#endif
+
+#include "sys_linux.h"
+#include "sys_timex.h"
+#include "conf.h"
+#include "local.h"
+#include "logging.h"
+#include "privops.h"
+#include "util.h"
+
+/* Frequency scale to convert from ppm to the timex freq */
+#define FREQ_SCALE (double)(1 << 16)
+
+/* Definitions used if missed in the system headers */
+#ifndef ADJ_SETOFFSET
+#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */
+#endif
+#ifndef ADJ_NANO
+#define ADJ_NANO 0x2000 /* select nanosecond resolution */
+#endif
+
+/* This is the uncompensated system tick value */
+static int nominal_tick;
+
+/* Current tick value */
+static int current_delta_tick;
+
+/* The maximum amount by which 'tick' can be biased away from 'nominal_tick'
+ (sys_adjtimex() in the kernel bounds this to 10%) */
+static int max_tick_bias;
+
+/* The kernel USER_HZ constant */
+static int hz;
+static double dhz; /* And dbl prec version of same for arithmetic */
+
+/* Flag indicating whether adjtimex() can step the clock */
+static int have_setoffset;
+
+/* The assumed rate at which the effective frequency and tick values are
+ updated in the kernel */
+static int tick_update_hz;
+
+/* ================================================== */
+/* Positive means currently fast of true time, i.e. jump backwards */
+
+static int
+apply_step_offset(double offset)
+{
+ struct timex txc;
+
+ txc.modes = ADJ_SETOFFSET | ADJ_NANO;
+ txc.time.tv_sec = -offset;
+ txc.time.tv_usec = 1.0e9 * (-offset - txc.time.tv_sec);
+ if (txc.time.tv_usec < 0) {
+ txc.time.tv_sec--;
+ txc.time.tv_usec += 1000000000;
+ }
+
+ if (SYS_Timex_Adjust(&txc, 1) < 0)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+/* This call sets the Linux kernel frequency to a given value in parts
+ per million relative to the nominal running frequency. Nominal is taken to
+ be tick=10000, freq=0 (for a USER_HZ==100 system, other values otherwise).
+ The convention is that this is called with a positive argument if the local
+ clock runs fast when uncompensated. */
+
+static double
+set_frequency(double freq_ppm)
+{
+ struct timex txc;
+ long required_tick;
+ double required_freq;
+ int required_delta_tick;
+
+ required_delta_tick = round(freq_ppm / dhz);
+
+ /* Older kernels (pre-2.6.18) don't apply the frequency offset exactly as
+ set by adjtimex() and a scaling constant (that depends on the internal
+ kernel HZ constant) would be needed to compensate for the error. Because
+ chronyd is closed loop it doesn't matter much if we don't scale the
+ required frequency, but we want to prevent thrashing between two states
+ when the system's frequency error is close to a multiple of USER_HZ. With
+ USER_HZ <= 250, the maximum frequency adjustment of 500 ppm overlaps at
+ least two ticks and we can stick to the current tick if it's next to the
+ required tick. */
+ if (hz <= 250 && (required_delta_tick + 1 == current_delta_tick ||
+ required_delta_tick - 1 == current_delta_tick)) {
+ required_delta_tick = current_delta_tick;
+ }
+
+ required_freq = -(freq_ppm - dhz * required_delta_tick);
+ required_tick = nominal_tick - required_delta_tick;
+
+ txc.modes = ADJ_TICK | ADJ_FREQUENCY;
+ txc.freq = required_freq * FREQ_SCALE;
+ txc.tick = required_tick;
+
+ SYS_Timex_Adjust(&txc, 0);
+
+ current_delta_tick = required_delta_tick;
+
+ return dhz * current_delta_tick - txc.freq / FREQ_SCALE;
+}
+
+/* ================================================== */
+/* Read the ppm frequency from the kernel */
+
+static double
+read_frequency(void)
+{
+ struct timex txc;
+
+ txc.modes = 0;
+
+ SYS_Timex_Adjust(&txc, 0);
+
+ current_delta_tick = nominal_tick - txc.tick;
+
+ return dhz * current_delta_tick - txc.freq / FREQ_SCALE;
+}
+
+/* ================================================== */
+
+/* Estimate the value of USER_HZ given the value of txc.tick that chronyd finds when
+ * it starts. The only credible values are 100 (Linux/x86) or powers of 2.
+ * Also, the bounds checking inside the kernel's adjtimex system call enforces
+ * a +/- 10% movement of tick away from the nominal value 1e6/USER_HZ. */
+
+static int
+guess_hz(void)
+{
+ struct timex txc;
+ int i, tick, tick_lo, tick_hi, ihz;
+ double tick_nominal;
+
+ txc.modes = 0;
+ SYS_Timex_Adjust(&txc, 0);
+ tick = txc.tick;
+
+ /* Pick off the hz=100 case first */
+ if (tick >= 9000 && tick <= 11000) {
+ return 100;
+ }
+
+ for (i=4; i<16; i++) { /* surely 16 .. 32768 is a wide enough range? */
+ ihz = 1 << i;
+ tick_nominal = 1.0e6 / (double) ihz;
+ tick_lo = (int)(0.5 + tick_nominal*2.0/3.0);
+ tick_hi = (int)(0.5 + tick_nominal*4.0/3.0);
+
+ if (tick_lo < tick && tick <= tick_hi) {
+ return ihz;
+ }
+ }
+
+ /* oh dear. doomed. */
+ LOG_FATAL("Can't determine hz from tick %d", tick);
+
+ return 0;
+}
+
+/* ================================================== */
+
+static int
+get_hz(void)
+{
+#ifdef _SC_CLK_TCK
+ int hz;
+
+ if ((hz = sysconf(_SC_CLK_TCK)) < 1)
+ return 0;
+
+ return hz;
+#else
+ return 0;
+#endif
+}
+
+/* ================================================== */
+
+static int
+kernelvercmp(int major1, int minor1, int patch1,
+ int major2, int minor2, int patch2)
+{
+ if (major1 != major2)
+ return major1 - major2;
+ if (minor1 != minor2)
+ return minor1 - minor2;
+ return patch1 - patch2;
+}
+
+/* ================================================== */
+
+static void
+get_kernel_version(int *major, int *minor, int *patch)
+{
+ struct utsname uts;
+
+ if (uname(&uts) < 0)
+ LOG_FATAL("uname() failed");
+
+ *patch = 0;
+ if (sscanf(uts.release, "%d.%d.%d", major, minor, patch) < 2)
+ LOG_FATAL("Could not parse kernel version");
+}
+
+/* ================================================== */
+
+/* Compute the scaling to use on any frequency we set, according to
+ the vintage of the Linux kernel being used. */
+
+static void
+get_version_specific_details(void)
+{
+ int major, minor, patch;
+
+ hz = get_hz();
+
+ if (!hz)
+ hz = guess_hz();
+
+ dhz = (double) hz;
+ nominal_tick = (1000000L + (hz/2))/hz; /* Mirror declaration in kernel */
+ max_tick_bias = nominal_tick / 10;
+
+ /* In modern kernels the frequency of the clock is updated immediately in the
+ adjtimex() system call. Assume a maximum delay of 10 microseconds. */
+ tick_update_hz = 100000;
+
+ get_kernel_version(&major, &minor, &patch);
+ DEBUG_LOG("Linux kernel major=%d minor=%d patch=%d", major, minor, patch);
+
+ if (kernelvercmp(major, minor, patch, 2, 2, 0) < 0) {
+ LOG_FATAL("Kernel version not supported, sorry.");
+ }
+
+ if (kernelvercmp(major, minor, patch, 2, 6, 27) >= 0 &&
+ kernelvercmp(major, minor, patch, 2, 6, 33) < 0) {
+ /* In tickless kernels before 2.6.33 the frequency is updated in
+ a half-second interval */
+ tick_update_hz = 2;
+ } else if (kernelvercmp(major, minor, patch, 4, 19, 0) < 0) {
+ /* In kernels before 4.19 the frequency is updated only on internal ticks
+ (CONFIG_HZ). As their rate cannot be reliably detected from the user
+ space, and it may not even be constant (CONFIG_NO_HZ - aka tickless),
+ assume the lowest commonly used constant rate */
+ tick_update_hz = 100;
+ }
+
+ /* ADJ_SETOFFSET support */
+ if (kernelvercmp(major, minor, patch, 2, 6, 39) < 0) {
+ have_setoffset = 0;
+ } else {
+ have_setoffset = 1;
+ }
+
+ DEBUG_LOG("hz=%d nominal_tick=%d max_tick_bias=%d tick_update_hz=%d",
+ hz, nominal_tick, max_tick_bias, tick_update_hz);
+}
+
+/* ================================================== */
+
+static void
+reset_adjtime_offset(void)
+{
+ struct timex txc;
+
+ /* Reset adjtime() offset */
+ txc.modes = ADJ_OFFSET_SINGLESHOT;
+ txc.offset = 0;
+
+ SYS_Timex_Adjust(&txc, 0);
+}
+
+/* ================================================== */
+
+static int
+test_step_offset(void)
+{
+ struct timex txc;
+
+ /* Zero maxerror and check it's reset to a maximum after ADJ_SETOFFSET.
+ This seems to be the only way how to verify that the kernel really
+ supports the ADJ_SETOFFSET mode as it doesn't return an error on unknown
+ mode. */
+
+ txc.modes = MOD_MAXERROR;
+ txc.maxerror = 0;
+
+ if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror != 0)
+ return 0;
+
+ txc.modes = ADJ_SETOFFSET | ADJ_NANO;
+ txc.time.tv_sec = 0;
+ txc.time.tv_usec = 0;
+
+ if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror < 100000)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+report_time_adjust_blockers(void)
+{
+#if defined(FEAT_PRIVDROP) && defined(CAP_IS_SUPPORTED)
+ if (CAP_IS_SUPPORTED(CAP_SYS_TIME) && cap_get_bound(CAP_SYS_TIME))
+ return;
+ LOG(LOGS_WARN, "CAP_SYS_TIME not present");
+#endif
+}
+
+/* ================================================== */
+/* Initialisation code for this module */
+
+void
+SYS_Linux_Initialise(void)
+{
+ get_version_specific_details();
+
+ report_time_adjust_blockers();
+
+ reset_adjtime_offset();
+
+ if (have_setoffset && !test_step_offset()) {
+ LOG(LOGS_INFO, "adjtimex() doesn't support ADJ_SETOFFSET");
+ have_setoffset = 0;
+ }
+
+ SYS_Timex_InitialiseWithFunctions(1.0e6 * max_tick_bias / nominal_tick,
+ 1.0 / tick_update_hz,
+ read_frequency, set_frequency,
+ have_setoffset ? apply_step_offset : NULL,
+ 0.0, 0.0, NULL, NULL);
+}
+
+/* ================================================== */
+/* Finalisation code for this module */
+
+void
+SYS_Linux_Finalise(void)
+{
+ SYS_Timex_Finalise();
+}
+
+/* ================================================== */
+
+#ifdef FEAT_PRIVDROP
+void
+SYS_Linux_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context, int clock_control)
+{
+ char cap_text[256];
+ cap_t cap;
+
+ if (prctl(PR_SET_KEEPCAPS, 1)) {
+ LOG_FATAL("prctl() failed");
+ }
+
+ UTI_DropRoot(uid, gid);
+
+ /* Keep CAP_NET_BIND_SERVICE if the NTP server sockets may need to be bound
+ to a privileged port.
+ Keep CAP_NET_RAW if an NTP socket may need to be bound to a device on
+ kernels before 5.7.
+ Keep CAP_SYS_TIME if the clock control is enabled. */
+ if (snprintf(cap_text, sizeof (cap_text), "%s %s %s",
+ (CNF_GetNTPPort() > 0 && CNF_GetNTPPort() < 1024) ?
+ "cap_net_bind_service=ep" : "",
+ (CNF_GetBindNtpInterface() || CNF_GetBindAcquisitionInterface()) &&
+ !SYS_Linux_CheckKernelVersion(5, 7) ? "cap_net_raw=ep" : "",
+ clock_control ? "cap_sys_time=ep" : "") >= sizeof (cap_text))
+ assert(0);
+
+ /* Helpers don't need any capabilities */
+ if (context != SYS_MAIN_PROCESS)
+ cap_text[0] = '\0';
+
+ if ((cap = cap_from_text(cap_text)) == NULL) {
+ LOG_FATAL("cap_from_text() failed");
+ }
+
+ if (cap_set_proc(cap)) {
+ LOG_FATAL("cap_set_proc() failed");
+ }
+
+ cap_free(cap);
+}
+#endif
+
+/* ================================================== */
+
+#ifdef FEAT_SCFILTER
+static
+void check_seccomp_applicability(void)
+{
+ int mail_enabled;
+ double mail_threshold;
+ char *mail_user;
+
+ CNF_GetMailOnChange(&mail_enabled, &mail_threshold, &mail_user);
+ if (mail_enabled)
+ LOG_FATAL("mailonchange directive cannot be used with -F enabled");
+}
+
+/* ================================================== */
+
+void
+SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context)
+{
+ const int allowed[] = {
+ /* Clock */
+ SCMP_SYS(adjtimex),
+ SCMP_SYS(clock_adjtime),
+#ifdef __NR_clock_adjtime64
+ SCMP_SYS(clock_adjtime64),
+#endif
+ SCMP_SYS(clock_gettime),
+#ifdef __NR_clock_gettime64
+ SCMP_SYS(clock_gettime64),
+#endif
+ SCMP_SYS(gettimeofday),
+ SCMP_SYS(settimeofday),
+ SCMP_SYS(time),
+
+ /* Process */
+ SCMP_SYS(clone),
+#ifdef __NR_clone3
+ SCMP_SYS(clone3),
+#endif
+ SCMP_SYS(exit),
+ SCMP_SYS(exit_group),
+ SCMP_SYS(getpid),
+ SCMP_SYS(getrlimit),
+ SCMP_SYS(getuid),
+ SCMP_SYS(getuid32),
+#ifdef __NR_membarrier
+ SCMP_SYS(membarrier),
+#endif
+#ifdef __NR_rseq
+ SCMP_SYS(rseq),
+#endif
+ 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 */
+ SCMP_SYS(brk),
+ SCMP_SYS(madvise),
+ SCMP_SYS(mmap),
+ SCMP_SYS(mmap2),
+ SCMP_SYS(mprotect),
+ SCMP_SYS(mremap),
+ SCMP_SYS(munmap),
+ SCMP_SYS(shmdt),
+
+ /* Filesystem */
+ SCMP_SYS(_llseek),
+ SCMP_SYS(access),
+ SCMP_SYS(chmod),
+ SCMP_SYS(chown),
+ SCMP_SYS(chown32),
+ SCMP_SYS(faccessat),
+ SCMP_SYS(fchmodat),
+ SCMP_SYS(fchownat),
+ SCMP_SYS(fstat),
+ SCMP_SYS(fstat64),
+ SCMP_SYS(fstatat64),
+ SCMP_SYS(getdents),
+ SCMP_SYS(getdents64),
+ SCMP_SYS(lseek),
+ SCMP_SYS(lstat),
+ SCMP_SYS(lstat64),
+ SCMP_SYS(newfstatat),
+ SCMP_SYS(readlink),
+ SCMP_SYS(readlinkat),
+ SCMP_SYS(rename),
+ SCMP_SYS(renameat),
+#ifdef __NR_renameat2
+ SCMP_SYS(renameat2),
+#endif
+ SCMP_SYS(stat),
+ SCMP_SYS(stat64),
+ SCMP_SYS(statfs),
+ SCMP_SYS(statfs64),
+#ifdef __NR_statx
+ SCMP_SYS(statx),
+#endif
+ SCMP_SYS(unlink),
+ SCMP_SYS(unlinkat),
+
+ /* Socket */
+ SCMP_SYS(accept),
+ SCMP_SYS(bind),
+ SCMP_SYS(connect),
+ SCMP_SYS(getsockname),
+ SCMP_SYS(getsockopt),
+ SCMP_SYS(recv),
+ SCMP_SYS(recvfrom),
+ SCMP_SYS(recvmmsg),
+#ifdef __NR_recvmmsg_time64
+ SCMP_SYS(recvmmsg_time64),
+#endif
+ 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 */
+ SCMP_SYS(_newselect),
+ SCMP_SYS(close),
+ SCMP_SYS(open),
+ SCMP_SYS(openat),
+ SCMP_SYS(pipe),
+ SCMP_SYS(pipe2),
+ SCMP_SYS(poll),
+ SCMP_SYS(ppoll),
+#ifdef __NR_ppoll_time64
+ SCMP_SYS(ppoll_time64),
+#endif
+ SCMP_SYS(pread64),
+ SCMP_SYS(pselect6),
+#ifdef __NR_pselect6_time64
+ SCMP_SYS(pselect6_time64),
+#endif
+ SCMP_SYS(read),
+ SCMP_SYS(futex),
+#ifdef __NR_futex_time64
+ SCMP_SYS(futex_time64),
+#endif
+ SCMP_SYS(select),
+ SCMP_SYS(set_robust_list),
+ SCMP_SYS(write),
+ SCMP_SYS(writev),
+
+ /* Miscellaneous */
+ SCMP_SYS(getrandom),
+ SCMP_SYS(sysinfo),
+ SCMP_SYS(uname),
+ };
+
+ const int denied_any[] = {
+ SCMP_SYS(execve),
+#ifdef __NR_execveat
+ SCMP_SYS(execveat),
+#endif
+ SCMP_SYS(fork),
+ SCMP_SYS(ptrace),
+ SCMP_SYS(vfork),
+ };
+
+ const int denied_ntske[] = {
+ SCMP_SYS(ioctl),
+ SCMP_SYS(setsockopt),
+ SCMP_SYS(socket),
+ };
+
+ const int socket_domains[] = {
+ AF_NETLINK, AF_UNIX, AF_INET,
+#ifdef FEAT_IPV6
+ AF_INET6,
+#endif
+ };
+
+ const static int socket_options[][2] = {
+ { SOL_IP, IP_PKTINFO }, { SOL_IP, IP_FREEBIND }, { SOL_IP, IP_TOS },
+#ifdef FEAT_IPV6
+ { SOL_IPV6, IPV6_V6ONLY }, { SOL_IPV6, IPV6_RECVPKTINFO },
+#ifdef IPV6_TCLASS
+ { SOL_IPV6, IPV6_TCLASS },
+#endif
+#endif
+#ifdef SO_BINDTODEVICE
+ { SOL_SOCKET, SO_BINDTODEVICE },
+#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_GETFL, F_SETFL };
+
+ const static unsigned long ioctls[] = {
+ FIONREAD, TCGETS, TIOCGWINSZ,
+#if defined(FEAT_PHC) || defined(HAVE_LINUX_TIMESTAMPING)
+ PTP_EXTTS_REQUEST, PTP_SYS_OFFSET,
+#ifdef PTP_PIN_SETFUNC
+ PTP_PIN_SETFUNC,
+#endif
+#ifdef PTP_SYS_OFFSET_EXTENDED
+ PTP_SYS_OFFSET_EXTENDED,
+#endif
+#ifdef PTP_SYS_OFFSET_PRECISE
+ PTP_SYS_OFFSET_PRECISE,
+#endif
+#endif
+#ifdef FEAT_PPS
+ PPS_FETCH,
+#endif
+#ifdef FEAT_RTC
+ RTC_RD_TIME, RTC_SET_TIME, RTC_UIE_ON, RTC_UIE_OFF,
+#endif
+#ifdef HAVE_LINUX_TIMESTAMPING
+ SIOCETHTOOL,
+#endif
+ };
+
+ unsigned int default_action, deny_action;
+ scmp_filter_ctx *ctx;
+ int i;
+
+ /* Sign of the level determines the deny action (kill or SIGSYS).
+ At level 1, selected syscalls are allowed, others are denied.
+ At level 2, selected syscalls are denied, others are allowed. */
+
+ deny_action = level > 0 ? SCMP_ACT_KILL : SCMP_ACT_TRAP;
+ if (level < 0)
+ level = -level;
+
+ switch (level) {
+ case 1:
+ default_action = deny_action;
+ break;
+ case 2:
+ default_action = SCMP_ACT_ALLOW;
+ break;
+ default:
+ LOG_FATAL("Unsupported filter level");
+ }
+
+ if (context == SYS_MAIN_PROCESS) {
+ /* Check if the chronyd configuration is supported */
+ check_seccomp_applicability();
+
+ /* At level 1, start a helper process which will not have a seccomp filter.
+ It will be used for getaddrinfo(), for which it is difficult to maintain
+ a list of required system calls (with glibc it depends on what NSS
+ modules are installed and enabled on the system). */
+ if (default_action != SCMP_ACT_ALLOW)
+ PRV_StartHelper();
+ }
+
+ ctx = seccomp_init(default_action);
+ if (ctx == NULL)
+ LOG_FATAL("Failed to initialize seccomp");
+
+ if (default_action != SCMP_ACT_ALLOW) {
+ for (i = 0; i < sizeof (allowed) / sizeof (*allowed); i++) {
+ if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, allowed[i], 0) < 0)
+ goto add_failed;
+ }
+ } else {
+ for (i = 0; i < sizeof (denied_any) / sizeof (*denied_any); i++) {
+ if (seccomp_rule_add(ctx, deny_action, denied_any[i], 0) < 0)
+ goto add_failed;
+ }
+
+ if (context == SYS_NTSKE_HELPER) {
+ for (i = 0; i < sizeof (denied_ntske) / sizeof (*denied_ntske); i++) {
+ if (seccomp_rule_add(ctx, deny_action, denied_ntske[i], 0) < 0)
+ goto add_failed;
+ }
+ }
+ }
+
+ if (default_action != SCMP_ACT_ALLOW && 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 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), 2,
+ SCMP_A1(SCMP_CMP_EQ, socket_options[i][0]),
+ SCMP_A2(SCMP_CMP_EQ, socket_options[i][1])))
+ 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 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(context == SYS_MAIN_PROCESS ? LOGS_INFO : LOGS_DEBUG,
+ "Loaded seccomp filter (level %d)", level);
+ seccomp_release(ctx);
+ return;
+
+add_failed:
+ LOG_FATAL("Failed to add seccomp rules");
+}
+#endif
+
+/* ================================================== */
+
+int
+SYS_Linux_CheckKernelVersion(int req_major, int req_minor)
+{
+ int major, minor, patch;
+
+ get_kernel_version(&major, &minor, &patch);
+
+ return kernelvercmp(req_major, req_minor, 0, major, minor, patch) <= 0;
+}
+
+/* ================================================== */
+
+#if defined(FEAT_PHC) || defined(HAVE_LINUX_TIMESTAMPING)
+
+static int
+get_phc_readings(int phc_fd, int max_samples, struct timespec ts[][3])
+{
+ struct ptp_sys_offset sys_off;
+ int i;
+
+ max_samples = CLAMP(0, max_samples, PTP_MAX_SAMPLES);
+
+ /* Silence valgrind */
+ memset(&sys_off, 0, sizeof (sys_off));
+
+ sys_off.n_samples = max_samples;
+
+ if (ioctl(phc_fd, PTP_SYS_OFFSET, &sys_off)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "PTP_SYS_OFFSET", strerror(errno));
+ return 0;
+ }
+
+ for (i = 0; i < max_samples; i++) {
+ ts[i][0].tv_sec = sys_off.ts[i * 2].sec;
+ ts[i][0].tv_nsec = sys_off.ts[i * 2].nsec;
+ ts[i][1].tv_sec = sys_off.ts[i * 2 + 1].sec;
+ ts[i][1].tv_nsec = sys_off.ts[i * 2 + 1].nsec;
+ ts[i][2].tv_sec = sys_off.ts[i * 2 + 2].sec;
+ ts[i][2].tv_nsec = sys_off.ts[i * 2 + 2].nsec;
+ }
+
+ return max_samples;
+}
+
+/* ================================================== */
+
+static int
+get_extended_phc_readings(int phc_fd, int max_samples, struct timespec ts[][3])
+{
+#ifdef PTP_SYS_OFFSET_EXTENDED
+ struct ptp_sys_offset_extended sys_off;
+ int i;
+
+ max_samples = CLAMP(0, max_samples, PTP_MAX_SAMPLES);
+
+ /* Silence valgrind */
+ memset(&sys_off, 0, sizeof (sys_off));
+
+ sys_off.n_samples = max_samples;
+
+ if (ioctl(phc_fd, PTP_SYS_OFFSET_EXTENDED, &sys_off)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "PTP_SYS_OFFSET_EXTENDED", strerror(errno));
+ return 0;
+ }
+
+ for (i = 0; i < max_samples; i++) {
+ ts[i][0].tv_sec = sys_off.ts[i][0].sec;
+ ts[i][0].tv_nsec = sys_off.ts[i][0].nsec;
+ ts[i][1].tv_sec = sys_off.ts[i][1].sec;
+ ts[i][1].tv_nsec = sys_off.ts[i][1].nsec;
+ ts[i][2].tv_sec = sys_off.ts[i][2].sec;
+ ts[i][2].tv_nsec = sys_off.ts[i][2].nsec;
+ }
+
+ return max_samples;
+#else
+ return 0;
+#endif
+}
+
+/* ================================================== */
+
+static int
+get_precise_phc_readings(int phc_fd, int max_samples, struct timespec ts[][3])
+{
+#ifdef PTP_SYS_OFFSET_PRECISE
+ struct ptp_sys_offset_precise sys_off;
+
+ if (max_samples < 1)
+ return 0;
+
+ /* Silence valgrind */
+ memset(&sys_off, 0, sizeof (sys_off));
+
+ if (ioctl(phc_fd, PTP_SYS_OFFSET_PRECISE, &sys_off)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "PTP_SYS_OFFSET_PRECISE",
+ strerror(errno));
+ return 0;
+ }
+
+ ts[0][0].tv_sec = sys_off.sys_realtime.sec;
+ ts[0][0].tv_nsec = sys_off.sys_realtime.nsec;
+ ts[0][1].tv_sec = sys_off.device.sec;
+ ts[0][1].tv_nsec = sys_off.device.nsec;
+ ts[0][2] = ts[0][0];
+
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+/* ================================================== */
+
+int
+SYS_Linux_OpenPHC(const char *path, int phc_index)
+{
+ struct ptp_clock_caps caps;
+ char phc_path[64];
+ int phc_fd;
+
+ if (!path) {
+ if (snprintf(phc_path, sizeof (phc_path), "/dev/ptp%d", phc_index) >= sizeof (phc_path))
+ return -1;
+ path = phc_path;
+ }
+
+ phc_fd = open(path, O_RDONLY);
+ if (phc_fd < 0) {
+ LOG(LOGS_ERR, "Could not open %s : %s", path, strerror(errno));
+ return -1;
+ }
+
+ /* Make sure it is a PHC */
+ if (ioctl(phc_fd, PTP_CLOCK_GETCAPS, &caps)) {
+ LOG(LOGS_ERR, "ioctl(%s) failed : %s", "PTP_CLOCK_GETCAPS", strerror(errno));
+ close(phc_fd);
+ return -1;
+ }
+
+ UTI_FdSetCloexec(phc_fd);
+
+ return phc_fd;
+}
+
+/* ================================================== */
+
+int
+SYS_Linux_GetPHCReadings(int fd, int nocrossts, int *reading_mode, int max_readings,
+ struct timespec tss[][3])
+{
+ int r = 0;
+
+ if ((*reading_mode == 2 || *reading_mode == 0) && !nocrossts &&
+ (r = get_precise_phc_readings(fd, max_readings, tss)) > 0) {
+ *reading_mode = 2;
+ } else if ((*reading_mode == 3 || *reading_mode == 0) &&
+ (r = get_extended_phc_readings(fd, max_readings, tss)) > 0) {
+ *reading_mode = 3;
+ } else if ((*reading_mode == 1 || *reading_mode == 0) &&
+ (r = get_phc_readings(fd, max_readings, tss)) > 0) {
+ *reading_mode = 1;
+ }
+
+ return r;
+}
+
+/* ================================================== */
+
+int
+SYS_Linux_SetPHCExtTimestamping(int fd, int pin, int channel,
+ int rising, int falling, int enable)
+{
+ struct ptp_extts_request extts_req;
+#ifdef PTP_PIN_SETFUNC
+ struct ptp_pin_desc pin_desc;
+
+ memset(&pin_desc, 0, sizeof (pin_desc));
+ pin_desc.index = pin;
+ pin_desc.func = enable ? PTP_PF_EXTTS : PTP_PF_NONE;
+ pin_desc.chan = channel;
+
+ if (pin >= 0 && ioctl(fd, PTP_PIN_SETFUNC, &pin_desc)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "PTP_PIN_SETFUNC", strerror(errno));
+ return 0;
+ }
+#else
+ DEBUG_LOG("Missing PTP_PIN_SETFUNC");
+ return 0;
+#endif
+
+ memset(&extts_req, 0, sizeof (extts_req));
+ extts_req.index = channel;
+ extts_req.flags = (enable ? PTP_ENABLE_FEATURE : 0) |
+ (rising ? PTP_RISING_EDGE : 0) |
+ (falling ? PTP_FALLING_EDGE : 0);
+
+ if (ioctl(fd, PTP_EXTTS_REQUEST, &extts_req)) {
+ DEBUG_LOG("ioctl(%s) failed : %s", "PTP_EXTTS_REQUEST", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+SYS_Linux_ReadPHCExtTimestamp(int fd, struct timespec *phc_ts, int *channel)
+{
+ struct ptp_extts_event extts_event;
+ struct pollfd pfd;
+
+ /* Make sure the read will not block in case we have multiple
+ descriptors of the same PHC (O_NONBLOCK does not work) */
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ if (poll(&pfd, 1, 0) != 1 || pfd.revents != POLLIN) {
+ DEBUG_LOG("Missing PHC extts event");
+ return 0;
+ }
+
+ if (read(fd, &extts_event, sizeof (extts_event)) != sizeof (extts_event)) {
+ DEBUG_LOG("Could not read PHC extts event");
+ return 0;
+ }
+
+ phc_ts->tv_sec = extts_event.t.sec;
+ phc_ts->tv_nsec = extts_event.t.nsec;
+ *channel = extts_event.index;
+
+ return 1;
+}
+
+#endif
diff --git a/sys_linux.h b/sys_linux.h
new file mode 100644
index 0000000..4741624
--- /dev/null
+++ b/sys_linux.h
@@ -0,0 +1,52 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ The header file for the linux driver
+ */
+
+#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, SYS_ProcessContext context, int clock_control);
+
+extern void SYS_Linux_EnableSystemCallFilter(int level, SYS_ProcessContext context);
+
+extern int SYS_Linux_CheckKernelVersion(int req_major, int req_minor);
+
+extern int SYS_Linux_OpenPHC(const char *path, int phc_index);
+
+extern int SYS_Linux_GetPHCReadings(int fd, int nocrossts, int *reading_mode, int max_readings,
+ struct timespec tss[][3]);
+
+extern int SYS_Linux_SetPHCExtTimestamping(int fd, int pin, int channel,
+ int rising, int falling, int enable);
+
+extern int SYS_Linux_ReadPHCExtTimestamp(int fd, struct timespec *phc_ts, int *channel);
+
+#endif /* GOT_SYS_LINUX_H */
diff --git a/sys_macosx.c b/sys_macosx.c
new file mode 100644
index 0000000..e3a38ed
--- /dev/null
+++ b/sys_macosx.c
@@ -0,0 +1,516 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2001
+ * Copyright (C) J. Hannken-Illjes 2001
+ * Copyright (C) Bryan Christianson 2015, 2017, 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Driver file for the macOS operating system.
+
+ */
+
+#include "config.h"
+
+#ifdef MACOSX
+
+#include "sysincl.h"
+
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <pthread.h>
+
+#include "sys_macosx.h"
+#include "conf.h"
+#include "local.h"
+#include "localp.h"
+#include "logging.h"
+#include "sched.h"
+#include "privops.h"
+#include "util.h"
+
+#include <dlfcn.h>
+
+#ifdef HAVE_MACOS_SYS_TIMEX
+#include "sys_netbsd.h"
+
+static int have_ntp_adjtime = 0;
+#endif
+
+/* ================================================== */
+
+/* This register contains the number of seconds by which the local
+ clock was estimated to be fast of reference time at the epoch when
+ LCL_ReadRawTime() returned T0 */
+
+static double offset_register;
+
+/* This register contains the epoch to which the offset is referenced */
+
+static struct timespec T0;
+
+/* This register contains the current estimate of the system
+ frequency, in absolute (NOT ppm) */
+
+static double current_freq;
+
+/* This register contains the number of seconds of adjustment that
+ were passed to adjtime last time it was called. */
+
+static double adjustment_requested;
+
+/* Interval in seconds between adjustments to cancel systematic drift */
+
+#define DRIFT_REMOVAL_INTERVAL (4.0)
+#define DRIFT_REMOVAL_INTERVAL_MIN (0.5)
+
+/* If current_drift_removal_interval / drift_removal_interval exceeds this
+ ratio, then restart the drift removal timer */
+
+#define DRIFT_REMOVAL_RESTART_RATIO (8.0)
+
+static double drift_removal_interval;
+static double current_drift_removal_interval;
+static struct timespec Tdrift;
+
+/* weighting applied to error in calculating drift_removal_interval */
+#define ERROR_WEIGHT (0.5)
+
+/* minimum resolution of current_frequency */
+#define FREQUENCY_RES (1.0e-9)
+
+#define NANOS_PER_MSEC (1000000ULL)
+
+/* RTC synchronisation - once an hour */
+
+static struct timespec last_rtc_sync;
+#define RTC_SYNC_INTERVAL (60 * 60.0)
+
+/* ================================================== */
+
+static void
+clock_initialise(void)
+{
+ struct timeval newadj, oldadj;
+
+ offset_register = 0.0;
+ adjustment_requested = 0.0;
+ current_freq = 0.0;
+ drift_removal_interval = DRIFT_REMOVAL_INTERVAL;
+ current_drift_removal_interval = DRIFT_REMOVAL_INTERVAL;
+
+ LCL_ReadRawTime(&T0);
+ Tdrift = T0;
+ last_rtc_sync = T0;
+
+ newadj.tv_sec = 0;
+ newadj.tv_usec = 0;
+
+ if (PRV_AdjustTime(&newadj, &oldadj) < 0) {
+ LOG_FATAL("adjtime() failed");
+ }
+}
+
+/* ================================================== */
+
+static void
+clock_finalise(void)
+{
+ /* Nothing to do yet */
+}
+
+/* ================================================== */
+
+static void
+start_adjust(void)
+{
+ struct timeval newadj, oldadj;
+ struct timespec T1;
+ double elapsed, accrued_error, predicted_error, drift_removal_elapsed;
+ double adjust_required;
+ double rounding_error;
+ double old_adjust_remaining;
+
+ /* Determine the amount of error built up since the last adjustment */
+ LCL_ReadRawTime(&T1);
+
+ elapsed = UTI_DiffTimespecsToDouble(&T1, &T0);
+ accrued_error = elapsed * current_freq;
+
+ drift_removal_elapsed = UTI_DiffTimespecsToDouble(&T1, &Tdrift);
+
+ /* To allow for the clock being stepped either forward or backwards, clamp
+ the elapsed time to bounds [ 0.0, current_drift_removal_interval ] */
+ drift_removal_elapsed = MIN(MAX(0.0, drift_removal_elapsed), current_drift_removal_interval);
+
+ predicted_error = (current_drift_removal_interval - drift_removal_elapsed) / 2.0 * current_freq;
+
+ DEBUG_LOG("drift_removal_elapsed: %.3f current_drift_removal_interval: %.3f predicted_error: %.3f",
+ 1.0e6 * drift_removal_elapsed, 1.0e6 * current_drift_removal_interval,
+ 1.0e6 * predicted_error);
+
+ adjust_required = - (accrued_error + offset_register + predicted_error);
+
+ UTI_DoubleToTimeval(adjust_required, &newadj);
+ adjustment_requested = UTI_TimevalToDouble(&newadj);
+ rounding_error = adjust_required - adjustment_requested;
+
+ if (PRV_AdjustTime(&newadj, &oldadj) < 0) {
+ LOG_FATAL("adjtime() failed");
+ }
+
+ old_adjust_remaining = UTI_TimevalToDouble(&oldadj);
+
+ offset_register = rounding_error - old_adjust_remaining - predicted_error;
+
+ T0 = T1;
+}
+
+/* ================================================== */
+
+static void
+stop_adjust(void)
+{
+ struct timespec T1;
+ struct timeval zeroadj, remadj;
+ double adjustment_remaining, adjustment_achieved;
+ double elapsed, elapsed_plus_adjust;
+
+ zeroadj.tv_sec = 0;
+ zeroadj.tv_usec = 0;
+
+ if (PRV_AdjustTime(&zeroadj, &remadj) < 0) {
+ LOG_FATAL("adjtime() failed");
+ }
+
+ LCL_ReadRawTime(&T1);
+
+ elapsed = UTI_DiffTimespecsToDouble(&T1, &T0);
+ adjustment_remaining = UTI_TimevalToDouble(&remadj);
+
+ adjustment_achieved = adjustment_requested - adjustment_remaining;
+ elapsed_plus_adjust = elapsed - adjustment_achieved;
+
+ offset_register += current_freq * elapsed_plus_adjust - adjustment_remaining;
+
+ adjustment_requested = 0.0;
+ T0 = T1;
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+ slew backwards */
+
+static void
+accrue_offset(double offset, double corr_rate)
+{
+ stop_adjust();
+ offset_register += offset;
+ start_adjust();
+}
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+ step backwards */
+
+static int
+apply_step_offset(double offset)
+{
+ struct timespec old_time, new_time, T1;
+ struct timeval new_time_tv;
+
+ stop_adjust();
+
+ LCL_ReadRawTime(&old_time);
+
+ UTI_AddDoubleToTimespec(&old_time, -offset, &new_time);
+ UTI_TimespecToTimeval(&new_time, &new_time_tv);
+
+ if (PRV_SetTime(&new_time_tv, NULL) < 0) {
+ DEBUG_LOG("settimeofday() failed");
+ return 0;
+ }
+
+ UTI_AddDoubleToTimespec(&T0, -offset, &T1);
+ T0 = T1;
+
+ start_adjust();
+
+ return 1;
+}
+
+/* ================================================== */
+
+static double
+set_frequency(double new_freq_ppm)
+{
+ stop_adjust();
+ current_freq = new_freq_ppm * 1.0e-6;
+ start_adjust();
+
+ return current_freq * 1.0e6;
+}
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+ return current_freq * 1.0e6;
+}
+
+/* ================================================== */
+
+static void
+get_offset_correction(struct timespec *raw,
+ double *corr, double *err)
+{
+ stop_adjust();
+ *corr = -offset_register;
+ start_adjust();
+ if (err)
+ *err = 0.0;
+}
+
+/* ================================================== */
+
+/* Cancel systematic drift */
+
+static SCH_TimeoutID drift_removal_id;
+
+/* ================================================== */
+/* This is the timer callback routine which is called periodically to
+ invoke a time adjustment to take out the machine's drift.
+ Otherwise, times reported through this software (e.g. by running
+ ntpdate from another machine) show the machine being correct (since
+ they correct for drift build-up), but any program on this machine
+ that reads the system time will be given an erroneous value, the
+ degree of error depending on how long it is since
+ get_offset_correction was last called. */
+
+static void
+drift_removal_timeout(SCH_ArbitraryArgument not_used)
+{
+
+ stop_adjust();
+
+ LCL_ReadRawTime(&Tdrift);
+
+ current_drift_removal_interval = drift_removal_interval;
+
+ start_adjust();
+
+ drift_removal_id = SCH_AddTimeoutByDelay(drift_removal_interval, drift_removal_timeout, NULL);
+}
+
+/* ================================================== */
+
+/* use est_error to calculate the drift_removal_interval and
+ update the RTC */
+
+static void
+set_sync_status(int synchronised, double est_error, double max_error)
+{
+ double interval;
+
+ if (!synchronised) {
+ drift_removal_interval = MAX(drift_removal_interval, DRIFT_REMOVAL_INTERVAL);
+ } else {
+ if (CNF_GetRtcSync()) {
+ struct timespec now;
+ double rtc_sync_elapsed;
+
+ SCH_GetLastEventTime(NULL, NULL, &now);
+ rtc_sync_elapsed = UTI_DiffTimespecsToDouble(&now, &last_rtc_sync);
+ if (fabs(rtc_sync_elapsed) >= RTC_SYNC_INTERVAL) {
+ /* update the RTC by applying a step of 0.0 secs */
+ apply_step_offset(0.0);
+ last_rtc_sync = now;
+ DEBUG_LOG("rtc synchronised");
+ }
+ }
+
+ interval = ERROR_WEIGHT * est_error / (fabs(current_freq) + FREQUENCY_RES);
+ drift_removal_interval = MAX(interval, DRIFT_REMOVAL_INTERVAL_MIN);
+
+ DEBUG_LOG("est_error: %.3f current_freq: %.3f est drift_removal_interval: %.3f act drift_removal_interval: %.3f",
+ est_error * 1.0e6, current_freq * 1.0e6, interval, drift_removal_interval);
+ }
+
+ if (current_drift_removal_interval / drift_removal_interval > DRIFT_REMOVAL_RESTART_RATIO) {
+ /* recover from a large est_error by resetting the timer */
+ SCH_ArbitraryArgument unused;
+ SCH_RemoveTimeout(drift_removal_id);
+ unused = NULL;
+ drift_removal_timeout(unused);
+ }
+}
+
+/* ================================================== */
+/*
+ Give chronyd real time priority so that time critical calculations
+ are not pre-empted by the kernel.
+*/
+
+static int
+set_realtime(void)
+{
+ /* https://developer.apple.com/library/ios/technotes/tn2169/_index.html */
+
+ mach_timebase_info_data_t timebase_info;
+ double clock2abs;
+ thread_time_constraint_policy_data_t policy;
+ int kr;
+
+ mach_timebase_info(&timebase_info);
+ clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC;
+
+ policy.period = 0;
+ policy.computation = (uint32_t)(5 * clock2abs); /* 5 ms of work */
+ policy.constraint = (uint32_t)(10 * clock2abs);
+ policy.preemptible = 0;
+
+ kr = thread_policy_set(
+ pthread_mach_thread_np(pthread_self()),
+ THREAD_TIME_CONSTRAINT_POLICY,
+ (thread_policy_t)&policy,
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);
+
+ if (kr != KERN_SUCCESS) {
+ LOG(LOGS_WARN, "Cannot set real-time priority: %d", kr);
+ return -1;
+ }
+ return 0;
+}
+
+/* ================================================== */
+
+void
+SYS_MacOSX_SetScheduler(int SchedPriority)
+{
+ if (SchedPriority) {
+ set_realtime();
+ }
+}
+
+/* ================================================== */
+
+#ifdef FEAT_PRIVDROP
+void SYS_MacOSX_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context)
+{
+ if (context == SYS_MAIN_PROCESS)
+ PRV_StartHelper();
+
+ UTI_DropRoot(uid, gid);
+}
+#endif
+
+/* ================================================== */
+
+static void
+legacy_MacOSX_Initialise(void)
+{
+ clock_initialise();
+
+ lcl_RegisterSystemDrivers(read_frequency, set_frequency,
+ accrue_offset, apply_step_offset,
+ get_offset_correction,
+ NULL /* set_leap */,
+ set_sync_status);
+
+
+ drift_removal_id = SCH_AddTimeoutByDelay(drift_removal_interval, drift_removal_timeout, NULL);
+}
+
+/* ================================================== */
+
+static void
+legacy_MacOSX_Finalise(void)
+{
+ SCH_RemoveTimeout(drift_removal_id);
+
+ clock_finalise();
+}
+
+/* ================================================== */
+
+#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)
+{
+#ifdef HAVE_MACOS_SYS_TIMEX
+ have_ntp_adjtime = (dlsym(RTLD_NEXT, "ntp_adjtime") != NULL);
+ if (have_ntp_adjtime) {
+ SYS_NetBSD_Initialise();
+ return;
+ }
+#endif
+ legacy_MacOSX_Initialise();
+}
+
+/* ================================================== */
+
+void
+SYS_MacOSX_Finalise(void)
+{
+#ifdef HAVE_MACOS_SYS_TIMEX
+ if (have_ntp_adjtime) {
+ SYS_NetBSD_Finalise();
+ return;
+ }
+#endif
+ legacy_MacOSX_Finalise();
+}
+
+#endif
diff --git a/sys_macosx.h b/sys_macosx.h
new file mode 100644
index 0000000..09f0beb
--- /dev/null
+++ b/sys_macosx.h
@@ -0,0 +1,40 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2001
+ * Copyright (C) J. Hannken-Illjes 2001
+ * Copyright (C) Bryan Christianson 2015
+ *
+ * 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 macOS driver
+
+ */
+
+#ifndef GOT_SYS_MACOSX_H
+#define GOT_SYS_MACOSX_H
+
+#include "sys.h"
+
+void SYS_MacOSX_SetScheduler(int SchedPriority);
+void SYS_MacOSX_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context);
+void SYS_MacOSX_Initialise(void);
+void SYS_MacOSX_Finalise(void);
+
+#endif
diff --git a/sys_netbsd.c b/sys_netbsd.c
new file mode 100644
index 0000000..e1b99bb
--- /dev/null
+++ b/sys_netbsd.c
@@ -0,0 +1,158 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2001
+ * Copyright (C) J. Hannken-Illjes 2001
+ * Copyright (C) Miroslav Lichvar 2015
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Driver file for the NetBSD and FreeBSD operating system.
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "sys_netbsd.h"
+#include "sys_timex.h"
+#include "logging.h"
+#include "privops.h"
+#include "util.h"
+
+/* Maximum frequency offset accepted by the kernel (in ppm) */
+#define MAX_FREQ 500.0
+
+/* Minimum assumed rate at which the kernel updates the clock frequency */
+#define MIN_TICK_RATE 100
+
+/* Interval between kernel updates of the adjtime() offset */
+#define ADJTIME_UPDATE_INTERVAL 1.0
+
+/* Maximum adjtime() slew rate (in ppm) */
+#define MAX_ADJTIME_SLEWRATE 5000.0
+
+/* Minimum offset adjtime() slews faster than MAX_FREQ */
+#define MIN_FASTSLEW_OFFSET 1.0
+
+/* ================================================== */
+
+/* Positive offset means system clock is fast of true time, therefore
+ slew backwards */
+
+static void
+accrue_offset(double offset, double corr_rate)
+{
+ struct timeval newadj, oldadj;
+ double doldadj;
+
+ UTI_DoubleToTimeval(-offset, &newadj);
+
+ if (PRV_AdjustTime(&newadj, &oldadj) < 0)
+ LOG_FATAL("adjtime() failed");
+
+ /* Add the old remaining adjustment if not zero */
+ doldadj = UTI_TimevalToDouble(&oldadj);
+ if (doldadj != 0.0) {
+ UTI_DoubleToTimeval(-offset + doldadj, &newadj);
+ if (PRV_AdjustTime(&newadj, NULL) < 0)
+ LOG_FATAL("adjtime() failed");
+ }
+}
+
+/* ================================================== */
+
+static void
+get_offset_correction(struct timespec *raw,
+ double *corr, double *err)
+{
+ struct timeval remadj;
+ double adjustment_remaining;
+#ifdef MACOSX
+ struct timeval tv = {0, 0};
+
+ if (PRV_AdjustTime(&tv, &remadj) < 0)
+ LOG_FATAL("adjtime() failed");
+
+ if (PRV_AdjustTime(&remadj, NULL) < 0)
+ LOG_FATAL("adjtime() failed");
+#else
+ if (PRV_AdjustTime(NULL, &remadj) < 0)
+ LOG_FATAL("adjtime() failed");
+#endif
+
+ adjustment_remaining = UTI_TimevalToDouble(&remadj);
+
+ *corr = adjustment_remaining;
+ if (err) {
+ if (*corr != 0.0)
+ *err = 1.0e-6 * MAX_ADJTIME_SLEWRATE / ADJTIME_UPDATE_INTERVAL;
+ else
+ *err = 0.0;
+ }
+}
+
+/* ================================================== */
+
+void
+SYS_NetBSD_Initialise(void)
+{
+ SYS_Timex_InitialiseWithFunctions(MAX_FREQ, 1.0 / MIN_TICK_RATE,
+ NULL, NULL, NULL,
+ MIN_FASTSLEW_OFFSET, MAX_ADJTIME_SLEWRATE,
+ accrue_offset, get_offset_correction);
+}
+
+/* ================================================== */
+
+void
+SYS_NetBSD_Finalise(void)
+{
+ SYS_Timex_Finalise();
+}
+
+/* ================================================== */
+
+#ifdef FEAT_PRIVDROP
+void
+SYS_NetBSD_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context, int clock_control)
+{
+#ifdef NETBSD
+ int fd;
+#endif
+
+ /* On NetBSD the helper is used only for socket binding, but on FreeBSD
+ it's used also for setting and adjusting the system clock */
+ if (context == SYS_MAIN_PROCESS)
+ PRV_StartHelper();
+
+ UTI_DropRoot(uid, gid);
+
+#ifdef NETBSD
+ if (!clock_control)
+ return;
+
+ /* Check if we have write access to /dev/clockctl */
+ fd = open("/dev/clockctl", O_WRONLY);
+ if (fd < 0)
+ LOG_FATAL("Can't write to /dev/clockctl");
+ close(fd);
+#endif
+}
+#endif
diff --git a/sys_netbsd.h b/sys_netbsd.h
new file mode 100644
index 0000000..7a05e6c
--- /dev/null
+++ b/sys_netbsd.h
@@ -0,0 +1,39 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2001
+ * Copyright (C) J. Hannken-Illjes 2001
+ *
+ * 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 NetBSD driver
+ */
+
+#ifndef GOT_SYS_NETBSD_H
+#define GOT_SYS_NETBSD_H
+
+#include "sys.h"
+
+void SYS_NetBSD_Initialise(void);
+
+void SYS_NetBSD_Finalise(void);
+
+void SYS_NetBSD_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context, int clock_control);
+
+#endif
diff --git a/sys_null.c b/sys_null.c
new file mode 100644
index 0000000..3a0d5f6
--- /dev/null
+++ b/sys_null.c
@@ -0,0 +1,140 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2017
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Null clock driver for operation with no clock control.
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "sys_null.h"
+
+#include "local.h"
+#include "localp.h"
+#include "logging.h"
+#include "util.h"
+
+/* Current frequency offset of the system clock (in ppm) */
+static double freq;
+
+/* Offset of the system clock at the last update */
+static double offset_register;
+
+/* Time of the last update */
+static struct timespec last_update;
+
+/* Minimum interval between updates when frequency is constant */
+#define MIN_UPDATE_INTERVAL 1000.0
+
+/* ================================================== */
+
+static void
+update_offset(void)
+{
+ struct timespec now;
+ double duration;
+
+ LCL_ReadRawTime(&now);
+ duration = UTI_DiffTimespecsToDouble(&now, &last_update);
+ offset_register += 1.0e-6 * freq * duration;
+ last_update = now;
+
+ DEBUG_LOG("System clock offset=%e freq=%f", offset_register, freq);
+}
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+ return freq;
+}
+
+/* ================================================== */
+
+static double
+set_frequency(double freq_ppm)
+{
+ update_offset();
+ freq = freq_ppm;
+
+ return freq;
+}
+
+/* ================================================== */
+
+static void
+accrue_offset(double offset, double corr_rate)
+{
+ offset_register += offset;
+}
+
+/* ================================================== */
+
+static int
+apply_step_offset(double offset)
+{
+ return 0;
+}
+
+/* ================================================== */
+
+static void
+offset_convert(struct timespec *raw, double *corr, double *err)
+{
+ double duration;
+
+ duration = UTI_DiffTimespecsToDouble(raw, &last_update);
+
+ if (duration > MIN_UPDATE_INTERVAL) {
+ update_offset();
+ duration = 0.0;
+ }
+
+ *corr = -1.0e-6 * freq * duration - offset_register;
+
+ if (err)
+ *err = 0.0;
+}
+
+/* ================================================== */
+
+void
+SYS_Null_Initialise(void)
+{
+ offset_register = 0.0;
+ LCL_ReadRawTime(&last_update);
+
+ lcl_RegisterSystemDrivers(read_frequency, set_frequency, accrue_offset,
+ apply_step_offset, offset_convert, NULL, NULL);
+
+ LOG(LOGS_INFO, "Disabled control of system clock");
+}
+
+/* ================================================== */
+
+void
+SYS_Null_Finalise(void)
+{
+}
diff --git a/sys_null.h b/sys_null.h
new file mode 100644
index 0000000..0fbf077
--- /dev/null
+++ b/sys_null.h
@@ -0,0 +1,34 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2017
+ *
+ * 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 null clock driver
+ */
+
+#ifndef GOT_SYS_NULL_H
+#define GOT_SYS_NULL_H
+
+extern void SYS_Null_Initialise(void);
+
+extern void SYS_Null_Finalise(void);
+
+#endif
diff --git a/sys_posix.c b/sys_posix.c
new file mode 100644
index 0000000..356e86a
--- /dev/null
+++ b/sys_posix.c
@@ -0,0 +1,109 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) John G. Hasler 2009
+ * Copyright (C) Miroslav Lichvar 2009-2012, 2014-2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ This module is for POSIX compliant operating systems.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <sys/utsname.h>
+
+#if defined(HAVE_PTHREAD_SETSCHEDPARAM)
+#include <pthread.h>
+#include <sched.h>
+#endif
+
+#if defined(HAVE_MLOCKALL)
+#include <sys/mman.h>
+#endif
+#if defined(HAVE_SETRLIMIT_MEMLOCK)
+#include <sys/resource.h>
+#endif
+
+#include "sys_posix.h"
+#include "conf.h"
+#include "local.h"
+#include "logging.h"
+#include "util.h"
+
+/* ================================================== */
+
+#if defined(HAVE_PTHREAD_SETSCHEDPARAM)
+/* Install SCHED_FIFO real-time scheduler with specified priority */
+void
+SYS_Posix_SetScheduler(int priority)
+{
+ struct sched_param sched;
+ int pmax, pmin;
+
+ if (priority < 1 || priority > 99)
+ LOG_FATAL("Bad scheduler priority: %d", priority);
+
+ sched.sched_priority = priority;
+ pmax = sched_get_priority_max(SCHED_FIFO);
+ pmin = sched_get_priority_min(SCHED_FIFO);
+ if (priority > pmax) {
+ sched.sched_priority = pmax;
+ } else if (priority < pmin) {
+ sched.sched_priority = pmin;
+ }
+
+ if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sched) < 0) {
+ LOG(LOGS_ERR, "pthread_setschedparam() failed");
+ } else {
+ DEBUG_LOG("Enabled SCHED_FIFO with priority %d", sched.sched_priority);
+ }
+}
+#endif /* HAVE_PTHREAD_SETSCHEDPARAM */
+
+/* ================================================== */
+
+#if defined(HAVE_MLOCKALL)
+/* Lock the process into RAM so that it will never be swapped out */
+void
+SYS_Posix_MemLockAll(void)
+{
+#if defined(HAVE_SETRLIMIT_MEMLOCK)
+ struct rlimit rlim;
+
+ /* Ensure we can reserve as much as we need */
+ rlim.rlim_max = RLIM_INFINITY;
+ rlim.rlim_cur = RLIM_INFINITY;
+ if (setrlimit(RLIMIT_MEMLOCK, &rlim) < 0) {
+ LOG(LOGS_ERR, "setrlimit() failed");
+ return;
+ }
+#endif
+
+ if (mlockall(MCL_CURRENT|MCL_FUTURE) < 0) {
+ LOG(LOGS_ERR, "mlockall() failed");
+ } else {
+ DEBUG_LOG("Successfully locked into RAM");
+ }
+}
+#endif /* HAVE_MLOCKALL */
diff --git a/sys_posix.h b/sys_posix.h
new file mode 100644
index 0000000..bb34b80
--- /dev/null
+++ b/sys_posix.h
@@ -0,0 +1,36 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) John G. Hasler 2009
+ * Copyright (C) Miroslav Lichvar 2009-2012, 2014-2018
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ The header file for shared Posix functionality
+ */
+
+#ifndef GOT_SYS_POSIX_H
+#define GOT_SYS_POSIX_H
+
+extern void SYS_Posix_MemLockAll(void);
+
+extern void SYS_Posix_SetScheduler(int priority);
+
+#endif /* GOT_SYS_POSIX_H */
diff --git a/sys_solaris.c b/sys_solaris.c
new file mode 100644
index 0000000..3210ef0
--- /dev/null
+++ b/sys_solaris.c
@@ -0,0 +1,95 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Driver file for illumos operating system (previously Solaris)
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "logging.h"
+#include "privops.h"
+#include "sys_solaris.h"
+#include "sys_timex.h"
+#include "util.h"
+
+#include <kvm.h>
+#include <nlist.h>
+
+/* ================================================== */
+
+static void
+set_dosynctodr(int on_off)
+{
+ struct nlist nl[] = { {"dosynctodr"}, {NULL} };
+ kvm_t *kt;
+
+ kt = kvm_open(NULL, NULL, NULL, O_RDWR, NULL);
+ if (!kt)
+ LOG_FATAL("Could not open kvm");
+
+ if (kvm_nlist(kt, nl) < 0 || !nl[0].n_value)
+ LOG_FATAL("Could not get dosynctodr address");
+
+ if (kvm_kwrite(kt, nl[0].n_value, &on_off, sizeof (on_off)) < 0)
+ LOG_FATAL("Could not write to dosynctodr");
+
+ kvm_close(kt);
+}
+
+/* ================================================== */
+
+void
+SYS_Solaris_Initialise(void)
+{
+ /* The kernel keeps the system clock and hardware clock synchronised to each
+ other. The dosynctodr variable needs to be set to zero to prevent the
+ the system clock from following the hardware clock when the system clock
+ is not adjusted by adjtime() or ntp_adjtime(modes=MOD_OFFSET). */
+ set_dosynctodr(0);
+
+ /* The kernel allows the frequency to be set in the full range off int32_t */
+ SYS_Timex_InitialiseWithFunctions(32500, 1.0 / 100, NULL, NULL, NULL,
+ 0.0, 0.0, NULL, NULL);
+}
+
+/* ================================================== */
+
+void
+SYS_Solaris_Finalise(void)
+{
+ SYS_Timex_Finalise();
+}
+
+/* ================================================== */
+
+#ifdef FEAT_PRIVDROP
+void
+SYS_Solaris_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context)
+{
+ if (context == SYS_MAIN_PROCESS)
+ PRV_StartHelper();
+ UTI_DropRoot(uid, gid);
+}
+#endif
diff --git a/sys_solaris.h b/sys_solaris.h
new file mode 100644
index 0000000..5979232
--- /dev/null
+++ b/sys_solaris.h
@@ -0,0 +1,38 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2002
+ *
+ * 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 Solaris driver
+ */
+
+#ifndef GOT_SYS_SOLARIS_H
+#define GOT_SYS_SOLARIS_H
+
+#include "sys.h"
+
+void SYS_Solaris_Initialise(void);
+
+void SYS_Solaris_Finalise(void);
+
+void SYS_Solaris_DropRoot(uid_t uid, gid_t gid, SYS_ProcessContext context);
+
+#endif
diff --git a/sys_timex.c b/sys_timex.c
new file mode 100644
index 0000000..0ee6c8e
--- /dev/null
+++ b/sys_timex.c
@@ -0,0 +1,276 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009-2012, 2014-2015, 2017
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Driver for systems that implement the adjtimex()/ntp_adjtime() system call
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "conf.h"
+#include "privops.h"
+#include "sys_generic.h"
+#include "sys_timex.h"
+#include "logging.h"
+
+#ifdef PRIVOPS_ADJUSTTIMEX
+#define NTP_ADJTIME PRV_AdjustTimex
+#define NTP_ADJTIME_NAME "ntp_adjtime"
+#else
+#ifdef LINUX
+#define NTP_ADJTIME adjtimex
+#define NTP_ADJTIME_NAME "adjtimex"
+#else
+#define NTP_ADJTIME ntp_adjtime
+#define NTP_ADJTIME_NAME "ntp_adjtime"
+#endif
+#endif
+
+/* Maximum frequency offset accepted by the kernel (in ppm) */
+#define MAX_FREQ 500.0
+
+/* Frequency scale to convert from ppm to the timex freq */
+#define FREQ_SCALE (double)(1 << 16)
+
+/* Threshold for the timex maxerror when the kernel sets the UNSYNC flag */
+#define MAX_SYNC_ERROR 16.0
+
+/* Minimum assumed rate at which the kernel updates the clock frequency */
+#define MIN_TICK_RATE 100
+
+/* Saved timex status */
+static int sys_status;
+
+/* Saved TAI-UTC offset */
+static int sys_tai_offset;
+
+/* ================================================== */
+
+static double
+convert_timex_frequency(const struct timex *txc)
+{
+ double freq_ppm;
+
+ freq_ppm = txc->freq / FREQ_SCALE;
+
+ return -freq_ppm;
+}
+
+/* ================================================== */
+
+static double
+read_frequency(void)
+{
+ struct timex txc;
+
+ txc.modes = 0;
+
+ SYS_Timex_Adjust(&txc, 0);
+
+ return convert_timex_frequency(&txc);
+}
+
+/* ================================================== */
+
+static double
+set_frequency(double freq_ppm)
+{
+ struct timex txc;
+
+ txc.modes = MOD_FREQUENCY;
+ txc.freq = freq_ppm * -FREQ_SCALE;
+
+ SYS_Timex_Adjust(&txc, 0);
+
+ return convert_timex_frequency(&txc);
+}
+
+/* ================================================== */
+
+static void
+set_leap(int leap, int tai_offset)
+{
+ struct timex txc;
+ int applied, prev_status;
+
+ txc.modes = 0;
+ applied = SYS_Timex_Adjust(&txc, 0) == TIME_WAIT;
+
+ prev_status = sys_status;
+ sys_status &= ~(STA_INS | STA_DEL);
+
+ if (leap > 0)
+ sys_status |= STA_INS;
+ else if (leap < 0)
+ sys_status |= STA_DEL;
+
+ txc.modes = MOD_STATUS;
+ txc.status = sys_status;
+
+#ifdef MOD_TAI
+ if (tai_offset) {
+ txc.modes |= MOD_TAI;
+ txc.constant = tai_offset;
+
+ if (applied && !(sys_status & (STA_INS | STA_DEL)))
+ sys_tai_offset += prev_status & STA_INS ? 1 : -1;
+
+ if (sys_tai_offset != tai_offset) {
+ sys_tai_offset = tai_offset;
+ LOG(LOGS_INFO, "System clock TAI offset set to %d seconds", tai_offset);
+ }
+ }
+#endif
+
+ SYS_Timex_Adjust(&txc, 0);
+
+ if (prev_status != sys_status) {
+ LOG(LOGS_INFO, "System clock status %s leap second",
+ leap ? (leap > 0 ? "set to insert" : "set to delete") :
+ (applied ? "reset after" : "set to not insert/delete"));
+ }
+}
+
+/* ================================================== */
+
+static void
+set_sync_status(int synchronised, double est_error, double max_error)
+{
+ struct timex txc;
+
+ if (synchronised) {
+ if (est_error > MAX_SYNC_ERROR)
+ est_error = MAX_SYNC_ERROR;
+ if (max_error >= MAX_SYNC_ERROR) {
+ max_error = MAX_SYNC_ERROR;
+ synchronised = 0;
+ }
+ } else {
+ est_error = max_error = MAX_SYNC_ERROR;
+ }
+
+#ifdef LINUX
+ /* On Linux clear the UNSYNC flag only if rtcsync is enabled */
+ if (!CNF_GetRtcSync())
+ synchronised = 0;
+#endif
+
+ if (synchronised)
+ sys_status &= ~STA_UNSYNC;
+ else
+ sys_status |= STA_UNSYNC;
+
+ txc.modes = MOD_STATUS | MOD_ESTERROR | MOD_MAXERROR;
+ txc.status = sys_status;
+ txc.esterror = est_error * 1.0e6;
+ txc.maxerror = max_error * 1.0e6;
+
+ if (SYS_Timex_Adjust(&txc, 1) < 0)
+ ;
+}
+
+/* ================================================== */
+
+static void
+initialise_timex(void)
+{
+ struct timex txc;
+
+ sys_status = STA_UNSYNC;
+ sys_tai_offset = 0;
+
+ /* Reset PLL offset */
+ txc.modes = MOD_OFFSET | MOD_STATUS;
+ txc.status = STA_PLL | sys_status;
+ txc.offset = 0;
+ SYS_Timex_Adjust(&txc, 0);
+
+ /* Turn PLL off */
+ txc.modes = MOD_STATUS;
+ txc.status = sys_status;
+ SYS_Timex_Adjust(&txc, 0);
+}
+
+/* ================================================== */
+
+void
+SYS_Timex_Initialise(void)
+{
+ SYS_Timex_InitialiseWithFunctions(MAX_FREQ, 1.0 / MIN_TICK_RATE, NULL, NULL, NULL,
+ 0.0, 0.0, NULL, NULL);
+}
+
+/* ================================================== */
+
+void
+SYS_Timex_InitialiseWithFunctions(double max_set_freq_ppm, double max_set_freq_delay,
+ lcl_ReadFrequencyDriver sys_read_freq,
+ lcl_SetFrequencyDriver sys_set_freq,
+ lcl_ApplyStepOffsetDriver sys_apply_step_offset,
+ double min_fastslew_offset, double max_fastslew_rate,
+ lcl_AccrueOffsetDriver sys_accrue_offset,
+ lcl_OffsetCorrectionDriver sys_get_offset_correction)
+{
+ initialise_timex();
+
+ SYS_Generic_CompleteFreqDriver(max_set_freq_ppm, max_set_freq_delay,
+ sys_read_freq ? sys_read_freq : read_frequency,
+ sys_set_freq ? sys_set_freq : set_frequency,
+ sys_apply_step_offset,
+ min_fastslew_offset, max_fastslew_rate,
+ sys_accrue_offset, sys_get_offset_correction,
+ set_leap, set_sync_status);
+}
+
+/* ================================================== */
+
+void
+SYS_Timex_Finalise(void)
+{
+ SYS_Generic_Finalise();
+}
+
+/* ================================================== */
+
+int
+SYS_Timex_Adjust(struct timex *txc, int ignore_error)
+{
+ int state;
+
+#ifdef SOLARIS
+ /* The kernel seems to check the constant even when it's not being set */
+ if (!(txc->modes & MOD_TIMECONST))
+ txc->constant = 10;
+#endif
+
+ state = NTP_ADJTIME(txc);
+
+ if (state < 0) {
+ LOG(ignore_error ? LOGS_DEBUG : LOGS_FATAL,
+ NTP_ADJTIME_NAME"(0x%x) failed : %s", txc->modes, strerror(errno));
+ }
+
+ return state;
+}
diff --git a/sys_timex.h b/sys_timex.h
new file mode 100644
index 0000000..b8617a2
--- /dev/null
+++ b/sys_timex.h
@@ -0,0 +1,48 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2015
+ *
+ * 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 a driver based on the adjtimex()/ntp_adjtime() function
+ */
+
+#ifndef GOT_SYS_TIMEX_H
+#define GOT_SYS_TIMEX_H
+
+#include "localp.h"
+
+extern void SYS_Timex_Initialise(void);
+
+/* Initialise with some driver functions replaced with special versions */
+extern void SYS_Timex_InitialiseWithFunctions(double max_set_freq_ppm, double max_set_freq_delay,
+ lcl_ReadFrequencyDriver sys_read_freq,
+ lcl_SetFrequencyDriver sys_set_freq,
+ lcl_ApplyStepOffsetDriver sys_apply_step_offset,
+ double min_fastslew_offset, double max_fastslew_rate,
+ lcl_AccrueOffsetDriver sys_accrue_offset,
+ lcl_OffsetCorrectionDriver sys_get_offset_correction);
+
+extern void SYS_Timex_Finalise(void);
+
+/* Wrapper for adjtimex()/ntp_adjtime() */
+extern int SYS_Timex_Adjust(struct timex *txc, int ignore_error);
+
+#endif /* GOT_SYS_GENERIC_H */
diff --git a/sysincl.h b/sysincl.h
new file mode 100644
index 0000000..e26b236
--- /dev/null
+++ b/sysincl.h
@@ -0,0 +1,69 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * 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 file includes most system header files that the software
+ requires to better isolate system dependencies.
+ */
+
+#ifndef GOT_SYSINCL_H
+#define GOT_SYSINCL_H
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <float.h>
+#include <glob.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+#include <netinet/in.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/shm.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#if defined(LINUX) || defined(FREEBSD) || defined(NETBSD) || defined(SOLARIS) || defined(HAVE_MACOS_SYS_TIMEX)
+#include <sys/timex.h>
+#endif
+
+#ifdef HAVE_GETRANDOM
+#include <sys/random.h>
+#endif
+
+#endif /* GOT_SYSINCL_H */
diff --git a/tempcomp.c b/tempcomp.c
new file mode 100644
index 0000000..a468e33
--- /dev/null
+++ b/tempcomp.c
@@ -0,0 +1,176 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2011, 2014
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Routines implementing temperature compensation.
+
+ */
+
+#include "config.h"
+
+#include "array.h"
+#include "conf.h"
+#include "local.h"
+#include "memory.h"
+#include "util.h"
+#include "logging.h"
+#include "sched.h"
+#include "tempcomp.h"
+
+/* Sanity limit (in ppm) */
+#define MAX_COMP 10.0
+
+static SCH_TimeoutID timeout_id;
+
+static LOG_FileID logfileid;
+
+static char *filename;
+static double update_interval;
+static double T0, k0, k1, k2;
+
+struct Point {
+ double temp;
+ double comp;
+};
+
+static ARR_Instance points;
+
+static double
+get_tempcomp(double temp)
+{
+ unsigned int i;
+ struct Point *p1 = NULL, *p2 = NULL;
+
+ /* If not configured with points, calculate the compensation from the
+ specified quadratic function */
+ if (!points)
+ return k0 + (temp - T0) * k1 + (temp - T0) * (temp - T0) * k2;
+
+ /* Otherwise interpolate/extrapolate between two nearest points */
+
+ for (i = 1; i < ARR_GetSize(points); i++) {
+ p2 = (struct Point *)ARR_GetElement(points, i);
+ if (p2->temp >= temp)
+ break;
+ }
+ p1 = p2 - 1;
+
+ return (temp - p1->temp) / (p2->temp - p1->temp) *
+ (p2->comp - p1->comp) + p1->comp;
+}
+
+static void
+read_timeout(void *arg)
+{
+ FILE *f;
+ double temp, comp;
+
+ f = UTI_OpenFile(NULL, filename, NULL, 'r', 0);
+
+ if (f && fscanf(f, "%lf", &temp) == 1) {
+ comp = get_tempcomp(temp);
+
+ if (fabs(comp) <= MAX_COMP) {
+ comp = LCL_SetTempComp(comp);
+
+ DEBUG_LOG("tempcomp updated to %f for %f", comp, temp);
+
+ if (logfileid != -1) {
+ struct timespec now;
+
+ LCL_ReadCookedTime(&now, NULL);
+ LOG_FileWrite(logfileid, "%s %11.4e %11.4e",
+ UTI_TimeToLogForm(now.tv_sec), temp, comp);
+ }
+ } else {
+ LOG(LOGS_WARN, "Temperature compensation of %.3f ppm exceeds sanity limit of %.1f",
+ comp, MAX_COMP);
+ }
+ } else {
+ LOG(LOGS_WARN, "Could not read temperature from %s", filename);
+ }
+
+ if (f)
+ fclose(f);
+
+ timeout_id = SCH_AddTimeoutByDelay(update_interval, read_timeout, NULL);
+}
+
+static void
+read_points(const char *filename)
+{
+ FILE *f;
+ char line[256];
+ struct Point *p;
+
+ f = UTI_OpenFile(NULL, filename, NULL, 'R', 0);
+
+ points = ARR_CreateInstance(sizeof (struct Point));
+
+ while (fgets(line, sizeof (line), f)) {
+ p = (struct Point *)ARR_GetNewElement(points);
+ if (sscanf(line, "%lf %lf", &p->temp, &p->comp) != 2) {
+ LOG_FATAL("Could not read tempcomp point from %s", filename);
+ break;
+ }
+ }
+
+ fclose(f);
+
+ if (ARR_GetSize(points) < 2)
+ LOG_FATAL("Not enough points in %s", filename);
+}
+
+void
+TMC_Initialise(void)
+{
+ char *point_file;
+
+ CNF_GetTempComp(&filename, &update_interval, &point_file, &T0, &k0, &k1, &k2);
+
+ if (filename == NULL)
+ return;
+
+ if (update_interval <= 0.0)
+ update_interval = 1.0;
+
+ if (point_file)
+ read_points(point_file);
+
+ logfileid = CNF_GetLogTempComp() ? LOG_FileOpen("tempcomp",
+ " Date (UTC) Time Temp. Comp.")
+ : -1;
+
+ read_timeout(NULL);
+}
+
+void
+TMC_Finalise(void)
+{
+ if (filename == NULL)
+ return;
+
+ if (points)
+ ARR_DestroyInstance(points);
+
+ SCH_RemoveTimeout(timeout_id);
+}
diff --git a/tempcomp.h b/tempcomp.h
new file mode 100644
index 0000000..b876f90
--- /dev/null
+++ b/tempcomp.h
@@ -0,0 +1,29 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2011
+ *
+ * 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 temperature compensation.
+
+ */
+
+extern void TMC_Initialise(void);
+extern void TMC_Finalise(void);
diff --git a/test/compilation/001-features b/test/compilation/001-features
new file mode 100755
index 0000000..9bd340f
--- /dev/null
+++ b/test/compilation/001-features
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# Try to compile chrony in various combinations of disabled features
+
+cd ../..
+
+export CFLAGS="-O2 -Werror -Wpointer-arith -Wformat-signedness -Wno-unknown-warning-option -D_FORTIFY_SOURCE=2"
+
+for opts in \
+ "--enable-debug" \
+ "--enable-ntp-signd" \
+ "--enable-scfilter" \
+ "--disable-asyncdns" \
+ "--disable-ipv6" \
+ "--disable-privdrop" \
+ "--disable-readline" \
+ "--disable-rtc" \
+ "--disable-sechash" \
+ "--disable-cmdmon" \
+ "--disable-cmdmon --enable-scfilter" \
+ "--disable-ntp" \
+ "--disable-ntp --enable-scfilter" \
+ "--disable-nts" \
+ "--disable-refclock" \
+ "--disable-timestamping" \
+ "--disable-timestamping --disable-ntp" \
+ "--disable-cmdmon --disable-ntp" \
+ "--disable-cmdmon --disable-ntp --enable-scfilter" \
+ "--disable-cmdmon --disable-refclock" \
+ "--disable-cmdmon --disable-ntp --disable-refclock"
+do
+ ./configure $opts || exit 1
+ make clean
+ make "$@" || exit 1
+ make -C test/unit check || exit 1
+done
diff --git a/test/compilation/002-scanbuild b/test/compilation/002-scanbuild
new file mode 100755
index 0000000..0395df9
--- /dev/null
+++ b/test/compilation/002-scanbuild
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+cd ../..
+
+for opts in \
+ "--host-system=Linux" \
+ "--host-system=NetBSD" \
+ "--host-system=FreeBSD" \
+ "--without-nettle" \
+ "--without-nettle --without-gnutls" \
+ "--without-nettle --without-gnutls --without-nss" \
+ "--without-nettle --without-gnutls --without-nss --without-tomcrypt"
+do
+ ./configure $opts
+ scan-build make "$@" || exit 1
+done
diff --git a/test/compilation/003-sanitizers b/test/compilation/003-sanitizers
new file mode 100755
index 0000000..9287223
--- /dev/null
+++ b/test/compilation/003-sanitizers
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+# Run the unit and simulation tests with different compiler sanitizers
+# and under valgrind
+
+valgrind_opts="--leak-check=full --errors-for-leak-kinds=definite"
+
+cd ../..
+
+if [ "$(uname -sm)" != "Linux x86_64" ]; then
+ echo Test supported on Linux x86_64 only
+ exit 1
+fi
+
+[ -f /etc/os-release ] && . /etc/os-release
+
+if [ "$ID" = "fedora" ]; then
+ echo Checking test dependencies:
+ rpm -q {gcc,clang}.x86_64 {valgrind,libgcc,clang-libs}.{x86_64,i686} || exit 1
+ rpm -q {libseccomp,nettle,nss-softokn-freebl,libtomcrypt,gnutls}-devel.{x86_64,i686} || exit 1
+ echo
+fi
+
+touch Makefile
+
+for extra_config_opts in \
+ "--all-privops" \
+ "--disable-ipv6" \
+ "--disable-nts" \
+ "--disable-scfilter" \
+ "--without-aes-gcm-siv" \
+ "--without-nettle" \
+ "--without-nettle --without-gnutls" \
+ "--without-nettle --without-gnutls --without-nss" \
+ "--without-nettle --without-gnutls --without-nss --without-tomcrypt"; \
+do
+ for arch_opts in "-m32" ""; do
+ pushd test/simulation/clknetsim || exit 1
+ make clean > /dev/null 2>&1
+ CFLAGS="$arch_opts -DCLKNETSIM_DISABLE_SYSCALL" make "$@" || exit 1
+ echo
+
+ popd
+
+ for CC in gcc clang; do
+ export CC
+
+ for san_options in "" "-fsanitize=address" "-fsanitize=memory"; do
+ export CFLAGS="-O2 -g -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize-recover=all $san_options $arch_opts"
+
+ # clang msan doesn't work on i686 and otherwise requires patches
+ echo $CFLAGS | grep -q 'sanitize=memory' && continue
+
+ [ -n "$TEST_NO_M32_CLANG" -a "$arch_opts" = "-m32" -a "$CC" = "clang" ] && continue
+
+ [ -n "$TEST_GCC_STATIC_ASAN" -a "$CC" = "gcc" ] &&
+ echo $CFLAGS | grep -q 'sanitize=address' && CFLAGS="$CFLAGS -static-libasan"
+
+ config_opts="--with-user=chrony --with-ntp-era=1000000000 --enable-debug --enable-scfilter --enable-ntp-signd $extra_config_opts"
+
+ echo -----------------------------------------------------------------------------
+ echo CC=\"$CC\" CFLAGS=\"$CFLAGS\" ./configure $config_opts
+
+ make distclean > /dev/null 2>&1
+
+ ./configure $config_opts || exit 1
+
+ if echo "$config_opts" | grep -q all-privops; then
+ for op in ADJUSTTIME ADJUSTTIMEX SETTIME BINDSOCKET; do
+ echo "#define PRIVOPS_$op 1" >> config.h
+ done
+ fi
+
+ make "$@" || exit 1
+
+ [ -n "$TEST_BUILD_ONLY" ] && continue
+
+ echo
+ pushd test/unit || exit 1
+ make "$@" || exit 1
+ if [ "$san_options" = "" ]; then
+ make check TEST_WRAPPER="valgrind $valgrind_opts --error-exitcode=1" || exit 1
+ else
+ make check || exit 1
+ fi
+ popd
+
+ [ -n "$TEST_UNIT_ONLY" ] && continue
+
+ echo
+ pushd test/simulation || exit 1
+ export CLKNETSIM_RANDOM_SEED=101
+ if [ "$arch_opts" = "" -a "$san_options" = "" ]; then
+ CLKNETSIM_CLIENT_WRAPPER="valgrind $valgrind_opts" ./run -i 1 || exit 1
+ elif [ "$CC" = "gcc" ] && ! echo $CFLAGS | grep -q "-static-libasan"; then
+ libasan=$(ldd ../../chronyd | grep -o '/.*lib.*/libasan.so.[0-9]')
+ CLKNETSIM_PRELOAD=$libasan ./run -i 1 || exit 1
+ else
+ ./run -i 1 || exit 1
+ fi
+ popd
+ done
+ done
+ done
+done
diff --git a/test/kernel/Makefile b/test/kernel/Makefile
new file mode 100644
index 0000000..6ec8341
--- /dev/null
+++ b/test/kernel/Makefile
@@ -0,0 +1,7 @@
+CFLAGS=-O2 -Wall
+PROGS=adjtime ntpadjtime
+
+all: $(PROGS)
+
+clean:
+ rm -f $(PROGS)
diff --git a/test/kernel/adjtime.c b/test/kernel/adjtime.c
new file mode 100644
index 0000000..0ca8ff2
--- /dev/null
+++ b/test/kernel/adjtime.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) Miroslav Lichvar 2015
+ *
+ * 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.
+ */
+
+/* Test the system adjtime() function. Check the range of supported offset,
+ support for readonly operation, and slew rate with different update
+ intervals and offsets. */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+static int
+diff_tv(struct timeval *tv1, struct timeval *tv2)
+{
+ return 1000000 * (tv1->tv_sec - tv2->tv_sec) + (tv1->tv_usec - tv2->tv_usec);
+}
+
+static struct timeval
+usec_to_tv(int usec)
+{
+ struct timeval tv;
+
+ tv.tv_sec = usec / 1000000;
+ tv.tv_usec = usec % 1000000;
+
+ return tv;
+}
+
+static int
+try_adjtime(struct timeval *new, struct timeval *old)
+{
+ int r;
+
+ r = adjtime(new, old);
+ if (r)
+ printf("adjtime() failed : %s ", strerror(errno));
+ return r;
+}
+
+static void
+reset_adjtime(void)
+{
+ struct timeval tv;
+
+ tv = usec_to_tv(0);
+ try_adjtime(&tv, NULL);
+}
+
+static void
+test_range(void)
+{
+ struct timeval tv;
+ int i;
+
+ printf("range:\n");
+
+ for (i = 0; i < sizeof (time_t) * 8; i++) {
+ tv.tv_usec = 0;
+ tv.tv_sec = (1ULL << i) - 1;
+ printf("%20lld s : ", (long long)tv.tv_sec);
+ printf("%s\n", !try_adjtime(&tv, NULL) ? "ok" : "");
+ tv.tv_sec = ~tv.tv_sec;
+ printf("%20lld s : ", (long long)tv.tv_sec);
+ printf("%s\n", !try_adjtime(&tv, NULL) ? "ok" : "");
+ }
+}
+
+static void
+test_readonly(void)
+{
+ struct timeval tv1, tv2;
+ int i, r;
+
+ printf("readonly:\n");
+
+ for (i = 0; i <= 20; i++) {
+ tv1 = usec_to_tv(1 << i);
+
+ printf("%9d us : ", 1 << i);
+ try_adjtime(&tv1, NULL);
+ r = !try_adjtime(NULL, &tv2) && !diff_tv(&tv1, &tv2);
+ printf("%s\n", r ? "ok" : "fail");
+ }
+}
+
+static void
+test_readwrite(void)
+{
+ struct timeval tv1, tv2, tv3;
+ int i, r;
+
+ printf("readwrite:\n");
+
+ for (i = 0; i <= 20; i++) {
+ tv1 = usec_to_tv(1 << i);
+ tv3 = usec_to_tv(0);
+
+ printf("%9d us : ", 1 << i);
+ try_adjtime(&tv1, NULL);
+ r = !try_adjtime(&tv3, &tv2) && !diff_tv(&tv1, &tv2);
+ printf("%s\n", r ? "ok" : "fail");
+ }
+}
+
+static void
+xusleep(int usec)
+{
+ struct timeval tv;
+
+ tv = usec_to_tv(usec);
+ select(0, NULL, NULL, NULL, &tv);
+}
+
+static void
+test_slew(void)
+{
+ struct timeval tv1, tv2, tv3;
+ int i, j, k, diff, min, has_min;
+
+ printf("slew:\n");
+
+ for (i = 9; i <= 20; i++) {
+ printf("%9d us : ", 1 << i);
+ for (j = 4; j <= 20; j += 4) {
+ for (min = has_min = 0, k = 4; k < 16; k += 2) {
+
+ tv1 = usec_to_tv(1 << j);
+ tv3 = usec_to_tv(0);
+
+ xusleep(1 << i);
+ reset_adjtime();
+
+ xusleep(1 << i);
+ if (try_adjtime(&tv1, NULL))
+ continue;
+
+ xusleep(1 << i);
+ if (try_adjtime(&tv3, &tv2))
+ continue;
+
+ diff = diff_tv(&tv1, &tv2);
+ if (!has_min || min > diff) {
+ min = diff;
+ has_min = 1;
+ }
+ }
+
+ if (!has_min)
+ continue;
+
+ printf(" %5d (%d)", min, 1 << j);
+ fflush(stdout);
+ }
+ printf("\n");
+ }
+}
+
+int
+main()
+{
+ test_range();
+ test_readonly();
+ test_readwrite();
+ test_slew();
+
+ reset_adjtime();
+
+ return 0;
+}
diff --git a/test/kernel/ntpadjtime.c b/test/kernel/ntpadjtime.c
new file mode 100644
index 0000000..4af96b4
--- /dev/null
+++ b/test/kernel/ntpadjtime.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) Miroslav Lichvar 2015
+ *
+ * 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.
+ */
+
+/* Check the frequency range of the system ntp_adjtime() implementation */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/timex.h>
+
+static int
+try_ntpadjtime(struct timex *t)
+{
+ int r;
+ r = ntp_adjtime(t);
+ if (r < 0)
+ printf("ntp_adjtime() failed : %s ", strerror(errno));
+ return r;
+}
+
+static void
+reset_ntpadjtime(void)
+{
+ struct timex t;
+
+ t.modes = MOD_OFFSET | MOD_FREQUENCY;
+ t.offset = 0;
+ t.freq = 0;
+ try_ntpadjtime(&t);
+}
+
+static void
+test_freqrange(void)
+{
+ struct timex t;
+ int i;
+
+ printf("freq range:\n");
+
+ for (i = -1000; i <= 1000; i += 50) {
+ t.modes = MOD_FREQUENCY;
+ t.freq = i * (1 << 16);
+ printf("%4d ppm => ", i);
+ if (try_ntpadjtime(&t) < 0)
+ continue;
+
+ printf("%4ld ppm : ", t.freq / (1 << 16));
+ printf("%s\n", t.freq == i * (1 << 16) ? "ok" : "fail");
+ }
+}
+
+int
+main()
+{
+ test_freqrange();
+
+ reset_ntpadjtime();
+
+ return 0;
+}
diff --git a/test/simulation/001-defaults b/test/simulation/001-defaults
new file mode 100755
index 0000000..b39d95c
--- /dev/null
+++ b/test/simulation/001-defaults
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "default test settings"
+
+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
diff --git a/test/simulation/002-largenetwork b/test/simulation/002-largenetwork
new file mode 100755
index 0000000..a9e0ad8
--- /dev/null
+++ b/test/simulation/002-largenetwork
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "large network"
+
+time_rms_limit=5e-4
+
+server_strata=3
+servers=4
+clients=5
+
+client_start=2000
+min_sync_time=2100
+max_sync_time=2300
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/003-largefreqoffset b/test/simulation/003-largefreqoffset
new file mode 100755
index 0000000..9381b1a
--- /dev/null
+++ b/test/simulation/003-largefreqoffset
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "large frequency offset"
+
+max_sync_time=1000
+
+for freq_offset in -5e-2 -5e-3 5e-3 5e-2; do
+ # Adjust offset so it's close to 0 on first clock update
+ time_offset=$(awk "BEGIN {print -($freq_offset * 130)}")
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/004-largetimeoffset b/test/simulation/004-largetimeoffset
new file mode 100755
index 0000000..4aebdd3
--- /dev/null
+++ b/test/simulation/004-largetimeoffset
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "large time offset"
+
+min_sync_time=1300
+max_sync_time=1400
+
+for time_offset in -1e2 1e2; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/005-externalstep b/test/simulation/005-externalstep
new file mode 100755
index 0000000..e6fff26
--- /dev/null
+++ b/test/simulation/005-externalstep
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "external time step"
+
+min_sync_time=1500
+max_sync_time=1550
+
+for step in -1e2 1e2; do
+ # Make one step in 150th second
+ client_step="(* $step (equal 0.1 (sum 1.0) 150))"
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+min_sync_time=5120
+max_sync_time=6200
+client_conf="makestep 1 -1"
+
+for step in -1e8 -1e5 1e5 1e8; do
+ # Make one step in 5000th second
+ client_step="(* $step (equal 0.1 (sum 1.0) 5000))"
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+min_sync_time=$default_min_sync_time
+max_sync_time=$default_max_sync_time
+time_max_limit=2e4
+time_rms_limit=8e3
+
+for step in -1e4 1e4; do
+ # Make a step every 500 seconds
+ client_step="(* $step (equal 0.1 (% (sum 1.0) 500) 0))"
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/006-largejitter b/test/simulation/006-largejitter
new file mode 100755
index 0000000..36ae5e2
--- /dev/null
+++ b/test/simulation/006-largejitter
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "large jitter"
+
+time_offset=1e0
+jitter=1e-1
+
+time_max_limit=5e-1
+freq_max_limit=2e-1
+time_rms_limit=1e-1
+freq_rms_limit=5e-3
+
+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
diff --git a/test/simulation/007-largewander b/test/simulation/007-largewander
new file mode 100755
index 0000000..af0c599
--- /dev/null
+++ b/test/simulation/007-largewander
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "large wander"
+
+wander=1e-7
+
+time_max_limit=5e-3
+freq_max_limit=5e-3
+time_rms_limit=1e-3
+freq_rms_limit=1e-4
+
+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
diff --git a/test/simulation/008-ntpera b/test/simulation/008-ntpera
new file mode 100755
index 0000000..2eea63b
--- /dev/null
+++ b/test/simulation/008-ntpera
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "NTP eras"
+
+if check_config_h 'HAVE_LONG_TIME_T 1'; then
+ ntp_start=$(awk "BEGIN {print $(grep NTP_ERA_SPLIT ../../config.h | tr -dc '0-9*+-')}")
+else
+ ntp_start="-2147483648"
+fi
+
+# Set the starting test date to 500 seconds before the second NTP era.
+# This should work with 32-bit time_t and also with 64-bit time_t if the
+# configured NTP interval covers the test interval.
+export CLKNETSIM_START_DATE=$(date -d 'Feb 7 06:19:56 UTC 2036' +'%s')
+
+if awk "BEGIN {exit !($ntp_start <= $CLKNETSIM_START_DATE && \
+ $CLKNETSIM_START_DATE + $limit < $ntp_start + 2^32)}"; then
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+fi
+
+# The following tests need 64-bit time_t and ntp_start not before 1970
+check_config_h 'HAVE_LONG_TIME_T 1' || test_skip
+echo "$ntp_start" | grep -q '-' && test_skip
+
+for time_offset in -1e-1 1e-1; do
+ for start_offset in 0 "2^32 - $limit"; do
+ export CLKNETSIM_START_DATE=$(awk "BEGIN {printf \"%.0f\", $ntp_start + $start_offset}")
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+ done
+
+ for start_offset in -$limit "2^32"; do
+ export CLKNETSIM_START_DATE=$(awk "BEGIN {printf \"%.0f\", $ntp_start + $start_offset}")
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync && test_fail
+ done
+done
+
+test_pass
diff --git a/test/simulation/009-sourceselection b/test/simulation/009-sourceselection
new file mode 100755
index 0000000..547c376
--- /dev/null
+++ b/test/simulation/009-sourceselection
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "source selection"
+
+# Falsetickers should be detected if their number is less than half of all
+
+base_delay=1e-3
+servers=5
+
+for falsetickers in 1 2; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+for falsetickers in 3 4; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ # These check are expected to fail
+ check_source_selection && test_fail
+ check_sync && test_fail
+done
+
+# Sources with large asymmetric delay should be excluded
+
+servers=3
+falsetickers=0
+base_delay="(+ 1e-3 (equal 0.1 to 2) (equal 0.1 to 3))"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/010-multrecv b/test/simulation/010-multrecv
new file mode 100755
index 0000000..36e7476
--- /dev/null
+++ b/test/simulation/010-multrecv
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+export CLKNETSIM_RECV_MULTIPLY=4
+
+test_start "multiple received packets"
+
+limit=50000
+client_server_options="minpoll 6 maxpoll 6"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/011-asymjitter b/test/simulation/011-asymjitter
new file mode 100755
index 0000000..9fb5567
--- /dev/null
+++ b/test/simulation/011-asymjitter
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "asymmetric jitter"
+
+jitter=5e-4
+jitter_asymmetry=0.47
+limit=100000
+max_sync_time=2000
+
+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
diff --git a/test/simulation/012-daemonts b/test/simulation/012-daemonts
new file mode 100755
index 0000000..a1b90e3
--- /dev/null
+++ b/test/simulation/012-daemonts
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "daemon timestamping"
+
+export CLKNETSIM_TIMESTAMPING=0
+
+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
diff --git a/test/simulation/013-nameserv b/test/simulation/013-nameserv
new file mode 100755
index 0000000..941026b
--- /dev/null
+++ b/test/simulation/013-nameserv
@@ -0,0 +1,15 @@
+#!/usr/bin/env 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
diff --git a/test/simulation/101-poll b/test/simulation/101-poll
new file mode 100755
index 0000000..1416b22
--- /dev/null
+++ b/test/simulation/101-poll
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "minpoll/maxpoll options"
+
+wander=0.0
+jitter=1e-6
+
+time_max_limit=1e-5
+freq_max_limit=1e-5
+time_rms_limit=5e-6
+freq_rms_limit=5e-6
+client_conf="makestep 1e-2 1"
+
+for poll in $(seq 1 14); do
+ client_server_options="minpoll $poll maxpoll $poll"
+ limit=$[2**$poll * 10]
+ min_sync_time=$[2**$poll * 2]
+ max_sync_time=$[2**$poll * 21 / 10 + 1]
+ client_max_min_out_interval=$(awk "BEGIN {print 2^$poll * 1.1}")
+ client_min_mean_out_interval=$(awk "BEGIN {print 2^$poll * 0.99}")
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+min_sync_time=$default_min_sync_time
+max_sync_time=$default_max_sync_time
+client_max_min_out_interval=$default_client_max_min_out_interval
+client_min_mean_out_interval=$default_client_min_mean_out_interval
+
+limit=10
+
+for poll in $(seq -7 2 -1); do
+ client_server_options="minpoll $poll maxpoll $poll"
+
+ base_delay=1e-4
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_file_messages " 2 1 " \
+ $[2**-poll * limit * 9 / 10] $[2**-poll * limit] log.packets || test_fail
+
+ base_delay=2e-2
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_file_messages " 2 1 " $[limit * 9 / 10] $limit log.packets || test_fail
+done
+
+test_pass
diff --git a/test/simulation/102-iburst b/test/simulation/102-iburst
new file mode 100755
index 0000000..9936572
--- /dev/null
+++ b/test/simulation/102-iburst
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "iburst option"
+
+freq_offset=1e-4
+
+client_conf="makestep 1e-2 1
+driftfile tmp/drift"
+client_server_options="iburst"
+
+min_sync_time=4
+max_sync_time=6
+
+echo "100 1.0" > tmp/drift
+
+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
diff --git a/test/simulation/103-initstepslew b/test/simulation/103-initstepslew
new file mode 100755
index 0000000..fe47b68
--- /dev/null
+++ b/test/simulation/103-initstepslew
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "initstepslew directive"
+
+freq_offset=0.0
+wander=0.0
+time_rms_limit=1e-3
+limit=100
+
+client_conf="initstepslew 5 192.168.123.1"
+client_server_conf="#"
+
+min_sync_time=6
+max_sync_time=35
+
+for time_offset in -2.0 -0.2 0.2 2.0; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+ check_log_messages "00:00:0.Z System's initial.*slew" 1 1 || test_fail
+done
+
+min_sync_time=5
+max_sync_time=5
+
+for time_offset in -1e8 -1e2 1e2 1e8; do
+ run_test || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+ check_log_messages "System's initial.*step" 1 1 || test_fail
+done
+
+time_offset=3
+limit=500
+servers=2
+falsetickers=1
+client_conf="initstepslew 5 192.168.123.1 192.168.123.2"
+client_server_conf="server 192.168.123.2"
+
+min_sync_time=360
+max_sync_time=450
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+check_log_messages "00:03:2.Z No suitable source for initstepslew" 1 1 || test_fail
+
+client_conf="initstepslew 5 192.168.123.1 192.168.123.2"
+
+min_sync_time=1
+max_sync_time=500
+server_conf="deny all"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_sync && test_fail
+check_log_messages "00:00:1.Z No suitable source for initstepslew" 1 1 || test_fail
+
+test_pass
diff --git a/test/simulation/104-driftfile b/test/simulation/104-driftfile
new file mode 100755
index 0000000..93d4363
--- /dev/null
+++ b/test/simulation/104-driftfile
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "driftfile directive"
+
+servers=0
+time_offset=0.0
+wander=0.0
+limit=10
+freq_max_limit=1e-9
+min_sync_time=1
+max_sync_time=1
+client_conf="driftfile tmp/drift"
+
+for freq_offset in -5e-2 -5e-4 -5e-6 5e-6 5e-4 5e-2; do
+ awk "BEGIN {printf \"%.9e 1\", 1e6 - 1 / (1 + $freq_offset) * 1e6}" > tmp/drift
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/105-ntpauth b/test/simulation/105-ntpauth
new file mode 100755
index 0000000..1f228f5
--- /dev/null
+++ b/test/simulation/105-ntpauth
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "NTP authentication"
+
+server_conf="keyfile tmp/server.keys"
+client_conf="keyfile tmp/client.keys"
+
+cat > tmp/server.keys <<-EOF
+1 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E
+2 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E
+3 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E
+4 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E
+EOF
+
+cat > tmp/client.keys <<-EOF
+1 k]<j.Jtw^Oo;z5E>n\_0-x=)yP\f<)Z^
+2 ASCII:k]<j.Jtw^Oo;z5E>n\_0-x=)yP\f<)Z^
+3 MD5 ASCII:k]<j.Jtw^Oo;z5E>n\_0-x=)yP\f<)Z^
+4 MD5 HEX:6B5D3C6A2E4A74775E4F6F3B7A35453E6E5C5F302D783D2979505C663C295A5E
+EOF
+
+keys=4
+
+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 type in $types; do
+ keys=$[$keys + 1]
+ 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
+
+for version in 3 4; do
+ for key in $(seq $keys); do
+ client_server_options="version $version key $key"
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+ done
+done
+
+server_conf=""
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+# This check must fail as the server doesn't know the key
+check_sync && test_fail
+check_packet_interval || test_fail
+
+server_conf="keyfile tmp/server.keys"
+client_conf=""
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+# This check must fail as the client doesn't know the key
+check_sync && test_fail
+check_packet_interval || test_fail
+
+client_conf="keyfile tmp/client.keys"
+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 2"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+# This check must fail as the peers are using different keys"
+check_sync && test_fail
+
+test_pass
diff --git a/test/simulation/106-refclock b/test/simulation/106-refclock
new file mode 100755
index 0000000..ba7c482
--- /dev/null
+++ b/test/simulation/106-refclock
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "reference clocks"
+
+check_config_h 'FEAT_REFCLOCK 1' || test_skip
+check_config_h 'FEAT_PHC 1' || test_skip
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+export CLKNETSIM_PHC_DELAY=1e-6
+export CLKNETSIM_PHC_JITTER=1e-7
+
+servers=0
+limit=1000
+refclock_jitter=$jitter
+min_sync_time=45
+max_sync_time=70
+chronyc_start=70
+chronyc_conf="tracking"
+
+for refclock in "SHM 0" "PHC /dev/ptp0" "PHC /dev/ptp0:nocrossts"; do
+ client_conf="refclock $refclock stratum 3 delay 1e-3 refid GPS
+logdir tmp
+log refclocks"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+ check_chronyc_output "^Reference ID.*47505300 \(GPS\)
+Stratum.*: 4
+.*
+Root delay : 0.001000000 seconds
+.*
+Update interval : 16\.. seconds
+.*$" || test_fail
+
+ if echo "$refclock" | grep -q 'PHC.*nocrossts'; then
+ check_file_messages "20.* GPS.*[0-9] N " 650 750 refclocks.log || test_fail
+ else
+ check_file_messages "20.* GPS.*[0-9] N " 997 1001 refclocks.log || test_fail
+ fi
+ check_file_messages "20.* GPS.*- N " 61 63 refclocks.log || test_fail
+ rm -f tmp/refclocks.log
+done
+
+if check_config_h 'FEAT_PPS 1'; then
+ refclock_offset=0.35
+ refclock_jitter=0.05
+
+ client_conf="
+refclock SHM 0 refid NMEA noselect
+refclock PPS /dev/pps0 lock NMEA
+logdir tmp
+log refclocks"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+ check_chronyc_output "^Reference ID.*50505331 \(PPS1\)
+Stratum.*: 1
+.*
+Root delay : 0\.000000001 seconds
+.*$" || test_fail
+
+ check_file_messages "20.* PPS1.*[0-9] N " 620 740 refclocks.log || test_fail
+ check_file_messages "20.* PPS1.*- N " 60 63 refclocks.log || test_fail
+ rm -f tmp/refclocks.log
+
+ client_conf="
+refclock SHM 0 noselect
+refclock PPS /dev/pps0
+local
+logdir tmp
+log refclocks"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+ check_chronyc_output "^Reference ID.*50505331 \(PPS1\)
+Stratum.*: 10
+.*
+Root delay : 0\.000000001 seconds
+.*$" || test_fail
+
+ check_file_messages "20.* PPS1.*[0-9] N " 997 1001 refclocks.log || test_fail
+ check_file_messages "20.* PPS1.*- N " 60 63 refclocks.log || test_fail
+ rm -f tmp/refclocks.log
+
+ min_sync_time=100
+ max_sync_time=220
+ chronyc_start=220
+ client_conf="
+refclock SHM 0 refid NMEA offset 0.35 delay 0.1
+refclock PPS /dev/pps0
+logdir tmp
+log refclocks"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+ check_chronyc_output "^Reference ID.*50505331 \(PPS1\)
+Stratum.*: 1
+.*
+Root delay : 0\.000000001 seconds
+.*$" || test_fail
+
+ check_file_messages "20.* PPS1.*[0-9] N " 800 940 refclocks.log || test_fail
+ check_file_messages "20.* PPS1.*- N " 50 63 refclocks.log || test_fail
+ rm -f tmp/refclocks.log
+fi
+
+refclock_offset="(+ 0.399 (sum 1e-3))"
+refclock_jitter=1e-6
+servers=1
+freq_offset="(* 1e-4 (sine 1000))"
+base_delay="(* -1.0 (equal 0.1 (min time 5000) 5000))"
+client_server_options="minpoll 4 maxpoll 4 filter 5 minsamples 64"
+client_conf="
+refclock PHC /dev/ptp0 local poll 2
+logdir tmp
+log refclocks tracking"
+chronyc_conf=""
+limit=10000
+max_sync_time=5000
+time_max_limit=1e-3
+time_rms_limit=5e-4
+freq_max_limit=2e-5
+freq_rms_limit=5e-6
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_sync || test_fail
+
+check_file_messages "20.* PHC0 .* [0-9] ? " 9999 10001 refclocks.log || test_fail
+check_file_messages "20.* PHC0 .* - ? " 2499 2501 refclocks.log || test_fail
+check_file_messages "20.* PHC0 " 0 0 tracking.log || test_fail
+rm -f tmp/refclocks.log tmp/tracking.log
+
+test_pass
diff --git a/test/simulation/107-allowdeny b/test/simulation/107-allowdeny
new file mode 100755
index 0000000..4665337
--- /dev/null
+++ b/test/simulation/107-allowdeny
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "allow/deny directives"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+limit=500
+
+# Note that start_client in clknetsim.bash always adds allow to the config
+
+for server_conf in \
+ "deny" \
+ "deny all" \
+ "deny 192.168.0.0/16" \
+ "deny 192.168.123" \
+ "deny 192.168.123.2" \
+ "deny all
+allow 192.168.124.0/24"
+do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ # These checks are expected to fail
+ check_source_selection && test_fail
+ check_sync && test_fail
+done
+
+for server_conf in \
+ "deny all
+allow" \
+ "deny all
+allow all" \
+ "deny all
+allow 192.168.123" \
+ "deny all
+allow 192.168.123/24" \
+ "deny 192.168.124.0/24"
+do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/108-peer b/test/simulation/108-peer
new file mode 100755
index 0000000..906de17
--- /dev/null
+++ b/test/simulation/108-peer
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "NTP peers"
+
+# Allow and drop packets to the server in 1000 second intervals, so only one
+# client has access to it and the other is forced to switch to the peer.
+base_delay=$(cat <<-EOF | tr -d '\n'
+ (+ 1e-4
+ (* -1
+ (equal 0.1 from 2)
+ (equal 0.1 to 1)
+ (equal 0.1 (min (% time 2000) 1000) 1000))
+ (* -1
+ (equal 0.1 from 3)
+ (equal 0.1 to 1)
+ (equal 0.1 (max (% time 2000) 1000) 1000)))
+EOF
+)
+
+clients=2
+peers=2
+max_sync_time=1000
+client_server_options="minpoll 6 maxpoll 6"
+client_peer_options="minpoll 6 maxpoll 6"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))"
+client_peer_options=""
+
+while read lminpoll lmaxpoll rminpoll rmaxpoll max_sync_time; do
+ client_lpeer_options="minpoll $lminpoll maxpoll $lmaxpoll"
+ client_rpeer_options="minpoll $rminpoll maxpoll $rmaxpoll"
+ limit=$[$max_sync_time * 10]
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_sync || test_fail
+done <<-EOF
+ 3 6 3 6 400
+ 3 3 6 6 450
+ 6 6 3 3 450
+ 3 6 6 6 450
+ 6 6 3 6 450
+ -2 -2 2 2 220
+ 2 2 -2 -2 220
+EOF
+
+test_pass
diff --git a/test/simulation/109-makestep b/test/simulation/109-makestep
new file mode 100755
index 0000000..78d8d59
--- /dev/null
+++ b/test/simulation/109-makestep
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "makestep directive"
+
+client_conf="makestep 0 -1
+corrtimeratio 1e10"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+limit=200
+jitter=1e-5
+client_conf="makestep 2 1"
+
+min_sync_time=130
+max_sync_time=150
+
+for time_offset in -1.0 -0.1 0.1 1.0; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+min_sync_time=120
+max_sync_time=140
+
+for time_offset in -1e8 -1e2 1e2 1e8; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/110-chronyc b/test/simulation/110-chronyc
new file mode 100755
index 0000000..46b0a3f
--- /dev/null
+++ b/test/simulation/110-chronyc
@@ -0,0 +1,529 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "chronyc"
+
+check_config_h 'FEAT_REFCLOCK 1' || test_skip
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+refclock_jitter=$jitter
+client_server_conf="
+server node1.net1.clk
+server 192.168.123.2"
+client_conf="
+refclock SHM 0 noselect
+smoothtime 400 0.001 leaponly"
+cmdmon_unix=0
+
+chronyc_conf="activity
+tracking
+sourcename 192.168.123.1
+sourcename 192.168.123.2
+sources
+sourcestats
+manual list
+smoothing
+waitsync
+rtcdata"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_chronyc_output "^200 OK
+2 sources online
+0 sources offline
+0 sources doing burst \(return to online\)
+0 sources doing burst \(return to offline\)
+0 sources with unknown address
+Reference ID : C0A87B01 \(192\.168\.123\.1\)
+Stratum : 2
+Ref time \(UTC\) : Fri Jan 01 00:1.:.. 2010
+System time : 0\.0000..... seconds (slow|fast) of NTP time
+Last offset : [+-]0\.000...... seconds
+RMS offset : 0\.000...... seconds
+Frequency : (99|100)\.... ppm fast
+Residual freq : [+-][0-9]\.... ppm
+Skew : [0-9]\.... ppm
+Root delay : 0\.000...... seconds
+Root dispersion : 0\.000...... seconds
+Update interval : [0-9]+\.. seconds
+Leap status : Normal
+node1\.net1\.clk
+192\.168\.123\.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
+\^\? 192\.168\.123\.2 0 [0-9]+ 0 - \+0ns\[ \+0ns\] \+/- 0ns
+Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
+==============================================================================
+SHM0 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-][01]\.... [0-9 ]+\.... [0-9 +-]+[un]s [0-9 ]+[un]s
+192\.168\.123\.1 [0-9 ]+ [0-9 ]+ [0-9 ]+ [ +-][01]\.... [0-9 ]+\.... [0-9 +-]+[un]s [0-9 ]+[un]s
+192\.168\.123\.2 0 0 0 \+0\.000 2000\.000 \+0ns 4000ms
+210 n_samples = 0
+# Date Time\(UTC\) Slewed Original Residual
+=======================================================
+Active : Yes \(leap second only\)
+Offset : \+0\.000000000 seconds
+Frequency : \+0\.000000 ppm
+Wander : \+0\.000000 ppm per second
+Last update : [0-9]+\.. seconds ago
+Remaining time : 0\.0 seconds
+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
+
+chronyc_options="-c"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_chronyc_output "^C0A87B01,192\.168\.123\.1,2,12623049..\..........,-?0\.0000.....,-?0\.000......,0\.000......,(99|100)\....,-?[0-9]\....,[0-9]\....,0\.000......,0\.000......,[0-9]+\..,Normal$" \
+ || test_fail
+
+chronyc_options="-c -e"
+chronyc_conf="sources"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_chronyc_output "^#,.,SHM0.*
+\^,.,192\.168\.123\.1.*
+\^,.,192\.168\.123\.2.*
+\.$" \
+ || test_fail
+
+chronyc_options=""
+server_strata=0
+chronyc_start=0.5
+client_server_conf=""
+client_conf=""
+server_conf="server 192.168.123.1"
+limit=1
+
+for chronyc_conf in \
+ "accheck 1.2.3.4" \
+ "add peer 10.0.0.0 minpoll 2 maxpoll 6" \
+ "add server 10.0.0.0 minpoll 6 maxpoll 10 iburst burst key 1 certset 2 maxdelay 1e-3 maxdelayratio 10.0 maxdelaydevratio 10.0 maxdelayquant 0.5 mindelay 1e-4 asymmetry 0.5 offset 1e-5 minsamples 6 maxsamples 6 filter 3 offline auto_offline prefer noselect trust require xleave polltarget 20 port 123 presend 7 minstratum 3 version 4 nts ntsport 4460 copy extfield F323 extfield F324" \
+ "add server node1.net1.clk" \
+ "allow 1.2.3.4" \
+ "allow 1.2" \
+ "allow 3.4.5" \
+ "allow 6.7.8/22" \
+ "allow 6.7.8.9/22" \
+ "allow 0/0" \
+ "allow" \
+ "allow all 10/24" \
+ "authdata" \
+ "burst 5/10" \
+ "burst 3/5 255.255.255.0/1.2.3.0" \
+ "burst 1/2 1.2.3.0/24" \
+ "clients" \
+ "clients -k" \
+ "clients -p 100" \
+ "clients -r" \
+ "cmdaccheck 1.2.3.4" \
+ "cmdallow 1.2.3.4" \
+ "cmdallow all 1.2.3.0/24" \
+ "cmddeny 1.2.3.4" \
+ "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" \
+ "doffset -1.0" \
+ "dump" \
+ "local stratum 5 distance 1.0 orphan" \
+ "local off" \
+ "makestep 10.0 3" \
+ "makestep" \
+ "manual delete 0" \
+ "manual off" \
+ "manual on" \
+ "manual reset" \
+ "maxdelay 1.2.3.4 1e-2" \
+ "maxdelaydevratio 1.2.3.4 5.0" \
+ "maxdelayratio 1.2.3.4 3.0" \
+ "maxpoll 1.2.3.4 5" \
+ "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" \
+ "offline 1.2.3.0/24" \
+ "online" \
+ "online 1.2.3.0/24" \
+ "onoffline" \
+ "polltarget 1.2.3.4 10" \
+ "refresh" \
+ "rekey" \
+ "reload sources" \
+ "reselect" \
+ "reselectdist 1e-3" \
+ "reset sources" \
+ "selectdata" \
+ "selectopts 1.2.3.4 -noselect +trust +require +prefer" \
+ "selectopts ID#0000000001 +prefer" \
+ "selectopts PPS0 +prefer" \
+ "settime 16:30" \
+ "settime 16:30:05" \
+ "settime Nov 21, 2015 16:30:05" \
+ "serverstats" \
+ "shutdown" \
+ "smoothtime reset" \
+ "smoothtime activate" \
+ "trimrtc" \
+ "writertc"
+do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_chronyc_output "501 Not authorised$" || test_fail
+done
+
+cmdmon_unix=1
+
+chronyc_conf="
+authdata
+clients -k -p 2
+clients -r
+clients
+ntpdata
+selectdata
+serverstats"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+node1\.net1\.clk - 0 0 0 - 0 0 0 0
+Hostname NTP Drop Int IntL Last NTS-KE Drop Int Last
+===============================================================================
+Hostname NTP Drop Int IntL Last Cmd Drop Int Last
+===============================================================================
+node1\.net1\.clk 1 0 - - 0 0 0 - -
+Hostname NTP Drop Int IntL Last Cmd Drop Int Last
+===============================================================================
+node1\.net1\.clk 0 0 - - 0 0 0 - -
+
+Remote address : 192\.168\.123\.1 \(C0A87B01\)
+Remote port : 123
+Local address : 192\.168\.123\.1 \(C0A87B01\)
+Leap status : Normal
+Version : 4
+Mode : Server
+Stratum : 1
+Poll interval : 6 \(64 seconds\)
+Precision : -23 \(0\.000000119 seconds\)
+Root delay : 0\.000000 seconds
+Root dispersion : 0\.000000 seconds
+Reference ID : 7F7F0101 \(\)
+Reference time : Thu Dec 31 23:59:5[89] 2009
+Offset : [-+]0\.000...... seconds
+Peer delay : 0\.00....... seconds
+Peer dispersion : 0\.00000.... seconds
+Response time : 0\.000000... seconds
+Jitter asymmetry: \+0\.00
+NTP tests : 111 111 1110
+Interleaved : No
+Authenticated : No
+TX timestamping : Kernel
+RX timestamping : Kernel
+Total TX : 1
+Total RX : 1
+Total valid RX : 1
+Total good RX : 0
+S Name/IP Address Auth COpts EOpts Last Score Interval Leap
+=======================================================================
+M node1\.net1\.clk N ----- ----- 0 1\.0 \+0ns \+0ns N
+NTP packets received : 1
+NTP packets dropped : 0
+Command packets received : 12
+Command packets dropped : 0
+Client log records dropped : 0
+NTS-KE connections accepted: 0
+NTS-KE connections dropped : 0
+Authenticated NTP packets : 0
+Interleaved NTP packets : 0
+NTP timestamps held : 0
+NTP timestamp span : 0
+NTP daemon RX timestamps : 0
+NTP daemon TX timestamps : 1
+NTP kernel RX timestamps : 1
+NTP kernel TX timestamps : 0
+NTP hardware RX timestamps : 0
+NTP hardware TX timestamps : 0$" || test_fail
+
+chronyc_conf="
+deny all
+cmdallow all
+allow 1.2.3.4
+allow 1.2.3.0/28
+deny 1.2.3.0/27
+allow 1.2.4.5
+deny all 1.2.4.0/27
+cmddeny 5.6.7.8
+cmdallow all 5.6.7.0/28
+accheck 1.2.3.4
+accheck 1.2.3.5
+accheck 1.2.4.5
+cmdaccheck 5.6.7.8"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_chronyc_output "^200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+208 Access allowed
+209 Access denied
+209 Access denied
+208 Access allowed$" || test_fail
+
+if check_config_h 'FEAT_IPV6 1'; then
+ chronyc_conf="
+ deny all
+ cmdallow all
+ allow 2001:db8::1
+ allow 2001:db8::/64
+ deny 2001:db8::/63
+ allow 2001:db8:1::1
+ deny all 2001:db8:1::/63
+ cmddeny 2001:db9::1
+ cmdallow all 2001:db9::/64
+ accheck 2001:db8::1
+ accheck 2001:db8::2
+ accheck 2001:db8:1::1
+ cmdaccheck 2001:db9::1"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+
+ check_chronyc_output "^200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+208 Access allowed
+209 Access denied
+209 Access denied
+208 Access allowed$" || test_fail
+fi
+
+chronyc_conf="
+delete 192.168.123.1
+add server node1.net1.clk minpoll 6 maxpoll 10 iburst
+offline 192.168.123.1
+burst 1/1 192.168.123.1
+online 192.168.123.1
+maxdelay 192.168.123.1 1e-2
+maxdelaydevratio 192.168.123.1 5.0
+maxdelayratio 192.168.123.1 3.0
+maxpoll 192.168.123.1 5
+maxupdateskew 192.168.123.1 10.0
+minpoll 192.168.123.1 3
+minstratum 192.168.123.1 1
+polltarget 192.168.123.1 10
+selectopts 192.168.123.1 +trust +prefer -require
+selectdata
+selectopts 192.168.123.1 +noselect -prefer -trust +require
+selectdata
+delete 192.168.123.1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_chronyc_output "^200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+S Name/IP Address Auth COpts EOpts Last Score Interval Leap
+=======================================================================
+M node1\.net1\.clk N \-PT\-\- \-PT\-\- 0 1\.0 \+0ns \+0ns \?
+200 OK
+S Name/IP Address Auth COpts EOpts Last Score Interval Leap
+=======================================================================
+M node1\.net1\.clk N N\-\-R\- N\-\-R\- 0 1\.0 \+0ns \+0ns \?
+200 OK$" || test_fail
+
+chronyc_conf="
+cyclelogs
+dump
+dfreq 1.0e-3
+doffset -0.01
+local stratum 5 distance 1.0 orphan
+local off
+makestep 10.0 3
+makestep
+manual on
+settime now
+manual delete 0
+manual reset
+manual off
+onoffline
+refresh
+rekey
+reload sources
+reselect
+reselectdist 1e-3
+reset sources
+shutdown"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_chronyc_output "^200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+Clock was .\... seconds fast. Frequency change = 0.00ppm, new frequency = 0.00ppm
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK
+200 OK$" || test_fail
+
+server_conf="
+server 192.168.123.1
+noclientlog"
+
+commands=(
+ "add server nosuchnode.net1.clk" "^Invalid host/IP address$"
+ "allow nosuchnode.net1.clk" "^Could not read address$"
+ "allow 192.168.123.0/2 4" "^Could not read address$"
+ "allow 192.168.123.0/2e" "^Could not read address$"
+ "allow 192.168.12e" "^Could not read address$"
+ "allow 192.168123" "^Could not read address$"
+ "allow 192.168.123.2/33" "^507 Bad subnet$"
+ "clients" "Hostname.*519 Client logging is not active in the daemon$"
+ "delete 192.168.123.2" "^503 No such source$"
+ "minpoll 192.168.123.2 5" "^503 No such source$"
+ "ntpdata 192.168.123.2" "^503 No such source$"
+ "settime now" "^505 Facility not enabled in daemon$"
+ "smoothing" "^505 Facility not enabled in daemon$"
+ "smoothtime activate" "^505 Facility not enabled in daemon$"
+ "smoothtime reset" "^505 Facility not enabled in daemon$"
+ "sourcename 192.168.123.2" "^503 No such source$"
+ "trimrtc" "^513 RTC driver not running$"
+ "writertc" "^513 RTC driver not running$"
+)
+
+for i in $(seq 0 $[${#commands[*]} / 2]); do
+ chronyc_conf=${commands[$[i * 2]]}
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_chronyc_output "${commands[$[i * 2 + 1]]}" || test_fail
+done
+
+cmdmon_unix=0
+server_conf="server 192.168.123.1"
+
+chronyc_conf="dns -n
+dns +n
+dns -4
+dns -6
+dns -46
+timeout 200
+retries 1
+keygen
+keygen 10 MD5 128
+keygen 11 MD5 40
+help
+quit
+nosuchcommand"
+
+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
+
+# Pass every fourth request
+base_delay=$(cat <<-EOF | tr -d '\n'
+ (+ 1e-4
+ (* -1
+ (equal 0.1 from 2)
+ (equal 0.1 (min (% (sum 1) 4) 1) 1)))
+EOF
+)
+limit=15
+
+chronyc_conf="sources"
+run_test || test_fail
+check_chronyc_output "^506 Cannot talk to daemon$" || test_fail
+
+chronyc_conf="retries 3
+sources"
+run_test || test_fail
+check_chronyc_output "^MS.*0ns$" || test_fail
+
+test_pass
diff --git a/test/simulation/111-knownclient b/test/simulation/111-knownclient
new file mode 100755
index 0000000..92bad54
--- /dev/null
+++ b/test/simulation/111-knownclient
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "reply to client configured as server"
+
+server_conf="server 192.168.123.2 noselect
+acquisitionport 123"
+client_conf="acquisitionport 123"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_port || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/112-port b/test/simulation/112-port
new file mode 100755
index 0000000..2f10eed
--- /dev/null
+++ b/test/simulation/112-port
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "port and acquisitionport directives"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+# This check is expected to fail
+check_packet_port && test_fail
+
+client_conf="acquisitionport 123"
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_port || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+client_conf=""
+for server_conf in \
+ "port 0" \
+ "acquisitionport 123
+port 0"
+do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_port || test_fail
+ check_packet_interval || test_fail
+ # These checks are expected to fail
+ check_source_selection && test_fail
+ check_sync && test_fail
+done
+
+server_conf="port 124
+acquisitionport 123"
+client_server_options="port 124"
+for client_conf in \
+ "acquisitionport 0" \
+ "acquisitionport 123" \
+ "acquisitionport 124"
+do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+ # This check is expected to fail
+ check_packet_port && test_fail
+done
+
+test_pass
diff --git a/test/simulation/113-leapsecond b/test/simulation/113-leapsecond
new file mode 100755
index 0000000..394440b
--- /dev/null
+++ b/test/simulation/113-leapsecond
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "leap second"
+
+check_config_h 'FEAT_REFCLOCK 1' || test_skip
+
+export CLKNETSIM_START_DATE=$(TZ=UTC date -d 'Dec 30 2008 0:00:00' +'%s')
+
+leap=$[2 * 24 * 3600]
+limit=$[4 * 24 * 3600]
+client_start=$[2 * 3600]
+server_conf="refclock SHM 0 dpoll 10 poll 10
+leapsectz right/UTC"
+refclock_jitter=1e-9
+refclock_offset="(* -1.0 (equal 0.1 (max (sum 1.0) $leap) $leap))"
+
+for leapmode in system step slew; do
+ client_conf="leapsecmode $leapmode"
+ if [ $leapmode = slew ]; then
+ max_sync_time=$[$leap + 12]
+ else
+ max_sync_time=$[$leap]
+ fi
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+client_server_options="trust"
+client_conf="refclock SHM 0 dpoll 10 poll 10 delay 1e-3"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+client_server_options=""
+client_conf="leapsecmode system"
+min_sync_time=230000
+max_sync_time=240000
+
+for smoothmode in "" "leaponly"; do
+ server_conf="refclock SHM 0 dpoll 10 poll 10
+ leapsectz right/UTC
+ leapsecmode slew
+ smoothtime 400 0.001 $smoothmode"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/114-presend b/test/simulation/114-presend
new file mode 100755
index 0000000..3b89a70
--- /dev/null
+++ b/test/simulation/114-presend
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "presend option"
+
+limit=9900
+min_sync_time=136
+max_sync_time=260
+client_server_options="presend 6 maxdelay 16"
+client_conf="maxdistance 10"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+base_delay=5
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+limit=10
+base_delay=$default_base_delay
+client_conf="logdir tmp
+log measurements"
+
+client_server_options="presend 5"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+
+check_file_messages "20.*123\.1.* 111 111 0111" 1 1 measurements.log || test_fail
+check_file_messages "20.*123\.1.* 111 111 1111" 1 1 measurements.log || test_fail
+rm -f tmp/measurements.log
+
+client_server_options="presend 5 xleave"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+
+check_file_messages "20.*123\.1.* 111 111 0111" 2 2 measurements.log || test_fail
+check_file_messages "20.*123\.1.* 111 111 1111" 1 1 measurements.log || test_fail
+rm -f tmp/measurements.log
+
+test_pass
diff --git a/test/simulation/115-cmdmontime b/test/simulation/115-cmdmontime
new file mode 100755
index 0000000..525062d
--- /dev/null
+++ b/test/simulation/115-cmdmontime
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "cmdmon timestamps"
+
+# The following tests need 64-bit time_t
+check_config_h 'HAVE_LONG_TIME_T 1' || test_skip
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+limit=2
+client_server_options="noselect"
+client_conf="local stratum 1"
+chronyc_start="1.5"
+chronyc_conf="tracking"
+
+for year in `seq 1850 100 2300`; do
+ 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\).*Jan 01 00:00:0. $year.*$" || test_fail
+done
+
+test_pass
diff --git a/test/simulation/116-minsources b/test/simulation/116-minsources
new file mode 100755
index 0000000..f576423
--- /dev/null
+++ b/test/simulation/116-minsources
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "minsources directive"
+
+client_conf="minsources 3"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+# These check are expected to fail
+check_source_selection && test_fail
+check_sync && test_fail
+
+servers=3
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/117-fallbackdrift b/test/simulation/117-fallbackdrift
new file mode 100755
index 0000000..21f6963
--- /dev/null
+++ b/test/simulation/117-fallbackdrift
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "fallback drift"
+
+limit=100000
+wander=0.0
+jitter=1e-6
+time_offset=10
+freq_offset="(* 1e-4 (sine 1000))"
+base_delay="(* -1.0 (equal 0.1 (min time 4250) 4250))"
+client_server_options="minpoll 4 maxpoll 4"
+client_conf="fallbackdrift 6 10"
+max_sync_time=4500
+time_max_limit=1e0
+time_rms_limit=1e0
+freq_max_limit=2e-4
+freq_rms_limit=1e-4
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/118-maxdelay b/test/simulation/118-maxdelay
new file mode 100755
index 0000000..117b170
--- /dev/null
+++ b/test/simulation/118-maxdelay
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "maxdelay options"
+
+max_sync_time=2000
+base_delay=1e-5
+jitter=1e-5
+wander=0.0
+freq_offset="(sum 1e-10)"
+time_rms_limit=2e-4
+
+client_server_options="maxpoll 6 maxdelay 3e-5 maxdelayratio 2.0 maxdelaydevratio 2.0"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+for client_server_options in "maxpoll 6 maxdelay 2e-5"; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_packet_interval || test_fail
+ check_sync && test_fail
+done
+
+min_sync_time=10
+client_conf="
+logdir tmp
+log rawmeasurements"
+client_server_options="minpoll 2 maxpoll 2 maxdelayquant 0.1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+check_file_messages "20.*123\.1.* 111 111 1111" 200 500 measurements.log || test_fail
+check_file_messages "20.*123\.1.* 111 111 1101" 2000 2300 measurements.log || test_fail
+
+test_pass
diff --git a/test/simulation/119-smoothtime b/test/simulation/119-smoothtime
new file mode 100755
index 0000000..7f5114c
--- /dev/null
+++ b/test/simulation/119-smoothtime
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "smoothtime option"
+
+check_config_h 'FEAT_REFCLOCK 1' || test_skip
+
+server_strata=2
+server_conf="smoothtime 400 0.001"
+server_server_options="minpoll 8"
+min_sync_time=600
+max_sync_time=800
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+limit=10000
+refclock_jitter=1e-4
+refclock_offset="(* 10.0 (equal 0.1 (max (sum 1.0) 1000) 1000))"
+server_step="(* -10.0 (equal 0.1 (sum 1.0) 1))"
+server_strata=1
+server_conf="refclock SHM 0 dpoll 4 poll 6
+smoothtime 2000 1
+maxjitter 10.0"
+time_offset=-10
+server_server_options=""
+client_server_options="minpoll 6 maxpoll 6"
+client_conf="corrtimeratio 100"
+min_sync_time=8000
+max_sync_time=9000
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+client_server_options="minpoll 6 maxpoll 6 xleave maxdelay 1e-1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+client_server_options="minpoll 6 maxpoll 6"
+min_sync_time=$default_min_sync_time
+max_sync_time=$default_max_sync_time
+time_max_limit=11
+time_rms_limit=11
+freq_max_limit=1e-2
+freq_rms_limit=2e-3
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+refclock_jitter=1e-9
+refclock_offset="(* 1e-1 (triangle 1000) (+ -1.0 (pulse 1000 10000)))"
+server_step=""
+server_conf="refclock SHM 0 dpoll 4 poll 6 minsamples 4 maxsamples 4
+smoothtime 1e4 1e-6"
+client_server_options="minpoll 4 maxpoll 4"
+time_offset=0.1
+jitter=1e-6
+wander=0.0
+min_sync_time=30
+max_sync_time=40
+time_max_limit=1e-5
+time_rms_limit=5e-6
+freq_max_limit=1e-6
+freq_rms_limit=1e-7
+
+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
diff --git a/test/simulation/120-selectoptions b/test/simulation/120-selectoptions
new file mode 100755
index 0000000..611815e
--- /dev/null
+++ b/test/simulation/120-selectoptions
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "source selection options"
+
+servers=3
+falsetickers=2
+
+base_delay=0.6
+client_server_conf="
+server 192.168.123.1
+server 192.168.123.2
+server 192.168.123.3 trust"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+client_server_conf="
+server 192.168.123.1
+server 192.168.123.2
+server 192.168.123.3 prefer"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+# This check is expected to fail
+check_sync && test_fail
+
+base_delay=1.1
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+base_delay=1e-3
+falsetickers=1
+
+client_server_conf="
+server 192.168.123.1
+server 192.168.123.2
+server 192.168.123.3 require"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+client_server_conf="
+server 192.168.123.1 require
+server 192.168.123.2
+server 192.168.123.3"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+# These checks are expected to fail
+check_source_selection && test_fail
+check_sync && test_fail
+
+cat > tmp/keys <<-EOF
+1 MD5 HEX:1B81CBF88D4A73F2E8CE59647F6E5C1719B6CAF5
+EOF
+
+server_conf="keyfile tmp/keys"
+client_server_conf="
+server 192.168.123.1 key 1
+server 192.168.123.2
+server 192.168.123.3"
+
+for authselectmode in require prefer mix ignore; do
+ client_conf="keyfile tmp/keys
+ authselectmode $authselectmode"
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ if [ $authselectmode = ignore ]; then
+ check_sync || test_fail
+ else
+ check_sync && test_fail
+ fi
+done
+
+test_pass
diff --git a/test/simulation/121-orphan b/test/simulation/121-orphan
new file mode 100755
index 0000000..7579997
--- /dev/null
+++ b/test/simulation/121-orphan
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "orphan option"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+server_strata=3
+server_conf="local stratum 5 orphan
+server 192.168.123.1
+server 192.168.123.2
+server 192.168.123.3"
+max_sync_time=900
+client_start=140
+chronyc_start=700
+chronyc_conf="tracking"
+time_rms_limit=5e-4
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+check_chronyc_output "^.*Stratum *: 7.*$" || test_fail
+
+test_pass
diff --git a/test/simulation/122-xleave b/test/simulation/122-xleave
new file mode 100755
index 0000000..c19063a
--- /dev/null
+++ b/test/simulation/122-xleave
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "interleaved mode"
+
+client_server_options="xleave"
+client_conf="
+logdir tmp
+log rawmeasurements"
+
+server_conf="noclientlog"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+check_file_messages "111 111 .111.* 4I [DKH] [DKH]\$" 0 0 measurements.log || test_fail
+rm -f tmp/measurements.log
+
+server_conf=""
+max_sync_time=270
+
+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 "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
+check_file_messages "111 111 0111.* 4I [DKH] [DKH]\$" 1 1 measurements.log || test_fail
+rm -f tmp/measurements.log
+
+clients=2
+peers=2
+max_sync_time=500
+base_delay="(+ 1e-4 (* -1 (equal 0.1 from 2) (equal 0.1 to 1)))"
+
+client_lpeer_options="xleave minpoll 5 maxpoll 5"
+client_rpeer_options="minpoll 5 maxpoll 5"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+# These checks are expected to 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"
+
+ run_test || test_fail
+ 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
+
+if check_config_h 'FEAT_CMDMON 1'; then
+ # test client timestamp selection and server timestamp correction
+ base_delay="(+ 1.25e-6 (* -1 (equal 0.1 from 5)))"
+ jitter=1e-9
+ wander=1e-12
+ client_lpeer_options="xleave minpoll 5 maxpoll 5 noselect"
+ client_rpeer_options="xleave minpoll 5 maxpoll 5 noselect"
+ chronyc_conf="doffset -0.1"
+ chronyc_start=7200
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync && test_fail
+
+ check_file_messages "\.2 N 2 111 111 .... 5 5 .\... ..\....e-.. 2\....e-06" \
+ 290 310 measurements.log || test_fail
+ check_file_messages "\.2 N 2 111 111 .... 5 5 .\... ..\....e-.. .\....e-0[0123]" \
+ 0 0 measurements.log || test_fail
+ rm -f tmp/measurements.log
+fi
+
+test_pass
diff --git a/test/simulation/123-mindelay b/test/simulation/123-mindelay
new file mode 100755
index 0000000..89a9f33
--- /dev/null
+++ b/test/simulation/123-mindelay
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "mindelay and asymmetry options"
+
+jitter_asymmetry=0.499
+time_rms_limit=1e-6
+time_freq_limit=1e-9
+wander=1e-12
+
+for client_server_options in "mindelay 2e-4 asymmetry 0.499"; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_packet_interval || test_fail
+ check_sync || test_fail
+done
+
+for client_server_options in "mindelay 1e-4 asymmetry 0.499" "mindelay 2e-4 asymmetry 0.0"; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync && test_fail
+done
+
+test_pass
diff --git a/test/simulation/124-tai b/test/simulation/124-tai
new file mode 100755
index 0000000..97064f7
--- /dev/null
+++ b/test/simulation/124-tai
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "tai option"
+
+check_config_h 'FEAT_REFCLOCK 1' || test_skip
+
+export CLKNETSIM_START_DATE=$(TZ=UTC date -d 'Dec 31 2008 23:50:00' +'%s')
+
+leap=$[10 * 60]
+limit=$[20 * 60]
+min_sync_time=2
+max_sync_time=15
+refclock_jitter=1e-6
+servers=0
+
+refclock_offset="(+ -34 (equal 0.1 (max (sum 1.0) $leap) $leap))"
+client_conf="
+refclock SHM 0 dpoll 0 poll 0 tai
+leapsectz right/UTC
+leapsecmode ignore
+maxchange 1e-3 1 0"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+export CLKNETSIM_START_DATE=$(TZ=UTC date -d 'Jan 01 2009 00:10:00' +'%s')
+
+time_offset=-1000
+refclock_offset="(+ -34)"
+client_conf="
+refclock SHM 0 dpoll 0 poll 0 tai
+leapsectz right/UTC
+makestep 1 1
+maxchange 1e-3 1 0"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/125-packetloss b/test/simulation/125-packetloss
new file mode 100755
index 0000000..505e4fa
--- /dev/null
+++ b/test/simulation/125-packetloss
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "packet loss"
+
+# Drop 33% of packets by default and 100% on the 3->1 path
+base_delay=$(cat <<-EOF | tr -d '\n'
+ (+ 1e-4
+ (* -1 (equal 0.33 (uniform) 1.0))
+ (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))
+EOF
+)
+clients=2
+peers=2
+jitter=1e-5
+limit=20000
+max_sync_time=10000
+
+for options in "maxpoll 8" "maxpoll 8 xleave"; do
+ client_server_options=$options
+ client_peer_options=$options
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_sync || test_fail
+done
+
+test_pass
diff --git a/test/simulation/126-burst b/test/simulation/126-burst
new file mode 100755
index 0000000..1cb6f9c
--- /dev/null
+++ b/test/simulation/126-burst
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "burst option"
+
+# Pass every fourth packet on the 2->1 path
+base_delay=$(cat <<-EOF | tr -d '\n'
+ (+ 1e-4
+ (* -1
+ (equal 0.1 from 2)
+ (equal 0.1 to 1)
+ (equal 0.1 (min (% (sum 1) 4) 1) 1)))
+EOF
+)
+
+client_server_options="burst polltarget 1"
+min_sync_time=700
+max_sync_time=730
+client_max_min_out_interval=2.2
+client_min_mean_out_interval=150.0
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+# Add a significant delay to 70% of packets on the 2->1 path after 6th packet
+base_delay=$(cat <<-EOF | tr -d '\n'
+ (+ 1e-4
+ (* 0.15
+ (equal 0.1 from 2)
+ (equal 0.1 to 1)
+ (equal 0.1 (min (sum 1) 7) 7)
+ (equal 0.7 (uniform) 0.0)))
+EOF
+)
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+
+test_pass
diff --git a/test/simulation/127-filter b/test/simulation/127-filter
new file mode 100755
index 0000000..739dd91
--- /dev/null
+++ b/test/simulation/127-filter
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "filter option"
+
+client_server_options="minpoll 4 maxpoll 4 filter 15 maxdelay 3.5e-4"
+min_sync_time=710
+max_sync_time=720
+client_max_min_out_interval=16.1
+client_min_mean_out_interval=15.9
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+base_delay="(+ 1e-4 (* -1 (equal 0.3 (uniform) 0.0)))"
+client_server_options="minpoll 4 maxpoll 4 filter 3"
+min_sync_time=130
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+limit=10
+client_server_options="minpoll -6 maxpoll -6 filter 1"
+
+base_delay=1e-4
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_file_messages " 2 1 " 590 640 log.packets || test_fail
+
+base_delay=2e-2
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_file_messages " 2 1 " 9 10 log.packets || test_fail
+
+test_pass
diff --git a/test/simulation/128-nocontrol b/test/simulation/128-nocontrol
new file mode 100755
index 0000000..3f0d18d
--- /dev/null
+++ b/test/simulation/128-nocontrol
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "-x option"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+wander=0.0
+time_offset=0.0
+freq_offset=0.0
+time_max_limit=1e-6
+freq_max_limit=1e-9
+min_sync_time=0
+max_sync_time=0
+client_chronyd_options="-x"
+chronyc_start=300
+chronyc_conf="tracking"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+check_chronyc_output "^.*Stratum *: 2.*$" || test_fail
+
+test_pass
diff --git a/test/simulation/129-reload b/test/simulation/129-reload
new file mode 100755
index 0000000..56bc3da
--- /dev/null
+++ b/test/simulation/129-reload
@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "-r option"
+
+wander=0.0
+limit=100
+min_sync_time=100
+max_sync_time=104
+client_chronyd_options="-r"
+client_conf="dumpdir tmp
+maxupdateskew 10000"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_log_messages "Loaded dump file" 0 0 || test_fail
+check_file_messages "." 6 6 192.168.123.1.dat || test_fail
+
+client_start=$limit
+limit=1000
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+check_log_messages "Loaded dump file" 1 1 || test_fail
+check_file_messages "." 10 30 192.168.123.1.dat || test_fail
+
+rm -f tmp/*.dat
+
+client_start=0
+limit=200
+jitter=1e-6
+client_conf="dumpdir tmp
+maxupdateskew 1e-6
+maxslewrate 1e-6"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+
+check_log_messages "Loaded dump file" 0 0 || test_fail
+check_file_messages "." 8 8 192.168.123.1.dat || test_fail
+cp tmp/192.168.123.1.dat tmp/backup.dat
+
+client_start=$limit
+limit=1000
+min_sync_time=201
+max_sync_time=203
+client_server_options="offline"
+client_conf="dumpdir tmp"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+check_log_messages "Loaded dump file" 1 1 || test_fail
+check_file_messages "." 8 8 192.168.123.1.dat || test_fail
+
+cp -f tmp/backup.dat tmp/192.168.123.1.dat
+
+client_server_options="key 1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_sync && test_fail
+
+check_log_messages "Could not load dump file" 1 1 || test_fail
+check_log_messages "Loaded dump file" 0 0 || test_fail
+
+client_server_options=""
+
+if check_config_h 'FEAT_REFCLOCK 1'; then
+ refclock_jitter=1e-6
+ servers=0
+ client_start=0
+ limit=40
+ min_sync_time=56
+ max_sync_time=58
+ client_chronyd_options="-r"
+ client_conf="dumpdir tmp
+ refclock SHM 0"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+
+ check_log_messages "Loaded dump file" 0 0 || test_fail
+ check_file_messages "." 6 6 refid:53484d30.dat || test_fail
+
+ client_start=$limit
+ limit=300
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+
+ check_log_messages "Loaded dump file" 1 1 || test_fail
+ check_file_messages "." 6 23 refid:53484d30.dat || test_fail
+
+ rm -f tmp/*.dat
+fi
+
+test_pass
diff --git a/test/simulation/130-quit b/test/simulation/130-quit
new file mode 100755
index 0000000..da2b8cf
--- /dev/null
+++ b/test/simulation/130-quit
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "-q/-Q option"
+
+wander=0.0
+freq_offset=0.0
+min_sync_time=5
+max_sync_time=10
+client_chronyd_options="-q"
+client_server_options="iburst"
+
+run_test || test_fail
+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
+
+test_pass
diff --git a/test/simulation/131-maxchange b/test/simulation/131-maxchange
new file mode 100755
index 0000000..59cc0c1
--- /dev/null
+++ b/test/simulation/131-maxchange
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "maxchange directive"
+
+time_offset=2
+max_sync_time=5000
+client_conf="maxchange 0.1 1 3"
+client_step="(* $step (equal 0.1 (sum 1.0) 300))"
+
+run_test || test_fail
+check_chronyd_exit && test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync && test_fail
+check_log_messages "seconds exceeds.*ignored" 3 3 || test_fail
+check_log_messages "seconds exceeds.*exiting" 1 1 || test_fail
+
+test_pass
diff --git a/test/simulation/132-logchange b/test/simulation/132-logchange
new file mode 100755
index 0000000..8ef570e
--- /dev/null
+++ b/test/simulation/132-logchange
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "logchange directive"
+
+time_offset=2
+min_sync_time=590
+max_sync_time=700
+client_server_options="maxsamples 6"
+client_conf="logchange 0.1"
+client_step="(* $step (equal 0.1 (sum 1.0) 300))"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+check_log_messages "clock wrong by" 3 8 || test_fail
+
+test_pass
diff --git a/test/simulation/133-hwtimestamp b/test/simulation/133-hwtimestamp
new file mode 100755
index 0000000..f02a010
--- /dev/null
+++ b/test/simulation/133-hwtimestamp
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "hwtimestamp directive"
+
+check_config_h 'HAVE_LINUX_TIMESTAMPING 1' || test_skip
+
+export CLKNETSIM_TIMESTAMPING=2
+export CLKNETSIM_PHC_DELAY=1e-6
+export CLKNETSIM_PHC_JITTER=1e-7
+export CLKNETSIM_PHC_JITTER_ASYM=0.4
+
+refclock_jitter=1e-8
+refclock_offset=10.0
+min_sync_time=4
+max_sync_time=20
+time_rms_limit=1e-7
+freq_rms_limit=3e-8
+jitter=1e-8
+freq_offset=1e-5
+limit=200
+server_conf="
+clockprecision 1e-9
+hwtimestamp eth0"
+client_server_options="minpoll 0 maxpoll 0 xleave"
+client_chronyd_options="-d"
+
+for client_conf in \
+ "hwtimestamp eth0 nocrossts
+ clockprecision 1e-9" \
+ "hwtimestamp eth0
+ clockprecision 1e-9
+ acquisitionport 123"; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+
+ if check_config_h 'FEAT_DEBUG 1'; then
+ check_log_messages "Accepted reading" 0 2 || test_fail
+ check_log_messages "Combined .* readings" 190 220 || test_fail
+ check_log_messages "HW clock samples" 190 200 || test_fail
+ check_log_messages "HW clock reset" 0 0 || test_fail
+ check_log_messages "Missing TX timestamp" 1 1 || 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 "Polling PHC" 195 220 || test_fail
+ if echo "$client_conf" | grep -q nocrossts; then
+ check_log_messages "update_tx_timestamp.*Updated" 180 200 || test_fail
+ check_log_messages "update_tx_timestamp.*Unacceptable" 0 13 || test_fail
+ else
+ check_log_messages "update_tx_timestamp.*Updated" 50 140 || test_fail
+ check_log_messages "update_tx_timestamp.*Unacceptable" 50 140 || test_fail
+ fi
+ fi
+done
+
+server_conf+="
+server 192.168.123.2 minpoll 1 maxpoll 1 noselect"
+
+for maxpoll in -1 0 1; do
+ client_conf="hwtimestamp eth0 minpoll -1 maxpoll $maxpoll nocrossts"
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+
+ if check_config_h 'FEAT_DEBUG 1'; then
+ case $maxpoll in
+ -1)
+ check_log_messages "Polling PHC on eth0$" 360 380 || test_fail
+ check_log_messages "Polling PHC.*before" 3 25 || test_fail
+ ;;
+ 0)
+ check_log_messages "Polling PHC on eth0$" 8 45 || test_fail
+ check_log_messages "Polling PHC.*before" 150 190 || test_fail
+ ;;
+ 1)
+ check_log_messages "Polling PHC on eth0$" 1 1 || test_fail
+ check_log_messages "Polling PHC.*before" 194 199 || test_fail
+ ;;
+ esac
+ fi
+done
+
+test_pass
diff --git a/test/simulation/134-log b/test/simulation/134-log
new file mode 100755
index 0000000..ab1ced2
--- /dev/null
+++ b/test/simulation/134-log
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "log directive"
+
+check_config_h 'FEAT_PHC 1' || test_skip
+
+refclock_jitter=$jitter
+client_server_options="maxpoll 6"
+client_conf="refclock PHC /dev/ptp0 dpoll 4 poll 6 noselect
+logbanner 10
+logdir tmp
+log tracking rawmeasurements measurements selection statistics rtc refclocks tempcomp
+tempcomp tmp/tempcomp 64 0.0 0.0 0.0 0.0"
+
+echo 0.0 > tmp/tempcomp
+
+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 "=============" 31 33 \
+ tracking.log measurements.log tempcomp.log || test_fail
+check_file_messages "20.*192\.168\.123\.1" 150 160 \
+ tracking.log measurements.log statistics.log || test_fail
+check_file_messages "20.*PHC0 * N " 300 320 selection.log || test_fail
+check_file_messages "20.*192\.168\.123\.1 *[M*]" 300 320 selection.log || test_fail
+check_file_messages "20.*PHC0" 150 160 statistics.log || test_fail
+check_file_messages "20.*PHC0" 750 800 refclocks.log || test_fail
+check_file_messages "20.* 0\.0000" 150 160 tempcomp.log || test_fail
+
+test_pass
diff --git a/test/simulation/135-ratelimit b/test/simulation/135-ratelimit
new file mode 100755
index 0000000..86c435d
--- /dev/null
+++ b/test/simulation/135-ratelimit
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "ratelimit directive"
+
+server_conf="ratelimit interval 6 burst 2 leak 4"
+client_server_options="minpoll 3 maxpoll 3"
+min_sync_time=16
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+check_file_messages " 2 1 " 1200 1300 log.packets || test_fail
+check_file_messages " 1 2 " 180 220 log.packets || test_fail
+
+test_pass
diff --git a/test/simulation/136-broadcast b/test/simulation/136-broadcast
new file mode 100755
index 0000000..1488c53
--- /dev/null
+++ b/test/simulation/136-broadcast
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "broadcast directive"
+
+server_conf="broadcast 64 192.168.123.255"
+client_server_options="offline"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval && test_fail
+
+check_file_messages " 1 2 " 150 160 log.packets || test_fail
+
+test_pass
diff --git a/test/simulation/137-pool b/test/simulation/137-pool
new file mode 100755
index 0000000..de8d77d
--- /dev/null
+++ b/test/simulation/137-pool
@@ -0,0 +1,49 @@
+#!/usr/bin/env 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
diff --git a/test/simulation/138-syncloop b/test/simulation/138-syncloop
new file mode 100755
index 0000000..2d3999e
--- /dev/null
+++ b/test/simulation/138-syncloop
@@ -0,0 +1,34 @@
+#!/usr/bin/env 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
diff --git a/test/simulation/139-nts b/test/simulation/139-nts
new file mode 100755
index 0000000..f1d2de3
--- /dev/null
+++ b/test/simulation/139-nts
@@ -0,0 +1,316 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "NTP authentication with NTS"
+
+check_config_h 'FEAT_NTS 1' || test_skip
+certtool --help &> /dev/null || test_skip
+
+export CLKNETSIM_START_DATE=$(date -d 'Jan 1 00:00:00 UTC 2010' +'%s')
+
+for i in 1 2; do
+ cat > tmp/cert$i.cfg <<-EOF
+ cn = "node$i.net1.clk"
+ dns_name = "node$i.net1.clk"
+ ip_address = "192.168.123.$i"
+ serial = 001
+ activation_date = "2010-01-01 00:00:00 UTC"
+ expiration_date = "2010-01-02 00:00:00 UTC"
+ signing_key
+ encryption_key
+ EOF
+
+ certtool --generate-privkey --key-type=ed25519 --outfile tmp/server$i.key &> \
+ tmp/log.certtool$i
+ certtool --generate-self-signed --load-privkey tmp/server$i.key \
+ --template tmp/cert$i.cfg --outfile tmp/server$i.crt &>> tmp/log.certtool$i
+done
+
+max_sync_time=400
+dns=1
+server_conf="
+ntsserverkey tmp/server1.key
+ntsservercert tmp/server1.crt
+ntsprocesses 0
+ntsrotate 66
+ntsdumpdir tmp
+"
+client_server_options="minpoll 6 maxpoll 6 nts"
+client_conf="
+nosystemcert
+ntstrustedcerts /dev/null
+ntstrustedcerts tmp/server1.crt
+ntstrustedcerts /dev/null
+logdir tmp
+log rawmeasurements"
+
+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 1111" 75 80 measurements.log || test_fail
+check_file_messages "20.*123\.1.* 111 001 0000" 37 39 measurements.log || test_fail
+check_file_messages " 2 1 .* 4460 " 260 300 log.packets || test_fail
+check_file_messages "." 6 6 ntskeys || test_fail
+rm -f tmp/measurements.log
+
+client_conf+="
+ntsrefresh 120
+ntsdumpdir tmp"
+
+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 1111" 99 103 measurements.log || test_fail
+check_file_messages "20.*123\.1.* 111 001 0000" 0 0 measurements.log || test_fail
+check_file_messages " 2 1 .* 4460 " 350 390 log.packets || test_fail
+check_file_messages "." 6 6 ntskeys || test_fail
+check_file_messages "." 12 13 192.168.123.1.nts || test_fail
+rm -f tmp/measurements.log
+
+export CLKNETSIM_START_DATE=$(date -d 'Jan 1 00:00:00 UTC 2010 + 40000 sec' +'%s')
+
+server_conf+="
+ntsrotate 100000"
+client_conf+="
+ntsrefresh 39500"
+
+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 1111" 150 160 measurements.log || test_fail
+check_file_messages "20.*123\.1.* 111 001 0000" 0 0 measurements.log || test_fail
+check_file_messages " 2 1 .* 4460 " 6 10 log.packets || test_fail
+check_file_messages "^9\.......e+03 2 1 .* 4460 " 6 10 log.packets || test_fail
+check_file_messages "." 6 6 ntskeys || test_fail
+check_file_messages "." 12 13 192.168.123.1.nts || test_fail
+rm -f tmp/measurements.log
+
+client_conf="
+nosystemcert"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection && test_fail
+check_sync && test_fail
+
+check_file_messages " 2 1 .* 123 " 0 0 log.packets || test_fail
+check_file_messages " 2 1 .* 4460 " 10 20 log.packets || test_fail
+
+export CLKNETSIM_START_DATE=$(date -d 'Jan 2 00:00:01 UTC 2010' +'%s')
+
+client_conf="
+nosystemcert
+ntstrustedcerts tmp/server1.crt"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection && test_fail
+check_sync && test_fail
+
+check_file_messages " 2 1 .* 123 " 0 0 log.packets || test_fail
+check_file_messages " 2 1 .* 4460 " 10 20 log.packets || test_fail
+check_log_messages "expired certificate" 4 4 || test_fail
+
+client_conf+="
+nocerttimecheck 1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+export CLKNETSIM_START_DATE=$(date -d 'Jan 1 00:00:00 UTC 2010' +'%s')
+
+client_conf="
+nosystemcert
+ntstrustedcerts tmp/server1.crt
+ntsrefresh 500"
+
+for dns in 1 0; do
+ server_conf="
+ ntsserverkey tmp/server1.key
+ ntsservercert tmp/server1.crt
+ ntsprocesses 0
+ ntsrotate 0
+ ntsdumpdir tmp"
+
+ if [ $dns != 0 ]; then
+ server_conf+="
+ ntsntpserver node2.net1.clk"
+ client_server_conf="server node1.net1.clk $client_server_options"
+ else
+ server_conf+="
+ ntsntpserver 192.168.123.2"
+ client_server_conf="server 192.168.123.1 $client_server_options"
+ fi
+
+ servers=1
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection && test_fail
+ check_sync && test_fail
+
+ check_file_messages " 2 1 .* 4460 " 45 100 log.packets || test_fail
+ check_file_messages " 2 2 .* 4460 " 0 0 log.packets || test_fail
+ check_log_messages "Source 192.168.123.1 changed to 192.168.123.2" 4 10 || test_fail
+ check_log_messages "Source 192.168.123.2 replaced with 192.168.123.1" 3 10 || test_fail
+
+ servers=2
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+
+ check_file_messages " 3 1 .* 4460 " 100 150 log.packets || test_fail
+ check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail
+ check_log_messages "Source 192.168.123.1 changed to 192.168.123.2" 1 1 || test_fail
+ check_log_messages "Source 192.168.123.2 replaced with 192.168.123.1" 0 0 || test_fail
+
+ server_conf+="
+ ntsratelimit interval 12 burst 1 leak 4"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection && test_fail
+
+ check_file_messages " 3 1 .* 4460 1 0 2" 25 50 log.packets || test_fail
+ check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail
+ check_log_messages "Source 192.168.123.1 changed to 192.168.123.2" 2 6 || test_fail
+ check_log_messages "Source 192.168.123.2 replaced with 192.168.123.1" 1 6 || test_fail
+done
+
+servers=2
+server_conf="
+ntsserverkey tmp/server1.key
+ntsservercert tmp/server1.crt
+ntsprocesses 0
+ntsrotate 0
+ntsntpserver node2.net1.clk
+port 11123
+ntsdumpdir tmp"
+client_conf="
+nosystemcert
+ntstrustedcerts tmp/server1.crt
+ntsdumpdir tmp"
+client_server_conf="server 192.168.123.1 $client_server_options"
+
+rm -f tmp/*.nts
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+check_log_messages "Could not change" 0 0 || test_fail
+check_file_messages " 3 1 .* 4460 1 0 2" 1 1 log.packets || test_fail
+check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail
+
+for dns in 1 0; do
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+
+ check_log_messages "Could not change" 0 0 || test_fail
+ check_file_messages " 3 1 .* 4460 1 0 2" 0 0 log.packets || test_fail
+ check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail
+done
+
+min_sync_time=$[default_min_sync_time + 200]
+max_sync_time=600
+server_conf="
+ntsserverkey tmp/server1.key
+ntsservercert tmp/server1.crt
+ntsprocesses 0
+ntsrotate 0
+ntsdumpdir tmp"
+
+head -n 8 tmp/192.168.123.1.nts > tmp/192.168.123.1.nts_
+mv tmp/192.168.123.1.nts_ tmp/192.168.123.1.nts
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+check_log_messages "Could not change" 0 0 || test_fail
+check_file_messages " 3 1 .* 4460 1 0 2" 1 1 log.packets || test_fail
+check_file_messages " 3 2 .* 4460 " 0 0 log.packets || test_fail
+check_file_messages " 3 1 .* 11123 " 0 0 log.packets || test_fail
+check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail
+check_file_messages " 3 2 .* 11123 " 3 3 log.packets || test_fail
+
+dns=1
+min_sync_time=$default_min_sync_time
+max_sync_time=400
+server_conf="
+ntsserverkey tmp/server1.key
+ntsservercert tmp/server1.crt
+ntsserverkey tmp/server2.key
+ntsservercert tmp/server2.crt
+ntsprocesses 0"
+client_conf="
+nosystemcert
+ntstrustedcerts tmp/server1.crt
+ntstrustedcerts tmp/server2.crt
+minsources 2"
+client_server_conf=""
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+client_conf="
+nosystemcert
+ntstrustedcerts tmp/server1.crt
+ntstrustedcerts 1 tmp/server1.crt
+ntstrustedcerts 2 tmp/server2.crt
+ntstrustedcerts 3 tmp/server2.crt"
+client_server_conf="
+server node1.net1.clk $client_server_options certset 0
+server node2.net1.clk $client_server_options certset 2"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+check_file_messages " 3 1 .* 123 " 100 200 log.packets || test_fail
+check_file_messages " 3 2 .* 123 " 100 200 log.packets || test_fail
+
+client_server_conf="
+server node1.net1.clk $client_server_options certset 2
+server node2.net1.clk $client_server_options"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection && test_fail
+check_sync && test_fail
+
+check_file_messages " 3 1 .* 123 " 0 0 log.packets || test_fail
+check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail
+
+client_conf="
+nosystemcert
+ntstrustedcerts tmp/nosuch.crt
+ntstrustedcerts 2 tmp/nosuch.crt"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection && test_fail
+check_sync && test_fail
+
+check_file_messages " 3 1 .* 123 " 0 0 log.packets || test_fail
+check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail
+
+test_pass
diff --git a/test/simulation/140-noclientlog b/test/simulation/140-noclientlog
new file mode 100755
index 0000000..502398f
--- /dev/null
+++ b/test/simulation/140-noclientlog
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "noclientlog option"
+
+server_conf="noclientlog"
+client_server_options="xleave"
+client_conf="
+logdir tmp
+log rawmeasurements"
+
+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 "111 111 1111.* 4B " 30 200 measurements.log || test_fail
+check_file_messages "111 111 1111.* 4I " 0 0 measurements.log || test_fail
+
+test_pass
diff --git a/test/simulation/141-copy b/test/simulation/141-copy
new file mode 100755
index 0000000..80e56bc
--- /dev/null
+++ b/test/simulation/141-copy
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "copy option"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+client_server_options="copy"
+chronyc_conf="tracking"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+check_chronyc_output "^Reference ID *: 7F7F0101 \(192\.168\.123\.1\)
+Stratum *: 1" || test_fail
+
+test_pass
diff --git a/test/simulation/142-ntpoverptp b/test/simulation/142-ntpoverptp
new file mode 100755
index 0000000..2996dc0
--- /dev/null
+++ b/test/simulation/142-ntpoverptp
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "NTP over PTP"
+
+# Block communication between 3 and 1
+base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))"
+
+cat > tmp/peer.keys <<-EOF
+1 MD5 1234567890
+EOF
+
+clients=2
+peers=2
+max_sync_time=420
+
+server_conf="
+ptpport 319"
+client_conf="
+ptpport 319
+authselectmode ignore
+keyfile tmp/peer.keys"
+client_server_options="minpoll 6 maxpoll 6 port 319"
+client_peer_options="minpoll 6 maxpoll 6 port 319 key 1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+check_file_messages " 2 1 .* 319 319 1 96 " 150 160 \
+ log.packets || test_fail
+check_file_messages " 1 2 .* 319 319 1 96 " 150 160 \
+ log.packets || test_fail
+check_file_messages " 2 3 .* 319 319 1 116 " 150 160 \
+ log.packets || test_fail
+check_file_messages " 3 2 .* 319 319 1 116 " 150 160 \
+ log.packets || test_fail
+
+check_config_h 'HAVE_LINUX_TIMESTAMPING 1' || test_skip
+
+export CLKNETSIM_TIMESTAMPING=2
+export CLKNETSIM_LINK_SPEED=100
+
+client_server_options+=" extfield F324 minpoll 0 maxpoll 0"
+client_peer_options+=" extfield F324 minpoll 0 maxpoll 0 maxdelaydevratio 1e6"
+server_conf+="
+clockprecision 1e-9
+hwtimestamp eth0"
+client_conf+="
+clockprecision 1e-9
+hwtimestamp eth0"
+delay_correction="(+ delay (* -8e-8 (+ length 46)))"
+wander=1e-9
+limit=1000
+freq_offset=-1e-4
+min_sync_time=5
+max_sync_time=20
+time_max_limit=1e-7
+time_rms_limit=2e-8
+freq_max_limit=1e-7
+freq_rms_limit=5e-8
+client_chronyd_options="-d"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+if check_config_h 'FEAT_DEBUG 1'; then
+ check_log_messages "apply_net_correction.*Applied" 900 2100 || test_fail
+ check_log_messages "apply_net_correction.*Invalid" 0 4 || test_fail
+fi
+
+client_server_options+=" xleave"
+client_peer_options+=" xleave"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+if check_config_h 'FEAT_DEBUG 1'; then
+ check_log_messages "apply_net_correction.*Applied" 900 2100 || test_fail
+ check_log_messages "apply_net_correction.*Invalid" 0 4 || test_fail
+
+ freq_offset=0.0
+ delay_correction="(+ -1.0e-9 (* 1.0001 delay))"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+
+ check_log_messages "apply_net_correction.*Applied" 350 1400 || test_fail
+ check_log_messages "apply_net_correction.*Invalid" 350 1400 || test_fail
+
+ server_conf="ptpport 319"
+ client_conf="ptpport 319"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+
+ check_log_messages "apply_net_correction.*Applied" 0 0 || test_fail
+fi
+
+test_pass
diff --git a/test/simulation/143-manual b/test/simulation/143-manual
new file mode 100755
index 0000000..618cee6
--- /dev/null
+++ b/test/simulation/143-manual
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+export TZ=UTC
+
+test_start "manual input"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+limit=$[12 * 3600]
+client_server_conf=" "
+client_conf="manual"
+chronyc_conf="timeout 4000000
+settime 1:00:00
+settime 2:00:00
+settime 3:00:00
+settime 4:00:00
+manual delete 2
+settime 6:00:00
+manual list
+settime 8:00:00
+manual reset
+settime 10:00:00
+manual list"
+chronyc_start=1800
+base_delay=1800
+jitter=1e-6
+
+time_max_limit=4e-3
+freq_max_limit=4e-3
+time_rms_limit=2e-3
+freq_rms_limit=2e-5
+min_sync_time=7204
+max_sync_time=7206
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_sync || test_fail
+
+check_chronyc_output "^200 OK
+Clock was 0\.4. seconds fast\. Frequency change = 0\.00ppm, new frequency = 0\.00ppm
+200 OK
+Clock was 0\.3. seconds fast\. Frequency change = (99|100)\...ppm, new frequency = (99|100)\...ppm
+200 OK
+Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[01].ppm, new frequency = (99|100)\...ppm
+200 OK
+Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[01].ppm, new frequency = (99|100)\...ppm
+200 OK
+200 OK
+Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[012].ppm, new frequency = (99|100)\...ppm
+210 n_samples = 4
+# Date Time\(UTC\) Slewed Original Residual
+=======================================================
+ 0 2010-01-01 (00:59:59|01:00:00) [- ]0\.00 0\.46 [- ]0\.00
+ 1 2010-01-01 (01:59:59|02:00:00) [- ]0\.00 0\.36 [- ]0\.00
+ 2 2010-01-01 (03:59:59|04:00:00) [- ]0\.00 [- ]0\.00 [- ]0\.00
+ 3 2010-01-01 (05:59:59|06:00:00) [- ]0\.00 [- ]0\.00 [- ]0\.00
+200 OK
+Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.[012].ppm, new frequency = (99|100)\...ppm
+200 OK
+200 OK
+Clock was \-?0\.0. seconds fast\. Frequency change = \-?0\.00ppm, new frequency = (99|100)\...ppm
+210 n_samples = 1
+# Date Time\(UTC\) Slewed Original Residual
+=======================================================
+ 0 2010-01-01 (09:59:59|10:00:00) [- ]0\.00 [- ]0\.00 [- ]0\.00$" \
+ || test_fail
+
+test_pass
diff --git a/test/simulation/144-monoroot b/test/simulation/144-monoroot
new file mode 100755
index 0000000..20fae12
--- /dev/null
+++ b/test/simulation/144-monoroot
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "mono+root extension field"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+primary_time_offset=0.1
+server_strata=4
+min_sync_time=2000
+max_sync_time=2300
+chronyc_conf="doffset 0.1"
+chronyc_options="-h /clknetsim/unix/1:1"
+chronyc_start=2000
+
+for options in "extfield F323" "xleave extfield F323"; do
+ client_server_options="minpoll 6 maxpoll 6 $options"
+ server_server_options="$client_server_options"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+done
+
+server_server_options=""
+server_strata=1
+clients=4
+peers=4
+max_sync_time=2400
+# chain of peers and one enabled chronyc
+base_delay=$(cat <<-EOF | tr -d '\n'
+ (+ 1e-4 -1
+ (equal 0.1 from (+ to 1))
+ (equal 0.1 from (+ to -1))
+ (equal 0.1 from 6)
+ (equal 0.1 to 6))
+EOF
+)
+
+for lpoll in 5 6 7; do
+ for options in "minsamples 16 extfield F323" "minsamples 16 xleave extfield F323"; do
+ client_lpeer_options="minpoll $lpoll maxpoll $lpoll $options"
+ client_rpeer_options="minpoll 6 maxpoll 6 $options"
+ client_server_options="$client_rpeer_options"
+
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_source_selection || test_fail
+ check_sync || test_fail
+ done
+done
+
+test_pass
diff --git a/test/simulation/145-rtc b/test/simulation/145-rtc
new file mode 100755
index 0000000..22b62d9
--- /dev/null
+++ b/test/simulation/145-rtc
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+. ./test.common
+test_start "RTC tracking"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+check_config_h 'FEAT_RTC 1' || test_skip
+
+export CLKNETSIM_START_DATE=$(date -d 'Jan 1 00:00:00 UTC 2010' +'%s')
+export CLKNETSIM_RTC_OFFSET=-10.0
+
+time_offset=$(awk "BEGIN {print -($freq_offset * $limit)}")
+wander=0.0
+chronyc_start=9900
+chronyc_conf="rtcdata"
+client_chronyd_options="-x"
+
+client_conf="
+hwclockfile /dev/null
+driftfile tmp/drift
+rtcfile tmp/rtc
+rtconutc"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_chronyc_output "^RTC ref time \(UTC\) : Fri Jan 01 02:4[34]:.. 2010
+Number of samples : [0-9]+
+Number of runs : [0-9]+
+Sample span period : [ 0-9]+
+RTC is fast by : -9\.01.... seconds
+RTC gains time at : 99\.9[98]. ppm$" \
+|| test_fail
+
+export CLKNETSIM_START_DATE=$(date -d 'Jan 5 00:00:00 UTC 2010' +'%s')
+export CLKNETSIM_RTC_OFFSET=$(awk "BEGIN {print -(10.0 - 4 * 86400 * $freq_offset)}")
+touch -d 'Jan 1 00:00:00 UTC 2010' tmp/drift
+
+time_offset=10
+min_sync_time=2
+max_sync_time=12
+time_max_limit=1e-2
+freq_max_limit=1e-1
+time_rms_limit=1e-3
+freq_rms_limit=1e-3
+client_chronyd_options="-s"
+client_conf+="
+rtcautotrim 1"
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+check_chronyc_output "^RTC ref time \(UTC\) : Tue Jan 05 02:4[34]:.. 2010
+Number of samples : [0-9]+
+Number of runs : [0-9]+
+Sample span period : [ 0-9]+
+RTC is fast by : 0\.1..... seconds
+RTC gains time at : [- ]0\.0.. ppm$" \
+|| test_fail
+
+export CLKNETSIM_START_DATE=$(date -d 'Jan 10 00:00:00 UTC 2010' +'%s')
+export CLKNETSIM_RTC_OFFSET=-10.0
+touch -d 'Jan 10 00:00:00 UTC 2010' tmp/drift
+
+time_offset=-10000
+min_sync_time=1
+max_sync_time=1
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_sync || test_fail
+
+test_pass
diff --git a/test/simulation/146-offline b/test/simulation/146-offline
new file mode 100755
index 0000000..f110a12
--- /dev/null
+++ b/test/simulation/146-offline
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "online/offline switching"
+
+check_config_h 'FEAT_CMDMON 1' || test_skip
+
+servers=2
+limit=$[10 * 1800]
+client_server_conf="
+server 192.168.123.1 offline iburst
+server 192.168.123.2 polltarget 64"
+chronyc_conf="timeout 4000000
+activity
+offline
+activity
+onoffline 192.168.123.1
+online 192.168.123.2
+activity
+offline
+activity
+"
+chronyc_start=1
+base_delay="(+ 1e-4 (* 1800 (equal 0.1 from 4)))"
+jitter=1e-6
+
+time_max_limit=2e-2
+freq_max_limit=1e-3
+time_rms_limit=2e-2
+freq_rms_limit=1e-5
+min_sync_time=120
+max_sync_time=140
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+check_file_messages " 3 1 .* 123 " 30 90 log.packets || test_fail
+check_file_messages " 3 2 .* 123 " 130 150 log.packets || test_fail
+
+check_chronyc_output "^200 OK
+1 sources online
+1 sources offline
+0 sources doing burst \(return to online\)
+0 sources doing burst \(return to offline\)
+0 sources with unknown address
+200 OK
+200 OK
+0 sources online
+2 sources offline
+0 sources doing burst \(return to online\)
+0 sources doing burst \(return to offline\)
+0 sources with unknown address
+200 OK
+200 OK
+200 OK
+2 sources online
+0 sources offline
+0 sources doing burst \(return to online\)
+0 sources doing burst \(return to offline\)
+0 sources with unknown address
+200 OK
+200 OK
+0 sources online
+2 sources offline
+0 sources doing burst \(return to online\)
+0 sources doing burst \(return to offline\)
+0 sources with unknown address" \
+ || test_fail
+
+test_pass
diff --git a/test/simulation/147-refresh b/test/simulation/147-refresh
new file mode 100755
index 0000000..ea091e6
--- /dev/null
+++ b/test/simulation/147-refresh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "address refreshment"
+
+limit=1000
+servers=5
+client_conf="logdir tmp
+log measurements"
+client_server_conf="server nodes-1-2.net1.clk maxpoll 6
+pool nodes-3-4-5.net1.clk maxpoll 6 maxsources 2"
+client_chronyd_options="-d"
+chronyc_conf="refresh"
+chronyc_start=500
+
+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" 0 0 measurements.log || test_fail
+check_file_messages "20.*192.168.123.2" 15 17 measurements.log || test_fail
+check_file_messages "20.*192.168.123.[345]" 31 33 measurements.log || test_fail
+rm -f tmp/measurements.log
+if check_config_h 'FEAT_DEBUG 1'; then
+ check_log_messages "refreshing 192.168.123" 3 3 || test_fail
+ check_log_messages "resolved_name.*still fresh" 3 3 || test_fail
+fi
+
+limit=1100
+client_server_conf="
+server nodes-1-2.net1.clk maxpoll 6
+pool nodes-3-4-5.net1.clk maxpoll 6 maxsources 3"
+client_conf+="
+refresh 128"
+chronyc_conf=""
+
+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" 0 0 measurements.log || test_fail
+check_file_messages "20.*192.168.123.2" 16 18 measurements.log || test_fail
+check_file_messages "20.*192.168.123.[345]" 50 55 measurements.log || test_fail
+rm -f tmp/measurements.log
+if check_config_h 'FEAT_DEBUG 1'; then
+ check_log_messages "refreshing 192.168.123" 8 8 || test_fail
+ check_log_messages "resolved_name.*still fresh" 8 8 || test_fail
+ check_log_messages "refreshing 192.168.123.2" 2 2 || test_fail
+ check_log_messages "refreshing 192.168.123.3" 2 2 || test_fail
+ check_log_messages "refreshing 192.168.123.4" 2 2 || test_fail
+ check_log_messages "refreshing 192.168.123.5" 2 2 || test_fail
+fi
+
+test_pass
diff --git a/test/simulation/148-replacement b/test/simulation/148-replacement
new file mode 100755
index 0000000..f15fc4d
--- /dev/null
+++ b/test/simulation/148-replacement
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "source replacement"
+
+limit=5000
+client_conf="logdir tmp
+log measurements"
+
+servers=6
+falsetickers=2
+client_server_conf="pool nodes-1-2-3-4-5-6.net1.clk maxsources 5 polltarget 1 iburst"
+wander=1e-12
+jitter=1e-6
+min_sync_time=7
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection || test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+check_log_messages "Detected falseticker" 2 10 || test_fail
+check_log_messages "Source 192.168.123.. replaced with" 1 3 || test_fail
+check_file_messages "20.*192.168.123.* 11.1 6 6 " 15 18 measurements.log || test_fail
+check_file_messages "20.*00:[1-5].:.. 192.168.123.* 11.1 6 6 " 1 4 measurements.log || test_fail
+rm -f tmp/measurements.log
+
+# 1 unreplaceable falseticker against 2 replaceable unreachable servers
+servers=5
+falsetickers=1
+limit=200000
+base_delay="(+ 1e-4 (* -1 (equal 0.6 to 4.5)))"
+client_conf+="
+minsources 2"
+client_server_conf="
+server 192.168.123.1
+server nodes-2-4.net1.clk
+server nodes-3-5.net1.clk"
+max_sync_time=150000
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_source_selection && test_fail
+check_packet_interval || test_fail
+check_sync || test_fail
+
+check_log_messages "Detected falseticker" 2 10 || test_fail
+check_log_messages "Source 192.168.123.. replaced with" 2 70 || test_fail
+check_log_messages "2010-01-01T0[0-4]:.*Source 192.168.123.. replaced with" 2 15 || test_fail
+check_log_messages "2010-01-01T0[5-9]:.*Source 192.168.123.. replaced with" 0 15 || test_fail
+check_file_messages "20.*192.168.123.* 11.1 6 6 " 20 500 measurements.log || test_fail
+rm -f tmp/measurements.log
+
+test_pass
diff --git a/test/simulation/201-freqaccumulation b/test/simulation/201-freqaccumulation
new file mode 100755
index 0000000..6f14246
--- /dev/null
+++ b/test/simulation/201-freqaccumulation
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+# Test fix in commit 60d0fa299307076143da94d36deb7b908fa9bdb7
+
+test_start "frequency accumulation"
+
+time_offset=100.0
+jitter=1e-6
+base_delay=1e-6
+wander=0.0
+
+limit=180
+time_max_limit=1e-5
+freq_max_limit=1e-7
+time_rms_limit=1e-5
+freq_rms_limit=1e-7
+min_sync_time=120
+max_sync_time=140
+
+client_server_options="minpoll 6 maxpoll 6"
+client_conf="driftfile tmp/drift
+makestep 1 1"
+
+for freq_offset in -5e-2 -5e-4 5e-4 5e-2; do
+ for drift in -1e+4 -1e+2 1e+2 1e+4; do
+ echo "$drift 100000" > tmp/drift
+ run_test || test_fail
+ check_chronyd_exit || test_fail
+ check_sync || test_fail
+ done
+done
+
+test_pass
diff --git a/test/simulation/202-prefer b/test/simulation/202-prefer
new file mode 100755
index 0000000..207c800
--- /dev/null
+++ b/test/simulation/202-prefer
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+# Test fix in commit 4253075a97141edfa62043ab71bd0673587e6629
+
+test_start "prefer option"
+
+servers=3
+client_server_conf="
+server 192.168.123.1
+server 192.168.123.2
+server 192.168.123.3 prefer"
+
+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
diff --git a/test/simulation/README b/test/simulation/README
new file mode 100644
index 0000000..e174500
--- /dev/null
+++ b/test/simulation/README
@@ -0,0 +1,11 @@
+This is a collection of simulation tests using the clknetsim simulator
+(supported on Linux only).
+
+https://github.com/mlichvar/clknetsim
+
+The CLKNETSIM_PATH environment variable should point to the directory where
+clknetsim was downloaded and compiled. If the variable is not set, the tests
+will look for clknetsim in ./clknetsim in the current directory.
+
+The tests are written in bash and they can be run directly. The ./run script
+runs all tests.
diff --git a/test/simulation/run b/test/simulation/run
new file mode 100755
index 0000000..0954438
--- /dev/null
+++ b/test/simulation/run
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+
+print_help() {
+ echo "$1 [-a] [-i ITERATIONS] [-m MAXFAILS] [-s SEED] [TEST]..."
+}
+
+run_test() {
+ local result name=$1 seed=$2
+
+ CLKNETSIM_RANDOM_SEED=$seed ./$name
+ result=$?
+
+ if [ $result -ne 0 -a $result -ne 9 ]; then
+ if [ $abort_on_fail -ne 0 ]; then
+ echo 1>&2
+ echo Failed with random seed $seed 1>&2
+ exit 1
+ fi
+ failed_seeds=(${failed_seeds[@]} $seed)
+ fi
+
+ return $result
+}
+
+abort_on_fail=0
+iterations=1
+max_fails=0
+random_seed=${CLKNETSIM_RANDOM_SEED:-$RANDOM}
+
+while getopts ":ai:m:s:" opt; do
+ case $opt in
+ a) abort_on_fail=1;;
+ i) iterations=$OPTARG;;
+ m) max_fails=$OPTARG;;
+ s) random_seed=$OPTARG;;
+ *) print_help "$0"; exit 3;;
+ esac
+done
+
+shift $[$OPTIND - 1]
+
+passed=() failed=() skipped=() failed_seeds=()
+
+[ $# -gt 0 ] && tests=($@) || tests=([0-9]*-*[^_])
+
+for test in "${tests[@]}"; do
+ if [ $iterations -gt 1 ]; then
+ printf "%-30s" "$test"
+ fails=0
+ for i in $(seq 1 $iterations); do
+ run_test $test $[$random_seed + $i - 1] > /dev/null
+ case $? in
+ 0) echo -n ".";;
+ 9) break;;
+ *) echo -n "x"; fails=$[$fails + 1];;
+ esac
+ done
+ if [ $i -lt $iterations ]; then
+ printf "%${iterations}s" ""
+ echo " SKIP"
+ result=9
+ elif [ $fails -gt $max_fails ]; then
+ echo " FAIL"
+ result=1
+ else
+ echo " PASS"
+ result=0
+ fi
+ else
+ printf "%s " "$test"
+ run_test $test $random_seed
+ result=$?
+ echo
+ fi
+
+ case $result in
+ 0) passed=(${passed[@]} $test);;
+ 9) skipped=(${skipped[@]} $test);;
+ *) failed=(${failed[@]} $test);;
+ esac
+done
+
+echo
+echo "SUMMARY:"
+echo " TOTAL $[${#passed[@]} + ${#failed[@]} + ${#skipped[@]}]"
+echo " PASSED ${#passed[@]}"
+echo " FAILED ${#failed[@]} (${failed[@]}) (${failed_seeds[@]})"
+echo " SKIPPED ${#skipped[@]} (${skipped[@]})"
+
+[ ${#failed[@]} -eq 0 ]
diff --git a/test/simulation/test.common b/test/simulation/test.common
new file mode 100644
index 0000000..42a2917
--- /dev/null
+++ b/test/simulation/test.common
@@ -0,0 +1,539 @@
+# Copyright (C) 2013-2014 Miroslav Lichvar <mlichvar@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of 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.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+export LC_ALL=C
+export PATH=../../:$PATH
+export CLKNETSIM_PATH=${CLKNETSIM_PATH:-clknetsim}
+
+if [ ! -x $CLKNETSIM_PATH/clknetsim ]; then
+ echo "SKIP (clknetsim not found)"
+ exit 9
+fi
+
+. $CLKNETSIM_PATH/clknetsim.bash
+
+# Default test testings
+
+default_limit=10000
+default_primary_time_offset=0.0
+default_time_offset=1e-1
+default_freq_offset=1e-4
+default_base_delay=1e-4
+default_delay_correction=""
+default_jitter=1e-4
+default_jitter_asymmetry=0.0
+default_wander=1e-9
+default_refclock_jitter=""
+default_refclock_offset=0.0
+
+default_update_interval=0
+default_shift_pll=2
+
+default_server_strata=1
+default_servers=1
+default_clients=1
+default_peers=0
+default_falsetickers=0
+default_server_start=0.0
+default_client_start=0.0
+default_chronyc_start=1000.0
+default_server_step=""
+default_client_step=""
+
+default_client_server_conf=""
+default_server_server_options=""
+default_client_server_options=""
+default_server_peer_options=""
+default_server_lpeer_options=""
+default_server_rpeer_options=""
+default_client_peer_options=""
+default_client_lpeer_options=""
+default_client_rpeer_options=""
+default_server_conf=""
+default_client_conf=""
+default_chronyc_conf=""
+default_server_chronyd_options=""
+default_client_chronyd_options=""
+default_chronyc_options=""
+
+default_time_max_limit=1e-3
+default_freq_max_limit=5e-4
+default_time_rms_limit=3e-4
+default_freq_rms_limit=1e-5
+default_min_sync_time=120
+default_max_sync_time=210
+
+default_client_min_mean_out_interval=0.0
+default_client_max_min_out_interval=inf
+
+default_cmdmon_unix=1
+default_pcap_dumps=0
+default_dns=0
+
+# Initialize test settings from their defaults
+for defoptname in ${!default_*}; do
+ optname=${defoptname#default_}
+ [ -z "${!optname}" ] && declare "$optname"="${!defoptname}"
+done
+
+test_start() {
+ rm -rf tmp/*
+ echo "Testing $@:"
+
+ check_config_h 'FEAT_NTP 1' || test_skip
+}
+
+test_pass() {
+ echo "PASS"
+ exit 0
+}
+
+test_fail() {
+ echo "FAIL"
+ exit 1
+}
+
+test_skip() {
+ echo "SKIP"
+ exit 9
+}
+
+test_ok() {
+ pad_line
+ echo -e "\tOK"
+ return 0
+}
+
+test_bad() {
+ pad_line
+ echo -e "\tBAD"
+ return 1
+}
+
+test_error() {
+ pad_line
+ echo -e "\tERROR"
+ return 1
+}
+
+msg_length=0
+pad_line() {
+ local line_length=56
+ [ $msg_length -lt $line_length ] && \
+ printf "%$[$line_length - $msg_length]s" ""
+ msg_length=0
+}
+
+# Print aligned message
+test_message() {
+ local level=$1 eol=$2
+ shift 2
+ local msg="$*"
+
+ while [ $level -gt 0 ]; do
+ echo -n " "
+ level=$[$level - 1]
+ msg_length=$[$msg_length + 2]
+ done
+ echo -n "$msg"
+
+ msg_length=$[$msg_length + ${#msg}]
+ if [ $eol -ne 0 ]; then
+ echo
+ msg_length=0
+ fi
+}
+
+get_wander_expr() {
+ local scaled_wander
+
+ scaled_wander=$(awk "BEGIN {print $wander / \
+ sqrt($update_interval < 0 ? 2^-($update_interval) : 1)}")
+
+ echo "(+ $freq_offset (sum (* $scaled_wander (normal))))"
+}
+
+
+get_delay_expr() {
+ local direction=$1 asym
+
+ if [ $jitter_asymmetry == "0.0" ]; then
+ asym=""
+ elif [ $direction = "up" ]; then
+ asym=$(awk "BEGIN {print 1 - 2 * $jitter_asymmetry}")
+ elif [ $direction = "down" ]; then
+ asym=$(awk "BEGIN {print 1 + 2 * $jitter_asymmetry}")
+ fi
+ echo "(+ $base_delay (* $asym $jitter (exponential)))"
+}
+
+get_refclock_expr() {
+ echo "(+ $refclock_offset (* $refclock_jitter (normal)))"
+}
+
+get_chronyd_nodes() {
+ echo $[$servers * $server_strata + $clients]
+}
+
+get_node_name() {
+ local index=$1
+
+ if [ $dns -ne 0 ]; then
+ echo "node$index.net1.clk"
+ else
+ echo "192.168.123.$index"
+ fi
+}
+
+get_chronyd_conf() {
+ local i stratum=$1 peer=$2
+
+ if [ $stratum -eq 1 ]; then
+ echo "local stratum 1"
+ echo "$server_conf"
+ elif [ $stratum -le $server_strata ]; then
+ for i in $(seq 1 $servers); do
+ echo "server $(get_node_name $[$servers * ($stratum - 2) + $i]) $server_server_options"
+ done
+ for i in $(seq 1 $peers); do
+ [ $i -eq $peer -o $i -gt $servers ] && continue
+ echo -n "peer $(get_node_name $[$servers * ($stratum - 1) + $i]) $server_peer_options "
+ [ $i -lt $peer ] && echo "$server_lpeer_options" || echo "$server_rpeer_options"
+ done
+ echo "$server_conf"
+ else
+ echo "deny"
+ if [ -n "$client_server_conf" ]; then
+ echo "$client_server_conf"
+ else
+ for i in $(seq 1 $servers); do
+ echo "server $(get_node_name $[$servers * ($stratum - 2) + $i]) $client_server_options"
+ done
+ fi
+ for i in $(seq 1 $peers); do
+ [ $i -eq $peer -o $i -gt $clients ] && continue
+ echo -n "peer $(get_node_name $[$servers * ($stratum - 1) + $i]) $client_peer_options "
+ [ $i -lt $peer ] && echo "$client_lpeer_options" || echo "$client_rpeer_options"
+ done
+ echo "$client_conf"
+ fi
+}
+
+# Check if chrony was built with specified option in config.h
+check_config_h() {
+ local pattern=$1
+ grep -q "^#define $pattern" ../../config.h
+}
+
+# Check if the clock was well synchronized
+check_sync() {
+ local i sync_time max_time_error max_freq_error ret=0
+ local rms_time_error rms_freq_error
+
+ test_message 2 1 "checking clock sync time, max/rms time/freq error:"
+
+ for i in $(seq 1 $(get_chronyd_nodes)); do
+ [ $i -gt $[$servers * $server_strata] ] || continue
+
+ sync_time=$(find_sync tmp/log.offset tmp/log.freq $i \
+ $time_max_limit $freq_max_limit 1.0)
+ max_time_error=$(get_stat 'Maximum absolute offset' $i)
+ max_freq_error=$(get_stat 'Maximum absolute frequency' $i)
+ rms_time_error=$(get_stat 'RMS offset' $i)
+ rms_freq_error=$(get_stat 'RMS frequency' $i)
+
+ test_message 3 0 "node $i: $sync_time $(printf '%.2e %.2e %.2e %.2e' \
+ $max_time_error $max_freq_error $rms_time_error $rms_freq_error)"
+
+ check_stat $sync_time $min_sync_time $max_sync_time && \
+ check_stat $max_time_error 0.0 $time_max_limit && \
+ check_stat $max_freq_error 0.0 $freq_max_limit && \
+ check_stat $rms_time_error 0.0 $time_rms_limit && \
+ check_stat $rms_freq_error 0.0 $freq_rms_limit && \
+ test_ok || test_bad
+
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Check if chronyd exited properly
+check_chronyd_exit() {
+ local i ret=0
+
+ test_message 2 1 "checking chronyd exit:"
+
+ for i in $(seq 1 $(get_chronyd_nodes)); do
+ test_message 3 0 "node $i:"
+
+ grep -q 'chronyd exiting' tmp/log.$i && \
+ ! grep -q 'Adjustment.*exceeds.*exiting' tmp/log.$i && \
+ ! grep -q 'Assertion.*failed' tmp/log.$i && \
+ test_ok || test_bad
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Check for problems in source selection
+check_source_selection() {
+ local i ret=0
+
+ test_message 2 1 "checking source selection:"
+
+ for i in $(seq $[$servers * $server_strata + 1] $(get_chronyd_nodes)); do
+ test_message 3 0 "node $i:"
+
+ ! grep -q 'no majority\|no selectable sources' tmp/log.$i && \
+ grep -q 'Selected source' tmp/log.$i && \
+ test_ok || test_bad
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Check if incoming and outgoing packet intervals are sane
+check_packet_interval() {
+ local i ret=0 mean_in_interval mean_out_interval min_in_interval min_out_interval
+
+ test_message 2 1 "checking mean/min incoming/outgoing packet interval:"
+
+ for i in $(seq 1 $(get_chronyd_nodes)); do
+ mean_in_interval=$(get_stat 'Mean incoming packet interval' $i)
+ mean_out_interval=$(get_stat 'Mean outgoing packet interval' $i)
+ min_in_interval=$(get_stat 'Minimum incoming packet interval' $i)
+ min_out_interval=$(get_stat 'Minimum outgoing packet interval' $i)
+
+ test_message 3 0 "node $i: $(printf '%.2e %.2e %.2e %.2e' \
+ $mean_in_interval $mean_out_interval $min_in_interval $min_out_interval)"
+
+ # Check that the mean intervals are non-zero and shorter than
+ # limit, incoming is not longer than outgoing for stratum 1
+ # servers, outgoing is not longer than incoming for clients,
+ # and the minimum outgoing interval is not shorter than the NTP
+ # sampling separation or iburst interval for clients
+ nodes=$[$servers * $server_strata + $clients]
+ check_stat $mean_in_interval 0.1 inf && \
+ check_stat $mean_out_interval 0.1 inf && \
+ ([ $i -gt $servers ] || \
+ check_stat $mean_in_interval 0.0 $mean_out_interval 10*$jitter) && \
+ ([ $i -le $[$servers * $server_strata] ] || \
+ check_stat $mean_out_interval $client_min_mean_out_interval \
+ $mean_in_interval 10*$jitter) && \
+ ([ $i -le $[$servers * $server_strata] ] || \
+ check_stat $min_out_interval \
+ $([ $servers -gt 1 ] && echo 0.18 || echo 1.8) \
+ $client_max_min_out_interval) && \
+ test_ok || test_bad
+
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Compare chronyc output with specified pattern
+check_chronyc_output() {
+ local i ret=0 pattern=$1
+
+ test_message 2 1 "checking chronyc output:"
+
+ for i in $(seq $[$(get_chronyd_nodes) + 1] $[$(get_chronyd_nodes) + $clients]); do
+ test_message 3 0 "node $i:"
+
+ [[ "$(cat tmp/log.$i)" =~ $pattern ]] && \
+ test_ok || test_bad
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Check the number of messages matching a pattern in the client logs
+check_log_messages() {
+ local i count ret=0 pattern=$1 min=$2 max=$3
+
+ test_message 2 1 "checking number of messages \"$pattern\":"
+
+ for i in $(seq $[$servers * $server_strata + 1] $(get_chronyd_nodes)); do
+ count=$(grep "$pattern" tmp/log.$i | wc -l)
+ test_message 3 0 "node $i: $count"
+
+ [ "$min" -le "$count" ] && [ "$count" -le "$max" ] && \
+ test_ok || test_bad
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Check the number of messages matching a pattern in a specified file
+check_file_messages() {
+ local i count ret=0 pattern=$1 min=$2 max=$3
+ shift 3
+
+ test_message 2 1 "checking number of messages \"$pattern\":"
+
+ for i; do
+ count=$(grep "$pattern" tmp/$i | wc -l)
+ test_message 3 0 "$i: $count"
+
+ [ "$min" -le "$count" ] && [ "$count" -le "$max" ] && \
+ test_ok || test_bad
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Check if only NTP port (123) was used
+check_packet_port() {
+ local i ret=0 port=123
+
+ test_message 2 1 "checking port numbers in packet log:"
+
+ for i in $(seq 1 $(get_chronyd_nodes)); do
+ test_message 3 0 "node $i:"
+
+ grep -E -q "^([0-9e.+-]+ ){5}$port " tmp/log.packets && \
+ ! grep -E "^[0-9e.+-]+ $i " tmp/log.packets | \
+ grep -E -q -v "^([0-9e.+-]+ ){5}$port " && \
+ test_ok || test_bad
+ [ $? -eq 0 ] || ret=1
+ done
+
+ return $ret
+}
+
+# Print test settings which differ from default value
+print_nondefaults() {
+ local defoptname optname
+
+ test_message 2 1 "non-default settings:"
+ for defoptname in ${!default_*}; do
+ optname=${defoptname#default_}
+ [ "${!defoptname}" = "${!optname}" ] || \
+ test_message 3 1 $optname=${!optname}
+ done
+}
+
+run_simulation() {
+ local nodes=$1
+
+ test_message 2 0 "running simulation:"
+
+ start_server $nodes \
+ -n 2 \
+ -o tmp/log.offset -f tmp/log.freq -p tmp/log.packets \
+ -R $(awk "BEGIN {print $update_interval < 0 ? 2^-($update_interval) : 1}") \
+ -r $(awk "BEGIN {print $max_sync_time * 2^$update_interval}") \
+ -l $(awk "BEGIN {print $limit * 2^$update_interval}") && test_ok || test_error
+}
+
+run_test() {
+ local i j n stratum node nodes step start freq offset conf options
+
+ test_message 1 1 "network with $servers*$server_strata servers and $clients clients:"
+ print_nondefaults
+
+ nodes=$(get_chronyd_nodes)
+ [ -n "$chronyc_conf" ] && nodes=$[$nodes + $clients]
+
+ export CLKNETSIM_UNIX_SUBNET=$[$cmdmon_unix != 0 ? 2 : 0]
+
+ for i in $(seq 1 $nodes); do
+ echo "node${i}_shift_pll = $shift_pll"
+ for j in $(seq 1 $nodes); do
+ echo "node${i}_delay${j} = $(get_delay_expr up)"
+ echo "node${j}_delay${i} = $(get_delay_expr down)"
+ if [ -n "$delay_correction" ]; then
+ echo "node${i}_delay_correction${j} = $delay_correction"
+ echo "node${j}_delay_correction${i} = $delay_correction"
+ fi
+ done
+ done > tmp/conf
+
+ node=1
+
+ for stratum in $(seq 1 $[$server_strata + 1]); do
+ [ $stratum -le $server_strata ] && n=$servers || n=$clients
+
+ for i in $(seq 1 $n); do
+ test_message 2 0 "starting node $node:"
+
+ [ $pcap_dumps -ne 0 ] && export CLKNETSIM_PCAP_DUMP=tmp/pcap.$node
+
+ if [ $stratum -eq 1 ]; then
+ step=$server_step
+ start=$server_start
+ freq=""
+ [ $i -le $falsetickers ] &&
+ offset=$i.0 || offset=$primary_time_offset
+ options=$server_chronyd_options
+ elif [ $stratum -le $server_strata ]; then
+ step=$server_step
+ start=$server_start
+ freq=$(get_wander_expr)
+ offset=0.0
+ options=$server_chronyd_options
+ else
+ step=$client_step
+ start=$client_start
+ freq=$(get_wander_expr)
+ offset=$time_offset
+ options=$client_chronyd_options
+ fi
+
+ conf=$(get_chronyd_conf $stratum $i $n)
+
+ [ -z "$freq" ] || echo "node${node}_freq = $freq" >> tmp/conf
+ [ -z "$step" ] || echo "node${node}_step = $step" >> tmp/conf
+ [ -z "$refclock_jitter" ] || \
+ echo "node${node}_refclock = $(get_refclock_expr)" >> tmp/conf
+ echo "node${node}_offset = $offset" >> tmp/conf
+ echo "node${node}_start = $start" >> tmp/conf
+ start_client $node chronyd "$conf" "" "$options" && \
+ test_ok || test_error
+
+ [ $? -ne 0 ] && return 1
+ node=$[$node + 1]
+ done
+ done
+
+ for i in $(seq 1 $[$nodes - $node + 1]); do
+ test_message 2 0 "starting node $node:"
+
+ [ $pcap_dumps -ne 0 ] && export CLKNETSIM_PCAP_DUMP=tmp/pcap.$node
+
+ options=$([ $dns -eq 0 ] && printf "%s" "-n")
+ if [ $cmdmon_unix -ne 0 ]; then
+ options+=" -h /clknetsim/unix/$[$node - $clients]:1"
+ else
+ options+=" -h $(get_node_name $[$node - $clients])"
+ fi
+
+ echo "node${node}_start = $chronyc_start" >> tmp/conf
+ start_client $node chronyc "$chronyc_conf" "" "$options $chronyc_options" && \
+ test_ok || test_error
+
+ [ $? -ne 0 ] && return 1
+ node=$[$node + 1]
+ done
+
+ run_simulation $nodes
+}
diff --git a/test/system/001-minimal b/test/system/001-minimal
new file mode 100755
index 0000000..107fa3f
--- /dev/null
+++ b/test/system/001-minimal
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "minimal configuration"
+
+minimal_config=1
+
+start_chronyd || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+
+test_pass
diff --git a/test/system/002-extended b/test/system/002-extended
new file mode 100755
index 0000000..7a6734f
--- /dev/null
+++ b/test/system/002-extended
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "extended configuration"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/003-memlock b/test/system/003-memlock
new file mode 100755
index 0000000..e4ab1bf
--- /dev/null
+++ b/test/system/003-memlock
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "memory locking"
+
+extra_chronyd_options="-m"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/004-priority b/test/system/004-priority
new file mode 100755
index 0000000..bf8a04b
--- /dev/null
+++ b/test/system/004-priority
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "process priority"
+
+extra_chronyd_options="-P 1"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/006-privdrop b/test/system/006-privdrop
new file mode 100755
index 0000000..6d7b0c9
--- /dev/null
+++ b/test/system/006-privdrop
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features PRIVDROP || test_skip "PRIVDROP support disabled"
+
+user="nobody"
+
+test_start "dropping of root privileges"
+
+minimal_config=1
+
+start_chronyd || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+
+test_pass
diff --git a/test/system/007-cmdmon b/test/system/007-cmdmon
new file mode 100755
index 0000000..f9541d3
--- /dev/null
+++ b/test/system/007-cmdmon
@@ -0,0 +1,188 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "chronyc commands"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+has_ipv6=$(check_chronyd_features IPV6 && ping6 -c 1 ::1 > /dev/null 2>&1 && echo 1 || echo 0)
+
+for command in \
+ "allow 1.2.3.4" \
+ "deny 1.2.3.4" \
+ "cmddeny" \
+ "cmdallow" \
+ "cmddeny 1.2.3.4" \
+ "cmdallow 1.2.3.4" \
+ "add server 127.123.1.1" \
+ "delete 127.123.1.1" \
+ "burst 1/1" \
+ "cyclelogs" \
+ "dfreq 1.0e-3" \
+ "doffset -0.1" \
+ "dump" \
+ "offline" \
+ "local off" \
+ "local" \
+ "online" \
+ "onoffline" \
+ "maxdelay $server 1e-1" \
+ "maxdelaydevratio $server 5.0" \
+ "maxdelayratio $server 3.0" \
+ "maxpoll $server 12" \
+ "maxupdateskew $server 10.0" \
+ "minpoll $server 10" \
+ "minstratum $server 1" \
+ "polltarget $server 10" \
+ "refresh" \
+ "rekey" \
+ "reload sources" \
+ "reselect" \
+ "reselectdist 1e-3" \
+ "reset sources" \
+ "selectopts $server -noselect +trust +prefer +require" \
+ "smoothtime reset" \
+ "smoothtime activate" \
+; do
+ run_chronyc "$command" || test_fail
+ check_chronyc_output "^200 OK$" || test_fail
+done
+
+run_chronyc "accheck $server" || test_fail
+check_chronyc_output "^208 Access allowed$" || test_fail
+run_chronyc "accheck 1.2.3.4" || test_fail
+check_chronyc_output "^209 Access denied$" || test_fail
+
+run_chronyc "cmdaccheck 1.2.3.4" || test_fail
+check_chronyc_output "^208 Access allowed$" || test_fail
+
+run_chronyc "authdata" || test_fail
+check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+127\.0\.0\.1 - 0 0 0 - 0 0 0 0$" \
+ || test_chronyc
+
+run_chronyc "clients" || test_fail
+check_chronyc_output "^Hostname NTP Drop Int IntL Last Cmd Drop Int Last
+===============================================================================
+127\.0\.0\.1 [0-9 ]+ 0 [-0-9 ]+ - [ 0-9]+ 0 0 - -$" \
+ || test_fail
+
+run_chronyc "ntpdata $server" || test_fail
+check_chronyc_output "^Remote address : 127\.0\.0\.1 \(7F000001\)
+Remote port : [0-9]+
+Local address : 127\.0\.0\.1 \(7F000001\)
+Leap status : Normal
+Version : 4
+Mode : Server
+Stratum : 10
+Poll interval : (-6|[0-9]+) \([0-9]+ seconds\)
+Precision : [0-9 +-]+ \(0\.[0-9]+ seconds\)
+Root delay : 0\.000000 seconds
+Root dispersion : 0\.000000 seconds
+Reference ID : 7F7F0101 \(\)
+Reference time : [A-Za-z0-9: ]+
+Offset : [+-]0\.......... seconds
+Peer delay : 0\.......... seconds
+Peer dispersion : 0\.......... seconds
+Response time : 0\.......... seconds
+Jitter asymmetry: \+0\.00
+NTP tests : 111 111 1110
+Interleaved : No
+Authenticated : No
+TX timestamping : (Daemon|Kernel)
+RX timestamping : (Daemon|Kernel)
+Total TX : [0-9]+
+Total RX : [0-9]+
+Total valid RX : [0-9]+
+Total good RX : [0-9]+$" || test_fail
+
+run_chronyc "selectdata" || test_fail
+check_chronyc_output "^S Name/IP Address Auth COpts EOpts Last Score Interval Leap
+=======================================================================
+s 127\.0\.0\.1 N -PTR- -PTR- 0 1\.0 \+0ns \+0ns \?$" || test_fail
+
+run_chronyc "serverstats" || test_fail
+check_chronyc_output "^NTP packets received : [0-9]+
+NTP packets dropped : 0
+Command packets received : [0-9]+
+Command packets dropped : 0
+Client log records dropped : 0
+NTS-KE connections accepted: 0
+NTS-KE connections dropped : 0
+Authenticated NTP packets : 0
+Interleaved NTP packets : 0
+NTP timestamps held : 0
+NTP timestamp span : 0
+NTP daemon RX timestamps : 0
+NTP daemon TX timestamps : [0-9]+
+NTP kernel RX timestamps : [0-9]+
+NTP kernel TX timestamps : 0
+NTP hardware RX timestamps : 0
+NTP hardware TX timestamps : 0$"|| test_fail
+
+run_chronyc "manual on" || test_fail
+check_chronyc_output "^200 OK$" || test_fail
+
+run_chronyc "settime now" || test_fail
+check_chronyc_output "^200 OK
+Clock was.*$" || test_fail
+
+run_chronyc "manual delete 0" || test_fail
+check_chronyc_output "^200 OK$" || test_fail
+
+run_chronyc "settime now" || test_fail
+check_chronyc_output "^200 OK
+Clock was.*$" || test_fail
+
+run_chronyc "manual list" || test_fail
+check_chronyc_output "^210 n_samples = 1
+# Date Time\(UTC\) Slewed Original Residual
+=======================================================
+ 0.*$" || test_fail
+
+run_chronyc "manual reset" || test_fail
+check_chronyc_output "^200 OK$" || test_fail
+
+run_chronyc "manual off" || test_fail
+check_chronyc_output "^200 OK$" || test_fail
+
+run_chronyc "shutdown" || test_fail
+check_chronyc_output "^200 OK$" || test_fail
+
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+start_chronyd || test_fail
+
+run_chronyc "makestep" && test_fail
+check_chronyc_output "500 Failure" || test_fail
+
+run_chronyc "trimrtc" && test_fail
+check_chronyc_output "513 RTC driver not running" || test_fail
+
+run_chronyc "writertc" && test_fail
+check_chronyc_output "513 RTC driver not running" || test_fail
+
+chronyc_host=127.0.0.1
+
+run_chronyc "tracking" || test_fail
+check_chronyc_output "^Reference ID" || test_fail
+
+run_chronyc "makestep" && test_fail
+check_chronyc_output "^501 Not authorised$" || test_fail
+
+if [ "$has_ipv6" = "1" ]; then
+ chronyc_host=::1
+
+ run_chronyc "tracking" || test_fail
+ check_chronyc_output "^Reference ID" || test_fail
+
+ run_chronyc "makestep" && test_fail
+ check_chronyc_output "^501 Not authorised$" || test_fail
+fi
+
+stop_chronyd || test_fail
+
+test_pass
diff --git a/test/system/008-confload b/test/system/008-confload
new file mode 100755
index 0000000..7e80698
--- /dev/null
+++ b/test/system/008-confload
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "loading of configuration"
+
+minimal_config=1
+extra_chronyd_directives="
+include $TEST_DIR/conf1.d/conf.1
+confdir $TEST_DIR/conf1.d
+confdir $TEST_DIR/conf2.d $TEST_DIR/conf3.d $TEST_DIR/conf4.d
+sourcedir $TEST_DIR/conf5.d
+include $TEST_DIR/conf1.d/conf.2"
+
+mkdir $TEST_DIR/conf{1,2,3,4,5}.d
+
+echo "server 127.123.1.1" > $TEST_DIR/conf1.d/conf.1
+echo "server 127.123.1.2" > $TEST_DIR/conf1.d/conf.2
+echo "server 127.123.1.3" > $TEST_DIR/conf1.d/3.conf
+echo "server 127.123.1.4" > $TEST_DIR/conf1.d/4.conf
+echo "server 127.123.2.2" > $TEST_DIR/conf2.d/2.conf
+echo "server 127.123.2.3" > $TEST_DIR/conf2.d/3.conf
+echo "server 127.123.3.1" > $TEST_DIR/conf3.d/1.conf
+echo "server 127.123.3.2" > $TEST_DIR/conf3.d/2.conf
+echo "server 127.123.3.3" > $TEST_DIR/conf3.d/3.conf
+echo "server 127.123.4.1" > $TEST_DIR/conf4.d/1.conf
+echo "server 127.123.4.2" > $TEST_DIR/conf4.d/2.conf
+echo "server 127.123.4.3" > $TEST_DIR/conf4.d/3.conf
+echo "server 127.123.4.4" > $TEST_DIR/conf4.d/4.conf
+echo "server 127.123.5.1" > $TEST_DIR/conf5.d/1.sources
+echo "server 127.123.5.2" > $TEST_DIR/conf5.d/2.sources
+echo "server 127.123.5.3" > $TEST_DIR/conf5.d/3.sources
+echo "server 127.123.5.4" > $TEST_DIR/conf5.d/4.sources
+echo "server 127.123.5.5" > $TEST_DIR/conf5.d/5.sources
+
+start_chronyd || test_fail
+
+run_chronyc "sources" || test_fail
+check_chronyc_output "^[^=]*
+=*
+.. 127\.123\.1\.1 [^^]*
+.. 127\.123\.1\.3 [^^]*
+.. 127\.123\.1\.4 [^^]*
+.. 127\.123\.3\.1 [^^]*
+.. 127\.123\.2\.2 [^^]*
+.. 127\.123\.2\.3 [^^]*
+.. 127\.123\.4\.4 [^^]*
+.. 127\.123\.1\.2 [^^]*
+.. 127\.123\.5\.1 [^^]*
+.. 127\.123\.5\.2 [^^]*
+.. 127\.123\.5\.3 [^^]*
+.. 127\.123\.5\.4 [^^]*
+.. 127\.123\.5\.5 [^^]*$" || test_fail
+
+rm $TEST_DIR/conf5.d/1.sources
+echo "server 127.123.5.2 minpoll 5" > $TEST_DIR/conf5.d/2.sources
+echo "server 127.123.5.3 minpoll 7" > $TEST_DIR/conf5.d/3.sources
+echo > $TEST_DIR/conf5.d/4.sources
+echo "server 127.123.5.5" >> $TEST_DIR/conf5.d/5.sources
+echo "server 127.123.5.6" > $TEST_DIR/conf5.d/6.sources
+
+run_chronyc "reload sources" || test_fail
+
+run_chronyc "sources" || test_fail
+check_chronyc_output "^[^=]*
+=*
+.. 127\.123\.1\.1 [^^]*
+.. 127\.123\.1\.3 [^^]*
+.. 127\.123\.1\.4 [^^]*
+.. 127\.123\.3\.1 [^^]*
+.. 127\.123\.2\.2 [^^]*
+.. 127\.123\.2\.3 [^^]*
+.. 127\.123\.4\.4 [^^]*
+.. 127\.123\.1\.2 *[05] 6 [^^]*
+.. 127\.123\.5\.5 [^^]*
+.. 127\.123\.5\.2 *[05] 5 [^^]*
+.. 127\.123\.5\.3 *[05] 7 [^^]*
+.. 127\.123\.5\.6 [^^]*$" || test_fail
+
+stop_chronyd || test_fail
+check_chronyd_message_count "Could not add source" 1 1 || test_fail
+
+test_pass
diff --git a/test/system/009-binddevice b/test/system/009-binddevice
new file mode 100755
index 0000000..fc64ae2
--- /dev/null
+++ b/test/system/009-binddevice
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+[ "$(uname -s)" = "Linux" ] || test_skip "non-Linux system"
+
+test_start "binddevice directives"
+
+extra_chronyd_directives="
+binddevice lo
+bindacqdevice lo
+bindcmddevice lo"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+run_chronyc "ntpdata $server" || test_fail
+check_chronyc_output "^Remote address" || test_fail
+
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/010-nts b/test/system/010-nts
new file mode 100755
index 0000000..8d92bbc
--- /dev/null
+++ b/test/system/010-nts
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features NTS || test_skip "NTS support disabled"
+certtool --help &> /dev/null || test_skip "certtool missing"
+
+test_start "NTS authentication"
+
+cat > $TEST_DIR/cert.cfg <<EOF
+cn = "chrony-nts-test"
+dns_name = "chrony-nts-test"
+ip_address = "$server"
+serial = 001
+activation_date = "$[$(date '+%Y') - 1]-01-01 00:00:00 UTC"
+expiration_date = "$[$(date '+%Y') + 2]-01-01 00:00:00 UTC"
+signing_key
+encryption_key
+EOF
+
+certtool --generate-privkey --key-type=ed25519 --outfile $TEST_DIR/server.key \
+ &> $TEST_DIR/certtool.log
+certtool --generate-self-signed --load-privkey $TEST_DIR/server.key \
+ --template $TEST_DIR/cert.cfg --outfile $TEST_DIR/server.crt &>> $TEST_DIR/certtool.log
+chown $user $TEST_DIR/server.*
+
+ntpport=$(get_free_port)
+ntsport=$(get_free_port)
+
+server_options="port $ntpport nts ntsport $ntsport"
+extra_chronyd_directives="
+port $ntpport
+ntsport $ntsport
+ntsserverkey $TEST_DIR/server.key
+ntsservercert $TEST_DIR/server.crt
+ntstrustedcerts $TEST_DIR/server.crt
+ntsdumpdir $TEST_LIBDIR
+ntsprocesses 3"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+run_chronyc "authdata" || test_fail
+check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
+
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+server_options="port $ntpport nts ntsport $((ntsport + 1))"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+run_chronyc "authdata" || test_fail
+check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================
+127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
+
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/011-systemd b/test/system/011-systemd
new file mode 100755
index 0000000..1049966
--- /dev/null
+++ b/test/system/011-systemd
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features NTS || test_skip "NTS support disabled"
+certtool --help &> /dev/null || test_skip "certtool missing"
+check_chronyd_features DEBUG || test_skip "DEBUG support disabled"
+systemd-socket-activate -h &> /dev/null || test_skip "systemd-socket-activate missing"
+has_ipv6=$(check_chronyd_features IPV6 && ping6 -c 1 ::1 > /dev/null 2>&1 && echo 1 || echo 0)
+
+test_start "systemd socket activation"
+
+cat > $TEST_DIR/cert.cfg <<EOF
+cn = "chrony-nts-test"
+dns_name = "chrony-nts-test"
+ip_address = "$server"
+$([ "$has_ipv6" = "1" ] && echo 'ip_address = "::1"')
+serial = 001
+activation_date = "$[$(date '+%Y') - 1]-01-01 00:00:00 UTC"
+expiration_date = "$[$(date '+%Y') + 2]-01-01 00:00:00 UTC"
+signing_key
+encryption_key
+EOF
+
+certtool --generate-privkey --key-type=ed25519 --outfile $TEST_DIR/server.key \
+ &> $TEST_DIR/certtool.log
+certtool --generate-self-signed --load-privkey $TEST_DIR/server.key \
+ --template $TEST_DIR/cert.cfg --outfile $TEST_DIR/server.crt &>> $TEST_DIR/certtool.log
+chown $user $TEST_DIR/server.*
+
+ntpport=$(get_free_port)
+ntsport=$(get_free_port)
+
+server_options="port $ntpport nts ntsport $ntsport"
+extra_chronyd_directives="
+port $ntpport
+ntsport $ntsport
+ntsserverkey $TEST_DIR/server.key
+ntsservercert $TEST_DIR/server.crt
+ntstrustedcerts $TEST_DIR/server.crt
+ntsdumpdir $TEST_LIBDIR
+ntsprocesses 3"
+
+if [ "$has_ipv6" = "1" ]; then
+ extra_chronyd_directives="$extra_chronyd_directives
+ bindaddress ::1
+ server ::1 minpoll -6 maxpoll -6 $server_options"
+fi
+
+# enable debug logging
+extra_chronyd_options="-L -1"
+# Hack to trigger systemd-socket-activate to activate the service. Normally,
+# chronyd.service would be configured with the WantedBy= directive so it starts
+# without waiting for socket activation.
+# (https://0pointer.de/blog/projects/socket-activation.html).
+for i in $(seq 10); do
+ sleep 1
+ (echo "wake up" > /dev/udp/127.0.0.1/$ntpport) 2>/dev/null
+ (echo "wake up" > /dev/tcp/127.0.0.1/$ntsport) 2>/dev/null
+done &
+
+# Test with UDP sockets (unfortunately systemd-socket-activate doesn't support
+# both datagram and stream sockets in the same invocation:
+# https://github.com/systemd/systemd/issues/9983).
+CHRONYD_WRAPPER="systemd-socket-activate \
+ --datagram \
+ --listen 127.0.0.1:$ntpport \
+ --listen 127.0.0.1:$ntsport"
+if [ "$has_ipv6" = "1" ]; then
+ CHRONYD_WRAPPER="$CHRONYD_WRAPPER \
+ --listen [::1]:$ntpport \
+ --listen [::1]:$ntsport"
+fi
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+if [ "$has_ipv6" = "1" ]; then
+ run_chronyc "ntpdata ::1" || test_fail
+ check_chronyc_output "Total RX +: [1-9]" || test_fail
+fi
+run_chronyc "authdata" || test_fail
+check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================\
+$([ "$has_ipv6" = "1" ] && printf "\n%s\n" '::1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)')
+127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
+
+stop_chronyd || test_fail
+# DGRAM ntpport socket should be used
+check_chronyd_message_count "Reusing UDPv4 socket fd=3 local=127.0.0.1:$ntpport" 1 1 || test_fail
+# DGRAM ntsport socket should be ignored
+check_chronyd_message_count "Reusing TCPv4 socket fd=4 local=127.0.0.1:$ntsport" 0 0 || test_fail
+if [ "$has_ipv6" = "1" ]; then
+ # DGRAM ntpport socket should be used
+ check_chronyd_message_count "Reusing UDPv6 socket fd=5 local=\[::1\]:$ntpport" 1 1 || test_fail
+ # DGRAM ntsport socket should be ignored
+ check_chronyd_message_count "Reusing TCPv6 socket fd=6 local=\[::1\]:$ntsport" 0 0 || test_fail
+fi
+
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+# Test with TCP sockets
+CHRONYD_WRAPPER="systemd-socket-activate \
+ --listen 127.0.0.1:$ntpport \
+ --listen 127.0.0.1:$ntsport"
+if [ "$has_ipv6" = "1" ]; then
+ CHRONYD_WRAPPER="$CHRONYD_WRAPPER \
+ --listen [::1]:$ntpport \
+ --listen [::1]:$ntsport"
+fi
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+
+if [ "$has_ipv6" = "1" ]; then
+ run_chronyc "ntpdata ::1" || test_fail
+ check_chronyc_output "Total RX +: [1-9]" || test_fail
+fi
+run_chronyc "authdata" || test_fail
+check_chronyc_output "^Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
+=========================================================================\
+$([ "$has_ipv6" = "1" ] && printf "\n%s\n" '::1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)')
+127\.0\.0\.1 NTS 1 (30|15) (128|256) [0-9] 0 0 [78] ( 64|100)$" || test_fail
+
+stop_chronyd || test_fail
+# STREAM ntpport should be ignored
+check_chronyd_message_count "Reusing TCPv4 socket fd=3 local=127.0.0.1:$ntpport" 0 0 || test_fail
+# STREAM ntsport should be used
+check_chronyd_message_count "Reusing TCPv4 socket fd=4 local=127.0.0.1:$ntsport" 1 1 || test_fail
+if [ "$has_ipv6" = "1" ]; then
+ # STREAM ntpport should be ignored
+ check_chronyd_message_count "Reusing TCPv6 socket fd=5 local=\[::1\]:$ntpport" 0 0 || test_fail
+ # STREAM ntsport should be used
+ check_chronyd_message_count "Reusing TCPv6 socket fd=6 local=\[::1\]:$ntsport" 1 1 || test_fail
+fi
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/099-scfilter b/test/system/099-scfilter
new file mode 100755
index 0000000..7be02bd
--- /dev/null
+++ b/test/system/099-scfilter
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled"
+
+test_start "system call filter in non-destructive tests"
+
+for level in 1 2 -1 -2; do
+ test_message 1 1 "level $level:"
+ for test in 0[0-8][0-9]-*[^_]; do
+ test_message 2 0 "$test"
+ TEST_SCFILTER=$level "./$test" > /dev/null 2> /dev/null
+ result=$?
+
+ if [ $result != 0 ] && [ $result != 9 ] ; then
+ test_bad
+ test_fail
+ fi
+ test_ok
+ done
+done
+
+test_pass
diff --git a/test/system/100-clockupdate b/test/system/100-clockupdate
new file mode 100755
index 0000000..191e461
--- /dev/null
+++ b/test/system/100-clockupdate
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+test_start "update of system clock"
+
+clock_control=1
+minimal_config=1
+
+start_chronyd || test_fail
+run_chronyc "dfreq 1e-3" || test_fail
+check_chronyc_output "200 OK" || test_fail
+
+before=$(date '+%s')
+run_chronyc "doffset -1.0" || test_fail
+check_chronyc_output "200 OK" || test_fail
+run_chronyc "makestep" || test_fail
+check_chronyc_output "200 OK" || test_fail
+after=$(date '+%s')
+
+test_message 1 0 "checking system clock"
+[ "$before" -lt "$after" ] && test_ok || test_bad || test_fail
+
+run_chronyc "doffset 1.0" || test_fail
+run_chronyc "makestep" || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_message_count "System clock was stepped by" 2 2 || test_fail
+
+test_pass
diff --git a/test/system/101-rtc b/test/system/101-rtc
new file mode 100755
index 0000000..68bce68
--- /dev/null
+++ b/test/system/101-rtc
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features RTC || test_skip "RTC support disabled"
+[ -c "/dev/rtc" ] || test_skip "missing /dev/rtc"
+
+test_start "real-time clock"
+
+minimal_config=1
+extra_chronyd_options="-s"
+extra_chronyd_directives="rtcfile $TEST_DIR/rtcfile"
+echo "1 $(date +%s) 0.0 0.0" > "$TEST_DIR/rtcfile"
+
+start_chronyd || test_fail
+stop_chronyd || test_fail
+check_chronyd_message_count "\(clock off from RTC\|RTC time before last\|Could not \(enable\|disable\) RTC interrupt\)" 1 1 || test_fail
+
+test_pass
diff --git a/test/system/102-hwtimestamp b/test/system/102-hwtimestamp
new file mode 100755
index 0000000..6b86d20
--- /dev/null
+++ b/test/system/102-hwtimestamp
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+[ "$(uname -s)" = "Linux" ] || test_skip "non-Linux system"
+
+hwts_iface=""
+for iface_path in /sys/class/net/*; do
+ iface=$(basename "$iface_path")
+ if ethtool -T "$iface" 2> /dev/null | grep -q ' all\($\| \)'; then
+ hwts_iface="$iface"
+ break
+ fi
+done
+
+[ -n "$hwts_iface" ] || test_skip "no HW timestamping interface found"
+
+test_start "hardware timestamping"
+
+minimal_config=1
+extra_chronyd_directives="hwtimestamp $hwts_iface"
+
+start_chronyd || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_message_count "Enabled HW timestamping on $hwts_iface" 1 1 || test_fail
+
+test_pass
diff --git a/test/system/103-refclock b/test/system/103-refclock
new file mode 100755
index 0000000..e5b74e0
--- /dev/null
+++ b/test/system/103-refclock
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features REFCLOCK || test_skip "refclock support disabled"
+
+test_start "reference clocks"
+
+extra_chronyd_directives="
+refclock SOCK $TEST_DIR/refclock.sock
+refclock SHM 100"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/104-systemdirs b/test/system/104-systemdirs
new file mode 100755
index 0000000..15508dc
--- /dev/null
+++ b/test/system/104-systemdirs
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+TEST_LIBDIR=${CHRONY_LIBDIR:-/var/lib/chrony}
+TEST_LOGDIR=${CHRONY_LOGDIR:-/var/log/chrony}
+TEST_RUNDIR=${CHRONY_RUNDIR:-/var/run/chrony}
+
+. ./test.common
+
+user=$(ls -ld "$TEST_RUNDIR" 2> /dev/null | awk '{print $3}')
+
+test_start "system directories"
+
+start_chronyd || test_fail
+wait_for_sync || test_fail
+stop_chronyd || test_fail
+check_chronyd_messages || test_fail
+check_chronyd_files || test_fail
+
+test_pass
diff --git a/test/system/199-scfilter b/test/system/199-scfilter
new file mode 100755
index 0000000..40441b7
--- /dev/null
+++ b/test/system/199-scfilter
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+. ./test.common
+
+check_chronyd_features SCFILTER || test_skip "SCFILTER support disabled"
+
+test_start "system call filter in destructive tests"
+
+for level in 1 2 -1 -2; do
+ test_message 1 1 "level $level:"
+ for test in 1[0-8][0-9]-*[^_]; do
+ test_message 2 0 "$test"
+ TEST_SCFILTER=$level "./$test" > /dev/null 2> /dev/null
+ result=$?
+
+ if [ $result != 0 ] && [ $result != 9 ] ; then
+ test_bad
+ test_fail
+ fi
+ test_ok
+ done
+done
+
+test_pass
diff --git a/test/system/run b/test/system/run
new file mode 100755
index 0000000..5516ed4
--- /dev/null
+++ b/test/system/run
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+
+print_help() {
+ echo "$1 [-a] [-d] [TEST]..."
+}
+
+run_test() {
+ local result name=$1
+
+ if [ $destructive -ne 1 ] && [[ "$name" == 1[0-9][0-9]-* ]]; then
+ echo "SKIP (destructive test)"
+ return 9
+ fi
+
+ ./$name
+ result=$?
+
+ if [ $result -ne 0 -a $result -ne 9 ]; then
+ if [ $abort_on_fail -ne 0 ]; then
+ exit 1
+ fi
+ fi
+
+ return $result
+}
+
+passed=() failed=() skipped=()
+
+abort_on_fail=0
+destructive=0
+
+while getopts ":ad" opt; do
+ case $opt in
+ a) abort_on_fail=1;;
+ d) destructive=1;;
+ *) print_help "$0"; exit 3;;
+ esac
+done
+
+shift $[$OPTIND - 1]
+
+[ $# -gt 0 ] && tests=($@) || tests=([0-9]*-*[^_])
+
+for test in "${tests[@]}"; do
+ printf "%s " "$test"
+ run_test $test
+ result=$?
+ echo
+
+ case $result in
+ 0) passed=(${passed[@]} $test);;
+ 9) skipped=(${skipped[@]} $test);;
+ *) failed=(${failed[@]} $test);;
+ esac
+done
+
+echo
+echo "SUMMARY:"
+echo " TOTAL $[${#passed[@]} + ${#failed[@]} + ${#skipped[@]}]"
+echo " PASSED ${#passed[@]}"
+echo " FAILED ${#failed[@]} (${failed[@]})"
+echo " SKIPPED ${#skipped[@]} (${skipped[@]})"
+
+[ ${#failed[@]} -eq 0 ]
diff --git a/test/system/test.common b/test/system/test.common
new file mode 100644
index 0000000..c389b48
--- /dev/null
+++ b/test/system/test.common
@@ -0,0 +1,375 @@
+# Copyright (C) Miroslav Lichvar 2009
+#
+# 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.
+
+export LC_ALL=C
+export PATH=${CHRONY_PATH:-../..}:$PATH
+
+TEST_DIR=${TEST_DIR:-$(pwd)/tmp}
+TEST_LIBDIR=${TEST_LIBDIR:-$TEST_DIR}
+TEST_LOGDIR=${TEST_LOGDIR:-$TEST_DIR}
+TEST_RUNDIR=${TEST_RUNDIR:-$TEST_DIR}
+TEST_SCFILTER=${TEST_SCFILTER:-0}
+
+test_start() {
+ check_chronyd_features NTP CMDMON || test_skip "NTP/CMDMON support disabled"
+
+ [ "${#TEST_DIR}" -ge 5 ] || test_skip "invalid TEST_DIR"
+
+ rm -rf "$TEST_DIR"
+ mkdir -p "$TEST_DIR" && chmod 700 "$TEST_DIR" || test_skip "could not create $TEST_DIR"
+
+ [ -d "$TEST_LIBDIR" ] || test_skip "missing $TEST_LIBDIR"
+ [ -d "$TEST_LOGDIR" ] || test_skip "missing $TEST_LOGDIR"
+ [ -d "$TEST_RUNDIR" ] || test_skip "missing $TEST_RUNDIR"
+
+ rm -f "$TEST_LIBDIR"/* "$TEST_LOGDIR"/* "$TEST_RUNDIR"/*
+
+ if [ "$user" != "root" ]; then
+ id -u "$user" > /dev/null 2> /dev/null || test_skip "missing user $user"
+ chown "$user:$(id -g "$user")" "$TEST_DIR" || test_skip "could not chown $TEST_DIR"
+ su "$user" -s /bin/sh -c "touch $TEST_DIR/test" 2> /dev/null || \
+ test_skip "$user cannot access $TEST_DIR"
+ rm "$TEST_DIR/test"
+ else
+ chown 0:0 "$TEST_DIR" || test_skip "could not chown $TEST_DIR"
+ fi
+
+ echo "Testing $*:"
+}
+
+test_pass() {
+ echo "PASS"
+ exit 0
+}
+
+test_fail() {
+ echo "FAIL"
+ exit 1
+}
+
+test_skip() {
+ local msg=$1
+
+ [ -n "$msg" ] && echo "SKIP ($msg)" || echo "SKIP"
+ exit 9
+}
+
+test_ok() {
+ pad_line
+ echo -e "\tOK"
+ return 0
+}
+
+test_bad() {
+ pad_line
+ echo -e "\tBAD"
+ return 1
+}
+
+test_error() {
+ pad_line
+ echo -e "\tERROR"
+ return 1
+}
+
+chronyd=$(command -v chronyd)
+chronyc=$(command -v chronyc)
+
+[ $EUID -eq 0 ] || test_skip "not root"
+
+[ -x "$chronyd" ] || test_skip "chronyd not found"
+[ -x "$chronyc" ] || test_skip "chronyc not found"
+
+if netstat -aln > /dev/null 2> /dev/null; then
+ port_list_command="netstat -aln"
+elif ss -atun > /dev/null 2> /dev/null; then
+ port_list_command="ss -atun"
+else
+ test_skip "missing netstat or ss"
+fi
+
+# Default test testings
+default_minimal_config=0
+default_extra_chronyd_directives=""
+default_extra_chronyd_options=""
+default_clock_control=0
+default_server=127.0.0.1
+default_server_name=127.0.0.1
+default_server_options=""
+default_user=root
+
+# Initialize test settings from their defaults
+for defoptname in ${!default_*}; do
+ optname=${defoptname#default_}
+ [ -z "${!optname}" ] && declare "$optname"="${!defoptname}"
+done
+
+msg_length=0
+pad_line() {
+ local line_length=56
+ [ $msg_length -lt $line_length ] && \
+ printf "%$((line_length - msg_length))s" ""
+ msg_length=0
+}
+
+# Print aligned message
+test_message() {
+ local level=$1 eol=$2
+ shift 2
+ local msg="$*"
+
+ while [ "$level" -gt 0 ]; do
+ echo -n " "
+ level=$((level - 1))
+ msg_length=$((msg_length + 2))
+ done
+ echo -n "$msg"
+
+ msg_length=$((msg_length + ${#msg}))
+ if [ "$eol" -ne 0 ]; then
+ echo
+ msg_length=0
+ fi
+}
+
+# Check if chronyd has specified features
+check_chronyd_features() {
+ local feature features
+
+ features=$($chronyd -v | sed 's/.*(\(.*\)).*/\1/')
+
+ for feature; do
+ echo "$features" | grep -q "+$feature" || return 1
+ done
+}
+
+# Print test settings which differ from default value
+print_nondefaults() {
+ local defoptname optname
+
+ test_message 1 1 "non-default settings:"
+ for defoptname in ${!default_*}; do
+ optname=${defoptname#default_}
+ [ "${!defoptname}" = "${!optname}" ] || \
+ test_message 2 1 "$optname"=${!optname}
+ done
+}
+
+get_conffile() {
+ echo "$TEST_DIR/chronyd.conf"
+}
+
+get_pidfile() {
+ echo "$TEST_RUNDIR/chronyd.pid"
+}
+
+get_logfile() {
+ echo "$TEST_LOGDIR/chronyd.log"
+}
+
+get_cmdsocket() {
+ echo "$TEST_RUNDIR/chronyd.sock"
+}
+
+# Find a free port in the 10000-20000 range (their use is racy)
+get_free_port() {
+ local port
+
+ while true; do
+ port=$((RANDOM % 10000 + 10000))
+ $port_list_command | grep -q '^\(tcp\|udp\).*[:.]'"$port " && continue
+ break
+ done
+
+ echo $port
+}
+
+generate_chrony_conf() {
+ local ntpport cmdport
+
+ ntpport=$(get_free_port)
+ cmdport=$(get_free_port)
+
+ echo "0.0 10000" > "$TEST_LIBDIR/driftfile"
+ echo "1 MD5 abcdefghijklmnopq" > "$TEST_DIR/keys"
+ chown "$user:$(id -g "$user")" "$TEST_LIBDIR/driftfile" "$TEST_DIR/keys"
+ echo "0.0" > "$TEST_DIR/tempcomp"
+
+ (
+ echo "pidfile $(get_pidfile)"
+ echo "bindcmdaddress $(get_cmdsocket)"
+ echo "port $ntpport"
+ echo "cmdport $cmdport"
+
+ echo "$extra_chronyd_directives"
+
+ [ "$minimal_config" -ne 0 ] && exit 0
+
+ echo "allow"
+ echo "cmdallow"
+ echo "local"
+
+ echo "server $server_name port $ntpport minpoll -6 maxpoll -6 $server_options"
+
+ [ "$server" = "127.0.0.1" ] && echo "bindacqaddress $server"
+ echo "bindaddress 127.0.0.1"
+ echo "bindcmdaddress 127.0.0.1"
+ echo "dumpdir $TEST_RUNDIR"
+ echo "logdir $TEST_LOGDIR"
+ echo "log tempcomp rawmeasurements refclocks statistics tracking rtc"
+ echo "logbanner 0"
+ echo "smoothtime 100.0 0.001"
+ echo "leapsectz right/UTC"
+ echo "dscp 46"
+
+ echo "include /dev/null"
+ echo "keyfile $TEST_DIR/keys"
+ echo "driftfile $TEST_LIBDIR/driftfile"
+ echo "tempcomp $TEST_DIR/tempcomp 0.1 0 0 0 0"
+
+ ) > "$(get_conffile)"
+}
+
+get_chronyd_options() {
+ [ "$clock_control" -eq 0 ] && echo "-x"
+ echo "-l $(get_logfile)"
+ echo "-f $(get_conffile)"
+ echo "-u $user"
+ echo "-F $TEST_SCFILTER"
+ echo "$extra_chronyd_options"
+}
+
+# Start a chronyd instance
+start_chronyd() {
+ local pid pidfile=$(get_pidfile)
+
+ print_nondefaults
+ test_message 1 0 "starting chronyd"
+
+ generate_chrony_conf
+
+ trap stop_chronyd EXIT
+
+ rm -f "$TEST_LOGDIR"/*.log
+
+ $CHRONYD_WRAPPER "$chronyd" $(get_chronyd_options) > "$TEST_DIR/chronyd.out" 2>&1
+
+ [ $? -eq 0 ] && [ -f "$pidfile" ] && ps -p "$(cat "$pidfile")" > /dev/null && test_ok || test_error
+}
+
+wait_for_sync() {
+ local prev_length
+
+ test_message 1 0 "waiting for synchronization"
+ prev_length=$msg_length
+
+ for i in $(seq 1 10); do
+ run_chronyc "ntpdata $server" > /dev/null 2>&1 || break
+ if check_chronyc_output "Total RX +: [1-9]" > /dev/null 2>&1; then
+ msg_length=$prev_length
+ test_ok
+ return
+ fi
+ sleep 1
+ done
+
+ msg_length=$prev_length
+ test_error
+}
+
+# Stop the chronyd instance
+stop_chronyd() {
+ local pid pidfile
+
+ pidfile=$(get_pidfile)
+ [ -f "$pidfile" ] || return 0
+
+ pid=$(cat "$pidfile")
+
+ test_message 1 0 "stopping chronyd"
+
+ if ! kill "$pid" 2> /dev/null; then
+ test_error
+ return
+ fi
+
+ # Wait for the process to terminate (we cannot use "wait")
+ while ps -p "$pid" > /dev/null; do
+ sleep 0.1
+ done
+
+ test_ok
+}
+
+# Check chronyd log for expected and unexpected messages
+check_chronyd_messages() {
+ local logfile=$(get_logfile)
+
+ test_message 1 0 "checking chronyd messages"
+
+ grep -q 'chronyd exiting' "$logfile" && \
+ ([ "$clock_control" -eq 0 ] || ! grep -q 'Disabled control of system clock' "$logfile") && \
+ ([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \
+ ([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \
+ grep -q 'chronyd exiting' "$logfile" && \
+ ! (grep -v '^.\{19\}Z D:' "$logfile" | grep -q 'Could not') && \
+ ! grep -q 'Disabled command socket' "$logfile" && \
+ test_ok || test_bad
+}
+
+# Check the number of messages matching a pattern in a specified file
+check_chronyd_message_count() {
+ local count pattern=$1 min=$2 max=$3 logfile=$(get_logfile)
+
+ test_message 1 0 "checking message \"$pattern\""
+
+ count=$(grep "$pattern" "$(get_logfile)" | wc -l)
+
+ [ "$min" -le "$count" ] && [ "$count" -le "$max" ] && test_ok || test_bad
+}
+
+# Check the logs and dump file for measurements and a clock update
+check_chronyd_files() {
+ test_message 1 0 "checking chronyd files"
+
+ grep -q " $server .* 111 111 1110 " "$TEST_LOGDIR/measurements.log" && \
+ [ -f "$TEST_LOGDIR/tempcomp.log" ] && [ "$(wc -l < "$TEST_LOGDIR/tempcomp.log")" -ge 2 ] && \
+ test_ok || test_bad
+}
+
+# Run a chronyc command
+run_chronyc() {
+ local host=$chronyc_host options="-n -m"
+
+ test_message 1 0 "running chronyc $([ -n "$host" ] && echo "@$host ")$*"
+
+ if [ -z "$host" ]; then
+ host="$(get_cmdsocket)"
+ else
+ options="$options -p $(grep cmdport "$(get_conffile)" | awk '{print $2}')"
+ fi
+
+ $CHRONYC_WRAPPER "$chronyc" -h "$host" $options "$@" > "$TEST_DIR/chronyc.out" && \
+ test_ok || test_error
+}
+
+# Compare chronyc output with specified pattern
+check_chronyc_output() {
+ local pattern=$1
+
+ test_message 1 0 "checking chronyc output"
+
+ [[ "$(cat "$TEST_DIR/chronyc.out")" =~ $pattern ]] && test_ok || test_bad
+}
diff --git a/test/unit/Makefile.in b/test/unit/Makefile.in
new file mode 100644
index 0000000..9979840
--- /dev/null
+++ b/test/unit/Makefile.in
@@ -0,0 +1,48 @@
+TEST_WRAPPER =
+CHRONY_SRCDIR = ../..
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = -I$(CHRONY_SRCDIR) @CPPFLAGS@
+LDFLAGS = @LDFLAGS@ @LIBS@ @EXTRA_LIBS@
+
+SHARED_OBJS = test.o
+
+TEST_OBJS := $(sort $(patsubst %.c,%.o,$(wildcard *.c)))
+TESTS := $(patsubst %.o,%.test,$(filter-out $(SHARED_OBJS),$(TEST_OBJS)))
+
+CHRONYD_OBJS := $(patsubst %.o,$(CHRONY_SRCDIR)/%.o,$(filter-out main.o,\
+ $(filter %.o,$(shell $(MAKE) -f $(CHRONY_SRCDIR)/Makefile \
+ print-chronyd-objects NODEPS=1))))
+
+all: $(TESTS)
+
+$(CHRONYD_OBJS): ;
+
+%.test: %.o $(SHARED_OBJS) $(CHRONYD_OBJS)
+ $(CC) $(CFLAGS) -o $@ $(filter-out $(CHRONY_SRCDIR)/$<,$^) $(LDFLAGS)
+
+%.o: %.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c $<
+
+check: $(TESTS)
+ @ret=0; \
+ for t in $^; do \
+ $(TEST_WRAPPER) ./$$t || ret=1; \
+ done; \
+ exit $$ret
+
+clean:
+ rm -f *.o *.gcda *.gcno core.* $(TESTS)
+ rm -rf .deps
+
+distclean: clean
+ rm -f Makefile
+
+.deps:
+ @mkdir .deps
+
+.deps/%.d: %.c | .deps
+ @$(CC) -MM $(CPPFLAGS) -MT '$(<:%.c=%.o) $@' $< -o $@
+
+-include $(TEST_OBJS:%.o=.deps/%.d)
diff --git a/test/unit/addrfilt.c b/test/unit/addrfilt.c
new file mode 100644
index 0000000..b236073
--- /dev/null
+++ b/test/unit/addrfilt.c
@@ -0,0 +1,83 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <addrfilt.c>
+#include <logging.h>
+#include <util.h>
+#include "test.h"
+
+void
+test_unit(void)
+{
+ int i, j, sub, maxsub;
+ IPAddr ip;
+ ADF_AuthTable table;
+
+ table = ADF_CreateTable();
+
+ for (i = 0; i < 100; i++) {
+ for (j = 0; j < 1000; j++) {
+ if (j % 2) {
+ maxsub = 32;
+ TST_GetRandomAddress(&ip, IPADDR_INET4, -1);
+ } else {
+ maxsub = 128;
+ TST_GetRandomAddress(&ip, IPADDR_INET6, -1);
+ }
+
+ DEBUG_LOG("address %s", UTI_IPToString(&ip));
+
+ sub = random() % (maxsub + 1);
+
+ TEST_CHECK(!ADF_IsAllowed(table, &ip));
+ ADF_Allow(table, &ip, sub);
+ TEST_CHECK(ADF_IsAllowed(table, &ip));
+
+ if (sub < maxsub) {
+ TST_SwapAddressBit(&ip, sub);
+ TEST_CHECK(ADF_IsAllowed(table, &ip));
+ }
+
+ if (sub > 0) {
+ TST_SwapAddressBit(&ip, sub - 1);
+ TEST_CHECK(!ADF_IsAllowed(table, &ip));
+ if (sub % 4 != 1) {
+ ADF_Deny(table, &ip, sub - 1);
+ TST_SwapAddressBit(&ip, sub - 1);
+ TEST_CHECK(!ADF_IsAllowed(table, &ip));
+ }
+ }
+
+ if (sub > 4) {
+ ADF_AllowAll(table, &ip, sub - 4);
+ TEST_CHECK(ADF_IsAllowed(table, &ip));
+ }
+
+ ADF_DenyAll(table, &ip, 0);
+ }
+
+ ip.family = IPADDR_INET4;
+ ADF_DenyAll(table, &ip, 0);
+ ip.family = IPADDR_INET6;
+ ADF_DenyAll(table, &ip, 0);
+ }
+
+ ADF_DestroyTable(table);
+}
diff --git a/test/unit/array.c b/test/unit/array.c
new file mode 100644
index 0000000..65ad1d8
--- /dev/null
+++ b/test/unit/array.c
@@ -0,0 +1,97 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <array.c>
+#include <util.h>
+#include "test.h"
+
+void
+test_unit(void)
+{
+ unsigned int i, j, k, k2, l, es, s;
+ unsigned char *el1, el2[20];
+ ARR_Instance a;
+
+ for (i = 0; i < 1000; i++) {
+ es = random() % sizeof (el2) + 1;
+
+ a = ARR_CreateInstance(es);
+
+ TEST_CHECK(ARR_GetSize(a) == 0);
+
+ for (j = 0; j < 100; j++) {
+ s = ARR_GetSize(a);
+
+ switch (random() % 6) {
+ case 0:
+ el1 = ARR_GetNewElement(a);
+ TEST_CHECK(ARR_GetSize(a) == s + 1);
+ memset(el1, s % 256, es);
+ TEST_CHECK(ARR_GetElement(a, s) == el1);
+ break;
+ case 1:
+ for (k = 0; k < s; k++) {
+ el1 = ARR_GetElement(a, k);
+ for (l = 0; l < es; l++)
+ TEST_CHECK(el1[l] == k % 256);
+ }
+ break;
+ case 2:
+ if (s == 0)
+ break;
+ TEST_CHECK(ARR_GetElements(a) == ARR_GetElement(a, 0));
+ break;
+ case 3:
+ memset(el2, s % 256, es);
+ ARR_AppendElement(a, el2);
+ TEST_CHECK(ARR_GetSize(a) == s + 1);
+ break;
+ case 4:
+ if (s == 0)
+ break;
+ k2 = random() % s;
+ ARR_RemoveElement(a, k2);
+ TEST_CHECK(ARR_GetSize(a) == s - 1);
+ for (k = k2; k < s - 1; k++) {
+ el1 = ARR_GetElement(a, k);
+ for (l = 0; l < es; l++) {
+ TEST_CHECK(el1[l] == (k + 1) % 256);
+ el1[l] = k % 256;
+ }
+ }
+ break;
+ case 5:
+ k2 = random() % 1000;
+ ARR_SetSize(a, k2);
+ TEST_CHECK(ARR_GetSize(a) == k2);
+ for (k = s; k < k2; k++) {
+ el1 = ARR_GetElement(a, k);
+ for (l = 0; l < es; l++)
+ el1[l] = k % 256;
+ }
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ ARR_DestroyInstance(a);
+ }
+}
diff --git a/test/unit/clientlog.c b/test/unit/clientlog.c
new file mode 100644
index 0000000..e5bf1f4
--- /dev/null
+++ b/test/unit/clientlog.c
@@ -0,0 +1,298 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016, 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#if defined(FEAT_NTP) || defined(FEAT_CMDMON)
+
+#include <clientlog.c>
+
+static uint64_t
+get_random64(void)
+{
+ return ((uint64_t)random() << 40) ^ ((uint64_t)random() << 20) ^ random();
+}
+
+void
+test_unit(void)
+{
+ uint64_t ts64, prev_first_ts64, prev_last_ts64, max_step;
+ uint32_t index2, prev_first, prev_size;
+ NTP_Timestamp_Source ts_src, ts_src2;
+ struct timespec ts, ts2;
+ int i, j, k, index, shift;
+ CLG_Service s;
+ NTP_int64 ntp_ts;
+ IPAddr ip;
+ char conf[][100] = {
+ "clientloglimit 20000",
+ "ratelimit interval 3 burst 4 leak 3",
+ "cmdratelimit interval 3 burst 4 leak 3",
+ "ntsratelimit interval 6 burst 8 leak 3",
+ };
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ LCL_Initialise();
+ CLG_Initialise();
+
+ TEST_CHECK(ARR_GetSize(records) == 16);
+
+ for (i = 0; i < 500; i++) {
+ DEBUG_LOG("iteration %d", i);
+
+ ts.tv_sec = (time_t)random() & 0x0fffffff;
+ ts.tv_nsec = 0;
+
+ for (j = 0; j < 1000; j++) {
+ TST_GetRandomAddress(&ip, IPADDR_UNSPEC, i % 8 ? -1 : i / 8 % 9);
+ DEBUG_LOG("address %s", UTI_IPToString(&ip));
+
+ s = random() % MAX_SERVICES;
+ index = CLG_LogServiceAccess(s, &ip, &ts);
+ TEST_CHECK(index >= 0);
+ CLG_LimitServiceRate(s, index);
+
+ UTI_AddDoubleToTimespec(&ts, (1 << random() % 14) / 100.0, &ts);
+ }
+ }
+
+ DEBUG_LOG("records %u", ARR_GetSize(records));
+ TEST_CHECK(ARR_GetSize(records) == 128);
+
+ s = CLG_NTP;
+
+ for (i = j = 0; i < 10000; i++) {
+ ts.tv_sec += 1;
+ index = CLG_LogServiceAccess(s, &ip, &ts);
+ TEST_CHECK(index >= 0);
+ if (!CLG_LimitServiceRate(s, index))
+ j++;
+ }
+
+ DEBUG_LOG("requests %d responses %d", i, j);
+ TEST_CHECK(j * 4 < i && j * 6 > i);
+
+ TEST_CHECK(!ntp_ts_map.timestamps);
+
+ UTI_ZeroNtp64(&ntp_ts);
+ CLG_SaveNtpTimestamps(&ntp_ts, NULL, 0);
+ TEST_CHECK(ntp_ts_map.timestamps);
+ TEST_CHECK(ntp_ts_map.first == 0);
+ TEST_CHECK(ntp_ts_map.size == 0);
+ TEST_CHECK(ntp_ts_map.max_size == 128);
+ TEST_CHECK(ARR_GetSize(ntp_ts_map.timestamps) == ntp_ts_map.max_size);
+
+ TEST_CHECK(ntp_ts_map.max_size > NTPTS_INSERT_LIMIT);
+
+ for (i = 0; i < 200; i++) {
+ DEBUG_LOG("iteration %d", i);
+
+ max_step = (1ULL << (i % 50));
+ ts64 = 0ULL - 100 * max_step;
+
+ if (i > 150)
+ ntp_ts_map.max_size = 1U << (i % 8);
+ assert(ntp_ts_map.max_size <= 128);
+ ntp_ts_map.first = i % ntp_ts_map.max_size;
+ ntp_ts_map.size = 0;
+ ntp_ts_map.cached_rx_ts = 0ULL;
+ ntp_ts_map.slew_epoch = i * 400;
+
+ for (j = 0; j < 500; j++) {
+ do {
+ ts64 += get_random64() % max_step + 1;
+ } while (ts64 == 0ULL);
+
+ int64_to_ntp64(ts64, &ntp_ts);
+
+ if (random() % 10) {
+ UTI_Ntp64ToTimespec(&ntp_ts, &ts);
+ UTI_AddDoubleToTimespec(&ts, TST_GetRandomDouble(-1.999, 1.999), &ts);
+ } else {
+ UTI_ZeroTimespec(&ts);
+ }
+
+ ts_src = random() % (MAX_NTP_TS + 1);
+ CLG_SaveNtpTimestamps(&ntp_ts,
+ UTI_IsZeroTimespec(&ts) ? (random() % 2 ? &ts : NULL) : &ts,
+ ts_src);
+
+ if (j < ntp_ts_map.max_size) {
+ TEST_CHECK(ntp_ts_map.size == j + 1);
+ TEST_CHECK(ntp_ts_map.first == i % ntp_ts_map.max_size);
+ } else {
+ TEST_CHECK(ntp_ts_map.size == ntp_ts_map.max_size);
+ TEST_CHECK(ntp_ts_map.first == (i + j + ntp_ts_map.size + 1) % ntp_ts_map.max_size);
+ }
+ TEST_CHECK(ntp_ts_map.cached_index == ntp_ts_map.size - 1);
+ TEST_CHECK(get_ntp_tss(ntp_ts_map.size - 1)->slew_epoch == ntp_ts_map.slew_epoch);
+ TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts2, &ts_src2));
+ TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0);
+ TEST_CHECK(UTI_IsZeroTimespec(&ts) || ts_src == ts_src2);
+
+ for (k = random() % 4; k > 0; k--) {
+ index2 = random() % ntp_ts_map.size;
+ int64_to_ntp64(get_ntp_tss(index2)->rx_ts, &ntp_ts);
+ if (random() % 2)
+ TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2));
+
+ UTI_Ntp64ToTimespec(&ntp_ts, &ts);
+ UTI_AddDoubleToTimespec(&ts, TST_GetRandomDouble(-1.999, 1.999), &ts);
+
+ ts2 = ts;
+ CLG_UndoNtpTxTimestampSlew(&ntp_ts, &ts);
+ if ((get_ntp_tss(index2)->slew_epoch + 1) % (1U << 16) != ntp_ts_map.slew_epoch) {
+ TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0);
+ } else {
+ TEST_CHECK(fabs(UTI_DiffTimespecsToDouble(&ts, &ts2) - ntp_ts_map.slew_offset) <
+ 1.0e-9);
+ }
+
+ ts_src = random() % (MAX_NTP_TS + 1);
+ CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts, ts_src);
+
+ TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts2, &ts_src2));
+ TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0);
+ TEST_CHECK(ts_src == ts_src2);
+
+ if (random() % 2) {
+ uint16_t prev_epoch = ntp_ts_map.slew_epoch;
+ handle_slew(NULL, NULL, 0.0, TST_GetRandomDouble(-1.0e-5, 1.0e-5),
+ LCL_ChangeAdjust, NULL);
+ TEST_CHECK((prev_epoch + 1) % (1U << 16) == ntp_ts_map.slew_epoch);
+ }
+
+ if (ntp_ts_map.size > 1) {
+ index = random() % (ntp_ts_map.size - 1);
+ if (get_ntp_tss(index)->rx_ts + 1 != get_ntp_tss(index + 1)->rx_ts) {
+ int64_to_ntp64(get_ntp_tss(index)->rx_ts + 1, &ntp_ts);
+ TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2));
+ int64_to_ntp64(get_ntp_tss(index + 1)->rx_ts - 1, &ntp_ts);
+ TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2));
+ CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts, ts_src);
+ CLG_UndoNtpTxTimestampSlew(&ntp_ts, &ts);
+ }
+ }
+
+ if (random() % 2) {
+ int64_to_ntp64(get_ntp_tss(0)->rx_ts - 1, &ntp_ts);
+ TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2));
+ int64_to_ntp64(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts + 1, &ntp_ts);
+ TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2));
+ CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts, ts_src);
+ CLG_UndoNtpTxTimestampSlew(&ntp_ts, &ts);
+ }
+ }
+ }
+
+ for (j = 0; j < 500; j++) {
+ shift = (i % 3) * 26;
+
+ if (i % 7 == 0) {
+ while (ntp_ts_map.size < ntp_ts_map.max_size) {
+ ts64 += get_random64() >> (shift + 8);
+ int64_to_ntp64(ts64, &ntp_ts);
+ CLG_SaveNtpTimestamps(&ntp_ts, NULL, 0);
+ if (ntp_ts_map.cached_index + NTPTS_INSERT_LIMIT < ntp_ts_map.size)
+ ts64 = get_ntp_tss(ntp_ts_map.size - 1)->rx_ts;
+ }
+ }
+ do {
+ if (ntp_ts_map.size > 1 && random() % 2) {
+ k = random() % (ntp_ts_map.size - 1);
+ ts64 = get_ntp_tss(k)->rx_ts +
+ (get_ntp_tss(k + 1)->rx_ts - get_ntp_tss(k)->rx_ts) / 2;
+ } else {
+ ts64 = get_random64() >> shift;
+ }
+ } while (ts64 == 0ULL);
+
+ int64_to_ntp64(ts64, &ntp_ts);
+
+ prev_first = ntp_ts_map.first;
+ prev_size = ntp_ts_map.size;
+ prev_first_ts64 = get_ntp_tss(0)->rx_ts;
+ prev_last_ts64 = get_ntp_tss(prev_size - 1)->rx_ts;
+ CLG_SaveNtpTimestamps(&ntp_ts, NULL, 0);
+
+ TEST_CHECK(find_ntp_rx_ts(ts64, &index2));
+
+ if (ntp_ts_map.size > 1) {
+ TEST_CHECK(ntp_ts_map.size > 0 && ntp_ts_map.size <= ntp_ts_map.max_size);
+ if (get_ntp_tss(index2)->flags & NTPTS_DISABLED)
+ continue;
+
+ TEST_CHECK(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts - ts64 <= NTPTS_FUTURE_LIMIT);
+
+ if ((int64_t)(prev_last_ts64 - ts64) <= NTPTS_FUTURE_LIMIT) {
+ TEST_CHECK(prev_size + 1 >= ntp_ts_map.size);
+ if (index2 + NTPTS_INSERT_LIMIT + 1 >= ntp_ts_map.size &&
+ !(index2 == 0 && NTPTS_INSERT_LIMIT < ntp_ts_map.max_size &&
+ ((NTPTS_INSERT_LIMIT == prev_size && (int64_t)(ts64 - prev_first_ts64) > 0) ||
+ (NTPTS_INSERT_LIMIT + 1 == prev_size && (int64_t)(ts64 - prev_first_ts64) < 0))))
+ TEST_CHECK((prev_first + prev_size + 1) % ntp_ts_map.max_size ==
+ (ntp_ts_map.first + ntp_ts_map.size) % ntp_ts_map.max_size);
+ else
+ TEST_CHECK(prev_first + prev_size == ntp_ts_map.first + ntp_ts_map.size);
+ }
+
+ TEST_CHECK((int64_t)(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts -
+ get_ntp_tss(0)->rx_ts) > 0);
+ for (k = 0; k + 1 < ntp_ts_map.size; k++)
+ TEST_CHECK((int64_t)(get_ntp_tss(k + 1)->rx_ts - get_ntp_tss(k)->rx_ts) > 0);
+ }
+
+ if (random() % 10 == 0) {
+ CLG_DisableNtpTimestamps(&ntp_ts);
+ TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2));
+ }
+
+ for (k = random() % 10; k > 0; k--) {
+ ts64 = get_random64() >> shift;
+ int64_to_ntp64(ts64, &ntp_ts);
+ CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2);
+ }
+ }
+
+ if (random() % 2) {
+ handle_slew(NULL, NULL, 0.0, TST_GetRandomDouble(-1.0e9, 1.0e9),
+ LCL_ChangeUnknownStep, NULL);
+ TEST_CHECK(ntp_ts_map.size == 0);
+ TEST_CHECK(ntp_ts_map.cached_rx_ts == 0ULL);
+ TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts, &ts_src2));
+ CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts, ts_src);
+ }
+ }
+
+ CLG_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/cmac.c b/test/unit/cmac.c
new file mode 100644
index 0000000..be4ffbb
--- /dev/null
+++ b/test/unit/cmac.c
@@ -0,0 +1,109 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include <sysincl.h>
+#include <cmac.h>
+#include <logging.h>
+#include <util.h>
+#include "test.h"
+
+#define MAX_KEY_LENGTH 64
+#define MAX_HASH_LENGTH 64
+
+struct cmac_test {
+ const char *name;
+ const unsigned char key[MAX_KEY_LENGTH];
+ int key_length;
+ const unsigned char hash[MAX_HASH_LENGTH];
+ int hash_length;
+};
+
+void
+test_unit(void)
+{
+ unsigned char data[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ unsigned char hash[MAX_HASH_LENGTH];
+ struct cmac_test tests[] = {
+ { "AES128", "\xfc\x24\x97\x1b\x52\x66\xdc\x46\xef\xe0\xe8\x08\x46\x89\xb6\x88", 16,
+ "\xaf\x3c\xfe\xc2\x66\x71\x08\x04\xd4\xaf\x5b\x16\x2b\x11\xf4\x85", 16 },
+ { "AES256", "\x14\x02\x8e\x7d\x17\x3c\x2f\x4e\x17\x0f\x37\x96\xc3\x2c\xc5\x99"
+ "\x18\xdd\x55\x23\xb7\xd7\x9b\xc5\x76\x36\x88\x3f\xc5\x82\xb5\x83", 32,
+ "\xfe\xf7\x94\x96\x14\x04\x11\x0b\x87\xe4\xd4\x3f\x81\xb3\xb2\x2d", 16 },
+ { "", "", 0, "", 0 }
+ };
+
+ CMC_Algorithm algorithm;
+ CMC_Instance inst;
+ int i, j, length;
+
+#ifndef HAVE_CMAC
+ TEST_REQUIRE(0);
+#endif
+
+ TEST_CHECK(CMC_INVALID == 0);
+
+ for (i = 0; tests[i].name[0] != '\0'; i++) {
+ algorithm = UTI_CmacNameToAlgorithm(tests[i].name);
+ TEST_CHECK(algorithm != 0);
+ TEST_CHECK(CMC_GetKeyLength(algorithm) == tests[i].key_length);
+
+ DEBUG_LOG("testing %s", tests[i].name);
+
+ for (j = -1; j <= 128; j++) {
+ if (j == tests[i].key_length)
+ continue;
+ TEST_CHECK(!CMC_CreateInstance(algorithm, tests[i].key, j));
+ }
+
+ inst = CMC_CreateInstance(algorithm, tests[i].key, tests[i].key_length);
+ TEST_CHECK(inst);
+
+ TEST_CHECK(!CMC_CreateInstance(0, tests[i].key, tests[i].key_length));
+
+ TEST_CHECK(CMC_Hash(inst, data, -1, hash, sizeof (hash)) == 0);
+ TEST_CHECK(CMC_Hash(inst, data, sizeof (data) - 1, hash, -1) == 0);
+
+ for (j = 0; j <= sizeof (hash); j++) {
+ memset(hash, 0, sizeof (hash));
+ length = CMC_Hash(inst, data, sizeof (data) - 1, hash, j);
+
+#if 0
+ for (int k = 0; k < length; k++)
+ printf("\\x%02x", hash[k]);
+ printf("\n");
+#endif
+
+ if (j >= tests[i].hash_length)
+ TEST_CHECK(length == tests[i].hash_length);
+ else
+ TEST_CHECK(length == j);
+
+ TEST_CHECK(!memcmp(hash, tests[i].hash, length));
+ }
+
+ for (j = 0; j < sizeof (data); j++) {
+ length = CMC_Hash(inst, data, j, hash, sizeof (hash));
+ TEST_CHECK(length == tests[i].hash_length);
+ }
+
+ CMC_DestroyInstance(inst);
+ }
+}
diff --git a/test/unit/hash.c b/test/unit/hash.c
new file mode 100644
index 0000000..2f9ffef
--- /dev/null
+++ b/test/unit/hash.c
@@ -0,0 +1,131 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include <sysincl.h>
+#include <hash.h>
+#include <logging.h>
+#include <util.h>
+#include "test.h"
+
+struct hash_test {
+ const char *name;
+ const unsigned char out[MAX_HASH_LENGTH];
+ int length;
+};
+
+void
+test_unit(void)
+{
+ unsigned char data1[] = "abcdefghijklmnopqrstuvwxyz";
+ unsigned char data2[] = "12345678910";
+ unsigned char out[MAX_HASH_LENGTH];
+ struct hash_test tests[] = {
+ { "MD5-NC", "\xfc\x24\x97\x1b\x52\x66\xdc\x46\xef\xe0\xe8\x08\x46\x89\xb6\x88", 16 },
+ { "MD5", "\xfc\x24\x97\x1b\x52\x66\xdc\x46\xef\xe0\xe8\x08\x46\x89\xb6\x88", 16 },
+ { "SHA1", "\xd8\x85\xb3\x86\xce\xea\x93\xeb\x92\xcd\x7b\x94\xb9\x8d\xc2\x8e"
+ "\x3e\x31\x13\xdd", 20},
+ { "SHA256", "\x0e\x35\x14\xe7\x15\x7a\x1d\xdd\xea\x11\x78\xd3\x41\xf6\xb9\x3e"
+ "\xa0\x42\x96\x73\x3c\x54\x74\x0b\xfa\x6b\x9e\x29\x59\xad\x69\xd3", 32 },
+ { "SHA384", "\x2c\xeb\xbd\x4d\x95\xed\xad\x03\xed\x80\xc4\xf3\xa6\x10\x21\xde"
+ "\x40\x69\x54\xed\x42\x70\xb8\x95\xb0\x6f\x01\x1d\x04\xdf\x57\xbc"
+ "\x1d\xb5\x85\xbf\x4f\x03\x88\xd5\x83\x93\xbc\x81\x90\xb0\xa9\x9b", 48 },
+ { "SHA512", "\x20\xba\xec\xcb\x68\x98\x33\x5b\x70\x26\x63\x13\xe2\xf7\x0e\x67"
+ "\x08\xf3\x77\x4f\xbd\xeb\xc4\xa8\xc5\x94\xe2\x39\x40\x7e\xed\x0b"
+ "\x69\x0e\x18\xa5\xa2\x03\x73\xe7\x1d\x20\x7d\x3f\xc8\x70\x2d\x64"
+ "\x9e\x89\x6d\x20\x6a\x4a\x5a\x46\xe7\x4f\x2c\xf9\x0f\x0a\x54\xdc", 64 },
+ { "SHA3-224", "\x3b\xa2\x22\x28\xdd\x26\x18\xec\x3b\xb9\x25\x39\x5e\xbd\x94\x25"
+ "\xd4\x20\x8a\x76\x76\xc0\x3c\x5d\x9e\x0a\x06\x46", 28},
+ { "SHA3-256", "\x26\xd1\x19\xb2\xc1\x64\xc8\xb8\x10\xd8\xa8\x1c\xb6\xa4\x0d\x29"
+ "\x09\xc9\x8e\x2e\x2d\xde\x7a\x74\x8c\x43\x70\xb8\xaa\x0f\x09\x17", 32 },
+ { "SHA3-384", "\x6a\x64\xb9\x89\x08\x29\xd0\xa7\x4b\x84\xba\xa6\x65\xf5\xe7\x54"
+ "\xe2\x18\x12\xc3\x63\x34\xc6\xba\x26\xf5\x6e\x99\xe2\x54\xcc\x9d"
+ "\x01\x10\x9d\xee\x35\x38\x04\x83\xe5\x71\x70\xd8\xc8\x99\x96\xd8", 48 },
+ { "SHA3-512", "\xa8\xe3\x2b\x65\x1f\x87\x90\x73\x19\xc8\xa0\x3f\xe3\x85\x60\x3c"
+ "\x39\xfc\xcb\xc1\x29\xe1\x23\x7d\x8b\x56\x54\xe3\x08\x9d\xf9\x74"
+ "\x78\x69\x2e\x3c\x7e\x51\x1e\x9d\xab\x09\xbe\xe7\x6b\x1a\xa1\x22"
+ "\x93\xb1\x2b\x82\x9d\x1e\xcf\xa8\x99\xc5\xec\x7b\x1d\x89\x07\x2b", 64 },
+ { "TIGER", "\x1c\xcd\x68\x74\xca\xd6\xd5\x17\xba\x3e\x82\xaf\xbd\x70\xdc\x66"
+ "\x99\xaa\xae\x16\x72\x59\xd1\x64", 24},
+ { "WHIRLPOOL", "\xe3\xcd\xe6\xbf\xe1\x8c\xe4\x4d\xc8\xb4\xa5\x7c\x36\x8d\xc8\x8a"
+ "\x8b\xad\x52\x24\xc0\x4e\x99\x5b\x7e\x86\x94\x2d\x10\x56\x12\xa3"
+ "\x29\x2a\x65\x0f\x9e\x07\xbc\x15\x21\x14\xe6\x07\xfc\xe6\xb9\x2f"
+ "\x13\xe2\x57\xe9\x0a\xb0\xd2\xf4\xa3\x20\x36\x9c\x88\x92\x8e\xc9", 64 },
+ { "", "", 0 }
+ };
+
+ HSH_Algorithm algorithm;
+ int i, j, hash_id, length;
+
+ TEST_CHECK(HSH_INVALID == 0);
+
+ for (i = 0; tests[i].name[0] != '\0'; i++) {
+ algorithm = UTI_HashNameToAlgorithm(tests[i].name);
+ if (strcmp(tests[i].name, "MD5-NC") == 0) {
+ TEST_CHECK(algorithm == 0);
+ algorithm = HSH_MD5_NONCRYPTO;
+ } else {
+ TEST_CHECK(algorithm != 0);
+ }
+ hash_id = HSH_GetHashId(algorithm);
+ if (hash_id < 0) {
+ TEST_CHECK(algorithm != HSH_MD5_NONCRYPTO);
+ TEST_CHECK(algorithm != HSH_MD5);
+#ifdef FEAT_SECHASH
+ TEST_CHECK(algorithm != HSH_SHA1);
+ TEST_CHECK(algorithm != HSH_SHA256);
+ TEST_CHECK(algorithm != HSH_SHA384);
+ TEST_CHECK(algorithm != HSH_SHA512);
+#endif
+ continue;
+ }
+
+ DEBUG_LOG("testing %s", tests[i].name);
+
+ TEST_CHECK(HSH_Hash(hash_id, data1, -1, NULL, 0, out, sizeof (out)) == 0);
+ TEST_CHECK(HSH_Hash(hash_id, data1, sizeof (data1) - 1, data2, -1, out, sizeof (out)) == 0);
+ TEST_CHECK(HSH_Hash(hash_id, data1, sizeof (data1) - 1, NULL, 0, out, -1) == 0);
+
+ for (j = 0; j <= sizeof (out); j++) {
+ TEST_CHECK(HSH_GetHashId(algorithm) == hash_id);
+ TEST_CHECK(HSH_GetHashId(0) < 0);
+
+ memset(out, 0, sizeof (out));
+ length = HSH_Hash(hash_id, data1, sizeof (data1) - 1, data2, sizeof (data2) - 1,
+ out, j);
+
+ if (j >= tests[i].length)
+ TEST_CHECK(length == tests[i].length);
+ else
+ TEST_CHECK(length == j);
+
+ TEST_CHECK(!memcmp(out, tests[i].out, length));
+ }
+
+ for (j = 0; j < 10000; j++) {
+ length = HSH_Hash(hash_id, data1, random() % sizeof (data1),
+ random() % 2 ? data2 : NULL, random() % sizeof (data2),
+ out, sizeof (out));
+ TEST_CHECK(length == tests[i].length);
+ }
+ }
+
+ HSH_Finalise();
+}
diff --git a/test/unit/hwclock.c b/test/unit/hwclock.c
new file mode 100644
index 0000000..79c0879
--- /dev/null
+++ b/test/unit/hwclock.c
@@ -0,0 +1,117 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016-2018, 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#if defined(FEAT_PHC) || defined(HAVE_LINUX_TIMESTAMPING)
+
+#include <hwclock.c>
+
+#define MAX_READINGS 20
+
+void
+test_unit(void)
+{
+ struct timespec start_hw_ts, start_local_ts, hw_ts, local_ts, ts;
+ struct timespec readings[MAX_READINGS][3];
+ HCL_Instance clock;
+ double freq, jitter, interval, dj, err, sum;
+ int i, j, k, l, new_sample, n_readings, count;
+
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+
+ for (i = 1; i <= 8; i++) {
+ clock = HCL_CreateInstance(random() % (1 << i), 1 << i, 1.0, 1e-9);
+
+ for (j = 0, count = 0, sum = 0.0; j < 100; j++) {
+ UTI_ZeroTimespec(&start_hw_ts);
+ UTI_ZeroTimespec(&start_local_ts);
+ UTI_AddDoubleToTimespec(&start_hw_ts, TST_GetRandomDouble(0.0, 1e9), &start_hw_ts);
+ UTI_AddDoubleToTimespec(&start_local_ts, TST_GetRandomDouble(0.0, 1e9), &start_local_ts);
+
+ DEBUG_LOG("iteration %d", j);
+
+ freq = TST_GetRandomDouble(0.9, 1.1);
+ jitter = TST_GetRandomDouble(10.0e-9, 1000.0e-9);
+ interval = TST_GetRandomDouble(0.1, 10.0);
+
+ clock->n_samples = 0;
+ clock->valid_coefs = 0;
+ QNT_Reset(clock->delay_quants);
+
+ new_sample = 0;
+
+ for (k = 0; k < 100; k++) {
+ UTI_AddDoubleToTimespec(&start_hw_ts, k * interval * freq, &hw_ts);
+ UTI_AddDoubleToTimespec(&start_local_ts, k * interval, &local_ts);
+ if (HCL_CookTime(clock, &hw_ts, &ts, NULL) && new_sample) {
+ dj = fabs(UTI_DiffTimespecsToDouble(&ts, &local_ts) / jitter);
+ DEBUG_LOG("delta/jitter %f", dj);
+ if (clock->n_samples >= clock->max_samples / 2)
+ sum += dj, count++;
+ TEST_CHECK(clock->n_samples < 4 || dj <= 4.0);
+ TEST_CHECK(clock->n_samples < 8 || dj <= 3.0);
+ }
+
+ UTI_AddDoubleToTimespec(&start_hw_ts, k * interval * freq + TST_GetRandomDouble(-jitter, jitter), &hw_ts);
+
+ if (HCL_NeedsNewSample(clock, &local_ts)) {
+ n_readings = random() % MAX_READINGS + 1;
+ for (l = 0; l < n_readings; l++) {
+ UTI_AddDoubleToTimespec(&local_ts, -TST_GetRandomDouble(0.0, jitter / 10.0), &readings[l][0]);
+ readings[l][1] = hw_ts;
+ UTI_AddDoubleToTimespec(&local_ts, TST_GetRandomDouble(0.0, jitter / 10.0), &readings[l][2]);
+ }
+
+ UTI_ZeroTimespec(&hw_ts);
+ UTI_ZeroTimespec(&local_ts);
+ if (HCL_ProcessReadings(clock, n_readings, readings, &hw_ts, &local_ts, &err)) {
+ HCL_AccumulateSample(clock, &hw_ts, &local_ts, 2.0 * jitter);
+ new_sample = 1;
+ } else {
+ new_sample = 0;
+ }
+ }
+
+ TEST_CHECK(clock->valid_coefs == (clock->n_samples >= 2));
+
+ if (!clock->valid_coefs)
+ continue;
+
+ TEST_CHECK(fabs(clock->offset) <= 2.0 * jitter);
+ }
+ }
+
+ TEST_CHECK(sum / count < 2.4 / sqrt(clock->max_samples));
+
+ HCL_DestroyInstance(clock);
+ }
+
+ LCL_Finalise();
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/keys.c b/test/unit/keys.c
new file mode 100644
index 0000000..aa5e649
--- /dev/null
+++ b/test/unit/keys.c
@@ -0,0 +1,173 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#if defined(FEAT_NTP) || defined(FEAT_CMDMON)
+
+#include <keys.c>
+
+#define KEYS 100
+#define KEYFILE "keys.test-keys"
+
+static
+uint32_t write_random_key(FILE *f)
+{
+ const char *type, *prefix;
+ char key[128];
+ uint32_t id;
+ int i, length;
+
+ length = random() % sizeof (key) + 1;
+ length = MAX(length, 4);
+ prefix = random() % 2 ? "HEX:" : "";
+
+ switch (random() % 8) {
+#ifdef FEAT_SECHASH
+ case 0:
+ type = "SHA1";
+ break;
+ case 1:
+ type = "SHA256";
+ break;
+ case 2:
+ type = "SHA384";
+ break;
+ case 3:
+ type = "SHA512";
+ break;
+#endif
+#ifdef HAVE_CMAC
+ case 4:
+ type = "AES128";
+ length = prefix[0] == '\0' ? 8 : 16;
+ break;
+ case 5:
+ type = "AES256";
+ length = prefix[0] == '\0' ? 16 : 32;
+ break;
+#endif
+ case 6:
+ type = "MD5";
+ break;
+ default:
+ type = "";
+ }
+
+ UTI_GetRandomBytes(&id, sizeof (id));
+ UTI_GetRandomBytes(key, length);
+
+ fprintf(f, "%u %s %s", id, type, prefix);
+ for (i = 0; i < length; i++)
+ fprintf(f, "%02hhX", key[i]);
+ fprintf(f, "\n");
+
+ return id;
+}
+
+static void
+generate_key_file(const char *name, uint32_t *keys)
+{
+ FILE *f;
+ int i;
+
+ f = fopen(name, "w");
+ TEST_CHECK(f);
+ for (i = 0; i < KEYS; i++)
+ keys[i] = write_random_key(f);
+ fclose(f);
+}
+
+void
+test_unit(void)
+{
+ int i, j, data_len, auth_len, type, bits;
+ uint32_t keys[KEYS], key;
+ unsigned char data[100], auth[MAX_HASH_LENGTH];
+ char conf[][100] = {
+ "keyfile "KEYFILE
+ };
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ generate_key_file(KEYFILE, keys);
+ KEY_Initialise();
+
+ for (i = 0; i < 100; i++) {
+ DEBUG_LOG("iteration %d", i);
+
+ if (i) {
+ generate_key_file(KEYFILE, keys);
+ KEY_Reload();
+ }
+
+ UTI_GetRandomBytes(data, sizeof (data));
+
+ for (j = 0; j < KEYS; j++) {
+ TEST_CHECK(KEY_KeyKnown(keys[j]));
+ TEST_CHECK(KEY_GetAuthLength(keys[j]) >= 16);
+
+ data_len = random() % (sizeof (data) + 1);
+ auth_len = KEY_GenerateAuth(keys[j], data, data_len, auth, sizeof (auth));
+ TEST_CHECK(auth_len >= 16);
+
+ TEST_CHECK(KEY_CheckAuth(keys[j], data, data_len, auth, auth_len, auth_len));
+
+ if (j > 0 && keys[j - 1] != keys[j])
+ TEST_CHECK(!KEY_CheckAuth(keys[j - 1], data, data_len, auth, auth_len, auth_len));
+
+ auth_len = random() % auth_len + 1;
+ if (auth_len < MAX_HASH_LENGTH)
+ auth[auth_len]++;
+ TEST_CHECK(KEY_CheckAuth(keys[j], data, data_len, auth, auth_len, auth_len));
+
+ auth[auth_len - 1]++;
+ TEST_CHECK(!KEY_CheckAuth(keys[j], data, data_len, auth, auth_len, auth_len));
+
+ TEST_CHECK(KEY_GetKeyInfo(keys[j], &type, &bits));
+ TEST_CHECK(type > 0 && bits > 0);
+ }
+
+ for (j = 0; j < 1000; j++) {
+ UTI_GetRandomBytes(&key, sizeof (key));
+ if (KEY_KeyKnown(key))
+ continue;
+ TEST_CHECK(!KEY_GetKeyInfo(key, &type, &bits));
+ TEST_CHECK(!KEY_GenerateAuth(key, data, data_len, auth, sizeof (auth)));
+ TEST_CHECK(!KEY_CheckAuth(key, data, data_len, auth, auth_len, auth_len));
+ }
+ }
+
+ unlink(KEYFILE);
+
+ KEY_Finalise();
+ CNF_Finalise();
+ HSH_Finalise();
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/ntp_auth.c b/test/unit/ntp_auth.c
new file mode 100644
index 0000000..5f2a9bc
--- /dev/null
+++ b/test/unit/ntp_auth.c
@@ -0,0 +1,289 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include <sysincl.h>
+#include <conf.h>
+#include <keys.h>
+#include <local.h>
+#include <ntp_ext.h>
+#include <ntp_signd.h>
+#include <nts_ntp_server.h>
+#include <socket.h>
+#include "test.h"
+
+#ifdef FEAT_NTP
+
+#include <ntp_auth.c>
+
+static void
+prepare_packet(NTP_AuthMode auth_mode, NTP_Packet *packet, NTP_PacketInfo *info, int req)
+{
+ unsigned char buf[64];
+ int i, version;
+ NTP_Mode mode;
+
+ switch (auth_mode) {
+ case NTP_AUTH_MSSNTP:
+ case NTP_AUTH_MSSNTP_EXT:
+ version = 3;
+ mode = random() % 2 ? (req ? MODE_CLIENT : MODE_SERVER) :
+ (req ? MODE_ACTIVE : MODE_PASSIVE);
+ break;
+ case NTP_AUTH_NTS:
+ version = 4;
+ mode = req ? MODE_CLIENT : MODE_SERVER;
+ break;
+ default:
+ version = 3 + random() % 2;
+ mode = random() % 2 ? (req ? MODE_CLIENT : MODE_SERVER) :
+ (req ? MODE_ACTIVE : MODE_PASSIVE);
+ break;
+ }
+
+ memset(packet, 0, sizeof (*packet));
+ memset(info, 0, sizeof (*info));
+ packet->lvm = NTP_LVM(LEAP_Normal, version, mode);
+ info->length = NTP_HEADER_LENGTH;
+ info->version = version;
+ info->mode = mode;
+
+ if (version == 4) {
+ memset(buf, 0, sizeof (buf));
+ for (i = random() % 5; i > 0; i--)
+ TEST_CHECK(NEF_AddField(packet, info, 0, buf, sizeof (buf)));
+ }
+}
+
+static void
+add_dummy_auth(NTP_AuthMode auth_mode, uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info)
+{
+ unsigned char buf[64];
+ int len, fill;
+
+ info->auth.mode = auth_mode;
+
+ switch (auth_mode) {
+ case NTP_AUTH_NONE:
+ break;
+ case NTP_AUTH_SYMMETRIC:
+ case NTP_AUTH_MSSNTP:
+ case NTP_AUTH_MSSNTP_EXT:
+ switch (auth_mode) {
+ case NTP_AUTH_SYMMETRIC:
+ len = 16 + random() % 2 * 4;
+ fill = 1 + random() % 255;
+ break;
+ case NTP_AUTH_MSSNTP:
+ len = 16;
+ fill = 0;
+ break;
+ case NTP_AUTH_MSSNTP_EXT:
+ len = 68;
+ fill = 0;
+ break;
+ default:
+ assert(0);
+ }
+
+ assert(info->length + 4 + len <= sizeof (*packet));
+
+ *(uint32_t *)((unsigned char *)packet + info->length) = htonl(key_id);
+ info->auth.mac.key_id = key_id;
+ info->length += 4;
+
+ memset((unsigned char *)packet + info->length, fill, len);
+ info->length += len;
+ break;
+ case NTP_AUTH_NTS:
+ memset(buf, 0, sizeof (buf));
+ TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_AUTH_AND_EEF, buf, sizeof (buf)));
+ break;
+ default:
+ assert(0);
+ }
+}
+
+void
+test_unit(void)
+{
+ int i, j, can_auth_req, can_auth_res;
+ NTP_PacketInfo req_info, res_info;
+ NTP_Packet req, res;
+ NAU_Instance inst;
+ RPT_AuthReport report;
+ NTP_AuthMode mode;
+ IPSockAddr nts_addr;
+ uint32_t key_id, kod;
+ char conf[][100] = {
+ "keyfile ntp_core.keys"
+ };
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ LCL_Initialise();
+ KEY_Initialise();
+ NSD_Initialise();
+ NNS_Initialise();
+
+ SCK_GetAnyLocalIPAddress(IPADDR_INET4, &nts_addr.ip_addr);
+ nts_addr.port = 0;
+
+ for (i = 0; i < 1000; i++) {
+ key_id = INACTIVE_AUTHKEY;
+
+ switch (i % 5) {
+ case 0:
+ inst = NAU_CreateNoneInstance();
+ TEST_CHECK(!NAU_IsAuthEnabled(inst));
+ TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) == 4);
+ mode = NTP_AUTH_NONE;
+ can_auth_req = 1;
+ can_auth_res = 1;
+ break;
+ case 1:
+ key_id = random() % 7 + 2;
+ inst = NAU_CreateSymmetricInstance(key_id);
+ TEST_CHECK(NAU_IsAuthEnabled(inst));
+ TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) ==
+ (KEY_KeyKnown(inst->key_id) && KEY_GetAuthLength(inst->key_id) > 20 ? 3 : 4));
+ mode = NTP_AUTH_SYMMETRIC;
+ can_auth_req = KEY_KeyKnown(key_id);
+ can_auth_res = can_auth_req;
+ break;
+ case 2:
+ inst = NAU_CreateNtsInstance(&nts_addr, "test", 0, 0);
+ TEST_CHECK(NAU_IsAuthEnabled(inst));
+ TEST_CHECK(NAU_GetSuggestedNtpVersion(inst) == 4);
+ mode = NTP_AUTH_NTS;
+ can_auth_req = 0;
+ can_auth_res = 0;
+ break;
+ case 3:
+ key_id = 1 + random() % 100;
+ inst = NULL;
+ mode = NTP_AUTH_MSSNTP;
+ can_auth_req = 1;
+ can_auth_res = 0;
+ break;
+ case 4:
+ key_id = 1 + random() % 100;
+ inst = NULL;
+ mode = NTP_AUTH_MSSNTP_EXT;
+ can_auth_req = 0;
+ can_auth_res = 0;
+ break;
+ default:
+ assert(0);
+ }
+
+ DEBUG_LOG("iteration %d auth=%d key_id=%d", i, (int)mode, (int)key_id);
+
+ prepare_packet(mode, &req, &req_info, 1);
+
+ if (inst) {
+ TEST_CHECK(inst->mode == mode);
+ TEST_CHECK(inst->key_id == key_id);
+
+ NAU_DumpData(inst);
+ NAU_GetReport(inst, &report);
+ if (random() % 2)
+ NAU_ChangeAddress(inst, &nts_addr.ip_addr);
+
+ if (inst->mode == NTP_AUTH_NTS) {
+ for (j = random() % 5; j > 0; j--)
+#ifdef FEAT_NTS
+ TEST_CHECK(!NAU_PrepareRequestAuth(inst));
+#else
+ TEST_CHECK(NAU_PrepareRequestAuth(inst));
+#endif
+ TEST_CHECK(!NAU_GenerateRequestAuth(inst, &req, &req_info));
+ } else if (can_auth_req) {
+ TEST_CHECK(NAU_PrepareRequestAuth(inst));
+ TEST_CHECK(NAU_GenerateRequestAuth(inst, &req, &req_info));
+ } else {
+ TEST_CHECK(NAU_PrepareRequestAuth(inst));
+ TEST_CHECK(!NAU_GenerateRequestAuth(inst, &req, &req_info));
+ }
+ }
+
+ if (!inst || !can_auth_req)
+ add_dummy_auth(mode, key_id, &req, &req_info);
+
+ assert(req_info.auth.mode == mode);
+ assert(req_info.auth.mac.key_id == key_id);
+
+ kod = 1;
+ TEST_CHECK(NAU_CheckRequestAuth(&req, &req_info, &kod) == can_auth_req);
+ TEST_CHECK(kod == 0);
+
+ if (inst) {
+ for (j = NTP_AUTH_NONE; j <= NTP_AUTH_NTS; j++) {
+ if (j == mode && j == NTP_AUTH_NONE)
+ continue;
+
+ prepare_packet(j, &res, &res_info, 0);
+ add_dummy_auth(j, key_id ? key_id : 1, &res, &res_info);
+
+ TEST_CHECK(res_info.auth.mode == j);
+ TEST_CHECK(!NAU_CheckResponseAuth(inst, &res, &res_info));
+ }
+ }
+
+ prepare_packet(mode, &res, &res_info, 0);
+ TEST_CHECK(NAU_GenerateResponseAuth(&req, &req_info, &res, &res_info, NULL, NULL, kod) ==
+ can_auth_res);
+ if (!can_auth_res)
+ add_dummy_auth(mode, key_id, &res, &res_info);
+
+ assert(res_info.auth.mode == mode);
+ assert(res_info.auth.mac.key_id == key_id);
+
+ if (inst) {
+ if (mode == NTP_AUTH_SYMMETRIC) {
+ res_info.auth.mac.key_id ^= 1;
+ TEST_CHECK(!NAU_CheckResponseAuth(inst, &res, &res_info));
+ res_info.auth.mac.key_id ^= 1;
+ }
+
+ TEST_CHECK(NAU_CheckResponseAuth(inst, &res, &res_info) == can_auth_res);
+
+ NAU_GetReport(inst, &report);
+ NAU_DestroyInstance(inst);
+ }
+ }
+
+ NNS_Finalise();
+ NSD_Finalise();
+ KEY_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+ HSH_Finalise();
+}
+
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/ntp_core.c b/test/unit/ntp_core.c
new file mode 100644
index 0000000..989b294
--- /dev/null
+++ b/test/unit/ntp_core.c
@@ -0,0 +1,648 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2017-2018, 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include <sysincl.h>
+#include <cmdparse.h>
+#include <conf.h>
+#include <keys.h>
+#include <ntp_ext.h>
+#include <ntp_io.h>
+#include <sched.h>
+#include <local.h>
+#include "test.h"
+
+#ifdef FEAT_NTP
+
+static struct timespec current_time;
+static NTP_Packet req_buffer, res_buffer;
+static int req_length, res_length;
+
+#define NIO_IsHwTsEnabled() 1
+#define NIO_OpenServerSocket(addr) ((addr)->ip_addr.family != IPADDR_UNSPEC ? 100 : 0)
+#define NIO_CloseServerSocket(fd) assert(fd == 100)
+#define NIO_OpenClientSocket(addr) ((addr)->ip_addr.family != IPADDR_UNSPEC ? 101 : 0)
+#define NIO_CloseClientSocket(fd) assert(fd == 101)
+#define NIO_IsServerSocket(fd) (fd == 100)
+#define NIO_IsServerSocketOpen() 1
+#define NIO_SendPacket(msg, to, from, len, process_tx) (memcpy(&req_buffer, msg, len), req_length = len, 1)
+#define SCH_AddTimeoutByDelay(delay, handler, arg) (1 ? 102 : (handler(arg), 1))
+#define SCH_AddTimeoutInClass(delay, separation, randomness, class, handler, arg) \
+ add_timeout_in_class(delay, separation, randomness, class, handler, arg)
+#define SCH_RemoveTimeout(id) assert(!id || id == 102)
+#define LCL_ReadRawTime(ts) (*ts = current_time)
+#define LCL_ReadCookedTime(ts, err) do {double *p = err; *ts = current_time; if (p) *p = 0.0;} while (0)
+#define LCL_GetSysPrecisionAsLog() (random() % 10 - 30)
+#define SRC_UpdateReachability(inst, reach)
+#define SRC_ResetReachability(inst)
+
+static SCH_TimeoutID
+add_timeout_in_class(double min_delay, double separation, double randomness,
+ SCH_TimeoutClass class, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg)
+{
+ return 102;
+}
+
+#include <ntp_core.c>
+
+static void
+advance_time(double x)
+{
+ UTI_AddDoubleToTimespec(&current_time, x, &current_time);
+}
+
+static uint32_t
+get_random_key_id(void)
+{
+ uint32_t id;
+
+ do {
+ id = random() % 8 + 2;
+ } while (!KEY_KeyKnown(id));
+
+ return id;
+}
+
+static void
+send_request(NCR_Instance inst, int late_hwts)
+{
+ NTP_Local_Address local_addr;
+ NTP_Local_Timestamp local_ts;
+ uint32_t prev_tx_count;
+
+ prev_tx_count = inst->report.total_tx_count;
+
+ transmit_timeout(inst);
+ TEST_CHECK(!inst->valid_rx);
+ TEST_CHECK(prev_tx_count + 1 == inst->report.total_tx_count);
+
+ advance_time(1e-5);
+
+ if (random() % 2) {
+ local_addr.ip_addr.family = IPADDR_UNSPEC;
+ local_addr.if_index = INVALID_IF_INDEX;
+ local_addr.sock_fd = 101;
+ zero_local_timestamp(&local_ts);
+ local_ts.ts = current_time;
+ local_ts.source = NTP_TS_KERNEL;
+
+ NCR_ProcessTxKnown(inst, &local_addr, &local_ts, &req_buffer, req_length);
+ }
+
+ if (late_hwts) {
+ inst->report.total_good_count++;
+ } else {
+ inst->report.total_good_count = 0;
+ }
+}
+
+static void
+process_request(NTP_Remote_Address *remote_addr)
+{
+ NTP_Local_Address local_addr;
+ NTP_Local_Timestamp local_ts;
+
+ local_addr.ip_addr.family = IPADDR_UNSPEC;
+ local_addr.if_index = INVALID_IF_INDEX;
+ local_addr.sock_fd = 100;
+ zero_local_timestamp(&local_ts);
+ local_ts.ts = current_time;
+ local_ts.source = NTP_TS_KERNEL;
+
+ res_length = 0;
+ NCR_ProcessRxUnknown(remote_addr, &local_addr, &local_ts,
+ &req_buffer, req_length);
+ res_length = req_length;
+ res_buffer = req_buffer;
+
+ advance_time(1e-5);
+
+ if (random() % 2) {
+ local_ts.ts = current_time;
+ NCR_ProcessTxUnknown(remote_addr, &local_addr, &local_ts,
+ &res_buffer, res_length);
+ }
+}
+
+static void
+send_response(int interleaved, int authenticated, int allow_update, int valid_ts, int valid_auth)
+{
+ NTP_Packet *req, *res;
+ uint32_t key_id = 0;
+ int i, auth_len = 0, ef_len, efs;
+
+ req = &req_buffer;
+ res = &res_buffer;
+
+ TEST_CHECK(req_length >= NTP_HEADER_LENGTH);
+
+ res->lvm = NTP_LVM(LEAP_Normal, NTP_LVM_TO_VERSION(req->lvm),
+ NTP_LVM_TO_MODE(req->lvm) == MODE_CLIENT ? MODE_SERVER : MODE_ACTIVE);
+ res->stratum = 1;
+ res->poll = req->poll;
+ res->precision = -20;
+ res->root_delay = UTI_DoubleToNtp32(0.1);
+ res->root_dispersion = UTI_DoubleToNtp32(0.1);
+ res->reference_id = 0;
+ UTI_ZeroNtp64(&res->reference_ts);
+ res->originate_ts = interleaved ? req->receive_ts : req->transmit_ts;
+
+ advance_time(TST_GetRandomDouble(1e-4, 1e-2));
+ UTI_TimespecToNtp64(&current_time, &res->receive_ts, NULL);
+ advance_time(TST_GetRandomDouble(-1e-4, 1e-3));
+ UTI_TimespecToNtp64(&current_time, &res->transmit_ts, NULL);
+ advance_time(TST_GetRandomDouble(1e-4, 1e-2));
+
+ if (!valid_ts) {
+ switch (random() % (allow_update ? 4 : 5)) {
+ case 0:
+ res->originate_ts.hi = random();
+ break;
+ case 1:
+ res->originate_ts.lo = random();
+ break;
+ case 2:
+ UTI_ZeroNtp64(&res->originate_ts);
+ break;
+ case 3:
+ UTI_ZeroNtp64(&res->receive_ts);
+ break;
+ case 4:
+ UTI_ZeroNtp64(&res->transmit_ts);
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ res_length = NTP_HEADER_LENGTH;
+
+ if (NTP_LVM_TO_VERSION(res->lvm) == 4 && random() % 2) {
+ unsigned char buf[128];
+
+ memset(buf, 0, sizeof (buf));
+ efs = random() % 5;
+
+ for (i = 0; i < efs; i++) {
+ ef_len = (i + 1 == efs ? NTP_MAX_V4_MAC_LENGTH + 4 : NTP_MIN_EF_LENGTH) +
+ 4 * (random() % 10);
+ TEST_CHECK(NEF_SetField((unsigned char *)res, sizeof (*res), res_length, 0,
+ buf, ef_len - 4, &ef_len));
+ res_length += ef_len;
+ }
+ }
+
+ if (authenticated) {
+ key_id = ntohl(*(uint32_t *)req->extensions);
+ if (key_id == 0)
+ key_id = get_random_key_id();
+ auth_len = KEY_GetAuthLength(key_id);
+ assert(auth_len);
+ if (NTP_LVM_TO_VERSION(res->lvm) == 4)
+ auth_len = MIN(auth_len, NTP_MAX_V4_MAC_LENGTH - 4);
+
+ if (KEY_GenerateAuth(key_id, res, res_length,
+ (unsigned char *)res + res_length + 4, auth_len) != auth_len)
+ assert(0);
+ res_length += 4 + auth_len;
+ }
+
+ if (!valid_auth && authenticated) {
+ assert(auth_len);
+
+ switch (random() % 5) {
+ case 0:
+ key_id++;
+ break;
+ case 1:
+ key_id ^= 1;
+ if (KEY_GenerateAuth(key_id, res, res_length - auth_len - 4,
+ (unsigned char *)res + res_length - auth_len, auth_len) != auth_len)
+ assert(0);
+ break;
+ case 2:
+ ((unsigned char *)res)[res_length - auth_len + random() % auth_len]++;
+ break;
+ case 3:
+ res_length -= 4 + auth_len;
+ auth_len = 4 * (random() % (auth_len / 4));
+ res_length += 4 + auth_len;
+ break;
+ case 4:
+ if (NTP_LVM_TO_VERSION(res->lvm) == 4 && random() % 2 &&
+ KEY_GetAuthLength(key_id) > NTP_MAX_V4_MAC_LENGTH - 4) {
+ res_length -= 4 + auth_len;
+ auth_len += 4 + 4 * (random() %
+ ((KEY_GetAuthLength(key_id) - NTP_MAX_V4_MAC_LENGTH - 4) / 4));
+ if (KEY_GenerateAuth(key_id, res, res_length,
+ (unsigned char *)res + res_length + 4, auth_len) != auth_len)
+ assert(0);
+ res_length += 4 + auth_len;
+ } else {
+ memset((unsigned char *)res + res_length, 0, 4);
+ auth_len += 4;
+ res_length += 4;
+ }
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ assert(res_length <= sizeof (*res));
+ assert(res_length >= NTP_HEADER_LENGTH + auth_len);
+
+ if (authenticated)
+ *(uint32_t *)((unsigned char *)res + res_length - auth_len - 4) = htonl(key_id);
+}
+
+static void
+proc_response(NCR_Instance inst, int good, int valid, int updated_sync,
+ int updated_init, int save)
+{
+ NTP_Local_Address local_addr;
+ NTP_Local_Timestamp local_ts;
+ NTP_Packet *res;
+ uint32_t prev_rx_count, prev_valid_count;
+ struct timespec prev_rx_ts, prev_init_rx_ts;
+ int ret;
+
+ res = &res_buffer;
+
+ local_addr.ip_addr.family = IPADDR_UNSPEC;
+ local_addr.if_index = INVALID_IF_INDEX;
+ local_addr.sock_fd = NTP_LVM_TO_MODE(res->lvm) != MODE_SERVER ? 100 : 101;
+ zero_local_timestamp(&local_ts);
+ local_ts.ts = current_time;
+ local_ts.source = NTP_TS_KERNEL;
+
+ prev_rx_count = inst->report.total_rx_count;
+ prev_valid_count = inst->report.total_valid_count;
+ prev_rx_ts = inst->local_rx.ts;
+ prev_init_rx_ts = inst->init_local_rx.ts;
+
+ ret = NCR_ProcessRxKnown(inst, &local_addr, &local_ts, res, res_length);
+
+ if (save) {
+ TEST_CHECK(ret);
+ TEST_CHECK(inst->saved_response);
+ TEST_CHECK(inst->saved_response->timeout_id != 0);
+ TEST_CHECK(has_saved_response(inst));
+ if (random() % 2)
+ saved_response_timeout(inst);
+ else
+ transmit_timeout(inst);
+ TEST_CHECK(inst->saved_response->timeout_id == 0);
+ TEST_CHECK(!has_saved_response(inst));
+ }
+
+ if (good > 0)
+ TEST_CHECK(ret);
+ else if (!good)
+ TEST_CHECK(!ret);
+
+ TEST_CHECK(prev_rx_count + 1 == inst->report.total_rx_count);
+
+ if (valid)
+ TEST_CHECK(prev_valid_count + 1 == inst->report.total_valid_count);
+ else
+ TEST_CHECK(prev_valid_count == inst->report.total_valid_count);
+
+ if (updated_sync)
+ TEST_CHECK(UTI_CompareTimespecs(&inst->local_rx.ts, &prev_rx_ts));
+ else
+ TEST_CHECK(!UTI_CompareTimespecs(&inst->local_rx.ts, &prev_rx_ts));
+
+ if (updated_init > 0)
+ TEST_CHECK(UTI_CompareTimespecs(&inst->init_local_rx.ts, &prev_init_rx_ts));
+ else if (!updated_init)
+ TEST_CHECK(!UTI_CompareTimespecs(&inst->init_local_rx.ts, &prev_init_rx_ts));
+
+ if (valid) {
+ TEST_CHECK(UTI_IsZeroTimespec(&inst->init_local_rx.ts));
+ TEST_CHECK(UTI_IsZeroNtp64(&inst->init_remote_ntp_tx));
+ }
+}
+
+static void
+process_replay(NCR_Instance inst, NTP_Packet *packet_queue,
+ int queue_length, int updated_init)
+{
+ do {
+ res_buffer = packet_queue[random() % queue_length];
+ } while (!UTI_CompareNtp64(&res_buffer.transmit_ts, &inst->remote_ntp_tx));
+ proc_response(inst, 0, 0, 0, updated_init, 0);
+ advance_time(1e-6);
+}
+
+static void
+add_dummy_auth(NTP_AuthMode auth_mode, uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info)
+{
+ unsigned char buf[64];
+ int len, fill;
+
+ info->auth.mode = auth_mode;
+
+ switch (auth_mode) {
+ case NTP_AUTH_NONE:
+ break;
+ case NTP_AUTH_SYMMETRIC:
+ case NTP_AUTH_MSSNTP:
+ case NTP_AUTH_MSSNTP_EXT:
+ switch (auth_mode) {
+ case NTP_AUTH_SYMMETRIC:
+ len = 16 + random() % 2 * 4;
+ fill = 1 + random() % 255;
+ break;
+ case NTP_AUTH_MSSNTP:
+ len = 16;
+ fill = 0;
+ break;
+ case NTP_AUTH_MSSNTP_EXT:
+ len = 68;
+ fill = 0;
+ break;
+ default:
+ assert(0);
+ }
+
+ assert(info->length + 4 + len <= sizeof (*packet));
+
+ *(uint32_t *)((unsigned char *)packet + info->length) = htonl(key_id);
+ info->auth.mac.key_id = key_id;
+ info->length += 4;
+
+ memset((unsigned char *)packet + info->length, fill, len);
+ info->length += len;
+ break;
+ case NTP_AUTH_NTS:
+ memset(buf, 0, sizeof (buf));
+ TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_AUTH_AND_EEF, buf, sizeof (buf)));
+ break;
+ default:
+ assert(0);
+ }
+}
+
+#define PACKET_QUEUE_LENGTH 10
+
+void
+test_unit(void)
+{
+ char source_line[] = "127.0.0.1 maxdelaydevratio 1e6 noselect";
+ char conf[][100] = {
+ "allow",
+ "port 0",
+ "local",
+ "keyfile ntp_core.keys"
+ };
+ int i, j, k, interleaved, authenticated, valid, updated, has_updated, late_hwts;
+ CPS_NTP_Source source;
+ NTP_Remote_Address remote_addr;
+ NCR_Instance inst1, inst2;
+ NTP_Packet packet_queue[PACKET_QUEUE_LENGTH], packet;
+ NTP_PacketInfo info;
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+ SCH_Initialise();
+ SRC_Initialise();
+ NIO_Initialise();
+ NCR_Initialise();
+ REF_Initialise();
+ KEY_Initialise();
+ CLG_Initialise();
+
+ CNF_SetupAccessRestrictions();
+
+ CPS_ParseNTPSourceAdd(source_line, &source);
+
+ for (i = 0; i < 1000; i++) {
+ source.params.interleaved = random() % 2;
+ source.params.authkey = random() % 2 ? get_random_key_id() : INACTIVE_AUTHKEY;
+ source.params.version = random() % 4 + 1;
+
+ UTI_ZeroTimespec(&current_time);
+#if HAVE_LONG_TIME_T
+ advance_time(NTP_ERA_SPLIT);
+#endif
+ advance_time(TST_GetRandomDouble(1.0, 1e9));
+
+ TST_GetRandomAddress(&remote_addr.ip_addr, IPADDR_UNSPEC, -1);
+ remote_addr.port = 123;
+
+ inst1 = NCR_CreateInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER,
+ &source.params, NULL);
+ NCR_StartInstance(inst1);
+ has_updated = 0;
+
+ for (j = 0; j < 50; j++) {
+ DEBUG_LOG("client/peer test iteration %d/%d", i, j);
+
+ late_hwts = random() % 2;
+ authenticated = random() % 2;
+ interleaved = random() % 2 && (inst1->mode != MODE_CLIENT ||
+ inst1->tx_count < MAX_CLIENT_INTERLEAVED_TX);
+ authenticated = random() % 2;
+ valid = (!interleaved || (source.params.interleaved && has_updated)) &&
+ ((source.params.authkey == INACTIVE_AUTHKEY) == !authenticated);
+ updated = (valid || inst1->mode == MODE_ACTIVE) &&
+ ((source.params.authkey == INACTIVE_AUTHKEY) == !authenticated);
+ has_updated = has_updated || updated;
+ if (inst1->mode == MODE_CLIENT)
+ updated = 0;
+
+ DEBUG_LOG("authkey=%d version=%d interleaved=%d authenticated=%d valid=%d updated=%d has_updated=%d",
+ (int)source.params.authkey, source.params.version,
+ interleaved, authenticated, valid, updated, has_updated);
+
+ send_request(inst1, late_hwts);
+
+ send_response(interleaved, authenticated, 1, 0, 1);
+ DEBUG_LOG("response 1");
+ proc_response(inst1, 0, 0, 0, updated, 0);
+
+ if (source.params.authkey) {
+ send_response(interleaved, authenticated, 1, 1, 0);
+ DEBUG_LOG("response 2");
+ proc_response(inst1, 0, 0, 0, 0, 0);
+ }
+
+ send_response(interleaved, authenticated, 1, 1, 1);
+ DEBUG_LOG("response 3");
+ proc_response(inst1, -1, valid, valid, updated, valid && late_hwts);
+ DEBUG_LOG("response 4");
+ proc_response(inst1, 0, 0, 0, 0, 0);
+
+ advance_time(-1.0);
+
+ send_response(interleaved, authenticated, 1, 1, 1);
+ DEBUG_LOG("response 5");
+ proc_response(inst1, 0, 0, 0, updated && valid, 0);
+
+ advance_time(1.0);
+
+ send_response(interleaved, authenticated, 1, 1, 1);
+ DEBUG_LOG("response 6");
+ proc_response(inst1, 0, 0, valid && updated, updated, 0);
+ }
+
+ NCR_DestroyInstance(inst1);
+
+ inst1 = NCR_CreateInstance(&remote_addr, random() % 2 ? NTP_SERVER : NTP_PEER,
+ &source.params, NULL);
+ NCR_StartInstance(inst1);
+
+ for (j = 0; j < 20; j++) {
+ DEBUG_LOG("server test iteration %d/%d", i, j);
+
+ send_request(inst1, 0);
+ process_request(&remote_addr);
+ proc_response(inst1,
+ !source.params.interleaved || source.params.version != 4 ||
+ inst1->mode == MODE_ACTIVE || j != 2,
+ 1, 1, 0, 0);
+ advance_time(1 << inst1->local_poll);
+ }
+
+ NCR_DestroyInstance(inst1);
+
+ inst1 = NCR_CreateInstance(&remote_addr, NTP_PEER, &source.params, NULL);
+ NCR_StartInstance(inst1);
+ inst2 = NCR_CreateInstance(&remote_addr, NTP_PEER, &source.params, NULL);
+ NCR_StartInstance(inst2);
+
+ res_length = req_length = 0;
+
+ for (j = 0; j < 20; j++) {
+ DEBUG_LOG("peer replay test iteration %d/%d", i, j);
+
+ late_hwts = random() % 2;
+
+ send_request(inst1, late_hwts);
+ res_buffer = req_buffer;
+ assert(!res_length || res_length == req_length);
+ res_length = req_length;
+
+ TEST_CHECK(inst1->valid_timestamps == (j > 0));
+
+ DEBUG_LOG("response 1->2");
+ proc_response(inst2, j > source.params.interleaved, j > 0, j > 0, 1, 0);
+
+ packet_queue[(j * 2) % PACKET_QUEUE_LENGTH] = res_buffer;
+
+ for (k = 0; k < j % 4 + 1; k++) {
+ DEBUG_LOG("replay ?->1 %d", k);
+ process_replay(inst1, packet_queue, MIN(j * 2 + 1, PACKET_QUEUE_LENGTH), k ? -1 : 1);
+ DEBUG_LOG("replay ?->2 %d", k);
+ process_replay(inst2, packet_queue, MIN(j * 2 + 1, PACKET_QUEUE_LENGTH), -1);
+ }
+
+ advance_time(1 << (source.params.minpoll - 1));
+
+ send_request(inst2, 0);
+ res_buffer = req_buffer;
+ assert(res_length == req_length);
+
+ TEST_CHECK(inst2->valid_timestamps == (j > 0));
+
+ DEBUG_LOG("response 2->1");
+ proc_response(inst1, 1, 1, 1, 1, late_hwts);
+
+ packet_queue[(j * 2 + 1) % PACKET_QUEUE_LENGTH] = res_buffer;
+
+ for (k = 0; k < j % 4 + 1; k++) {
+ DEBUG_LOG("replay ?->1 %d", k);
+ process_replay(inst1, packet_queue, MIN(j * 2 + 2, PACKET_QUEUE_LENGTH), k ? -1 : 1);
+ DEBUG_LOG("replay ?->2 %d", k);
+ process_replay(inst2, packet_queue, MIN(j * 2 + 2, PACKET_QUEUE_LENGTH), -1);
+ }
+
+ advance_time(1 << (source.params.minpoll - 1));
+ }
+
+ NCR_DestroyInstance(inst1);
+ NCR_DestroyInstance(inst2);
+ }
+
+ memset(&packet, 0, sizeof (packet));
+ packet.lvm = NTP_LVM(LEAP_Normal, NTP_VERSION, MODE_CLIENT);
+
+ TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info));
+ TEST_CHECK(info.auth.mode == NTP_AUTH_NONE);
+
+ TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info));
+ add_dummy_auth(NTP_AUTH_SYMMETRIC, 100, &packet, &info);
+ memset(&info.auth, 0, sizeof (info.auth));
+ TEST_CHECK(parse_packet(&packet, info.length, &info));
+ TEST_CHECK(info.auth.mode == NTP_AUTH_SYMMETRIC);
+ TEST_CHECK(info.auth.mac.start == NTP_HEADER_LENGTH);
+ TEST_CHECK(info.auth.mac.length == info.length - NTP_HEADER_LENGTH);
+ TEST_CHECK(info.auth.mac.key_id == 100);
+
+ TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info));
+ add_dummy_auth(NTP_AUTH_NTS, 0, &packet, &info);
+ memset(&info.auth, 0, sizeof (info.auth));
+ TEST_CHECK(parse_packet(&packet, info.length, &info));
+ TEST_CHECK(info.auth.mode == NTP_AUTH_NTS);
+
+ packet.lvm = NTP_LVM(LEAP_Normal, 3, MODE_CLIENT);
+
+ TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info));
+ add_dummy_auth(NTP_AUTH_MSSNTP, 200, &packet, &info);
+ memset(&info.auth, 0, sizeof (info.auth));
+ TEST_CHECK(parse_packet(&packet, info.length, &info));
+ TEST_CHECK(info.auth.mode == NTP_AUTH_MSSNTP);
+ TEST_CHECK(info.auth.mac.start == NTP_HEADER_LENGTH);
+ TEST_CHECK(info.auth.mac.length == 20);
+ TEST_CHECK(info.auth.mac.key_id == 200);
+
+ TEST_CHECK(parse_packet(&packet, NTP_HEADER_LENGTH, &info));
+ add_dummy_auth(NTP_AUTH_MSSNTP_EXT, 300, &packet, &info);
+ memset(&info.auth, 0, sizeof (info.auth));
+ TEST_CHECK(parse_packet(&packet, info.length, &info));
+ TEST_CHECK(info.auth.mode == NTP_AUTH_MSSNTP_EXT);
+ TEST_CHECK(info.auth.mac.start == NTP_HEADER_LENGTH);
+ TEST_CHECK(info.auth.mac.length == 72);
+ TEST_CHECK(info.auth.mac.key_id == 300);
+
+ CLG_Finalise();
+ KEY_Finalise();
+ REF_Finalise();
+ NCR_Finalise();
+ NIO_Finalise();
+ SRC_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+ HSH_Finalise();
+}
+
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/ntp_core.keys b/test/unit/ntp_core.keys
new file mode 100644
index 0000000..a3e4f6c
--- /dev/null
+++ b/test/unit/ntp_core.keys
@@ -0,0 +1,8 @@
+2 MD5 HEX:38979C567358C0896F4D9D459A3C8B8478654579
+3 MD5 HEX:38979C567358C0896F4D9D459A3C8B8478654579
+4 SHA1 HEX:B71744EA01FBF01CA30D173ECDDF901952AE356A
+5 SHA1 HEX:B71744EA01FBF01CA30D173ECDDF901952AE356A
+6 SHA512 HEX:DE027482F22B201FC20863F58C74095E7906089F
+7 SHA512 HEX:DE027482F22B201FC20863F58C74095E7906089F
+8 AES128 HEX:5D5E8A31D4B459A66D445259E147CFB5
+9 AES128 HEX:5D5E8A31D4B459A66D445259E147CFB5
diff --git a/test/unit/ntp_ext.c b/test/unit/ntp_ext.c
new file mode 100644
index 0000000..c37e702
--- /dev/null
+++ b/test/unit/ntp_ext.c
@@ -0,0 +1,167 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTP
+
+#include <util.h>
+#include <logging.h>
+
+#include <ntp_ext.c>
+
+void
+test_unit(void)
+{
+ unsigned char *buffer, body[NTP_MAX_EXTENSIONS_LENGTH];
+ void *bodyp;
+ NTP_PacketInfo info;
+ NTP_Packet packet;
+ int i, j, start, length, type, type2, body_length, body_length2;
+
+ assert(sizeof (uint16_t) == 2);
+ assert(sizeof (body) == sizeof (packet.extensions));
+
+ buffer = (unsigned char *)packet.extensions;
+
+ for (i = 0; i < 10000; i++) {
+ body_length = random() % (sizeof (body) - 4 + 1) / 4 * 4;
+ start = random() % (sizeof (packet.extensions) - body_length - 4 + 1) / 4 * 4;
+ type = random() % 0x10000;
+
+ DEBUG_LOG("body_length=%d start=%d type=%d", body_length, start, type);
+ assert(body_length + start <= sizeof (packet.extensions));
+
+ UTI_GetRandomBytes(body, body_length);
+
+ TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start,
+ type, body, body_length + 4, &length));
+ TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start + 4,
+ type, body, body_length, &length));
+ TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start,
+ type, body, body_length - 1, &length));
+ TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start,
+ type, body, body_length - 2, &length));
+ TEST_CHECK(!NEF_SetField(buffer, body_length + start + 4, start,
+ type, body, body_length - 3, &length));
+ TEST_CHECK(!NEF_SetField(buffer, body_length + start + 3, start,
+ type, body, body_length, &length));
+ TEST_CHECK(!NEF_SetField(buffer, body_length + start + 5, start + 1,
+ type, body, body_length, &length));
+
+ TEST_CHECK(NEF_SetField(buffer, body_length + start + 4, start,
+ type, body, body_length, &length));
+ TEST_CHECK(length == body_length + 4);
+ TEST_CHECK(((uint16_t *)buffer)[start / 2] == htons(type));
+ TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] == htons(length));
+ TEST_CHECK(memcmp(buffer + start + 4, body, body_length) == 0);
+
+ memset(&packet, 0, sizeof (packet));
+ packet.lvm = NTP_LVM(0, 4, MODE_CLIENT);
+ memset(&info, 0, sizeof (info));
+
+ info.version = 3;
+ info.length = NTP_HEADER_LENGTH;
+ TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp));
+
+ info.version = 4;
+ info.length = NTP_HEADER_LENGTH - 4;
+ TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp));
+
+ info.length = sizeof (packet) - body_length;
+ TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp));
+
+ info.length = NTP_HEADER_LENGTH + start;
+
+ if (body_length < 12) {
+ TEST_CHECK(!NEF_AddBlankField(&packet, &info, type, body_length, &bodyp));
+ continue;
+ }
+
+ TEST_CHECK(NEF_AddBlankField(&packet, &info, type, body_length, &bodyp));
+ TEST_CHECK(info.length == NTP_HEADER_LENGTH + start + body_length + 4);
+ TEST_CHECK(((uint16_t *)buffer)[start / 2] == htons(type));
+ TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] == htons(length));
+ TEST_CHECK(bodyp == buffer + start + 4);
+ TEST_CHECK(info.ext_fields == 1);
+
+ memset(buffer, 0, sizeof (packet.extensions));
+ info.length = NTP_HEADER_LENGTH + start;
+ info.ext_fields = 0;
+
+ TEST_CHECK(NEF_AddField(&packet, &info, type, body, body_length));
+ TEST_CHECK(info.length == NTP_HEADER_LENGTH + start + body_length + 4);
+ TEST_CHECK(((uint16_t *)buffer)[start / 2] == htons(type));
+ TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] == htons(length));
+ TEST_CHECK(memcmp(buffer + start + 4, body, body_length) == 0);
+ TEST_CHECK(info.ext_fields == 1);
+
+ for (j = 1; j <= 4; j++) {
+ TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] = htons(length + j));
+ TEST_CHECK(!NEF_ParseSingleField(buffer, start + body_length + 4, start,
+ &length, &type2, &bodyp, &body_length2));
+ }
+
+ TEST_CHECK(((uint16_t *)buffer)[start / 2 + 1] = htons(length));
+
+ TEST_CHECK(NEF_ParseSingleField(buffer, sizeof (packet.extensions), start,
+ &length, &type2, &bodyp, &body_length2));
+ TEST_CHECK(length == body_length + 4);
+ TEST_CHECK(type2 == type);
+ TEST_CHECK(bodyp == buffer + start + 4);
+ TEST_CHECK(body_length2 == body_length);
+
+ TEST_CHECK(!NEF_ParseField(&packet, sizeof (packet) + 4,
+ NTP_HEADER_LENGTH + start,
+ &length, &type2, &bodyp, &body_length2));
+
+ if (body_length < 24) {
+ TEST_CHECK(!NEF_ParseField(&packet, NTP_HEADER_LENGTH + start + length,
+ NTP_HEADER_LENGTH + start,
+ &length, &type2, &bodyp, &body_length2));
+ if (sizeof (packet.extensions) - start <= 24) {
+ TEST_CHECK(!NEF_ParseField(&packet, sizeof (packet), NTP_HEADER_LENGTH + start,
+ &length, &type2, &bodyp, &body_length2));
+ continue;
+ } else {
+ TEST_CHECK(NEF_ParseField(&packet, sizeof (packet), NTP_HEADER_LENGTH + start,
+ &length, &type2, &bodyp, &body_length2));
+ }
+ } else {
+ TEST_CHECK(NEF_ParseField(&packet, NTP_HEADER_LENGTH + start + length,
+ NTP_HEADER_LENGTH + start,
+ &length, &type2, &bodyp, &body_length2));
+ }
+ TEST_CHECK(length == body_length + 4);
+ TEST_CHECK(type2 == type);
+ TEST_CHECK(bodyp == buffer + start + 4);
+ TEST_CHECK(body_length2 == body_length);
+
+ }
+}
+
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/ntp_sources.c b/test/unit/ntp_sources.c
new file mode 100644
index 0000000..e3d7c4d
--- /dev/null
+++ b/test/unit/ntp_sources.c
@@ -0,0 +1,378 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016, 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTP
+
+#include <conf.h>
+#include <cmdparse.h>
+#include <nameserv_async.h>
+#include <ntp_core.h>
+#include <ntp_io.h>
+#include <sched.h>
+
+static char *requested_name = NULL;
+static DNS_NameResolveHandler resolve_handler = NULL;
+static void *resolve_handler_arg = NULL;
+
+#define DNS_Name2IPAddressAsync(name, handler, arg) \
+ requested_name = (name), \
+ resolve_handler = (handler), \
+ resolve_handler_arg = (arg)
+#define NCR_ChangeRemoteAddress(inst, remote_addr, ntp_only) \
+ change_remote_address(inst, remote_addr, ntp_only)
+#define NCR_ProcessRxKnown(remote_addr, local_addr, ts, msg, len) (random() % 2)
+#define NIO_IsServerConnectable(addr) (random() % 2)
+#define SCH_GetLastEventMonoTime() get_mono_time()
+
+static void change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr,
+ int ntp_only);
+static double get_mono_time(void);
+
+#include <ntp_sources.c>
+
+#undef NCR_ChangeRemoteAddress
+
+static void
+resolve_random_address(DNS_Status status, int rand_bits)
+{
+ IPAddr ip_addrs[DNS_MAX_ADDRESSES];
+ int i, n_addrs;
+
+ TEST_CHECK(requested_name);
+ requested_name = NULL;
+
+ if (status == DNS_Success) {
+ n_addrs = random() % DNS_MAX_ADDRESSES + 1;
+ for (i = 0; i < n_addrs; i++)
+ TST_GetRandomAddress(&ip_addrs[i], IPADDR_UNSPEC, rand_bits);
+ } else {
+ n_addrs = 0;
+ }
+
+ (resolve_handler)(status, n_addrs, ip_addrs, resolve_handler_arg);
+}
+
+static int
+update_random_address(NTP_Remote_Address *addr, int rand_bits)
+{
+ NTP_Remote_Address new_addr;
+ NSR_Status status;
+
+ TST_GetRandomAddress(&new_addr.ip_addr, IPADDR_UNSPEC, rand_bits);
+ new_addr.port = random() % 1024;
+
+ status = NSR_UpdateSourceNtpAddress(addr, &new_addr);
+ if (status == NSR_InvalidAF) {
+ TEST_CHECK(!UTI_IsIPReal(&addr->ip_addr));
+ } else {
+ TEST_CHECK(status == NSR_Success || status == NSR_AlreadyInUse);
+ }
+
+ TEST_CHECK(strlen(NSR_StatusToString(status)) > 0);
+
+ return status == NSR_Success;
+}
+
+static void
+change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, int ntp_only)
+{
+ int update = !ntp_only && random() % 4 == 0, update_pos = random() % 2, r = 0;
+
+ TEST_CHECK(record_lock);
+
+ if (update && update_pos == 0)
+ r = update_random_address(random() % 2 ? remote_addr : NCR_GetRemoteAddress(inst), 4);
+
+ NCR_ChangeRemoteAddress(inst, remote_addr, ntp_only);
+
+ if (update && update_pos == 1)
+ r = update_random_address(random() % 2 ? remote_addr : NCR_GetRemoteAddress(inst), 4);
+
+ if (r)
+ TEST_CHECK(UTI_IsIPReal(&saved_address_update.old_address.ip_addr));
+}
+
+static double get_mono_time(void) {
+ static double t = 0.0;
+
+ if (random() % 2)
+ t += TST_GetRandomDouble(0.0, 100.0);
+
+ return t;
+}
+
+void
+test_unit(void)
+{
+ char source_line[] = "127.0.0.1 offline", conf[] = "port 0", name[64];
+ int i, j, k, slot, found, pool, prev_n;
+ uint32_t hash = 0, conf_id;
+ NTP_Remote_Address addrs[256], addr;
+ NTP_Local_Address local_addr;
+ NTP_Local_Timestamp local_ts;
+ struct UnresolvedSource *us;
+ RPT_ActivityReport report;
+ CPS_NTP_Source source;
+ NSR_Status status;
+ NTP_Packet msg;
+
+ CNF_Initialise(0, 0);
+ CNF_ParseLine(NULL, 1, conf);
+
+ PRV_Initialise();
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+ SCH_Initialise();
+ SRC_Initialise();
+ NIO_Initialise();
+ NCR_Initialise();
+ REF_Initialise();
+ NSR_Initialise();
+
+ CPS_ParseNTPSourceAdd(source_line, &source);
+
+ TEST_CHECK(n_sources == 0);
+
+ for (i = 0; i < 6; i++) {
+ TEST_CHECK(ARR_GetSize(records) == 1);
+
+ DEBUG_LOG("collision mod %u", 1U << i);
+
+ for (j = 0; j < sizeof (addrs) / sizeof (addrs[0]); j++) {
+ while (1) {
+ do {
+ TST_GetRandomAddress(&addrs[j].ip_addr, IPADDR_UNSPEC, -1);
+ } while (UTI_IPToHash(&addrs[j].ip_addr) % (1U << i) != hash % (1U << i));
+
+ for (k = 0; k < j; k++)
+ if (UTI_CompareIPs(&addrs[k].ip_addr, &addrs[j].ip_addr, NULL) == 0)
+ break;
+ if (k == j)
+ break;
+ }
+
+ addrs[j].port = random() % 1024;
+
+ if (!j)
+ hash = UTI_IPToHash(&addrs[j].ip_addr);
+
+ DEBUG_LOG("adding source %s hash %"PRIu32, UTI_IPToString(&addrs[j].ip_addr),
+ UTI_IPToHash(&addrs[j].ip_addr) % (1U << i));
+
+ status = NSR_AddSource(&addrs[j], random() % 2 ? NTP_SERVER : NTP_PEER,
+ &source.params, NULL);
+ TEST_CHECK(status == NSR_Success);
+ TEST_CHECK(n_sources == j + 1);
+
+ TEST_CHECK(strcmp(NSR_GetName(&addrs[j].ip_addr), UTI_IPToString(&addrs[j].ip_addr)) == 0);
+
+ for (k = 0; k <= j; k++) {
+ addr = addrs[k];
+ found = find_slot2(&addr, &slot);
+ TEST_CHECK(found == 2);
+ TEST_CHECK(!UTI_CompareIPs(&get_record(slot)->remote_addr->ip_addr,
+ &addr.ip_addr, NULL));
+ addr.port++;
+ found = find_slot2(&addr, &slot);
+ TEST_CHECK(found == 1);
+ TEST_CHECK(!UTI_CompareIPs(&get_record(slot)->remote_addr->ip_addr,
+ &addr.ip_addr, NULL));
+ }
+
+ status = NSR_AddSource(&addrs[j], NTP_SERVER, &source.params, &conf_id);
+ TEST_CHECK(status == NSR_AlreadyInUse);
+ }
+
+ for (j = 0; j < sizeof (addrs) / sizeof (addrs[0]); j++) {
+ DEBUG_LOG("removing source %s", UTI_IPToString(&addrs[j].ip_addr));
+ NSR_RemoveSource(&addrs[j].ip_addr);
+
+ for (k = 0; k < sizeof (addrs) / sizeof (addrs[0]); k++) {
+ found = find_slot2(&addrs[k], &slot);
+ TEST_CHECK(found == (k <= j ? 0 : 2));
+ }
+ }
+ }
+
+ TEST_CHECK(n_sources == 0);
+
+ status = NSR_AddSourceByName("a b", 0, 0, 0, &source.params, &conf_id);
+ TEST_CHECK(status == NSR_InvalidName);
+
+ local_addr.ip_addr.family = IPADDR_INET4;
+ local_addr.ip_addr.addr.in4 = 0;
+ local_addr.if_index = -1;
+ local_addr.sock_fd = 0;
+ memset(&local_ts, 0, sizeof (local_ts));
+
+ for (i = 0; i < 500; i++) {
+ for (j = 0; j < 20; j++) {
+ snprintf(name, sizeof (name), "ntp%d.example.net", (int)(random() % 10));
+ pool = random() % 2;
+ prev_n = n_sources;
+
+ DEBUG_LOG("%d/%d adding source %s pool=%d", i, j, name, pool);
+ status = NSR_AddSourceByName(name, 0, pool, random() % 2 ? NTP_SERVER : NTP_PEER,
+ &source.params, &conf_id);
+ TEST_CHECK(status == NSR_UnresolvedName);
+
+ TEST_CHECK(n_sources == prev_n + (pool ? source.params.max_sources * 2 : 1));
+ TEST_CHECK(unresolved_sources);
+
+ for (us = unresolved_sources; us->next; us = us->next)
+ ;
+ TEST_CHECK(strcmp(us->name, name) == 0);
+ if (pool) {
+ TEST_CHECK(us->address.ip_addr.family == IPADDR_UNSPEC && us->pool_id >= 0);
+ } else {
+ TEST_CHECK(strcmp(NSR_GetName(&us->address.ip_addr), name) == 0);
+ TEST_CHECK(find_slot2(&us->address, &slot) == 2);
+ }
+
+ if (random() % 2) {
+ if (!resolving_id || random() % 2) {
+ NSR_ResolveSources();
+ } else {
+ SCH_RemoveTimeout(resolving_id);
+ resolve_sources_timeout(NULL);
+ TEST_CHECK(resolving_id == 0);
+ TEST_CHECK(requested_name);
+ }
+
+ TEST_CHECK(!!unresolved_sources == (resolving_id != 0) || requested_name);
+ }
+
+ while (requested_name && random() % 2) {
+ TEST_CHECK(resolving_source);
+ TEST_CHECK(strcmp(requested_name, resolving_source->name) == 0);
+ TEST_CHECK(!record_lock);
+
+ switch (random() % 3) {
+ case 0:
+ resolve_random_address(DNS_Success, 4);
+ break;
+ case 1:
+ resolve_random_address(DNS_TryAgain, 0);
+ break;
+ case 2:
+ resolve_random_address(DNS_Failure, 0);
+ break;
+ }
+ }
+
+ while (random() % 8 > 0) {
+ slot = random() % ARR_GetSize(records);
+ if (!get_record(slot)->remote_addr)
+ continue;
+
+ switch (random() % 5) {
+ case 0:
+ msg.lvm = NTP_LVM(0, NTP_VERSION, random() % 2 ? MODE_CLIENT : MODE_SERVER);
+ NSR_ProcessTx(get_record(slot)->remote_addr, &local_addr,
+ &local_ts, &msg, 0);
+ break;
+ case 1:
+ msg.lvm = NTP_LVM(0, NTP_VERSION, random() % 2 ? MODE_CLIENT : MODE_SERVER);
+ NSR_ProcessRx(get_record(slot)->remote_addr, &local_addr,
+ &local_ts, &msg, 0);
+ break;
+ case 2:
+ NSR_HandleBadSource(&get_record(slot)->remote_addr->ip_addr);
+ break;
+ case 3:
+ NSR_SetConnectivity(NULL, &get_record(slot)->remote_addr->ip_addr, SRC_OFFLINE);
+ break;
+ case 4:
+ update_random_address(get_record(slot)->remote_addr, 4);
+ TEST_CHECK(!UTI_IsIPReal(&saved_address_update.old_address.ip_addr));
+ break;
+ }
+
+ TEST_CHECK(!record_lock);
+ }
+
+ NSR_GetActivityReport(&report);
+ TEST_CHECK(report.online == 0);
+ TEST_CHECK(report.offline >= 0);
+ TEST_CHECK(report.burst_online == 0);
+ TEST_CHECK(report.burst_offline == 0);
+ TEST_CHECK(report.unresolved >= 0);
+
+ if (random() % 4 == 0) {
+ NSR_RemoveSourcesById(conf_id);
+ TEST_CHECK(n_sources <= prev_n);
+ } else if (random() % 8 == 0) {
+ NSR_RefreshAddresses();
+ TEST_CHECK(unresolved_sources);
+ }
+ }
+
+ NSR_RemoveAllSources();
+ TEST_CHECK(n_sources == 0);
+
+ for (j = 0; j < ARR_GetSize(pools); j++) {
+ TEST_CHECK(get_pool(j)->sources == 0);
+ TEST_CHECK(get_pool(j)->unresolved_sources == 0);
+ TEST_CHECK(get_pool(j)->confirmed_sources == 0);
+ TEST_CHECK(get_pool(j)->max_sources == 0);
+ }
+
+ while (requested_name) {
+ TEST_CHECK(resolving_source);
+ resolve_random_address(random() % 2 ? DNS_Success : DNS_TryAgain, 4);
+ }
+
+ if (unresolved_sources && resolving_id == 0)
+ NSR_ResolveSources();
+
+ TEST_CHECK(!!unresolved_sources == (resolving_id != 0));
+
+ if (resolving_id) {
+ SCH_RemoveTimeout(resolving_id);
+ resolve_sources_timeout(NULL);
+ }
+
+ TEST_CHECK(resolving_id == 0);
+ TEST_CHECK(!requested_name);
+ TEST_CHECK(!unresolved_sources);
+ }
+
+ NSR_Finalise();
+ REF_Finalise();
+ NCR_Finalise();
+ NIO_Finalise();
+ SRC_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ PRV_Finalise();
+ CNF_Finalise();
+ HSH_Finalise();
+}
+
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/nts_ke.crt b/test/unit/nts_ke.crt
new file mode 100644
index 0000000..8165287
--- /dev/null
+++ b/test/unit/nts_ke.crt
@@ -0,0 +1,8 @@
+-----BEGIN CERTIFICATE-----
+MIIBDjCBwaADAgECAgEBMAUGAytlcDAPMQ0wCwYDVQQDEwR0ZXN0MCAXDTcwMDEw
+MTAwMDAwMFoYDzIxMDAwMTAyMDAwMDAwWjAPMQ0wCwYDVQQDEwR0ZXN0MCowBQYD
+K2VwAyEA3oh/FZeOxRYvJVLfCDEwGI6Oe23gTgLHx8a87tvwgfyjQDA+MAwGA1Ud
+EwEB/wQCMAAwDwYDVR0PAQH/BAUDAweAADAdBgNVHQ4EFgQUYwAqF9q3jxUk68m1
+cuz8DueOHeMwBQYDK2VwA0EAne0dCRXb0dW8bn2v3RhVTqeTJWXfl74x8MTQMTM7
+/uGTqoYOA0YJffypd+p27pvx2BEoNQWRYM6pqBg55KbwDw==
+-----END CERTIFICATE-----
diff --git a/test/unit/nts_ke.key b/test/unit/nts_ke.key
new file mode 100644
index 0000000..6414c64
--- /dev/null
+++ b/test/unit/nts_ke.key
@@ -0,0 +1,25 @@
+Public Key Info:
+ Public Key Algorithm: EdDSA (Ed25519)
+ Key Security Level: High (256 bits)
+
+curve: Ed25519
+private key:
+ 01:d9:75:42:7c:52:cc:29:9e:90:01:f3:da:26:f6:d7
+ ad:af:a5:2a:82:36:1d:86:c6:57:a7:b4:99:9b:6c:6d
+
+
+x:
+ de:88:7f:15:97:8e:c5:16:2f:25:52:df:08:31:30:18
+ 8e:8e:7b:6d:e0:4e:02:c7:c7:c6:bc:ee:db:f0:81:fc
+
+
+
+Public Key PIN:
+ pin-sha256:C4LBJP2cRxvLcZ6pjowcOEQhcW3ZPMVTpLgRGsBDeMw=
+Public Key ID:
+ sha256:0b82c124fd9c471bcb719ea98e8c1c384421716dd93cc553a4b8111ac04378cc
+ sha1:63002a17dab78f1524ebc9b572ecfc0ee78e1de3
+
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIAHZdUJ8UswpnpAB89om9tetr6UqgjYdhsZXp7SZm2xt
+-----END PRIVATE KEY-----
diff --git a/test/unit/nts_ke_client.c b/test/unit/nts_ke_client.c
new file mode 100644
index 0000000..3100d1d
--- /dev/null
+++ b/test/unit/nts_ke_client.c
@@ -0,0 +1,147 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTS
+
+#include <nts_ke_client.c>
+#include <local.h>
+
+static void
+prepare_response(NKSN_Instance session, int valid)
+{
+ uint16_t data[16];
+ int i, index, length;
+
+ if (valid)
+ index = -1;
+ else
+ index = random() % 10;
+ DEBUG_LOG("index=%d", index);
+
+ NKSN_BeginMessage(session);
+
+ memset(data, 0, sizeof (data));
+ length = 2;
+ assert(sizeof (data[0]) == 2);
+
+ if (index == 0) {
+ data[0] = htons(random() % 100);
+ TEST_CHECK(NKSN_AddRecord(session, 1, random() % 2 ? NKE_RECORD_ERROR : NKE_RECORD_WARNING,
+ data, length));
+ } else if (index == 1) {
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_ERROR + 1000, data, length));
+ }
+
+ if (index != 2) {
+ if (index == 3)
+ data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4 + random() % 10 + 1);
+ else
+ data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4);
+ if (index == 4)
+ length = 3 + random() % 10;
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length));
+ }
+
+ if (index != 5) {
+ if (index == 6)
+ do {
+ data[0] = htons(random() % 100);
+ } while (SIV_GetKeyLength(ntohs(data[0])) > 0);
+ else
+ data[0] = htons(random() % 2 && SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ?
+ AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256);
+ if (index == 7)
+ length = 3 + random() % 10;
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length));
+ }
+
+ if (random() % 2) {
+ const char server[] = "127.0.0.1";
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_SERVER_NEGOTIATION,
+ server, sizeof (server) - 1));
+ }
+
+ if (random() % 2) {
+ data[0] = htons(123);
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length));
+ }
+
+ if (random() % 2) {
+ length = random() % (sizeof (data) + 1);
+ TEST_CHECK(NKSN_AddRecord(session, 0, 1000 + random() % 1000, data, length));
+ }
+
+ if (index != 8) {
+ for (i = 0; i < NKE_MAX_COOKIES; i++) {
+ length = (random() % sizeof (data) + 1) / 4 * 4;
+ if (index == 9)
+ length += (length < sizeof (data) ? 1 : -1) * (random() % 3 + 1);
+ TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, data, length));
+ }
+ }
+
+ TEST_CHECK(NKSN_EndMessage(session));
+}
+
+void
+test_unit(void)
+{
+ NKC_Instance inst;
+ IPSockAddr addr;
+ int i, r, valid;
+
+ char conf[][100] = {
+ "nosystemcert",
+ };
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ LCL_Initialise();
+
+ SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr);
+ addr.port = 0;
+
+ inst = NKC_CreateInstance(&addr, "test", 0);
+ TEST_CHECK(inst);
+
+ for (i = 0; i < 10000; i++) {
+ valid = random() % 2;
+ prepare_response(inst->session, valid);
+ r = process_response(inst);
+ TEST_CHECK(r == valid);
+ }
+
+ NKC_DestroyInstance(inst);
+
+ LCL_Finalise();
+ CNF_Finalise();
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/nts_ke_server.c b/test/unit/nts_ke_server.c
new file mode 100644
index 0000000..3d2f295
--- /dev/null
+++ b/test/unit/nts_ke_server.c
@@ -0,0 +1,235 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTS
+
+#include <local.h>
+#include <nts_ke_session.h>
+#include <util.h>
+
+#define NKSN_GetKeys get_keys
+
+static int
+get_keys(NKSN_Instance session, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c)
+{
+ c2s->length = SIV_GetKeyLength(siv);
+ UTI_GetRandomBytes(c2s->key, c2s->length);
+ s2c->length = SIV_GetKeyLength(siv);
+ UTI_GetRandomBytes(s2c->key, s2c->length);
+ return 1;
+}
+
+#include <nts_ke_server.c>
+
+static void
+prepare_request(NKSN_Instance session, int valid)
+{
+ uint16_t data[16];
+ int index, length;
+
+ if (valid)
+ index = -1;
+ else
+ index = random() % 9;
+ DEBUG_LOG("index=%d", index);
+
+ NKSN_BeginMessage(session);
+
+ memset(data, 0, sizeof (data));
+ length = 2;
+ assert(sizeof (data[0]) == 2);
+
+ if (index != 0) {
+ memset(data, NKE_NEXT_PROTOCOL_NTPV4 + 1, sizeof (data));
+ data[0] = htons(NKE_NEXT_PROTOCOL_NTPV4);
+ if (index == 1)
+ length = 0;
+ else if (index == 2)
+ length = 3 + random() % 15 * 2;
+ else
+ length = 2 + random() % 16 * 2;
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length));
+ }
+
+ if (index == 3)
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, length));
+
+ if (index != 4) {
+ data[0] = htons(random() % 2 && SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ?
+ AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256);
+ if (index == 5)
+ length = 0;
+ else if (index == 6)
+ length = 3 + random() % 15 * 2;
+ else
+ length = 2 + random() % 16 * 2;
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length));
+ }
+
+ if (index == 7)
+ TEST_CHECK(NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length));
+
+ if (index == 8) {
+ length = random() % (sizeof (data) + 1);
+ TEST_CHECK(NKSN_AddRecord(session, 1, 1000 + random() % 1000, data, length));
+ }
+
+ if (random() % 2) {
+ const char server[] = "127.0.0.1";
+ TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_SERVER_NEGOTIATION,
+ server, sizeof (server) - 1));
+ }
+
+ if (random() % 2) {
+ data[0] = htons(123);
+ TEST_CHECK(NKSN_AddRecord(session, 0, NKE_RECORD_NTPV4_PORT_NEGOTIATION, data, length));
+ }
+
+ if (random() % 2) {
+ length = random() % (sizeof (data) + 1);
+ TEST_CHECK(NKSN_AddRecord(session, 0, 1000 + random() % 1000, data, length));
+ }
+
+ TEST_CHECK(NKSN_EndMessage(session));
+}
+
+static void
+process_response(NKSN_Instance session, int valid)
+{
+ int records, errors, critical, type, length;
+
+ for (records = errors = 0; ; records++) {
+ if (!NKSN_GetRecord(session, &critical, &type, &length, NULL, 0))
+ break;
+ if (type == NKE_RECORD_ERROR)
+ errors++;
+ }
+
+ if (valid) {
+ TEST_CHECK(records >= 2);
+ } else {
+ TEST_CHECK(records == 1);
+ TEST_CHECK(errors == 1);
+ }
+}
+
+void
+test_unit(void)
+{
+ NKSN_Instance session;
+ NKE_Context context, context2;
+ NKE_Cookie cookie;
+ int i, j, valid, l;
+ uint32_t sum, sum2;
+
+ char conf[][100] = {
+ "ntsdumpdir .",
+ "ntsport 0",
+ "ntsprocesses 0",
+ "ntsserverkey nts_ke.key",
+ "ntsservercert nts_ke.crt",
+ };
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+ SCH_Initialise();
+
+ unlink("ntskeys");
+ NKS_PreInitialise(0, 0, 0);
+ NKS_Initialise();
+
+ session = NKSN_CreateInstance(1, NULL, handle_message, NULL);
+
+ for (i = 0; i < 10000; i++) {
+ valid = random() % 2;
+ prepare_request(session, valid);
+ TEST_CHECK(process_request(session));
+ process_response(session, valid);
+ }
+
+
+ for (i = 0; i < 10000; i++) {
+ context.algorithm = AEAD_AES_SIV_CMAC_256;
+ get_keys(session, context.algorithm, &context.c2s, &context.s2c);
+ memset(&cookie, 0, sizeof (cookie));
+ TEST_CHECK(NKS_GenerateCookie(&context, &cookie));
+ TEST_CHECK(NKS_DecodeCookie(&cookie, &context2));
+ TEST_CHECK(context.algorithm == context2.algorithm);
+ TEST_CHECK(context.c2s.length == context2.c2s.length);
+ TEST_CHECK(context.s2c.length == context2.s2c.length);
+ TEST_CHECK(memcmp(context.c2s.key, context2.c2s.key, context.c2s.length) == 0);
+ TEST_CHECK(memcmp(context.s2c.key, context2.s2c.key, context.s2c.length) == 0);
+
+ if (random() % 4) {
+ cookie.cookie[random() % (cookie.length)]++;
+ } else if (random() % 4) {
+ generate_key(current_server_key);
+ } else {
+ l = cookie.length;
+ while (l == cookie.length)
+ cookie.length = random() % (sizeof (cookie.cookie) + 1);
+ }
+ TEST_CHECK(!NKS_DecodeCookie(&cookie, &context2));
+ }
+
+ unlink("ntskeys");
+ save_keys();
+
+ for (i = 0, sum = 0; i < MAX_SERVER_KEYS; i++) {
+ sum += server_keys[i].id;
+ for (j = 0; j < sizeof (server_keys[i].key); j++)
+ sum += server_keys[i].key[j];
+ generate_key(i);
+ }
+
+ load_keys();
+ TEST_CHECK(unlink("ntskeys") == 0);
+
+ for (i = 0, sum2 = 0; i < MAX_SERVER_KEYS; i++) {
+ sum2 += server_keys[i].id;
+ for (j = 0; j < sizeof (server_keys[i].key); j++)
+ sum2 += server_keys[i].key[j];
+ }
+
+ TEST_CHECK(sum == sum2);
+
+ NKSN_DestroyInstance(session);
+
+ NKS_Finalise();
+ TEST_CHECK(unlink("ntskeys") == 0);
+
+ SCH_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/nts_ke_session.c b/test/unit/nts_ke_session.c
new file mode 100644
index 0000000..d0e72c7
--- /dev/null
+++ b/test/unit/nts_ke_session.c
@@ -0,0 +1,224 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTS
+
+#include <nts_ke_session.c>
+
+#include <local.h>
+#include <socket.h>
+#include <sched.h>
+
+static NKSN_Instance client, server;
+static unsigned char record[NKE_MAX_MESSAGE_LENGTH];
+static int record_length, critical, type_start, records;
+static int request_received;
+static int response_received;
+
+static void
+send_message(NKSN_Instance inst)
+{
+ int i;
+
+ record_length = random() % (NKE_MAX_MESSAGE_LENGTH - 4 + 1);
+ for (i = 0; i < record_length; i++)
+ record[i] = random() % 256;
+ critical = random() % 2;
+ type_start = random() % 30000 + 1;
+ assert(sizeof (struct RecordHeader) == 4);
+ records = random() % ((NKE_MAX_MESSAGE_LENGTH - 4) / (4 + record_length) + 1);
+
+ DEBUG_LOG("critical=%d type_start=%d records=%d*%d",
+ critical, type_start, records, record_length);
+
+ NKSN_BeginMessage(inst);
+
+ TEST_CHECK(check_message_format(&inst->message, 0));
+ TEST_CHECK(!check_message_format(&inst->message, 1));
+
+ TEST_CHECK(!NKSN_AddRecord(inst, 0, 1, record, NKE_MAX_MESSAGE_LENGTH - 4 + 1));
+
+ TEST_CHECK(check_message_format(&inst->message, 0));
+ TEST_CHECK(!check_message_format(&inst->message, 1));
+
+ for (i = 0; i < records; i++) {
+ TEST_CHECK(NKSN_AddRecord(inst, critical, type_start + i, record, record_length));
+ TEST_CHECK(!NKSN_AddRecord(inst, 0, 1, &record,
+ NKE_MAX_MESSAGE_LENGTH - inst->message.length - 4 + 1));
+
+ TEST_CHECK(check_message_format(&inst->message, 0));
+ TEST_CHECK(!check_message_format(&inst->message, 1));
+ }
+
+ TEST_CHECK(NKSN_EndMessage(inst));
+
+ TEST_CHECK(check_message_format(&inst->message, 0));
+ TEST_CHECK(check_message_format(&inst->message, 1));
+}
+
+static void
+verify_message(NKSN_Instance inst)
+{
+ unsigned char buffer[NKE_MAX_MESSAGE_LENGTH];
+ int i, c, t, length, buffer_length, msg_length, prev_parsed;
+ NKE_Key c2s, s2c;
+
+ for (i = 0; i < records; i++) {
+ memset(buffer, 0, sizeof (buffer));
+ buffer_length = random() % (record_length + 1);
+ assert(buffer_length <= sizeof (buffer));
+
+ prev_parsed = inst->message.parsed;
+ msg_length = inst->message.length;
+
+ TEST_CHECK(NKSN_GetRecord(inst, &c, &t, &length, buffer, buffer_length));
+ TEST_CHECK(c == critical);
+ TEST_CHECK(t == type_start + i);
+ TEST_CHECK(length == record_length);
+ TEST_CHECK(memcmp(record, buffer, buffer_length) == 0);
+ if (buffer_length < record_length)
+ TEST_CHECK(buffer[buffer_length] == 0);
+
+ inst->message.length = inst->message.parsed - 1;
+ inst->message.parsed = prev_parsed;
+ TEST_CHECK(!get_record(&inst->message, NULL, NULL, NULL, buffer, buffer_length));
+ TEST_CHECK(inst->message.parsed == prev_parsed);
+ inst->message.length = msg_length;
+ if (msg_length < 0x8000) {
+ inst->message.data[prev_parsed + 2] ^= 0x80;
+ TEST_CHECK(!get_record(&inst->message, NULL, NULL, NULL, buffer, buffer_length));
+ TEST_CHECK(inst->message.parsed == prev_parsed);
+ inst->message.data[prev_parsed + 2] ^= 0x80;
+ }
+ TEST_CHECK(get_record(&inst->message, NULL, NULL, NULL, buffer, buffer_length));
+ TEST_CHECK(inst->message.parsed > prev_parsed);
+ }
+
+ TEST_CHECK(!NKSN_GetRecord(inst, &critical, &t, &length, buffer, sizeof (buffer)));
+
+ TEST_CHECK(NKSN_GetKeys(inst, AEAD_AES_SIV_CMAC_256, &c2s, &s2c));
+ TEST_CHECK(c2s.length == SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256));
+ TEST_CHECK(s2c.length == SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256));
+}
+
+static int
+handle_request(void *arg)
+{
+ NKSN_Instance server = arg;
+
+ verify_message(server);
+
+ request_received = 1;
+
+ send_message(server);
+
+ return 1;
+}
+
+static int
+handle_response(void *arg)
+{
+ NKSN_Instance client = arg;
+
+ response_received = 1;
+
+ verify_message(client);
+
+ return 1;
+}
+
+static void
+check_finished(void *arg)
+{
+ DEBUG_LOG("checking for stopped sessions");
+ if (!NKSN_IsStopped(server) || !NKSN_IsStopped(client)) {
+ SCH_AddTimeoutByDelay(0.001, check_finished, NULL);
+ return;
+ }
+
+ SCH_QuitProgram();
+}
+
+void
+test_unit(void)
+{
+ NKSN_Credentials client_cred, server_cred;
+ const char *cert, *key;
+ int sock_fds[2], i;
+ uint32_t cert_id;
+
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+
+ cert = "nts_ke.crt";
+ key = "nts_ke.key";
+ cert_id = 0;
+
+ for (i = 0; i < 50; i++) {
+ SCH_Initialise();
+
+ server = NKSN_CreateInstance(1, NULL, handle_request, NULL);
+ client = NKSN_CreateInstance(0, "test", handle_response, NULL);
+
+ server_cred = NKSN_CreateServerCertCredentials(&cert, &key, 1);
+ client_cred = NKSN_CreateClientCertCredentials(&cert, &cert_id, 1, 0);
+
+ TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds) == 0);
+ TEST_CHECK(fcntl(sock_fds[0], F_SETFL, O_NONBLOCK) == 0);
+ TEST_CHECK(fcntl(sock_fds[1], F_SETFL, O_NONBLOCK) == 0);
+
+ TEST_CHECK(NKSN_StartSession(server, sock_fds[0], "client", server_cred, 4.0));
+ TEST_CHECK(NKSN_StartSession(client, sock_fds[1], "server", client_cred, 4.0));
+
+ send_message(client);
+
+ request_received = response_received = 0;
+
+ check_finished(NULL);
+
+ SCH_MainLoop();
+
+ TEST_CHECK(NKSN_IsStopped(server));
+ TEST_CHECK(NKSN_IsStopped(client));
+
+ TEST_CHECK(request_received);
+ TEST_CHECK(response_received);
+
+ NKSN_DestroyInstance(server);
+ NKSN_DestroyInstance(client);
+
+ NKSN_DestroyCertCredentials(server_cred);
+ NKSN_DestroyCertCredentials(client_cred);
+
+ SCH_Finalise();
+ }
+
+ LCL_Finalise();
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/nts_ntp_auth.c b/test/unit/nts_ntp_auth.c
new file mode 100644
index 0000000..c3a7432
--- /dev/null
+++ b/test/unit/nts_ntp_auth.c
@@ -0,0 +1,135 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTS
+
+#include <nts_ntp_auth.c>
+
+#include "ntp_ext.h"
+#include "siv.h"
+
+void
+test_unit(void)
+{
+ unsigned char key[SIV_MAX_KEY_LENGTH], nonce[256], plaintext[256], plaintext2[256];
+ NTP_PacketInfo info;
+ NTP_Packet packet;
+ SIV_Algorithm algo;
+ SIV_Instance siv;
+ int i, j, r, packet_length, nonce_length, key_length;
+ int plaintext_length, plaintext2_length, min_ef_length;
+
+ for (algo = 1; algo < 100; algo++) {
+ siv = SIV_CreateInstance(algo);
+ if (!siv) {
+ TEST_CHECK(algo != AEAD_AES_SIV_CMAC_256);
+ continue;
+ }
+
+ DEBUG_LOG("algo=%d", (int)algo);
+
+ for (i = 0; i < 10000; i++) {
+ key_length = SIV_GetKeyLength(algo);
+ for (j = 0; j < key_length; j++)
+ key[j] = random() % 256;
+ TEST_CHECK(SIV_SetKey(siv, key, key_length));
+
+ assert(sizeof (nonce) >= SIV_GetMinNonceLength(siv));
+ nonce_length = SIV_GetMinNonceLength(siv) +
+ random() % (MIN(sizeof (nonce), SIV_GetMaxNonceLength(siv)) -
+ SIV_GetMinNonceLength(siv) + 1);
+ for (j = 0; j < nonce_length; j++)
+ nonce[j] = random() % 256;
+
+ plaintext_length = random() % (sizeof (plaintext) + 1);
+ for (j = 0; j < plaintext_length; j++)
+ plaintext[j] = random() % 256;
+
+ packet_length = NTP_HEADER_LENGTH + random() % 100 * 4;
+ min_ef_length = random() % (sizeof (packet) - packet_length);
+
+ memset(&packet, 0, sizeof (packet));
+ packet.lvm = NTP_LVM(0, 4, 0);
+ memset(&info, 0, sizeof (info));
+ info.version = 4;
+ info.length = packet_length;
+
+ DEBUG_LOG("packet_length=%d nonce_length=%d plaintext_length=%d min_ef_length=%d",
+ packet_length, nonce_length, plaintext_length, min_ef_length);
+
+ r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext,
+ -1, 0);
+ TEST_CHECK(!r);
+ r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, 0, plaintext,
+ plaintext_length, 0);
+ TEST_CHECK(!r);
+ if (SIV_GetMinNonceLength(siv) > 1) {
+ r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, SIV_GetMinNonceLength(siv) - 1,
+ plaintext, plaintext_length, 0);
+ TEST_CHECK(!r);
+ TEST_CHECK(info.ext_fields == 0);
+ }
+ if (SIV_GetMaxNonceLength(siv) <= sizeof (nonce)) {
+ r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, SIV_GetMaxNonceLength(siv) - 1,
+ plaintext, plaintext_length, 0);
+ TEST_CHECK(!r);
+ TEST_CHECK(info.ext_fields == 0);
+ }
+ r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext,
+ plaintext_length, sizeof (packet) - info.length + 1);
+ TEST_CHECK(!r);
+
+ r = NNA_GenerateAuthEF(&packet, &info, siv, nonce, nonce_length, plaintext,
+ plaintext_length, min_ef_length);
+ TEST_CHECK(r);
+ TEST_CHECK(info.length - packet_length >= min_ef_length);
+
+ r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2,
+ -1, &plaintext2_length);
+ TEST_CHECK(!r);
+
+ r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2,
+ sizeof (plaintext2), &plaintext2_length);
+ TEST_CHECK(r);
+ TEST_CHECK(plaintext_length == plaintext2_length);
+ TEST_CHECK(memcmp(plaintext, plaintext2, plaintext_length) == 0);
+
+ j = random() % (packet_length + plaintext_length +
+ nonce_length + SIV_GetTagLength(siv) + 8) / 4 * 4;
+ ((unsigned char *)&packet)[j]++;
+ r = NNA_DecryptAuthEF(&packet, &info, siv, packet_length, plaintext2,
+ sizeof (plaintext2), &plaintext2_length);
+ TEST_CHECK(!r);
+ ((unsigned char *)&packet)[j]--;
+ }
+
+ SIV_DestroyInstance(siv);
+ }
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/nts_ntp_client.c b/test/unit/nts_ntp_client.c
new file mode 100644
index 0000000..7953413
--- /dev/null
+++ b/test/unit/nts_ntp_client.c
@@ -0,0 +1,302 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTS
+
+#include "socket.h"
+#include "ntp.h"
+#include "nts_ke_client.h"
+
+#define NKC_CreateInstance(address, name, cert_set) Malloc(1)
+#define NKC_DestroyInstance(inst) Free(inst)
+#define NKC_Start(inst) (random() % 2)
+#define NKC_IsActive(inst) (random() % 2)
+#define NKC_GetRetryFactor(inst) (1)
+
+static int get_nts_data(NKC_Instance inst, NKE_Context *context,
+ NKE_Cookie *cookies, int *num_cookies, int max_cookies,
+ IPSockAddr *ntp_address);
+#define NKC_GetNtsData get_nts_data
+
+#include <nts_ntp_client.c>
+
+static int
+get_nts_data(NKC_Instance inst, NKE_Context *context,
+ NKE_Cookie *cookies, int *num_cookies, int max_cookies,
+ IPSockAddr *ntp_address)
+{
+ int i;
+
+ if (random() % 2)
+ return 0;
+
+ do {
+ context->algorithm = AEAD_AES_SIV_CMAC_256 + random() %
+ (AEAD_AES_256_GCM_SIV - AEAD_AES_SIV_CMAC_256 + 10);
+ } while (SIV_GetKeyLength(context->algorithm) <= 0);
+
+ context->c2s.length = SIV_GetKeyLength(context->algorithm);
+ UTI_GetRandomBytes(context->c2s.key, context->c2s.length);
+ context->s2c.length = SIV_GetKeyLength(context->algorithm);
+ UTI_GetRandomBytes(context->s2c.key, context->s2c.length);
+
+ *num_cookies = random() % max_cookies + 1;
+ for (i = 0; i < *num_cookies; i++) {
+ cookies[i].length = random() % (sizeof (cookies[i].cookie) + 1);
+ if (random() % 4 != 0)
+ cookies[i].length = cookies[i].length / 4 * 4;
+ memset(cookies[i].cookie, random(), cookies[i].length);
+ }
+
+ ntp_address->ip_addr.family = IPADDR_UNSPEC;
+ ntp_address->port = 0;
+
+ return 1;
+}
+
+static int
+get_request(NNC_Instance inst)
+{
+ unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH], uniq_id[NTS_MIN_UNIQ_ID_LENGTH];
+ int nonce_length, expected_length, req_cookies;
+ NTP_PacketInfo info;
+ NTP_Packet packet;
+
+ memset(&packet, 0, sizeof (packet));
+ memset(&info, 0, sizeof (info));
+ info.version = 4;
+ info.mode = MODE_CLIENT;
+ info.length = random() % (sizeof (packet) + 1);
+ if (random() % 4 != 0)
+ info.length = info.length / 4 * 4;
+
+ if (inst->num_cookies > 0 && random() % 2) {
+ inst->num_cookies = 0;
+
+ TEST_CHECK(!NNC_GenerateRequestAuth(inst, &packet, &info));
+ }
+
+ while (!NNC_PrepareForAuth(inst)) {
+ inst->next_nke_attempt = SCH_GetLastEventMonoTime() + random() % 10 - 7;
+ }
+
+ TEST_CHECK(inst->num_cookies > 0);
+ TEST_CHECK(inst->siv);
+
+ switch (inst->context.algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ nonce_length = 16;
+ break;
+ case AEAD_AES_128_GCM_SIV:
+ nonce_length = 12;
+ break;
+ default:
+ assert(0);
+ }
+
+ memcpy(nonce, inst->nonce, sizeof (nonce));
+ memcpy(uniq_id, inst->uniq_id, sizeof (uniq_id));
+ TEST_CHECK(NNC_PrepareForAuth(inst));
+ TEST_CHECK(memcmp(nonce, inst->nonce, sizeof (nonce)) != 0);
+ TEST_CHECK(memcmp(uniq_id, inst->uniq_id, sizeof (uniq_id)) != 0);
+
+ req_cookies = MIN(NTS_MAX_COOKIES - inst->num_cookies + 1,
+ MAX_TOTAL_COOKIE_LENGTH /
+ (inst->cookies[inst->cookie_index].length + 4));
+ expected_length = info.length + 4 + sizeof (inst->uniq_id) +
+ req_cookies * (4 + inst->cookies[inst->cookie_index].length) +
+ 4 + 4 + nonce_length + SIV_GetTagLength(inst->siv);
+ DEBUG_LOG("algo=%d length=%d cookie_length=%d expected_length=%d",
+ (int)inst->context.algorithm, info.length,
+ inst->cookies[inst->cookie_index].length, expected_length);
+
+ if (info.length % 4 == 0 && info.length >= NTP_HEADER_LENGTH &&
+ inst->cookies[inst->cookie_index].length % 4 == 0 &&
+ inst->cookies[inst->cookie_index].length >= (NTP_MIN_EF_LENGTH - 4) &&
+ expected_length <= sizeof (packet)) {
+ TEST_CHECK(NNC_GenerateRequestAuth(inst, &packet, &info));
+ TEST_CHECK(info.length == expected_length);
+ return 1;
+ } else {
+ TEST_CHECK(!NNC_GenerateRequestAuth(inst, &packet, &info));
+ return 0;
+ }
+}
+
+static void
+prepare_response(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info, int valid, int nak)
+{
+ unsigned char cookie[508], plaintext[528], nonce[448];
+ int nonce_length, ef_length, cookie_length, plaintext_length, min_auth_length;
+ int i, index, auth_start;
+ SIV_Instance siv;
+
+ memset(packet, 0, sizeof (*packet));
+ packet->lvm = NTP_LVM(0, 4, MODE_SERVER);
+ memset(info, 0, sizeof (*info));
+ info->version = 4;
+ info->mode = MODE_SERVER;
+ info->length = NTP_HEADER_LENGTH;
+
+ if (valid)
+ index = -1;
+ else
+ index = random() % (nak ? 2 : 8);
+
+ DEBUG_LOG("index=%d nak=%d", index, nak);
+
+ if (index != 0)
+ TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER, inst->uniq_id,
+ sizeof (inst->uniq_id)));
+ if (index == 1)
+ ((unsigned char *)packet)[NTP_HEADER_LENGTH + 4]++;
+
+ if (nak) {
+ packet->stratum = NTP_INVALID_STRATUM;
+ packet->reference_id = htonl(NTP_KOD_NTS_NAK);
+ return;
+ }
+
+ nonce_length = SIV_GetMinNonceLength(inst->siv) + random() %
+ (MIN(SIV_GetMaxNonceLength(inst->siv), sizeof (nonce)) -
+ SIV_GetMinNonceLength(inst->siv) + 1);
+ assert(nonce_length >= 1 && nonce_length <= sizeof (nonce));
+
+ do {
+ cookie_length = random() % (sizeof (cookie) + 1);
+ } while (cookie_length % 4 != 0 ||
+ ((index != 2) == (cookie_length < NTP_MIN_EF_LENGTH - 4 ||
+ cookie_length > NKE_MAX_COOKIE_LENGTH)));
+
+ min_auth_length = random() % (512 + 1);
+
+ DEBUG_LOG("nonce_length=%d cookie_length=%d min_auth_length=%d",
+ nonce_length, cookie_length, min_auth_length);
+
+ UTI_GetRandomBytes(nonce, nonce_length);
+ UTI_GetRandomBytes(cookie, cookie_length);
+
+ if (cookie_length >= 12 && cookie_length <= 32 && random() % 2 == 0)
+ TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE, cookie, cookie_length));
+
+ plaintext_length = 0;
+ if (index != 3) {
+ for (i = random() % ((sizeof (plaintext) - 16) / (cookie_length + 4)); i >= 0; i--) {
+ TEST_CHECK(NEF_SetField(plaintext, sizeof (plaintext), plaintext_length,
+ NTP_EF_NTS_COOKIE, cookie,
+ i == 0 ? cookie_length : random() % (cookie_length + 1) / 4 * 4,
+ &ef_length));
+ plaintext_length += ef_length;
+ }
+ }
+ auth_start = info->length;
+ if (index != 4) {
+ if (index == 5) {
+ assert(plaintext_length + 16 <= sizeof (plaintext));
+ memset(plaintext + plaintext_length, 0, 16);
+ plaintext_length += 16;
+ }
+ siv = SIV_CreateInstance(inst->context.algorithm);
+ TEST_CHECK(siv);
+ TEST_CHECK(SIV_SetKey(siv, inst->context.s2c.key, inst->context.s2c.length));
+ TEST_CHECK(NNA_GenerateAuthEF(packet, info, siv,
+ nonce, nonce_length, plaintext, plaintext_length,
+ min_auth_length));
+ SIV_DestroyInstance(siv);
+ }
+ if (index == 6)
+ ((unsigned char *)packet)[auth_start + 8]++;
+ if (index == 7)
+ TEST_CHECK(NEF_AddField(packet, info, 0x7000, inst->uniq_id, sizeof (inst->uniq_id)));
+}
+
+void
+test_unit(void)
+{
+ NNC_Instance inst;
+ NTP_PacketInfo info;
+ NTP_Packet packet;
+ IPSockAddr addr;
+ IPAddr ip_addr;
+ int i, j, prev_num_cookies, valid;
+
+ TEST_CHECK(SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0);
+
+ SCK_GetLoopbackIPAddress(AF_INET, &addr.ip_addr);
+ addr.port = 0;
+
+ inst = NNC_CreateInstance(&addr, "test", 0, 0);
+ TEST_CHECK(inst);
+
+ for (i = 0; i < 100000; i++) {
+ if (!get_request(inst))
+ continue;
+
+ valid = random() % 2;
+
+ TEST_CHECK(!inst->nak_response);
+ TEST_CHECK(!inst->ok_response);
+
+ if (random() % 2) {
+ prepare_response(inst, &packet, &info, 0, 1);
+ TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info));
+ TEST_CHECK(!inst->nak_response);
+ TEST_CHECK(!inst->ok_response);
+ for (j = random() % 3; j > 0; j--) {
+ prepare_response(inst, &packet, &info, 1, 1);
+ TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info));
+ TEST_CHECK(inst->nak_response);
+ TEST_CHECK(!inst->ok_response);
+ }
+ }
+
+ prev_num_cookies = inst->num_cookies;
+ prepare_response(inst, &packet, &info, valid, 0);
+
+ if (valid) {
+ TEST_CHECK(NNC_CheckResponseAuth(inst, &packet, &info));
+ TEST_CHECK(inst->num_cookies >= MIN(NTS_MAX_COOKIES, prev_num_cookies + 1));
+ TEST_CHECK(inst->ok_response);
+ }
+
+ prev_num_cookies = inst->num_cookies;
+ TEST_CHECK(!NNC_CheckResponseAuth(inst, &packet, &info));
+ TEST_CHECK(inst->num_cookies == prev_num_cookies);
+ TEST_CHECK(inst->ok_response == valid);
+
+ if (random() % 10 == 0) {
+ TST_GetRandomAddress(&ip_addr, IPADDR_INET4, 32);
+ NNC_ChangeAddress(inst, &ip_addr);
+ TEST_CHECK(UTI_CompareIPs(&inst->nts_address.ip_addr, &ip_addr, NULL) == 0);
+ }
+ }
+
+ NNC_DestroyInstance(inst);
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/nts_ntp_server.c b/test/unit/nts_ntp_server.c
new file mode 100644
index 0000000..2777942
--- /dev/null
+++ b/test/unit/nts_ntp_server.c
@@ -0,0 +1,180 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include "test.h"
+
+#ifdef FEAT_NTS
+
+#include <local.h>
+#include <sched.h>
+
+#include <nts_ntp_server.c>
+
+static void
+prepare_request(NTP_Packet *packet, NTP_PacketInfo *info, int valid, int nak)
+{
+ unsigned char uniq_id[NTS_MIN_UNIQ_ID_LENGTH], nonce[NTS_MIN_UNPADDED_NONCE_LENGTH];
+ SIV_Instance siv;
+ NKE_Context context;
+ NKE_Cookie cookie;
+ int i, index, cookie_start, auth_start;
+
+ context.algorithm = random() % 2 && SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0 ?
+ AEAD_AES_128_GCM_SIV : AEAD_AES_SIV_CMAC_256;
+ context.c2s.length = SIV_GetKeyLength(context.algorithm);
+ assert(context.c2s.length <= sizeof (context.c2s.key));
+ UTI_GetRandomBytes(&context.c2s.key, context.c2s.length);
+ context.s2c.length = SIV_GetKeyLength(context.algorithm);
+ assert(context.s2c.length <= sizeof (context.s2c.key));
+ UTI_GetRandomBytes(&context.s2c.key, context.s2c.length);
+
+ TEST_CHECK(NKS_GenerateCookie(&context, &cookie));
+
+ UTI_GetRandomBytes(uniq_id, sizeof (uniq_id));
+ UTI_GetRandomBytes(nonce, sizeof (nonce));
+
+ memset(packet, 0, sizeof (*packet));
+ packet->lvm = NTP_LVM(0, 4, MODE_CLIENT);
+ memset(info, 0, sizeof (*info));
+ info->version = 4;
+ info->mode = MODE_CLIENT;
+ info->length = NTP_HEADER_LENGTH;
+
+ if (valid)
+ index = -1;
+ else
+ index = random() % 3;
+
+ DEBUG_LOG("valid=%d nak=%d index=%d", valid, nak, index);
+
+ if (index != 0)
+ TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER,
+ uniq_id, sizeof (uniq_id)));
+
+ cookie_start = info->length;
+
+ if (index != 1)
+ TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE,
+ cookie.cookie, cookie.length));
+
+ for (i = random() % 4; i > 0; i--)
+ TEST_CHECK(NEF_AddField(packet, info, NTP_EF_NTS_COOKIE_PLACEHOLDER,
+ cookie.cookie, cookie.length));
+
+ auth_start = info->length;
+
+ if (index != 2) {
+ siv = SIV_CreateInstance(context.algorithm);
+ TEST_CHECK(siv);
+ TEST_CHECK(SIV_SetKey(siv, context.c2s.key, context.c2s.length));
+ TEST_CHECK(NNA_GenerateAuthEF(packet, info, siv, nonce, sizeof (nonce),
+ (const unsigned char *)"", 0, 0));
+ SIV_DestroyInstance(siv);
+ }
+
+ if (nak)
+ ((unsigned char *)packet)[(index == 2 ? cookie_start :
+ (index == 1 ? auth_start :
+ (random() % 2 ? cookie_start : auth_start))) +
+ 4 + random() % 16]++;
+}
+
+static void
+init_response(NTP_Packet *packet, NTP_PacketInfo *info)
+{
+ memset(packet, 0, sizeof (*packet));
+ packet->lvm = NTP_LVM(0, 4, MODE_SERVER);
+ memset(info, 0, sizeof (*info));
+ info->version = 4;
+ info->mode = MODE_SERVER;
+ info->length = NTP_HEADER_LENGTH;
+}
+
+void
+test_unit(void)
+{
+ NTP_PacketInfo req_info, res_info;
+ NTP_Packet request, response;
+ int i, valid, nak;
+ uint32_t kod;
+
+ char conf[][100] = {
+ "ntsport 0",
+ "ntsprocesses 0",
+ "ntsserverkey nts_ke.key",
+ "ntsservercert nts_ke.crt",
+ };
+
+ CNF_Initialise(0, 0);
+ for (i = 0; i < sizeof conf / sizeof conf[0]; i++)
+ CNF_ParseLine(NULL, i + 1, conf[i]);
+
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+ SCH_Initialise();
+ NKS_PreInitialise(0, 0, 0);
+ NKS_Initialise();
+ NNS_Initialise();
+
+ for (i = 0; i < 50000; i++) {
+ valid = random() % 2;
+ nak = random() % 2;
+ prepare_request(&request, &req_info, valid, nak);
+
+ TEST_CHECK(NNS_CheckRequestAuth(&request, &req_info, &kod) == (valid && !nak));
+
+ if (valid && !nak) {
+ TEST_CHECK(kod == 0);
+ TEST_CHECK(server->num_cookies > 0);
+
+ init_response(&response, &res_info);
+ TEST_CHECK(NNS_GenerateResponseAuth(&request, &req_info, &response, &res_info, kod));
+
+ TEST_CHECK(res_info.ext_fields == 2);
+ TEST_CHECK(server->num_cookies == 0);
+ } else if (valid && nak) {
+ TEST_CHECK(kod == NTP_KOD_NTS_NAK);
+ TEST_CHECK(server->num_cookies == 0);
+
+ init_response(&response, &res_info);
+ TEST_CHECK(NNS_GenerateResponseAuth(&request, &req_info, &response, &res_info, kod));
+
+ TEST_CHECK(res_info.ext_fields == 1);
+ TEST_CHECK(server->num_cookies == 0);
+ } else {
+ TEST_CHECK(kod == 0);
+ TEST_CHECK(server->num_cookies == 0);
+ }
+ }
+
+ NNS_Finalise();
+ NKS_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/quantiles.c b/test/unit/quantiles.c
new file mode 100644
index 0000000..5d97df6
--- /dev/null
+++ b/test/unit/quantiles.c
@@ -0,0 +1,68 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <local.h>
+#include "test.h"
+
+#include <quantiles.c>
+
+void
+test_unit(void)
+{
+ int i, j, k, min_k, max_k, q, r, in_order, out_order;
+ QNT_Instance inst;
+ double x;
+
+ in_order = out_order = 0;
+
+ for (i = 0; i < 100; i++) {
+ r = random() % 10 + 1;
+ q = random() % 20 + 2;
+ do {
+ min_k = random() % (q - 1) + 1;
+ max_k = random() % (q - 1) + 1;
+ } while (min_k > max_k);
+
+ inst = QNT_CreateInstance(min_k, max_k, q, r, 1e-9);
+
+ TEST_CHECK(min_k == QNT_GetMinK(inst));
+
+ for (j = 0; j < 3000; j++) {
+ x = TST_GetRandomDouble(0.0, 2e-6);
+ QNT_Accumulate(inst, x);
+ for (k = min_k; k < max_k; k++)
+ if (j < max_k - min_k) {
+ TEST_CHECK(QNT_GetQuantile(inst, k) <= QNT_GetQuantile(inst, k + 1));
+ } else if (j > 1000) {
+ if (QNT_GetQuantile(inst, k) <= QNT_GetQuantile(inst, k + 1))
+ in_order++;
+ else
+ out_order++;
+ }
+ }
+
+ QNT_Reset(inst);
+ TEST_CHECK(inst->n_set == 0);
+
+ QNT_DestroyInstance(inst);
+ }
+
+ TEST_CHECK(in_order > 100 * out_order);
+}
diff --git a/test/unit/regress.c b/test/unit/regress.c
new file mode 100644
index 0000000..f47d1c4
--- /dev/null
+++ b/test/unit/regress.c
@@ -0,0 +1,119 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+#include <regress.c>
+#include "test.h"
+
+#define POINTS 64
+
+void
+test_unit(void)
+{
+ double x[POINTS], x2[POINTS], y[POINTS], w[POINTS];
+ double b0, b1, b2, s2, sb0, sb1, slope, slope2, intercept, sd, median;
+ double xrange, yrange, wrange, x2range;
+ int i, j, n, m, c1, c2, c3, runs, best_start, dof;
+
+ for (n = 3; n <= POINTS; n++) {
+ for (i = 0; i < 200; i++) {
+ slope = TST_GetRandomDouble(-0.1, 0.1);
+ intercept = TST_GetRandomDouble(-1.0, 1.0);
+ sd = TST_GetRandomDouble(1e-6, 1e-4);
+ slope2 = (random() % 2 ? 1 : -1) * TST_GetRandomDouble(0.1, 0.5);
+
+ DEBUG_LOG("iteration %d n=%d intercept=%e slope=%e sd=%e",
+ i, n, intercept, slope, sd);
+
+ for (j = 0; j < n; j++) {
+ x[j] = -j;
+ y[j] = intercept + slope * x[j] + (j % 2 ? 1 : -1) * TST_GetRandomDouble(1e-6, sd);
+ w[j] = TST_GetRandomDouble(1.0, 2.0);
+ x2[j] = (y[j] - intercept - slope * x[j]) / slope2;
+ }
+
+ RGR_WeightedRegression(x, y, w, n, &b0, &b1, &s2, &sb0, &sb1);
+ DEBUG_LOG("WR b0=%e b1=%e s2=%e sb0=%e sb1=%e", b0, b1, s2, sb0, sb1);
+ TEST_CHECK(fabs(b0 - intercept) < sd + 1e-3);
+ TEST_CHECK(fabs(b1 - slope) < sd);
+
+ if (RGR_FindBestRegression(x, y, w, n, 0, 3, &b0, &b1, &s2, &sb0, &sb1,
+ &best_start, &runs, &dof)) {
+ DEBUG_LOG("BR b0=%e b1=%e s2=%e sb0=%e sb1=%e runs=%d bs=%d dof=%d",
+ b0, b1, s2, sb0, sb1, runs, best_start, dof);
+
+ TEST_CHECK(fabs(b0 - intercept) < sd + 1e-3);
+ TEST_CHECK(fabs(b1 - slope) < sd);
+ }
+
+ if (RGR_MultipleRegress(x, x2, y, n, &b2)) {
+ DEBUG_LOG("MR b2=%e", b2);
+ TEST_CHECK(fabs(b2 - slope2) < 1e-6);
+ }
+
+ for (j = 0; j < n / 7; j++)
+ y[random() % n] += 100 * sd;
+
+ if (RGR_FindBestRobustRegression(x, y, n, 1e-8, &b0, &b1, &runs, &best_start)) {
+ DEBUG_LOG("BRR b0=%e b1=%e runs=%d bs=%d", b0, b1, runs, best_start);
+
+ TEST_CHECK(fabs(b0 - intercept) < sd + 1e-2);
+ TEST_CHECK(fabs(b1 - slope) < 5.0 * sd);
+ }
+
+ for (j = 0; j < n; j++)
+ x[j] = random() % 4 * TST_GetRandomDouble(-1000, 1000);
+
+ median = RGR_FindMedian(x, n);
+
+ for (j = c1 = c2 = c3 = 0; j < n; j++) {
+ if (x[j] < median)
+ c1++;
+ if (x[j] > median)
+ c3++;
+ else
+ c2++;
+ }
+
+ TEST_CHECK(c1 + c2 >= c3 && c1 <= c2 + c3);
+
+ xrange = TST_GetRandomDouble(1e-6, pow(10.0, random() % 10));
+ yrange = random() % 3 * TST_GetRandomDouble(0.0, pow(10.0, random() % 10));
+ wrange = random() % 3 * TST_GetRandomDouble(0.0, pow(10.0, random() % 10));
+ x2range = random() % 3 * TST_GetRandomDouble(0.0, pow(10.0, random() % 10));
+ m = random() % n;
+
+ for (j = 0; j < n; j++) {
+ x[j] = (j ? x[j - 1] : 0.0) + TST_GetRandomDouble(1e-6, xrange);
+ y[j] = TST_GetRandomDouble(-yrange, yrange);
+ w[j] = 1.0 + TST_GetRandomDouble(0.0, wrange);
+ x2[j] = TST_GetRandomDouble(-x2range, x2range);
+ }
+
+ RGR_WeightedRegression(x, y, w, n, &b0, &b1, &s2, &sb0, &sb1);
+
+ if (RGR_FindBestRegression(x + m, y + m, w, n - m, m, 3, &b0, &b1, &s2, &sb0, &sb1,
+ &best_start, &runs, &dof))
+ ;
+ if (RGR_MultipleRegress(x, x2, y, n, &b2))
+ ;
+ if (RGR_FindBestRobustRegression(x, y, n, 1e-8, &b0, &b1, &runs, &best_start))
+ ;
+ }
+ }
+}
diff --git a/test/unit/samplefilt.c b/test/unit/samplefilt.c
new file mode 100644
index 0000000..19d2f70
--- /dev/null
+++ b/test/unit/samplefilt.c
@@ -0,0 +1,120 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <local.h>
+#include "test.h"
+
+#define LCL_GetSysPrecisionAsQuantum() (1.0e-6)
+
+#include <samplefilt.c>
+
+void
+test_unit(void)
+{
+ NTP_Sample sample_in, sample_out;
+ SPF_Instance filter;
+ int i, j, k, sum_count, min_samples, max_samples;
+ double mean, combine_ratio, sum_err;
+
+ LCL_Initialise();
+
+ memset(&sample_in, 0, sizeof (sample_in));
+ memset(&sample_out, 0, sizeof (sample_out));
+
+ for (i = 0; i <= 100; i++) {
+ max_samples = random() % 20 + 1;
+ min_samples = random() % (max_samples) + 1;
+ combine_ratio = TST_GetRandomDouble(0.0, 1.0);
+
+ filter = SPF_CreateInstance(min_samples, max_samples, 2.0, combine_ratio);
+
+ TEST_CHECK(max_samples == SPF_GetMaxSamples(filter));
+
+ for (j = 0, sum_count = 0, sum_err = 0.0; j < 100; j++) {
+ DEBUG_LOG("iteration %d/%d", i, j);
+
+ mean = TST_GetRandomDouble(-1.0e3, 1.0e3);
+ UTI_ZeroTimespec(&sample_in.time);
+
+ for (k = 0; k < 100; k++) {
+ UTI_AddDoubleToTimespec(&sample_in.time, TST_GetRandomDouble(1.0e-1, 1.0e2),
+ &sample_in.time);
+ sample_in.offset = mean + TST_GetRandomDouble(-1.0, 1.0);
+ sample_in.peer_dispersion = TST_GetRandomDouble(1.0e-4, 2.0e-4);
+ sample_in.root_dispersion = TST_GetRandomDouble(1.0e-3, 2.0e-3);
+ sample_in.peer_delay = TST_GetRandomDouble(1.0e-2, 2.0e-2);
+ sample_in.root_delay = TST_GetRandomDouble(1.0e-1, 2.0e-1);
+
+ TEST_CHECK(SPF_AccumulateSample(filter, &sample_in));
+ TEST_CHECK(!SPF_AccumulateSample(filter, &sample_in));
+
+ TEST_CHECK(SPF_GetNumberOfSamples(filter) == MIN(k + 1, max_samples));
+
+ SPF_GetLastSample(filter, &sample_out);
+ TEST_CHECK(!memcmp(&sample_in, &sample_out, sizeof (sample_in)));
+
+ SPF_SlewSamples(filter, &sample_in.time, 0.0, 0.0);
+ SPF_CorrectOffset(filter, 0.0);
+ SPF_AddDispersion(filter, 0.0);
+
+ if (k + 1 < min_samples)
+ TEST_CHECK(!SPF_GetFilteredSample(filter, &sample_out));
+
+ TEST_CHECK(SPF_GetNumberOfSamples(filter) == MIN(k + 1, max_samples));
+ }
+
+ if (random() % 10) {
+ TEST_CHECK(SPF_GetFilteredSample(filter, &sample_out));
+
+ TEST_CHECK(SPF_GetAvgSampleDispersion(filter) <= 2.0);
+
+ sum_err += sample_out.offset - mean;
+ sum_count++;
+
+ TEST_CHECK(UTI_CompareTimespecs(&sample_out.time, &sample_in.time) <= 0 &&
+ sample_out.time.tv_sec >= 0);
+ TEST_CHECK(fabs(sample_out.offset - mean) <= 1.0);
+ TEST_CHECK(sample_out.peer_dispersion >= 1.0e-4 &&
+ (sample_out.peer_dispersion <= 2.0e-4 || filter->max_samples > 1));
+ TEST_CHECK(sample_out.root_dispersion >= 1.0e-3 &&
+ (sample_out.root_dispersion <= 2.0e-3 || filter->max_samples > 1));
+ TEST_CHECK(sample_out.peer_delay >= 1.0e-2 &&
+ sample_out.peer_delay <= 2.0e-2);
+ TEST_CHECK(sample_out.root_delay >= 1.0e-1 &&
+ sample_out.root_delay <= 2.0e-1);
+
+ if (max_samples == 1)
+ TEST_CHECK(!memcmp(&sample_in, &sample_out, sizeof (sample_in)));
+
+ } else {
+ SPF_DropSamples(filter);
+ TEST_CHECK(filter->last < 0);
+ }
+
+ TEST_CHECK(SPF_GetNumberOfSamples(filter) == 0);
+ }
+
+ TEST_CHECK(fabs(sum_err / sum_count) < 0.3);
+
+ SPF_DestroyInstance(filter);
+ }
+
+ LCL_Finalise();
+}
diff --git a/test/unit/siv.c b/test/unit/siv.c
new file mode 100644
index 0000000..54f435d
--- /dev/null
+++ b/test/unit/siv.c
@@ -0,0 +1,423 @@
+/*
+ **********************************************************************
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include <sysincl.h>
+#include <logging.h>
+#include <siv.h>
+#include "test.h"
+
+#ifdef HAVE_SIV
+
+struct siv_test {
+ SIV_Algorithm algorithm;
+ const unsigned char key[64];
+ int key_length;
+ const unsigned char nonce[128];
+ int nonce_length;
+ const unsigned char assoc[128];
+ int assoc_length;
+ const unsigned char plaintext[128];
+ int plaintext_length;
+ const unsigned char ciphertext[128];
+ int ciphertext_length;
+};
+
+void
+test_unit(void)
+{
+ struct siv_test tests[] = {
+ { AEAD_AES_SIV_CMAC_256,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde"
+ "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16,
+ "", 0,
+ "", 0,
+ "\x22\x3e\xb5\x94\xe0\xe0\x25\x4b\x00\x25\x8e\x21\x9a\x1c\xa4\x21", 16
+ },
+ { AEAD_AES_SIV_CMAC_256,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde"
+ "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16,
+ "", 0,
+ "\xd7\x20\x19\x89\xc6\xdb\xc6\xd6\x61\xfc\x62\xbc\x86\x5e\xee\xef", 16
+ },
+ { AEAD_AES_SIV_CMAC_256,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde"
+ "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16,
+ "", 0,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16,
+ "\xb6\xc1\x60\xe9\xc2\xfd\x2a\xe8\xde\xc5\x36\x8b\x2a\x33\xed\xe1"
+ "\x14\xff\xb3\x97\x34\x5c\xcb\xe4\x4a\xa4\xde\xac\xd9\x36\x90\x46", 32
+ },
+ { AEAD_AES_SIV_CMAC_256,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde"
+ "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e", 15,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c", 15,
+ "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4", 15,
+ "\x03\x8c\x41\x51\xba\x7a\x8f\x77\x6e\x56\x31\x99\x42\x0b\xc7\x03"
+ "\xe7\x6c\x67\xc9\xda\xb7\x0d\x5b\x44\x06\x26\x5a\xd0\xd2\x3b", 31
+ },
+ { AEAD_AES_SIV_CMAC_256,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde"
+ "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 16,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16,
+ "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4\xa7", 16,
+ "\x5c\x05\x23\x65\xf4\x57\x0a\xa0\xfb\x38\x3e\xce\x9b\x75\x85\xeb"
+ "\x68\x85\x19\x36\x0c\x7c\x48\x11\x40\xcb\x9b\x57\x9a\x0e\x65\x32", 32
+ },
+ { AEAD_AES_SIV_CMAC_256,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde"
+ "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+ "\xd5", 17,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b"
+ "\xa0", 17,
+ "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4\xa7"
+ "\x08", 17,
+ "\xaf\x58\x4b\xe7\x82\x1e\x96\x19\x29\x91\x25\xe0\xdd\x80\x3b\x49"
+ "\xa5\x11\xcd\xb6\x08\xf3\x76\xa0\xb6\xfa\x15\x82\xf3\x95\xe1\xeb"
+ "\xbd", 33
+ },
+ { AEAD_AES_SIV_CMAC_256,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde"
+ "\xef\x01\x23\x45\x67\x89\xab\xcd\xde\xf0\x12\x34\x56\x78\x9a\xbc", 32,
+ "\xb0\x5a\x1b\xc7\x56\xe7\xb6\x2c\xb4\x85\xe5\x56\xa5\x28\xc0\x6c"
+ "\x2f\x3b\x0b\x9d\x1a\x0c\xdf\x69\x47\xe0\xcc\xc0\x87\xaa\x5c\x09"
+ "\x98\x48\x8d\x6a\x8e\x1e\x05\xd7\x8b\x68\x74\x83\xb5\x1d\xf1\x2c", 48,
+ "\xe5\x8b\xd2\x6a\x30\xc5\xc5\x61\xcc\xbd\x7c\x27\xbf\xfe\xf9\x06"
+ "\x00\x5b\xd7\xfc\x11\x0b\xcf\x16\x61\xef\xac\x05\xa7\xaf\xec\x27"
+ "\x41\xc8\x5e\x9e\x0d\xf9\x2f\xaf\x20\x79\x17\xe5\x17\x91\x2a\x27"
+ "\x34\x1c\xbc\xaf\xeb\xef\x7f\x52\xe7\x1e\x4c\x2a\xca\xbd\x2b\xbe"
+ "\x34\xd6\xfb\x69\xd3\x3e\x49\x59\x60\xb4\x26\xc9\xb8\xce\xba", 79,
+ "\x6c\xe7\xcf\x7e\xab\x7b\xa0\xe1\xa7\x22\xcb\x88\xde\x5e\x42\xd2"
+ "\xec\x79\xe0\xa2\xcf\x5f\x0f\x6f\x6b\x89\x57\xcd\xae\x17\xd4\xc2"
+ "\xf3\x1b\xa2\xa8\x13\x78\x23\x2f\x83\xa8\xd4\x0c\xc0\xd2\xf3\x99"
+ "\xae\x81\xa1\xca\x5b\x5f\x45\xa6\x6f\x0c\x8a\xf3\xd4\x67\x40\x81"
+ "\x26\xe2\x01\x86\xe8\x5a\xd5\xf8\x58\x80\x9f\x56\xaa\x76\x96\xbf"
+ "\x31", 81,
+ "\x9a\x06\x33\xe0\xee\x00\x6a\x9b\xc8\x20\xd5\xe2\xc2\xed\xb5\x75"
+ "\xfa\x9e\x42\x2a\x31\x6b\xda\xca\xaa\x7d\x31\x8b\x84\x7a\xb8\xd7"
+ "\x8a\x81\x25\x64\xed\x41\x9b\xa9\x77\x10\xbd\x05\x0c\x4e\xc5\x31"
+ "\x0c\xa2\x86\xec\x8a\x94\xc8\x24\x23\x3c\x13\xee\xa5\x51\xc9\xdf"
+ "\x48\xc9\x55\xc5\x2f\x40\x73\x3f\x98\xbb\x8d\x69\x78\x46\x64\x17"
+ "\x8d\x49\x2f\x14\x62\xa4\x7c\x2a\x57\x38\x87\xce\xc6\x72\xd3\x5c"
+ "\xa1", 97
+ },
+ { AEAD_AES_128_GCM_SIV,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde", 16,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b", 12,
+ "", 0,
+ "", 0,
+ "\xba\x05\x1c\x40\xeb\x7e\x5f\xa2\x3f\x6c\xe5\xbe\xfe\x5b\x04\xad", 16
+ },
+ { AEAD_AES_128_GCM_SIV,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde", 16,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b", 12,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16,
+ "", 0,
+ "\x8f\x47\xfe\x1f\x26\x4e\xe2\x99\x5f\x35\x3d\x26\x74\x14\xd4\x3b", 16
+ },
+ { AEAD_AES_128_GCM_SIV,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde", 16,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b", 12,
+ "", 0,
+ "\xba\x05\x1c\x40\xeb\x7e\x5f\xa2\x3f\x6c\xe5\xbe\xfe\x5b\x04\xad", 16,
+ "\xa1\xc6\x1b\xf7\x32\x39\x93\x0e\x10\xf8\xa6\x21\x6c\x6e\x26\x83"
+ "\x5c\xa9\xb0\xdd\x91\x0f\x81\xa6\xf0\x3b\x45\xda\xa6\x9a\x2b\x24", 32,
+ },
+ { AEAD_AES_128_GCM_SIV,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde", 16,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b", 12,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c", 15,
+ "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4", 15,
+ "\x7a\x23\xa7\x35\x8d\x34\x5b\xf6\x0d\xa7\x6d\x3b\x58\x8c\x4c\x65"
+ "\xd9\x85\x4e\x17\xb7\x52\x48\xf7\x91\xb4\xdd\xd6\x8b\xec\x02", 31
+ },
+ { AEAD_AES_128_GCM_SIV,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde", 16,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b", 12,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b", 16,
+ "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4\xa7", 16,
+ "\xa3\x10\xae\x5f\x26\xd9\x90\xfa\xab\x30\x29\x80\x7f\x93\x62\x23"
+ "\x83\x8f\xc9\x57\x90\xbb\x05\x87\x02\x11\x57\xd6\x13\x9b\x82\x4d", 32
+ },
+ { AEAD_AES_128_GCM_SIV,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde", 16,
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b", 12,
+ "\x4c\x9d\x4f\xca\xed\x8a\xe2\xba\xad\x3f\x3e\xa6\xe9\x3c\x8c\x8b"
+ "\xa0", 17,
+ "\xba\x99\x79\x31\x23\x7e\x3c\x53\x58\x7e\xd4\x93\x02\xab\xe4\xa7"
+ "\x08", 17,
+ "\x4c\x48\x67\x48\xce\x8b\x14\x7b\x70\xac\x71\xe8\x7b\x4e\x4a\x6a"
+ "\xb4\x3d\xb5\x8e\x58\x81\xfc\x3e\x97\xcd\xdf\xef\x67\x1e\xf4\x4f"
+ "\x0d", 33
+ },
+ { AEAD_AES_128_GCM_SIV,
+ "\x01\x23\x45\x67\x89\xab\xcd\xef\xf0\x12\x34\x56\x78\x9a\xbc\xde", 16,
+ "\xb0\x5a\x1b\xc7\x56\xe7\xb6\x2c\xb4\x85\xe5\x56", 12,
+ "\xe5\x8b\xd2\x6a\x30\xc5\xc5\x61\xcc\xbd\x7c\x27\xbf\xfe\xf9\x06"
+ "\x00\x5b\xd7\xfc\x11\x0b\xcf\x16\x61\xef\xac\x05\xa7\xaf\xec\x27"
+ "\x41\xc8\x5e\x9e\x0d\xf9\x2f\xaf\x20\x79\x17\xe5\x17\x91\x2a\x27"
+ "\x34\x1c\xbc\xaf\xeb\xef\x7f\x52\xe7\x1e\x4c\x2a\xca\xbd\x2b\xbe"
+ "\x34\xd6\xfb\x69\xd3\x3e\x49\x59\x60\xb4\x26\xc9\xb8\xce\xba", 79,
+ "\x6c\xe7\xcf\x7e\xab\x7b\xa0\xe1\xa7\x22\xcb\x88\xde\x5e\x42\xd2"
+ "\xec\x79\xe0\xa2\xcf\x5f\x0f\x6f\x6b\x89\x57\xcd\xae\x17\xd4\xc2"
+ "\xf3\x1b\xa2\xa8\x13\x78\x23\x2f\x83\xa8\xd4\x0c\xc0\xd2\xf3\x99"
+ "\xae\x81\xa1\xca\x5b\x5f\x45\xa6\x6f\x0c\x8a\xf3\xd4\x67\x40\x81"
+ "\x26\xe2\x01\x86\xe8\x5a\xd5\xf8\x58\x80\x9f\x56\xaa\x76\x96\xbf"
+ "\x31", 81,
+ "\xf6\xa0\x1a\xf3\x4f\xe9\x36\xde\x5c\xbd\xb6\x0a\x26\x9d\x60\x1d"
+ "\xe6\xc9\x6d\xb8\xf2\x5f\xcd\xce\x26\xf4\x0d\x86\xec\xdd\x84\x25"
+ "\xaf\xec\x72\x10\x2d\x74\x2d\xde\x95\x84\xac\xce\xbf\x8a\x52\x9f"
+ "\x10\x6f\xc2\xa8\x1f\xed\x47\xff\xeb\x28\x57\x54\xb3\x45\x45\x56"
+ "\xbb\xcf\x7d\x9b\x99\x68\xbd\x36\x75\xe3\xf7\x8c\x09\x25\x01\xbe"
+ "\xe1\xe2\x3d\x19\x4f\x15\x64\x12\x6e\xea\x67\x6c\x42\x2f\xc1\x91"
+ "\xff", 97
+ },
+ { 0, "", 0 }
+ };
+
+ unsigned char plaintext[sizeof (((struct siv_test *)NULL)->plaintext)];
+ unsigned char ciphertext[sizeof (((struct siv_test *)NULL)->ciphertext)];
+ SIV_Instance siv;
+ int i, j, r, fixed_nonce_length;
+
+ for (i = 0; i < AEAD_AES_256_GCM_SIV + 10; i++) {
+ switch (i) {
+ case AEAD_AES_SIV_CMAC_256:
+ case AEAD_AES_128_GCM_SIV:
+ continue;
+ }
+ TEST_CHECK(SIV_GetKeyLength(i) == 0);
+ TEST_CHECK(SIV_CreateInstance(i) == NULL);
+ }
+
+ for (i = 0; tests[i].algorithm != 0; i++) {
+ DEBUG_LOG("testing %d (%d)", (int)tests[i].algorithm, i);
+
+ assert(tests[i].key_length <= sizeof (tests[i].key));
+ assert(tests[i].nonce_length <= sizeof (tests[i].nonce));
+ assert(tests[i].assoc_length <= sizeof (tests[i].assoc));
+ assert(tests[i].plaintext_length <= sizeof (tests[i].plaintext));
+ assert(tests[i].ciphertext_length <= sizeof (tests[i].ciphertext));
+
+ siv = SIV_CreateInstance(tests[i].algorithm);
+
+ switch (tests[i].algorithm) {
+ case AEAD_AES_SIV_CMAC_256:
+ TEST_CHECK(siv != NULL);
+ fixed_nonce_length = 0;
+ break;
+ case AEAD_AES_128_GCM_SIV:
+ fixed_nonce_length = 1;
+ break;
+ default:
+ assert(0);
+ }
+
+ if (!siv) {
+ DEBUG_LOG("missing %d support", (int)tests[i].algorithm);
+ TEST_CHECK(SIV_GetKeyLength(tests[i].algorithm) == 0);
+ continue;
+ }
+
+ TEST_CHECK(SIV_GetKeyLength(tests[i].algorithm) == tests[i].key_length);
+ TEST_CHECK(SIV_GetMinNonceLength(siv) >= 1);
+ TEST_CHECK(SIV_GetMinNonceLength(siv) <= 12);
+ TEST_CHECK(SIV_GetMaxNonceLength(siv) >= 12);
+ TEST_CHECK(SIV_GetMinNonceLength(siv) <= SIV_GetMaxNonceLength(siv));
+ if (fixed_nonce_length)
+ TEST_CHECK(SIV_GetMinNonceLength(siv) == SIV_GetMaxNonceLength(siv));
+
+ r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].plaintext, tests[i].plaintext_length,
+ ciphertext, tests[i].ciphertext_length);
+ TEST_CHECK(!r);
+ r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].ciphertext, tests[i].ciphertext_length,
+ plaintext, tests[i].plaintext_length);
+ TEST_CHECK(!r);
+
+ for (j = -1; j < 1024; j++) {
+ r = SIV_SetKey(siv, tests[i].key, j);
+ TEST_CHECK(r == (j == tests[i].key_length));
+ }
+
+ TEST_CHECK(SIV_GetTagLength(siv) == tests[i].ciphertext_length - tests[i].plaintext_length);
+
+ r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].plaintext, tests[i].plaintext_length,
+ ciphertext, tests[i].ciphertext_length);
+ TEST_CHECK(r);
+
+#if 0
+ for (j = 0; j < tests[i].ciphertext_length; j++) {
+ printf("\\x%02x", ciphertext[j]);
+ if (j % 16 == 15)
+ printf("\n");
+ }
+ printf("\n");
+#endif
+ TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, tests[i].ciphertext_length) == 0);
+
+ for (j = -1; j < tests[i].nonce_length; j++) {
+ r = SIV_Encrypt(siv, tests[i].nonce, j,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].plaintext, tests[i].plaintext_length,
+ ciphertext, tests[i].ciphertext_length);
+ if (j > 0 && (j == tests[i].nonce_length || !fixed_nonce_length)) {
+ TEST_CHECK(r);
+ TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, tests[i].ciphertext_length) != 0);
+ } else {
+ TEST_CHECK(!r);
+ }
+ }
+
+ for (j = -1; j < tests[i].assoc_length; j++) {
+ r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, j,
+ tests[i].plaintext, tests[i].plaintext_length,
+ ciphertext, tests[i].ciphertext_length);
+ if (j >= 0) {
+ TEST_CHECK(r);
+ TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, tests[i].ciphertext_length) != 0);
+ } else {
+ TEST_CHECK(!r);
+ }
+ }
+
+ for (j = -1; j < tests[i].plaintext_length; j++) {
+ r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].plaintext, j,
+ ciphertext, j + SIV_GetTagLength(siv));
+ if (j >= 0) {
+ TEST_CHECK(r);
+ TEST_CHECK(memcmp(ciphertext, tests[i].ciphertext, j + SIV_GetTagLength(siv)) != 0);
+ } else {
+ TEST_CHECK(!r);
+ }
+ }
+
+ for (j = -1; j < 2 * tests[i].plaintext_length; j++) {
+ if (j == tests[i].plaintext_length)
+ continue;
+ r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].plaintext, j,
+ ciphertext, tests[i].ciphertext_length);
+ TEST_CHECK(!r);
+ }
+
+ for (j = -1; j < 2 * tests[i].ciphertext_length; j++) {
+ if (j == tests[i].ciphertext_length)
+ continue;
+ r = SIV_Encrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].plaintext, tests[i].plaintext_length,
+ ciphertext, j);
+ TEST_CHECK(!r);
+ }
+
+ r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].ciphertext, tests[i].ciphertext_length,
+ plaintext, tests[i].plaintext_length);
+ TEST_CHECK(r);
+ TEST_CHECK(memcmp(plaintext, tests[i].plaintext, tests[i].plaintext_length) == 0);
+
+ for (j = -1; j < tests[i].nonce_length; j++) {
+ r = SIV_Decrypt(siv, tests[i].nonce, j,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].ciphertext, tests[i].ciphertext_length,
+ plaintext, tests[i].plaintext_length);
+ TEST_CHECK(!r);
+ }
+
+ for (j = -1; j < tests[i].assoc_length; j++) {
+ r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, j,
+ tests[i].ciphertext, tests[i].ciphertext_length,
+ plaintext, tests[i].plaintext_length);
+ TEST_CHECK(!r);
+ }
+
+ for (j = -1; j < 2 * tests[i].ciphertext_length; j++) {
+ if (j == tests[i].ciphertext_length)
+ continue;
+
+ r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].ciphertext, j,
+ plaintext, tests[i].plaintext_length);
+ TEST_CHECK(!r);
+ }
+
+ for (j = -1; j < tests[i].plaintext_length; j++) {
+ r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].ciphertext, tests[i].ciphertext_length,
+ plaintext, j);
+ TEST_CHECK(!r);
+
+ r = SIV_Decrypt(siv, tests[i].nonce, tests[i].nonce_length,
+ tests[i].assoc, tests[i].assoc_length,
+ tests[i].ciphertext, j + SIV_GetTagLength(siv),
+ plaintext, j);
+ TEST_CHECK(!r);
+ }
+
+ SIV_DestroyInstance(siv);
+ }
+
+ siv = SIV_CreateInstance(tests[0].algorithm);
+ for (i = 0; i < 1000; i++) {
+ for (j = 0; tests[j].algorithm == tests[0].algorithm; j++) {
+ r = SIV_SetKey(siv, tests[j].key, tests[j].key_length);
+ TEST_CHECK(r);
+ r = SIV_Encrypt(siv, tests[j].nonce, tests[j].nonce_length,
+ tests[j].assoc, tests[j].assoc_length,
+ tests[j].plaintext, tests[j].plaintext_length,
+ ciphertext, tests[j].ciphertext_length);
+ TEST_CHECK(r);
+ r = SIV_Decrypt(siv, tests[j].nonce, tests[j].nonce_length,
+ tests[j].assoc, tests[j].assoc_length,
+ tests[j].ciphertext, tests[j].ciphertext_length,
+ plaintext, tests[j].plaintext_length);
+ TEST_CHECK(r);
+ }
+ }
+ SIV_DestroyInstance(siv);
+}
+#else
+void
+test_unit(void)
+{
+ TEST_REQUIRE(0);
+}
+#endif
diff --git a/test/unit/smooth.c b/test/unit/smooth.c
new file mode 100644
index 0000000..998a4d1
--- /dev/null
+++ b/test/unit/smooth.c
@@ -0,0 +1,63 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <smooth.c>
+#include "test.h"
+
+void
+test_unit(void)
+{
+ int i, j;
+ struct timespec ts;
+ double offset, freq, wander;
+ char conf[] = "smoothtime 300 0.01";
+
+ CNF_Initialise(0, 0);
+ CNF_ParseLine(NULL, 1, conf);
+
+ LCL_Initialise();
+ SMT_Initialise();
+ locked = 0;
+
+ for (i = 0; i < 500; i++) {
+ UTI_ZeroTimespec(&ts);
+ SMT_Reset(&ts);
+
+ DEBUG_LOG("iteration %d", i);
+
+ offset = (random() % 1000000 - 500000) / 1.0e6;
+ freq = (random() % 1000000 - 500000) / 1.0e9;
+ update_smoothing(&ts, offset, freq);
+
+ for (j = 0; j < 10000; j++) {
+ update_smoothing(&ts, 0.0, 0.0);
+ UTI_AddDoubleToTimespec(&ts, 16.0, &ts);
+ get_smoothing(&ts, &offset, &freq, &wander);
+ }
+
+ TEST_CHECK(fabs(offset) < 1e-12);
+ TEST_CHECK(fabs(freq) < 1e-12);
+ TEST_CHECK(fabs(wander) < 1e-12);
+ }
+
+ SMT_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+}
diff --git a/test/unit/socket.c b/test/unit/socket.c
new file mode 100644
index 0000000..c4edea0
--- /dev/null
+++ b/test/unit/socket.c
@@ -0,0 +1,61 @@
+/*
+ **********************************************************************
+ * Copyright (C) Luke Valenta 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <socket.c>
+#include "test.h"
+
+static void
+test_preinitialise(void)
+{
+#ifdef LINUX
+ /* Test LISTEN_FDS environment variable parsing */
+
+ /* normal */
+ putenv("LISTEN_FDS=2");
+ SCK_PreInitialise();
+ TEST_CHECK(reusable_fds == 2);
+
+ /* negative */
+ putenv("LISTEN_FDS=-2");
+ SCK_PreInitialise();
+ TEST_CHECK(reusable_fds == 0);
+
+ /* trailing characters */
+ putenv("LISTEN_FDS=2a");
+ SCK_PreInitialise();
+ TEST_CHECK(reusable_fds == 0);
+
+ /* non-integer */
+ putenv("LISTEN_FDS=a2");
+ SCK_PreInitialise();
+ TEST_CHECK(reusable_fds == 0);
+
+ /* not set */
+ unsetenv("LISTEN_FDS");
+ SCK_PreInitialise();
+ TEST_CHECK(reusable_fds == 0);
+#endif
+}
+
+void
+test_unit(void)
+{
+ test_preinitialise();
+}
diff --git a/test/unit/sources.c b/test/unit/sources.c
new file mode 100644
index 0000000..155e819
--- /dev/null
+++ b/test/unit/sources.c
@@ -0,0 +1,289 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016, 2018, 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <sources.c>
+#include "test.h"
+
+static SRC_Instance
+create_source(SRC_Type type, IPAddr *addr, int authenticated, int sel_options)
+{
+ TST_GetRandomAddress(addr, IPADDR_UNSPEC, -1);
+
+ return SRC_CreateNewInstance(UTI_IPToRefid(addr), type, authenticated, sel_options,
+ type == SRC_NTP ? addr : NULL,
+ SRC_DEFAULT_MINSAMPLES, SRC_DEFAULT_MAXSAMPLES, 0.0, 1.0);
+}
+
+void
+test_unit(void)
+{
+ SRC_AuthSelectMode sel_mode;
+ SRC_Instance srcs[16];
+ IPAddr addrs[16];
+ RPT_SourceReport report;
+ NTP_Sample sample;
+ int i, j, k, l, n1, n2, n3, n4, samples, sel_options;
+ char conf[128];
+
+ CNF_Initialise(0, 0);
+ LCL_Initialise();
+ TST_RegisterDummyDrivers();
+ SCH_Initialise();
+ SRC_Initialise();
+ REF_Initialise();
+ NSR_Initialise();
+
+ REF_SetMode(REF_ModeIgnore);
+
+ for (i = 0; i < 1000; i++) {
+ DEBUG_LOG("iteration %d", i);
+
+ for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) {
+ TEST_CHECK(n_sources == j);
+
+ sel_options = i & random() & (SRC_SELECT_NOSELECT | SRC_SELECT_PREFER |
+ SRC_SELECT_TRUST | SRC_SELECT_REQUIRE);
+
+ DEBUG_LOG("added source %d options %d", j, sel_options);
+ srcs[j] = create_source(SRC_NTP, &addrs[j], 0, sel_options);
+ SRC_UpdateReachability(srcs[j], 1);
+
+ samples = (i + j) % 5 + 3;
+
+ sample.offset = TST_GetRandomDouble(-1.0, 1.0);
+
+ for (k = 0; k < samples; k++) {
+ SCH_GetLastEventTime(&sample.time, NULL, NULL);
+ UTI_AddDoubleToTimespec(&sample.time, TST_GetRandomDouble(k - samples, k - samples + 1),
+ &sample.time);
+
+ sample.offset += TST_GetRandomDouble(-1.0e-2, 1.0e-2);
+ sample.peer_delay = TST_GetRandomDouble(1.0e-6, 1.0e-1);
+ sample.peer_dispersion = TST_GetRandomDouble(1.0e-6, 1.0e-1);
+ sample.root_delay = sample.peer_delay;
+ sample.root_dispersion = sample.peer_dispersion;
+
+ if (random() % 2)
+ SRC_UpdateStatus(srcs[j], 1, random() % 4);
+
+ DEBUG_LOG("source %d sample %d offset %f delay %f disp %f", j, k,
+ sample.offset, sample.peer_delay, sample.peer_dispersion);
+
+ SRC_AccumulateSample(srcs[j], &sample);
+ }
+
+ for (k = 0; k <= j; k++) {
+ int passed = 0, trusted = 0, trusted_passed = 0, required = 0, required_passed = 0;
+ double trusted_lo = DBL_MAX, trusted_hi = DBL_MIN;
+ double passed_lo = DBL_MAX, passed_hi = DBL_MIN;
+
+ SRC_SelectSource(srcs[k]);
+ DEBUG_LOG("source %d status %c", k, get_status_char(sources[k]->status));
+
+ for (l = 0; l <= j; l++) {
+ TEST_CHECK(sources[l]->status > SRC_OK && sources[l]->status <= SRC_SELECTED);
+ if (sources[l]->sel_options & SRC_SELECT_NOSELECT) {
+ TEST_CHECK(sources[l]->status == SRC_UNSELECTABLE);
+ } else if (sources[l]->leap == LEAP_Unsynchronised) {
+ TEST_CHECK(sources[l]->status == SRC_UNSYNCHRONISED);
+ } else if (sources[l]->status != SRC_BAD_DISTANCE) {
+ if (sources[l]->status >= SRC_NONPREFERRED) {
+ passed++;
+ if (passed_lo > sources[l]->sel_info.lo_limit)
+ passed_lo = sources[l]->sel_info.lo_limit;
+ if (passed_hi < sources[l]->sel_info.hi_limit)
+ passed_hi = sources[l]->sel_info.hi_limit;
+ }
+ if (sources[l]->sel_options & SRC_SELECT_TRUST) {
+ trusted++;
+ if (trusted_lo > sources[l]->sel_info.lo_limit)
+ trusted_lo = sources[l]->sel_info.lo_limit;
+ if (trusted_hi < sources[l]->sel_info.hi_limit)
+ trusted_hi = sources[l]->sel_info.hi_limit;
+ if (sources[l]->status >= SRC_NONPREFERRED)
+ trusted_passed++;
+ }
+ if (sources[l]->sel_options & SRC_SELECT_REQUIRE) {
+ required++;
+ if (sources[l]->status >= SRC_NONPREFERRED)
+ required_passed++;
+ }
+ if (sources[l]->sel_options & SRC_SELECT_PREFER)
+ TEST_CHECK(sources[l]->status != SRC_NONPREFERRED);
+ }
+ }
+
+ DEBUG_LOG("sources %d passed %d trusted %d/%d required %d/%d", j + 1, passed,
+ trusted_passed, trusted, required_passed, required);
+
+ TEST_CHECK(!trusted || !passed || (passed_lo >= trusted_lo && passed_hi <= trusted_hi));
+ TEST_CHECK(!passed || !trusted || trusted_passed >= 1);
+ TEST_CHECK(!passed || !required || required_passed > 0);
+
+ for (l = 0; l <= j; l++) {
+ TEST_CHECK(sources[l]->leap_vote ==
+ (sources[l]->status >= SRC_NONPREFERRED &&
+ (!trusted || sources[l]->sel_options & SRC_SELECT_TRUST)));
+ }
+ }
+ }
+
+ for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) {
+ SRC_ReportSource(j, &report, &sample.time);
+ SRC_DestroyInstance(srcs[j]);
+ }
+ }
+
+ for (i = 0; i < 16; i++) {
+ DEBUG_LOG("iteration %d", i);
+
+ for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) {
+ TEST_CHECK(n_sources == j);
+
+ srcs[j] = create_source(SRC_NTP, &addrs[j], 0, 0);
+ SRC_UpdateReachability(srcs[j], 1);
+
+ samples = 8;
+ for (k = 0; k < samples; k++) {
+ SCH_GetLastEventTime(&sample.time, NULL, NULL);
+ UTI_AddDoubleToTimespec(&sample.time, k - samples, &sample.time);
+ if (j != 0)
+ UTI_AddDoubleToTimespec(&sample.time, -i * (1.0e-3 / LCL_GetMaxClockError()), &sample.time);
+
+ sample.offset = (k % 2) * 1e-8;
+ sample.peer_delay = sample.root_delay = 2.0e-3 * (j + 1);
+ sample.peer_dispersion = sample.root_dispersion = 4.0e-3;
+
+ SRC_AccumulateSample(srcs[j], &sample);
+ SRC_UpdateStatus(srcs[j], 1, LEAP_Normal);
+ }
+ }
+
+ SRC_SelectSource(srcs[0]);
+
+ for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) {
+ DEBUG_LOG("%d %c %f", j, get_status_char(srcs[j]->status),
+ srcs[j]->sel_info.root_distance);
+ if (j == 0)
+ TEST_CHECK(sources[j]->status == SRC_SELECTED);
+ else if (j < 11 - i)
+ TEST_CHECK(sources[j]->status == SRC_UNSELECTED);
+ else
+ TEST_CHECK(sources[j]->status == SRC_DISTANT);
+ }
+
+ for (j = 0; j < sizeof (srcs) / sizeof (srcs[0]); j++) {
+ SCH_GetLastEventTime(&sample.time, NULL, NULL);
+ SRC_ReportSource(j, &report, &sample.time);
+ SRC_DestroyInstance(srcs[j]);
+ }
+ }
+
+ TEST_CHECK(CNF_GetAuthSelectMode() == SRC_AUTHSELECT_MIX);
+
+ for (i = 0; i < 1000; i++) {
+ DEBUG_LOG("iteration %d", i);
+
+ switch (i % 4) {
+ case 0:
+ snprintf(conf, sizeof (conf), "authselectmode require");
+ sel_mode = SRC_AUTHSELECT_REQUIRE;
+ break;
+ case 1:
+ snprintf(conf, sizeof (conf), "authselectmode prefer");
+ sel_mode = SRC_AUTHSELECT_PREFER;
+ break;
+ case 2:
+ snprintf(conf, sizeof (conf), "authselectmode mix");
+ sel_mode = SRC_AUTHSELECT_MIX;
+ break;
+ case 3:
+ snprintf(conf, sizeof (conf), "authselectmode ignore");
+ sel_mode = SRC_AUTHSELECT_IGNORE;
+ break;
+ }
+
+ CNF_ParseLine(NULL, 0, conf);
+ TEST_CHECK(CNF_GetAuthSelectMode() == sel_mode);
+
+ sel_options = random() & (SRC_SELECT_PREFER | SRC_SELECT_TRUST | SRC_SELECT_REQUIRE);
+
+ n1 = random() % 3;
+ n2 = random() % 3;
+ n3 = random() % 3;
+ n4 = random() % 3;
+ assert(n1 + n2 + n3 < sizeof (srcs) / sizeof (srcs[0]));
+
+ for (j = 0; j < n1; j++)
+ srcs[j] = create_source(SRC_REFCLOCK, &addrs[j], random() % 2, sel_options);
+ for (; j < n1 + n2; j++)
+ srcs[j] = create_source(SRC_NTP, &addrs[j], 1, sel_options);
+ for (; j < n1 + n2 + n3; j++)
+ srcs[j] = create_source(SRC_NTP, &addrs[j], 0, sel_options);
+ for (; j < n1 + n2 + n3 + n4; j++)
+ srcs[j] = create_source(random() % 2 ? SRC_REFCLOCK : SRC_NTP, &addrs[j],
+ random() % 2, sel_options | SRC_SELECT_NOSELECT);
+
+ switch (sel_mode) {
+ case SRC_AUTHSELECT_IGNORE:
+ for (j = 0; j < n1 + n2 + n3; j++)
+ TEST_CHECK(srcs[j]->sel_options == sel_options);
+ break;
+ case SRC_AUTHSELECT_MIX:
+ for (j = 0; j < n1 + n2; j++)
+ TEST_CHECK(srcs[j]->sel_options ==
+ (sel_options | (n2 > 0 && n3 > 0 ? SRC_SELECT_REQUIRE | SRC_SELECT_TRUST : 0)));
+ for (; j < n1 + n2 + n3; j++)
+ TEST_CHECK(srcs[j]->sel_options == sel_options);
+ break;
+ case SRC_AUTHSELECT_PREFER:
+ for (j = 0; j < n1 + n2; j++)
+ TEST_CHECK(srcs[j]->sel_options == sel_options);
+ for (; j < n1 + n2 + n3; j++)
+ TEST_CHECK(srcs[j]->sel_options == (sel_options | (n2 > 0 ? SRC_SELECT_NOSELECT : 0)));
+ break;
+ case SRC_AUTHSELECT_REQUIRE:
+ for (j = 0; j < n1 + n2; j++)
+ TEST_CHECK(srcs[j]->sel_options == sel_options);
+ for (; j < n1 + n2 + n3; j++)
+ TEST_CHECK(srcs[j]->sel_options == (sel_options | SRC_SELECT_NOSELECT));
+ break;
+ default:
+ assert(0);
+ }
+
+ for (j = n1 + n2 + n3; j < n1 + n2 + n3 + n4; j++)
+ TEST_CHECK(srcs[j]->sel_options == (sel_options | SRC_SELECT_NOSELECT));
+
+ for (j = n1 + n2 + n3 + n4 - 1; j >= 0; j--) {
+ if (j < n1 + n2)
+ TEST_CHECK(srcs[j]->sel_options == sel_options);
+ SRC_DestroyInstance(srcs[j]);
+ }
+ }
+
+ NSR_Finalise();
+ REF_Finalise();
+ SRC_Finalise();
+ SCH_Finalise();
+ LCL_Finalise();
+ CNF_Finalise();
+ HSH_Finalise();
+}
diff --git a/test/unit/test.c b/test/unit/test.c
new file mode 100644
index 0000000..1ebb11a
--- /dev/null
+++ b/test/unit/test.c
@@ -0,0 +1,182 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <config.h>
+#include <sysincl.h>
+#include <logging.h>
+#include <localp.h>
+#include <util.h>
+
+#include "test.h"
+
+void
+TST_Fail(int line)
+{
+ printf("FAIL (on line %d)\n", line);
+ exit(1);
+}
+
+void
+TST_Skip(int line)
+{
+ printf("SKIP (on line %d)\n", line);
+ exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+ LOG_Severity log_severity;
+ char *test_name, *s;
+ int i, seed = 0;
+ struct timeval tv;
+
+ test_name = argv[0];
+ s = strrchr(test_name, '.');
+ if (s)
+ *s = '\0';
+ s = strrchr(test_name, '/');
+ if (s)
+ test_name = s + 1;
+
+ log_severity = LOGS_FATAL;
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-d")) {
+ log_severity = LOGS_DEBUG;
+ } else if (!strcmp(argv[i], "-s") && i + 1 < argc) {
+ seed = atoi(argv[++i]);
+ } else {
+ fprintf(stderr, "Unknown option\n");
+ exit(1);
+ }
+ }
+
+ gettimeofday(&tv, NULL);
+ srandom(seed ? seed : tv.tv_sec ^ (tv.tv_usec << 10));
+
+ printf("Testing %-30s ", test_name);
+ fflush(stdout);
+
+ LOG_Initialise();
+ LOG_SetMinSeverity(log_severity);
+
+ test_unit();
+
+ UTI_ResetGetRandomFunctions();
+ LOG_Finalise();
+
+ printf("PASS\n");
+
+ return 0;
+}
+
+double
+TST_GetRandomDouble(double min, double max)
+{
+ return min + random() / 2147483647.0 * (max - min);
+}
+
+void
+TST_GetRandomAddress(IPAddr *ip, int family, int bits)
+{
+ if (family != IPADDR_INET4 && family != IPADDR_INET6)
+ family = random() % 2 ? IPADDR_INET4 : IPADDR_INET6;
+
+ ip->family = family;
+
+ if (family == IPADDR_INET4) {
+ if (bits < 0)
+ bits = 32;
+ assert(bits <= 32);
+
+ if (bits > 16)
+ ip->addr.in4 = (uint32_t)random() % (1U << (bits - 16)) << 16 |
+ (uint32_t)random() % (1U << 16);
+ else
+ ip->addr.in4 = (uint32_t)random() % (1U << bits);
+ } else {
+ int i, b;
+
+ if (bits < 0)
+ bits = 128;
+ assert(bits <= 128);
+
+ for (i = 0, b = 120; i < 16; i++, b -= 8) {
+ if (b >= bits) {
+ ip->addr.in6[i] = 0;
+ } else {
+ ip->addr.in6[i] = random() % (1U << MIN(bits - b, 8));
+ }
+ }
+ }
+}
+
+void
+TST_SwapAddressBit(IPAddr *ip, unsigned int b)
+{
+ if (ip->family == IPADDR_INET4) {
+ assert(b < 32);
+ ip->addr.in4 ^= 1U << (31 - b);
+ } else if (ip->family == IPADDR_INET6) {
+ assert(b < 128);
+ ip->addr.in6[b / 8] ^= 1U << (7 - b % 8);
+ } else {
+ assert(0);
+ }
+}
+
+static double
+read_frequency(void)
+{
+ return 0.0;
+}
+
+static double
+set_frequency(double freq_ppm)
+{
+ return 0.0;
+}
+
+static void
+accrue_offset(double offset, double corr_rate)
+{
+}
+
+static int
+apply_step_offset(double offset)
+{
+ return 0;
+}
+
+static void
+offset_convert(struct timespec *raw, double *corr, double *err)
+{
+ *corr = 0.0;
+ if (err)
+ *err = 0.0;
+}
+
+void
+TST_RegisterDummyDrivers(void)
+{
+ lcl_RegisterSystemDrivers(read_frequency, set_frequency, accrue_offset,
+ apply_step_offset, offset_convert, NULL, NULL);
+}
diff --git a/test/unit/test.h b/test/unit/test.h
new file mode 100644
index 0000000..00d261d
--- /dev/null
+++ b/test/unit/test.h
@@ -0,0 +1,52 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2016
+ *
+ * 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.
+ *
+ **********************************************************************
+ */
+
+#ifndef GOT_TEST_H
+#define GOT_TEST_H
+
+#include <addressing.h>
+
+extern void test_unit(void);
+
+#define TEST_CHECK(expr) \
+ do { \
+ if (!(expr)) { \
+ TST_Fail(__LINE__); \
+ exit(1); \
+ } \
+ } while (0)
+
+#define TEST_REQUIRE(expr) \
+ do { \
+ if (!(expr)) { \
+ TST_Skip(__LINE__); \
+ exit(0); \
+ } \
+ } while (0)
+
+extern void TST_Fail(int line);
+extern void TST_Skip(int line);
+
+extern double TST_GetRandomDouble(double min, double max);
+extern void TST_GetRandomAddress(IPAddr *ip, int family, int bits);
+extern void TST_SwapAddressBit(IPAddr *ip, unsigned int b);
+extern void TST_RegisterDummyDrivers(void);
+
+#endif
diff --git a/test/unit/util.c b/test/unit/util.c
new file mode 100644
index 0000000..d52a268
--- /dev/null
+++ b/test/unit/util.c
@@ -0,0 +1,801 @@
+/*
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2017-2018, 2021, 2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+ */
+
+#include <util.c>
+#include "test.h"
+
+static volatile int handled_signal = 0;
+
+static void
+handle_signal(int signal)
+{
+ handled_signal = signal;
+}
+
+void
+test_unit(void)
+{
+ struct timespec ts, ts2, ts3, ts4;
+ char buf[16], *s, *s2, *words[3];
+ NTP_int64 ntp_ts, ntp_ts2, ntp_fuzz;
+ NTP_int32 ntp32_ts;
+ struct timeval tv;
+ double x, y, nan, inf;
+ IPAddr ip, ip2, ip3;
+ IPSockAddr ip_saddr;
+ Integer64 integer64;
+ Timespec tspec;
+ Float f;
+ int i, j, c;
+ uid_t uid;
+ gid_t gid;
+ struct stat st;
+ FILE *file;
+
+ for (i = -31; i < 31; i++) {
+ x = pow(2.0, i);
+ y = UTI_Log2ToDouble(i);
+ TEST_CHECK(y / x > 0.99999 && y / x < 1.00001);
+ }
+
+ for (i = -89; i < 63; i++) {
+ x = pow(2.0, i);
+ y = UTI_FloatNetworkToHost(UTI_FloatHostToNetwork(x));
+ TEST_CHECK(y / x > 0.99999 && y / x < 1.00001);
+ }
+
+ for (i = 0; i < 100000; i++) {
+ x = TST_GetRandomDouble(-1000.0, 1000.0);
+ y = UTI_FloatNetworkToHost(UTI_FloatHostToNetwork(x));
+ TEST_CHECK(y / x > 0.99999 && y / x < 1.00001);
+
+ UTI_GetRandomBytes(&f, sizeof (f));
+ x = UTI_FloatNetworkToHost(f);
+ TEST_CHECK(x > 0.0 || x <= 0.0);
+ }
+
+ TEST_CHECK(UTI_DoubleToNtp32(1.0) == htonl(65536));
+ TEST_CHECK(UTI_DoubleToNtp32(0.0) == htonl(0));
+ TEST_CHECK(UTI_DoubleToNtp32(1.0 / (65536.0)) == htonl(1));
+ TEST_CHECK(UTI_DoubleToNtp32(1.000001 / (65536.0)) == htonl(2));
+ TEST_CHECK(UTI_DoubleToNtp32(1.000001) == htonl(65537));
+ TEST_CHECK(UTI_DoubleToNtp32(1000000) == htonl(0xffffffff));
+ TEST_CHECK(UTI_DoubleToNtp32(-1.0) == htonl(0));
+
+ UTI_DoubleToTimeval(0.4e-6, &tv);
+ TEST_CHECK(tv.tv_sec == 0);
+ TEST_CHECK(tv.tv_usec == 0);
+ UTI_DoubleToTimeval(-0.4e-6, &tv);
+ TEST_CHECK(tv.tv_sec == 0);
+ TEST_CHECK(tv.tv_usec == 0);
+ UTI_DoubleToTimeval(0.5e-6, &tv);
+ TEST_CHECK(tv.tv_sec == 0);
+ TEST_CHECK(tv.tv_usec == 1);
+ UTI_DoubleToTimeval(-0.5e-6, &tv);
+ TEST_CHECK(tv.tv_sec == -1);
+ TEST_CHECK(tv.tv_usec == 999999);
+
+ UTI_DoubleToTimespec(0.9e-9, &ts);
+ TEST_CHECK(ts.tv_sec == 0);
+ TEST_CHECK(ts.tv_nsec == 0);
+ UTI_DoubleToTimespec(1.0e-9, &ts);
+ TEST_CHECK(ts.tv_sec == 0);
+ TEST_CHECK(ts.tv_nsec == 1);
+ UTI_DoubleToTimespec(-0.9e-9, &ts);
+ TEST_CHECK(ts.tv_sec == 0);
+ TEST_CHECK(ts.tv_nsec == 0);
+ UTI_DoubleToTimespec(-1.0e-9, &ts);
+ TEST_CHECK(ts.tv_sec == -1);
+ TEST_CHECK(ts.tv_nsec == 999999999);
+
+ ntp_ts.hi = htonl(JAN_1970);
+ ntp_ts.lo = 0xffffffff;
+ UTI_Ntp64ToTimespec(&ntp_ts, &ts);
+#if defined(HAVE_LONG_TIME_T) && NTP_ERA_SPLIT > 0
+ TEST_CHECK(ts.tv_sec == 0x100000000LL * (1 + (NTP_ERA_SPLIT - 1) / 0x100000000LL));
+#else
+ TEST_CHECK(ts.tv_sec == 0);
+#endif
+ TEST_CHECK(ts.tv_nsec == 999999999);
+
+ ntp_ts.hi = htonl(JAN_1970 - 1);
+ ntp_ts.lo = htonl(0xffffffff);
+ ntp_ts2.hi = htonl(JAN_1970 + 1);
+ ntp_ts2.lo = htonl(0x80000000);
+ TEST_CHECK(fabs(UTI_DiffNtp64ToDouble(&ntp_ts, &ntp_ts2) + 1.5) < 1e-9);
+ TEST_CHECK(fabs(UTI_DiffNtp64ToDouble(&ntp_ts2, &ntp_ts) - 1.5) < 1e-9);
+
+ UTI_AddDoubleToTimespec(&ts, 1e-9, &ts);
+#if defined(HAVE_LONG_TIME_T) && NTP_ERA_SPLIT > 0
+ TEST_CHECK(ts.tv_sec == 1 + 0x100000000LL * (1 + (NTP_ERA_SPLIT - 1) / 0x100000000LL));
+#else
+ TEST_CHECK(ts.tv_sec == 1);
+#endif
+ TEST_CHECK(ts.tv_nsec == 0);
+
+ ntp_fuzz.hi = 0;
+ ntp_fuzz.lo = htonl(0xff1234ff);
+
+ UTI_TimespecToNtp64(&ts, &ntp_ts, &ntp_fuzz);
+ TEST_CHECK(ntp_ts.hi == htonl(JAN_1970 + 1));
+ TEST_CHECK(ntp_ts.lo == ntp_fuzz.lo);
+
+ ts.tv_sec = ts.tv_nsec = 1;
+ UTI_ZeroTimespec(&ts);
+ TEST_CHECK(ts.tv_sec == 0);
+ TEST_CHECK(ts.tv_nsec == 0);
+ TEST_CHECK(UTI_IsZeroTimespec(&ts));
+
+ ntp_ts.hi = ntp_ts.lo == 1;
+ UTI_ZeroNtp64(&ntp_ts);
+ TEST_CHECK(ntp_ts.hi == 0);
+ TEST_CHECK(ntp_ts.lo == 0);
+
+ tv.tv_sec = tv.tv_usec = 1;
+ UTI_TimevalToTimespec(&tv, &ts);
+ TEST_CHECK(ts.tv_sec == 1);
+ TEST_CHECK(ts.tv_nsec == 1000);
+
+ UTI_TimespecToTimeval(&ts, &tv);
+ TEST_CHECK(tv.tv_sec == 1);
+ TEST_CHECK(tv.tv_usec == 1);
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 500000000;
+ TEST_CHECK(fabs(UTI_TimespecToDouble(&ts) - 1.5) < 1.0e-15);
+
+ UTI_DoubleToTimespec(2.75, &ts);
+ TEST_CHECK(ts.tv_sec == 2);
+ TEST_CHECK(ts.tv_nsec == 750000000);
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 1200000000;
+ UTI_NormaliseTimespec(&ts);
+ TEST_CHECK(ts.tv_sec == 2);
+ TEST_CHECK(ts.tv_nsec == 200000000);
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = -200000000;
+ UTI_NormaliseTimespec(&ts);
+ TEST_CHECK(ts.tv_sec == 0);
+ TEST_CHECK(ts.tv_nsec == 800000000);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 500000;
+ TEST_CHECK(fabs(UTI_TimevalToDouble(&tv) - 1.5) < 1.0e-15);
+
+ UTI_DoubleToTimeval(2.75, &tv);
+ TEST_CHECK(tv.tv_sec == 2);
+ TEST_CHECK(tv.tv_usec == 750000);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 1200000;
+ UTI_NormaliseTimeval(&tv);
+ TEST_CHECK(tv.tv_sec == 2);
+ TEST_CHECK(tv.tv_usec == 200000);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = -200000;
+ UTI_NormaliseTimeval(&tv);
+ TEST_CHECK(tv.tv_sec == 0);
+ TEST_CHECK(tv.tv_usec == 800000);
+
+ UTI_ZeroTimespec(&ts);
+ UTI_TimespecToNtp64(&ts, &ntp_ts, &ntp_fuzz);
+ TEST_CHECK(ntp_ts.hi == 0);
+ TEST_CHECK(ntp_ts.lo == 0);
+
+ TEST_CHECK(UTI_IsZeroNtp64(&ntp_ts));
+
+ ts.tv_sec = 1;
+ ntp_ts.hi = htonl(1);
+
+ TEST_CHECK(!UTI_IsZeroTimespec(&ts));
+ TEST_CHECK(!UTI_IsZeroNtp64(&ntp_ts));
+
+ ts.tv_sec = 0;
+ ntp_ts.hi = 0;
+ ts.tv_nsec = 1;
+ ntp_ts.lo = htonl(1);
+
+ TEST_CHECK(!UTI_IsZeroTimespec(&ts));
+ TEST_CHECK(!UTI_IsZeroNtp64(&ntp_ts));
+
+ ntp_ts.hi = 0;
+ ntp_ts.lo = 0;
+
+ UTI_Ntp64ToTimespec(&ntp_ts, &ts);
+ TEST_CHECK(UTI_IsZeroTimespec(&ts));
+ UTI_TimespecToNtp64(&ts, &ntp_ts, NULL);
+ TEST_CHECK(UTI_IsZeroNtp64(&ntp_ts));
+
+ ntp_fuzz.hi = htonl(1);
+ ntp_fuzz.lo = htonl(3);
+ ntp_ts.hi = htonl(1);
+ ntp_ts.lo = htonl(2);
+
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts) == 0);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_fuzz) < 0);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_fuzz, &ntp_ts) > 0);
+
+ ntp_ts.hi = htonl(0x80000002);
+ ntp_ts.lo = htonl(2);
+
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts) == 0);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_fuzz) < 0);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_fuzz, &ntp_ts) > 0);
+
+ ntp_fuzz.hi = htonl(0x90000001);
+
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts) == 0);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_fuzz) < 0);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_fuzz, &ntp_ts) > 0);
+
+ TEST_CHECK(UTI_IsEqualAnyNtp64(&ntp_ts, &ntp_ts, NULL, NULL));
+ TEST_CHECK(UTI_IsEqualAnyNtp64(&ntp_ts, NULL, &ntp_ts, NULL));
+ TEST_CHECK(UTI_IsEqualAnyNtp64(&ntp_ts, NULL, NULL, &ntp_ts));
+ TEST_CHECK(!UTI_IsEqualAnyNtp64(&ntp_ts, &ntp_fuzz, &ntp_fuzz, &ntp_fuzz));
+
+ ntp_ts.hi = htonl(0);
+ ntp_ts.lo = htonl(0);
+ x = UTI_Ntp64ToDouble(&ntp_ts);
+ TEST_CHECK(fabs(x) < 1e-10);
+ UTI_DoubleToNtp64(x, &ntp_ts2);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts2) == 0);
+
+ ntp_ts.hi = htonl(0);
+ ntp_ts.lo = htonl(0xffffffff);
+ x = UTI_Ntp64ToDouble(&ntp_ts);
+ TEST_CHECK(fabs(x - 1.0 + 0.23e-9) < 1e-10);
+ UTI_DoubleToNtp64(x, &ntp_ts2);
+ TEST_CHECK(fabs(UTI_DiffNtp64ToDouble(&ntp_ts, &ntp_ts2)) < 0.3e-9);
+
+ ntp_ts.hi = htonl(0xffffffff);
+ ntp_ts.lo = htonl(0xffffffff);
+ x = UTI_Ntp64ToDouble(&ntp_ts);
+ TEST_CHECK(fabs(x + 0.23e-9) < 1e-10);
+ UTI_DoubleToNtp64(x, &ntp_ts2);
+ TEST_CHECK(fabs(UTI_DiffNtp64ToDouble(&ntp_ts, &ntp_ts2)) < 0.3e-9);
+
+ ntp_ts.hi = htonl(0x80000000);
+ ntp_ts.lo = htonl(0);
+ x = UTI_Ntp64ToDouble(&ntp_ts);
+ TEST_CHECK(fabs(x + 0x80000000) < 1e-10);
+ UTI_DoubleToNtp64(x, &ntp_ts2);
+ TEST_CHECK(fabs(UTI_DiffNtp64ToDouble(&ntp_ts, &ntp_ts2)) < 0.3e-9);
+
+ ntp_ts.hi = htonl(0x7fffffff);
+ ntp_ts.lo = htonl(0xffffffff);
+ x = UTI_Ntp64ToDouble(&ntp_ts);
+ TEST_CHECK(fabs(x - 2147483648) < 1.0);
+
+ ntp_ts.lo = htonl(0);
+ ntp_ts.hi = htonl(0x7fffffff);
+ UTI_DoubleToNtp64(0x7fffffff + 0.1, &ntp_ts2);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts2) == 0);
+ ntp_ts.hi = htonl(0x80000000);
+ UTI_DoubleToNtp64(0x80000000 - 0.1, &ntp_ts);
+ TEST_CHECK(UTI_CompareNtp64(&ntp_ts, &ntp_ts2) == 0);
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 2;
+ ts2.tv_sec = 1;
+ ts2.tv_nsec = 3;
+
+ TEST_CHECK(UTI_CompareTimespecs(&ts, &ts) == 0);
+ TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) < 0);
+ TEST_CHECK(UTI_CompareTimespecs(&ts2, &ts) > 0);
+
+ ts2.tv_sec = 2;
+
+ TEST_CHECK(UTI_CompareTimespecs(&ts, &ts) == 0);
+ TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) < 0);
+ TEST_CHECK(UTI_CompareTimespecs(&ts2, &ts) > 0);
+
+ ts.tv_sec = 2;
+ ts.tv_nsec = 250000000;
+ ts2.tv_sec = 1;
+ ts2.tv_nsec = 750000000;
+ UTI_DiffTimespecs(&ts3, &ts, &ts2);
+ TEST_CHECK(ts3.tv_sec == 0);
+ TEST_CHECK(ts3.tv_nsec == 500000000);
+ TEST_CHECK(fabs(UTI_DiffTimespecsToDouble(&ts, &ts2) - 0.5) < 1.0e-15);
+
+ ts.tv_sec = 2;
+ ts.tv_nsec = 250000000;
+ ts2.tv_sec = 3;
+ ts2.tv_nsec = 750000000;
+ UTI_DiffTimespecs(&ts3, &ts, &ts2);
+ TEST_CHECK(ts3.tv_sec == -2);
+ TEST_CHECK(ts3.tv_nsec == 500000000);
+ TEST_CHECK(fabs(UTI_DiffTimespecsToDouble(&ts, &ts2) - -1.5) < 1.0e-15);
+
+ ts.tv_sec = 2;
+ ts.tv_nsec = 250000000;
+ UTI_AddDoubleToTimespec(&ts, 2.5, &ts2);
+ TEST_CHECK(ts2.tv_sec == 4);
+ TEST_CHECK(ts2.tv_nsec == 750000000);
+
+ ts.tv_sec = 4;
+ ts.tv_nsec = 500000000;
+ ts2.tv_sec = 1;
+ ts2.tv_nsec = 750000000;
+ UTI_AverageDiffTimespecs(&ts, &ts2, &ts3, &x);
+ TEST_CHECK(ts3.tv_sec == 3);
+ TEST_CHECK(ts3.tv_nsec == 125000000);
+ TEST_CHECK(x == -2.75);
+
+ ts.tv_sec = 4;
+ ts.tv_nsec = 500000000;
+ ts2.tv_sec = 1;
+ ts2.tv_nsec = 750000000;
+ ts3.tv_sec = 5;
+ ts3.tv_nsec = 250000000;
+ UTI_AddDiffToTimespec(&ts, &ts2, &ts3, &ts4);
+ TEST_CHECK(ts4.tv_sec == 8);
+ TEST_CHECK(ts4.tv_nsec == 0);
+
+ ts.tv_sec = 1600000000;
+ ts.tv_nsec = 123;
+ s = UTI_TimespecToString(&ts);
+ TEST_CHECK(strcmp(s, "1600000000.000000123") == 0);
+
+ ntp_ts.hi = 1;
+ ntp_ts.hi = 2;
+ UTI_Ntp64ToTimespec(&ntp_ts, &ts);
+ s = UTI_Ntp64ToString(&ntp_ts);
+ s2 = UTI_TimespecToString(&ts);
+ TEST_CHECK(strcmp(s, s2) == 0);
+
+ s = UTI_RefidToString(0x41424344);
+ TEST_CHECK(strcmp(s, "ABCD") == 0);
+
+ ip.family = IPADDR_UNSPEC;
+ s = UTI_IPToString(&ip);
+ TEST_CHECK(strcmp(s, "[UNSPEC]") == 0);
+ TEST_CHECK(UTI_IPToRefid(&ip) == 0);
+ TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip));
+
+ ip.family = IPADDR_INET4;
+ ip.addr.in4 = 0x7f010203;
+ s = UTI_IPToString(&ip);
+ TEST_CHECK(strcmp(s, "127.1.2.3") == 0);
+ TEST_CHECK(UTI_IPToRefid(&ip) == 0x7f010203);
+ TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip));
+
+ ip.family = IPADDR_INET6;
+ memset(&ip.addr.in6, 0, sizeof (ip.addr.in6));
+ ip.addr.in6[0] = 0xab;
+ ip.addr.in6[15] = 0xcd;
+ s = UTI_IPToString(&ip);
+#ifdef FEAT_IPV6
+ TEST_CHECK(strcmp(s, "ab00::cd") == 0);
+#else
+ TEST_CHECK(strcmp(s, "ab00:0000:0000:0000:0000:0000:0000:00cd") == 0);
+#endif
+ TEST_CHECK(UTI_IPToRefid(&ip) == 0x5f9aa602);
+ TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip));
+
+ ip.family = IPADDR_ID;
+ ip.addr.id = 12345;
+ s = UTI_IPToString(&ip);
+ TEST_CHECK(strcmp(s, "ID#0000012345") == 0);
+ TEST_CHECK(UTI_IPToRefid(&ip) == 0);
+ TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip));
+
+ ip.family = IPADDR_UNSPEC + 10;
+ s = UTI_IPToString(&ip);
+ TEST_CHECK(strcmp(s, "[UNKNOWN]") == 0);
+ TEST_CHECK(UTI_IPToRefid(&ip) == 0);
+ TEST_CHECK(UTI_IPToHash(&ip) == UTI_IPToHash(&ip));
+
+ TEST_CHECK(UTI_StringToIP("200.4.5.6", &ip));
+ TEST_CHECK(ip.family == IPADDR_INET4);
+ TEST_CHECK(ip.addr.in4 == 0xc8040506);
+
+#ifdef FEAT_IPV6
+ TEST_CHECK(UTI_StringToIP("1234::7890", &ip));
+ TEST_CHECK(ip.family == IPADDR_INET6);
+ TEST_CHECK(ip.addr.in6[0] == 0x12 && ip.addr.in6[1] == 0x34);
+ TEST_CHECK(ip.addr.in6[2] == 0x00 && ip.addr.in6[13] == 0x00);
+ TEST_CHECK(ip.addr.in6[14] == 0x78 && ip.addr.in6[15] == 0x90);
+#else
+ TEST_CHECK(!UTI_StringToIP("1234::7890", &ip));
+#endif
+
+ TEST_CHECK(!UTI_StringToIP("ID#0000012345", &ip));
+
+ TEST_CHECK(UTI_IsStringIP("1.2.3.4"));
+ TEST_CHECK(!UTI_IsStringIP("127.3.3"));
+ TEST_CHECK(!UTI_IsStringIP("127.3"));
+ TEST_CHECK(!UTI_IsStringIP("127"));
+#ifdef FEAT_IPV6
+ TEST_CHECK(UTI_IsStringIP("1234:5678::aaaa"));
+#else
+ TEST_CHECK(!UTI_IsStringIP("1234:5678::aaaa"));
+#endif
+ TEST_CHECK(!UTI_StringToIP("ID#0000012345", &ip));
+
+ TEST_CHECK(!UTI_StringToIdIP("1.2.3.4", &ip));
+ TEST_CHECK(UTI_StringToIdIP("ID#0000056789", &ip));
+ TEST_CHECK(ip.family == IPADDR_ID);
+ TEST_CHECK(ip.addr.id == 56789);
+
+ for (i = IPADDR_UNSPEC; i <= IPADDR_ID + 1; i++) {
+ ip.family = i;
+ TEST_CHECK(UTI_IsIPReal(&ip) == (i == IPADDR_INET4 || i == IPADDR_INET6));
+ }
+
+ ip.family = IPADDR_UNSPEC;
+ UTI_IPHostToNetwork(&ip, &ip2);
+ TEST_CHECK(ip2.family == htons(IPADDR_UNSPEC));
+ UTI_IPNetworkToHost(&ip2, &ip3);
+ TEST_CHECK(ip3.family == IPADDR_UNSPEC);
+
+ ip.family = IPADDR_INET4;
+ ip.addr.in4 = 0x12345678;
+ UTI_IPHostToNetwork(&ip, &ip2);
+ TEST_CHECK(ip2.family == htons(IPADDR_INET4));
+ TEST_CHECK(ip2.addr.in4 == htonl(0x12345678));
+ UTI_IPNetworkToHost(&ip2, &ip3);
+ TEST_CHECK(ip3.family == IPADDR_INET4);
+ TEST_CHECK(ip3.addr.in4 == 0x12345678);
+
+ ip.family = IPADDR_INET6;
+ for (i = 0; i < 16; i++)
+ ip.addr.in6[i] = i;
+ UTI_IPHostToNetwork(&ip, &ip2);
+ TEST_CHECK(ip2.family == htons(IPADDR_INET6));
+ for (i = 0; i < 16; i++)
+ TEST_CHECK(ip.addr.in6[i] == i);
+ UTI_IPNetworkToHost(&ip2, &ip3);
+ TEST_CHECK(ip3.family == IPADDR_INET6);
+ for (i = 0; i < 16; i++)
+ TEST_CHECK(ip.addr.in6[i] == i);
+
+ ip.family = IPADDR_ID;
+ ip.addr.in4 = 0x87654321;
+ UTI_IPHostToNetwork(&ip, &ip2);
+ TEST_CHECK(ip2.family == htons(IPADDR_ID));
+ TEST_CHECK(ip2.addr.in4 == htonl(0x87654321));
+ UTI_IPNetworkToHost(&ip2, &ip3);
+ TEST_CHECK(ip3.family == IPADDR_ID);
+ TEST_CHECK(ip3.addr.in4 == 0x87654321);
+
+ for (i = 0; i < 16; i++)
+ ip.addr.in6[i] = 0x80;
+ ip2 = ip;
+
+ for (i = IPADDR_UNSPEC; i <= IPADDR_ID; i++) {
+ ip.family = i;
+ ip2.family = i + 1;
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, NULL) < 0);
+ TEST_CHECK(UTI_CompareIPs(&ip2, &ip, NULL) > 0);
+ ip2 = ip;
+ ip2.addr.in4++;
+ if (i == IPADDR_UNSPEC) {
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, NULL) == 0);
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip) == 0);
+ } else {
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, NULL) < 0);
+ TEST_CHECK(UTI_CompareIPs(&ip2, &ip, NULL) > 0);
+ if (i == IPADDR_ID) {
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip) < 0);
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip2) < 0);
+ } else {
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip) == 0);
+ TEST_CHECK(UTI_CompareIPs(&ip, &ip2, &ip2) < 0);
+ }
+ }
+ }
+
+ ip_saddr.ip_addr.family = IPADDR_INET4;
+ ip_saddr.ip_addr.addr.in4 = 0x01020304;
+ ip_saddr.port = 12345;
+ s = UTI_IPSockAddrToString(&ip_saddr);
+ TEST_CHECK(strcmp(s, "1.2.3.4:12345") == 0);
+
+ ip = ip_saddr.ip_addr;
+ s = UTI_IPSubnetToString(&ip, 10);
+ TEST_CHECK(strcmp(s, "1.2.3.4/10") == 0);
+ s = UTI_IPSubnetToString(&ip, 32);
+ TEST_CHECK(strcmp(s, "1.2.3.4") == 0);
+ ip.family = IPADDR_UNSPEC;
+ s = UTI_IPSubnetToString(&ip, 0);
+ TEST_CHECK(strcmp(s, "any address") == 0);
+
+ s = UTI_TimeToLogForm(2000000000);
+ TEST_CHECK(strcmp(s, "2033-05-18 03:33:20") == 0);
+
+ ts.tv_sec = 3;
+ ts.tv_nsec = 500000000;
+ ts2.tv_sec = 4;
+ ts2.tv_nsec = 250000000;
+ UTI_AdjustTimespec(&ts, &ts2, &ts3, &x, 2.0, -5.0);
+ TEST_CHECK(fabs(x - 6.5) < 1.0e-15);
+ TEST_CHECK((ts3.tv_sec == 10 && ts3.tv_nsec == 0) ||
+ (ts3.tv_sec == 9 && ts3.tv_nsec == 999999999));
+
+ for (i = -32; i <= 32; i++) {
+ for (j = c = 0; j < 1000; j++) {
+ UTI_GetNtp64Fuzz(&ntp_fuzz, i);
+ if (i <= 0)
+ TEST_CHECK(ntp_fuzz.hi == 0);
+ if (i < 0)
+ TEST_CHECK(ntohl(ntp_fuzz.lo) < 1U << (32 + i));
+ else if (i < 32)
+ TEST_CHECK(ntohl(ntp_fuzz.hi) < 1U << i);
+ if (ntohl(ntp_fuzz.lo) >= 1U << (31 + CLAMP(-31, i, 0)))
+ c++;
+ }
+
+ if (i == -32)
+ TEST_CHECK(c == 0);
+ else
+ TEST_CHECK(c > 400 && c < 600);
+ }
+
+ TEST_CHECK(UTI_DoubleToNtp32(-1.0) == htonl(0));
+ TEST_CHECK(UTI_DoubleToNtp32(0.0) == htonl(0));
+ TEST_CHECK(UTI_DoubleToNtp32(1e-9) == htonl(1));
+ TEST_CHECK(UTI_DoubleToNtp32(32768.0) == htonl(0x80000000));
+ TEST_CHECK(UTI_DoubleToNtp32(65536.0) == htonl(0xffffffff));
+ TEST_CHECK(UTI_DoubleToNtp32(65537.0) == htonl(0xffffffff));
+
+ TEST_CHECK(UTI_DoubleToNtp32f28(-1.0) == htonl(0));
+ TEST_CHECK(UTI_DoubleToNtp32f28(0.0) == htonl(0));
+ TEST_CHECK(UTI_DoubleToNtp32f28(1e-9) == htonl(1));
+ TEST_CHECK(UTI_DoubleToNtp32f28(4e-9) == htonl(2));
+ TEST_CHECK(UTI_DoubleToNtp32f28(8.0) == htonl(0x80000000));
+ TEST_CHECK(UTI_DoubleToNtp32f28(16.0) == htonl(0xffffffff));
+ TEST_CHECK(UTI_DoubleToNtp32f28(16.1) == htonl(0xffffffff));
+ TEST_CHECK(UTI_DoubleToNtp32f28(16.1) == htonl(0xffffffff));
+
+ TEST_CHECK(UTI_Ntp32f28ToDouble(htonl(0xffffffff)) >= 65535.999);
+ for (i = 0; i < 100000; i++) {
+ UTI_GetRandomBytes(&ntp32_ts, sizeof (ntp32_ts));
+ TEST_CHECK(UTI_DoubleToNtp32(UTI_Ntp32ToDouble(ntp32_ts)) == ntp32_ts);
+ TEST_CHECK(UTI_DoubleToNtp32f28(UTI_Ntp32f28ToDouble(ntp32_ts)) == ntp32_ts);
+ }
+
+ ts.tv_nsec = 0;
+
+ ts.tv_sec = 10;
+ TEST_CHECK(!UTI_IsTimeOffsetSane(&ts, -20.0));
+
+#ifdef HAVE_LONG_TIME_T
+ ts.tv_sec = NTP_ERA_SPLIT + (1LL << 32);
+#else
+ ts.tv_sec = 0x7fffffff - MIN_ENDOFTIME_DISTANCE;
+#endif
+ TEST_CHECK(!UTI_IsTimeOffsetSane(&ts, 10.0));
+ TEST_CHECK(UTI_IsTimeOffsetSane(&ts, -20.0));
+
+ TEST_CHECK(UTI_Log2ToDouble(-1) == 0.5);
+ TEST_CHECK(UTI_Log2ToDouble(0) == 1.0);
+ TEST_CHECK(UTI_Log2ToDouble(1) == 2.0);
+ TEST_CHECK(UTI_Log2ToDouble(-31) < UTI_Log2ToDouble(-30));
+ TEST_CHECK(UTI_Log2ToDouble(-32) == UTI_Log2ToDouble(-31));
+ TEST_CHECK(UTI_Log2ToDouble(30) < UTI_Log2ToDouble(32));
+ TEST_CHECK(UTI_Log2ToDouble(31) == UTI_Log2ToDouble(32));
+
+ UTI_TimespecHostToNetwork(&ts, &tspec);
+#ifdef HAVE_LONG_TIME_T
+ TEST_CHECK(tspec.tv_sec_high == htonl(ts.tv_sec >> 32));
+#else
+ TEST_CHECK(tspec.tv_sec_high == htonl(TV_NOHIGHSEC));
+#endif
+ TEST_CHECK(tspec.tv_sec_low == htonl(ts.tv_sec));
+ TEST_CHECK(tspec.tv_nsec == htonl(ts.tv_nsec));
+ UTI_TimespecNetworkToHost(&tspec, &ts2);
+ TEST_CHECK(!UTI_CompareTimespecs(&ts, &ts2));
+
+ integer64 = UTI_Integer64HostToNetwork(0x1234567890ABCDEFULL);
+ TEST_CHECK(memcmp(&integer64, "\x12\x34\x56\x78\x90\xab\xcd\xef", 8) == 0);
+ TEST_CHECK(UTI_Integer64NetworkToHost(integer64) == 0x1234567890ABCDEFULL);
+
+ TEST_CHECK(UTI_CmacNameToAlgorithm("AES128") == CMC_AES128);
+ TEST_CHECK(UTI_CmacNameToAlgorithm("AES256") == CMC_AES256);
+ TEST_CHECK(UTI_CmacNameToAlgorithm("NOSUCHCMAC") == CMC_INVALID);
+
+ TEST_CHECK(UTI_HashNameToAlgorithm("MD5") == HSH_MD5);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA1") == HSH_SHA1);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA256") == HSH_SHA256);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA384") == HSH_SHA384);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA512") == HSH_SHA512);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-224") == HSH_SHA3_224);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-256") == HSH_SHA3_256);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-384") == HSH_SHA3_384);
+ TEST_CHECK(UTI_HashNameToAlgorithm("SHA3-512") == HSH_SHA3_512);
+ TEST_CHECK(UTI_HashNameToAlgorithm("TIGER") == HSH_TIGER);
+ TEST_CHECK(UTI_HashNameToAlgorithm("WHIRLPOOL") == HSH_WHIRLPOOL);
+ TEST_CHECK(UTI_HashNameToAlgorithm("NOSUCHHASH") == HSH_INVALID);
+
+ i = open("/dev/null", 0);
+ TEST_CHECK(UTI_FdSetCloexec(i));
+ j = fcntl(i, F_GETFD);
+ TEST_CHECK(j & F_GETFD);
+ close(i);
+
+ UTI_SetQuitSignalsHandler(handle_signal, 0);
+ TEST_CHECK(handled_signal == 0);
+ kill(getpid(), SIGPIPE);
+ while (handled_signal == 0)
+ ;
+ TEST_CHECK(handled_signal == SIGPIPE);
+
+ s = UTI_PathToDir("/aaa/bbb/ccc/ddd");
+ TEST_CHECK(!strcmp(s, "/aaa/bbb/ccc"));
+ Free(s);
+ s = UTI_PathToDir("aaa");
+ TEST_CHECK(!strcmp(s, "."));
+ Free(s);
+ s = UTI_PathToDir("/aaaa");
+ TEST_CHECK(!strcmp(s, "/"));
+ Free(s);
+
+ nan = strtod("nan", NULL);
+ inf = strtod("inf", NULL);
+
+ TEST_CHECK(MIN(2.0, -1.0) == -1.0);
+ TEST_CHECK(MIN(-1.0, 2.0) == -1.0);
+ TEST_CHECK(MIN(inf, 2.0) == 2.0);
+
+ TEST_CHECK(MAX(2.0, -1.0) == 2.0);
+ TEST_CHECK(MAX(-1.0, 2.0) == 2.0);
+ TEST_CHECK(MAX(inf, 2.0) == inf);
+
+ TEST_CHECK(CLAMP(1.0, -1.0, 2.0) == 1.0);
+ TEST_CHECK(CLAMP(1.0, 3.0, 2.0) == 2.0);
+ TEST_CHECK(CLAMP(1.0, inf, 2.0) == 2.0);
+ TEST_CHECK(CLAMP(1.0, nan, 2.0) == 2.0);
+
+ TEST_CHECK(SQUARE(3.0) == 3.0 * 3.0);
+
+ rmdir("testdir");
+
+ uid = geteuid();
+ gid = getegid();
+
+ TEST_CHECK(UTI_CreateDirAndParents("testdir", 0700, uid, gid));
+
+ TEST_CHECK(UTI_CheckDirPermissions("testdir", 0700, uid, gid));
+ TEST_CHECK(!UTI_CheckDirPermissions("testdir", 0300, uid, gid));
+ TEST_CHECK(!UTI_CheckDirPermissions("testdir", 0700, uid + 1, gid));
+ TEST_CHECK(!UTI_CheckDirPermissions("testdir", 0700, uid, gid + 1));
+
+ umask(0);
+
+ unlink("testfile");
+ file = UTI_OpenFile(NULL, "testfile", NULL, 'r', 0);
+ TEST_CHECK(!file);
+ TEST_CHECK(stat("testfile", &st) < 0);
+
+ file = UTI_OpenFile(NULL, "testfile", NULL, 'w', 0644);
+ TEST_CHECK(file);
+ TEST_CHECK(stat("testfile", &st) == 0);
+ TEST_CHECK((st.st_mode & 0777) == 0644);
+ fclose(file);
+
+ file = UTI_OpenFile(".", "test", "file", 'W', 0640);
+ TEST_CHECK(file);
+ TEST_CHECK(stat("testfile", &st) == 0);
+ TEST_CHECK((st.st_mode & 0777) == 0640);
+ fclose(file);
+
+ file = UTI_OpenFile(NULL, "test", "file", 'r', 0);
+ TEST_CHECK(file);
+ fclose(file);
+
+ TEST_CHECK(UTI_RenameTempFile(NULL, "testfil", "e", NULL));
+ TEST_CHECK(stat("testfil", &st) == 0);
+ file = UTI_OpenFile(NULL, "testfil", NULL, 'R', 0);
+ TEST_CHECK(file);
+ fclose(file);
+
+ TEST_CHECK(UTI_RenameTempFile(NULL, "test", "fil", "file"));
+ TEST_CHECK(stat("testfile", &st) == 0);
+ file = UTI_OpenFile(NULL, "testfile", NULL, 'R', 0);
+ TEST_CHECK(file);
+ fclose(file);
+
+ TEST_CHECK(UTI_RemoveFile(NULL, "testfile", NULL));
+ TEST_CHECK(stat("testfile", &st) < 0);
+ TEST_CHECK(!UTI_RemoveFile(NULL, "testfile", NULL));
+
+ for (i = c = 0; i < 100000; i++) {
+ j = random() % (sizeof (buf) + 1);
+ UTI_GetRandomBytesUrandom(buf, j);
+ if (j && buf[j - 1] % 2)
+ c++;
+ if (random() % 10000 == 0) {
+ UTI_ResetGetRandomFunctions();
+ TEST_CHECK(!urandom_file);
+ }
+ }
+ TEST_CHECK(c > 46000 && c < 48000);
+
+ for (i = c = 0; i < 100000; i++) {
+ j = random() % (sizeof (buf) + 1);
+ UTI_GetRandomBytes(buf, j);
+ if (j && buf[j - 1] % 2)
+ c++;
+ if (random() % 10000 == 0) {
+ UTI_ResetGetRandomFunctions();
+#if HAVE_GETRANDOM
+ TEST_CHECK(getrandom_buf_available == 0);
+#endif
+ }
+ }
+ TEST_CHECK(c > 46000 && c < 48000);
+
+ assert(sizeof (buf) >= 16);
+ TEST_CHECK(UTI_HexToBytes("", buf, sizeof (buf)) == 0);
+ TEST_CHECK(UTI_HexToBytes("0", buf, sizeof (buf)) == 0);
+ TEST_CHECK(UTI_HexToBytes("00123456789ABCDEF", buf, sizeof (buf)) == 0);
+ TEST_CHECK(UTI_HexToBytes("00123456789ABCDEF0", buf, 8) == 0);
+ TEST_CHECK(UTI_HexToBytes("00123456789ABCDEF0", buf, sizeof (buf)) == 9);
+ TEST_CHECK(memcmp(buf, "\x00\x12\x34\x56\x78\x9A\xBC\xDE\xF0", 9) == 0);
+ memcpy(buf, "AB123456780001", 15);
+ TEST_CHECK(UTI_HexToBytes(buf, buf, sizeof (buf)) == 7);
+ TEST_CHECK(memcmp(buf, "\xAB\x12\x34\x56\x78\x00\x01", 7) == 0);
+
+ TEST_CHECK(UTI_BytesToHex("", 0, buf, 0) == 0);
+ TEST_CHECK(UTI_BytesToHex("\xAB\x12\x34\x56\x78\x00\x01", 7, buf, 14) == 0);
+ TEST_CHECK(UTI_BytesToHex("\xAB\x12\x34\x56\x78\x00\x01", 7, buf, 15) == 1);
+ TEST_CHECK(strcmp(buf, "AB123456780001") == 0);
+ TEST_CHECK(UTI_BytesToHex("\xAB\x12\x34\x56\x78\x00\x01", 0, buf, 15) == 1);
+ TEST_CHECK(strcmp(buf, "") == 0);
+
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 3) == 0);
+ TEST_CHECK(!words[0]);
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " ") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 3) == 0);
+ TEST_CHECK(!words[0]);
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "a \n ") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 3) == 1);
+ TEST_CHECK(words[0] == buf + 0);
+ TEST_CHECK(strcmp(words[0], "a") == 0);
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " a ") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 3) == 1);
+ TEST_CHECK(words[0] == buf + 2);
+ TEST_CHECK(strcmp(words[0], "a") == 0);
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " \n a") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 3) == 1);
+ TEST_CHECK(words[0] == buf + 4);
+ TEST_CHECK(strcmp(words[0], "a") == 0);
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "a b") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 1) == 2);
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", "a b") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 2) == 2);
+ TEST_CHECK(words[0] == buf + 0);
+ TEST_CHECK(words[1] == buf + 4);
+ TEST_CHECK(strcmp(words[0], "a") == 0);
+ TEST_CHECK(strcmp(words[1], "b") == 0);
+ TEST_CHECK(snprintf(buf, sizeof (buf), "%s", " a b ") < sizeof (buf));
+ TEST_CHECK(UTI_SplitString(buf, words, 3) == 2);
+ TEST_CHECK(words[0] == buf + 1);
+ TEST_CHECK(words[1] == buf + 3);
+ TEST_CHECK(strcmp(words[0], "a") == 0);
+ TEST_CHECK(strcmp(words[1], "b") == 0);
+
+ HSH_Finalise();
+}
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..a4c8288
--- /dev/null
+++ b/util.c
@@ -0,0 +1,1650 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 2009, 2012-2023
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Various utility functions
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "logging.h"
+#include "memory.h"
+#include "util.h"
+#include "hash.h"
+
+#define NSEC_PER_SEC 1000000000
+
+/* ================================================== */
+
+void
+UTI_ZeroTimespec(struct timespec *ts)
+{
+ ts->tv_sec = 0;
+ ts->tv_nsec = 0;
+}
+
+/* ================================================== */
+
+int
+UTI_IsZeroTimespec(struct timespec *ts)
+{
+ return !ts->tv_sec && !ts->tv_nsec;
+}
+
+/* ================================================== */
+
+void
+UTI_TimevalToTimespec(const struct timeval *tv, struct timespec *ts)
+{
+ ts->tv_sec = tv->tv_sec;
+ ts->tv_nsec = 1000 * tv->tv_usec;
+}
+
+/* ================================================== */
+
+void
+UTI_TimespecToTimeval(const struct timespec *ts, struct timeval *tv)
+{
+ tv->tv_sec = ts->tv_sec;
+ tv->tv_usec = ts->tv_nsec / 1000;
+}
+
+/* ================================================== */
+
+double
+UTI_TimespecToDouble(const struct timespec *ts)
+{
+ return ts->tv_sec + 1.0e-9 * ts->tv_nsec;
+}
+
+/* ================================================== */
+
+void
+UTI_DoubleToTimespec(double d, struct timespec *ts)
+{
+ ts->tv_sec = d;
+ ts->tv_nsec = 1.0e9 * (d - ts->tv_sec);
+ UTI_NormaliseTimespec(ts);
+}
+
+/* ================================================== */
+
+void
+UTI_NormaliseTimespec(struct timespec *ts)
+{
+ if (ts->tv_nsec >= NSEC_PER_SEC || ts->tv_nsec < 0) {
+ ts->tv_sec += ts->tv_nsec / NSEC_PER_SEC;
+ ts->tv_nsec = ts->tv_nsec % NSEC_PER_SEC;
+
+ /* If seconds are negative nanoseconds would end up negative too */
+ if (ts->tv_nsec < 0) {
+ ts->tv_sec--;
+ ts->tv_nsec += NSEC_PER_SEC;
+ }
+ }
+}
+
+/* ================================================== */
+
+double
+UTI_TimevalToDouble(const struct timeval *tv)
+{
+ return tv->tv_sec + 1.0e-6 * tv->tv_usec;
+}
+
+/* ================================================== */
+
+void
+UTI_DoubleToTimeval(double a, struct timeval *b)
+{
+ double frac_part;
+
+ b->tv_sec = a;
+ frac_part = 1.0e6 * (a - b->tv_sec);
+ b->tv_usec = round(frac_part);
+ UTI_NormaliseTimeval(b);
+}
+
+/* ================================================== */
+
+void
+UTI_NormaliseTimeval(struct timeval *x)
+{
+ /* Reduce tv_usec to within +-1000000 of zero. JGH */
+ if ((x->tv_usec >= 1000000) || (x->tv_usec <= -1000000)) {
+ x->tv_sec += x->tv_usec/1000000;
+ x->tv_usec = x->tv_usec%1000000;
+ }
+
+ /* Make tv_usec positive. JGH */
+ if (x->tv_usec < 0) {
+ --x->tv_sec;
+ x->tv_usec += 1000000;
+ }
+
+}
+
+/* ================================================== */
+
+int
+UTI_CompareTimespecs(const struct timespec *a, const struct timespec *b)
+{
+ if (a->tv_sec < b->tv_sec)
+ return -1;
+ if (a->tv_sec > b->tv_sec)
+ return 1;
+ if (a->tv_nsec < b->tv_nsec)
+ return -1;
+ if (a->tv_nsec > b->tv_nsec)
+ return 1;
+ return 0;
+}
+
+/* ================================================== */
+
+void
+UTI_DiffTimespecs(struct timespec *result, const struct timespec *a, const struct timespec *b)
+{
+ result->tv_sec = a->tv_sec - b->tv_sec;
+ result->tv_nsec = a->tv_nsec - b->tv_nsec;
+ UTI_NormaliseTimespec(result);
+}
+
+/* ================================================== */
+
+/* Calculate result = a - b and return as a double */
+double
+UTI_DiffTimespecsToDouble(const struct timespec *a, const struct timespec *b)
+{
+ return ((double)a->tv_sec - (double)b->tv_sec) + 1.0e-9 * (a->tv_nsec - b->tv_nsec);
+}
+
+/* ================================================== */
+
+void
+UTI_AddDoubleToTimespec(const struct timespec *start, double increment, struct timespec *end)
+{
+ time_t int_part;
+
+ int_part = increment;
+ end->tv_sec = start->tv_sec + int_part;
+ end->tv_nsec = start->tv_nsec + 1.0e9 * (increment - int_part);
+ UTI_NormaliseTimespec(end);
+}
+
+/* ================================================== */
+
+/* Calculate the average and difference (as a double) of two timespecs */
+void
+UTI_AverageDiffTimespecs(const struct timespec *earlier, const struct timespec *later,
+ struct timespec *average, double *diff)
+{
+ *diff = UTI_DiffTimespecsToDouble(later, earlier);
+ UTI_AddDoubleToTimespec(earlier, *diff / 2.0, average);
+}
+
+/* ================================================== */
+
+void
+UTI_AddDiffToTimespec(const struct timespec *a, const struct timespec *b,
+ const struct timespec *c, struct timespec *result)
+{
+ double diff;
+
+ diff = UTI_DiffTimespecsToDouble(a, b);
+ UTI_AddDoubleToTimespec(c, diff, result);
+}
+
+/* ================================================== */
+
+#define POOL_ENTRIES 16
+#define BUFFER_LENGTH 64
+static char buffer_pool[POOL_ENTRIES][BUFFER_LENGTH];
+static int pool_ptr = 0;
+
+#define NEXT_BUFFER (buffer_pool[pool_ptr = ((pool_ptr + 1) % POOL_ENTRIES)])
+
+/* ================================================== */
+/* Convert a timespec into a temporary string, largely for diagnostic display */
+
+char *
+UTI_TimespecToString(const struct timespec *ts)
+{
+ char *result;
+
+ result = NEXT_BUFFER;
+#ifdef HAVE_LONG_TIME_T
+ snprintf(result, BUFFER_LENGTH, "%"PRId64".%09lu",
+ (int64_t)ts->tv_sec, (unsigned long)ts->tv_nsec);
+#else
+ snprintf(result, BUFFER_LENGTH, "%ld.%09lu",
+ (long)ts->tv_sec, (unsigned long)ts->tv_nsec);
+#endif
+ return result;
+}
+
+/* ================================================== */
+/* Convert an NTP timestamp into a temporary string, largely
+ for diagnostic display */
+
+char *
+UTI_Ntp64ToString(const NTP_int64 *ntp_ts)
+{
+ struct timespec ts;
+ UTI_Ntp64ToTimespec(ntp_ts, &ts);
+ return UTI_TimespecToString(&ts);
+}
+
+/* ================================================== */
+
+char *
+UTI_RefidToString(uint32_t ref_id)
+{
+ unsigned int i, j, c;
+ char *result;
+
+ result = NEXT_BUFFER;
+
+ for (i = j = 0; i < 4 && i < BUFFER_LENGTH - 1; i++) {
+ c = (ref_id >> (24 - i * 8)) & 0xff;
+ if (isprint(c))
+ result[j++] = c;
+ }
+
+ result[j] = '\0';
+
+ return result;
+}
+
+/* ================================================== */
+
+char *
+UTI_IPToString(const IPAddr *addr)
+{
+ unsigned long a, b, c, d, ip;
+ const uint8_t *ip6;
+ char *result;
+
+ result = NEXT_BUFFER;
+ switch (addr->family) {
+ case IPADDR_UNSPEC:
+ snprintf(result, BUFFER_LENGTH, "[UNSPEC]");
+ break;
+ case IPADDR_INET4:
+ ip = addr->addr.in4;
+ a = (ip>>24) & 0xff;
+ b = (ip>>16) & 0xff;
+ c = (ip>> 8) & 0xff;
+ d = (ip>> 0) & 0xff;
+ snprintf(result, BUFFER_LENGTH, "%lu.%lu.%lu.%lu", a, b, c, d);
+ break;
+ case IPADDR_INET6:
+ ip6 = addr->addr.in6;
+#ifdef FEAT_IPV6
+ inet_ntop(AF_INET6, ip6, result, BUFFER_LENGTH);
+#else
+ assert(BUFFER_LENGTH >= 40);
+ for (a = 0; a < 8; a++)
+ snprintf(result + a * 5, 40 - a * 5, "%04x:",
+ (unsigned int)(ip6[2 * a] << 8 | ip6[2 * a + 1]));
+#endif
+ break;
+ case IPADDR_ID:
+ snprintf(result, BUFFER_LENGTH, "ID#%010"PRIu32, addr->addr.id);
+ break;
+ default:
+ snprintf(result, BUFFER_LENGTH, "[UNKNOWN]");
+ }
+ return result;
+}
+
+/* ================================================== */
+
+int
+UTI_StringToIP(const char *addr, IPAddr *ip)
+{
+ struct in_addr in4;
+#ifdef FEAT_IPV6
+ struct in6_addr in6;
+#endif
+
+ if (inet_pton(AF_INET, addr, &in4) > 0) {
+ ip->family = IPADDR_INET4;
+ ip->_pad = 0;
+ ip->addr.in4 = ntohl(in4.s_addr);
+ return 1;
+ }
+
+#ifdef FEAT_IPV6
+ if (inet_pton(AF_INET6, addr, &in6) > 0) {
+ ip->family = IPADDR_INET6;
+ ip->_pad = 0;
+ memcpy(ip->addr.in6, in6.s6_addr, sizeof (ip->addr.in6));
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+UTI_IsStringIP(const char *string)
+{
+ IPAddr ip;
+
+ return UTI_StringToIP(string, &ip);
+}
+
+/* ================================================== */
+
+int
+UTI_StringToIdIP(const char *addr, IPAddr *ip)
+{
+ if (sscanf(addr, "ID#%"SCNu32, &ip->addr.id) == 1) {
+ ip->family = IPADDR_ID;
+ ip->_pad = 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+UTI_IsIPReal(const IPAddr *ip)
+{
+ switch (ip->family) {
+ case IPADDR_INET4:
+ case IPADDR_INET6:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* ================================================== */
+
+uint32_t
+UTI_IPToRefid(const IPAddr *ip)
+{
+ static int MD5_hash = -1;
+ unsigned char buf[16];
+
+ switch (ip->family) {
+ case IPADDR_INET4:
+ return ip->addr.in4;
+ case IPADDR_INET6:
+ if (MD5_hash < 0)
+ MD5_hash = HSH_GetHashId(HSH_MD5_NONCRYPTO);
+
+ if (MD5_hash < 0 ||
+ HSH_Hash(MD5_hash, (const unsigned char *)ip->addr.in6, sizeof (ip->addr.in6),
+ NULL, 0, buf, sizeof (buf)) != sizeof (buf))
+ LOG_FATAL("Could not get MD5");
+
+ return (uint32_t)buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
+ }
+ return 0;
+}
+
+/* ================================================== */
+
+uint32_t
+UTI_IPToHash(const IPAddr *ip)
+{
+ static uint32_t seed = 0;
+ const unsigned char *addr;
+ unsigned int i, len;
+ uint32_t hash;
+
+ switch (ip->family) {
+ case IPADDR_INET4:
+ addr = (unsigned char *)&ip->addr.in4;
+ len = sizeof (ip->addr.in4);
+ break;
+ case IPADDR_INET6:
+ addr = ip->addr.in6;
+ len = sizeof (ip->addr.in6);
+ break;
+ case IPADDR_ID:
+ addr = (unsigned char *)&ip->addr.id;
+ len = sizeof (ip->addr.id);
+ break;
+ default:
+ return 0;
+ }
+
+ /* Include a random seed in the hash to randomize collisions
+ and order of addresses in hash tables */
+ while (!seed)
+ UTI_GetRandomBytes(&seed, sizeof (seed));
+
+ for (i = 0, hash = seed; i < len; i++)
+ hash = 71 * hash + addr[i];
+
+ return hash + seed;
+}
+
+/* ================================================== */
+
+void
+UTI_IPHostToNetwork(const IPAddr *src, IPAddr *dest)
+{
+ /* Don't send uninitialized bytes over network */
+ memset(dest, 0, sizeof (IPAddr));
+
+ dest->family = htons(src->family);
+
+ switch (src->family) {
+ case IPADDR_INET4:
+ dest->addr.in4 = htonl(src->addr.in4);
+ break;
+ case IPADDR_INET6:
+ memcpy(dest->addr.in6, src->addr.in6, sizeof (dest->addr.in6));
+ break;
+ case IPADDR_ID:
+ dest->addr.id = htonl(src->addr.id);
+ break;
+ default:
+ dest->family = htons(IPADDR_UNSPEC);
+ }
+}
+
+/* ================================================== */
+
+void
+UTI_IPNetworkToHost(const IPAddr *src, IPAddr *dest)
+{
+ dest->family = ntohs(src->family);
+ dest->_pad = 0;
+
+ switch (dest->family) {
+ case IPADDR_INET4:
+ dest->addr.in4 = ntohl(src->addr.in4);
+ break;
+ case IPADDR_INET6:
+ memcpy(dest->addr.in6, src->addr.in6, sizeof (dest->addr.in6));
+ break;
+ case IPADDR_ID:
+ dest->addr.id = ntohl(src->addr.id);
+ break;
+ default:
+ dest->family = IPADDR_UNSPEC;
+ }
+}
+
+/* ================================================== */
+
+int
+UTI_CompareIPs(const IPAddr *a, const IPAddr *b, const IPAddr *mask)
+{
+ int i, d;
+
+ if (a->family != b->family)
+ return a->family - b->family;
+
+ if (mask && mask->family != b->family)
+ mask = NULL;
+
+ switch (a->family) {
+ case IPADDR_UNSPEC:
+ return 0;
+ case IPADDR_INET4:
+ if (mask)
+ return (a->addr.in4 & mask->addr.in4) - (b->addr.in4 & mask->addr.in4);
+ else
+ return a->addr.in4 - b->addr.in4;
+ case IPADDR_INET6:
+ for (i = 0, d = 0; !d && i < 16; i++) {
+ if (mask)
+ d = (a->addr.in6[i] & mask->addr.in6[i]) -
+ (b->addr.in6[i] & mask->addr.in6[i]);
+ else
+ d = a->addr.in6[i] - b->addr.in6[i];
+ }
+ return d;
+ case IPADDR_ID:
+ return a->addr.id - b->addr.id;
+ }
+ return 0;
+}
+
+/* ================================================== */
+
+char *
+UTI_IPSockAddrToString(const IPSockAddr *sa)
+{
+ char *result;
+
+ result = NEXT_BUFFER;
+ snprintf(result, BUFFER_LENGTH,
+ sa->ip_addr.family != IPADDR_INET6 ? "%s:%hu" : "[%s]:%hu",
+ UTI_IPToString(&sa->ip_addr), sa->port);
+
+ return result;
+}
+
+/* ================================================== */
+
+char *
+UTI_IPSubnetToString(IPAddr *subnet, int bits)
+{
+ char *result;
+
+ result = NEXT_BUFFER;
+
+ if (subnet->family == IPADDR_UNSPEC)
+ snprintf(result, BUFFER_LENGTH, "%s", "any address");
+ else if ((subnet->family == IPADDR_INET4 && bits == 32) ||
+ (subnet->family == IPADDR_INET6 && bits == 128))
+ snprintf(result, BUFFER_LENGTH, "%s", UTI_IPToString(subnet));
+ else
+ snprintf(result, BUFFER_LENGTH, "%s/%d", UTI_IPToString(subnet), bits);
+
+ return result;
+}
+
+/* ================================================== */
+
+char *
+UTI_TimeToLogForm(time_t t)
+{
+ struct tm *stm;
+ char *result;
+
+ result = NEXT_BUFFER;
+
+ stm = gmtime(&t);
+
+ if (stm)
+ strftime(result, BUFFER_LENGTH, "%Y-%m-%d %H:%M:%S", stm);
+ else
+ snprintf(result, BUFFER_LENGTH, "INVALID INVALID ");
+
+ return result;
+}
+
+/* ================================================== */
+
+void
+UTI_AdjustTimespec(const struct timespec *old_ts, const struct timespec *when,
+ struct timespec *new_ts, double *delta_time, double dfreq, double doffset)
+{
+ double elapsed;
+
+ elapsed = UTI_DiffTimespecsToDouble(when, old_ts);
+ *delta_time = elapsed * dfreq - doffset;
+ UTI_AddDoubleToTimespec(old_ts, *delta_time, new_ts);
+}
+
+/* ================================================== */
+
+void
+UTI_GetNtp64Fuzz(NTP_int64 *ts, int precision)
+{
+ int start, bits;
+
+ assert(precision >= -32 && precision <= 32);
+ assert(sizeof (*ts) == 8);
+
+ start = sizeof (*ts) - (precision + 32 + 7) / 8;
+ ts->hi = ts->lo = 0;
+
+ UTI_GetRandomBytes((unsigned char *)ts + start, sizeof (*ts) - start);
+
+ bits = (precision + 32) % 8;
+ if (bits)
+ ((unsigned char *)ts)[start] %= 1U << bits;
+}
+
+/* ================================================== */
+
+double
+UTI_Ntp32ToDouble(NTP_int32 x)
+{
+ return (double) ntohl(x) / 65536.0;
+}
+
+/* ================================================== */
+
+#define MAX_NTP_INT32 (4294967295.0 / 65536.0)
+
+NTP_int32
+UTI_DoubleToNtp32(double x)
+{
+ NTP_int32 r;
+
+ if (x >= MAX_NTP_INT32) {
+ r = 0xffffffff;
+ } else if (x <= 0.0) {
+ r = 0;
+ } else {
+ x *= 65536.0;
+ r = x;
+
+ /* Round up */
+ if (r < x)
+ r++;
+ }
+
+ return htonl(r);
+}
+
+/* ================================================== */
+
+double
+UTI_Ntp32f28ToDouble(NTP_int32 x)
+{
+ uint32_t r = ntohl(x);
+
+ /* Maximum value is special */
+ if (r == 0xffffffff)
+ return MAX_NTP_INT32;
+
+ return r / (double)(1U << 28);
+}
+
+/* ================================================== */
+
+NTP_int32
+UTI_DoubleToNtp32f28(double x)
+{
+ NTP_int32 r;
+
+ if (x >= 4294967295.0 / (1U << 28)) {
+ r = 0xffffffff;
+ } else if (x <= 0.0) {
+ r = 0;
+ } else {
+ x *= 1U << 28;
+ r = x;
+
+ /* Round up */
+ if (r < x)
+ r++;
+ }
+
+ return htonl(r);
+}
+
+/* ================================================== */
+
+void
+UTI_ZeroNtp64(NTP_int64 *ts)
+{
+ ts->hi = ts->lo = htonl(0);
+}
+
+/* ================================================== */
+
+int
+UTI_IsZeroNtp64(const NTP_int64 *ts)
+{
+ return !ts->hi && !ts->lo;
+}
+
+/* ================================================== */
+
+int
+UTI_CompareNtp64(const NTP_int64 *a, const NTP_int64 *b)
+{
+ int32_t diff;
+
+ if (a->hi == b->hi && a->lo == b->lo)
+ return 0;
+
+ diff = ntohl(a->hi) - ntohl(b->hi);
+
+ if (diff < 0)
+ return -1;
+ if (diff > 0)
+ return 1;
+
+ return ntohl(a->lo) < ntohl(b->lo) ? -1 : 1;
+}
+
+/* ================================================== */
+
+int
+UTI_IsEqualAnyNtp64(const NTP_int64 *a, const NTP_int64 *b1, const NTP_int64 *b2,
+ const NTP_int64 *b3)
+{
+ if (b1 && a->lo == b1->lo && a->hi == b1->hi)
+ return 1;
+
+ if (b2 && a->lo == b2->lo && a->hi == b2->hi)
+ return 1;
+
+ if (b3 && a->lo == b3->lo && a->hi == b3->hi)
+ return 1;
+
+ return 0;
+}
+
+/* ================================================== */
+
+/* Seconds part of NTP timestamp correponding to the origin of the time_t format */
+#define JAN_1970 0x83aa7e80UL
+
+#define NSEC_PER_NTP64 4.294967296
+
+void
+UTI_TimespecToNtp64(const struct timespec *src, NTP_int64 *dest, const NTP_int64 *fuzz)
+{
+ uint32_t hi, lo, sec, nsec;
+
+ sec = (uint32_t)src->tv_sec;
+ nsec = (uint32_t)src->tv_nsec;
+
+ /* Recognize zero as a special case - it always signifies
+ an 'unknown' value */
+ if (!nsec && !sec) {
+ hi = lo = 0;
+ } else {
+ hi = htonl(sec + JAN_1970);
+ lo = htonl(NSEC_PER_NTP64 * nsec);
+
+ /* Add the fuzz */
+ if (fuzz) {
+ hi ^= fuzz->hi;
+ lo ^= fuzz->lo;
+ }
+ }
+
+ dest->hi = hi;
+ dest->lo = lo;
+}
+
+/* ================================================== */
+
+void
+UTI_Ntp64ToTimespec(const NTP_int64 *src, struct timespec *dest)
+{
+ uint32_t ntp_sec, ntp_frac;
+
+ /* Zero is a special value */
+ if (UTI_IsZeroNtp64(src)) {
+ UTI_ZeroTimespec(dest);
+ return;
+ }
+
+ ntp_sec = ntohl(src->hi);
+ ntp_frac = ntohl(src->lo);
+
+#ifdef HAVE_LONG_TIME_T
+ dest->tv_sec = ntp_sec - (uint32_t)(NTP_ERA_SPLIT + JAN_1970) +
+ (time_t)NTP_ERA_SPLIT;
+#else
+ dest->tv_sec = ntp_sec - JAN_1970;
+#endif
+
+ dest->tv_nsec = ntp_frac / NSEC_PER_NTP64;
+}
+
+/* ================================================== */
+
+double
+UTI_DiffNtp64ToDouble(const NTP_int64 *a, const NTP_int64 *b)
+{
+ /* Don't convert to timespec to allow any epoch */
+ return (int32_t)(ntohl(a->hi) - ntohl(b->hi)) +
+ ((double)ntohl(a->lo) - (double)ntohl(b->lo)) / (1.0e9 * NSEC_PER_NTP64);
+}
+
+/* ================================================== */
+
+double
+UTI_Ntp64ToDouble(NTP_int64 *src)
+{
+ NTP_int64 zero;
+
+ UTI_ZeroNtp64(&zero);
+ return UTI_DiffNtp64ToDouble(src, &zero);
+}
+
+/* ================================================== */
+
+void
+UTI_DoubleToNtp64(double src, NTP_int64 *dest)
+{
+ int32_t hi;
+
+ src = CLAMP(INT32_MIN, src, INT32_MAX);
+ hi = round(src);
+ if (hi > src)
+ hi -= 1;
+
+ dest->hi = htonl(hi);
+ dest->lo = htonl((src - hi) * (1.0e9 * NSEC_PER_NTP64));
+}
+
+/* ================================================== */
+
+/* Maximum offset between two sane times */
+#define MAX_OFFSET 4294967296.0
+
+/* Minimum allowed distance from maximum 32-bit time_t */
+#define MIN_ENDOFTIME_DISTANCE (365 * 24 * 3600)
+
+int
+UTI_IsTimeOffsetSane(const struct timespec *ts, double offset)
+{
+ double t;
+
+ /* Handle nan correctly here */
+ if (!(offset > -MAX_OFFSET && offset < MAX_OFFSET))
+ return 0;
+
+ t = UTI_TimespecToDouble(ts) + offset;
+
+ /* Time before 1970 is not considered valid */
+ if (t < 0.0)
+ return 0;
+
+#ifdef HAVE_LONG_TIME_T
+ /* Check if it's in the interval to which NTP time is mapped */
+ if (t < (double)NTP_ERA_SPLIT || t > (double)(NTP_ERA_SPLIT + (1LL << 32)))
+ return 0;
+#else
+ /* Don't get too close to 32-bit time_t overflow */
+ if (t > (double)(0x7fffffff - MIN_ENDOFTIME_DISTANCE))
+ return 0;
+#endif
+
+ return 1;
+}
+
+/* ================================================== */
+
+double
+UTI_Log2ToDouble(int l)
+{
+ if (l >= 0) {
+ if (l > 31)
+ l = 31;
+ return (uint32_t)1 << l;
+ } else {
+ if (l < -31)
+ l = -31;
+ return 1.0 / ((uint32_t)1 << -l);
+ }
+}
+
+/* ================================================== */
+
+void
+UTI_TimespecNetworkToHost(const Timespec *src, struct timespec *dest)
+{
+ uint32_t sec_low, nsec;
+#ifdef HAVE_LONG_TIME_T
+ uint32_t sec_high;
+#endif
+
+ sec_low = ntohl(src->tv_sec_low);
+#ifdef HAVE_LONG_TIME_T
+ sec_high = ntohl(src->tv_sec_high);
+ if (sec_high == TV_NOHIGHSEC)
+ sec_high = 0;
+
+ dest->tv_sec = (uint64_t)sec_high << 32 | sec_low;
+#else
+ dest->tv_sec = sec_low;
+#endif
+
+ nsec = ntohl(src->tv_nsec);
+ dest->tv_nsec = MIN(nsec, 999999999U);
+}
+
+/* ================================================== */
+
+void
+UTI_TimespecHostToNetwork(const struct timespec *src, Timespec *dest)
+{
+ dest->tv_nsec = htonl(src->tv_nsec);
+#ifdef HAVE_LONG_TIME_T
+ dest->tv_sec_high = htonl((uint64_t)src->tv_sec >> 32);
+#else
+ dest->tv_sec_high = htonl(TV_NOHIGHSEC);
+#endif
+ dest->tv_sec_low = htonl(src->tv_sec);
+}
+
+/* ================================================== */
+
+uint64_t
+UTI_Integer64NetworkToHost(Integer64 i)
+{
+ return (uint64_t)ntohl(i.high) << 32 | ntohl(i.low);
+}
+
+/* ================================================== */
+
+Integer64
+UTI_Integer64HostToNetwork(uint64_t i)
+{
+ Integer64 r;
+ r.high = htonl(i >> 32);
+ r.low = htonl(i);
+ return r;
+}
+
+/* ================================================== */
+
+#define FLOAT_EXP_BITS 7
+#define FLOAT_EXP_MIN (-(1 << (FLOAT_EXP_BITS - 1)))
+#define FLOAT_EXP_MAX (-FLOAT_EXP_MIN - 1)
+#define FLOAT_COEF_BITS ((int)sizeof (int32_t) * 8 - FLOAT_EXP_BITS)
+#define FLOAT_COEF_MIN (-(1 << (FLOAT_COEF_BITS - 1)))
+#define FLOAT_COEF_MAX (-FLOAT_COEF_MIN - 1)
+
+double
+UTI_FloatNetworkToHost(Float f)
+{
+ int32_t exp, coef;
+ uint32_t x;
+
+ x = ntohl(f.f);
+
+ exp = x >> FLOAT_COEF_BITS;
+ if (exp >= 1 << (FLOAT_EXP_BITS - 1))
+ exp -= 1 << FLOAT_EXP_BITS;
+ exp -= FLOAT_COEF_BITS;
+
+ coef = x % (1U << FLOAT_COEF_BITS);
+ if (coef >= 1 << (FLOAT_COEF_BITS - 1))
+ coef -= 1 << FLOAT_COEF_BITS;
+
+ return coef * pow(2.0, exp);
+}
+
+Float
+UTI_FloatHostToNetwork(double x)
+{
+ int32_t exp, coef, neg;
+ Float f;
+
+ if (x < 0.0) {
+ x = -x;
+ neg = 1;
+ } else if (x >= 0.0) {
+ neg = 0;
+ } else {
+ /* Save NaN as zero */
+ x = 0.0;
+ neg = 0;
+ }
+
+ if (x < 1.0e-100) {
+ exp = coef = 0;
+ } else if (x > 1.0e100) {
+ exp = FLOAT_EXP_MAX;
+ coef = FLOAT_COEF_MAX + neg;
+ } else {
+ exp = log(x) / log(2) + 1;
+ coef = x * pow(2.0, -exp + FLOAT_COEF_BITS) + 0.5;
+
+ assert(coef > 0);
+
+ /* we may need to shift up to two bits down */
+ while (coef > FLOAT_COEF_MAX + neg) {
+ coef >>= 1;
+ exp++;
+ }
+
+ if (exp > FLOAT_EXP_MAX) {
+ /* overflow */
+ exp = FLOAT_EXP_MAX;
+ coef = FLOAT_COEF_MAX + neg;
+ } else if (exp < FLOAT_EXP_MIN) {
+ /* underflow */
+ if (exp + FLOAT_COEF_BITS >= FLOAT_EXP_MIN) {
+ coef >>= FLOAT_EXP_MIN - exp;
+ exp = FLOAT_EXP_MIN;
+ } else {
+ exp = coef = 0;
+ }
+ }
+ }
+
+ /* negate back */
+ if (neg)
+ coef = (uint32_t)-coef << FLOAT_EXP_BITS >> FLOAT_EXP_BITS;
+
+ f.f = htonl((uint32_t)exp << FLOAT_COEF_BITS | coef);
+ return f;
+}
+
+/* ================================================== */
+
+CMC_Algorithm
+UTI_CmacNameToAlgorithm(const char *name)
+{
+ if (strcmp(name, "AES128") == 0)
+ return CMC_AES128;
+ else if (strcmp(name, "AES256") == 0)
+ return CMC_AES256;
+ return CMC_INVALID;
+}
+
+/* ================================================== */
+
+HSH_Algorithm
+UTI_HashNameToAlgorithm(const char *name)
+{
+ if (strcmp(name, "MD5") == 0)
+ return HSH_MD5;
+ else if (strcmp(name, "SHA1") == 0)
+ return HSH_SHA1;
+ else if (strcmp(name, "SHA256") == 0)
+ return HSH_SHA256;
+ else if (strcmp(name, "SHA384") == 0)
+ return HSH_SHA384;
+ else if (strcmp(name, "SHA512") == 0)
+ return HSH_SHA512;
+ else if (strcmp(name, "SHA3-224") == 0)
+ return HSH_SHA3_224;
+ else if (strcmp(name, "SHA3-256") == 0)
+ return HSH_SHA3_256;
+ else if (strcmp(name, "SHA3-384") == 0)
+ return HSH_SHA3_384;
+ else if (strcmp(name, "SHA3-512") == 0)
+ return HSH_SHA3_512;
+ else if (strcmp(name, "TIGER") == 0)
+ return HSH_TIGER;
+ else if (strcmp(name, "WHIRLPOOL") == 0)
+ return HSH_WHIRLPOOL;
+ return HSH_INVALID;
+}
+
+/* ================================================== */
+
+int
+UTI_FdSetCloexec(int fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1) {
+ DEBUG_LOG("fcntl() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ flags |= FD_CLOEXEC;
+
+ if (fcntl(fd, F_SETFD, flags) < 0) {
+ DEBUG_LOG("fcntl() failed : %s", strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+UTI_SetQuitSignalsHandler(void (*handler)(int), int ignore_sigpipe)
+{
+ struct sigaction sa;
+
+ sa.sa_handler = handler;
+ sa.sa_flags = SA_RESTART;
+ if (sigemptyset(&sa.sa_mask) < 0)
+ LOG_FATAL("sigemptyset() failed");
+
+#ifdef SIGINT
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ LOG_FATAL("sigaction(%d) failed", SIGINT);
+#endif
+#ifdef SIGTERM
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ LOG_FATAL("sigaction(%d) failed", SIGTERM);
+#endif
+#ifdef SIGQUIT
+ if (sigaction(SIGQUIT, &sa, NULL) < 0)
+ LOG_FATAL("sigaction(%d) failed", SIGQUIT);
+#endif
+#ifdef SIGHUP
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ LOG_FATAL("sigaction(%d) failed", SIGHUP);
+#endif
+
+ if (ignore_sigpipe)
+ sa.sa_handler = SIG_IGN;
+
+ if (sigaction(SIGPIPE, &sa, NULL) < 0)
+ LOG_FATAL("sigaction(%d) failed", SIGPIPE);
+}
+
+/* ================================================== */
+
+char *
+UTI_PathToDir(const char *path)
+{
+ char *dir, *slash;
+ size_t dir_len;
+
+ slash = strrchr(path, '/');
+
+ if (!slash)
+ return Strdup(".");
+
+ if (slash == path)
+ return Strdup("/");
+
+ dir_len = slash - path;
+
+ dir = Malloc(dir_len + 1);
+ memcpy(dir, path, dir_len);
+ dir[dir_len] = '\0';
+
+ return dir;
+}
+
+/* ================================================== */
+
+static int
+create_dir(char *p, mode_t mode, uid_t uid, gid_t gid)
+{
+ int status;
+ struct stat buf;
+
+ /* See if directory exists */
+ status = stat(p, &buf);
+
+ if (status < 0) {
+ if (errno != ENOENT) {
+ LOG(LOGS_ERR, "Could not access %s : %s", p, strerror(errno));
+ return 0;
+ }
+ } else {
+ if (S_ISDIR(buf.st_mode))
+ return 1;
+ LOG(LOGS_ERR, "%s is not directory", p);
+ return 0;
+ }
+
+ /* Create the directory */
+ if (mkdir(p, mode) < 0) {
+ LOG(LOGS_ERR, "Could not create directory %s : %s", p, strerror(errno));
+ return 0;
+ }
+
+ /* Set its owner */
+ if (chown(p, uid, gid) < 0) {
+ LOG(LOGS_ERR, "Could not change ownership of %s : %s", p, strerror(errno));
+ /* Don't leave it there with incorrect ownership */
+ rmdir(p);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+/* Return 0 if the directory couldn't be created, 1 if it could (or
+ already existed) */
+int
+UTI_CreateDirAndParents(const char *path, mode_t mode, uid_t uid, gid_t gid)
+{
+ char *p;
+ int i, j, k, last;
+
+ /* Don't try to create current directory */
+ if (!strcmp(path, "."))
+ return 1;
+
+ p = (char *)Malloc(1 + strlen(path));
+
+ i = k = 0;
+ while (1) {
+ p[i++] = path[k++];
+
+ if (path[k] == '/' || !path[k]) {
+ /* Check whether its end of string, a trailing / or group of / */
+ last = 1;
+ j = k;
+ while (path[j]) {
+ if (path[j] != '/') {
+ /* Pick up a / into p[] thru the assignment at the top of the loop */
+ k = j - 1;
+ last = 0;
+ break;
+ }
+ j++;
+ }
+
+ p[i] = 0;
+
+ if (!create_dir(p, last ? mode : 0755, last ? uid : 0, last ? gid : 0)) {
+ Free(p);
+ return 0;
+ }
+
+ if (last)
+ break;
+ }
+
+ if (!path[k])
+ break;
+ }
+
+ Free(p);
+ return 1;
+}
+
+/* ================================================== */
+
+int
+UTI_CheckDirPermissions(const char *path, mode_t perm, uid_t uid, gid_t gid)
+{
+ struct stat buf;
+
+ if (stat(path, &buf)) {
+ LOG(LOGS_ERR, "Could not access %s : %s", path, strerror(errno));
+ return 0;
+ }
+
+ if (!S_ISDIR(buf.st_mode)) {
+ LOG(LOGS_ERR, "%s is not directory", path);
+ return 0;
+ }
+
+ if ((buf.st_mode & 0777) & ~perm) {
+ LOG(LOGS_ERR, "Wrong permissions on %s", path);
+ return 0;
+ }
+
+ if (buf.st_uid != uid) {
+ LOG(LOGS_ERR, "Wrong owner of %s (%s != %u)", path, "UID", uid);
+ return 0;
+ }
+
+ if (buf.st_gid != gid) {
+ LOG(LOGS_ERR, "Wrong owner of %s (%s != %u)", path, "GID", gid);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+UTI_CheckFilePermissions(const char *path, mode_t perm)
+{
+ mode_t extra_perm;
+ struct stat buf;
+
+ if (stat(path, &buf) < 0 || !S_ISREG(buf.st_mode)) {
+ /* Not considered an error */
+ return 1;
+ }
+
+ extra_perm = (buf.st_mode & 0777) & ~perm;
+ if (extra_perm != 0) {
+ LOG(LOGS_WARN, "%s permissions on %s", extra_perm & 0006 ?
+ (extra_perm & 0004 ? "World-readable" : "World-writable") : "Wrong", path);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+UTI_CheckReadOnlyAccess(const char *path)
+{
+ if (access(path, R_OK) != 0 && errno != ENOENT)
+ LOG(LOGS_WARN, "Missing read access to %s : %s", path, strerror(errno));
+ if (access(path, W_OK) == 0)
+ LOG(LOGS_WARN, "Having write access to %s", path);
+}
+
+/* ================================================== */
+
+static int
+join_path(const char *basedir, const char *name, const char *suffix,
+ char *buffer, size_t length, LOG_Severity severity)
+{
+ const char *sep;
+
+ if (!basedir) {
+ basedir = "";
+ sep = "";
+ } else {
+ sep = "/";
+ }
+
+ if (!suffix)
+ suffix = "";
+
+ if (snprintf(buffer, length, "%s%s%s%s", basedir, sep, name, suffix) >= length) {
+ LOG(severity, "File path %s%s%s%s too long", basedir, sep, name, suffix);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+FILE *
+UTI_OpenFile(const char *basedir, const char *name, const char *suffix,
+ char mode, mode_t perm)
+{
+ const char *file_mode;
+ char path[PATH_MAX];
+ LOG_Severity severity;
+ int fd, flags;
+ FILE *file;
+
+ severity = mode >= 'A' && mode <= 'Z' ? LOGS_FATAL : LOGS_ERR;
+
+ if (!join_path(basedir, name, suffix, path, sizeof (path), severity))
+ return NULL;
+
+ switch (mode) {
+ case 'r':
+ case 'R':
+ flags = O_RDONLY;
+ file_mode = "r";
+ if (severity != LOGS_FATAL)
+ severity = LOGS_DEBUG;
+ break;
+ case 'w':
+ case 'W':
+ flags = O_WRONLY | O_CREAT | O_EXCL;
+ file_mode = "w";
+ break;
+ case 'a':
+ case 'A':
+ flags = O_WRONLY | O_CREAT | O_APPEND | O_NOFOLLOW;
+ file_mode = "a";
+ break;
+ default:
+ assert(0);
+ return NULL;
+ }
+
+try_again:
+ fd = open(path, flags, perm);
+ if (fd < 0) {
+ if (errno == EEXIST) {
+ if (unlink(path) < 0) {
+ LOG(severity, "Could not remove %s : %s", path, strerror(errno));
+ return NULL;
+ }
+ DEBUG_LOG("Removed %s", path);
+ goto try_again;
+ }
+ LOG(severity, "Could not open %s : %s", path, strerror(errno));
+ return NULL;
+ }
+
+ UTI_FdSetCloexec(fd);
+
+ file = fdopen(fd, file_mode);
+ if (!file) {
+ LOG(severity, "Could not open %s : %s", path, strerror(errno));
+ close(fd);
+ return NULL;
+ }
+
+ DEBUG_LOG("Opened %s fd=%d mode=%c", path, fd, mode);
+
+ return file;
+}
+
+/* ================================================== */
+
+int
+UTI_RenameTempFile(const char *basedir, const char *name,
+ const char *old_suffix, const char *new_suffix)
+{
+ char old_path[PATH_MAX], new_path[PATH_MAX];
+
+ if (!join_path(basedir, name, old_suffix, old_path, sizeof (old_path), LOGS_ERR))
+ return 0;
+
+ if (!join_path(basedir, name, new_suffix, new_path, sizeof (new_path), LOGS_ERR))
+ goto error;
+
+ if (rename(old_path, new_path) < 0) {
+ LOG(LOGS_ERR, "Could not replace %s with %s : %s", new_path, old_path, strerror(errno));
+ goto error;
+ }
+
+ DEBUG_LOG("Renamed %s to %s", old_path, new_path);
+
+ return 1;
+
+error:
+ if (unlink(old_path) < 0)
+ LOG(LOGS_ERR, "Could not remove %s : %s", old_path, strerror(errno));
+
+ return 0;
+}
+
+/* ================================================== */
+
+int
+UTI_RemoveFile(const char *basedir, const char *name, const char *suffix)
+{
+ char path[PATH_MAX];
+ struct stat buf;
+
+ if (!join_path(basedir, name, suffix, path, sizeof (path), LOGS_ERR))
+ return 0;
+
+ /* Avoid logging an error message if the file is not accessible */
+ if (stat(path, &buf) < 0) {
+ DEBUG_LOG("Could not remove %s : %s", path, strerror(errno));
+ return 0;
+ }
+
+ if (unlink(path) < 0) {
+ LOG(LOGS_ERR, "Could not remove %s : %s", path, strerror(errno));
+ return 0;
+ }
+
+ DEBUG_LOG("Removed %s", path);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+UTI_DropRoot(uid_t uid, gid_t gid)
+{
+ /* Drop supplementary groups */
+ if (setgroups(0, NULL))
+ LOG_FATAL("setgroups() failed : %s", strerror(errno));
+
+ /* Set effective, saved and real group ID */
+ if (setgid(gid))
+ LOG_FATAL("setgid(%u) failed : %s", gid, strerror(errno));
+
+ /* Set effective, saved and real user ID */
+ if (setuid(uid))
+ LOG_FATAL("setuid(%u) failed : %s", uid, strerror(errno));
+
+ DEBUG_LOG("Dropped root privileges: UID %u GID %u", uid, gid);
+}
+
+/* ================================================== */
+
+#define DEV_URANDOM "/dev/urandom"
+
+static FILE *urandom_file = NULL;
+
+void
+UTI_GetRandomBytesUrandom(void *buf, unsigned int len)
+{
+ if (!urandom_file)
+ urandom_file = UTI_OpenFile(NULL, DEV_URANDOM, NULL, 'R', 0);
+ if (fread(buf, 1, len, urandom_file) != len)
+ LOG_FATAL("Can't read from %s", DEV_URANDOM);
+}
+
+/* ================================================== */
+
+#ifdef HAVE_GETRANDOM
+
+static unsigned int getrandom_buf_available = 0;
+
+static void
+get_random_bytes_getrandom(char *buf, unsigned int len)
+{
+ static char rand_buf[256];
+ static unsigned int disabled = 0;
+ unsigned int i;
+
+ for (i = 0; i < len; i++) {
+ if (getrandom_buf_available == 0) {
+ if (disabled)
+ break;
+
+ if (getrandom(rand_buf, sizeof (rand_buf), GRND_NONBLOCK) != sizeof (rand_buf)) {
+ disabled = 1;
+ break;
+ }
+
+ getrandom_buf_available = sizeof (rand_buf);
+ }
+
+ buf[i] = rand_buf[--getrandom_buf_available];
+ }
+
+ if (i < len)
+ UTI_GetRandomBytesUrandom(buf, len);
+}
+#endif
+
+/* ================================================== */
+
+void
+UTI_GetRandomBytes(void *buf, unsigned int len)
+{
+#ifdef HAVE_ARC4RANDOM
+ arc4random_buf(buf, len);
+#elif defined(HAVE_GETRANDOM)
+ get_random_bytes_getrandom(buf, len);
+#else
+ UTI_GetRandomBytesUrandom(buf, len);
+#endif
+}
+
+/* ================================================== */
+
+void
+UTI_ResetGetRandomFunctions(void)
+{
+ if (urandom_file) {
+ fclose(urandom_file);
+ urandom_file = NULL;
+ }
+#ifdef HAVE_GETRANDOM
+ getrandom_buf_available = 0;
+#endif
+}
+
+/* ================================================== */
+
+int
+UTI_BytesToHex(const void *buf, unsigned int buf_len, char *hex, unsigned int hex_len)
+{
+ unsigned int i, l;
+
+ if (hex_len < 1)
+ return 0;
+
+ hex[0] = '\0';
+
+ for (i = l = 0; i < buf_len; i++, l += 2) {
+ if (l + 2 >= hex_len ||
+ snprintf(hex + l, hex_len - l, "%02hhX", ((const char *)buf)[i]) != 2)
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+unsigned int
+UTI_HexToBytes(const char *hex, void *buf, unsigned int len)
+{
+ char *p, byte[3];
+ unsigned int i;
+
+ for (i = 0; i < len && *hex != '\0'; i++) {
+ byte[0] = *hex++;
+ if (*hex == '\0')
+ return 0;
+ byte[1] = *hex++;
+ byte[2] = '\0';
+ ((char *)buf)[i] = strtol(byte, &p, 16);
+
+ if (p != byte + 2)
+ return 0;
+ }
+
+ return *hex == '\0' ? i : 0;
+}
+
+/* ================================================== */
+
+int
+UTI_SplitString(char *string, char **words, int max_saved_words)
+{
+ char *s = string;
+ int i;
+
+ for (i = 0; i < max_saved_words; i++)
+ words[i] = NULL;
+
+ for (i = 0; ; i++) {
+ /* Zero white-space characters before the word */
+ while (*s != '\0' && isspace((unsigned char)*s))
+ *s++ = '\0';
+
+ if (*s == '\0')
+ break;
+
+ if (i < max_saved_words)
+ words[i] = s;
+
+ /* Find the next word */
+ while (*s != '\0' && !isspace((unsigned char)*s))
+ s++;
+ }
+
+ return i;
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..9f11ba6
--- /dev/null
+++ b/util.h
@@ -0,0 +1,275 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Various utility functions
+ */
+
+#ifndef GOT_UTIL_H
+#define GOT_UTIL_H
+
+#include "sysincl.h"
+
+#include "addressing.h"
+#include "ntp.h"
+#include "candm.h"
+#include "cmac.h"
+#include "hash.h"
+
+/* Zero a timespec */
+extern void UTI_ZeroTimespec(struct timespec *ts);
+
+/* Check if a timespec is zero */
+extern int UTI_IsZeroTimespec(struct timespec *ts);
+
+/* Convert a timeval into a timespec */
+extern void UTI_TimevalToTimespec(const struct timeval *tv, struct timespec *ts);
+
+/* Convert a timespec into a timeval */
+extern void UTI_TimespecToTimeval(const struct timespec *ts, struct timeval *tv);
+
+/* Convert a timespec into a floating point number of seconds */
+extern double UTI_TimespecToDouble(const struct timespec *ts);
+
+/* Convert a number of seconds expressed in floating point into a
+ timespec */
+extern void UTI_DoubleToTimespec(double d, struct timespec *ts);
+
+/* Normalise a timespec, by adding or subtracting seconds to bring
+ its nanosecond field into range */
+extern void UTI_NormaliseTimespec(struct timespec *ts);
+
+/* Convert a timeval into a floating point number of seconds */
+extern double UTI_TimevalToDouble(const struct timeval *tv);
+
+/* Convert a number of seconds expressed in floating point into a
+ timeval */
+extern void UTI_DoubleToTimeval(double a, struct timeval *b);
+
+/* Normalise a struct timeval, by adding or subtracting seconds to bring
+ its microseconds field into range */
+extern void UTI_NormaliseTimeval(struct timeval *x);
+
+/* Returns -1 if a comes earlier than b, 0 if a is the same time as b,
+ and +1 if a comes after b */
+extern int UTI_CompareTimespecs(const struct timespec *a, const struct timespec *b);
+
+/* Calculate result = a - b */
+extern void UTI_DiffTimespecs(struct timespec *result,
+ const struct timespec *a, const struct timespec *b);
+
+/* Calculate result = a - b and return as a double */
+extern double UTI_DiffTimespecsToDouble(const struct timespec *a, const struct timespec *b);
+
+/* Add a double increment to a timespec to get a new one. 'start' is
+ the starting time, 'end' is the result that we return. This is
+ safe to use if start and end are the same */
+extern void UTI_AddDoubleToTimespec(const struct timespec *start, double increment,
+ struct timespec *end);
+
+/* Calculate the average and difference (as a double) of two timespecs */
+extern void UTI_AverageDiffTimespecs(const struct timespec *earlier, const struct timespec *later,
+ struct timespec *average, double *diff);
+
+/* Calculate result = a - b + c */
+extern void UTI_AddDiffToTimespec(const struct timespec *a, const struct timespec *b,
+ const struct timespec *c, struct timespec *result);
+
+/* Convert a timespec into a temporary string, largely for diagnostic
+ display */
+extern char *UTI_TimespecToString(const struct timespec *ts);
+
+/* Convert an NTP timestamp into a temporary string, largely for
+ diagnostic display */
+extern char *UTI_Ntp64ToString(const NTP_int64 *ts);
+
+/* Convert ref_id into a temporary string, for diagnostics */
+extern char *UTI_RefidToString(uint32_t ref_id);
+
+/* Convert an IP address to string, for diagnostics */
+extern char *UTI_IPToString(const IPAddr *ip);
+
+extern int UTI_StringToIP(const char *addr, IPAddr *ip);
+extern int UTI_IsStringIP(const char *string);
+extern int UTI_StringToIdIP(const char *addr, IPAddr *ip);
+extern int UTI_IsIPReal(const IPAddr *ip);
+extern uint32_t UTI_IPToRefid(const IPAddr *ip);
+extern uint32_t UTI_IPToHash(const IPAddr *ip);
+extern void UTI_IPHostToNetwork(const IPAddr *src, IPAddr *dest);
+extern void UTI_IPNetworkToHost(const IPAddr *src, IPAddr *dest);
+extern int UTI_CompareIPs(const IPAddr *a, const IPAddr *b, const IPAddr *mask);
+
+extern char *UTI_IPSockAddrToString(const IPSockAddr *sa);
+
+extern char *UTI_IPSubnetToString(IPAddr *subnet, int bits);
+
+extern char *UTI_TimeToLogForm(time_t t);
+
+/* Adjust time following a frequency/offset change */
+extern void UTI_AdjustTimespec(const struct timespec *old_ts, const struct timespec *when,
+ struct timespec *new_ts, double *delta_time,
+ double dfreq, double doffset);
+
+/* Get zero NTP timestamp with random bits below precision */
+extern void UTI_GetNtp64Fuzz(NTP_int64 *ts, int precision);
+
+extern double UTI_Ntp32ToDouble(NTP_int32 x);
+extern NTP_int32 UTI_DoubleToNtp32(double x);
+
+extern double UTI_Ntp32f28ToDouble(NTP_int32 x);
+extern NTP_int32 UTI_DoubleToNtp32f28(double x);
+
+/* Zero an NTP timestamp */
+extern void UTI_ZeroNtp64(NTP_int64 *ts);
+
+/* Check if an NTP timestamp is zero */
+extern int UTI_IsZeroNtp64(const NTP_int64 *ts);
+
+/* Compare two NTP timestamps. Returns -1 if a is before b, 0 if a is equal to
+ b, and 1 if a is after b. */
+extern int UTI_CompareNtp64(const NTP_int64 *a, const NTP_int64 *b);
+
+/* Compare an NTP timestamp with up to three other timestamps. Returns 0
+ if a is not equal to any of b1, b2, and b3, 1 otherwise. */
+extern int UTI_IsEqualAnyNtp64(const NTP_int64 *a, const NTP_int64 *b1,
+ const NTP_int64 *b2, const NTP_int64 *b3);
+
+/* Convert a timespec into an NTP timestamp */
+extern void UTI_TimespecToNtp64(const struct timespec *src, NTP_int64 *dest,
+ const NTP_int64 *fuzz);
+
+/* Convert an NTP timestamp into a timespec */
+extern void UTI_Ntp64ToTimespec(const NTP_int64 *src, struct timespec *dest);
+
+/* Calculate a - b in any epoch */
+extern double UTI_DiffNtp64ToDouble(const NTP_int64 *a, const NTP_int64 *b);
+
+/* Convert a difference in double (not a timestamp) from and to NTP format */
+extern double UTI_Ntp64ToDouble(NTP_int64 *src);
+extern void UTI_DoubleToNtp64(double src, NTP_int64 *dest);
+
+/* Check if time + offset is sane */
+extern int UTI_IsTimeOffsetSane(const struct timespec *ts, double offset);
+
+/* Get 2 raised to power of a signed integer */
+extern double UTI_Log2ToDouble(int l);
+
+extern void UTI_TimespecNetworkToHost(const Timespec *src, struct timespec *dest);
+extern void UTI_TimespecHostToNetwork(const struct timespec *src, Timespec *dest);
+
+uint64_t UTI_Integer64NetworkToHost(Integer64 i);
+Integer64 UTI_Integer64HostToNetwork(uint64_t i);
+
+extern double UTI_FloatNetworkToHost(Float x);
+extern Float UTI_FloatHostToNetwork(double x);
+
+extern CMC_Algorithm UTI_CmacNameToAlgorithm(const char *name);
+extern HSH_Algorithm UTI_HashNameToAlgorithm(const char *name);
+
+/* Set FD_CLOEXEC on descriptor */
+extern int UTI_FdSetCloexec(int fd);
+
+extern void UTI_SetQuitSignalsHandler(void (*handler)(int), int ignore_sigpipe);
+
+/* Get directory (as an allocated string) for a path */
+extern char *UTI_PathToDir(const char *path);
+
+/* Create a directory with a specified mode (umasked) and set its uid/gid.
+ Create also any parent directories that don't exist with mode 755 and
+ default uid/gid. Returns 1 if created or already exists (even with
+ different mode/uid/gid), 0 otherwise. */
+extern int UTI_CreateDirAndParents(const char *path, mode_t mode, uid_t uid, gid_t gid);
+
+/* Check if a directory is secure. It must not have other than the specified
+ permissions and its uid/gid must match the specified values. */
+extern int UTI_CheckDirPermissions(const char *path, mode_t perm, uid_t uid, gid_t gid);
+
+/* Check and log a warning message if a file has more permissions than
+ specified. It does not return error if it is not an accessible file. */
+extern int UTI_CheckFilePermissions(const char *path, mode_t perm);
+
+/* Log a warning message if not having read access or having write access
+ to a file/directory */
+extern void UTI_CheckReadOnlyAccess(const char *path);
+
+/* Open a file. The full path of the file is constructed from the basedir
+ (may be NULL), '/' (if basedir is not NULL), name, and suffix (may be NULL).
+ Created files have specified permissions (umasked). Returns NULL on error.
+ The following modes are supported (if the mode is an uppercase character,
+ errors are fatal):
+ r/R - open an existing file for reading
+ w/W - open a new file for writing (remove existing file)
+ a/A - open an existing file for appending (create if does not exist) */
+extern FILE *UTI_OpenFile(const char *basedir, const char *name, const char *suffix,
+ char mode, mode_t perm);
+
+/* Rename a temporary file by changing its suffix. The paths are constructed as
+ in UTI_OpenFile(). If the renaming fails, the file will be removed. */
+extern int UTI_RenameTempFile(const char *basedir, const char *name,
+ const char *old_suffix, const char *new_suffix);
+
+/* Remove a file. The path is constructed as in UTI_OpenFile(). */
+extern int UTI_RemoveFile(const char *basedir, const char *name, const char *suffix);
+
+/* Set process user/group IDs and drop supplementary groups */
+extern void UTI_DropRoot(uid_t uid, gid_t gid);
+
+/* Fill buffer with random bytes from /dev/urandom */
+extern void UTI_GetRandomBytesUrandom(void *buf, unsigned int len);
+
+/* Fill buffer with random bytes from /dev/urandom or a faster source if it's
+ available (e.g. arc4random()), which may not necessarily be suitable for
+ generating long-term keys */
+extern void UTI_GetRandomBytes(void *buf, unsigned int len);
+
+/* Close /dev/urandom and drop any cached data used by the GetRandom functions
+ to prevent forked processes getting the same sequence of random numbers */
+extern void UTI_ResetGetRandomFunctions(void);
+
+/* Print data in hexadecimal format */
+extern int UTI_BytesToHex(const void *buf, unsigned int buf_len, char *hex, unsigned int hex_len);
+
+/* Parse a string containing data in hexadecimal format. In-place conversion
+ is supported. */
+extern unsigned int UTI_HexToBytes(const char *hex, void *buf, unsigned int len);
+
+/* Split a string into words separated by whitespace characters. It returns
+ the number of words found in the string, but saves only up to the specified
+ number of pointers to the words. */
+extern int UTI_SplitString(char *string, char **words, int max_saved_words);
+
+/* Macros to get maximum and minimum of two values */
+#ifdef MAX
+#undef MAX
+#endif
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#ifdef MIN
+#undef MIN
+#endif
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+
+/* Macro to clamp a value between two values */
+#define CLAMP(min, x, max) (MAX((min), MIN((x), (max))))
+
+#define SQUARE(x) ((x) * (x))
+
+#endif /* GOT_UTIL_H */
diff --git a/version.txt b/version.txt
new file mode 100644
index 0000000..4caecc7
--- /dev/null
+++ b/version.txt
@@ -0,0 +1 @@
+4.5