diff options
Diffstat (limited to '')
291 files changed, 82732 insertions, 0 deletions
@@ -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. @@ -0,0 +1,1045 @@ +Frequently Asked Questions + +Table of Contents + + o 1. chrony compared to other programs + ? 1.1. How does chrony compare to ntpd? + 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. Should be a leap smear enabled on NTP server? + ? 2.12. How should chronyd be configuration with gpsd? + ? 2.13. Does chrony support PTP? + ? 2.14. Why are client log records dropped before reaching + clientloglimit? + ? 2.15. 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. + +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 +authselmode directive. + +An example of a client configuration limiting the impact of the attacks could +be + +server foo.example.net iburst nts maxdelay 0.1 +server bar.example.net iburst nts maxdelay 0.2 +server baz.example.net iburst nts maxdelay 0.05 +server qux.example.net iburst nts maxdelay 0.1 +server quux.example.net iburst nts maxdelay 0.1 +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 foo.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 + +As an experimental feature added in 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 + +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. 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.12. How should chronyd be configuration 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 message-based time source provided by gpsd is specified as a SHM 0 +refclock, or other even number if gpsd is configured with multiple receivers. + +The PPS-based time source is specified as a SHM 1 refclock (or other odd +number), or SOCK /var/run/chrony.DEV.sock where DEV is the name of the serial +device (e.g. ttyS0). + +With chronyd and gpsd both supporting PPS, and gpsd providing two different +refclocks for PPS, there are three different recommended configurations: + +# First option +refclock SOCK /var/run/chrony.ttyS0.sock refid GPS + +# Second option +refclock SHM 1 refid GPS + +# Third option +refclock PPS /dev/pps0 lock NMEA refid GPS +refclock SHM 0 offset 0.5 delay 0.1 refid NMEA noselect + +Each option has some advantages: + + o SOCK does not use polling (i.e. it can get samples earlier than SHM), but + it requires gpsd to be started after chronyd in order to connect to its + socket + + o SOCK and SHM 1 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 SHM 0 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 SHM 0 offset 0.5 delay 0.1 refid GPS + +2.13. 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. + +2.14. 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.15. 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 packets +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 +=============================================================================== +^* foo.example.net 2 6 377 34 +484us[ -157us] +/- 30ms +^- bar.example.net 2 6 377 34 +33ms[ +32ms] +/- 47ms +^+ baz.example.net 3 6 377 35 -1397us[-2033us] +/- 60ms + +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 +========================================================================= +foo.example.net NTS 1 15 256 33m 0 0 8 100 +bar.example.net NTS 1 15 256 33m 0 0 8 100 +baz.example.net NTS 1 15 256 33m 0 0 8 100 + +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 +foo.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 2022-08-29 15:04:33 +0200 @@ -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 2022-08-29 15:04:33 +0200 diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..ef100a4 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,139 @@ +################################################## +# +# 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@ + +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) + +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 @@ -0,0 +1,977 @@ +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. @@ -0,0 +1,160 @@ +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.tuxfamily.org/ + +Where are new versions announced? +================================= + +There is a low volume mailing list where new versions and other +important news relating to chrony are announced. You can join this list +by sending mail with the subject "subscribe" to + +chrony-announce-request@chrony.tuxfamily.org + +How can I get support for chrony? +================================= + +There are two other mailing lists relating to chrony. chrony-users is a +discussion list for users, e.g. for questions about chrony configuration +and bug reports. chrony-dev is a more technical list for developers, +e.g. for submitting patches and discussing how new features should be +implemented. To subscribe to either of these lists, send a message with +the subject "subscribe" to + +chrony-users-request@chrony.tuxfamily.org +or +chrony-dev-request@chrony.tuxfamily.org + +as applicable. + +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> +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> +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> +Chris Perl <cperl@janestreet.com> +Gautier PHILIPPON <gautier.philippon@ensimag.grenoble-inp.fr> +Andreas Piesk <apiesk@virbus.de> +Baruch Siach <baruch@tkos.co.il> +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> +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 */ @@ -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 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_SetSize(ARR_Instance array, unsigned int size) +{ + realloc_array(array, size); + array->used = size; +} + +unsigned int +ARR_GetSize(ARR_Instance array) +{ + return array->used; +} @@ -0,0 +1,56 @@ +/* + 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); + +/* 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 @@ -0,0 +1,824 @@ +/* + 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 N_REQUEST_TYPES 72 + +/* 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 + +/* 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_EXP1 0x800 + +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; + +/* ================================================== */ + +#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; + } 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 N_REPLY_TYPES 25 + +/* 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 { + uint32_t ntp_hits; + uint32_t nke_hits; + uint32_t cmd_hits; + uint32_t ntp_drops; + uint32_t nke_drops; + uint32_t cmd_drops; + uint32_t log_drops; + uint32_t ntp_auth_hits; + uint32_t ntp_interleaved_hits; + uint32_t ntp_timestamps; + uint32_t ntp_span_seconds; + 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..0a7bcc8 --- /dev/null +++ b/client.c @@ -0,0 +1,3451 @@ +/* + 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-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. + * + ********************************************************************** + + ======================================================================= + + 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; + +/* ================================================== */ +/* 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 +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_EXP1 ? REQ_ADDSRC_EF_EXP1 : 0) | + (data.params.sel_options & SRC_SELECT_PREFER ? REQ_ADDSRC_PREFER : 0) | + (data.params.sel_options & SRC_SELECT_NOSELECT ? REQ_ADDSRC_NOSELECT : 0) | + (data.params.sel_options & SRC_SELECT_TRUST ? REQ_ADDSRC_TRUST : 0) | + (data.params.sel_options & SRC_SELECT_REQUIRE ? REQ_ADDSRC_REQUIRE : 0)); + 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" + "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", "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(unsigned long s) +{ + unsigned long d; + + if (s == (uint32_t)-1) { + printf(" -"); + } else if (s < 1200) { + printf("%4lu", s); + } else if (s < 36000) { + printf("%3lum", s / 60); + } else if (s < 345600) { + printf("%3luh", s / 3600); + } else { + d = s / 86400; + if (d > 999) { + printf("%3luy", d / 365); + } else { + printf("%3lud", 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 long long_uinteger; + unsigned int uinteger; + 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': /* interval with unit */ + long_uinteger = va_arg(ap, unsigned long); + print_seconds(long_uinteger); + 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 */ + long_uinteger = va_arg(ap, unsigned long); + printf("%08lX", long_uinteger); + 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': /* unsigned long in decimal */ + long_uinteger = va_arg(ap, unsigned long); + printf("%*lu", width, long_uinteger); + break; + case 'V': /* timespec as seconds since epoch */ + ts = va_arg(ap, struct timespec *); + printf("%s", UTI_TimespecToString(ts)); + 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; + + 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; + + format_name(name, sizeof (name), 25, + mode == RPY_SD_MD_REF && ip_addr.family == IPADDR_INET4, + ip_addr.addr.in4, 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), + (unsigned long)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, + (unsigned long)ntohl(reply.data.sourcestats.n_samples), + (unsigned long)ntohl(reply.data.sourcestats.n_runs), + (unsigned long)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", + (unsigned long)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, + (unsigned long)ntohl(reply.data.auth_data.key_id), + ntohs(reply.data.auth_data.key_type), + ntohs(reply.data.auth_data.key_length), + (unsigned long)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), (unsigned long)UTI_IPToRefid(&remote_addr), + ntohs(reply.data.ntp_data.remote_port), + UTI_IPToString(&local_addr), (unsigned long)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), + (unsigned long)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, + (unsigned long)ntohl(reply.data.ntp_data.total_tx_count), + (unsigned long)ntohl(reply.data.ntp_data.total_rx_count), + (unsigned long)ntohl(reply.data.ntp_data.total_valid_count), + (unsigned long)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' : '-', + '-', + (unsigned long)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_STATS3, 0)) + return 0; + + print_report("NTP packets received : %U\n" + "NTP packets dropped : %U\n" + "Command packets received : %U\n" + "Command packets dropped : %U\n" + "Client log records dropped : %U\n" + "NTS-KE connections accepted: %U\n" + "NTS-KE connections dropped : %U\n" + "Authenticated NTP packets : %U\n" + "Interleaved NTP packets : %U\n" + "NTP timestamps held : %U\n" + "NTP timestamp span : %U\n", + (unsigned long)ntohl(reply.data.server_stats.ntp_hits), + (unsigned long)ntohl(reply.data.server_stats.ntp_drops), + (unsigned long)ntohl(reply.data.server_stats.cmd_hits), + (unsigned long)ntohl(reply.data.server_stats.cmd_drops), + (unsigned long)ntohl(reply.data.server_stats.log_drops), + (unsigned long)ntohl(reply.data.server_stats.nke_hits), + (unsigned long)ntohl(reply.data.server_stats.nke_drops), + (unsigned long)ntohl(reply.data.server_stats.ntp_auth_hits), + (unsigned long)ntohl(reply.data.server_stats.ntp_interleaved_hits), + (unsigned long)ntohl(reply.data.server_stats.ntp_timestamps), + (unsigned long)ntohl(reply.data.server_stats.ntp_span_seconds), + 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), + (unsigned long)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, + (unsigned long)ntohl(client->ntp_hits), + (unsigned long)ntohl(client->ntp_drops), + client->ntp_interval, + client->ntp_timeout_interval, + (unsigned long)ntohl(client->last_ntp_hit_ago), + (unsigned long)ntohl(nke ? client->nke_hits : client->cmd_hits), + (unsigned long)ntohl(nke ? client->nke_drops : client->cmd_drops), + nke ? client->nke_interval : client->cmd_interval, + (unsigned long)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 = %lu\n", (unsigned long)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", + (unsigned long)ntohl(reply.data.activity.online), + (unsigned long)ntohl(reply.data.activity.offline), + (unsigned long)ntohl(reply.data.activity.burst_online), + (unsigned long)ntohl(reply.data.activity.burst_offline), + (unsigned long)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_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, (unsigned long)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, "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); + } + + 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-2022 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" +#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, "+46acdf: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 '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..adf0c59 --- /dev/null +++ b/clientlog.c @@ -0,0 +1,1088 @@ +/* + 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; + uint16_t flags; + 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 + +/* Global statistics */ +static uint32_t total_hits[MAX_SERVICES]; +static uint32_t total_drops[MAX_SERVICES]; +static uint32_t total_ntp_auth_hits; +static uint32_t total_ntp_interleaved_hits; +static uint32_t total_record_drops; + +#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_LogAuthNtpRequest(void) +{ + total_ntp_auth_hits++; +} + +/* ================================================== */ + +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_offset(NtpTimestamps *tss, NTP_int64 *rx_ts, struct timespec *tx_ts) +{ + 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; +} + +/* ================================================== */ + +static void +get_ntp_tx(NtpTimestamps *tss, struct timespec *tx_ts) +{ + 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); + } +} + +/* ================================================== */ + +void +CLG_SaveNtpTimestamps(NTP_int64 *rx_ts, struct timespec *tx_ts) +{ + 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_offset(tss, rx_ts, tx_ts); + + 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) +{ + uint32_t index; + + if (!ntp_ts_map.timestamps) + return; + + if (!find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index)) + return; + + set_ntp_tx_offset(get_ntp_tss(index), rx_ts, tx_ts); +} + +/* ================================================== */ + +int +CLG_GetNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts) +{ + 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); + + 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; +} diff --git a/clientlog.h b/clientlog.h new file mode 100644 index 0000000..2a5565e --- /dev/null +++ b/clientlog.h @@ -0,0 +1,63 @@ +/* + 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_LogAuthNtpRequest(void); +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); +extern void CLG_UndoNtpTxTimestampSlew(NTP_int64 *rx_ts, struct timespec *tx_ts); +extern void CLG_UpdateNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts); +extern int CLG_GetNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts); +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 */ @@ -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..e48a2fc --- /dev/null +++ b/cmdmon.c @@ -0,0 +1,1814 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2003 + * Copyright (C) Miroslav Lichvar 2009-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. + * + ********************************************************************** + + ======================================================================= + + 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 */ +}; + +/* ================================================== */ + +/* 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 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_EXP1 ? NTP_EF_FLAG_EXP1 : 0; + params.sel_options = + (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_PREFER ? SRC_SELECT_PREFER : 0) | + (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_NOSELECT ? SRC_SELECT_NOSELECT : 0) | + (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_TRUST ? SRC_SELECT_TRUST : 0) | + (ntohl(rx_message->data.ntp_source.flags) & REQ_ADDSRC_REQUIRE ? SRC_SELECT_REQUIRE : 0); + + status = NSR_AddSourceByName(name, port, pool, type, ¶ms, 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_STATS3); + tx_message->data.server_stats.ntp_hits = htonl(report.ntp_hits); + tx_message->data.server_stats.nke_hits = htonl(report.nke_hits); + tx_message->data.server_stats.cmd_hits = htonl(report.cmd_hits); + tx_message->data.server_stats.ntp_drops = htonl(report.ntp_drops); + tx_message->data.server_stats.nke_drops = htonl(report.nke_drops); + tx_message->data.server_stats.cmd_drops = htonl(report.cmd_drops); + tx_message->data.server_stats.log_drops = htonl(report.log_drops); + tx_message->data.server_stats.ntp_auth_hits = htonl(report.ntp_auth_hits); + tx_message->data.server_stats.ntp_interleaved_hits = htonl(report.ntp_interleaved_hits); + tx_message->data.server_stats.ntp_timestamps = htonl(report.ntp_timestamps); + tx_message->data.server_stats.ntp_span_seconds = htonl(report.ntp_span_seconds); +} + +/* ================================================== */ + +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_select_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_select_options(report.conf_options)); + tx_message->data.select_data.eff_options = htons(convert_select_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); +} + +/* ================================================== */ +/* 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) { + 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; + + default: + DEBUG_LOG("Unhandled command %d", rx_command); + tx_message.status = htons(STT_FAILED); + break; + } + } 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) { + 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..1a9e210 --- /dev/null +++ b/cmdparse.c @@ -0,0 +1,398 @@ +/* + 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; + + 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, "noselect")) { + src->params.sel_options |= SRC_SELECT_NOSELECT; + } else if (!strcasecmp(cmd, "prefer")) { + src->params.sel_options |= SRC_SELECT_PREFER; + } else if (!strcasecmp(cmd, "require")) { + src->params.sel_options |= SRC_SELECT_REQUIRE; + } else if (!strcasecmp(cmd, "trust")) { + src->params.sel_options |= SRC_SELECT_TRUST; + } 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_EXP1: + src->params.ext_fields |= NTP_EF_FLAG_EXP1; + 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 { + return 0; + } + } + + return 1; +} + +/* ================================================== */ + +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; +} diff --git a/cmdparse.h b/cmdparse.h new file mode 100644 index 0000000..fd1eb43 --- /dev/null +++ b/cmdparse.h @@ -0,0 +1,57 @@ +/* + 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); + +/* 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); + +#endif /* GOT_CMDPARSE_H */ @@ -0,0 +1,2607 @@ +/* + 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; + +/* 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; + +/* 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, "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, "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, stratum, tai; + uint32_t ref_id, lock_ref_id; + double offset, delay, precision, max_dispersion, pulse_width; + char *p, *cmd, *name, *param; + unsigned char ref[5]; + 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 (sscanf(line, "%4s%n", (char *)ref, &n) != 1) + break; + ref_id = (uint32_t)ref[0] << 24 | ref[1] << 16 | ref[2] << 8 | ref[3]; + } else if (!strcasecmp(cmd, "lock")) { + if (sscanf(line, "%4s%n", (char *)ref, &n) != 1) + break; + lock_ref_id = (uint32_t)ref[0] << 24 | ref[1] << 16 | ref[2] << 8 | ref[3]; + } 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 (!strcasecmp(cmd, "noselect")) { + n = 0; + sel_options |= SRC_SELECT_NOSELECT; + } else if (!strcasecmp(cmd, "prefer")) { + n = 0; + sel_options |= SRC_SELECT_PREFER; + } else if (!strcasecmp(cmd, "trust")) { + n = 0; + sel_options |= SRC_SELECT_TRUST; + } else if (!strcasecmp(cmd, "require")) { + n = 0; + sel_options |= SRC_SELECT_REQUIRE; + } 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; + char *p, filter[5]; + int n; + + 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, "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(); +} + +/* ================================================== */ + +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 = sa->pool - sb->pool) != 0) + return d; + if ((d = sa->params.port - 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; + + 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; + + qsort(new_sources, new_size, sizeof (new_sources[0]), compare_sources); + + for (i = j = 0; i < prev_size || j < new_size; ) { + if (i < prev_size && j < new_size) + d = compare_sources(&prev_sources[i], &new_sources[j]); + else + d = i < prev_size ? -1 : 1; + + if (d < 0) { + /* Remove the missing source */ + if (prev_sources[i].params.name[0] != '\0') + NSR_RemoveSourcesById(prev_ids[i]); + i++; + } else if (d > 0) { + /* Add a newly configured source */ + source = &new_sources[j]; + s = NSR_AddSourceByName(source->params.name, source->params.port, source->pool, + source->type, &source->params.params, &new_ids[j]); + + if (s == NSR_UnresolvedName) { + unresolved++; + } else if (s != NSR_Success) { + LOG(LOGS_ERR, "Could not add source %s", source->params.name); + + /* Mark the source as not present */ + source->params.name[0] = '\0'; + } + j++; + } else { + /* Keep the existing source */ + new_ids[j] = prev_ids[i]; + i++, j++; + } + } + + 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_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; +} + +/* ================================================== */ + +int +CNF_GetPtpPort(void) +{ + return ptp_port; +} + +/* ================================================== */ + +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; +} @@ -0,0 +1,170 @@ +/* + 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_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 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 int CNF_GetPtpPort(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..fd777c8 --- /dev/null +++ b/configure @@ -0,0 +1,1134 @@ +#!/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-2021 +# 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-nss Don't use NSS even if it is available + --without-tomcrypt Don't use libtomcrypt even if it is available + --disable-nts Disable NTS support + --without-gnutls Don't use gnutls even if it is available + --disable-cmdmon Disable command and monitoring support + --disable-ntp Disable NTP support + --disable-refclock Disable reference clock support + --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-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_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-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 + +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_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 + +if [ $feat_sechash = "1" ] && [ "x$HASH_LINK" = "x" ] && [ $try_gnutls = "1" ]; then + test_cflags="`pkg_config --cflags gnutls`" + test_link="`pkg_config --libs gnutls`" + if test_code 'gnutls' 'gnutls/crypto.h' \ + "$test_cflags" "$test_link" ' + return gnutls_hash((void *)1, (void *)2, 1);' + then + HASH_OBJ="hash_gnutls.o" + HASH_LINK="$test_link" + MYCPPFLAGS="$MYCPPFLAGS $test_cflags" + add_def FEAT_SECHASH + + if test_code 'CMAC in gnutls' 'gnutls/crypto.h' "$test_cflags" "$test_link" \ + 'return gnutls_hmac_init((void *)1, GNUTLS_MAC_AES_CMAC_128, (void *)2, 0);' + then + add_def HAVE_CMAC + EXTRA_OBJECTS="$EXTRA_OBJECTS cmac_gnutls.o" + EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS cmac_gnutls.o" + fi + fi +fi + +EXTRA_OBJECTS="$EXTRA_OBJECTS $HASH_OBJ" +EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS $HASH_OBJ" +LIBS="$LIBS $HASH_LINK" + +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 test_code 'SIV 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 + else + if test_code 'SIV 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 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%@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..3a0a2ef --- /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.tuxfamily.chronyc.plist +-rw-r--r-- 1 yourname staff 511 19 Jun 18:30 org.tuxfamily.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.tuxfamily.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.tuxfamily.chronyc.plist /Library/LaunchDaemons +sudo chown root:wheel /Library/LaunchDaemons/org.tuxfamily.chronyc.plist +sudo chmod 0644 /Library/LaunchDaemons/org.tuxfamily.chronyc.plist +sudo launchctl load -w /Library/LaunchDaemons/org.tuxfamily.chronyc.plist + + +3. org.tuxfamily.chronyd.plist +This file is the launchd plist that runs chronyd when the Macintosh starts. + +sudo cp org.tuxfamily.chronyd.plist /Library/LaunchDaemons +sudo chown root:wheel /Library/LaunchDaemons/org.tuxfamily.chronyd.plist +sudo chmod 0644 /Library/LaunchDaemons/org.tuxfamily.chronyd.plist +sudo launchctl load -w /Library/LaunchDaemons/org.tuxfamily.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.tuxfamily.chronyc.plist b/contrib/bryan_christianson_1/org.tuxfamily.chronyc.plist new file mode 100644 index 0000000..a3c42c6 --- /dev/null +++ b/contrib/bryan_christianson_1/org.tuxfamily.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.tuxfamily.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.tuxfamily.chronyd.plist b/contrib/bryan_christianson_1/org.tuxfamily.chronyd.plist new file mode 100644 index 0000000..2bf42aa --- /dev/null +++ b/contrib/bryan_christianson_1/org.tuxfamily.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.tuxfamily.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..2cf5326 --- /dev/null +++ b/doc/chrony.conf.adoc @@ -0,0 +1,3071 @@ +// 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-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. + += 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. It can also be +triggered manually for all sources by the <<chronyc.adoc#refresh,*refresh*>> +command in *chronyc*. ++ +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). ++ +The following extension field can be enabled by this option: ++ +_F323_:::: +This is an experimental extension field for some improvements that were +proposed for the next version of the NTP protocol (NTPv5). The field contains +root delay and dispersion in higher resolution and a monotonic receive +timestamp, which enables a frequency transfer between the server and client. It +can significantly improve stability of the synchronization. Generally, it +should be expected to work only between servers and clients running the same +version of *chronyd*. +{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 foo.example.net bar.example.net baz.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 GPS +refclock SHM 0 offset 0.5 delay 0.2 refid NMEA noselect +refclock PPS /dev/pps1:clear refid GPS2 +---- ++ +*SHM*::: +NTP shared memory driver. This driver uses a shared memory segment to receive +samples from another process (e.g. *gpsd*). The parameter is the number of the +shared memory segment, typically a small number like 0, 1, 2, or 3. 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}::: ++ +Examples: ++ +---- +refclock SHM 0 poll 3 refid GPS1 +refclock SHM 1:perm=0644 refid GPS2 +---- ++ +*SOCK*::: +Unix domain socket driver. It is similar to the SHM driver, but samples are +received from a Unix domain socket instead of shared memory and the messages +have a different format. The parameter is the path to the socket, which +*chronyd* creates on start. An advantage over the SHM driver is that SOCK does +not require polling and it can receive PPS samples with incomplete time. 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. The path +where *gpsd* expects the socket to be created is described in the *gpsd(8)* man +page. For example: ++ +---- +refclock SOCK /var/run/chrony.ttyS0.sock +---- ++ +*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. +*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). + +[[ntstrustedcerts]]*ntstrustedcerts* [_set-ID_] _file_|_directory_:: +This directive specifies a file or directory containing certificates (in the +PEM format) of trusted certificate authorities (CA) which can be used to +verify certificates of NTS servers. ++ +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/foo.crt +ntstrustedcerts 1 /etc/pki/nts/bar.crt +ntstrustedcerts 1 /etc/pki/nts/baz.crt +ntstrustedcerts 2 /etc/pki/nts/qux.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. + +=== 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 foo.example.net nts +server bar.example.net nts +server baz.example.net +refclock SHM 0 +---- ++ +is equivalent to the following configuration using the *ignore* mode: ++ +---- +authselectmode ignore +server foo.example.net nts require trust +server bar.example.net nts require trust +server baz.example.net +refclock SHM 0 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 master estimate. So if *chronyd* +has an existing highly-reliable master estimate and a new estimate is generated +which has large error bounds, the existing master estimate will dominate in the +new master estimate. + +[[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 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, this is useful to disable *chronyc* +access from the Internet. (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* switch 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). +. 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. The source of timestamps (i.e. hardware, kernel, or daemon) is +indicated in the _measurements.log_ file if enabled by the <<log,*log +measurements*>> directive, and the <<chronyc.adoc#ntpdata,*ntpdata*>> report in +*chronyc*. ++ +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 two. 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) and the minimum value is -6 (1/64th +of a second). +*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 * +---- + +[[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. The port recognized by the NICs is 319 (PTP event port). The default +value is 0 (disabled). ++ +The NTP-over-PTP support is experimental. The protocol and configuration can +change in future. It should be used only in local networks and expected to work +only between servers and clients running the same version of *chronyd*. ++ +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_. ++ +An example of client configuration is: ++ +---- +server foo.example.net minpoll 0 maxpoll 0 xleave port 319 +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 _foo.example.net_, _bar.example.net_ +and _baz.example.net_, your _chrony.conf_ file could contain as a minimum: + +---- +server foo.example.net +server bar.example.net +server baz.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 foo.example.net iburst +server bar.example.net iburst +server baz.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 foo.example.net iburst nts +server bar.example.net iburst nts +server baz.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 _foo.example.net_, +_bar.example.net_ and _baz.example.net_, your _chrony.conf_ file would now +contain: + +---- +server foo.example.net offline +server bar.example.net offline +server baz.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 foo.example.net maxdelay 0.4 offline +server bar.example.net maxdelay 0.4 offline +server baz.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 foo.example.net iburst +server bar.example.net iburst +server baz.example.net iburst +server qux.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.tuxfamily.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..1a51b24 --- /dev/null +++ b/doc/chrony.conf.man.in @@ -0,0 +1,4894 @@ +'\" t +.\" Title: chrony.conf +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-08-29 +.\" Manual: Configuration Files +.\" Source: chrony @CHRONY_VERSION@ +.\" Language: English +.\" +.TH "CHRONY.CONF" "5" "2022-08-29" "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. It can also be +triggered manually for all sources by the \fBrefresh\fP +command in \fBchronyc\fP. +.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 +The following extension field can be enabled by this option: +.sp +\fIF323\fP +.RS 4 +This is an experimental extension field for some improvements that were +proposed for the next version of the NTP protocol (NTPv5). The field contains +root delay and dispersion in higher resolution and a monotonic receive +timestamp, which enables a frequency transfer between the server and client. It +can significantly improve stability of the synchronization. Generally, it +should be expected to work only between servers and clients running the same +version of \fBchronyd\fP. +.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 foo.example.net bar.example.net baz.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 GPS +refclock SHM 0 offset 0.5 delay 0.2 refid NMEA noselect +refclock PPS /dev/pps1:clear refid GPS2 +.fam +.fi +.if n .RE +.RE +.sp +\fBSHM\fP +.RS 4 +NTP shared memory driver. This driver uses a shared memory segment to receive +samples from another process (e.g. \fBgpsd\fP). The parameter is the number of the +shared memory segment, typically a small number like 0, 1, 2, or 3. 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 +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 +\fBSOCK\fP +.RS 4 +Unix domain socket driver. It is similar to the SHM driver, but samples are +received from a Unix domain socket instead of shared memory and the messages +have a different format. The parameter is the path to the socket, which +\fBchronyd\fP creates on start. An advantage over the SHM driver is that SOCK does +not require polling and it can receive PPS samples with incomplete time. 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. The path +where \fBgpsd\fP expects the socket to be created is described in the \fBgpsd(8)\fP man +page. For example: +.sp +.if n .RS 4 +.nf +.fam C +refclock SOCK /var/run/chrony.ttyS0.sock +.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. +.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). +.RE +.sp +\fBntstrustedcerts\fP [\fIset\-ID\fP] \fIfile\fP|\fIdirectory\fP +.RS 4 +This directive specifies a file or directory containing certificates (in the +PEM format) of trusted certificate authorities (CA) which can be used to +verify certificates of NTS servers. +.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/foo.crt +ntstrustedcerts 1 /etc/pki/nts/bar.crt +ntstrustedcerts 1 /etc/pki/nts/baz.crt +ntstrustedcerts 2 /etc/pki/nts/qux.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 +.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 foo.example.net nts +server bar.example.net nts +server baz.example.net +refclock SHM 0 +.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 foo.example.net nts require trust +server bar.example.net nts require trust +server baz.example.net +refclock SHM 0 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 master estimate. So if \fBchronyd\fP +has an existing highly\-reliable master estimate and a new estimate is generated +which has large error bounds, the existing master estimate will dominate in the +new master estimate. +.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 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, this is useful to disable \fBchronyc\fP +access from the Internet. (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 switch 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 +.\} +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' 6.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 6." 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' 7.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 7." 4.2 +.\} +Interval since the last measurement of the source in seconds. [4.228e+01] +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04' 8.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 8." 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' 9.\h'+01'\c +.\} +.el \{\ +. sp -1 +. IP " 9." 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. The source of timestamps (i.e. hardware, kernel, or daemon) is +indicated in the \fImeasurements.log\fP file if enabled by the \fBlog +measurements\fP directive, and the \fBntpdata\fP report in +\fBchronyc\fP. +.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 two. 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) and the minimum value is \-6 (1/64th +of a second). +.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 +\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. The port recognized by the NICs 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 and expected to work +only between servers and clients running the same version of \fBchronyd\fP. +.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. +.sp +An example of client configuration is: +.sp +.if n .RS 4 +.nf +.fam C +server foo.example.net minpoll 0 maxpoll 0 xleave port 319 +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 \fIfoo.example.net\fP, \fIbar.example.net\fP +and \fIbaz.example.net\fP, your \fIchrony.conf\fP file could contain as a minimum: +.sp +.if n .RS 4 +.nf +.fam C +server foo.example.net +server bar.example.net +server baz.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 foo.example.net iburst +server bar.example.net iburst +server baz.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 foo.example.net iburst nts +server bar.example.net iburst nts +server baz.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 \fIfoo.example.net\fP, +\fIbar.example.net\fP and \fIbaz.example.net\fP, your \fIchrony.conf\fP file would now +contain: +.sp +.if n .RS 4 +.nf +.fam C +server foo.example.net offline +server bar.example.net offline +server baz.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 foo.example.net maxdelay 0.4 offline +server bar.example.net maxdelay 0.4 offline +server baz.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 foo.example.net iburst +server bar.example.net iburst +server baz.example.net iburst +server qux.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.tuxfamily.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..45d8548 --- /dev/null +++ b/doc/chronyc.adoc @@ -0,0 +1,1508 @@ +// 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-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. + += 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. + +*-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 (foo.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. _foo.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 +^? foo.example.net 2 6 377 23 -923us[ -924us] +/- 43ms +^+ bar.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. The number following the _+/-_ indicator shows the margin of error in +the measurement. Positive offsets indicate that the local clock is ahead of +the source. + +[[sourcestats]]*sourcestats* [*-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 +=============================================================================== +foo.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 foo.example.net Y ----- --TR- 4 1.0 -61ms +62ms N +* bar.example.net N ----- ----- 0 1.0 -6846us +7305us N ++ baz.example.net N ----- ----- 10 1.0 -7381us +7355us N +---- ++ +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#authselmode,*authselmode*>> 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). + +[[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 +========================================================================= +foo.example.net NTS 1 15 256 135m 0 0 8 100 +bar.example.net SK 30 13 128 - 0 0 0 0 +baz.example.net - 0 0 0 - 0 0 0 0 +---- ++ +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 +*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 foo.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 foo.example.net maxsources 3 iburst +---- + +[[add_server]]*add server* _name_ [_option_]...:: +The *add server* command allows a new NTP server to be added whilst +*chronyd* is running. ++ +Following the words *add server*, the syntax of the following parameters and +options is 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 foo.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 foo.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 foo.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 sources to IP addresses again, e.g. after suspending and resuming +the machine in a different network. ++ +Sources that stop responding will be replaced with newly resolved addresses +automatically after 8 polling intervals, but this command can still be useful +to replace them immediately and not wait until they are marked as unreachable. + +[[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. + +[[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 foo.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 +foo.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 +---- ++ +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. +{blank}:: ++ +Note that the numbers reported by this overflow to zero after 4294967295 +(32-bit values). + +[[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 one computer to be a +master time server with the other computers slaving to it.) ++ +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 foo.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.tuxfamily.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..36b5c81 --- /dev/null +++ b/doc/chronyc.man.in @@ -0,0 +1,2672 @@ +'\" t +.\" Title: chronyc +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-08-29 +.\" Manual: User manual +.\" Source: chrony @CHRONY_VERSION@ +.\" Language: English +.\" +.TH "CHRONYC" "1" "2022-08-29" "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\-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 (foo.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. \fIfoo.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 +^? foo.example.net 2 6 377 23 \-923us[ \-924us] +/\- 43ms +^+ bar.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. The number following the \fI+/\-\fP indicator shows the margin of error in +the measurement. Positive offsets indicate that the local clock is ahead of +the source. +.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 +=============================================================================== +foo.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 foo.example.net Y \-\-\-\-\- \-\-TR\- 4 1.0 \-61ms +62ms N +* bar.example.net N \-\-\-\-\- \-\-\-\-\- 0 1.0 \-6846us +7305us N ++ baz.example.net N \-\-\-\-\- \-\-\-\-\- 10 1.0 \-7381us +7355us N +.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 +\fBauthselmode\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 +\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 +========================================================================= +foo.example.net NTS 1 15 256 135m 0 0 8 100 +bar.example.net SK 30 13 128 \- 0 0 0 0 +baz.example.net \- 0 0 0 \- 0 0 0 0 +.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 +.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 foo.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 foo.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 foo.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 foo.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 foo.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 sources to IP addresses again, e.g. after suspending and resuming +the machine in a different network. +.sp +Sources that stop responding will be replaced with newly resolved addresses +automatically after 8 polling intervals, but this command can still be useful +to replace them immediately and not wait until they are marked as unreachable. +.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. +.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 foo.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 +foo.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 +.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 +.RE +.sp + +.RS 4 +.sp +Note that the numbers reported by this overflow to zero after 4294967295 +(32\-bit values). +.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 one computer to be a +master time server with the other computers slaving to it.) +.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 foo.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.tuxfamily.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..7ba991d --- /dev/null +++ b/doc/chronyd.adoc @@ -0,0 +1,222 @@ +// 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: +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 it allows *chronyd* to be +started without root privileges. + +*-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. + +== 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.tuxfamily.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..090d810 --- /dev/null +++ b/doc/chronyd.man.in @@ -0,0 +1,264 @@ +'\" t +.\" Title: chronyd +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-08-29 +.\" Manual: System Administration +.\" Source: chrony @CHRONY_VERSION@ +.\" Language: English +.\" +.TH "CHRONYD" "8" "2022-08-29" "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: +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 it allows \fBchronyd\fP to be +started without root privileges. +.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 "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.tuxfamily.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..1b299d2 --- /dev/null +++ b/doc/faq.adoc @@ -0,0 +1,1049 @@ +// This file is part of chrony +// +// Copyright (C) Richard P. Curnow 1997-2003 +// Copyright (C) Miroslav Lichvar 2014-2016, 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. + += 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.tuxfamily.org/comparison.html[comparison page] on the `chrony` +website. + +== 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 +`authselmode` directive. + +An example of a client configuration limiting the impact of the attacks could +be + +---- +server foo.example.net iburst nts maxdelay 0.1 +server bar.example.net iburst nts maxdelay 0.2 +server baz.example.net iburst nts maxdelay 0.05 +server qux.example.net iburst nts maxdelay 0.1 +server quux.example.net iburst nts maxdelay 0.1 +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 foo.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 +---- + +As an experimental feature added in 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 +---- + +=== 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 +---- + +=== 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 configuration 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 message-based time source provided by `gpsd` is specified as a `SHM 0` +refclock, or other even number if `gpsd` is configured with multiple receivers. + +The PPS-based time source is specified as a `SHM 1` refclock (or other odd +number), or `SOCK /var/run/chrony.DEV.sock` where `DEV` is the name of the +serial device (e.g. ttyS0). + +With `chronyd` and `gpsd` both supporting PPS, and `gpsd` providing two +different refclocks for PPS, there are three different recommended +configurations: + +---- +# First option +refclock SOCK /var/run/chrony.ttyS0.sock refid GPS + +# Second option +refclock SHM 1 refid GPS + +# Third option +refclock PPS /dev/pps0 lock NMEA refid GPS +refclock SHM 0 offset 0.5 delay 0.1 refid NMEA noselect +---- + +Each option has some advantages: + +* `SOCK` does not use polling (i.e. it can get samples earlier than `SHM`), + but it requires `gpsd` to be started after `chronyd` in order to connect to + its socket +* `SOCK` and `SHM 1` 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 `SHM 0` 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 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. + +=== 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 +packets 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 +=============================================================================== +^* foo.example.net 2 6 377 34 +484us[ -157us] +/- 30ms +^- bar.example.net 2 6 377 34 +33ms[ +32ms] +/- 47ms +^+ baz.example.net 3 6 377 35 -1397us[-2033us] +/- 60ms +---- + +=== 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 +========================================================================= +foo.example.net NTS 1 15 256 33m 0 0 8 100 +bar.example.net NTS 1 15 256 33m 0 0 8 100 +baz.example.net NTS 1 15 256 33m 0 0 8 100 +---- + +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 +foo.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..72b028f --- /dev/null +++ b/examples/chrony-wait.service @@ -0,0 +1,47 @@ +[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 +ProcSubset=pid +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..4e3e3a8 --- /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 foo.example.net iburst +! server bar.example.net iburst +! server baz.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/.../foo.example.net.crt +! ntsserverkey /etc/.../foo.example.net.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@foo.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..65b6be2 --- /dev/null +++ b/examples/chrony.keys.example @@ -0,0 +1,13 @@ +# 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 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..01e6fdb --- /dev/null +++ b/examples/chrony.nm-dispatcher.onoffline @@ -0,0 +1,27 @@ +#!/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) + ;; + dhcp6-change) + # No other action is reported for routable IPv6 + ;; + *) + 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.service b/examples/chronyd.service new file mode 100644 index 0000000..4fb930e --- /dev/null +++ b/examples/chronyd.service @@ -0,0 +1,49 @@ +[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 +ProcSubset=pid +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..5c38119 --- /dev/null +++ b/getdate.c @@ -0,0 +1,2606 @@ +/* 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 (); +extern struct tm *localtime (); +extern time_t mktime (); + +/* 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 (s) + char *s ATTRIBUTE_UNUSED; +{ + return 0; +} + +static int +ToHour (Hours, Meridian) + 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 (Year) + 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 (buff) + 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..f87875c --- /dev/null +++ b/getdate.y @@ -0,0 +1,1044 @@ +%{ +/* +** 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 (); +extern struct tm *localtime (); +extern time_t mktime (); + +/* 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 (s) + char *s ATTRIBUTE_UNUSED; +{ + return 0; +} + +static int +ToHour (Hours, Meridian) + 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 (Year) + 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 (buff) + 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) */ @@ -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 @@ -0,0 +1,436 @@ +/* + 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; + + 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); + + /* 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); +} @@ -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 */ @@ -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); + } +} + +/* ================================================== */ @@ -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..d858d2a --- /dev/null +++ b/logging.c @@ -0,0 +1,352 @@ +/* + 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; + +/* ================================================== */ +/* 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(""); + 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); + + 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) + fprintf(file_log, "%s%s:%d:(%s) ", 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_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..31bd462 --- /dev/null +++ b/logging.h @@ -0,0 +1,129 @@ +/* + 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); + +/* 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 */ @@ -0,0 +1,694 @@ +/* + 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; + + 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) { + exit(0); /* In the 'parent' */ + } 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. */ + for (fd=0; fd<1024; fd++) { + if (fd != pipefd[1]) + 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"); + + /* 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); + + 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; +} + +/* ================================================== */ @@ -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 */ @@ -0,0 +1,322 @@ +/* + *********************************************************************** + ** 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 (mdContext) +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 (mdContext, inBuf, inLen) +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 (mdContext) +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 (buf, in) +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) ******************************** + */ @@ -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..03366e5 --- /dev/null +++ b/memory.c @@ -0,0 +1,93 @@ +/* + 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; + + r = realloc(ptr, size); + if (!r && size) + 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 @@ -0,0 +1,182 @@ +/* + 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_EXP1 0xF323 + +#define NTP_EF_FLAG_EXP1 0x1 + +/* 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_ExtFieldExp1; + +#define NTP_EF_EXP1_MAGIC 0xF5BEDD9AU + +/* 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; + +#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..63c5a3a --- /dev/null +++ b/ntp_core.c @@ -0,0 +1,2999 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2003 + * Copyright (C) Miroslav Lichvar 2009-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. + * + ********************************************************************** + + ======================================================================= + + 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 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; + + 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 + +/* 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, double last_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 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); +} + +/* ================================================== */ + +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; +} + +/* ================================================== */ + +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, last_tx; + struct timespec now; + + 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)) { + SCH_GetLastEventTime(&now, NULL, NULL); + last_tx = UTI_DiffTimespecsToDouble(&now, &inst->local_tx.ts); + if (last_tx < 0.0) + last_tx = 0.0; + delay = get_transmit_delay(inst, 0, 0.0) - last_tx; + } 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; +} + +/* ================================================== */ + +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 = 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->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); + + 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) +{ + 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, 0.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); + } + + /* 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, double last_tx) +{ + int poll_to_use, stratum_diff; + double delay_time; + + /* 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; + + /* Substract the already spend time */ + if (last_tx > 0.0) + delay_time -= last_tx; + if (delay_time < 0.0) + delay_time = 0.0; + + 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; + } + + 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_ext_exp1(NTP_Packet *message, NTP_PacketInfo *info, struct timespec *rx, + double root_delay, double root_dispersion) +{ + struct timespec mono_rx; + NTP_ExtFieldExp1 exp1; + NTP_int64 ts_fuzz; + + memset(&exp1, 0, sizeof (exp1)); + exp1.magic = htonl(NTP_EF_EXP1_MAGIC); + + if (info->mode != MODE_CLIENT) { + exp1.root_delay = UTI_DoubleToNtp32f28(root_delay); + exp1.root_dispersion = UTI_DoubleToNtp32f28(root_dispersion); + if (rx) + UTI_AddDoubleToTimespec(rx, server_mono_offset, &mono_rx); + else + UTI_ZeroTimespec(&mono_rx); + UTI_GetNtp64Fuzz(&ts_fuzz, message->precision); + UTI_TimespecToNtp64(&mono_rx, &exp1.mono_receive_ts, &ts_fuzz); + exp1.mono_epoch = htonl(server_mono_epoch); + } + + if (!NEF_AddField(message, info, NTP_EF_EXP1, &exp1, sizeof (exp1))) { + DEBUG_LOG("Could not add EF"); + return 0; + } + + info->ext_field_flags |= NTP_EF_FLAG_EXP1; + + 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_EXP1) { + if (!add_ext_exp1(&message, &info, smooth_time ? NULL : &local_receive, + our_root_delay, our_root_dispersion)) + 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; + } + + 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; + + 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)) { + if (inst->burst_total_samples_to_go > 0) + inst->burst_total_samples_to_go--; + adjust_poll(inst, 0.25); + SRC_UpdateReachability(inst->source, 0); + restart_timeout(inst, get_transmit_delay(inst, 1, 0.0)); + return; + } + + /* Open new client socket */ + if (inst->mode == MODE_CLIENT) { + 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, 0.0)); + + /* 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 +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_EXP1: + if (ef_body_length == sizeof (NTP_ExtFieldExp1) && + ntohl(((NTP_ExtFieldExp1 *)ef_body)->magic) == NTP_EF_EXP1_MAGIC) + info->ext_field_flags |= NTP_EF_FLAG_EXP1; + 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 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 +process_response(NCR_Instance inst, NTP_Local_Address *local_addr, + NTP_Local_Timestamp *rx_ts, NTP_Packet *message, NTP_PacketInfo *info) +{ + NTP_Sample sample; + SST_Stats stats; + + int pkt_leap, pkt_version; + uint32_t pkt_refid; + 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_ExtFieldExp1 *ef_exp1; + + NTP_Local_Timestamp local_receive, local_transmit; + double remote_interval, local_interval, response_time; + double delay_time, precision, mono_doffset; + int updated_timestamps; + + /* ==================== */ + + stats = SRC_GetSourcestats(inst->source); + + ef_exp1 = 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_EXP1: + if (inst->ext_field_flags & NTP_EF_FLAG_EXP1 && + ef_body_length == sizeof (*ef_exp1) && + ntohl(((NTP_ExtFieldExp1 *)ef_body)->magic) == NTP_EF_EXP1_MAGIC) + ef_exp1 = ef_body; + 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_exp1) { + pkt_root_delay = UTI_Ntp32f28ToDouble(ef_exp1->root_delay); + pkt_root_dispersion = UTI_Ntp32f28ToDouble(ef_exp1->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 */ + test5 = 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; + + /* 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_exp1 && inst->remote_mono_epoch == ntohl(ef_exp1->mono_epoch) && + !UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts) && + !UTI_IsZeroNtp64(&inst->remote_ntp_monorx)) { + mono_doffset = + UTI_DiffNtp64ToDouble(&ef_exp1->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; + } + + /* 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; + 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; + 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; + + /* 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 requires that the minimum estimate of the peer delay is not + larger than the configured maximum, 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->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; + 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_exp1 && !UTI_IsZeroNtp64(&ef_exp1->mono_receive_ts)) { + inst->remote_mono_epoch = ntohl(ef_exp1->mono_epoch); + inst->remote_ntp_monorx = ef_exp1->mono_receive_ts; + inst->mono_doffset += mono_doffset; + } else { + inst->remote_mono_epoch = 0; + UTI_ZeroNtp64(&inst->remote_ntp_monorx); + inst->mono_doffset = 0.0; + } + + /* Don't use the same set of timestamps for the next sample */ + if (interleaved_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. Also, ignore presend responses. */ + if (inst->valid_rx) { + test2 = test3 = 0; + valid_packet = synced_packet = good_packet = 0; + } else if (valid_packet) { + if (inst->presend_done) { + testA = 0; + good_packet = 0; + } + inst->valid_rx = 1; + } + + 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, + UTI_DiffTimespecsToDouble(&inst->local_rx.ts, &inst->local_tx.ts)); + + 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 */ + 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 master, with the + other machines pointed at it. If the master 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, 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; + } else if (info.auth.mode != NTP_AUTH_NONE && info.auth.mode != NTP_AUTH_MSSNTP) { + CLG_LogAuthNtpRequest(); + } + + 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; + UTI_ZeroTimespec(&local_tx.ts); + interleaved = CLG_GetNtpTxTimestamp(&ntp_rx, &local_tx.ts); + + tx_ts = &local_tx; + if (interleaved) + CLG_DisableNtpTimestamps(&ntp_rx); + } + + /* 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 ? &tx_ts->ts : NULL); +} + +/* ================================================== */ + +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); +} + +/* ================================================== */ + +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)) + 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); +} + +/* ================================================== */ + +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); +} + +/* ================================================== */ + +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 = 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; + + /* 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..33f2bb8 --- /dev/null +++ b/ntp_core.h @@ -0,0 +1,144 @@ +/* + 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 enum { + NTP_TS_DAEMON = 0, + NTP_TS_KERNEL, + NTP_TS_HARDWARE +} NTP_Timestamp_Source; + +typedef struct { + struct timespec ts; + double err; + NTP_Timestamp_Source source; +} 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..86e3f26 --- /dev/null +++ b/ntp_io.c @@ -0,0 +1,610 @@ +/* + 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 (!SCK_SetIntOption(sock_fd, IPPROTO_IP, IP_TOS, 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; + +#ifdef HAVE_LINUX_TIMESTAMPING + NIO_Linux_NotifySocketClosing(sock_fd); +#endif + 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_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; + 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)) + 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; + +#ifdef HAVE_LINUX_TIMESTAMPING + if (NIO_Linux_ProcessEvent(sock_fd, event)) + return; +#endif + + 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) +{ + 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; + + DEBUG_LOG("Unwrapped PTP->NTP len=%d", message->length); + + return 1; +} + +/* ================================================== */ + +static int +wrap_message(SCK_Message *message, int sock_fd) +{ + 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->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..19ffeed --- /dev/null +++ b/ntp_io.h @@ -0,0 +1,70 @@ +/* + 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 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); + +/* 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..004a4d4 --- /dev/null +++ b/ntp_io_linux.c @@ -0,0 +1,847 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2016-2019, 2021-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. + * + ********************************************************************** + + ======================================================================= + + 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 "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; +}; + +/* Number of PHC readings per HW clock sample */ +#define PHC_READINGS 25 + +/* Minimum interval between PHC readings */ +#define MIN_PHC_POLL -6 + +/* 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; + +/* When sending client requests to a close and fast server, it is possible that + a response will be received before the HW transmit timestamp of the request + itself. To avoid processing of the response without the HW timestamp, we + monitor events returned by select() and suspend reading of packets from the + receive queue for up to 200 microseconds. As the requests are normally + separated by at least 200 milliseconds, it is sufficient to monitor and + suspend one socket at a time. */ +static int monitored_socket; +static int suspended_socket; +static SCH_TimeoutID resume_timeout_id; + +#define RESUME_TIMEOUT 200.0e-6 + +/* 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 int +add_interface(CNF_HwTsInterface *conf_iface) +{ + struct ethtool_ts_info ts_info; + struct hwtstamp_config ts_config; + struct ifreq req; + int sock_fd, if_index, phc_fd, req_hwts_flags, rx_filter; + 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; + + iface->clock = HCL_CreateInstance(conf_iface->min_samples, conf_iface->max_samples, + UTI_Log2ToDouble(MAX(conf_iface->minpoll, MIN_PHC_POLL)), + conf_iface->precision); + + 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); + + monitored_socket = INVALID_SOCK_FD; + suspended_socket = INVALID_SOCK_FD; + 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); + HCL_DestroyInstance(iface->clock); + close(iface->phc_fd); + } + + ARR_DestroyInstance(interfaces); +} + +/* ================================================== */ + +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 void +resume_socket(int sock_fd) +{ + if (monitored_socket == sock_fd) + monitored_socket = INVALID_SOCK_FD; + + if (sock_fd == INVALID_SOCK_FD || sock_fd != suspended_socket) + return; + + suspended_socket = INVALID_SOCK_FD; + + SCH_SetFileHandlerEvent(sock_fd, SCH_FILE_INPUT, 1); + + DEBUG_LOG("Resumed RX processing %s timeout fd=%d", + resume_timeout_id ? "before" : "on", sock_fd); + + if (resume_timeout_id) { + SCH_RemoveTimeout(resume_timeout_id); + resume_timeout_id = 0; + } +} + +/* ================================================== */ + +static void +resume_timeout(void *arg) +{ + resume_timeout_id = 0; + resume_socket(suspended_socket); +} + +/* ================================================== */ + +static void +suspend_socket(int sock_fd) +{ + resume_socket(suspended_socket); + + suspended_socket = sock_fd; + + SCH_SetFileHandlerEvent(suspended_socket, SCH_FILE_INPUT, 0); + resume_timeout_id = SCH_AddTimeoutByDelay(RESUME_TIMEOUT, resume_timeout, NULL); + + DEBUG_LOG("Suspended RX processing fd=%d", sock_fd); +} + +/* ================================================== */ + +int +NIO_Linux_ProcessEvent(int sock_fd, int event) +{ + if (sock_fd != monitored_socket) + return 0; + + if (event == SCH_FILE_INPUT) { + suspend_socket(monitored_socket); + monitored_socket = INVALID_SOCK_FD; + + /* Don't process the message yet */ + return 1; + } + + return 0; +} + +/* ================================================== */ + +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 +process_hw_timestamp(struct Interface *iface, struct timespec *hw_ts, + NTP_Local_Timestamp *local_ts, int rx_ntp_length, int family, + int l2_length) +{ + struct timespec sample_phc_ts, sample_sys_ts, sample_local_ts, ts; + struct timespec phc_readings[PHC_READINGS][3]; + double rx_correction, ts_delay, phc_err, local_err; + int n_readings; + + if (HCL_NeedsNewSample(iface->clock, &local_ts->ts)) { + n_readings = SYS_Linux_GetPHCReadings(iface->phc_fd, iface->phc_nocrossts, + &iface->phc_mode, PHC_READINGS, phc_readings); + if (n_readings > 0 && + HCL_ProcessReadings(iface->clock, n_readings, phc_readings, + &sample_phc_ts, &sample_sys_ts, &phc_err)) { + 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); + } + } + + /* 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; +} + +/* ================================================== */ + +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; + + 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 a HW transmit timestamp was received, resume processing + of non-error messages on this socket */ + if (is_tx) + resume_socket(local_addr->sock_fd); + } + + if (local_ts->source == NTP_TS_DAEMON && !UTI_IsZeroTimespec(&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)) + 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; + + /* If a HW transmit timestamp is requested on a client socket, monitor + events on the socket in order to avoid processing of a fast response + without the HW timestamp of the request */ + if (ts_tx_flags & SOF_TIMESTAMPING_TX_HARDWARE && !NIO_IsServerSocket(sock_fd)) + monitored_socket = sock_fd; + + /* 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; +} + +/* ================================================== */ + +void +NIO_Linux_NotifySocketClosing(int sock_fd) +{ + resume_socket(sock_fd); +} diff --git a/ntp_io_linux.h b/ntp_io_linux.h new file mode 100644 index 0000000..4d3af13 --- /dev/null +++ b/ntp_io_linux.h @@ -0,0 +1,47 @@ +/* + 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_SetTimestampSocketOptions(int sock_fd, int client_only, int *events); + +extern int NIO_Linux_ProcessEvent(int sock_fd, int event); + +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); + +extern void NIO_Linux_NotifySocketClosing(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..d46c211 --- /dev/null +++ b/ntp_sources.c @@ -0,0 +1,1433 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2003 + * Copyright (C) Miroslav Lichvar 2011-2012, 2014, 2016, 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. + * + ********************************************************************** + + ======================================================================= + + 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 "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) */ + 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 */ +} 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; + /* 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 MIN_REPLACEMENT_INTERVAL 8 + +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; + 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); + + 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); +} + +/* ================================================== */ + +/* 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->pool_id = pool_id; + record->tentative = 1; + record->conf_id = conf_id; + + 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); + + /* 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 (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; + + 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; + + 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; +} + +/* ================================================== */ + +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; + + 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; + 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) +{ + struct UnresolvedSource *us; + + DEBUG_LOG("trying to replace %s (%s)", + UTI_IPToString(&record->remote_addr->ip_addr), record->name); + + us = MallocNew(struct UnresolvedSource); + us->name = Strdup(record->name); + /* If there never was a valid reply from this source (e.g. it was a bad + replacement), ignore the order of addresses from the resolver to not get + stuck to a pair of addresses if the order doesn't change, or a group of + IPv4/IPv6 addresses if the resolver prefers inaccessible IP family */ + us->random_order = record->tentative; + us->pool_id = INVALID_POOL; + us->address = *record->remote_addr; + + append_unresolved_source(us); + NSR_ResolveSources(); +} + +/* ================================================== */ + +void +NSR_HandleBadSource(IPAddr *address) +{ + static struct timespec last_replacement; + struct timespec now; + SourceRecord *record; + IPAddr ip_addr; + double diff; + 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 */ + SCH_GetLastEventTime(NULL, NULL, &now); + diff = UTI_DiffTimespecsToDouble(&now, &last_replacement); + if (fabs(diff) < RESOLVE_INTERVAL_UNIT * (1 << MIN_REPLACEMENT_INTERVAL)) { + DEBUG_LOG("replacement postponed"); + return; + } + last_replacement = now; + + resolve_source_replacement(record); +} + +/* ================================================== */ + +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); + } +} + +/* ================================================== */ + +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); + } + } + } 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..ba3b9c4 --- /dev/null +++ b/ntp_sources.h @@ -0,0 +1,152 @@ +/* + 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); + +/* 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..c22b0eb --- /dev/null +++ b/nts_ke_client.c @@ -0,0 +1,442 @@ +/* + 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 datum; + + NKSN_BeginMessage(session); + + datum = htons(NKE_NEXT_PROTOCOL_NTPV4); + if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, &datum, sizeof (datum))) + return 0; + + datum = htons(AEAD_AES_SIV_CMAC_256); + if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, &datum, sizeof (datum))) + return 0; + + if (!NKSN_EndMessage(session)) + return 0; + + return 1; +} + +/* ================================================== */ + +static int +process_response(NKC_Instance inst) +{ + int next_protocol = -1, aead_algorithm = -1, error = 0; + int i, critical, type, length; + uint16_t data[NKE_MAX_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) { + DEBUG_LOG("Unexpected NTS-KE AEAD algorithm"); + error = 1; + break; + } + aead_algorithm = AEAD_AES_SIV_CMAC_256; + inst->context.algorithm = aead_algorithm; + break; + case NKE_RECORD_ERROR: + if (length == 2) + DEBUG_LOG("NTS-KE error %d", ntohs(data[0])); + error = 1; + break; + case NKE_RECORD_WARNING: + if (length == 2) + DEBUG_LOG("NTS-KE warning %d", ntohs(data[0])); + error = 1; + break; + case NKE_RECORD_COOKIE: + DEBUG_LOG("Got cookie 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 != AEAD_AES_SIV_CMAC_256) + return 0; + + return 1; +} + +/* ================================================== */ + +static int +handle_message(void *arg) +{ + NKC_Instance inst = arg; + + if (!process_response(inst)) { + LOG(LOGS_ERR, "Received invalid NTS-KE response from %s", inst->name); + return 0; + } + + if (!NKSN_GetKeys(inst->session, inst->context.algorithm, + &inst->context.c2s, &inst->context.s2c)) + return 0; + + if (inst->server_name[0] != '\0') { + if (inst->resolving_name) + return 0; + if (!UTI_StringToIP(inst->server_name, &inst->ntp_address.ip_addr)) { + 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; + } + + /* 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..ece1b4c --- /dev/null +++ b/nts_ke_server.c @@ -0,0 +1,967 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + + ======================================================================= + + NTS-KE server + */ + +#include "config.h" + +#include "sysincl.h" + +#include "nts_ke_server.h" + +#include "array.h" +#include "conf.h" +#include "clientlog.h" +#include "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 SERVER_COOKIE_SIV AEAD_AES_SIV_CMAC_256 +#define SERVER_COOKIE_NONCE_LENGTH 16 + +#define KEY_ID_INDEX_BITS 2 +#define MAX_SERVER_KEYS (1U << KEY_ID_INDEX_BITS) +#define FUTURE_KEYS 1 + +#define DUMP_FILENAME "ntskeys" +#define DUMP_IDENTIFIER "NKS0\n" + +#define INVALID_SOCK_FD (-7) + +typedef struct { + uint32_t key_id; + unsigned char nonce[SERVER_COOKIE_NONCE_LENGTH]; +} ServerCookieHeader; + +typedef struct { + uint32_t id; + unsigned char key[SIV_MAX_KEY_LENGTH]; + SIV_Instance siv; +} ServerKey; + +typedef struct { + uint32_t key_id; + unsigned char key[SIV_MAX_KEY_LENGTH]; + IPAddr client_addr; + uint16_t client_port; + uint16_t _pad; +} HelperRequest; + +/* ================================================== */ + +static ServerKey server_keys[MAX_SERVER_KEYS]; +static int current_server_key; +static double last_server_key_ts; +static int key_rotation_interval; + +static int server_sock_fd4; +static int server_sock_fd6; + +static int helper_sock_fd; +static int 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 +handle_helper_request(int fd, int event, void *arg) +{ + SCK_Message *message; + HelperRequest *req; + IPSockAddr client_addr; + 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 */ + server_keys[current_server_key].id = ntohl(req->key_id); + assert(sizeof (server_keys[current_server_key].key) == sizeof (req->key)); + memcpy(server_keys[current_server_key].key, req->key, + sizeof (server_keys[current_server_key].key)); + UTI_IPNetworkToHost(&req->client_addr, &client_addr.ip_addr); + client_addr.port = ntohs(req->client_port); + + if (!SIV_SetKey(server_keys[current_server_key].siv, server_keys[current_server_key].key, + SIV_GetKeyLength(SERVER_COOKIE_SIV))) + LOG_FATAL("Could not set SIV key"); + + 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); + 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++; + if (ntohs(data[i]) == AEAD_AES_SIV_CMAC_256) + aead_algorithm = AEAD_AES_SIV_CMAC_256; + } + break; + case NKE_RECORD_ERROR: + case NKE_RECORD_WARNING: + case NKE_RECORD_COOKIE: + error = NKE_ERROR_BAD_REQUEST; + break; + default: + if (critical) + error = NKE_ERROR_UNRECOGNIZED_CRITICAL_RECORD; + } + } + + if (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) +{ + int key_length; + + if (index < 0 || index >= MAX_SERVER_KEYS) + assert(0); + + key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV); + if (key_length > sizeof (server_keys[index].key)) + assert(0); + + UTI_GetRandomBytesUrandom(server_keys[index].key, key_length); + + if (!server_keys[index].siv || + !SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length)) + LOG_FATAL("Could not set SIV key"); + + UTI_GetRandomBytes(&server_keys[index].id, sizeof (server_keys[index].id)); + + /* Encode the index in the lowest bits of the ID */ + server_keys[index].id &= -1U << KEY_ID_INDEX_BITS; + server_keys[index].id |= index; + + DEBUG_LOG("Generated server key %"PRIX32, server_keys[index].id); + + last_server_key_ts = SCH_GetLastEventMonoTime(); +} + +/* ================================================== */ + +static void +save_keys(void) +{ + char buf[SIV_MAX_KEY_LENGTH * 2 + 1], *dump_dir; + int i, index, key_length; + double last_key_age; + FILE *f; + + /* Don't save the keys if rotation is disabled to enable an external + management of the keys (e.g. share them with another server) */ + if (key_rotation_interval == 0) + return; + + dump_dir = CNF_GetNtsDumpDir(); + if (!dump_dir) + return; + + f = UTI_OpenFile(dump_dir, DUMP_FILENAME, ".tmp", 'w', 0600); + if (!f) + return; + + key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV); + last_key_age = SCH_GetLastEventMonoTime() - last_server_key_ts; + + if (fprintf(f, "%s%d %.1f\n", DUMP_IDENTIFIER, SERVER_COOKIE_SIV, last_key_age) < 0) + goto error; + + for (i = 0; i < MAX_SERVER_KEYS; i++) { + index = (current_server_key + i + 1 + FUTURE_KEYS) % MAX_SERVER_KEYS; + + if (key_length > sizeof (server_keys[index].key) || + !UTI_BytesToHex(server_keys[index].key, key_length, buf, sizeof (buf)) || + fprintf(f, "%08"PRIX32" %s\n", server_keys[index].id, buf) < 0) + goto error; + } + + fclose(f); + + /* 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: + DEBUG_LOG("Could not %s server keys", "save"); + fclose(f); + + if (!UTI_RemoveFile(dump_dir, DUMP_FILENAME, NULL)) + ; +} + +/* ================================================== */ + +#define MAX_WORDS 2 + +static int +load_keys(void) +{ + char *dump_dir, line[1024], *words[MAX_WORDS]; + unsigned char key[SIV_MAX_KEY_LENGTH]; + int i, index, key_length, algorithm; + double key_age; + FILE *f; + uint32_t id; + + 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 || + !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 2 || + sscanf(words[0], "%d", &algorithm) != 1 || algorithm != SERVER_COOKIE_SIV || + sscanf(words[1], "%lf", &key_age) != 1) + goto error; + + key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV); + last_server_key_ts = SCH_GetLastEventMonoTime() - MAX(key_age, 0.0); + + for (i = 0; i < MAX_SERVER_KEYS && fgets(line, sizeof (line), f); i++) { + if (UTI_SplitString(line, words, MAX_WORDS) != 2 || + sscanf(words[0], "%"PRIX32, &id) != 1) + goto error; + + if (UTI_HexToBytes(words[1], key, sizeof (key)) != key_length) + goto error; + + index = id % MAX_SERVER_KEYS; + + server_keys[index].id = id; + assert(sizeof (server_keys[index].key) == sizeof (key)); + memcpy(server_keys[index].key, key, key_length); + + if (!SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length)) + LOG_FATAL("Could not set SIV key"); + + DEBUG_LOG("Loaded key %"PRIX32, id); + + current_server_key = (index + MAX_SERVER_KEYS - FUTURE_KEYS) % MAX_SERVER_KEYS; + } + + fclose(f); + + return 1; + +error: + DEBUG_LOG("Could not %s server keys", "load"); + 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"); + + /* 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 = SIV_CreateInstance(SERVER_COOKIE_SIV); + 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); + } + } + + 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 plaintext[2 * NKE_MAX_KEY_LENGTH], *ciphertext; + int plaintext_length, tag_length; + ServerCookieHeader *header; + ServerKey *key; + + if (!initialised) { + DEBUG_LOG("NTS server disabled"); + return 0; + } + + /* The algorithm is hardcoded for now */ + if (context->algorithm != AEAD_AES_SIV_CMAC_256) { + DEBUG_LOG("Unexpected SIV algorithm"); + return 0; + } + + if (context->c2s.length < 0 || context->c2s.length > NKE_MAX_KEY_LENGTH || + context->s2c.length < 0 || context->s2c.length > NKE_MAX_KEY_LENGTH) { + DEBUG_LOG("Invalid key length"); + return 0; + } + + key = &server_keys[current_server_key]; + + header = (ServerCookieHeader *)cookie->cookie; + + header->key_id = htonl(key->id); + UTI_GetRandomBytes(header->nonce, sizeof (header->nonce)); + + plaintext_length = context->c2s.length + context->s2c.length; + assert(plaintext_length <= sizeof (plaintext)); + memcpy(plaintext, context->c2s.key, context->c2s.length); + memcpy(plaintext + context->c2s.length, context->s2c.key, context->s2c.length); + + tag_length = SIV_GetTagLength(key->siv); + cookie->length = sizeof (*header) + plaintext_length + tag_length; + assert(cookie->length <= sizeof (cookie->cookie)); + ciphertext = cookie->cookie + sizeof (*header); + + if (!SIV_Encrypt(key->siv, header->nonce, sizeof (header->nonce), + "", 0, + plaintext, plaintext_length, + ciphertext, plaintext_length + tag_length)) { + DEBUG_LOG("Could not encrypt cookie"); + return 0; + } + + return 1; +} + +/* ================================================== */ + +int +NKS_DecodeCookie(NKE_Cookie *cookie, NKE_Context *context) +{ + unsigned char plaintext[2 * NKE_MAX_KEY_LENGTH], *ciphertext; + int ciphertext_length, plaintext_length, tag_length; + ServerCookieHeader *header; + ServerKey *key; + uint32_t key_id; + + if (!initialised) { + DEBUG_LOG("NTS server disabled"); + return 0; + } + + if (cookie->length <= (int)sizeof (*header)) { + DEBUG_LOG("Invalid cookie length"); + return 0; + } + + header = (ServerCookieHeader *)cookie->cookie; + ciphertext = cookie->cookie + sizeof (*header); + ciphertext_length = cookie->length - sizeof (*header); + + key_id = ntohl(header->key_id); + key = &server_keys[key_id % MAX_SERVER_KEYS]; + if (key_id != key->id) { + DEBUG_LOG("Unknown key %"PRIX32, key_id); + return 0; + } + + tag_length = SIV_GetTagLength(key->siv); + if (tag_length >= ciphertext_length) { + DEBUG_LOG("Invalid cookie length"); + return 0; + } + + plaintext_length = ciphertext_length - tag_length; + if (plaintext_length > sizeof (plaintext) || plaintext_length % 2 != 0) { + DEBUG_LOG("Invalid cookie length"); + return 0; + } + + if (!SIV_Decrypt(key->siv, header->nonce, sizeof (header->nonce), + "", 0, + ciphertext, ciphertext_length, + plaintext, plaintext_length)) { + DEBUG_LOG("Could not decrypt cookie"); + return 0; + } + + context->algorithm = AEAD_AES_SIV_CMAC_256; + + context->c2s.length = plaintext_length / 2; + context->s2c.length = plaintext_length / 2; + assert(context->c2s.length <= sizeof (context->c2s.key)); + + memcpy(context->c2s.key, plaintext, context->c2s.length); + memcpy(context->s2c.key, plaintext + context->c2s.length, context->s2c.length); + + return 1; +} 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..dfcd18a --- /dev/null +++ b/nts_ke_session.c @@ -0,0 +1,927 @@ +/* + 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++) { + 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..ac0763e --- /dev/null +++ b/nts_ntp_auth.c @@ -0,0 +1,183 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + + ======================================================================= + + NTS Authenticator and Encrypted Extension Fields extension field + */ + +#include "config.h" + +#include "sysincl.h" + +#include "nts_ntp_auth.h" + +#include "logging.h" +#include "ntp_ext.h" +#include "nts_ntp.h" +#include "siv.h" +#include "util.h" + +struct AuthHeader { + uint16_t nonce_length; + uint16_t ciphertext_length; +}; + +/* ================================================== */ + +static int +get_padding_length(int length) +{ + return length % 4U ? 4 - length % 4U : 0; +} + +/* ================================================== */ + +static int +get_padded_length(int length) +{ + return length + get_padding_length(length); +} + +/* ================================================== */ + +int +NNA_GenerateAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv, + const unsigned char *nonce, int nonce_length, + const unsigned char *plaintext, int plaintext_length, + int min_ef_length) +{ + int auth_length, ciphertext_length, assoc_length; + int nonce_padding, ciphertext_padding, additional_padding; + unsigned char *ciphertext, *body; + struct AuthHeader *header; + + assert(sizeof (*header) == 4); + + if (nonce_length <= 0 || plaintext_length < 0) { + DEBUG_LOG("Invalid nonce/plaintext length"); + return 0; + } + + assoc_length = info->length; + ciphertext_length = SIV_GetTagLength(siv) + plaintext_length; + nonce_padding = get_padding_length(nonce_length); + ciphertext_padding = get_padding_length(ciphertext_length); + min_ef_length = get_padded_length(min_ef_length); + + auth_length = sizeof (*header) + nonce_length + nonce_padding + + ciphertext_length + ciphertext_padding; + additional_padding = MAX(min_ef_length - auth_length - 4, 0); + additional_padding = MAX(NTS_MIN_UNPADDED_NONCE_LENGTH - nonce_length - nonce_padding, + additional_padding); + auth_length += additional_padding; + + if (!NEF_AddBlankField(packet, info, NTP_EF_NTS_AUTH_AND_EEF, auth_length, + (void **)&header)) { + DEBUG_LOG("Could not add EF"); + return 0; + } + + header->nonce_length = htons(nonce_length); + header->ciphertext_length = htons(ciphertext_length); + + body = (unsigned char *)(header + 1); + ciphertext = body + nonce_length + nonce_padding; + + 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; + return 0; + } + + memset(ciphertext + ciphertext_length, 0, ciphertext_padding + additional_padding); + + return 1; +} + +/* ================================================== */ + +int +NNA_DecryptAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv, int ef_start, + unsigned char *plaintext, int buffer_length, int *plaintext_length) +{ + unsigned int siv_tag_length, nonce_length, ciphertext_length; + unsigned char *nonce, *ciphertext; + int ef_type, ef_body_length; + void *ef_body; + struct AuthHeader *header; + + if (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); + + siv_tag_length = SIV_GetTagLength(siv); + + if (nonce_length < 1 || + ciphertext_length < siv_tag_length || + ciphertext_length - siv_tag_length > buffer_length) { + DEBUG_LOG("Unexpected nonce/ciphertext length"); + return 0; + } + + if (ef_body_length < sizeof (*header) + + NTS_MIN_UNPADDED_NONCE_LENGTH + get_padded_length(ciphertext_length)) { + DEBUG_LOG("Missing padding"); + return 0; + } + + *plaintext_length = ciphertext_length - siv_tag_length; + 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..856beb3 --- /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 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..34412a6 --- /dev/null +++ b/nts_ntp_client.c @@ -0,0 +1,709 @@ +/* + 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) + +/* 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, double now) +{ + int factor, interval; + + if (!inst->nke) + return; + + factor = NKC_GetRetryFactor(inst->nke); + interval = MIN(factor + inst->nke_attempts - 1, NKE_MAX_RETRY_INTERVAL2); + inst->next_nke_attempt = now + UTI_Log2ToDouble(interval); +} + +/* ================================================== */ + +static int +get_cookies(NNC_Instance inst) +{ + NTP_Remote_Address ntp_address; + double now; + int got_data; + + assert(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++; + update_next_nke_attempt(inst, now); + + if (!NKC_Start(inst->nke)) + return 0; + } + + update_next_nke_attempt(inst, 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.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..9226c89 --- /dev/null +++ b/nts_ntp_server.c @@ -0,0 +1,283 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + + ======================================================================= + + Server NTS-NTP authentication + */ + +#include "config.h" + +#include "sysincl.h" + +#include "nts_ntp_server.h" + +#include "conf.h" +#include "logging.h" +#include "memory.h" +#include "ntp.h" +#include "ntp_ext.h" +#include "nts_ke_server.h" +#include "nts_ntp.h" +#include "nts_ntp_auth.h" +#include "siv.h" +#include "util.h" + +#define SERVER_SIV AEAD_AES_SIV_CMAC_256 + +struct NtsServer { + SIV_Instance siv; + unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH]; + NKE_Cookie cookies[NTS_MAX_COOKIES]; + int num_cookies; + NTP_int64 req_tx; +}; + +/* The server instance handling all requests */ +struct NtsServer *server; + +/* ================================================== */ + +void +NNS_Initialise(void) +{ + const char **certs, **keys; + + /* 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 = SIV_CreateInstance(SERVER_SIV); + if (!server->siv) + LOG_FATAL("Could not initialise SIV cipher"); +} + +/* ================================================== */ + +void +NNS_Finalise(void) +{ + if (!server) + return; + + SIV_DestroyInstance(server->siv); + Free(server); + server = NULL; +} + +/* ================================================== */ + +int +NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod) +{ + int ef_type, ef_body_length, ef_length, has_uniq_id = 0, has_auth = 0, has_cookie = 0; + int i, plaintext_length, parsed, requested_cookies, cookie_length = -1, auth_start = 0; + unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH]; + NKE_Context context; + NKE_Cookie cookie; + void *ef_body; + + *kod = 0; + + if (!server) + return 0; + + server->num_cookies = 0; + server->req_tx = packet->transmit_ts; + + if (info->ext_fields == 0 || info->mode != MODE_CLIENT) + return 0; + + requested_cookies = 0; + + for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) { + if (!NEF_ParseField(packet, info->length, parsed, + &ef_length, &ef_type, &ef_body, &ef_body_length)) + /* 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; + } + + if (context.algorithm != SERVER_SIV) { + DEBUG_LOG("Unexpected SIV"); + return 0; + } + + if (!SIV_SetKey(server->siv, context.c2s.key, context.c2s.length)) { + DEBUG_LOG("Could not set C2S key"); + return 0; + } + + if (!NNA_DecryptAuthEF(packet, info, server->siv, auth_start, + plaintext, sizeof (plaintext), &plaintext_length)) { + *kod = NTP_KOD_NTS_NAK; + return 0; + } + + for (parsed = 0; parsed < plaintext_length; parsed += ef_length) { + if (!NEF_ParseSingleField(plaintext, plaintext_length, parsed, + &ef_length, &ef_type, &ef_body, &ef_body_length)) { + 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(server->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; + + /* 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->siv, + 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..642e477 --- /dev/null +++ b/pktlength.c @@ -0,0 +1,220 @@ +/* + 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 */ +}; + +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 */ + RPY_LENGTH_ENTRY(server_stats), /* SERVER_STATS3 */ +}; + +/* ================================================== */ + +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 @@ -0,0 +1,64 @@ +/* + 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 rest[26]; +} 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..e0e206e --- /dev/null +++ b/refclock_phc.c @@ -0,0 +1,184 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2013, 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. + * + ********************************************************************** + + ======================================================================= + + PTP hardware clock (PHC) refclock driver. + + */ + +#include "config.h" + +#include "refclock.h" + +#ifdef FEAT_PHC + +#include "sysincl.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 mode; + int nocrossts; + int extpps; + int pin; + int channel; + HCL_Instance clock; +}; + +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; + 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; + phc->mode = 0; + phc->nocrossts = RCL_GetDriverOption(instance, "nocrossts") ? 1 : 0; + phc->extpps = RCL_GetDriverOption(instance, "extpps") ? 1 : 0; + + 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); + } else { + phc->pin = phc->channel = 0; + } + + RCL_SetDriverData(instance, phc); + return 1; +} + +static void phc_finalise(RCL_Instance instance) +{ + struct phc_instance *phc; + + 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); + } + + HCL_DestroyInstance(phc->clock); + close(phc->fd); + Free(phc); +} + +static void read_ext_pulse(int fd, int event, void *anything) +{ + RCL_Instance instance; + struct phc_instance *phc; + struct timespec phc_ts, local_ts; + double local_err; + int channel; + + instance = anything; + phc = RCL_GetDriverData(instance); + + if (!SYS_Linux_ReadPHCExtTimestamp(phc->fd, &phc_ts, &channel)) + return; + + if (channel != phc->channel) { + DEBUG_LOG("Unexpected extts channel %d\n", channel); + return; + } + + 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)); +} + +#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, ¶ms) < 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, ¶ms) < 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..f0d91c5 --- /dev/null +++ b/refclock_sock.c @@ -0,0 +1,136 @@ +/* + 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; +}; + +static void read_sample(int sockfd, int event, void *anything) +{ + struct timespec sys_ts, ref_ts; + struct sock_sample sample; + RCL_Instance instance; + int s; + + instance = (RCL_Instance)anything; + + s = recv(sockfd, &sample, sizeof (sample), 0); + + if (s < 0) { + DEBUG_LOG("Could not read SOCK sample : %s", strerror(errno)); + return; + } + + if (s != sizeof (sample)) { + 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..e6cc547 --- /dev/null +++ b/reference.c @@ -0,0 +1,1437 @@ +/* + 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; +} + +/* ================================================== */ + +void +REF_ModifyMakestep(int limit, double threshold) +{ + make_step_limit = limit; + make_step_threshold = threshold; +} + +/* ================================================== */ + +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; +} + +/* ================================================== */ + +void +REF_DisableLocal(void) +{ + enable_local_stratum = 0; +} + +/* ================================================== */ + +#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..6674dd9 --- /dev/null +++ b/reports.h @@ -0,0 +1,206 @@ +/* + 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 { + uint32_t ntp_hits; + uint32_t nke_hits; + uint32_t cmd_hits; + uint32_t ntp_drops; + uint32_t nke_drops; + uint32_t cmd_drops; + uint32_t log_drops; + uint32_t ntp_auth_hits; + uint32_t ntp_interleaved_hits; + uint32_t ntp_timestamps; + uint32_t ntp_span_seconds; +} 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 */ @@ -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; + } +} + +/* ================================================== */ + @@ -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 @@ -0,0 +1,852 @@ +/* + 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 = NULL; + +/* 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; + + 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) { + ARR_DestroyInstance(file_handlers); + + 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]); + } + + 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; +} + +/* ================================================== */ + @@ -0,0 +1,92 @@ +/* + 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_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 */ @@ -0,0 +1,70 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2019 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ********************************************************************** + + ======================================================================= + + Header file for Synthetic Initialization Vector (SIV) ciphers. + + */ + +#ifndef GOT_SIV_H +#define GOT_SIV_H + +/* Maximum key length of all supported SIVs */ +#define SIV_MAX_KEY_LENGTH 32 + +/* Maximum difference between lengths of ciphertext and plaintext */ +#define SIV_MAX_TAG_LENGTH 16 + +/* Identifiers of SIV algorithms following the IANA AEAD registry */ +typedef enum { + AEAD_AES_SIV_CMAC_256 = 15, + AEAD_AES_SIV_CMAC_384 = 16, + AEAD_AES_SIV_CMAC_512 = 17, + AEAD_AES_128_GCM_SIV = 30, + AEAD_AES_256_GCM_SIV = 31, +} SIV_Algorithm; + +typedef struct SIV_Instance_Record *SIV_Instance; + +extern SIV_Instance SIV_CreateInstance(SIV_Algorithm algorithm); + +extern void SIV_DestroyInstance(SIV_Instance instance); + +extern int SIV_GetKeyLength(SIV_Algorithm algorithm); + +extern int SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length); + +extern int SIV_GetTagLength(SIV_Instance instance); + +extern int SIV_Encrypt(SIV_Instance instance, + const unsigned char *nonce, int nonce_length, + const void *assoc, int assoc_length, + const void *plaintext, int plaintext_length, + unsigned char *ciphertext, int ciphertext_length); + +extern int SIV_Decrypt(SIV_Instance instance, + const unsigned char *nonce, int nonce_length, + const void *assoc, int assoc_length, + const unsigned char *ciphertext, int ciphertext_length, + void *plaintext, int plaintext_length); + +#endif diff --git a/siv_gnutls.c b/siv_gnutls.c new file mode 100644 index 0000000..aba2bab --- /dev/null +++ b/siv_gnutls.c @@ -0,0 +1,271 @@ +/* + 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. + * + ********************************************************************** + + ======================================================================= + + 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; +}; + +/* ================================================== */ + +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; + 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; + + 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 < 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_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 < 1 || 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 < 1 || 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..d8f8b23 --- /dev/null +++ b/siv_nettle.c @@ -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. + * + ********************************************************************** + + ======================================================================= + + SIV ciphers using the Nettle library + */ + +#include "config.h" + +#include "sysincl.h" + +#ifdef HAVE_NETTLE_SIV_CMAC +#include <nettle/siv-cmac.h> +#else +#include "siv_nettle_int.c" +#endif + +#include "memory.h" +#include "siv.h" + +struct SIV_Instance_Record { + struct siv_cmac_aes128_ctx siv; + int key_set; +}; + +/* ================================================== */ + +SIV_Instance +SIV_CreateInstance(SIV_Algorithm algorithm) +{ + SIV_Instance instance; + + if (algorithm != AEAD_AES_SIV_CMAC_256) + return NULL; + + instance = MallocNew(struct SIV_Instance_Record); + instance->key_set = 0; + + return instance; +} + +/* ================================================== */ + +void +SIV_DestroyInstance(SIV_Instance instance) +{ + Free(instance); +} + +/* ================================================== */ + +int +SIV_GetKeyLength(SIV_Algorithm algorithm) +{ + assert(32 <= SIV_MAX_KEY_LENGTH); + + if (algorithm == AEAD_AES_SIV_CMAC_256) + return 32; + return 0; +} + +/* ================================================== */ + +int +SIV_SetKey(SIV_Instance instance, const unsigned char *key, int length) +{ + if (length != 32) + return 0; + + siv_cmac_aes128_set_key(&instance->siv, key); + + instance->key_set = 1; + + return 1; +} + +/* ================================================== */ + +int +SIV_GetTagLength(SIV_Instance instance) +{ + assert(SIV_DIGEST_SIZE <= SIV_MAX_TAG_LENGTH); + + return SIV_DIGEST_SIZE; +} + +/* ================================================== */ + +int +SIV_Encrypt(SIV_Instance instance, + const unsigned char *nonce, int nonce_length, + const void *assoc, int assoc_length, + const void *plaintext, int plaintext_length, + unsigned char *ciphertext, int ciphertext_length) +{ + if (!instance->key_set) + return 0; + + if (nonce_length < SIV_MIN_NONCE_SIZE || assoc_length < 0 || + plaintext_length < 0 || plaintext_length > ciphertext_length || + plaintext_length + SIV_DIGEST_SIZE != ciphertext_length) + return 0; + + assert(assoc && plaintext); + + siv_cmac_aes128_encrypt_message(&instance->siv, nonce_length, nonce, + assoc_length, assoc, + ciphertext_length, ciphertext, plaintext); + return 1; +} + +/* ================================================== */ + +int +SIV_Decrypt(SIV_Instance instance, + const unsigned char *nonce, int nonce_length, + const void *assoc, int assoc_length, + const unsigned char *ciphertext, int ciphertext_length, + void *plaintext, int plaintext_length) +{ + if (!instance->key_set) + return 0; + + if (nonce_length < SIV_MIN_NONCE_SIZE || assoc_length < 0 || + plaintext_length < 0 || plaintext_length > ciphertext_length || + plaintext_length + SIV_DIGEST_SIZE != ciphertext_length) + return 0; + + assert(assoc && plaintext); + + if (!siv_cmac_aes128_decrypt_message(&instance->siv, nonce_length, nonce, + assoc_length, assoc, + plaintext_length, plaintext, ciphertext)) + return 0; + + return 1; +} 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..4c350e9 --- /dev/null +++ b/smooth.c @@ -0,0 +1,368 @@ +/* + 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, "Time smoothing activated%s", 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; +} + +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..8a5d046 --- /dev/null +++ b/socket.c @@ -0,0 +1,1636 @@ +/* + 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 + * + * 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; + +/* 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 "?"; + } +} + +/* ================================================== */ + +#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 ( +#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 + (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 +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 */ + if (family == IPADDR_INET6 && !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 + + 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 = open_socket(domain, type, flags); + 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("Opened %s%s socket fd=%d%s%s%s%s", + 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 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_Initialise(int family) +{ + 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 + + initialised = 1; +} + +/* ================================================== */ + +void +SCK_Finalise(void) +{ + ARR_DestroyInstance(recv_sck_messages); + ARR_DestroyInstance(recv_headers); + ARR_DestroyInstance(recv_messages); + + 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_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)) + return 1; +#endif + + return 0; +} + +/* ================================================== */ + +int +SCK_ListenOnSocket(int sock_fd, int backlog) +{ + if (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) +{ + 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..cdbae2d --- /dev/null +++ b/socket.h @@ -0,0 +1,147 @@ +/* + 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; + +/* 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); + +/* 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..2aee6f2 --- /dev/null +++ b/sources.c @@ -0,0 +1,1756 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2003 + * Copyright (C) Miroslav Lichvar 2011-2016, 2018, 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. + * + ********************************************************************** + + ======================================================================= + + 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; + + /* 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; +}; + +/* ================================================== */ +/* 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) */ + +/* 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 + +static double max_distance; +static double max_jitter; +static double reselect_distance; +static double stratum_weight; +static double combine_limit; + +static LOG_FileID logfileid; + +/* Identifier of the dump file */ +#define DUMP_IDENTIFIER "SRC0\n" + +/* ================================================== */ +/* Forward prototype */ + +static void update_sel_options(void); +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); + + 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; + + assert(initialised); + + 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 this was the previous reference source, we have to reselect! */ + if (selected_source_index == dead_index) + SRC_ReselectSource(); + else if (selected_source_index > dead_index) + --selected_source_index; +} + +/* ================================================== */ + +void +SRC_ResetInstance(SRC_Instance instance) +{ + instance->updates = 0; + instance->reachability = 0; + instance->reachability_size = 0; + instance->distant = 0; + instance->status = SRC_BAD_STATS; + instance->sel_score = 1.0; + instance->stratum = 0; + instance->leap = LEAP_Unsynchronised; + instance->leap_vote = 0; + + memset(&instance->sel_info, 0, sizeof (instance->sel_info)); + + SST_ResetInstance(instance->stats); +} + +/* ================================================== */ + +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; +} + +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 NTP sources that are unreachable, falsetickers, or + have root distance or jitter larger than the allowed maximums */ + if (inst->type == SRC_NTP && + ((!inst->reachability && inst->reachability_size == SOURCE_REACH_BITS) || + inst->status == SRC_BAD_DISTANCE || inst->status == SRC_JITTERY || + inst->status == SRC_FALSETICKER)) { + NSR_HandleBadSource(inst->ip_addr); + } +} + +/* ================================================== */ + +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) + continue; + + 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(const char *format, const char *arg) +{ + if (REF_GetMode() != REF_ModeNormal) + return; + LOG(LOGS_INFO, format, arg); +} + +/* ================================================== */ + +static void +log_selection_source(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(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; + + DEBUG_LOG("%s status=%c options=%x reach=%o/%d updates=%d distant=%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, (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); + } +} + +/* ================================================== */ + +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++; + + if (n_sources == 0) { + /* In this case, we clearly cannot synchronise to anything */ + if (selected_source_index != INVALID_SOURCE) { + log_selection_message("Can't synchronise: no sources", NULL); + selected_source_index = INVALID_SOURCE; + } + 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); + return; + } + + if (n_endpoints == 0) { + /* No sources provided valid endpoints */ + if (selected_source_index != INVALID_SOURCE) { + log_selection_message("Can't synchronise: no selectable sources", NULL); + selected_source_index = INVALID_SOURCE; + } + 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 (selected_source_index != INVALID_SOURCE) { + log_selection_message("Can't synchronise: no majority", NULL); + 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 (!n_sel_sources || sel_req_source || n_sel_sources < CNF_GetMinSources()) { + if (selected_source_index != INVALID_SOURCE) { + log_selection_message("Can't synchronise: %s selectable sources", + !n_sel_sources ? "no" : + sel_req_source ? "no required source in" : "not enough"); + selected_source_index = INVALID_SOURCE; + } + 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) { + selected_source_index = INVALID_SOURCE; + mark_ok_sources(SRC_WAITS_UPDATE); + return; + } + + selected_source_index = max_score_index; + log_selection_source("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; + } + } + + 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]); +} + +/* ================================================== */ + +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; +} + +/* ================================================== */ + +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..6c799bb --- /dev/null +++ b/sources.h @@ -0,0 +1,140 @@ +/* + 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); + +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..eb4705e --- /dev/null +++ b/sourcestats.c @@ -0,0 +1,1040 @@ +/* + 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 of source, used for logging to statistics log */ + 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, °rees_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 receive timestamp */ + last_sample_time = inst->sample_times[i]; + 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 */ @@ -0,0 +1,567 @@ +/* + 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; +} + +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 */ @@ -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 +} + +/* ================================================== */ @@ -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..f2baab1 --- /dev/null +++ b/sys_linux.c @@ -0,0 +1,1007 @@ +/* + 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> +#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_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), + + /* 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 }, +#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, +#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; + + 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..35a3faf --- /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-nss" \ + "--without-nettle --without-nss --without-tomcrypt" \ + "--without-nettle --without-nss --without-tomcrypt --without-gnutls" +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..8040efe --- /dev/null +++ b/test/compilation/003-sanitizers @@ -0,0 +1,103 @@ +#!/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-scfilter" \ + "--without-gnutls" \ + "--without-nettle" \ + "--without-nettle --without-nss" \ + "--without-nettle --without-nss --without-tomcrypt" \ + "--without-nettle --without-nss --without-tomcrypt --without-gnutls"; \ +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 -fno-sanitize-recover=undefined,float-divide-by-zero $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..f09f170 --- /dev/null +++ b/test/simulation/106-refclock @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "SHM refclock" + +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..b78f0d8 --- /dev/null +++ b/test/simulation/110-chronyc @@ -0,0 +1,496 @@ +#!/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="" +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" \ + "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" \ + "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$" || 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 +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$" || 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..4fd89f8 --- /dev/null +++ b/test/simulation/114-presend @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +. ./test.common +test_start "presend option" + +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 + +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..59ddf7c --- /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" 4 8 || test_fail + +test_pass diff --git a/test/simulation/133-hwtimestamp b/test/simulation/133-hwtimestamp new file mode 100755 index 0000000..d3cce6d --- /dev/null +++ b/test/simulation/133-hwtimestamp @@ -0,0 +1,60 @@ +#!/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 + 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 10 || 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 + +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..6a2112d --- /dev/null +++ b/test/simulation/139-nts @@ -0,0 +1,312 @@ +#!/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 " 50 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" 6 8 || test_fail + check_log_messages "Source 192.168.123.2 replaced with 192.168.123.1" 6 8 || 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 + +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 +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-ptpport b/test/simulation/142-ptpport new file mode 100755 index 0000000..060932c --- /dev/null +++ b/test/simulation/142-ptpport @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "PTP port" + +# Block communication between 3 and 1 +base_delay="(+ 1e-4 (* -1 (equal 0.1 from 3) (equal 0.1 to 1)))" + +cat > tmp/peer.keys <<-EOF +1 MD5 1234567890 +EOF + +clients=2 +peers=2 +max_sync_time=420 + +server_conf=" +ptpport 319" +client_conf=" +ptpport 319 +authselectmode ignore +keyfile tmp/peer.keys" +client_server_options="minpoll 6 maxpoll 6 port 319" +client_peer_options="minpoll 6 maxpoll 6 port 319 key 1" + +run_test || test_fail +check_chronyd_exit || test_fail +check_source_selection || test_fail +check_sync || test_fail + +check_file_messages " 2 1 .* 319 319 1 96 " 150 160 \ + log.packets || test_fail +check_file_messages " 1 2 .* 319 319 1 96 " 150 160 \ + log.packets || test_fail +check_file_messages " 2 3 .* 319 319 1 116 " 150 160 \ + log.packets || test_fail +check_file_messages " 3 2 .* 319 319 1 116 " 150 160 \ + log.packets || test_fail + +test_pass 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-exp1 b/test/simulation/144-exp1 new file mode 100755 index 0000000..4a2042d --- /dev/null +++ b/test/simulation/144-exp1 @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +. ./test.common + +test_start "experimental 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/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..70bbde1 --- /dev/null +++ b/test/simulation/test.common @@ -0,0 +1,528 @@ +# 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_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_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)" + 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:" + 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:" + + 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..04d14c2 --- /dev/null +++ b/test/system/007-cmdmon @@ -0,0 +1,181 @@ +#!/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" \ + "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 ----- ----- 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$"|| 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..a7fc1d5 --- /dev/null +++ b/test/system/008-confload @@ -0,0 +1,74 @@ +#!/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 + +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 [^^]*$" || test_fail + +rm $TEST_DIR/conf5.d/1.sources +echo "server 127.123.5.2 minpoll 7" > $TEST_DIR/conf5.d/2.sources +echo > $TEST_DIR/conf5.d/3.sources +echo "server 127.123.5.4" > $TEST_DIR/conf5.d/4.sources + +run_chronyc "reload sources" || test_fail + +run_chronyc "sources" || test_fail +check_chronyc_output "^[^=]* +=* +.. 127\.123\.1\.1 [^^]* +.. 127\.123\.1\.3 [^^]* +.. 127\.123\.1\.4 [^^]* +.. 127\.123\.3\.1 [^^]* +.. 127\.123\.2\.2 [^^]* +.. 127\.123\.2\.3 [^^]* +.. 127\.123\.4\.4 [^^]* +.. 127\.123\.1\.2 *[05] 6 [^^]* +.. 127\.123\.5\.2 *[05] 7 [^^]* +.. 127\.123\.5\.4 [^^]*$" || test_fail + +stop_chronyd || 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..a0b366e --- /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 15 256 [0-9] 0 0 [78] 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 15 256 [0-9] 0 0 [78] 100$" || test_fail + +stop_chronyd || test_fail +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..6b098ac --- /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" "1" "-2" "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..29b7cc3 --- /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" "1" "-2" "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..7005c9e --- /dev/null +++ b/test/system/test.common @@ -0,0 +1,373 @@ +# 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" + 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 -q 'Could not' "$logfile" && \ + ! 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/clientlog.c b/test/unit/clientlog.c new file mode 100644 index 0000000..f59e130 --- /dev/null +++ b/test/unit/clientlog.c @@ -0,0 +1,292 @@ +/* + ********************************************************************** + * 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; + 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); + 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); + } + + CLG_SaveNtpTimestamps(&ntp_ts, + UTI_IsZeroTimespec(&ts) ? (random() % 2 ? &ts : NULL) : &ts); + + 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)); + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0); + + 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)); + + 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); + } + + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + + TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts2)); + TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0); + + 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)); + int64_to_ntp64(get_ntp_tss(index + 1)->rx_ts - 1, &ntp_ts); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + 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)); + int64_to_ntp64(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts + 1, &ntp_ts); + TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts)); + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + 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); + 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); + + 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)); + } + + for (k = random() % 10; k > 0; k--) { + ts64 = get_random64() >> shift; + int64_to_ntp64(ts64, &ntp_ts); + CLG_GetNtpTxTimestamp(&ntp_ts, &ts); + } + } + + 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)); + CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts); + } + } + + 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..875731e --- /dev/null +++ b/test/unit/ntp_core.c @@ -0,0 +1,623 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2017-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 <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_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(¤t_time, x, ¤t_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) +{ + 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; + local_ts.ts = current_time; + local_ts.err = 0.0; + local_ts.source = NTP_TS_KERNEL; + + NCR_ProcessTxKnown(inst, &local_addr, &local_ts, &req_buffer, req_length); + } +} + +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; + local_ts.ts = current_time; + local_ts.err = 0.0; + 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(¤t_time, &res->receive_ts, NULL); + advance_time(TST_GetRandomDouble(-1e-4, 1e-3)); + UTI_TimespecToNtp64(¤t_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) +{ + 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; + local_ts.ts = current_time; + local_ts.err = 0.0; + 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 (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); + 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; + 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(¤t_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); + + 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); + + send_response(interleaved, authenticated, 1, 0, 1); + DEBUG_LOG("response 1"); + proc_response(inst1, 0, 0, 0, updated); + + if (source.params.authkey) { + send_response(interleaved, authenticated, 1, 1, 0); + DEBUG_LOG("response 2"); + proc_response(inst1, 0, 0, 0, 0); + } + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 3"); + proc_response(inst1, -1, valid, valid, updated); + DEBUG_LOG("response 4"); + proc_response(inst1, 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); + + advance_time(1.0); + + send_response(interleaved, authenticated, 1, 1, 1); + DEBUG_LOG("response 6"); + proc_response(inst1, 0, 0, valid && updated, updated); + } + + 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); + process_request(&remote_addr); + proc_response(inst1, + !source.params.interleaved || source.params.version != 4 || + inst1->mode == MODE_ACTIVE || j != 2, + 1, 1, 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); + + send_request(inst1); + 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); + + 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); + 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); + + 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..ea3910f --- /dev/null +++ b/test/unit/ntp_sources.c @@ -0,0 +1,364 @@ +/* + ********************************************************************** + * 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> + +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) + +static void change_remote_address(NCR_Instance inst, NTP_Remote_Address *remote_addr, + int ntp_only); + +#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); + } + + 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(remote_addr, 4); + + NCR_ChangeRemoteAddress(inst, remote_addr, ntp_only); + + if (update && update_pos == 1) + r = update_random_address(remote_addr, 4); + + if (r) + TEST_CHECK(UTI_IsIPReal(&saved_address_update.old_address.ip_addr)); +} + +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..72690bf --- /dev/null +++ b/test/unit/nts_ke_client.c @@ -0,0 +1,144 @@ +/* + ********************************************************************** + * 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) + data[0] = htons(AEAD_AES_SIV_CMAC_256 + random() % 10 + 1); + else + data[0] = htons(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..f4f03a1 --- /dev/null +++ b/test/unit/nts_ke_server.c @@ -0,0 +1,230 @@ +/* + ********************************************************************** + * 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(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, 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 + server_keys[i].key[0]; + 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 + server_keys[i].key[0]; + } + + 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..307b93b --- /dev/null +++ b/test/unit/nts_ntp_auth.c @@ -0,0 +1,112 @@ +/* + ********************************************************************** + * 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_Instance siv; + int i, j, r, packet_length, nonce_length, key_length; + int plaintext_length, plaintext2_length, min_ef_length; + + siv = SIV_CreateInstance(AEAD_AES_SIV_CMAC_256); + TEST_CHECK(siv); + + for (i = 0; i < 10000; i++) { + key_length = SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256); + for (j = 0; j < key_length; j++) + key[j] = random() % 256; + TEST_CHECK(SIV_SetKey(siv, key, key_length)); + + nonce_length = random() % sizeof (nonce) + 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); + 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..c14fd41 --- /dev/null +++ b/test/unit/nts_ntp_client.c @@ -0,0 +1,284 @@ +/* + ********************************************************************** + * 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; + + context->algorithm = AEAD_AES_SIV_CMAC_256; + + 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]; + NTP_PacketInfo info; + NTP_Packet packet; + int expected_length, req_cookies; + + 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); + + 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 + sizeof (inst->nonce) + SIV_GetTagLength(inst->siv); + DEBUG_LOG("length=%d cookie_length=%d expected_length=%d", + 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 = random() % (sizeof (nonce)) + 1; + + 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..40938d6 --- /dev/null +++ b/test/unit/nts_ntp_server.c @@ -0,0 +1,176 @@ +/* + ********************************************************************** + * 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 = SERVER_SIV; + 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); + + 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_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..76846c8 --- /dev/null +++ b/test/unit/siv.c @@ -0,0 +1,321 @@ +/* + ********************************************************************** + * 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 + }, + { 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; + + TEST_CHECK(SIV_CreateInstance(0) == 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); + TEST_CHECK(siv != NULL); + + TEST_CHECK(SIV_GetKeyLength(tests[i].algorithm) == tests[i].key_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); + 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) { + 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/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..94619c1 --- /dev/null +++ b/test/unit/test.c @@ -0,0 +1,181 @@ +/* + ********************************************************************** + * 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(); + + 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..112676d --- /dev/null +++ b/test/unit/util.c @@ -0,0 +1,744 @@ +/* + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2017-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. + * + ********************************************************************** + */ + +#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; + 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)); + + 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); + + 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)); + + 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); +} @@ -0,0 +1,1550 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997-2003 + * Copyright (C) Miroslav Lichvar 2009, 2012-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. + * + ********************************************************************** + + ======================================================================= + + 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_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); +} + +/* ================================================== */ + +/* 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); +} + +/* ================================================== */ + +#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; +} + +/* ================================================== */ + +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; +} @@ -0,0 +1,258 @@ +/* + 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_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); + +/* 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); + +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); + +/* 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..69df05f --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +4.3 |