From 88840341045de784db28205584fa1e470010c3c3 Mon Sep 17 00:00:00 2001 From: "Richard P. Curnow" Date: Thu, 19 Jan 2006 21:34:28 +0000 Subject: [PATCH] Equivalent to V1.19.99.1 This is a verbatim copy of the files at that stage of the repository that was built from the CVS import. It allows future development to see a bit of recent history, but without carrying around the baggage going back to 1997. If that is really required, git grafts can be used. --- COPYING | 339 +++ INSTALL | 89 + Makefile.in | 161 ++ NEWS | 267 ++ README | 243 ++ acquire.c | 688 +++++ acquire.h | 47 + addressing.h | 46 + addrfilt.c | 385 +++ addrfilt.h | 77 + broadcast.c | 159 ++ broadcast.h | 39 + build_kit | 24 + candm.h | 594 +++++ chrony.1 | 65 + chrony.conf.5 | 49 + chrony.lsm | 29 + chrony.spec.sample | 52 + chrony.texi | 3979 +++++++++++++++++++++++++++++ chronyc.1 | 56 + chronyd.8 | 111 + client.c | 2589 +++++++++++++++++++ clientlog.c | 392 +++ clientlog.h | 91 + cmdmon.c | 2119 +++++++++++++++ cmdmon.h | 41 + cmdparse.c | 163 ++ cmdparse.h | 60 + conf.c | 1235 +++++++++ conf.h | 74 + configure | 313 +++ contrib/DNSchrony/COPYING | 339 +++ contrib/DNSchrony/DNSchrony.pl | 583 +++++ contrib/DNSchrony/DNSchronyADD | 21 + contrib/DNSchrony/DNSchronyDELETE | 7 + contrib/DNSchrony/DNSchronyUPDATE | 7 + contrib/DNSchrony/README | 166 ++ contrib/DNSchrony/ip-up.local | 22 + contrib/andrew_bishop_1 | 114 + contrib/andrew_bishop_2 | 95 + contrib/erik_bryer_1 | 65 + contrib/ken_gillett_1 | 100 + contrib/stephan_boettcher_1 | 162 ++ contrib/wolfgang_weisselberg1 | 118 + examples/chrony.conf.example | 289 +++ examples/chrony.keys.example | 27 + faq.txt | 384 +++ faqgen.pl | 140 + getdate.c | 2019 +++++++++++++++ getdate.h | 28 + keys.c | 243 ++ keys.h | 44 + local.c | 574 +++++ local.h | 184 ++ localp.h | 73 + logging.c | 216 ++ logging.h | 97 + main.c | 311 +++ main.h | 39 + manual.c | 331 +++ manual.h | 49 + md5.c | 322 +++ md5.h | 60 + memory.h | 43 + mkdirpp.c | 135 + mkdirpp.h | 35 + nameserv.c | 92 + nameserv.h | 42 + ntp.h | 116 + ntp_core.c | 1867 ++++++++++++++ ntp_core.h | 105 + ntp_io.c | 297 +++ ntp_io.h | 53 + ntp_sources.c | 499 ++++ ntp_sources.h | 99 + pktlength.c | 240 ++ pktlength.h | 42 + reference.c | 709 +++++ reference.h | 147 ++ regress.c | 628 +++++ regress.h | 108 + reports.h | 121 + rtc.c | 222 ++ rtc.h | 52 + rtc_linux.c | 1174 +++++++++ rtc_linux.h | 53 + sched.c | 601 +++++ sched.h | 83 + sources.c | 938 +++++++ sources.h | 155 ++ sourcestats.c | 931 +++++++ sourcestats.h | 156 ++ srcparams.h | 47 + strerror.c | 41 + sys.c | 104 + sys.h | 42 + sys_linux.c | 850 ++++++ sys_linux.h | 40 + sys_netbsd.c | 326 +++ sys_netbsd.h | 39 + sys_solaris.c | 483 ++++ sys_solaris.h | 38 + sys_sunos.c | 433 ++++ sys_sunos.h | 38 + sysincl.h | 129 + util.c | 364 +++ util.h | 104 + version.txt | 4 + wrap_adjtimex.c | 155 ++ wrap_adjtimex.h | 79 + 110 files changed, 34864 insertions(+) create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 Makefile.in create mode 100644 NEWS create mode 100644 README create mode 100644 acquire.c create mode 100644 acquire.h create mode 100644 addressing.h create mode 100644 addrfilt.c create mode 100644 addrfilt.h create mode 100644 broadcast.c create mode 100644 broadcast.h create mode 100755 build_kit create mode 100644 candm.h create mode 100644 chrony.1 create mode 100644 chrony.conf.5 create mode 100644 chrony.lsm create mode 100644 chrony.spec.sample create mode 100644 chrony.texi create mode 100644 chronyc.1 create mode 100644 chronyd.8 create mode 100644 client.c create mode 100644 clientlog.c create mode 100644 clientlog.h create mode 100644 cmdmon.c create mode 100644 cmdmon.h create mode 100644 cmdparse.c create mode 100644 cmdparse.h create mode 100644 conf.c create mode 100644 conf.h create mode 100755 configure create mode 100644 contrib/DNSchrony/COPYING create mode 100755 contrib/DNSchrony/DNSchrony.pl create mode 100755 contrib/DNSchrony/DNSchronyADD create mode 100755 contrib/DNSchrony/DNSchronyDELETE create mode 100755 contrib/DNSchrony/DNSchronyUPDATE create mode 100644 contrib/DNSchrony/README create mode 100644 contrib/DNSchrony/ip-up.local create mode 100644 contrib/andrew_bishop_1 create mode 100644 contrib/andrew_bishop_2 create mode 100644 contrib/erik_bryer_1 create mode 100644 contrib/ken_gillett_1 create mode 100644 contrib/stephan_boettcher_1 create mode 100644 contrib/wolfgang_weisselberg1 create mode 100644 examples/chrony.conf.example create mode 100644 examples/chrony.keys.example create mode 100644 faq.txt create mode 100644 faqgen.pl create mode 100644 getdate.c create mode 100644 getdate.h create mode 100644 keys.c create mode 100644 keys.h create mode 100644 local.c create mode 100644 local.h create mode 100644 localp.h create mode 100644 logging.c create mode 100644 logging.h create mode 100644 main.c create mode 100644 main.h create mode 100644 manual.c create mode 100644 manual.h create mode 100644 md5.c create mode 100644 md5.h create mode 100644 memory.h create mode 100644 mkdirpp.c create mode 100644 mkdirpp.h create mode 100644 nameserv.c create mode 100644 nameserv.h create mode 100644 ntp.h create mode 100644 ntp_core.c create mode 100644 ntp_core.h create mode 100644 ntp_io.c create mode 100644 ntp_io.h create mode 100644 ntp_sources.c create mode 100644 ntp_sources.h create mode 100644 pktlength.c create mode 100644 pktlength.h create mode 100644 reference.c create mode 100644 reference.h create mode 100644 regress.c create mode 100644 regress.h create mode 100644 reports.h create mode 100644 rtc.c create mode 100644 rtc.h create mode 100644 rtc_linux.c create mode 100644 rtc_linux.h create mode 100644 sched.c create mode 100644 sched.h create mode 100644 sources.c create mode 100644 sources.h create mode 100644 sourcestats.c create mode 100644 sourcestats.h create mode 100644 srcparams.h create mode 100644 strerror.c create mode 100644 sys.c create mode 100644 sys.h create mode 100644 sys_linux.c create mode 100644 sys_linux.h create mode 100644 sys_netbsd.c create mode 100644 sys_netbsd.h create mode 100644 sys_solaris.c create mode 100644 sys_solaris.h create mode 100644 sys_sunos.c create mode 100644 sys_sunos.h create mode 100644 sysincl.h create mode 100644 util.c create mode 100644 util.h create mode 100644 version.txt create mode 100644 wrap_adjtimex.c create mode 100644 wrap_adjtimex.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..916d1f0 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 Library 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 + + Appendix: 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. + + + Copyright (C) 19yy + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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) 19yy 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. + + , 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 Library General +Public License instead of this License. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..2299780 --- /dev/null +++ b/INSTALL @@ -0,0 +1,89 @@ +The software is distributed as source code which has to be compiled. + +PARTS OF THE SOFTWARE ARE HIGHLY SYSTEM-SPECIFIC AND NON-PORTABLE. +UNLESS YOU ARE RUNNING A SUPPORTED SYSTEM, BE PREPARED FOR SOME +PROGRAMMING! + +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 a single 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 CFLAGS=-O ./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, the files `options.h' and `Makefile' will +be generated. + +By default, chronyc will be built to make use of the readline library. If you +don't want this, specify the --disable-readline flag to configure. If you have +readline and/or ncurses installed in a non-standard location, please refer to +the chrony.txt file for information. + +Now type + + make + +to build the programs. + +If you want to build the manual in plain text, HTML and info versions, 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, plain text manual and manpages. + +To install the HTML and info versions of the manual as well, enter the command + + make install-docs + +If you want chrony to appear in the top level info directory listing, you need +to run the install-info command manually after this step. install-info takes 2 +arguments. The first is the path to the chrony.info file you have just +installed. This will be the argument you gave to --prefix when you configured +(/usr/local by default), with /info/chrony.info on the end. The second +argument is the location of the file called 'dir'. This will typically be +/usr/info/dir. So the typical command line would be + + install-info /usr/local/info/chrony.info /usr/info/dir + +Now that the software is successfully installed, the next step is to +set up a configuration file. The contents of this depend on the +network environment in which the computer operates. Typical scenarios +are described in the manual. The simplest case is for a computer with +a permanent Internet connection - suppose you want to use the NTP +server ntp1.foobar.com as your time reference. You would create an +/etc/chrony.conf file containing + + server ntp1.foobar.com + driftfile /etc/chrony.drift + +and then run /usr/local/sbin/chronyd. + + diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..0a91caf --- /dev/null +++ b/Makefile.in @@ -0,0 +1,161 @@ +################################################## +# +# $Header: /cvs/src/chrony/Makefile.in,v 1.47 2003/04/07 21:43:54 richard Exp $ +# +# ======================================================================= +# +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# ======================================================================= +# +# Makefile template + +INSTALL_PREFIX=@INSTALL_PREFIX@ +MANDIR=@MANDIR@ +INFODIR=@INFODIR@ + +CC = @CC@ +CCWARNFLAGS = @CCWARNFLAGS@ +OPTFLAGS = @CFLAGS@ @EXTRA_DEFS@ + +DESTDIR= + +OBJS = util.o sched.o regress.o local.o \ + sys.o main.o ntp_io.o ntp_core.o ntp_sources.o \ + sources.o sourcestats.o reference.o \ + logging.o conf.o cmdmon.o md5.o keys.o \ + nameserv.o acquire.o manual.o addrfilt.o \ + cmdparse.o mkdirpp.o rtc.o pktlength.o clientlog.o \ + broadcast.o + +EXTRA_OBJS=@EXTRA_OBJECTS@ + +CLI_OBJS = client.o md5.o nameserv.o getdate.o cmdparse.o \ + pktlength.o + +SRCS = $(patsubst %.o,%.c,$(OBJS)) +EXTRA_SRCS = $(patsubst %.o,%.c,$(EXTRA_OBJS)) + +CLI_SRCS = $(patsubst %.o,%.c,$(CLI_OBJS)) + +LIBS = @LIBS@ + +EXTRA_LIBS=@EXTRA_LIBS@ +EXTRA_CLI_LIBS=@EXTRA_CLI_LIBS@ + +DEFS=@SYSDEFS@ + +CFLAGS = $(CCWARNFLAGS) $(OPTFLAGS) + +# Until we have a main procedure we can link, just build object files +# to test compilation + +all : chronyd chronyc + +chronyd : $(OBJS) $(EXTRA_OBJS) + $(CC) $(OPTFLAGS) -o chronyd $(OBJS) $(EXTRA_OBJS) $(LIBS) $(EXTRA_LIBS) + +chronyc : $(CLI_OBJS) + $(CC) $(OPTFLAGS) -o chronyc $(CLI_OBJS) @READLINE_LINK@ $(LIBS) $(EXTRA_CLI_LIBS) + +client.o : client.c + $(CC) $(CFLAGS) $(DEFS) @READLINE_COMPILE@ -c $< + +.depend : + gcc -MM $(SRCS) $(EXTRA_SRCS) > .depend + +distclean : + -rm -f *.o *.s chronyc chronyd core options.h Makefile *~ + +clean : + -rm -f *.o *.s chronyc chronyd core *~ + +version.h : version.txt + sed -e 's/[$$]Name: \(.*\) [$$]/\1/;' < version.txt > version.h + + +# For install, don't use the install command, because its switches +# seem to vary between systems. + +install: chronyd chronyc + [ -d $(DESTDIR)$(INSTALL_PREFIX) ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX) + [ -d $(DESTDIR)$(INSTALL_PREFIX)/sbin ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/sbin + [ -d $(DESTDIR)$(INSTALL_PREFIX)/bin ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/bin + [ -d $(DESTDIR)$(INSTALL_PREFIX)/doc ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/doc + [ -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 + [ -d $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony + cp chronyd $(DESTDIR)$(INSTALL_PREFIX)/sbin/chronyd + chmod 555 $(DESTDIR)$(INSTALL_PREFIX)/sbin/chronyd + cp chronyc $(DESTDIR)$(INSTALL_PREFIX)/bin/chronyc + chmod 555 $(DESTDIR)$(INSTALL_PREFIX)/bin/chronyc + cp chrony.txt $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt + chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt + cp COPYING $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/COPYING + chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/COPYING + cp README $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/README + chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/README + cp chrony.1 $(DESTDIR)$(MANDIR)/man1 + chmod 444 $(DESTDIR)$(MANDIR)/man1/chrony.1 + cp chronyc.1 $(DESTDIR)$(MANDIR)/man1 + chmod 444 $(DESTDIR)$(MANDIR)/man1/chronyc.1 + cp chronyd.8 $(DESTDIR)$(MANDIR)/man8 + chmod 444 $(DESTDIR)$(MANDIR)/man8/chronyd.8 + cp chrony.conf.5 $(DESTDIR)$(MANDIR)/man5 + chmod 444 $(DESTDIR)$(MANDIR)/man5/chrony.conf.5 + +%.o : %.c + $(CC) $(CFLAGS) $(DEFS) -c $< + +%.s : %.c + $(CC) $(CFLAGS) $(DEFS) -S $< + +main.o logging.o client.o : version.h + +# makeinfo v4 required to generate plain text and html +MAKEINFO:=makeinfo + +install-docs : docs + [ -d $(DESTDIR)$(INSTALL_PREFIX)/doc ] || mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/doc + cp chrony.txt $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt + chown root $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt + chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.txt + cp chrony.html $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.html + chown root $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.html + chmod 444 $(DESTDIR)$(INSTALL_PREFIX)/doc/chrony/chrony.html + [ -d $(DESTDIR)$(INFODIR) ] || mkdir -p $(DESTDIR)$(INFODIR) + cp chrony.info* $(DESTDIR)$(INFODIR) + chown root $(DESTDIR)$(INFODIR)/chrony.info* + chmod 444 $(DESTDIR)$(INFODIR)/chrony.info* + +docs : chrony.txt chrony.html chrony.info + +chrony.txt : chrony.texi + $(MAKEINFO) --no-headers --number-sections -o chrony.txt chrony.texi + +chrony.html : chrony.texi + $(MAKEINFO) --no-split --html --number-sections -o chrony.html chrony.texi + +chrony.info : chrony.texi + $(MAKEINFO) chrony.texi + +# This is only relevant if you're maintaining the website! +faq.php : faq.txt faqgen.pl + perl faqgen.pl < faq.txt > faq.php + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..aeee89b --- /dev/null +++ b/NEWS @@ -0,0 +1,267 @@ +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) +* Fix for Linux on SH-5 (floating point denorm handling). +* Add detection of Linux 2.5 +* Allow arbitrary value of HZ in Linux kernel + +New in version 1.19 +=================== + +* Auto-detect kernel's timer interrupt rate (so-called 'HZ') when chronyd + starts instead of relying on compiled-in value. +* Fix 2 bugs in function that creates the directory for the log and dump files. +* Amended webpage URL and contact details. +* Generate more informative syslog messages before exiting on failed + assertions. +* Fix bugs in clamping code for the tick value used when slewing a large + offset. +* Don't chown files to root during install (should be pointless, and makes RPM + building awkward as ordinary user.) +* Include chrony.spec file for building RPMs + +New in version 1.18 +=================== +* Amend homepage and mailing list information to chrony.sunsite.dk +* Delete pidfile on exit from chronyd. +* Improvements to readline interface to chronyc +* Only generate syslog message when synchronisation is initially lost (instead + of on every failed synchronisation attempt) +* Use double fork approach when initialising daemon. +* More things in contrib directory. +* New options to help package builders: --infodir/--mandir for configure, and + DESTDIR=xxx for make. (See section 2.2 of chrony.txt for details). +* Changed the wording of the messages generated by mailonchange and logchange + directives. + +New in version 1.17 +=================== +* Port to NetBSD +* Configuration supports Linux on PPC +* Fix compilation warnings +* Several documentation improvements +* Bundled manpages (taken from the 'missing manpages project') +* Cope with lack of bzero function for Solaris 2.3 systems +* Store chronyd's pid in a file (default /var/run/chronyd.pid) and check if + chronyd may already be running when starting up. New pidfile directive in + configuration file. +* Any size subnet is now allowed in allow and deny commands. (Example: + 6.7.8/20 or 6.7.8.x/20 (any x) mean a 20 bit subnet). +* The environment variables CC and CFLAGS passed to configure can now be used + to select the compiler and optimisation/debug options to use +* Write syslog messages when chronyd loses synchronisation. +* Print GPL text when chronyc is run. +* Add NTP broadcast server capability (new broadcast directive). +* Add 'auto_offline' option to server/peer (conf file) or add server/peer (via + chronyc). +* Add 'activity' command to chronyc, to report how many servers/peers are + currently online/offline. +* Fix long-standing bug with how the system time quantum was calculated. +* Include support for systems with HZ!=100 (HZ is the timer interrupt + frequency). +* Include example chrony.conf and chrony.keys files (examples subdirectory). +* Include support for readline in chronyc. + +New in version 1.16.1 +===================== +* Fix compilation problem on Linux 2.4.13 (spinlock.h / spinlock_t) + +New in version 1.16 +=================== +* More informative captions for 'sources' and 'sourcestats' commands in chronyc + (use 'sources -v' and 'sourcestats -v' to get them). +* Correct behaviour for Solaris versions>=2.6 (dosynctodr not required on these + versions.) +* Remove some compiler warnings (Solaris) +* If last line of keys file doesn't have end-of-line, don't truncate final + character of that key. +* Change timestamp format used in logfiles to make it fully numeric (to aid + importing data into spreadsheets etc) +* Minor documentation updates and improvements. + +New in version 1.15 +=================== +* Add contributed change to 'configure' to support Solaris 2.8 on x86 +* Workaround for assertion failure that arises if two received packets occur + close together. (Still need to find out why this happens at all.) +* Hopefully fix problem where fast slewing was incompatible with machines + that have a large background drift rate (=> tick value went out of range + for adjtimex() on Linux.) +* Fix rtc_linux.c compile problems with 2.4.x kernel include files. +* Include support for RTC device not being at /dev/rtc (new rtcdevice directive + in configuration file). +* Include support for restricting network interfaces for commands (new + bindcmdaddress directive in configuration file) +* Fix potential linking fault in pktlength.c (use of CROAK macro replaced by + normal assert). +* Add some material on bug reporting + contributing to the chrony.texi file +* Made the chrony.texi file "Vim6-friendly" (removed xrefs on @node lines, + added folding markers to chapters + sections.) +* Switched over to GPL for the licence + +New in version 1.14 +=================== +* Fix compilation for certain other Linux distributions (including Mandrake + 7.1) + +New in version 1.13 +=================== +* Fixed compilation problems on Redhat/SuSE installations with recent 2.2.x + kernels. +* Minor tidy-ups and documentation enhancements. +* Add support for Linux 2.4 kernels + +New in version 1.12 +=================== + +* Trial fix for long-standing bug in Linux RTC estimator when system time is + slewed. +* Fix bug in chronyc if -h is specified without a hostname +* Fixes to logging various error conditions when operating in daemon mode. +* More stuff under contrib/ +* Changes to README file (e.g. about the new chrony-users mailing list) + +New in version 1.11a +==================== + +* Minor changes to contact details +* Minor changes to installation details (chrony subdirectory under doc/) + +New in version 1.11 +=================== + +* Improve robustness of installation procedure +* Tidy up documenation and contact details +* Distribute manual as .txt rather than as .ps +* Add -n option to chronyc to work with numeric IP addresses rather than + names. +* Add material in contrib subdirectory +* Improve robustness of handling drift file and RTC coefficients file +* Improve robustness of regression algorithm + +New in version 1.1 +================== + +Bug fixes +--------- + +* Made linear regression more resistant to rounding errors (old one + occasionally generated negative variances which made everything go + haywire). Trap infinite or 'not-a-number' values being used to + alter system clock to increase robustness further. + +Other changes/Enhancements +-------------------------- + +* Support for Linux 2.1 and 2.2 kernels + +* New command 'makestep' in chronyc to immediately jump the system + time to match the NTP estimated time (Linux only) - a response to + systems booting an hour wrong after summertime/wintertime changes, + due to RTCs running on local time. Needs extending to Sun driver + files too. + +* New directives 'logchange' and 'mailonchange' to log to syslog or + email to a specific address respectively if chronyd detects a clock + offset exceeding a defined threshold. + +* Added capability to log all client/peer NTP accesses and command + accesses (can be turned off with conf file directive 'noclientlog'). + Added 'clients' command to chronyc to display this data. + +* Improved manual mode to use robust regression rather than 2 point + fit. + +* Added 'manual list' and 'manual delete' commands to chronyc to + allow display of entered timestamps and discretionary deletion of + outliers. + +* If host goes unsynchronised the dummy IP address 0.0.0.0 is detected + to avoid attempting a reverse name lookup (to stop dial on demand IP + links from being started) + +* Changed chronyc/chronyd protocol so messages are now all variable + length. Saves on network bandwidth particularly for large replies + from chronyd to chronyc (to support the clients command). + +* Added bindaddress directive to configuration file, to give + additional control over limiting which hosts can access the local + server. + +* Groundwork done for a port to Windows NT to compile with Cygwin + toolkit. chronyc works (to monitor another host). sys_winnt.c + needs finishing to use NT clock control API. Program structure + needs adapting to use Windows NT service functions, so it can be + started at boot time. Hopefully a Windows NT / Cygwin guru with + some spare time can take this port over :-) + +New in version 1.02 +=================== + +Bug fixes +--------- + +* Fix error messages in chronyc if daemon is not reachable. + +* Fix config file problem for 'allow all' and 'deny all' without a + trailing machine address. + +* Remove fatal failed assertion if command socket cannot be read from + in daemon. + +* Rewrote timezone handling for Linux real time clock, following + various reported problems related to daylight saving. + +Other changes/Enhancements +-------------------------- + +* Configure script recognizes BSD/386 and uses SunOS 4.1 driver for + it. + +* Log files now print date as day-month-year rather than as a day + number. Milliseconds removed from timestamps of logged data. + Banners included in file to give meanings of columns. + +* Only do 1 initial step (followed by a trimming slew) when + initialising from RTC on Linux (previously did 2 steps). + +New in version 1.01 +=================== + +Bug fixes +--------- + +* Handle timezone of RTC correctly with respect to daylight saving + time + +* Syntax check the chronyc 'local' command properly + +* Fixed assertion failed fault in median finder (used by RTC + regression fitting) + +Other changes/Enhancements +-------------------------- + +* Log selection of new NTP reference source to syslog. + +* Don't zero-pad IP address fields + +* Add new command to chronyc to allow logfiles to be cycled. + +* Extend allow/deny directive syntax in configuration file to so + directive can apply to all hosts on the Internet. + +* Tidy up printout of timestamps to make it clear they are in UTC + +* Make 'configure' check the processor type as well as the operating + system. diff --git a/README b/README new file mode 100644 index 0000000..4846ec8 --- /dev/null +++ b/README @@ -0,0 +1,243 @@ +This is the README for chrony. + +What is chrony? +=============== +Chrony is a pair of programs for maintaining the accuracy of computer +clocks. + +chronyd is a (background) daemon program that can be started at boot +time. This does most of the work. + +chronyc is a command-line interface program which can be used to +monitor chronyd's performance and to change various operating +parateters whilst it is running. + +chronyd's main function is to obtain measurements of the true (UTC) +time from one of several sources, and correct the system clock +accordingly. It also works out the rate at which the system clock +gains or loses time and uses this information to keep it accurate +between measurements from the reference. + +The reference time can be derived from either Network Time Protocol +(NTP) servers (preferred), or wristwatch-and-keyboard (via chronyc). +The main source of information about the Network Time Protocol is +http://www.eecis.udel.edu/~ntp. + +It is designed so that it can work on computers which only have +intermittent access to reference sources, for example computers which +use a dial-up account to access the Internet. Of course, it will work +on computers with permanent connections too. + +In addition, the Linux 2.0.x (for x >= 32), 2.2.x and 2.3.x versions +can monitor the system's real time clock performance, so the system +can maintain accurate time even across reboots. + +Typical accuracies available between 2 machines are + +On an ethernet LAN : 100-200 microseconds, often much better +On a V32bis dial-up modem connection : 10's of milliseconds (from one +session to the next) + +chronyd can also operate as an RFC1305-compatible NTP server and peer. + + +What will chrony run on? +======================== + +Chrony can be successfully built and run on + +1. Linux v1.2.13, v2.0.x, 2.1.x (partially), 2.2.x, 2.3.x, 2.4.x (i386). +Real time clock support is limited to 2.0.32 onwards and to 2.2, 2.3 and +2.4 series only. PowerPC is also known to be supported. + +2. Solaris 2.5/2.5.1/2.6/2.7/2.8 (various platforms) + +3. SunOS 4.1.4 (Sparc 2 and Sparc 20) + +4. BSD/386 v1.1 has been reported to work using the SunOS 4.1 driver. + +5. NetBSD. + +Any other system will require a porting exercise. You would need to +start from one of the existing system-specific drivers and look into +the quirks of certain system calls and the kernel on your target +system. (This is described in the manual). + +How do I set it up? +=================== + +The file INSTALL gives instructions. On supported systems the +compilation process should be automatic. + +You will need an ANSI C compiler -- gcc is recommended. Versions +2.7.2/2.7.2.2 are known to work. + +The manual (in texinfo and text formats) describes how to set the +software up for the less straightforward cases. + +What documentation is there? +============================ + +A manual is supplied in Texinfo format (chrony.texi) and +ready-formatted plain text (chrony.txt) in the distribution. + +There is also information available on the chrony web pages, accessible +through the URL + + http://chrony.sunsite.dk/ + +What can chrony not do? +======================= + +Compared to the `reference' RFC1305 implementation xntpd, chronyd does +not support hardware reference clocks, leap seconds or broadcast +modes. + +Where are new versions announced? +================================= + +There is a low volume mailing list where new versions and other +important news relating to chrony is announced. You can join this list +by sending mail to + +chrony-announce-subscribe@sunsite.dk + +These messages will be copied to chrony-users (see below). I also try +to announce new versions on Freshmeat (http://freshmeat.net/). + +I don't reliably announce via news any more - I don't tend to keep up +with news as I haven't enough time. + +How can I get support for chrony? +and where can I discuss new features, possible bugs etc? +======================================================== + +There are 3 mailing lists relating to chrony. chrony-announce was +mentioned above. chrony-users is a users' discussion list, e.g. for +general questions and answers about using chrony. chrony-dev is a more +technical list, e.g. for discussing how new features should be +implemented, exchange of information between developers etc. To +subscribe to either of these lists, send an empty message to + +chrony-users-subscribe@sunsite.dk +or +chrony-dev-subscribe@sunsite.dk + +as applicable. + +Note that due to family commitments (a 3 year-old and a 1 year-old), I +no longer have the time to give to supporting chrony that I once had. +Therefore, the chrony-users list should be your main route for support, +rather than mailing me directly. Even if it's me that responds to your +question on the list, at least *ALL* subscribers then benefit from +seeing the discussion, rather than me taking up lots of time on +supporting people on a one-to-one basis. If you do mail me directly, +don't be surprised if I cc: the response to the mailing list. + +But how can I contact the author if I need to? +============================================== + +You can email me at . If that fails, you could try to +find me through one of the mailing lists. It would be nice if: + +- you include the word 'chrony' in the subject line (so my mail reader +can sort my mail by topic) + +- you don't send complete log files, encoded binaries etc, without +editing such material down to just the relevant bits - a few tens of +lines at most. (My dial-up connection handles large messages rather +slowly ...). + +Acknowledgements +================ + +The following people have provided patches and other major contributions +to the program : + +Andrew Bishop + Fixes for bugs in logging when in daemon mode + Fixes for compiler warnings + Robustness improvements for drift file + Improve installation (directory checking etc) + Entries in contrib directory + Improvements to 'sources' and 'sourcestats' output from chronyc + Improvements to documentation + Investigation of required dosynctodr behaviour for various Solaris + versions. + +Stephan I. Boettcher + Entries in contrib directory + +Erik Bryer + Entries in contrib directory + +Paul Elliott + DNSchrony (in contrib directory), a tool for handling NTP servers + with variable IP addresses. + +Mike Fleetwood + Fixes for compiler warnings + +Alexander Gretencord + Changes to installation directory system to make it easier for + package builders. + +Walter Haidinger + Providing me with login access to a Linux installation where v1.12 + wouldn't compile, so I could develop the fixes for v1.13. Also, for + providing the disc space so I can keep an independent backup of the + sources. + +Juergen Hannken-Illjes + Port to NetBSD + +John Hasler + Changes to support 64 bit machines (i.e. those where + sizeof(unsigned long) > 4) + +Liam Hatton + Advice on configuring for Linux on PPC + +Jachym Holecek + Patch to make Linux real time clock work with devfs + +Jim Knoble + Fixes for compiler warnings + +Antti Jrvinen + Advice on configuring for BSD/386 + +Victor Moroz + Patch to support Linux with HZ!=100 + +Kalle Olavi Niemitalo + acquisitionport support + +Frank Otto + Handling arbitrary HZ values + +Andreas Piesk + Patch to make chronyc use the readline library if available + +Wolfgang Weisselberg + Entries in contrib directory + +Ralf Wildenhues + Many robustness and security improvements + +Ulrich Windl for the + Providing me with information about the Linux 2.2 kernel + functionality compared to 2.0. + +Doug Woodward + Advice on configuring for Solaris 2.8 on x86 + +Many other people have contributed bug reports and suggestions. I'm +sorry I can't identify all of you individually. + +Version control information +=========================== + +$Header: /cvs/src/chrony/README,v 1.27 2003/07/01 20:56:23 richard Exp $ + +vim:tw=72 diff --git a/acquire.c b/acquire.c new file mode 100644 index 0000000..26e8708 --- /dev/null +++ b/acquire.c @@ -0,0 +1,688 @@ +/* + $Header: /cvs/src/chrony/acquire.c,v 1.23 2003/03/27 23:45:47 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Processing to perform the equivalent of what ntpdate does. That is, + make a rapid-fire set of measurements to a designated set of + sources, and step or slew the local clock to bring it into line with + the result. + + This is kept completely separate of the main chronyd processing, by + using a separate socket for sending/receiving the measurement + packets. That way, ntp_core.c can be kept completely independent of + this functionality. + + A few of the finer points of how to construct valid RFC1305 packets + and validate responses for this case have been cribbed from the + ntpdate source. + + */ + +#include "sysincl.h" + +#include "acquire.h" +#include "memory.h" +#include "sched.h" +#include "local.h" +#include "logging.h" +#include "ntp.h" +#include "util.h" +#include "main.h" +#include "conf.h" + +/* ================================================== */ + +/* Interval between firing off the first sample to successive sources */ +#define INTER_SOURCE_START (0.2) + +#define MAX_SAMPLES 8 + +#define MAX_DEAD_PROBES 4 +#define N_GOOD_SAMPLES 4 + +#define RETRANSMISSION_TIMEOUT (1.0) + +typedef struct { unsigned long ip_addr; + int sanity; /* Flag indicating whether source + looks sane or not */ + int n_dead_probes; /* Number of probes sent to the server + since a good one */ + int n_samples; /* Number of samples accumulated */ + int n_total_samples; /* Total number of samples received + including useless ones */ + double offsets[MAX_SAMPLES]; /* In seconds, positive means local + clock is fast of reference */ + double root_distances[MAX_SAMPLES]; /* in seconds */ + double inter_lo; /* Low end of estimated range of offset */ + double inter_hi; /* High end of estimated range of offset */ + + NTP_int64 last_tx; /* Transmit timestamp in last packet + transmitted to source. */ + + int timer_running; + SCH_TimeoutID timeout_id; +} SourceRecord; + +static SourceRecord *sources; +static int n_sources; +static int n_started_sources; +static int n_completed_sources; + +static int init_slew_threshold = -1; + +static int sock_fd = -1; + +/* ================================================== */ + +static void (*saved_after_hook)(void *) = NULL; +static void *saved_after_hook_anything = NULL; + +/* ================================================== */ + +typedef struct { + double offset; + enum {LO, HIGH} type; + int index; +} Endpoint; + +typedef struct { + double lo; + double hi; +} Interval; + +/* ================================================== */ + +static void read_from_socket(void *anything); +static void transmit_timeout(void *x); +static void wind_up_acquisition(void); +static void start_source_timeout_handler(void *not_used); + +/* ================================================== */ + +static SCH_TimeoutID source_start_timeout_id; + +/* ================================================== */ + +void +ACQ_Initialise(void) +{ + return; +} + + +/* ================================================== */ + +void +ACQ_Finalise(void) +{ + return; +} + +/* ================================================== */ + +static void +initialise_io(void) +{ + unsigned short port_number = CNF_GetAcquisitionPort(); + + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + + if (sock_fd < 0) { + LOG_FATAL(LOGF_Acquire, "Could not open socket : %s", strerror(errno)); + } + + if (port_number == 0) { + /* Don't bother binding this socket - we're not fussed what port + number it gets */ + } else { + struct sockaddr_in my_addr; + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(port_number); + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + LOG(LOGS_ERR, LOGF_Acquire, "Could not bind socket : %s\n", strerror(errno)); + /* but keep running */ + } + } + + SCH_AddInputFileHandler(sock_fd, read_from_socket, NULL); + +} + +/* ================================================== */ + +static void +finalise_io(void) +{ + if (sock_fd >= 0) { + SCH_RemoveInputFileHandler(sock_fd); + close(sock_fd); + } + + return; +} + +/* ================================================== */ + +static void +probe_source(SourceRecord *src) +{ + NTP_Packet pkt; + int version = 3; + NTP_Mode my_mode = MODE_CLIENT; + struct timeval cooked; + double local_time_err; + struct sockaddr_in his_addr; + +#if 0 + printf("Sending probe to %08lx sent=%d samples=%d\n", src->ip_addr, src->n_probes_sent, src->n_samples); +#endif + + pkt.lvm = (((LEAP_Unsynchronised << 6) & 0xc0) | + ((version << 3) & 0x38) | + ((my_mode) & 0x7)); + + pkt.stratum = 0; + pkt.poll = 4; + pkt.precision = -6; /* as ntpdate */ + pkt.root_delay = double_to_int32(1.0); /* 1 second */ + pkt.root_dispersion = double_to_int32(1.0); /* likewise */ + pkt.reference_id = 0UL; + pkt.reference_ts.hi = 0; /* Set to 0 */ + pkt.reference_ts.lo = 0; /* Set to 0 */ + pkt.originate_ts.hi = 0; /* Set to 0 */ + pkt.originate_ts.lo = 0; /* Set to 0 */ + pkt.receive_ts.hi = 0; /* Set to 0 */ + pkt.receive_ts.lo = 0; /* Set to 0 */ + + /* And do transmission */ + his_addr.sin_addr.s_addr = htonl(src->ip_addr); + his_addr.sin_port = htons(123); /* Fixed for now */ + his_addr.sin_family = AF_INET; + + LCL_ReadCookedTime(&cooked, &local_time_err); + UTI_TimevalToInt64(&cooked, &pkt.transmit_ts); + + if (sendto(sock_fd, (void *) &pkt, NTP_NORMAL_PACKET_SIZE, + 0, + (struct sockaddr *) &his_addr, sizeof(his_addr)) < 0) { + LOG(LOGS_WARN, LOGF_Acquire, "Could not send to %s : %s", + UTI_IPToDottedQuad(src->ip_addr), + strerror(errno)); + } + + src->last_tx = pkt.transmit_ts; + + ++(src->n_dead_probes); + src->timer_running = 1; + src->timeout_id = SCH_AddTimeoutByDelay(RETRANSMISSION_TIMEOUT, transmit_timeout, (void *) src); + + return; +} + +/* ================================================== */ + +static void +transmit_timeout(void *x) +{ + SourceRecord *src = (SourceRecord *) x; + + src->timer_running = 0; + +#if 0 + printf("Timeout expired for server %08lx\n", src->ip_addr); +#endif + + if (src->n_dead_probes < MAX_DEAD_PROBES) { + probe_source(src); + } else { + /* Source has croaked or is taking too long to respond */ + ++n_completed_sources; + if (n_completed_sources == n_sources) { + wind_up_acquisition(); + } + } +} + +/* ================================================== */ + +#define MAX_STRATUM 15 + +static void +process_receive(NTP_Packet *msg, SourceRecord *src, struct timeval *now) +{ + + unsigned long lvm; + int leap, version, mode; + double root_delay, root_dispersion; + double total_root_delay, total_root_dispersion, total_root_distance; + + struct timeval local_orig, local_average, remote_rx, remote_tx, remote_average; + double remote_interval, local_interval; + double delta, theta, epsilon; + int n; + + /* Most of the checks are from ntpdate */ + + /* Need to do something about authentication */ + + lvm = msg->lvm; + leap = (lvm >> 6) & 0x3; + version = (lvm >> 3) & 0x7; + mode = lvm & 0x7; + + if ((leap == LEAP_Unsynchronised) || + (version != 3) || + (mode != MODE_SERVER && mode != MODE_PASSIVE)) { + return; + } + + if (msg->stratum > MAX_STRATUM) { + return; + } + + /* Check whether server is responding to our last request */ + if ((msg->originate_ts.hi != src->last_tx.hi) || + (msg->originate_ts.lo != src->last_tx.lo)) { + return; + } + + /* Check that the server is sane */ + if (((msg->originate_ts.hi == 0) && (msg->originate_ts.lo == 0)) || + ((msg->receive_ts.hi == 0) && (msg->receive_ts.lo) == 0)) { + return; + } + + root_delay = int32_to_double(msg->root_delay); + root_dispersion = int32_to_double(msg->root_dispersion); + + UTI_Int64ToTimeval(&src->last_tx, &local_orig); + UTI_Int64ToTimeval(&msg->receive_ts, &remote_rx); + UTI_Int64ToTimeval(&msg->transmit_ts, &remote_tx); + UTI_AverageDiffTimevals(&remote_rx, &remote_tx, &remote_average, &remote_interval); + UTI_AverageDiffTimevals(&local_orig, now, &local_average, &local_interval); + + delta = local_interval - remote_interval; + + /* Defined as positive if we are fast. Note this sign convention is + opposite to that used in ntp_core.c */ + + UTI_DiffTimevalsToDouble(&theta, &local_average, &remote_average); + + /* Could work out epsilon - leave till later */ + epsilon = 0.0; + + total_root_delay = fabs(delta) + root_delay; + total_root_dispersion = epsilon + root_dispersion; + total_root_distance = 0.5 * fabs(total_root_delay) + total_root_dispersion; + + n = src->n_samples; +#if 0 + printf("Sample %d theta=%.6f delta=%.6f root_del=%.6f root_disp=%.6f root_dist=%.6f\n", + n, theta, delta, total_root_delay, total_root_dispersion, total_root_distance); +#endif + src->offsets[n] = theta; + src->root_distances[n] = total_root_distance; + ++(src->n_samples); + +} + +/* ================================================== */ + +static void +read_from_socket(void *anything) +{ + int status; + ReceiveBuffer msg; + struct sockaddr_in his_addr; + int his_addr_len; + int flags; + int message_length; + unsigned long remote_ip; + int i, ok; + struct timeval now; + double local_time_err; + SourceRecord *src; + + flags = 0; + message_length = sizeof(msg); + his_addr_len = sizeof(his_addr); + + /* Get timestamp */ + LCL_ReadCookedTime(&now, &local_time_err); + + status = recvfrom (sock_fd, (char *)&msg, message_length, flags, + (struct sockaddr *) &his_addr, &his_addr_len); + + if (status < 0) { + LOG(LOGS_WARN, LOGF_Acquire, "Error reading from socket, %s", strerror(errno)); + return; + } + + remote_ip = ntohl(his_addr.sin_addr.s_addr); + +#if 0 + printf("Got message from %08lx\n", remote_ip); +#endif + + /* Find matching host */ + ok = 0; + for (i=0; in_total_samples; + + src->n_dead_probes = 0; /* reset this when we actually receive something */ + + /* If we got into this function, we know the retransmission timeout has not + expired for the source */ + if (src->timer_running) { + SCH_RemoveTimeout(src->timeout_id); + src->timer_running = 0; + } + + process_receive(&msg.ntp_pkt, src, &now); + + /* Check if server done and requeue timeout */ + if ((src->n_samples >= N_GOOD_SAMPLES) || + (src->n_total_samples >= MAX_SAMPLES)) { + ++n_completed_sources; +#if 0 + printf("Source %08lx completed\n", src->ip_addr); +#endif + if (n_completed_sources == n_sources) { + wind_up_acquisition(); + } + } else { + + /* Send the next probe */ + probe_source(src); + + } + } + +} + +/* ================================================== */ + +static void +start_next_source(void) +{ + probe_source(sources + n_started_sources); +#if 0 + printf("Trying to start source %08lx\n", sources[n_started_sources].ip_addr); +#endif + n_started_sources++; + + if (n_started_sources < n_sources) { + source_start_timeout_id = SCH_AddTimeoutByDelay(INTER_SOURCE_START, start_source_timeout_handler, NULL); + } +} + +/* ================================================== */ + +static int +endpoint_compare(const void *a, const void *b) +{ + const Endpoint *aa = (const Endpoint *) a; + const Endpoint *bb = (const Endpoint *) b; + + if (aa->offset < bb->offset) { + return -1; + } else if (aa->offset > bb->offset) { + return +1; + } else { + return 0; + } +} + +/* ================================================== */ + +static void +process_measurements(void) +{ + + SourceRecord *s; + Endpoint *eps; + int i, j; + int n_sane_sources; + double lo, hi; + double inter_lo, inter_hi; + + int depth; + int best_depth; + int n_at_best_depth; + Interval *intervals; + double estimated_offset; + int index1, index2; + + n_sane_sources = 0; + + /* First, get a consistent interval for each source. Those for + which this is not possible are considered to be insane. */ + + for (i=0; in_samples == 0) { + s->sanity = 0; + } else { + s->sanity = 1; /* so far ... */ + lo = s->offsets[0] - s->root_distances[0]; + hi = s->offsets[0] + s->root_distances[0]; + inter_lo = lo; + inter_hi = hi; + for (j=1; jn_samples; j++) { + lo = s->offsets[j] - s->root_distances[j]; + hi = s->offsets[j] + s->root_distances[j]; + if ((inter_hi <= lo) || (inter_lo >= hi)) { + /* Oh dear, we won't get an interval for this source */ + s->sanity = 0; + break; + } else { + inter_lo = (lo < inter_lo) ? inter_lo : lo; + inter_hi = (hi > inter_hi) ? inter_hi : hi; + } + } + if (s->sanity) { + s->inter_lo = inter_lo; + s->inter_hi = inter_hi; + } + } + + if (s->sanity) { + ++n_sane_sources; + } + + } + + /* Now build the endpoint list, similar to the RFC1305 clock + selection algorithm. */ + eps = MallocArray(Endpoint, 2*n_sane_sources); + intervals = MallocArray(Interval, n_sane_sources); + + j = 0; + for (i=0; isanity) { + eps[j].offset = s->inter_lo; + eps[j].type = LO; + eps[j].index = i; + eps[j+1].offset = s->inter_hi; + eps[j+1].type = HIGH; + eps[j+1].index = i; + j += 2; + } + } + + qsort(eps, 2*n_sane_sources, sizeof(Endpoint), endpoint_compare); + + /* Now do depth searching algorithm */ + n_at_best_depth = best_depth = depth = 0; + for (i=0; i<2*n_sane_sources; i++) { + +#if 0 + fprintf(stderr, "Endpoint type %s source index %d [ip=%08lx] offset=%.6f\n", + (eps[i].type == LO) ? "LO" : "HIGH", + eps[i].index, + sources[eps[i].index].ip_addr, + eps[i].offset); +#endif + + switch (eps[i].type) { + case LO: + depth++; + if (depth > best_depth) { + best_depth = depth; + n_at_best_depth = 0; + intervals[0].lo = eps[i].offset; + } else if (depth == best_depth) { + intervals[n_at_best_depth].lo = eps[i].offset; + } else { + /* Nothing to do */ + } + + break; + + case HIGH: + if (depth == best_depth) { + intervals[n_at_best_depth].hi = eps[i].offset; + n_at_best_depth++; + } + + depth--; + + break; + + } + } + + if (best_depth > 0) { + if ((n_at_best_depth % 2) == 1) { + index1 = (n_at_best_depth - 1) / 2; + estimated_offset = 0.5 * (intervals[index1].lo + intervals[index1].hi); + } else { + index2 = (n_at_best_depth / 2); + index1 = index2 - 1; + estimated_offset = 0.5 * (intervals[index1].lo + intervals[index2].hi); + } + + + /* Apply a step change to the system clock. As per sign + convention in local.c and its children, a positive offset means + the system clock is fast of the reference, i.e. it needs to be + stepped backwards. */ + + if (fabs(estimated_offset) > (double) init_slew_threshold) { + LOG(LOGS_INFO, LOGF_Acquire, "System's initial offset : %.6f seconds %s of true (step)", + fabs(estimated_offset), + (estimated_offset >= 0) ? "fast" : "slow"); + LCL_ApplyStepOffset(estimated_offset); + } else { + LOG(LOGS_INFO, LOGF_Acquire, "System's initial offset : %.6f seconds %s of true (slew)", + fabs(estimated_offset), + (estimated_offset >= 0) ? "fast" : "slow"); + LCL_AccumulateOffset(estimated_offset); + } + + } else { + LOG(LOGS_WARN, LOGF_Acquire, "No intersecting endpoints found"); + } + + Free(intervals); + Free(eps); + +} + +/* ================================================== */ + +static void +wind_up_acquisition(void) +{ + + /* Now process measurements */ + process_measurements(); + + Free(sources); + + finalise_io(); + + if (saved_after_hook) { + (saved_after_hook)(saved_after_hook_anything); + } + +} + +/* ================================================== */ + +static void +start_source_timeout_handler(void *not_used) +{ + + start_next_source(); +} + +/* ================================================== */ + +void +ACQ_StartAcquisition(int n, unsigned long *ip_addrs, int threshold, void (*after_hook)(void *), void *anything) +{ + + int i; + + saved_after_hook = after_hook; + saved_after_hook_anything = anything; + + init_slew_threshold = threshold; + + n_started_sources = 0; + n_completed_sources = 0; + n_sources = n; + sources = MallocArray(SourceRecord, n); + + for (i=0; i> (32-NBITS)) & ((1UL<base.state = DENY; + result->base.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; iextended))[i]); + close_node(child_node); + } + Free(node->extended); + node->extended = NULL; + } + + return; +} + + +/* ================================================== */ +/* 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 = MallocNew(ExtendedTable); + + for (i=0; iextended))[i]); + child_node->state = AS_PARENT; + child_node->extended = NULL; + } + } + return; +} + +/* ================================================== */ + +static ADF_Status +set_subnet(TableNode *start_node, + unsigned long ip, + int subnet_bits, + State new_state, + int delete_children) +{ + int bits_to_go; + unsigned long residual; + unsigned long subnet; + TableNode *node; + + bits_to_go = subnet_bits; + residual = ip; + node = start_node; + + if ((subnet_bits < 0) || + (subnet_bits > 32)) { + + return ADF_BADSUBNET; + + } else { + + if ((bits_to_go & (NBITS-1)) == 0) { + + while (bits_to_go > 0) { + subnet = get_subnet(residual); + residual = get_residual(residual); + if (!(node->extended)) { + open_node(node); + } + node = &((*(node->extended))[subnet]); + bits_to_go -= 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(residual); + residual = get_residual(residual); + if (!(node->extended)) { + open_node(node); + } + node = &((*(node->extended))[subnet]); + bits_to_go -= NBITS; + } + + /* How many subnet entries to set : 1->8, 2->4, 3->2 */ + N = 1 << (NBITS-bits_to_go); + subnet = get_subnet(residual); + if (!(node->extended)) { + open_node(node); + } + + for (i=subnet, j=0; jextended))[i]); + if (delete_children) { + close_node(this_node); + } + this_node->state = new_state; + } + } + + return ADF_SUCCESS; + } + +} + +/* ================================================== */ + +ADF_Status +ADF_Allow(ADF_AuthTable table, + unsigned long ip, + int subnet_bits) +{ + return set_subnet(&(table->base), ip, subnet_bits, ALLOW, 0); +} + +/* ================================================== */ + + +ADF_Status +ADF_AllowAll(ADF_AuthTable table, + unsigned long ip, + int subnet_bits) +{ + return set_subnet(&(table->base), ip, subnet_bits, ALLOW, 1); +} + +/* ================================================== */ + +ADF_Status +ADF_Deny(ADF_AuthTable table, + unsigned long ip, + int subnet_bits) +{ + return set_subnet(&(table->base), ip, subnet_bits, DENY, 0); +} + +/* ================================================== */ + +ADF_Status +ADF_DenyAll(ADF_AuthTable table, + unsigned long ip, + int subnet_bits) +{ + return set_subnet(&(table->base), ip, subnet_bits, DENY, 1); +} + +/* ================================================== */ + +void +ADF_DestroyTable(ADF_AuthTable table) +{ + close_node(&(table->base)); + Free(table); +} + +/* ================================================== */ + +static int +check_ip_in_node(TableNode *start_node, unsigned long ip) +{ + unsigned long residual, subnet; + int result = 0; + int finished = 0; + TableNode *node; + State state=DENY; + + node = start_node; + residual = ip; + + do { + if (node->state != AS_PARENT) { + state = node->state; + } + if (node->extended) { + subnet = get_subnet(residual); + residual = get_residual(residual); + node = &((*(node->extended))[subnet]); + } 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, + unsigned long ip) +{ + + return check_ip_in_node(&(table->base), ip); + +} + +/* ================================================== */ + +#if defined TEST + +static void print_node(TableNode *node, unsigned long addr, int shift, int subnet_bits) +{ + unsigned long new_addr; + int i; + TableNode *sub_node; + + for (i=0; i> 24) & 255), + ((addr >> 16) & 255), + ((addr >> 8) & 255), + ((addr ) & 255), + subnet_bits, + (node->state == ALLOW) ? "allow" : + (node->state == DENY) ? "deny" : "as parent"); + if (node->extended) { + for (i=0; i<16; i++) { + sub_node = &((*(node->extended))[i]); + new_addr = addr | ((unsigned long) i << shift); + print_node(sub_node, new_addr, shift - 4, subnet_bits + 4); + } + } + return; +} + + +static void print_table(ADF_AuthTable table) +{ + unsigned long addr = 0; + int shift = 28; + int subnet_bits = 0; + + print_node(&table->base, addr, shift, subnet_bits); + return; +} + +/* ================================================== */ + +int main (int argc, char **argv) +{ + ADF_AuthTable table; + table = ADF_CreateTable(); + + ADF_Allow(table, 0x7e800000, 9); + ADF_Deny(table, 0x7ecc0000, 14); + /* ADF_Deny(table, 0x7f000001, 32); */ + /* ADF_Allow(table, 0x7f000000, 8); */ + + print_table(table); + + ADF_DestroyTable(table); + return 0; +} + + + +#endif /* defined TEST */ + + + + diff --git a/addrfilt.h b/addrfilt.h new file mode 100644 index 0000000..f96f0fb --- /dev/null +++ b/addrfilt.h @@ -0,0 +1,77 @@ +/* + $Header: /cvs/src/chrony/addrfilt.h,v 1.6 2002/02/28 23:27:08 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Module for providing an authorisation filter on IP addresses + */ + +#ifndef GOT_ADDRFILT_H +#define GOT_ADDRFILT_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, + unsigned long 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, + unsigned long 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, + unsigned long 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, + unsigned long 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, + unsigned long ip); + +#endif /* GOT_ADDRFILT_H */ diff --git a/broadcast.c b/broadcast.c new file mode 100644 index 0000000..be217e7 --- /dev/null +++ b/broadcast.c @@ -0,0 +1,159 @@ +/* + $Header: /cvs/src/chrony/broadcast.c,v 1.3 2002/02/28 23:27:08 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Deal with broadcast server functions. + */ + +#include "sysincl.h" +#include "memory.h" + +#include "addressing.h" +#include "broadcast.h" +#include "sched.h" +#include "ntp.h" +#include "local.h" +#include "reference.h" +#include "util.h" +#include "ntp_io.h" + +typedef struct { + NTP_Remote_Address addr; + int interval; +} Destination; +static Destination *destinations = 0; +static int n_destinations = 0; +static int max_destinations = 0; + +void +BRD_Initialise(void) +{ + return; /* Nothing to do */ +} + +/* ================================================== */ + +void +BRD_Finalise(void) +{ + return; /* Nothing to do */ +} + +/* ================================================== */ +/* This is a cut-down version of what transmit_packet in ntp_core.c does */ + +static void +timeout_handler(void *arbitrary) +{ + Destination *d = (Destination *) arbitrary; + NTP_Packet message; + /* Parameters read from reference module */ + int version; + int leap; + int are_we_synchronised, our_stratum; + NTP_Leap leap_status; + unsigned long our_ref_id; + struct timeval our_ref_time; + double our_root_delay, our_root_dispersion; + double local_time_err; + struct timeval local_transmit; + + version = 3; + + LCL_ReadCookedTime(&local_transmit, &local_time_err); + REF_GetReferenceParams(&local_transmit, + &are_we_synchronised, &leap_status, + &our_stratum, + &our_ref_id, &our_ref_time, + &our_root_delay, &our_root_dispersion); + + + if (are_we_synchronised) { + leap = (int) leap_status; + } else { + leap = 3; + } + + message.lvm = ((leap << 6) &0xc0) | ((version << 3) & 0x38) | (MODE_BROADCAST & 0x07); + message.stratum = our_stratum; + message.poll = 6; /* FIXME: what should this be? */ + message.precision = LCL_GetSysPrecisionAsLog(); + + /* If we're sending a client mode packet and we aren't synchronized yet, + we might have to set up artificial values for some of these parameters */ + message.root_delay = double_to_int32(our_root_delay); + message.root_dispersion = double_to_int32(our_root_dispersion); + + message.reference_id = htonl((NTP_int32) our_ref_id); + + /* Now fill in timestamps */ + UTI_TimevalToInt64(&our_ref_time, &message.reference_ts); + message.originate_ts.hi = 0UL; + message.originate_ts.lo = 0UL; + message.receive_ts.hi = 0UL; + message.receive_ts.lo = 0UL; + + LCL_ReadCookedTime(&local_transmit, &local_time_err); + UTI_TimevalToInt64(&local_transmit, &message.transmit_ts); + NIO_SendNormalPacket(&message, &d->addr); + + /* Requeue timeout. Don't care if interval drifts gradually, so just do it + * at the end. */ + SCH_AddTimeoutInClass((double) d->interval, 1.0, + SCH_NtpBroadcastClass, + timeout_handler, (void *) d); + + +} + +/* ================================================== */ + +void +BRD_AddDestination(unsigned long addr, unsigned short port, int interval) +{ + if (max_destinations == n_destinations) { + /* Expand array */ + max_destinations += 8; + if (destinations) { + destinations = ReallocArray(Destination, max_destinations, destinations); + } else { + destinations = MallocArray(Destination, max_destinations); + } + } + + destinations[n_destinations].addr.ip_addr = addr; + destinations[n_destinations].addr.port = port; + destinations[n_destinations].interval = interval; + + SCH_AddTimeoutInClass((double) interval, 1.0, + SCH_NtpBroadcastClass, + timeout_handler, (void *)(destinations + n_destinations)); + + ++n_destinations; + +} + + diff --git a/broadcast.h b/broadcast.h new file mode 100644 index 0000000..01013bc --- /dev/null +++ b/broadcast.h @@ -0,0 +1,39 @@ +/* + $Header: /cvs/src/chrony/broadcast.h,v 1.2 2002/02/28 23:27:08 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Deal with broadcast server functions. + */ + +#ifndef GOT_BROADCAST_H +#define GOT_BROADCAST_H + +extern void BRD_Initialise(void); +extern void BRD_Finalise(void); +extern void BRD_AddDestination(unsigned long addr, unsigned short port, int interval); + +#endif /* GOT_BROADCAST_H */ + diff --git a/build_kit b/build_kit new file mode 100755 index 0000000..3067318 --- /dev/null +++ b/build_kit @@ -0,0 +1,24 @@ +#!/usr/bin/env perl +# $Header: /cvs/src/chrony/build_kit,v 1.13 2003/01/12 23:50:54 richard Exp $ +# Perl script for building a release + +chmod 0755, "configure"; + +# Construct chrony.spec file +$version = $ARGV[0] || die "No version on command line"; +open (IN, "chrony.spec"); +while () { + s/\@\@VERSION\@\@/$version/; + print OUT; +} +close (IN); +close (OUT); + +unlink "chrony.spec.sample"; + +# Requires the makeinfo from texinfo v4 +system("makeinfo --no-headers --number-sections -o chrony.txt chrony.texi"); +unlink("build_kit"); +unlink("LICINS"); + diff --git a/candm.h b/candm.h new file mode 100644 index 0000000..e411fc3 --- /dev/null +++ b/candm.h @@ -0,0 +1,594 @@ +/* + $Header: /cvs/src/chrony/candm.h,v 1.39 2003/04/01 20:54:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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" + +/* 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 N_REQUEST_TYPES 45 + +/* Special utoken value used to log on with first exchange being the + password. (This time value has long since gone by) */ +#define SPECIAL_UTOKEN 0x10101010 + +/* 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 { + uint32_t mask; + uint32_t address; + int32_t EOR; +} REQ_Online; + +typedef struct { + uint32_t mask; + uint32_t address; + int32_t EOR; +} REQ_Offline; + +typedef struct { + uint32_t mask; + uint32_t address; + int32_t n_good_samples; + int32_t n_total_samples; + int32_t EOR; +} REQ_Burst; + +typedef struct { + uint32_t address; + int32_t new_minpoll; + int32_t EOR; +} REQ_Modify_Minpoll; + +typedef struct { + uint32_t address; + int32_t new_maxpoll; + int32_t EOR; +} REQ_Modify_Maxpoll; + +typedef struct { + int32_t pad; + int32_t EOR; +} REQ_Dump; + +typedef struct { + uint32_t address; + int32_t new_max_delay; + int32_t EOR; +} REQ_Modify_Maxdelay; + +typedef struct { + uint32_t address; + int32_t new_max_delay_ratio; + int32_t EOR; +} REQ_Modify_Maxdelayratio; + +typedef struct { + int32_t new_max_update_skew; + int32_t EOR; +} REQ_Modify_Maxupdateskew; + +typedef struct { + struct timeval ts; + int32_t EOR; +} REQ_Logon; + +typedef struct { + struct timeval ts; + int32_t EOR; +} REQ_Settime; + +typedef struct { + int32_t on_off; + int32_t stratum; + int32_t EOR; +} REQ_Local; + +typedef struct { + int32_t option; + int32_t EOR; +} REQ_Manual; + +typedef struct { + int32_t EOR; +} REQ_N_Sources; + +typedef struct { + int32_t index; + int32_t EOR; +} REQ_Source_Data; + +typedef struct { + int32_t EOR; +} REQ_Rekey; + +typedef struct { + uint32_t ip; + int32_t subnet_bits; + int32_t EOR; +} REQ_Allow_Deny; + +typedef struct { + uint32_t ip; + int32_t EOR; +} REQ_Ac_Check; + +typedef struct { + uint32_t ip_addr; + uint32_t port; + int32_t minpoll; + int32_t maxpoll; + int32_t presend_minpoll; + int32_t online; + int32_t auto_offline; + uint32_t authkey; + int32_t max_delay; + int32_t max_delay_ratio; + int32_t EOR; +} REQ_NTP_Source; + +typedef struct { + uint32_t ip_addr; + int32_t EOR; +} REQ_Del_Source; + +typedef struct { + int32_t EOR; +} REQ_WriteRtc; + +typedef struct { + int32_t dfreq; + int32_t EOR; +} REQ_Dfreq; + +typedef struct { + int32_t sec; + int32_t usec; + int32_t EOR; +} REQ_Doffset; + +typedef struct { + int32_t EOR; +} REQ_Tracking; + +typedef struct { + uint32_t index; + int32_t EOR; +} REQ_Sourcestats; + +typedef struct { + int32_t EOR; +} REQ_RTCReport; + +typedef struct { + int32_t EOR; +} REQ_TrimRTC; + +typedef struct { + int32_t EOR; +} REQ_CycleLogs; + +typedef struct { + uint32_t ip; + uint32_t bits_specd; +} REQ_SubnetsAccessed_Subnet; + +#define MAX_SUBNETS_ACCESSED 8 + +typedef struct { + uint32_t n_subnets; + REQ_SubnetsAccessed_Subnet subnets[MAX_SUBNETS_ACCESSED]; +} REQ_SubnetsAccessed; + +/* This is based on the response size rather than the + request size */ +#define MAX_CLIENT_ACCESSES 16 + +typedef struct { + uint32_t n_clients; + uint32_t client_ips[MAX_CLIENT_ACCESSES]; +} REQ_ClientAccesses; + +typedef struct { + uint32_t first_index; + uint32_t n_indices; + int32_t EOR; +} REQ_ClientAccessesByIndex; + +typedef struct { + int32_t EOR; +} REQ_ManualList; + +typedef struct { + int32_t index; + int32_t EOR; +} REQ_ManualDelete; + +typedef struct { + int32_t EOR; +} REQ_MakeStep; + +typedef struct { + int32_t EOR; +} REQ_Activity; + +/* ================================================== */ + +#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) + + */ + +#define PROTO_VERSION_NUMBER 3 + +/* ================================================== */ + +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 utoken; /* Unique token per incarnation of daemon */ + uint32_t token; /* Command token (to prevent replay attack) */ + uint32_t auth[4]; /* MD5 authentication of the packet */ + + union { + 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_Maxupdateskew modify_maxupdateskew; + REQ_Logon logon; + REQ_Settime settime; + REQ_Local local; + REQ_Manual manual; + REQ_N_Sources n_sources; + REQ_Source_Data source_data; + REQ_Rekey rekey; + REQ_Allow_Deny allow_deny; + REQ_Ac_Check ac_check; + REQ_NTP_Source ntp_source; + REQ_Del_Source del_source; + REQ_WriteRtc writertc; + REQ_Dfreq dfreq; + REQ_Doffset doffset; + REQ_Tracking tracking; + REQ_Sourcestats sourcestats; + REQ_RTCReport rtcreport; + REQ_TrimRTC trimrtc; + REQ_CycleLogs cyclelogs; + REQ_SubnetsAccessed subnets_accessed; + REQ_ClientAccesses client_accesses; + REQ_ClientAccessesByIndex client_accesses_by_index; + REQ_ManualList manual_list; + REQ_ManualDelete manual_delete; + REQ_MakeStep make_step; + REQ_Activity activity; + } data; /* Command specific parameters */ + +} CMD_Request; + +/* ================================================== */ +/* Authority codes for command types */ + +#define PERMIT_OPEN 0 +#define PERMIT_LOCAL 1 +#define PERMIT_AUTH 2 + +/* ================================================== */ +/* These conversion utilities are used to convert between the internal + and the 'wire' representation of real quantities */ + +#define WIRE2REAL(x) ((double) ((int32_t) ntohl(x)) / 65536.0) +#define REAL2WIRE(x) (htonl((int32_t)(0.5 + 65536.0 * (x)))) + +/* ================================================== */ + +/* 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 N_REPLY_TYPES 13 + +/* 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 +#define STT_SOURCEALREADYKNOWN 11 +#define STT_TOOMANYSOURCES 12 +#define STT_NORTC 13 +#define STT_BADRTCFILE 14 +#define STT_INACTIVE 15 +#define STT_BADSAMPLE 16 + +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_SYNC 0 +#define RPY_SD_ST_UNREACH 1 +#define RPY_SD_ST_FALSETICKER 2 +#define RPY_SD_ST_JITTERY 3 +#define RPY_SD_ST_OTHER 4 + +typedef struct { + uint32_t ip_addr; + uint16_t poll; + uint16_t stratum; + uint16_t state; + uint16_t mode; + uint32_t since_sample; + int32_t orig_latest_meas; + int32_t latest_meas; + uint32_t latest_meas_err; + int32_t est_offset; + uint32_t est_offset_err; + int32_t resid_freq; + uint32_t resid_skew; + int32_t EOR; +} RPY_Source_Data; + +typedef struct { + uint32_t ref_id; + uint32_t stratum; + uint32_t ref_time_s; + uint32_t ref_time_us; + uint32_t current_correction_s; + uint32_t current_correction_us; + int32_t freq_ppm; + int32_t resid_freq_ppm; + int32_t skew_ppm; + int32_t root_delay; + int32_t root_dispersion; + int32_t EOR; +} RPY_Tracking; + +typedef struct { + uint32_t ip_addr; + uint32_t n_samples; + uint32_t n_runs; + uint32_t span_seconds; + uint32_t sd_us; + int32_t resid_freq_ppm; + int32_t skew_ppm; + int32_t EOR; +} RPY_Sourcestats; + +typedef struct { + uint32_t ref_time; + uint16_t n_samples; + uint16_t n_runs; + uint32_t span_seconds; + int32_t rtc_seconds_fast; + int32_t rtc_gain_rate_ppm; + int32_t EOR; +} RPY_Rtc; + +typedef struct { + uint32_t centiseconds; + int32_t dfreq_ppm; + int32_t new_afreq_ppm; + int32_t EOR; +} RPY_ManualTimestamp; + +typedef struct { + uint32_t ip; + uint32_t bits_specd; + uint32_t bitmap[8]; +} RPY_SubnetsAccessed_Subnet; + +typedef struct { + uint32_t n_subnets; + RPY_SubnetsAccessed_Subnet subnets[MAX_SUBNETS_ACCESSED]; +} RPY_SubnetsAccessed; + +typedef struct { + uint32_t ip; + uint32_t client_hits; + uint32_t peer_hits; + uint32_t cmd_hits_auth; + uint32_t cmd_hits_normal; + uint32_t cmd_hits_bad; + uint32_t last_ntp_hit_ago; + uint32_t last_cmd_hit_ago; +} RPY_ClientAccesses_Client; + +typedef struct { + uint32_t n_clients; + RPY_ClientAccesses_Client clients[MAX_CLIENT_ACCESSES]; +} RPY_ClientAccesses; + +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]; +} RPY_ClientAccessesByIndex; + +#define MAX_MANUAL_LIST_SAMPLES 32 + +typedef struct { + uint32_t when; + int32_t slewed_offset; + int32_t orig_offset; + int32_t residual; +} RPY_ManualListSample; + +typedef struct { + uint32_t n_samples; + RPY_ManualListSample samples[MAX_MANUAL_LIST_SAMPLES]; +} RPY_ManualList; + +typedef struct { + int32_t online; + int32_t offline; + int32_t burst_online; + int32_t burst_offline; + int32_t EOR; +} RPY_Activity; + +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 number; /* Which packet this is in reply sequence */ + uint16_t total; /* Number of replies to expect in this sequence */ + uint16_t pad1; /* Get up to 4 byte alignment */ + uint32_t sequence; /* Echo of client's sequence number */ + uint32_t utoken; /* Unique token per incarnation of daemon */ + uint32_t token; /* New command token (only if command was successfully + authenticated) */ + uint32_t auth[4]; /* MD5 authentication of the packet */ + + 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_SubnetsAccessed subnets_accessed; + RPY_ClientAccesses client_accesses; + RPY_ClientAccessesByIndex client_accesses_by_index; + RPY_ManualList manual_list; + RPY_Activity activity; + } data; /* Reply specific parameters */ + +} CMD_Reply; + +/* ================================================== */ + +#endif /* GOT_CANDM_H */ diff --git a/chrony.1 b/chrony.1 new file mode 100644 index 0000000..81e6ee2 --- /dev/null +++ b/chrony.1 @@ -0,0 +1,65 @@ +.TH CHRONY 1 "August 10, 2001" chrony "User's Manual" +.SH NAME +chrony \- programs for keeping computer clocks accurate + +.SH SYNOPSIS +\fBchronyc\fR [\fIOPTIONS\fR] + +\fBchronyd\fR [\fIOPTIONS\fR] + +.SH DESCRIPTION +\fBchrony\fR is a pair of programs for keeping computer clocks accurate. +\fIchronyd\fR is a background (daemon) program and \fIchronyc\fR is a +command-line interface to it. Time reference sources for chronyd can be +RFC1305 NTP servers, human (via keyboard and \fIchronyc\fR), or the computer's +real-time clock at boot time (Linux only). chronyd can determine the rate at +which the computer gains or loses time and compensate for it while no external +reference is present. Its use of NTP servers can be switched on and off +(through \fIchronyc\fR) to support computers with dial-up/intermittent access +to the Internet, and it can also act as an RFC1305-compatible NTP server. + +.SH USAGE +\fIchronyc\fR is a command-line interface program which can be used to +monitor \fIchronyd\fR's performance and to change various operating +parateters whilst it is running. + +\fIchronyd\fR's main function is to obtain measurements of the true (UTC) +time from one of several sources, and correct the system clock +accordingly. It also works out the rate at which the system clock +gains or loses time and uses this information to keep it accurate +between measurements from the reference. + +The reference time can be derived from either Network Time Protocol +(NTP) servers (preferred), or wristwatch-and-keyboard (via \fIchronyc\fR). +The main source of information about the Network Time Protocol is +\fIhttp://www.eecis.udel.edu/~ntp\fR. + +It is designed so that it can work on computers which only have +intermittent access to reference sources, for example computers which +use a dial-up account to access the Internet. Of course, it will work +on computers with permanent connections too. + +In addition, for Linux 2.0.x (for x >= 32) or 2.2 onwards, chronyd can monitor +the system's real time clock performance, so the system can maintain accurate +time even across reboots. + +Typical accuracies available between 2 machines are + +On an ethernet LAN : 100-200 microseconds, often much better +On a V32bis dial-up modem connection : 10's of milliseconds (from one +session to the next) + +\fIchronyd\fR can also operate as an RFC1305-compatible NTP server and peer. + +.SH "SEE ALSO" +.BR chronyc(1), +.BR chrony(1) + +.I http://chrony.sunsite.dk/ + +.SH AUTHOR +Richard Curnow + +This man-page was written by Jan Schaumann as part +of "The Missing Man Pages Project". Please see +\fIhttp://www.netmeister.org/misc/m2p2/index.html\fR for details. diff --git a/chrony.conf.5 b/chrony.conf.5 new file mode 100644 index 0000000..968662e --- /dev/null +++ b/chrony.conf.5 @@ -0,0 +1,49 @@ +.TH chrony.conf 5 "August 10, 2001" chrony "Configuration Files" +.SH NAME +chrony.conf \- chronyd configuration file + +.SH SYNOPSIS +.B /etc/chrony.conf + +.SH DESCRIPTION +\fIchrony\fR is a pair of programs for maintaining the accuracy of computer +clocks. \fIchronyd\fR is a background daemon program that can be started at +boot time. + +Assuming that you have found some servers, you need to set up a +configuration file to run \fIchrony\fR. The (compiled-in) default location +for this file is \fB/etc/chrony.conf\fR. Assuming that your ntp servers +are called `a.b.c' and `d.e.f', your \fBchrony.conf\fR file could contain +as a minimum + + server a.b.c + server d.e.f + server g.h.i + +However, you will probably want to include some of the other directives +described in detail in the documentation supplied with the distribution +(\fIchrony.txt\fR and \fIchrony.texi\fR). The following directives will be +particularly useful : `driftfile', `commandkey', `keyfile'. The smallest +useful configuration file would look something like + + server a.b.c + server d.e.f + server g.h.i + keyfile /etc/chrony.keys + commandkey 1 + driftfile /etc/chrony.drift + + +.SH "SEE ALSO" +.BR chrony(1), +.BR chronyc(1), +.BR chronyd(1) + +.I http://chrony.sunsite.dk/ + +.SH AUTHOR +Richard Curnow + +This man-page was written by Jan Schaumann as part of "The Missing +Man Pages Project". Please see \fIhttp://www.netmeister.org/misc/m2p2/index.html\fR +for details. diff --git a/chrony.lsm b/chrony.lsm new file mode 100644 index 0000000..1e65785 --- /dev/null +++ b/chrony.lsm @@ -0,0 +1,29 @@ +Begin3 +Title: chrony +Version: 1.18 +Entered-date: 01APR02 +Description: A pair of programs for keeping computer clocks accurate. + chronyd is a background (daemon) program and chronyc is a + command-line interface to it. Time reference sources for + chronyd can be RFC1305 NTP servers, human (via keyboard and + chronyc), and the computer's real-time clock at boot time + (Linux only). chronyd can determine the rate at which the + computer gains or loses time and compensate for it whilst no + external reference is present. chronyd's use of NTP servers + can be switched on and off (through chronyc) to support + computers with dial-up/intermittent access to the + Internet. chronyd can also act as an RFC1305-compatible NTP + server. +Keywords: time NTP RFC1305 RTC adjtime +Author: rc@rc0.org.uk (Richard Curnow) +Maintained-by: rc@rc0.org.uk (Richard Curnow) +Primary-site: sunsite.unc.edu /pub/Linux/system/admin/time + 295k chrony-1.18.tar.gz + 2k chrony.lsm +Platforms: Linux 2.0/2.1/2.2/2.3/2.4 (x86, powerpc) + Solaris 2.5/6/7/8, SunOS 4.1.4. (Sparc) + BSDI/386. + NetBSD + Solaris 2.8 (x86) +Copying-policy: GPL +End diff --git a/chrony.spec.sample b/chrony.spec.sample new file mode 100644 index 0000000..361617a --- /dev/null +++ b/chrony.spec.sample @@ -0,0 +1,52 @@ +Summary: An NTP client/server +Name: chrony +Version: @@VERSION@@ +Release: 1 +Source: chrony-%{version}.tar.gz +Copyright: GPL +Group: Applications/Utilities +Packager: Richard P. Curnow +BuildRoot: %{_tmppath}/%{name}-%{version}-root-%(id -u -n) +Requires: info + +%description +A pair of programs for keeping computer clocks accurate. chronyd is a +background (daemon) program and chronyc is a command-line interface to it. +Time reference sources for chronyd can be RFC1305 NTP servers, human (via +keyboard and chronyc), and the computer's real-time clock at boot time (Linux +only). chronyd can determine the rate at which the computer gains or loses +time and compensate for it whilst no external reference is present. chronyd's +use of NTP servers can be switched on and off (through chronyc) to support +computers with dial-up/intermittent access to the Internet. chronyd can also +act as an RFC1305-compatible NTP server. + +%prep +%setup + +%build +./configure --prefix=%{_prefix} +make CC=gcc CFLAGS=-O2 prefix=%{_prefix} +make chrony.txt prefix=%{_prefix} +make chrony.info prefix=%{_prefix} + +%install +rm -rf $RPM_BUILD_ROOT +cd $RPM_BUILD_DIR/chrony-%{version} +make install DESTDIR=$RPM_BUILD_ROOT prefix=%{_prefix} +mkdir -p $RPM_BUILD_ROOT%{_infodir} +cp chrony.info* $RPM_BUILD_ROOT%{_infodir} + +%files +%{_sbindir}/chronyd +%{_bindir}/chronyc +%{_infodir}/chrony.info* +%{_mandir}/man1/chrony.1.gz +%{_mandir}/man1/chronyc.1.gz +%{_mandir}/man5/chrony.conf.5.gz +%{_mandir}/man8/chronyd.8.gz +%doc README +%doc chrony.txt +%doc COPYING +%doc examples/chrony.conf.example +%doc examples/chrony.keys.example + diff --git a/chrony.texi b/chrony.texi new file mode 100644 index 0000000..4642bb7 --- /dev/null +++ b/chrony.texi @@ -0,0 +1,3979 @@ +\input texinfo +@c {{{ Main header stuff +@afourwide +@paragraphindent 0 +@setfilename chrony.info +@settitle User guide for the chrony suite +@c @setchapternewpage off + +@ifinfo +@dircategory Net Utilities +@direntry +* chrony: (chrony). How to use chronyd and chronyc +* chronyd: (chrony)Starting chronyd. Reference for chronyd +* chronyc: (chrony)Running chronyc. Reference for chronyc +@end direntry +@end ifinfo + +@titlepage +@sp 10 +@title The chrony suite +@subtitle This manual describes how to use +@subtitle the programs chronyd and chronyc +@author Richard P. Curnow +@page +@vskip 0pt plus 1filll +Copyright @copyright{} 1997-1999 Richard P. Curnow +@end titlepage +@c }}} +@c {{{ Top node +@node Top +@top +@menu +* Introduction:: What the chrony suite does +* Installation:: How to compile and install the software +* Typical scenarios:: How to configure the software for some common cases +* Usage reference:: Reference manual +* Porting guide:: Hints to help with porting the software +* GPL:: The GNU General Public License +@end menu +@c }}} +@c {{{ Ch:Introduction +@c {{{ Chapter top +@node Introduction +@chapter Introduction +@menu +* Overview:: What the programs do +* Acknowledgements:: Credit where credit is due +* Availability:: Where to get the software +* Other time synchronisation packages:: Comparision with other software +* Distribution and warranty:: There is no warranty +* Bug reporting:: How to report bugs and make suggestions +* Contributing:: Areas where contributions are particularly welcome +@end menu +@c }}} +@c {{{ S:Overview +@node Overview +@section Overview +Chrony is a software package for maintaining the accuracy of computer +system clocks. It consists of a pair of programs : + +@itemize @bullet +@item @code{chronyd}. This is a daemon which runs in background on the +system. It obtains measurements (e.g. via the network) of the system's +offset relative to other systems, and adjusts the system time +accordingly. For isolated systems, the user can periodically enter the +correct time by hand (using @code{chronyc}). In either case, +@code{chronyd} determines the rate at which the computer gains or loses +time, and compensates for this. + +@code{chronyd} can also act as an NTP server, and provide a time-of-day service +to other computers. A typical set-up is to run @code{chronyd} on a gateway +computer that has a dial-up link to the Internet, and use it to serve time to +computers on a private LAN sitting behind the gateway. The IP addresses that +can act as clients of @code{chronyd} can be tightly controlled. The default is +no client access. + +@item @code{chronyc}. This is a command-line driven control and +monitoring program. An administrator can use this to fine-tune various +parameters within the daemon, add or delete servers etc whilst the +daemon is running. + +The IP addresses from which @code{chronyc} clients may connect can be tightly +controlled. The default is just the computer that @code{chronyd} itself is +running on. +@end itemize +@c }}} +@c {{{ S:Acknowledgments +@node Acknowledgements +@section Acknowledgements + +The @code{chrony} suite makes use of the algorithm known as @emph{RSA +Data Security, Inc. MD5 Message-Digest Algorithm} for authenticating +messages between different machines on the network. + +In writing the @code{chronyd} program, extensive use has been made of +RFC1305, written by David Mills. I have occasionally referred to the +@code{xntp} suite's source code to check details of the protocol that +the RFC did not make absolutely clear. The core algorithms in +@code{chronyd} are all completely distinct from @code{xntp}, however. +@c }}} +@c {{{ S:Availability +@node Availability +@section Availability +@menu +* Getting the software:: Where can I get the software from? +* Platforms:: Which platforms will it run on? +@end menu + + +@node Getting the software +@subsection Getting the software +Links on @uref{http://chrony.sunsite.dk/download.php, the +chrony home page} describe how to obtain the software. + + +@node Platforms +@subsection Platforms +Although most of the program is portable between +Unix-like systems, there are parts that have to be tailored to each +specific vendor's system. These are the parts that interface with the +operating system's facilities for adjusting the system clock; +different operating systems may provide different function calls to +achieve this, and even where the same function is used it may have +different quirks in its behaviour. + +The software is known to work in the following environments: +@itemize @bullet +@item Linux/i386 and Linux/ppc. The software is known to work on Linux 2.0.x, +2.2.x and 2.4.x. Prior to 2.0.31, the real time clock can't be used. + +@item NetBSD +@item BSD/386 + +@item Solaris 2.3/2.5/2.5.1/2.6/2.7/2.8 on Sparc (Sparc 20, Ultrasparc) and +i386 + +@item SunOS 4.1.4 on Sparc 2 and Sparc20. +@end itemize + +Closely related systems may work too, but they have not been tested. + +Porting the software to other system (particularly to those supporting +an @code{adjtime} system call) should not be difficult, however it +requires access to such systems to test out the driver. +@c }}} +@c {{{ S:Other programs +@node Other time synchronisation packages +@section Relationship to other software packages +@menu +* Comparison with xntpd:: +* Comparison with timed:: +@end menu + +@node Comparison with xntpd +@subsection xntpd +The `reference' implementation of the Network Time Protocol is the +program @code{xntpd}, available via +@uref{http://www.eecis.udel.edu/~ntp, The NTP home page}. + +@code{xntpd} is designed to support all the operating modes defined by +RFC1305, and has driver support for a large number of reference clocks +(such as GPS receivers) that can be connected directly to a computer, +thereby providing a so-called 'stratum 1' server. + +Things @code{chronyd} can do that @code{xntpd} can't: + +@itemize @bullet +@item +@code{chronyd} can perform usefully in an environment where access to +the time reference is intermittent. @code{chronyd} estimates +@emph{both} the current time offset @emph{and} the rate at which the +computer's clock gains or loses time, and can use that rate estimate to +trim the clock after the reference disappears. @code{xntpd} corrects +any time offset by speeding up and slowing down the computer clock, and +so could be left with a significant rate error if the reference +disappears whilst it is trying to correct a big offset. + +@item +@code{chronyd} provides support for isolated networks whether the only +method of time correction is manual entry (e.g. by the administrator +looking at a clock). @code{chronyd} can look at the errors corrected at +different updates to work out the rate at which the computer gains or +loses time, and use this estimate to trim the computer clock +subsequently. + +@item +@code{chronyd} provides support to work out the gain or loss rate of the +`real-time clock', i.e. the clock that maintains the time when the +computer is turned off. It can use this data when the system boots to +set the system time from a corrected version of the real-time clock. +These real-time clock facilities are only available on certain releases +of Linux, so far. + +@item +The @code{xntpd} program is supported by other programs to carry out +certain functions. @code{ntpdate} is used to provide an initial +correction to the system clock based on a `one-shot' sampling of other +NTP servers. @code{tickadj} is used to adjust certain operating system +parameters to make @code{xntpd} work better. All this functionality is +integrated into @code{chronyd}. +@end itemize + +Things @code{xntpd} can do that @code{chronyd} can't: + +@itemize @bullet +@item +@code{xntpd} supports a range of different hardware reference clocks +(GPS, atomic etc) that can be connected to a computer to provide a +`stratum-1' server. @code{chronyd} does not support any such hardware +@emph{yet}; I don't have access to any to do any development work. +However, the software architecture should allow such equipment to be +interfaced at a later date. + +@item +@code{xntpd} supports effectively all of RFC1305, including broadcast / +multicast clients, leap seconds, and extra encryption schemes for +authenticating data packets. + +@item +@code{xntpd} has been ported to more types of computer / operating +system (so far). + +@item +xntpd is designed to work solely with integer arithmetic (i.e. does not +require floating point support from its host). +@end itemize + +@node Comparison with timed +@subsection timed +@code{timed} is a program that is part of the BSD networking suite. It +uses broadcast packets to find all machines running the daemon within a +subnet. The machines elect a master which periodically measures the +system clock offsets of the other computers using ICMP timestamps. +Corrections are sent to each member as a result of this process. + +Problems that may arise with @code{timed} are : + +@itemize @bullet +@item +Because it uses broadcasts, it is not possible to isolate its +functionality to a particular group of computers; there is a risk of +upsetting other computers on the same network (e.g. where a whole +company is on the same subnet but different departments are independent +from the point of view of administering their computers.) +@item +The update period appears to be 10 minutes. Computers can build up +significant offsets relative to each other in that time. If a +computer can estimate its rate of drift it can keep itself closer to +the other computers between updates by adjusting its clock every few +seconds. @code{timed} does not seem to do this. +@item +@code{timed} does not have any integrated capability for feeding +real-time into its estimates, or for estimating the average rate of time +loss/gain of the machines relative to real-time (unless one of the +computers in the group has access to an external reference and is always +appointed as the `master'). +@end itemize + +@code{timed} does have the benefit over @code{chronyd} that for isolated +networks of computers, they will track the `majority vote' time. For +such isolated networks, @code{chronyd} requires one computer to be the +`master' with the others slaved to it. If the master has a particular +defective clock, the whole set of computers will tend to slip relative +to real time (but they @emph{will} stay accurate relative to one +another). +@c }}} +@c {{{ S:Rights + warranty +@node Distribution and warranty +@section Distribution rights and (lack of) warranty + +Chrony may be distributed in accordance with the GNU General Public License +version 2, reproduced in @xref{GPL}. + +@c }}} +@c {{{ S:Bug reporting + suggestions +@node Bug reporting +@section Bug reporting and suggestions + +If you think you've found a bug in chrony, or have a suggestion, please let me +know. My primary current email address is @email{rc@@rc0.org.uk}. If that +fails, you could try finding me through one of the chrony mailing lists, or by +looking up my name on a search engine. + +I can't promise a timescale to fix a bug; it depends a lot on the how complex +the bug is to track down, as I have a lot of other calls on my time : 2 young +children, my job, and indeed other free/open source software projects. +However, I do intend to look into problems when time allows. + +Another source of information to try is the chrony users mailing list. You can +join this by sending an empty message to +@email{chrony-users-subscribe@@sunsite.dk}. Only subscribers can post to +the list. + +When you are reporting a bug, please send me all the information you can. +Unfortunately, chrony has proven to be one of those programs where it is very +difficult to reproduce bugs in a different environment. So I may have to +interact with you quite a lot to obtain enough extra logging and tracing to +pin-point the problem in some cases. Please be patient and plan for this! + +Of course, if you can debug the problem yourself and send me a source code +patch to fix it, I will be very grateful! + +@c }}} +@c {{{ S:Contributions +@node Contributing +@section Contributions + +Although chrony is now a fairly mature and established project, there are still +areas that could be improved. If you can program in C and have some expertise +in these areas, you might be able to fill the gaps. + +Particular areas I know need addressing are : + +@enumerate +@item Porting to other Unices + +This involves creating equivalents of sys_solaris.c, sys_linux.c etc for the +new system. Note, the Linux driver has been reported as working on a range of +different architectures (Alpha, Sparc, MIPS as well as x86 of course). + +@item Porting to Windows NT + +I did a small amount of work on this under Cygwin. Only the sorting out of the +include files has really been achieved so far. The two main areas still to +address are + +@enumerate +@item The system clock driver. +@item How to make chronyd into an NT service (i.e. what to replace fork(), +setsid() etc with so that chronyd can be automatically started in the system +bootstrap. +@end enumerate + +@item Hardware clock support + +@item Automation of the trimrtc and writertc mechanisms + +Currently, the RTC trimming mechanism is a manual operation, because there has +to be a reasonable guarantee that the system will stay up for a reasonable +length of time afterwards. (If it is shut down too soon, a poor +characterisation of the RTC drift rate will be stored on disc, giving a bad +system clock error when the system is next booted.) + +To make chrony more automated for the non-expert user, it would be useful if +this problem could be avoided so that trimrtc could be done automatically (e.g. +in a crontab, or as part of the ip-up or ip-down scripts.) + +@end enumerate +@c }}} +@c }}} +@c {{{ Ch:Installation +@node Installation +@chapter Installation + +@c {{{ main introduction text +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. + +After unpacking the source code, change directory into it, and type + +@example +./configure +@end example + +This is a shell script that automatically determines the system type. +There is a single optional parameter, @code{--prefix} which indicates +the directory tree where the software should be installed. For example, + +@example +./configure --prefix=/opt/free +@end example + +will install the @code{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: + +@example +CC=cc CFLAGS=-O ./configure --prefix=/opt/free +@end example + +for Bourne-family shells, or + +@example +setenv CC cc +setenv CFLAGS -O +./configure --prefix=/opt/free +@end example + +for C-family shells. + +If the software cannot (yet) be built on your system, an error message +will be shown. Otherwise, the files @file{options.h} and +@file{Makefile} will be generated. + +By default, chronyc will be built to make use of the readline library. If you +don't want this, specify the --disable-readline flag to configure. If you have +readline and/or ncurses installed in a non-standard location, please refer to +@pxref{readline support} for information. + +Now type + +@example +make +@end example + +to build the programs. + +If you want to build the manual in plain text, HTML and info versions, type + +@example +make docs +@end example + +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. + +@example +make install +@end example + +This will install the binaries, plain text manual and manpages. + +To install the HTML and info versions of the manual as well, enter the command + +@example +make install-docs +@end example + +If you want chrony to appear in the top level info directory listing, you need +to run the @command{install-info} command manually after this step. +@command{install-info} takes 2 arguments. The first is the path to the +@file{chrony.info} file you have just installed. This will be the argument you +gave to --prefix when you configured (@file{/usr/local} by default), with +@file{/info/chrony.info} on the end. The second argument is the location of +the file called @file{dir}. This will typically be @file{/usr/info/dir}. So +the typical command line would be + +@example +install-info /usr/local/info/chrony.info /usr/info/dir +@end example + +Now that the software is successfully installed, the next step is to +set up a configuration file. The contents of this depend on the +network environment in which the computer operates. Typical scenarios +are described in the following section of the document. +@c }}} +@menu +* readline support:: If readline or ncurses in in a non-standard place +* package builders:: Extra options useful to package builders +@end menu +@c {{{ readline support +@node readline support +@section Support for the readline library +By default, chronyc is built to make use of the readline library. This allows +you to use the cursor keys to replay and edit old commands. If you don't want +to use readline (in which case chronyc will use a minimal command line +interface), invoke configure like this: + +@example +./configure --disable-readline other-options... +@end example + +If you have readline and/or ncurses installed in locations that aren't normally searched by the compiler and linker, you need extra options if you want readline to be used: + +@table @samp +@item --with-readline-includes=directory_name +This defines the name of the directory above the one where @file{readline.h} +is. @file{readline.h} is assumed to be in a @file{readline} subdirectory of +the named directory. + +@item --with-readline-library=directory_name +This defines the directory containing the @file{libreadline.a} or +@file{libreadline.so} file. + +@item --with-ncurses-library=directory_name +This defines the directory containing the @file{libncurses.a} or +@file{libncurses.so} file. +@end table + +@c }}} +@c {{{ +@node package builders +@section 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 --infodir=DIR option to configure specifies a different install directory +for the info files. This overrides the @file{info} subdirectory of the +argument to the --prefix option. For example, you might use + +@example +./configure --prefix=/usr --infodir=/usr/share/info +@end example + +The --mandir=DIR option to configure specifies a different install directory +for the man pages. This overrides the @file{man} subdirectory of the +argument to the --prefix option. + +@example +./configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man +@end example + +to set both options together. + +The final option is the DESTDIR option to the make command. For example, you +could use the commands + +@example +./configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man +make all docs +make install DESTDIR=./tmp +cd tmp +tar cvf - . | gzip -9 > chrony.tar.gz +@end example + +to build a package. When untarred within the root directory, this will install +the files to the intended final locations. + +@c }}} + +@c }}} +@c {{{ Ch:Typical operating scenarios +@c {{{ Chapter top +@node Typical scenarios +@chapter Typical operating scenarios +@menu +* Computers on the net:: Your computer is permanently on the Internet (or on + a private network with NTP servers) +* Infrequent connection:: You connect to the Internet sometimes (e.g. via a modem) +* Isolated networks:: You have an isolated network with no reference clocks +* Dial-up home PCs:: Additional considerations if you turn your computer off + when it's not in use. +* Configuration options overview:: Overview of some configuration options. +@end menu +@c }}} +@c {{{ S:Permanent connection +@node Computers on the net +@section Computers connected to the internet +In this section we discuss how to configure chrony for computers that +have permanent connections to the internet (or to any network +containing true NTP servers which ultimately derive their time from a +reference clock). + +To operate in this mode, you will need to know the names of the NTP +server machines you wish to use. You may be able to find names of +suitable servers by one of the following methods: + +@itemize @bullet +@item Your institution may already operate servers on its network. +Contact your system administrator to find out. + +@item Your ISP probably has one or more NTP servers available for its +customers. + +@item 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. +@end itemize + +Assuming that you have found some servers, you need to set up a +configuration file to run chrony. The (compiled-in) default location +for this file is @file{/etc/chrony.conf}. Assuming that your ntp +servers are called @code{a.b.c} and @code{d.e.f}, your +@file{chrony.conf} file could contain as a minimum + +@example +server a.b.c +server d.e.f +server g.h.i +@end example + +However, you will probably want to include some of the other directives +described later. The following directives will be particularly useful : +@code{driftfile}, @code{commandkey}, @code{keyfile}. The smallest +useful configuration file would look something like + +@example +server a.b.c +server d.e.f +server g.h.i +keyfile /etc/chrony.keys +commandkey 1 +driftfile /etc/chrony.drift +@end example +@c }}} +@c {{{ S:Infrequent connection +@node Infrequent connection +@section Infrequent connection to true NTP servers +In this section we discuss how to configure chrony for computers that +have occasional connections to the internet. + +@menu +* Configuration for infrequent connections:: How to set up the @code{/etc/chrony} file +* Advising chronyd of internet availability:: How to tell chronyd when the link is available +@end menu + +@node Configuration for infrequent connections +@subsection Setting up the configuration file for infrequent connections +As in the previous section, you will need access to NTP servers on the +internet. The same remarks apply for how to find them. + +In this case, you will need some additional configuration to tell +@code{chronyd} when the connection to the internet 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 @code{a.b.c} and +@code{d.e.f}, your @file{chrony.conf} file would need to contain +something like + +@example +server a.b.c +server d.e.f +server g.h.i +@end example + +However, the following issues need to be addressed: + +@enumerate 1 +@item +Your computer probably doesn't have DNS access whilst offline to turn +the machine names into IP addresses. +@item +Your computer will keep trying to contact the servers to obtain +timestamps, even whilst offline. If you operate a dial-on-demand +system, things are even worse, because the link to the internet will +keep getting established. +@end enumerate + +For this reason, it would be better to specify this part of your +configuration file in the following way: + +@example +server 1.2.3.4 offline +server 5.6.7.8 offline +server 9.10.11.12 offline +@end example + +Because numeric IP addresses have been used, the first problem is +overcome. The @code{offline} keyword indicates that the servers start +in an offline state, and that they should not be contacted until @code{chronyd} +receives notification that the link to the internet is present. + +An alternative is to use the names of the NTP servers, and put entries for them +into your @file{/etc/hosts} file. This will be OK as long as @samp{files} +comes before @samp{dns} in the @samp{hosts} line of the +@file{/etc/nsswitch.conf} file. + +In order to notify @code{chronyd} of the presence of the link, you will need to +be able to log in to it with the program chronyc. To do this, @code{chronyd} +needs to be configured with an administrator password. To set up an +administrator password, you can create a file @file{/etc/chrony.keys} +containing a single line + +@example +1 xyzzy +@end example + +and add the following line to @file{/etc/chrony.conf} (the order of the +lines does not matter) + +@example +commandkey 1 +@end example + +The smallest useful configuration file would look something like + +@example +server 1.2.3.4 offline +server 5.6.7.8 offline +server 9.10.11.12 offline +keyfile /etc/chrony.keys +commandkey 1 +driftfile /etc/chrony.drift +@end example + +The next section describes how to tell @code{chronyd} when the internet link +goes up and down. + +@node Advising chronyd of internet availability +@subsection How to tell chronyd when the internet link is available. +To use this option, you will need to configure a command key in +@code{chronyd's} configuration file @file{/etc/chrony.conf}, as described in +the previous section. + +To tell @code{chronyd} when to start and finish sampling the servers, the +@code{online} and @code{offline} commands of chronyc need to be used. +To give an example of their use, we assume that @code{pppd} is the +program being used to connect to the internet, and that chronyc has been +installed at its default location @file{/usr/local/bin/chronyc}. We +also assume that the command key has been set up as described in the +previous section. + +In the file @file{/etc/ppp/ip-up} we add the command sequence + +@example +/usr/local/bin/chronyc <| |-------| | | | +| clock | drift rate +-------+ +-----+ +---+ ++---------+ ^ | + | | | + +---------------------------+ --o-----o--- + set time at boot up | + +----------+ + |NTP server| + +----------+ +@end group +@end example + +When the computer is connected to the Internet (via the modem), +@code{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/losing time. + +When the computer is taken offline from the Internet, the best estimate +of the gain/loss rate is used to free-run the computer until it next +goes online. + +Whilst the computer is running, @code{chronyd} makes measurements of the +real-time clock (RTC) (via the @file{/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. + +The RTC is fully supported in 2.2 and 2.4 kernels. + +For kernels in the 2.0 series prior to 2.0.32, the kernel was set up to +trim the RTC every 11 minutes. This would be disasterous for +@code{chronyd} -- there is no reliable way of synchronising with this +trimming. For this reason, @code{chronyd} only supports the RTC in 2.0 +kernels from v2.0.32 onwards. + +When the computer is powered down, the measurement histories for all the +NTP servers are saved to files (if the @code{dumponexit} directive is +specified in the configuration file), and the RTC tracking information +is also saved to a file (if the @code{rtcfile} directive has been +specified). These pieces of information are also saved if the +@code{dump} and @code{writertc} commands respectively are issued through +@code{chronyc}. + +When the computer is rebooted, @code{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/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 @code{chronyd} is robust enough +to cope with this, some performance may be lost. (The main danger +arises if the RTC has been changed during the session, with the +@code{trimrtc} command in @code{chronyc}. Because of this, +@code{trimrtc} will make sure that a meaningful RTC file is saved out +after the change is completed). + +The easiest protection against power failure is to put the @code{dump} +and @code{writertc} commands in the same place as the @code{offline} +command is issued to take @code{chronyd} offline; because @code{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 home 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). @code{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 @code{log rtc} facility in the configuration file; this +option should not be used if you want your disc to spin down. + +@node Dial-up configuration +@subsection Typical configuration files. + +To illustrate how a dial-up home computer might be configured, example +configuration files are shown in this section. + +For the @file{/etc/chrony.conf} file, the following can be used as an +example. @emph{NOTE : The @code{server} directives are only applicable +to customers of Demon Internet; users of other ISPs will need to use +their own ISP's NTP servers or public NTP servers.} + +@example +server 158.152.1.65 minpoll 5 maxpoll 10 maxdelay 0.4 offline +server 158.152.1.76 minpoll 5 maxpoll 10 maxdelay 0.4 offline +server 194.159.253.2 minpoll 5 maxpoll 10 maxdelay 0.4 offline +logdir /var/log/chrony +log statistics measurements tracking +driftfile /etc/chrony.drift +keyfile /etc/chrony.keys +commandkey 25 +maxupdateskew 100.0 +dumponexit +dumpdir /var/log/chrony +rtcfile /etc/chrony.rtc +@end example + +With Freeserve as the ISP, I use the following server lines : + +@example +server 194.152.64.68 minpoll 5 maxpoll 10 maxdelay 0.4 offline +server 194.152.64.35 minpoll 5 maxpoll 10 maxdelay 0.4 offline +server 194.152.64.34 minpoll 5 maxpoll 10 maxdelay 0.4 offline +@end example + +I use @code{pppd} for connecting to my ISP. This runs two scripts +@file{/etc/ppp/ip-up} and @file{/etc/ppp/ip-down} when the link goes +online and offline respectively. + +The relevant part of the @file{/etc/ppp/ip-up} file is (with a dummy +password) + +@example +/usr/local/bin/chronyc < +This option can be used to specify an alternate location for the +configuration file (default @file{/etc/chrony.conf}). +@item -r +This option will reload sample histories for each of the servers being +used. These histories are created by using the @code{dump} command in +@code{chronyc}, or by setting the @code{dumponexit} directive in the +configuration file. This option is useful if you want to stop and +restart @code{chronyd} briefly for any reason, e.g. to install a new +version. However, it only makes sense on systems where the kernel can +maintain clock compensation whilst not under @code{chronyd's} control. +The only version where this happens so far is Linux. On systems where +this is not the case, e.g. Solaris and SunOS the option should not be +used. +@item -s +This option will set the system clock from the computer's real-time +clock. This is analogous to supplying the `-s' flag to the +@file{/sbin/clock} program during the Linux boot sequence. + +Support for real-time clocks is limited at present - the criteria are +described in the section on the @code{rtcfile} directive (@pxref{rtcfile +directive}). + +If @code{chronyd} cannot support the real time clock on your computer, +this option cannot be used and a warning message will be logged to the +syslog. + +If used in conjunction with the `-r' flag, @code{chronyd} will attempt +to preserve the old samples after setting the system clock from the real +time clock. This can be used to allow @code{chronyd} to perform long +term averaging of the gain or loss rate across system reboots, and is +useful for dial-up systems that are shut down when not in use. For this +to work well, it relies on @code{chronyd} having been able to determine +accurate statistics for the difference between the real time clock and +system clock last time the computer was on. + +@item -v +This option displays @code{chronyd's} version number to the terminal and +exits. +@end table + +On systems that support an @file{/etc/rc.local} file for starting +programs at boot time, @code{chronyd} can be started from there. + +On systems with a System V style initialisation (e.g. Solaris), a +suitable start/stop script might be as shown below. This might be +placed in the file @file{/etc/rc2.d/S83chrony}. + +@example +@group +#!/bin/sh +# This file should have uid root, gid sys and chmod 744 +# + +killproc() @{ # kill the named process(es) + pid=`/usr/bin/ps -e | + /usr/bin/grep -w $1 | + /usr/bin/sed -e 's/^ *//' -e 's/ .*//'` + [ "$pid" != "" ] && kill $pid +@} + +case "$1" in + +'start') + if [ -f /opt/free/sbin/chronyd -a -f /etc/chrony.conf ]; then + /opt/free/sbin/chronyd + fi + ;; +'stop') + killproc chronyd + ;; +*) + echo "Usage: /etc/rc2.d/S83chrony @{ start | stop @}" + ;; +esac +@end group +@end example + +(In both cases, you may want to bear in mind that @code{chronyd} can +step the time when it starts. There may be other programs started at +boot time that could be upset by this, so you may need to consider the +ordering carefully. However, @code{chronyd} will need to start after +daemons providing services that it may require, e.g. the domain name +service.) +@c }}} +@c {{{ S:chronyd configuration file +@node Configuration file +@section The chronyd configuration file +@c {{{ section top +The configuration file is normally called @file{/etc/chrony.conf}; in +fact, this is the compiled-in default. However, other locations can be +specified with a command line option. + +Each command in the configuration file is placed on a separate line. +The following sections describe each of the commands in turn. The +directives can occur in any order in the file. + +@menu +* comments in config file:: How to write a comment +* acquisitionport directive:: Set port to use for initial time probes +* allow directive:: Give access to NTP clients +* bindaddress directive:: Limit the network interface that is used for NTP +* bindcmdaddress directive:: Limit the network interface that is used for commands +* broadcast directive:: Make chronyd act as an NTP broadcast server +* cmdallow directive:: Give control access to chronyc on other computers +* cmddeny directive:: Deny control access to chronyc on other computers +* commandkey directive:: Set runtime command key +* cmdport directive:: Set port to use for runtime commanding +* deny directive:: Deny access to NTP clients +* driftfile directive:: Specify location of file containing drift data +* dumpdir directive:: Specify directory for dumping measurements +* dumponexit directive:: Dump measurements when daemon exits +* initstepslew directive:: Trim the system clock on boot-up. +* keyfile directive:: Specify location of file containing keys +* linux_hz directive:: Define a non-standard value of the kernel HZ constant +* linux_freq_scale directive:: Define a non-standard value to compensate the kernel frequency bias +* local directive:: Allow unsynchronised machine to act as server +* log directive:: Make daemon log certain sets of information +* logchange directive:: Generate syslog messages if large offsets occur +* logdir directive:: Specify directory for logging +* mailonchange directive:: Send email if a clock correction above a threshold occurs +* manual directive:: Allow manual entry using chronyc's settime cmd. +* maxupdateskew directive:: Stop bad estimates upsetting machine clock +* noclientlog directive:: Prevent chronyd from gathering data about clients +* peer directive:: Specify an NTP peer +* pidfile directive:: Specify the file where chronyd's pid is written +* port directive:: Set port to use for NTP packets +* rtcdevice directive:: Specify name of enhanced RTC device (if not /dev/rtc) +* rtcfile directive:: Specify the file where real-time clock data is stored +* rtconutc directive:: Specify that the real time clock keeps UTC not local time +* server directive:: Specify an NTP server +@end menu +@c }}} +@c {{{ comments in config file +@node comments in config file +@subsection Comments in the configuration file +The configuration file may contain comment lines. A comment line is any line +that starts with zero or more spaces followed by any one of the following +characters: +@itemize +@item ! +@item ; +@item # +@item % +@end itemize +Any line with this format will be ignored. +@c }}} +@c {{{ acquisitionport directive +@node acquisitionport directive +@subsection acquisitionport +@code{chronyd} uses a separate client-side port for the rapid-fire +measurements requested with the @code{initstepslew} directive +(@pxref{initstepslew directive}). Normally, that port is chosen +arbitrarily by the operating system. However, you can use +@code{acquisitionport} to explicitly specify a port. This may be useful +for getting through firewalls. + +Do not make acquisition and regular NTP service (@pxref{port directive}) +use the same port. + +An example of the @code{acquisitionport} command is + +@example +acquisitionport 1123 +@end example + +This would change the port used for rapid queries to udp/1123. You +could then persuade the firewall administrator to let that port through. +@c }}} +@c {{{ allow +@node allow directive +@subsection allow +The @code{allow} command is used to designate a particular subnet from +which NTP clients are allowed to access the computer as an NTP server. + +The default is that no clients are allowed access, i.e. @code{chronyd} +operates purely as an NTP client. If the @code{allow} directive is +used, @code{chronyd} will be both a client of its servers, and a server +to other clients. + +Examples of use of the command are as follows: + +@example +allow foo.bar.com +allow 1.2 +allow 3.4.5 +allow 6.7.8/22 +allow 6.7.8.9/22 +allow +@end example + +The first command allows the named node to be an NTP client of this computer. +The second command allows any node with an IP address of the form 1.2.x.y (with +x and y arbitrary) to be an NTP client of this computer. Likewise, the third +command allows any node with an IP address of the form 3.4.5.x to have client +NTP access. The fourth and fifth forms allow access from any node with an IP +address of the form 6.7.8.x, 6.7.9.x, 6.7.10.x or 6.7.11.x (with x arbitrary), +i.e. the value 22 is the number of bits defining the specified subnet. (In the +fifth form, the final byte is ignored). The sixth form allows access by any +node on the entire Internet. + +A second form of the directive, @code{allow all}, has a greater effect, +depending on the ordering of directives in the configuration file. To +illustrate the effect, consider the two examples + +@example +allow 1.2.3.4 +deny 1.2.3 +allow 1.2 +@end example + +and + +@example +allow 1.2.3.4 +deny 1.2.3 +allow all 1.2 +@end example + +In the first example, the effect is the same regardles of what order the +three directives are given in. So the 1.2.x.y subnet is allowed access, +except for the 1.2.3.x subnet, which is denied access, however the host +1.2.3.4 is allowed access. + +In the second example, the @code{allow all 1.2} directives overrides the +effect of @emph{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 @code{chronyc} (@pxref{allow all command}). + +Note, if the @code{initstepslew} directive (@pxref{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. +@c }}} +@c {{{ bindaddress +@node bindaddress directive +@subsection bindaddress +The bindaddress allows you to restrict the network interface to which +chronyd will listen for NTP packets. This provides an additional level of +access restriction above that available through the 'deny' mechanism. + +Suppose you have a local ethernet with addresses in the 192.168.1.0 +subnet together with a dial-up connection. The ethernet interface's IP +address is 192.168.1.1. Suppose (for some reason) you want to block all +access through the dialup connection (note, this will even block replies +from servers on the dialup side, so you will not be able to synchronise +to an external source). You could add the line + +@example +bindaddress 192.168.1.1 +@end example + +to the configuration file. + +This directive affects NTP (UDP port 123) packets. If no @code{bindcmdaddress} +directive is present, the address supplied by @code{bindaddress} will be used +to control binding of the command socket (UDP port 323) as well. + +The @code{bindaddress} directive has been found to cause problems when used on +computers that need to pass NTP traffic over multiple network interfaces (e.g. +firewalls). It is, therefore, not particularly useful. Use of the +@code{allow} and @code{deny} directives together with a network firewall is +more likely to be successful. + +@c }}} +@c {{{ bindcmdaddress +@node bindcmdaddress directive +@subsection bindcmdaddress +The bindcmdaddress allows you to restrict the network interface to which +chronyd will listen for command packets (issued by chronyc). + +Suppose you have a local ethernet with addresses in the 192.168.1.0 subnet +together with a dial-up connection. The ethernet interface's IP address is +192.168.1.1. Suppose you want to block all access through the dialup +connection. You could add the line + +@example +bindcmdaddress 192.168.1.1 +@end example + +to the configuration file. + +The @code{bindcmdaddress} directive has been found to cause problems when used +on computers that need to pass command traffic over multiple network +interfaces. It is, therefore, not particularly useful. Use of the +@code{cmdallow} and @code{cmddeny} directives together with a network firewall +is more likely to be successful. + +@c }}} +@c {{{ broadcast directive +@node broadcast directive +@subsection broadcast +The @code{broadcast} directive is used to declare a broadcast address to which +chronyd should send packets in NTP broadcast mode (i.e. make chronyd act as a +broadcast server). Broadcast clients on that subnet will be able to +synchronise. + +The syntax is as follows + +@example +broadcast 30 192.168.1.255 +broadcast 60 192.168.2.255 12123 +@end example + +In the first example, the destination port defaults to 123/udp (the normal NTP +port). In the second example, the destionation port is specified as 12123. +The first parameter in each case (30 or 60 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 @code{broadcast} directive if you have more than 1 +network interface onto which you wish to send NTP broadcast packets. + +Chronyd itself cannot currently 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 software (e.g. various MS Windows clients). + +If xntpd is used as the broadcast client, it will try to use a point-to-point +client/server NTP access to measure the round-trip delay. Thus, the broadcast +subnet should also be the subject of an @code{allow} directive (@pxref{allow +directive}). +@c }}} +@c {{{ cmdallow +@node cmdallow directive +@subsection cmdallow + +This is similar to the @code{allow} directive (@pxref{allow directive}), except +that it allows control access (rather than NTP client access) to a particular +subnet or host. (By 'control access' is meant that chronyc can be run on those +hosts and successfully connect to chronyd on this computer.) + +The syntax is identical to the @code{allow} directive. + +There is also a @code{cmdallow all} directive with similar behaviour to the +@code{allow all} directive (but applying to control access in this case, of +course). +@c }}} +@c {{{ cmddeny +@node cmddeny directive +@subsection cmddeny + +This is similar to the @code{cmdallow} directive (@pxref{cmdallow directive}), +except that it denies control access to a particular subnet or host, +rather than allowing it. + +The syntax is identical. + +There is also a @code{cmddeny all} directive with similar behaviour to the +@code{cmdallow all} directive. +@c }}} +@c {{{ commandkey +@node commandkey directive +@subsection commandkey +The commandkey command is used to set the key number used for +authenticating user commands via the chronyc program at run time. +This allows certain actions of the chronyc program to be restricted to +administrators. + +An example of the commandkey command is + +@example +commandkey 20 +@end example + +In the key file (see the keyfile command) there should be a line of +the form + +@example +20 foobar +@end example + +When running the chronyc program to perform run-time configuration, +the command + +@example +password foobar +@end example + +must be entered before any commands affecting the operation of the +daemon can be entered. +@c }}} +@c {{{ cmdport +@node cmdport directive +@subsection cmdport + +The @code{cmdport} directive allows the port that is used for run-time +command and monitoring (via the program @code{chronyc}) to be altered +from its default (323/udp). + +An example shows the syntax + +@example +cmdport 257 +@end example + +This would make @code{chronyd} use 257/udp as its command port. +(@code{chronyc} would need to be run with the @code{-p 257} switch to +inter-operate correctly). +@c }}} +@c {{{ deny +@node deny directive +@subsection deny + +This is similar to the @code{allow} directive (@pxref{allow directive}), +except that it denies NTP client access to a particular subnet or host, +rather than allowing it. + +The syntax is identical. + +There is also a @code{deny all} directive with similar behaviour to the +@code{allow all} directive. +@c }}} +@c {{{ driftfile +@node driftfile directive +@subsection driftfile +One of the main activities of the @code{chronyd} program is to work out +the rate at which the system clock gains or loses time relative to real +time. + +Whenever @code{chronyd} computes a new value of the gain/loss rate, it +is desirable to record it somewhere. This allows @code{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 may take many +minutes, at least). + +The driftfile command allows a file to be specified into which +@code{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 on +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 command is + +@example +driftfile /etc/chrony.drift +@end example +@c }}} +@c {{{ dumpdir +@node dumpdir directive +@subsection dumpdir +To compute the rate of gain or loss of time, @code{chronyd} has to store +a measurement history for each of the time sources it uses. + +Certain systems (so far only Linux) have operating system support for +setting the rate of gain or loss to compensate for known errors. (On +other systems, @code{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 @code{chronyd} (assuming no changes are made to the system +clock behaviour whilst it is not running). If this capability is to be +used (via the dumponexit command in the configuration file, or the dump +command in chronyc), the dumpdir command should be used to define the +directory where the measurement histories are saved. + +An example of the command is + +@example +dumpdir /var/log/chrony +@end example + +A source whose IP address is 1.2.3.4 would have its measurement +history saved in the file @file{/var/log/chrony/1.2.3.4.dat}. +@c }}} +@c {{{ dumponexit +@node dumponexit directive +@subsection dumponexit +If this command is present, it indicates that @code{chronyd} should save +the measurement history for each of its time sources recorded whenever +the program exits. (See the dumpdir command above). +@c }}} +@c {{{ initstepslew +@node initstepslew directive +@subsection initstepslew +In normal operation, @code{chronyd} always slews the time when it needs to +adjust the system clock. For example, to correct a system clock which +is 1 second slow, @code{chronyd} slightly increases the amount by which the +system clock is advanced on each clock interrupt, until the error is +removed. (Actually, this is done by calling the @code{adjtime()} or +similar system function which does it for us.) Note that at no time +does time run backwards with this method. + +On most Unix systems it is not desirable to step the system clock, +because many programs rely on time advancing monotonically forwards. + +When the @code{chronyd} daemon is initially started, it is possible that the +system clock is considerably in error. Attempting to correct such an +error by slewing may not be sensible, since it may take several hours +to correct the error by this means. + +The purpose of the @code{initstepslew} directive is to allow @code{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 easier to restart @code{chronyd} whilst the +system is in normal operation. + +The @code{initstepslew} directive takes a threshold and a list of NTP +servers as arguments. A maximum of 8 will be used. 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. +@code{chronyd} then enters its normal operating mode (where only slews are +used). + +An example of use of the command is + +@example +initstepslew 30 foo.bar.com baz.quz.com +@end example + +where 2 NTP servers are used to make the measurement. The @code{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 @code{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 master, and the other computers are slaved to +it. If each of the slaves is configured with the local option (see +below), the master can be set up with an @code{initstepslew} directive +which references some or all of the slaves. Then, if the master machine +has to be rebooted, the slaves can be relied on to 'flywheel' the time +for the master. +@c }}} +@c {{{ keyfile +@node keyfile directive +@subsection keyfile +This command is used to specify the location of the file containing +ID/key pairs for the following 2 uses: + +@itemize @bullet +@item Authentication of NTP packets. +@item Authentication of administrator commands entered via chronyc. +@end itemize + +The format of the command is shown in the example below + +@example +keyfile /etc/chrony.keys +@end example + +The argument is simply the name of the file containing the ID/key +pairs. The format of the file is shown below + +@example +10 tulip +11 hyacinth +20 crocus +25 iris + ... +@end example + +Each line consists of an ID and a password. The ID can be any +unsigned integer in the range 0 through 2**32-1. The password can be +any string of characters not containing a space. + +For NTP use, the MD5 authentication scheme is always used. This must be +borne in mind if @code{chronyd} is to inter-operate in authenticated +mode with @code{xntpd} running on other computers. + +The ID for the chronyc authentication key is specified with the +commandkey command (see earlier). +@c }}} +@c {{{ local +@node local directive +@subsection local +The local keyword is used to allow @code{chronyd} to appear synchronised +to real time (from the viewpoint of clients polling it), even if it has +no current synchronisation source. + +This option is normally used on computers in an isolated network, +where several computers are required to synchronise to one other, this +being the "master" which is kept vaguely in line with real time by +manual input. + +An example of the command is + +@example +local stratum 10 +@end example + +The value 10 may be substituted with other values in the range 1 +through 15. 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 large value of 10 indicates that the clock is so many hops away from +a reference clock that its time is fairly unreliable. Put another +way, if the computer ever has access to another computer which is +ultimately synchronised to a reference clock, it will almost certainly +be at a stratum less than 10. Therefore, the choice of a high value +like 10 for the local command prevents the machine's own time from +ever being confused with real time, were it ever to leak out to +clients that have visibility of real servers. +@c }}} +@c {{{ linux_hz +@node linux_hz directive +@subsection linux_hz +(This option only applies to Linux). + +By default, chronyd will find the value of @code{HZ} from a kernel header file +at compile time. @code{HZ} is the nominal number of timer interrupts per +second. If you're running chronyd on the system where it was built, the value +it has should be right, and you don't need to worry about this option. + +This option is provided for people who move a pre-built chronyd onto a system +where the value of HZ in the kernel headers has been changed from the default +value. + +An example of the command is + +@example +linux_hz 100 +@end example +@c }}} +@c {{{ linux_freq_scale +@node linux_freq_scale directive +@subsection linux_freq_scale +(This option only applies to Linux). + +By default, chronyd will find the value of @code{HZ} and @code{SHIFT_HZ} from +kernel header files at compile time. An internal value called +@code{freq_scale} is calculated from this. By default it is (1< +@end example + +Typical values for might be 100 for a dial-up connection +to servers over a phone line, and 5 or 10 for a computer on a LAN. + +It should be noted that this is not the only means of protection against +using unreliable estimates. At all times, @code{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 @code{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. +@c }}} +@c {{{ noclientlog +@node noclientlog directive +@subsection 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 @code{clients} command in @code{chronyc}. +@c }}} +@c {{{ peer +@node peer directive +@subsection peer +The syntax of this directive is identical to that for the @code{server} +directive (@pxref{server directive}), except that it is used to specify +an NTP peer rather than an NTP server. +@c }}} +@c {{{ pidfile +@node pidfile directive +@subsection pidfile +chronyd always writes its process ID (pid) to a file, and checks this file on startup to see if another chronyd may already be running on the system. By default, the file used is @code{/var/run/chronyd.pid}. The @code{pidfile} directive allows the name to be changed, e.g. + +@example +pidfile /var/tmp/chronyd.pid +@end example +@c }}} +@c {{{ port +@node port directive +@subsection port +This option allows you to configure the port used for the NTP service +on your machine. + +The compiled in default is udp/123, the standard NTP port. It is +unlikely that you would ever need to change this value. A possible +exception would be if you wanted to operate strictly in client-only +mode and never be available as a server to xntpd clients. + +An example of the port command is + +@example +port 11123 +@end example + +This would change the NTP port served by chronyd on the computer to +udp/11123. +@c }}} +@c {{{ rtcdevice +@node rtcdevice directive +@subsection rtcdevice +The @code{rtcdevice} directive defines the name of the device file for +accessing the real time clock. By default this is @code{/dev/rtc/}, unless the +directive is used to set a different value. This applies to Linux systems with +devfs. An example of use is + +@example +rtcdevice /dev/misc/rtc +@end example +@c }}} +@c {{{ rtcfile +@node rtcfile directive +@subsection rtcfile +The @code{rtcfile} directive defines the name of the file in which +@code{chronyd} can save parameters associated with tracking the accuracy +of the system's real-time clock (RTC). + +The syntax is illustrated in the following example + +@example +rtcfile /etc/chrony.rtc +@end example + +@code{chronyd} saves information in this file when it exits and when the +@code{writertc} command is issued in @code{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 real time clock facilities (the @code{rtcfile} directive and the +@code{-s} command line option to @code{chronyd}) if the following three +conditions apply: + +@enumerate 1 +@item +You are running Linux version 2.2.x or 2.4.x (for any value of x), or v2.0.x +with x>=32. + +@item +You have compiled the kernel with extended real-time clock support +(i.e. the @file{/dev/rtc} device is capable of doing useful things). + +@item +You don't have other applications that need to make use of +@file{/dev/rtc} at all. + +@end enumerate +@c }}} +@c {{{ rtconutc +@node rtconutc directive +@subsection rtconutc + +@code{chronyd} assumes by default that the real time clock (RTC) keeps +local time (including any daylight saving changes). This is convenient +on PCs running Linux which are dual-booted with DOS or Windows. + +NOTE : IF YOU KEEP THE REAL TIME CLOCK 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 @code{rtconutc} directive appears, it means the RTC is required +to keep UTC. The directive takes no arguments. It is equivalent to +specifying the @code{-u} switch to the Linux @file{/sbin/clock} program. +@c }}} +@c {{{ server +@node server directive +@subsection server +The @code{server} directive allows NTP servers to be specified. The +client/server relationship is strictly hierarchical : a client may +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 @code{server} directive is immediately followed by either the name +of the server, or its IP address in dotted-quad notation. The server +command also supports a number of subfields (which may be defined in any +order): + +@table @code +@item 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). +@item minpoll +Although @code{chronyd} will trim the rate at which it samples the +server during normal operation, the user may wish to constrain the +minimum polling interval. This is always defined as a power of 2, so + +This option allows the user to specify which host running the +@code{chronyd} program is to be contacted. This allows for remote +configuration, without having to telnet or rlogin to the other host +first. + +The default is to contact @code{chronyd} running on the same host as +that where chronyc is being run. +@item -p +This option allows the user to specify the UDP port number which the +target @code{chronyd} is using for its command & monitoring connections. +This defaults to the compiled-in default; there would rarely be a need +to change this. +@end table +@c }}} +@c {{{ SS:Security with chronyc +@node Security with chronyc +@subsection Security with chronyc +Many of the commands available through chronyc have a fair amount of +power to reconfigure the run-time behaviour of @code{chronyd}. Consequently, +@code{chronyc} is quite dangerous for the integrity of the target +system's clock performance. Having access to @code{chronyd} via chronyc is +more or less equivalent to being able to modify @code{chronyd's} configuration +file (typically @file{/etc/chrony.conf}) and to restart @code{chronyd}. + +Chronyc also provides a number of monitoring (as opposed to commanding) +commands, which will not affect the behaviour of @code{chronyd}. However, you +may still want to restrict access to these commands. + +In view of this, access to some of the capabilities of chronyc will +usually be tightly controlled. There are two mechanisms supported: + +@enumerate 1 +@item +The set of hosts from which @code{chronyd} will accept commands can be +restricted. By default, commands will only be accepted from the same +host that @code{chronyd} is running on. +@item +Any command that actually reconfigures some aspect of @code{chronyd's} +behaviour requires the user of chronyc to know a password. This +password is specified in @code{chronyd's} keys file (@pxref{keyfile directive}) +and specified via the commandkey option in its configuration file +(@pxref{commandkey directive}). +@end enumerate + +Only the following commands can be used @emph{without} providing a +password: + +@itemize @bullet +@item @code{exit} +@item @code{help} +@item @code{password} +@item @code{quit} +@item @code{rtcdata} +@item @code{sources} +@item @code{sourcestats} +@item @code{tracking} +@end itemize + +All other commands require a password to have been specified previously, +because they affect @code{chronyd's} operation. +@c }}} +@c {{{ SS:Chronyc command reference +@node Chronyc command reference +@subsection Command reference +@c {{{ Top/menu +This section describes each of the commands available within the chronyc +program. Chronyc offers the user a simple command-line driven +interface. + +@menu +* accheck command:: Verifying NTP client access +* activity command:: Check how many NTP servers/peers are online/offline +* add peer command:: Add a new NTP peer +* add server command:: Add a new NTP server +* allow command:: Allowing NTP client access +* allow all command:: Allowing NTP client access +* burst command:: Initiating a rapid set of measurements +* clients command:: Show clients that have accessed the server +* cmdaccheck command:: Verifying command client access +* cmdallow command:: Allowing command client access +* cmdallow all command:: Allowing command client access +* cmddeny command:: Denying command client access +* cmddeny all command:: Denying command client access +* cyclelogs command:: Close and re-open open log files +* delete command:: Remove an NTP server or peer +* deny command :: Denying NTP client access +* deny all command:: Denying NTP client access +* dump command:: Dump measurement histories to files +* exit command:: Exit from chronyc +* help command:: Generate help summary +* local command:: Let computer be a server when it is unsynchronised +* makestep command:: Immediately correct the system clock instead of slewing +* manual command:: Enable/disable/configure options for settime +* maxdelay command:: Set max measurement delay for a source +* maxdelayratio command:: Set max measurement delay for a source as ratio +* maxpoll command:: Set maximum polling interval for a source +* maxupdateskew command:: Set safety threshold for clock gain/loss rate +* minpoll command:: Set minimum polling interval for a source +* offline command:: Warn that connectivity to a source will be lost +* online command:: Warn that connectivity to a source has been restored +* password command:: Provide password needed for most commands +* quit command:: Exit from chronyc +* rtcdata command:: Display RTC parameters +* settime command:: Provide a manual input of the current time +* sources command:: Display information about the current set of sources +* sourcestats command:: Display the rate & offset estimation performance of sources +* tracking command:: Display system clock performance +* trimrtc command:: Correct the RTC time to the current system time +* writertc command:: Write the RTC parameters to file. +@end menu +@c }}} +@c {{{ accheck +@node accheck command +@subsubsection accheck +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: + +@example +accheck a.b.c +accheck 1.2.3.4 +@end example + +This command can be used to examine the effect of a series of +@code{allow}, @code{allow all}, @code{deny} and @code{deny all} commands +specified either via chronyc, or in @code{chronyd's} configuration file. +@c }}} +@c {{{ activity command +@node activity command +@subsubsection activity +This command reports the number of servers/peers that are online and offline. +If the auto_offline option is used in specifying some of the servers/peers, the +@code{activity} command may be useful for detecting when all of them have +entered the offline state after the PPP link has been disconnected. + +The report shows the number of servers/peers in 4 states: +@itemize +@item @code{online} : the server/peer is currently online (i.e. assumed by +chronyd to be reachable) +@item @code{offline} : the server/peer is currently offline (i.e. assumed by +chronyd to be unreachable, and no measurements from it will be attempted.) +@item @code{burst_online} : a burst command has been initiated for the +server/peer and is being performed; after the burst is complete, the +server/peer will be returned to the online state. +@item @code{burst_offline} : a burst command has been initiated for the +server/peer and is being performed; after the burst is complete, the +server/peer will be returned to the offline state. +@end itemize +@c }}} +@c {{{ add peer +@node add peer command +@subsubsection add peer +The @code{add peer} command allows a new NTP peer to be added whilst +@code{chronyd} is running. + +Following the words @code{add peer}, the syntax of the following +parameters and options is identical to that for the @code{peer} +directive in the configuration file (@pxref{peer directive}). + +An example of using this command is shown below. + +@example +add peer foo.bar.com minpoll 6 maxpoll 10 authkey 25 +@end example +@c }}} +@c {{{ add server +@node add server command +@subsubsection add server +The @code{add server} command allows a new NTP server to be added whilst +@code{chronyd} is running. + +Following the words @code{add server}, the syntax of the following +parameters and options is identical to that for the @code{server} +directive in the configuration file (@pxref{server directive}). + +An example of using this command is shown below. + +@example +add server foo.bar.com minpoll 6 maxpoll 10 authkey 25 +@end example +@c }}} +@c {{{ allow +@node allow command +@subsubsection allow +The effect of the allow command is identical to the @code{allow} directive in +the configuration file (@pxref{allow directive}). + +The syntax is illustrated in the following examples: + +@example +allow foo.bar.com +allow 1.2 +allow 3.4.5 +allow 6.7.8/22 +allow 6.7.8.9/22 +allow +@end example + +The effect of each of these examples is the same as that of the @code{allow} +directive in the configuration file. +@c }}} +@c {{{ allow all +@node allow all command +@subsubsection allow all +The effect of the allow command is identical to the @code{allow all} +directive in the configuration file (@pxref{allow directive}). +@c }}} +@c {{{ burst +@node burst command +@subsubsection burst +The @code{burst} command tells @code{chronyd} to make a set of measurements to +each of its sources over a short duration (rather than the usual +periodic measurements that it makes). After such a burst, @code{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. +(Switching a source between the online and offline states is described +in @ref{online command}, @ref{offline command}). + +The syntax of the burst command is as follows + +@example +burst / [/] +@end example + +The mask and masked-address arguments are optional, in which case +@code{chronyd} will initiate a burst for all of its currently defined sources. + +The arguments have the following meaning and format. + +@table @code +@item n-good-measurements +This defines the number of good measurements that @code{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 @code{chronyd} to reject measurements that are likely +to be bogus.) + +@item max-measurements +This defines the maximum number of measurements that @code{chronyd} will +attempt to make, even if the required number of good measurements has +not been obtained. + +@item mask +This is a dotted quad argument (e.g. @code{255.255.255.0}) with which +the IP address of each of @code{chronyd}'s sources is to be masked. + +@item masked-address +This is a dotted quad argument (e.g. @code{1.2.3.0}). If the masked IP +address of a source matches this value then the burst command is applied +to that source. +@end table + +If no mask or masked address arguments are provided, the default is +@code{0.0.0.0} and @code{0.0.0.0} respectively, which will match every +source. + +An example of the two-argument form of the command is + +@example +burst 2/10 +@end example + +This will cause @code{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. + +An example of the four-argument form of the command is + +@example +burst 2/10 255.255.0.0/1.2.0.0 +@end example + +In this case, the two out of ten sampling will only be applied to +sources whose IP addresses are of the form @code{1.2.x.y}, where x and y +are arbitrary. +@c }}} +@c {{{ clients +@node clients command +@comment node-name, next, previous, up +@subsubsection clients +This command shows a list of all clients that have accessed the server, +through either the NTP or command/monitoring ports. There are no arguments. + +An example of the output is + +@example +Hostname Client Peer CmdAuth CmdNorm CmdBad LstN LstC +========================= ====== ====== ====== ====== ====== ==== ==== +localhost 0 0 15 1 0 29y 0 +aardvark.xxx 4 0 0 0 0 49 29y +badger.xxx 4 0 0 0 0 6 29y +@end example + +Each row shows the data for a single host. Only hosts that have passed +the host access checks (set with the @code{allow}, @code{deny}, +@code{cmdallow} and @code{cmddeny} commands or configuration file +directives) are logged. + +The columns are as follows: + +@enumerate 1 +@item +The hostname of the client +@item +The number of times the client has accessed the server using an NTP +client mode packet. +@item +The number of times the client has accessed the server using an NTP +symmetric active mode packet. +@item +The number of authenticated command packets that have been processed +from the client (i.e. those following a successful @code{password} +command). +@item +The number of unauthenticated command packets that have been processed +from the client. +@item +The number of bad command packets received from the client (not all +forms of bad packet are logged). +@item +Time since the last NTP packet was received +@item +Time since the last command packet was received +@end enumerate + +The last two entries will be shown as the time since 1970 if no packet +of that type has ever been received. +@c }}} +@c {{{ cmdaccheck +@node cmdaccheck command +@subsubsection cmdaccheck +This command is similar to the @code{accheck} command, except that it is +used to check whether command access is permitted from a named host. + +Examples of use are as follows: + +@example +cmdaccheck a.b.c +cmdaccheck 1.2.3.4 +@end example +@c }}} +@c {{{ cmdallow +@node cmdallow command +@subsubsection cmdallow +This is similar to the @code{allow} command, except that it is used to +allow particular hosts or subnets to use the chronyc program to interact +with @code{chronyd} on the current host. +@c }}} +@c {{{ cmdallow all +@node cmdallow all command +@subsubsection cmdallow all +This is similar to the @code{allow all} command, except that it is used to@c {{{ +allow particular hosts or subnets to use the chronyc program to interact@c }}} +with @code{chronyd} on the current host. +@c }}} +@c {{{ cmddeny +@node cmddeny command +@subsubsection cmddeny +This is similar to the @code{deny} command, except that it is used to +allow particular hosts or subnets to use the chronyc program to interact +with @code{chronyd} on the current host. +@c }}} +@c {{{ cmddeny all +@node cmddeny all command +@subsubsection cmddeny all +This is similar to the @code{deny all} command, except that it is used +to allow particular hosts or subnets to use the chronyc program to +interact with @code{chronyd} on the current host. +@c }}} +@c {{{ cyclelogs +@node cyclelogs command +@subsubsection cyclelogs +The @code{cyclelogs} command causes all of @code{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. + +@example +% mv /var/log/chrony/measurements.log /var/log/chrony/measurements1.log +% chronyc +chronyc> password aardvark +200 OK +chronyc> cyclelogs +200 OK +chronyc> exit +% ls -l /var/log/chrony +-rw-r--r-- 1 root root 0 Jun 8 18:17 measurements.log +-rw-r--r-- 1 root root 12345 Jun 8 18:17 measurements1.log +% rm -f measurements1.log +@end example +@c }}} +@c {{{ delete +@node delete command +@subsubsection delete +The @code{delete} command allows an NTP server or peer to be removed +from the current set of sources. + +The syntax is illustrated in the examples below. + +@example +delete foo.bar.com +delete 1.2.3.4 +@end example + +There is one parameter, the name or IP address of the server or peer to +be deleted. +@c }}} +@c {{{ deny +@node deny command +@subsubsection deny +The effect of the allow command is identical to the @code{deny} +directive in the configuration file (@pxref{deny directive}). + +The syntax is illustrated in the following examples: + +@example +deny foo.bar.com +deny 1.2 +deny 3.4.5 +deny 6.7.8/22 +deny 6.7.8.9/22 +deny +@end example +@c }}} +@c {{{ deny all +@node deny all command +@subsubsection deny all +The effect of the allow command is identical to the @code{deny all} +directive in the configuration file (@pxref{deny directive}). +@c }}} +@c {{{ dump +@node dump command +@subsubsection dump +The @code{dump} command causes @code{chronyd} to write its current history of +measurements for each of its sources to dump files, either for +inspection or to support the @code{-r} option when @code{chronyd} is restarted. + +The @code{dump} command is somewhat equivalent to the @code{dumponexit} +directive in the chrony configuration file. @xref{dumponexit directive}. + +To use the @code{dump}, you probably want to configure the name of the +directory into which the dump files will be written. This can only be +done in the configuration file, see @ref{dumpdir directive}. +@c }}} +@c {{{ exit +@node exit command +@subsubsection exit +The exit command exits from chronyc and returns the user to the shell +(same as the quit command). +@c }}} +@c {{{ help +@node help command +@subsubsection help +The help command displays a summary of the commands and their arguments. +@c }}} +@c {{{ local +@node local command +@subsubsection local +The @code{local} command allows @code{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 @code{local} command is somewhat equivalent to the +@code{local} directive in the configuration file, see @ref{local directive}. + +The syntax is as shown in the following examples. + +@example +local stratum 10 +local off +@end example + +The first example enables the local reference mode on the host, and sets +the stratum at which it should claim to be synchronised. + +The second example disables the local reference mode. +@c }}} +@c {{{ makestep +@node makestep command +@subsubsection makestep +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 may be so far adrift that this +slewing process would take a very long time to correct the system clock. + +The @code{makestep} command can be used in this situation. It cancels +any remaining correction that was being slewed, and jumps the system +clock by the equivalent amount, making it correct immediately. + +BE WARNED - certain software will be seriously affected by such jumps to +the system time. (That is the reason why chronyd uses slewing +normally.) + +The @code{makestep} command is currently only available on the Linux +version of chrony. +@c }}} +@c {{{ manual +@node manual command +@subsubsection manual +The manual command enables and disables use of the @code{settime} +command (@pxref{settime command}), and is used to modify the behaviour +of the manual clock driver. + +Examples of the command are shown below. + +@example +manual on +manual off +manual delete 1 +manual list +manual reset +@end example + +The @code{on} form of the command enables use of the @code{settime} +command. + +The @code{off} form of the command disables use of the @code{settime} +command. + +The @code{list} form of the command lists all the samples currently +stored in @code{chronyd}. The output is illustrated below. + +@example +210 n_samples = 1 +# Date Time(UTC) Slewed Original Residual +==================================================== + 0 27Jan99 22:09:20 0.00 0.97 0.00 +@end example + +The columns as as follows : + +@enumerate 1 +@item +The sample index (used for the @code{manual delete} command) +@item +The date and time of the sample +@item +The system clock error when the timestamp was entered, adjusted to allow +for changes made to the system clock since. +@item +The system clock error when the timestamp was entered, as it originally +was (without allowing for changes to the system clock since). +@item +The regression residual at this point, in seconds. This allows +'outliers' to be easily spotted, so that they can be deleted using the +@code{manual delete} command. +@end enumerate + +The @code{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 @code{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 he/she has entered a very poor +timestamp. + +The @code{reset} form of the command deletes all samples at once. The +system clock is left running as it was before the command was entered. +@c }}} +@c {{{ maxdelay +@node maxdelay command +@subsubsection maxdelay +This allows the @code{maxdelay} option for one of the sources to be +modified, in the same way as specifying the @code{maxdelay} option for +the @code{server} directive in the configuration file (@pxref{server +directive}). + +The following examples illustrate the syntax + +@example +maxdelay foo.bar.com 0.3 +maxdelay 1.2.3.4 0.0015 +@end example + +The first example sets the maximum network delay allowed for a +measurement to the host @code{foo.bar.com} to 0.3 seconds. The second +example sets the maximum network delay for a measurement to the host +with IP address @code{1.2.3.4} to 1.5 milliseconds. + +(Any measurement whose network delay exceeds the specified value is +discarded.) +@c }}} +@c {{{ maxdelayratio +@node maxdelayratio command +@subsubsection maxdelayratio +This allows the @code{maxdelayratio} option for one of the sources to be +modified, in the same way as specifying the @code{maxdelayratio} option +for the @code{server} directive in the configuration file (@pxref{server +directive}). + +The following examples illustrate the syntax + +@example +maxdelayratio foo.bar.com 1.5 +maxdelayratio 1.2.3.4 2.0 +@end example + +The first example sets the maximum network delay for a measurement to +the host @code{foo.bar.com} to be 1.5 times the minimum delay found +amongst the previous measurements that have been retained. The second +example sets the maximum network delay for a measurement to the host +with IP address @code{1.2.3.4} to be double the retained minimum. + +As for @code{maxdelay}, any measurement whose network delay is too large +will be discarded. +@c }}} +@c {{{ maxpoll +@node maxpoll command +@subsubsection maxpoll +The @code{maxpoll} command is used to modify the minimum polling +interval for one of the current set of sources. It is equivalent to the +@code{maxpoll} option in the @code{server} directive in the +configuration file (@pxref{server directive}). + +The syntax is as follows + +@example +maxpoll +@end example + +where the host can be specified as either a machine name or dotted-quad +IP address. The new minimum poll is specified as a base-2 logarithm of +the number of seconds between polls (e.g. specify 6 for 64 second +sampling). + +An example is + +@example +maxpoll foo.bar.com 10 +@end example + +which sets the maximum polling interval for the host @code{foo.bar.com} +to 1024 seconds. + +Note that the new maximum polling interval only takes effect after the +next measurement has been made. +@c }}} +@c {{{ maxupdateskew +@node maxupdateskew command +@subsubsection maxupdateskew +This command has the same effect as the @code{maxupdateskew} directive +in the configuration file, see @ref{maxupdateskew directive}. +@c }}} +@c {{{ minpoll +@node minpoll command +@subsubsection minpoll +The @code{minpoll} command is used to modify the minimum polling +interval for one of the current set of sources. It is equivalent to the +@code{minpoll} option in the @code{server} directive in the +configuration file (@pxref{server directive}). + +The syntax is as follows + +@example +minpoll +@end example + +where the host can be specified as either a machine name or dotted-quad +IP address. The new minimum poll is specified as a base-2 logarithm of +the number of seconds between polls (e.g. specify 6 for 64 second +sampling). + +An example is + +@example +minpoll foo.bar.com 5 +@end example + +which sets the minimum polling interval for the host @code{foo.bar.com} +to 32 seconds. + +Note that the new minimum polling interval only takes effect after the +next measurement has been made. +@c }}} +@c {{{ offline +@node offline command +@subsubsection offline +The @code{offline} command is used to warn @code{chronyd} that the network +connection to a particular host or hosts is about to be lost. It should +be used on computers with a dial-up or similar connection to their time +sources, to warn @code{chronyd} that the connection is about to be broken. + +An example of how to use @code{offline} in this case is shown in +@ref{Advising chronyd of internet availability}. + +Another case where @code{offline} could be used is where a computer +serves time to a local group of computers, and has a permanant +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/loss rate during +the quiet periods and let the whole network coast through the loaded +periods. The @code{offline} and @code{online} commands can be used to +achieve this. The situation is shown in the figure below. + +@example +@group + +----------+ + |Ext source| + +----------+ + | + | + |/| <-- Link with variable + | reliability + | + +-------------------+ + |Local master server| + +-------------------+ + | + +---+---+-----+-----+----+----+ + | | | | | | | + Local clients +@end group +@end example + + + +If the source to which @code{chronyd} is currently synchronised is indicated +offline in this way, @code{chronyd} will continue to treat it as the +synchronisation source. If the network connection were broken without +the @code{offline} command being used, @code{chronyd} would assume that the +source had failed and would attempt to pick another synchronisation +source. + +There are two forms of the @code{offline} command. The first form is a +wildcard, meaning all sources. The second form allows a IP address mask +and a masked address to be specified. These forms are illustrated below. + +@example +offline +offline 255.255.255.0/1.2.3.0 +@end example + +The second form means that the @code{offline} command is to be applied +to any source whose IP 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 wildcard form of the address is actually equivalent to + +@example +offline 0.0.0.0/0.0.0.0 +@end example +@c }}} +@c {{{ online +@node online command +@subsubsection online +The @code{online} command is opposite in function to the @code{offline} +command. It is used to advise @code{chronyd} that network connectivity to a +particular source or sources has been restored. + +The syntax is identical to that of the @code{offline} command, see +@ref{offline command}. +@c }}} +@c {{{ password +@node password command +@subsubsection password +The password command is used to allow chronyc to send privileged +commands to @code{chronyd}. The password can either be entered on the command +line, or can be entered without echoing. The syntax for entering the +password on the command line is as follows + +@example +password xyzzy +@end example + +To enter the password without it being echoed, enter + +@example +password +@end example + +The computer will respond with a @samp{Password:} prompt, at which you +should enter the password and press return. (Note that the no-echo mode +is limited to 8 characters on SunOS 4.1 due to limitations in the system +library. Other systems do not have this restriction.) + +The password is any string of characters not containing whitespace. It +has to match @code{chronyd's} currently defined command key (@pxref{commandkey +directive}). +@c }}} +@c {{{ quit +@node quit command +@subsubsection quit +The quit command exits from chronyc and returns the user to the shell +(same as the exit command). +@c }}} +@c {{{ rtcdata +@node rtcdata command +@subsubsection rtcdata +The @code{rtcdata} command displays the current real time clock RTC parameters. + +An example output is shown below. + +@example +RTC ref time (GMT) : Sat May 30 07:25:56 1998 +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 +@end example + +The fields have the following meaning + +@table @code +@item RTC ref time (GMT) +This is the RTC reading the last time its error was measured. +@item Number of samples +This is the number of previous measurements being used to determine the +RTC gain/loss rate. +@item 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. +@item 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 may be used. +@item 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 +may (or may not) want to use the @code{trimrtc} command to bring the RTC +into line with the system clock. (Note, a large error will not affect +@code{chronyd's} operation, unless it becomes so big as to start causing +rounding errors. +@item 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. +@end table +@c }}} +@c {{{ settime +@node settime command +@subsubsection settime +The @code{settime} command allows the current time to be entered +manually, if this option has been configured into @code{chronyd}. (It may be +configured either with the @code{manual} directive in the configuration +file (@pxref{manual directive}), or with the @code{manual} command of +chronyc (@pxref{manual command}). + +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. +When inputting time to an isolated network, I have a battery operated +alarm clock that is synchronised to the Rugby MSF time signal in the UK. + +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 @code{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 @code{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 @code{manual delete} command, where the +intercept is used to set the current offset (since there is no +measurement that has just been typed in in that case). + +The time is parsed by the public domain @file{getdate} algorithm. +Consequently, you can only specify time to the nearest second. + +Examples of inputs that are valid are shown below. + +@example +settime 16:30 +settime 16:30:05 +settime Nov 21, 1997 16:30:05 +@end example + +For a full description of @code{getdate}, get hold of the getdate +documentation (bundled, for example, with the source for GNU tar). +@c }}} +@c {{{ sources +@node sources command +@subsubsection sources +This command displays information about the current time sources that +@code{chronyd} is accessing. + +The optional argument @code{-v} can be specified, meaning @emph{verbose}. In +this case, extra caption lines are shown as a reminder of the meanings of the +columns. + +@example +@group +210 Number of sources = 3 +MS Name/IP address Stratum Poll LastRx Last sample +======================================================================= +^+ a.b.c 3 6 47m -9491us[-6983us] +/- 159ms +^+ d.e.f 3 6 47m +32ms[ +35ms] +/- 274ms +^* g.h.i 2 6 47m +8839us[ +11ms] +/- 214ms +@end group +@end example + +The columns are as follows: + +@table @code +@item M +This indicates the mode of the source. @code{^} means a server, +@code{=} means a peer and @code{#} indicates a locally connected +reference clock@footnote{In the current version this will never be +shown, because @code{chronyd} has no support for reference clocks yet.}. + +@item S +This column indicates the state of the sources. @code{*} indicates the +source to which @code{chronyd} is current synchronised. @code{+} indicates +other acceptable sources. @code{?} indicates sources to which +connectivity has been lost. @code{x} indicates a clock which @code{chronyd} +thinks is is a falseticker (i.e. its time is inconsistent with a +majority of other sources). @code{~} indicates a source whose time +appears to have too much variability. The @code{~} condition is also +shown at start-up, until at least 3 samples have been gathered from it. + +@item Name/IP address +This shows the name or the IP address of the source. + +@item 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. + +@item 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. + +@code{chronyd} automatically varies the polling rate in response to prevailing +conditions. + +@item LastRx +This column shows how long ago the last sample was received from the +source. This is normally in seconds. The letters @code{m}, @code{h}, +@code{d} or @code{y} indicate minutes, hours, days or years. + +@item 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 may be suffixed by @code{us} (indicating +microseconds), @code{ms} (indicating milliseconds), or @code{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 @code{+/-} indicator +shows the margin of error in the measurement. + +Positive offsets indicate that the local clock is fast of the source. + +@end table +@c }}} +@c {{{ sourcestats +@node sourcestats command +@subsubsection sourcestats + +The @code{sourcestats} command displays information about the drift rate +and offset estimatation process for each of the sources currently being +examined by @code{chronyd}. + +The optional argument @code{-v} can be specified, meaning @emph{verbose}. In +this case, extra caption lines are shown as a reminder of the meanings of the +columns. + +An example report is + +@example +@group +210 Number of sources = 1 +Name/IP Address NP NR Span Frequency Freq Skew Std Dev +======================================================================== +abc.def.ghi 11 5 46m -0.001 0.045 25us +@end group +@end example + +The columns are as follows + +@table @code +@item Name/IP Address +This is the name or dotted-quad IP address of the NTP server (or peer) +to which the rest of the line relates. + +@item 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. + +@item 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, +@code{chronyd} discards older samples and re-runs the regression until +the number of runs becomes acceptable. + +@item 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. + +@item 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. + +@item Freq Skew +This is the estimated error bounds on @code{Freq} (again in parts per +million). + +@item Std Dev +This is the estimated sample standard deviation. + +@end table +@c }}} +@c {{{ tracking +@node tracking command +@subsubsection tracking +The @code{tracking} command displays parameters about the system's clock +performance. An example of the output is shown below. + +@example +Reference ID : 1.2.3.4 (a.b.c) +Stratum : 3 +Ref time (UTC) : Sun May 17 06:13:11 1998 +System time : 0.000000 seconds fast of NTP time +Frequency : 331.898 ppm fast +Residual freq : 0.004 ppm +Skew : 0.154 ppm +Root delay : 0.373169 seconds +Root dispersion : 0.024780 seconds +@end example + +The fields are explained as follows. + +@table @code +@item Reference ID +This is the IP address, and name if available, of the server to which +the computer is currently synchronised. If this is @code{127.127.1.1} +it means the computer is not synchronised to any external source and +that you have the `local' mode operating (via the @code{local} command +in @code{chronyc} (@pxref{local command}), or the @code{local} directive +in the @file{/etc/chrony.conf} file (@pxref{local directive})). + +@item 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. @code{a.b.c} is a stratum-2 and is synchronised from a stratum-1). + +@item Ref time +This is the time (GMT) at which the last measurement from the reference +source was processed. + +@item System time +In normal operation, @code{chronyd} @emph{never} steps the system clock, +because any jump in the timescale can have adverse consequences for +certain application programs. Instead, any error in the system clock is +corrected by slightly speeding up or slowing down the system clock until +the error has been removed, and then returning to the system clock's +normal speed. A consequence of this is that there will be a period when +the system clock (as read by other programs using the +@code{gettimeofday()} system call, or by the @code{date} command in the +shell) will be different from @code{chronyd's} estimate of the current +true time (which it reports to NTP clients when it is operating in +server mode). The value reported on this line is the difference due to +this effect. + +On systems such as Solaris and SunOS, @code{chronyd} has no means to +adjust the fundamental rate of the system clock, so keeps the system +time correct by periodically making offsets to it as though an error had +been measured. The build up of these offsets will be observed in this +report. On systems such as Linux where @code{chronyd} can adjust the +fundamental rate of the system clock, this value will show zero unless a +very recent measurement has shown the system to be error. + +@item Frequency +The `frequency' is the rate by which the system's clock would be would +be wrong if @code{chronyd} was not correcting it. It is expressed in +ppm (parts per million). For example, a value of 1ppm 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. + +As you can see in the example, the clock in the computer I developed +@code{chrony} on is not a very good one - it gains about 30 seconds per +day! This was the reason I started to write @code{chrony} in the first +place. + +@item 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. + +@item Skew +This is the estimated error bound on the the frequency. + +@item Root delay +This is the total of the network path delays to the stratum-1 computer +from which the computer is ultimately synchronised. + +In certain extreme situations, this value can be negative. (This can +arise in a symmetric peer arrangement where the computers' frequencies +are not tracking each other and the network delay is very short relative +to the turn-around time at each computer.) + +@item 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 + +@example +clock_error <= root_dispersion + (0.5 * |root_delay|) +@end example + +@end table +@c }}} +@c {{{ trimrtc +@node trimrtc command +@subsubsection trimrtc +The @code{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 +resolution of the RTC is only 1 second). + +The command takes no arguments. It performs the following steps (if the +RTC is more than 1 second away from the system clock): + +@enumerate 1 +@item +Remember the currently estimated gain/loss rate of the RTC and flush the +previous measurements. +@item +Step the real time clock to bring it within a second of the system clock. +@item +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) +@item +Save the RTC parameters to the RTC file (specified with the +@code{rtcfile} directive in the configuration file (@pxref{rtcfile +directive}). +@end enumerate + +The last step is done as a precaution against the computer suffering a +power failure before either the daemon exits or the @code{writertc} +command is issued. + +@code{chronyd} will still work perfectly well both whilst operating and +across machine reboots even if the @code{trimrtc} command is never used +(and the RTC is allowed to drift away from true time). The +@code{trimrtc} command is provided as a method by which it can be +corrected, in a manner compatible with @code{chronyd} using it to +maintain accurate time across machine reboots. +@c }}} +@c {{{ writertc +@node writertc command +@subsubsection writertc +The @code{writertc} command writes the currently estimated error and +gain/loss rate parameters for the RTC to the RTC file (specified with +the @code{rtcfile} directive (@pxref{rtcfile directive})). This +information is also written automatically when @code{chronyd} is killed +(with SIGHUP, SIGINT, SIGQUIT or SIGTERM). +@c }}} +@c }}} +@c }}} +@c }}} +@c {{{ apx: porting guide +@node Porting guide +@appendix Porting guide +@c {{{ section top +This appendix discusses issues that have arisen in writing the +system-specific parts of the existing ports. This will provide useful +information for those attempting to write ports to other systems. + + +@menu +* System driver files:: What needs to go in a driver file for a + particular type of system +* Quirks of particular systems:: Problem areas that have been found on ports + already written. +@end menu +@c }}} +@c {{{ S:system driver files +@node System driver files +@section System driver files +The system specific parts of the software are contained in files with +names like @code{sys_linux.c}. + +The following functions are required in a system driver file: + +@enumerate +@item +A function to read the current frequency +@item +A function to set the current frequency +@item +A function to slew the system time by a specified delta +@item +A function to step the system time by a specified delta +@item +A function to work out the error at a particular time between the +system's clock and @code{chronyd's} estimate of real time. (This is required +because some systems have to track real time by making the system time +follow it in a 'sawtooth' fashion). +@end enumerate + +The @dfn{frequency} is the rate at which the system gains or loses time, +measured relative to the system when running uncompensated. +@c }}} +@c {{{ system quirks +@node Quirks of particular systems +@section Quirks of particular systems +@c {{{ section top +These sections describe quirks in each system type that needed to be +investigated to port the software to each system type. + +@menu +* Linux porting quirks:: +* Solaris 2.5 porting quirks:: +* SunOS 4.1.4 porting quirks:: +@end menu +@c }}} +@c {{{ linux +@node Linux porting quirks +@subsection Linux +The following quirks have been found in developing the Linux port. + +@enumerate 1 +@item +In order to avoid floating point arithmetic, the kernel uses shifting +and adding to approximate a scaling of 100/128. This approximation +implies that the frequency set via the @code{adjtimex()} system call is +not the frequency that is actually obtained. The method of +approximation varies between kernel versions and must be determined by +examining the kernel source. An inverse factor must be included in the +driver to compensate. +@item +In some kernel versions, an @code{adjtimex()} system call with the flags +bits all zeroed will return the amount of offset still to be corrected. +In others (e.g. the 2.0 series beyond 2.0.32), the offset must be +changed in order to get the old offset returned (similar to +@code{adjtime()} on other systems). + +@end enumerate +@c }}} +@c {{{ solaris 2.5 +@node Solaris 2.5 porting quirks +@subsection Solaris 2.5 + +The following quirks have been found in developing the Solaris port. + +@enumerate 1 +@item +The @code{adjtime()} system call with a zero argument does not cancel an +adjustment that is in progress - it just reports the remaining +adjustment. +@item +The @code{settimeofday()} system call only observes the seconds part of +the argument - any fractional seconds part is lost. +second. +@item +The kernel variable @code{dosynctodr} has to be set to zero, otherwise +the system clock is periodically reset to the real-time clock. +@end enumerate +@c }}} +@c {{{ sunos 4.1.4 +@node SunOS 4.1.4 porting quirks +@subsection SunOS 4.1.4 +The following quirks have been found in developing the SunOS port. + +@enumerate 1 +@item +The @code{adjtime()} system call truncates its argument to a multiple of +the system's @code{tickadj} variable. (@code{chronyd} sets that to 100, +giving a 1 part in 100 slewing capability for correcting offsets.) +@item +The kernel variable @code{dosynctodr} has to be set to zero, otherwise +the system clock is periodically reset to the real-time clock. +@end enumerate +@c }}} +@c }}} +@c }}} +@c {{{ apx:GNU General Public License +@node GPL +@appendix GNU General Public License + +@center GNU GENERAL PUBLIC LICENSE +@center Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 Library 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 + + Appendix: 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. + + + Copyright (C) 19yy + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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) 19yy 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. + + , 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 Library General +Public License instead of this License. +@c }}} +@contents +@bye +@c vim:cms=@c\ %s:fdm=marker:fdc=5:syntax=off + diff --git a/chronyc.1 b/chronyc.1 new file mode 100644 index 0000000..9ac9e95 --- /dev/null +++ b/chronyc.1 @@ -0,0 +1,56 @@ +.TH CHRONYC 1 "August 10, 2001" chrony "User's Manual" +.SH NAME +chronyc \- command-line interface for chronyd + +.SH SYNOPSIS +.B chronyc +[\fIOPTIONS\fR] + +.SH DESCRIPTION +\fIchrony\fR is a pair of programs for maintaining the accuracy of computer +clocks. + +\fBchronyc\fR is a command-line interface program which can be used to +monitor \fIchronyd\fR's performance and to change various operating +parateters whilst it is running. + +.SH USAGE +A detailed description of all commands supported by \fBchronyc\fR is available +via the documentation supplied with the distribution (\fIchrony.txt\fR and +\fIchrony.texi\fR). + +.SH OPTIONS +A summary of the options supported by \fBchronyc\fR is included below. + +.TP +\fB\-h\fR \fIhostname\fR +specify hostname +.TP +\fB\-p\fR \fIport-number\fR +specify port-number +.TP +\fB\-n\fR +display raw IP addresses (don't attempt to look up hostnames) +.TP \fIcommand\fR +specify command. If no command is given, chronyc will read commands +interactively. + + +.SH VERSION +1.17 + +.SH BUGS +To report bugs, please contact the author and/or visit \fIhttp://go.to/chrony\fR + +.SH "SEE ALSO" +.BR chronyd(1), +.BR chrony(1) + +.I http://chrony.sunsite.dk/ + +.SH AUTHOR +Richard Curnow + +This man-page was written by Jan Schaumann as part of "The Missing +Man Pages Project". Please see \fIhttp://www.netmeister.org/misc/m2p2/index.html\fR +for details. diff --git a/chronyd.8 b/chronyd.8 new file mode 100644 index 0000000..4f5fa06 --- /dev/null +++ b/chronyd.8 @@ -0,0 +1,111 @@ +.TH CHRONYD 8 "August 10, 2001" chrony "System Administration" +.SH NAME +chronyd \- chrony background daemon + +.SH SYNOPSIS +.B chronyd +[\fIOPTIONS\fR] + +.SH DESCRIPTION +\fIchrony\fR is a pair of programs for maintaining the accuracy of computer +clocks. \fBchronyd\fR is a background daemon program that can be started at boot +time. + +\fBchronyd\fR is a daemon which runs in background on the +system. It obtains measurements (e.g. via the network) of the +system's offset relative to other systems, and adjusts the system +time accordingly. For isolated systems, the user can periodically +enter the correct time by hand (using \fIchronyc\fR). In either case, +\fBchronyd\fR determines the rate at which the computer +gains or loses time, and compensates for this. + +.SH USAGE +\fBchronyd\fR is usually started at boot-time and requires superuser +priviliges. + +If \fBchronyd\fR has been installed to its default location +\fI/usr/local/sbin/chronyd\fR, starting it is simply a matter of entering the +command: + +\fI/usr/local/sbin/chronyd\fR + +Information messages and warnings will be logged to syslog. + + +.SH OPTIONS +A summary of the options supported by \fBchronyd\fR is included below. + +.TP +.B \-d +When run in this mode, the program will not detach itself from the +terminal, and all messages will be sent to the terminal instead of +to syslog. +.TP +\fB\-f\fR \fIconf-file\fR +This option can be used to specify an alternate location for the +configuration file (default \fI/etc/chrony.conf\fR). +.TP +.B \-r +This option will reload sample histories for each of the servers being used. +These histories are created by using the \fIdump\fR command in \fIchronyc\fR, +or by setting the \fIdumponexit\fR directive in the configuration file. This +option is useful if you want to stop and restart \fBchronyd\fR briefly for any +reason, e.g. to install a new version. However, it only makes sense on +systems where the kernel can maintain clock compensation whilst not under +\fBchronyd\fR's control. The only version where this happens so far is Linux. +On systems where this is not the case, e.g. Solaris and SunOS the option +should not be used. +.TP +.B \-s +This option will set the system clock from the computer's real-time +clock. This is analogous to supplying the \fI-s\fR flag to the +\fI/sbin/clock\fR program during the Linux boot sequence. + +Support for real-time clocks is limited at present - the criteria +are described in the section on the \fIrtcfile\fR directive in the +documentation supplied with the distribution. + +If \fBchronyd\fR cannot support the real time clock on your computer, +this option cannot be used and a warning message will be logged to +the syslog. + +If used in conjunction with the \fB-r\fR flag, \fBchronyd\fR will attempt +to preserve the old samples after setting the system clock from +the real time clock. This can be used to allow \fBchronyd\fR to +perform long term averaging of the gain or loss rate across system +reboots, and is useful for dial-up systems that are shut down when +not in use. For this to work well, it relies on \fBchronyd\fR having +been able to determine accurate statistics for the difference +between the real time clock and system clock last time the +computer was on. +.TP +.B \-v +This option displays \fBchronyd\fR's version number to the terminal and exits + +.SH FILES +\fI/etc/chrony.conf\fR + +.SH VERSION +Version 1.17 + +.SH BUGS +To report bugs, please contact the author and/or visit \fIhttp://chrony.sunsite.dk/\fR + +.SH "SEE ALSO" +\fBchronyd\fR is documented in detail in the documentation supplied with the +distribution (\fIchrony.txt\fR and \fIchrony.texi\fR) and is also available +from \fIhttp://go.to/chrony\fR + +.BR chrony(1), +.BR chronyc(1), +.BR chrony.conf(5), +.BR clock(8), +.BR xntpd(8), +.BR ntpd(8) + +.SH AUTHOR +Richard Curnow + +This man-page was written by Jan Schaumann as part +of "The Missing Man Pages Project". Please see +\fIhttp://www.netmeister.org/misc/m2p2/index.html\fR for details. diff --git a/client.c b/client.c new file mode 100644 index 0000000..04d4b44 --- /dev/null +++ b/client.c @@ -0,0 +1,2589 @@ +/* + $Header: /cvs/src/chrony/client.c,v 1.66 2003/01/20 22:52:07 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Command line client for configuring the daemon and obtaining status + from it whilst running. + */ + +#include "sysincl.h" + +#include "candm.h" +#include "nameserv.h" +#include "md5.h" +#include "version.h" +#include "getdate.h" +#include "cmdparse.h" +#include "pktlength.h" +#include "memory.h" + +#ifdef FEAT_READLINE +#include +#include +#endif + +/* ================================================== */ + +static int sock_fd; +struct sockaddr_in his_addr; + +static int on_terminal = 0; + +static int no_dns = 0; + +/* ================================================== */ +/* Forward prototypes */ + +static void process_cmd_manual_list(const char *line); +static void process_cmd_manual_delete(const char *line); + +/* ================================================== */ +/* Ought to extract some code from util.c to make + a new set of utilities that can be linked into either + the daemon or the client. */ + +static char * +time_to_log_form(time_t t) +{ + struct tm stm; + static char buffer[64]; + static const char *months[] = {"Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec"}; + + + stm = *gmtime(&t); + sprintf(buffer, "%2d%s%02d %02d:%02d:%02d", + stm.tm_mday, months[stm.tm_mon], stm.tm_year % 100, + stm.tm_hour, stm.tm_min, stm.tm_sec); + + return buffer; +} + +/* ================================================== */ + +static char * +UTI_IPToDottedQuad(unsigned long ip) +{ + unsigned long a, b, c, d; + static char result[64]; + a = (ip>>24) & 0xff; + b = (ip>>16) & 0xff; + c = (ip>> 8) & 0xff; + d = (ip>> 0) & 0xff; + sprintf(result, "%ld.%ld.%ld.%ld", a, b, c, d); + return result; +} + +/* ================================================== */ +/* Read a single line of commands from standard input. Eventually we + might want to use the GNU readline library. */ + +static char * +read_line(void) +{ + static char line[2048]; + static const char *prompt = "chronyc> "; + + if (on_terminal) { +#ifdef FEAT_READLINE + char *cmd; + + /* 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(prompt); +#endif + } + if (fgets(line, sizeof(line), stdin)) { + return line; + } else { + return NULL; + } + +} + +/* ================================================== */ + +static unsigned long +get_address(const char *hostname) +{ + unsigned char *address0; + struct hostent *host; + unsigned long result; + + /* Note, this call could block for a while */ + host = gethostbyname(hostname); + if (host == NULL) { + fprintf(stderr, "Could not get IP address for %s\n", hostname); + exit(1); + } else { + address0 = host->h_addr_list[0]; + result = ((((unsigned long) address0[0]) << 24) | + (((unsigned long) address0[1]) << 16) | + (((unsigned long) address0[2]) << 8) | + (((unsigned long) address0[3]))); + } + + return result; + +} + +/* ================================================== */ +/* Initialise the socket used to talk to the daemon */ + +static void +open_io(const char *hostname, int port) +{ + struct sockaddr_in my_addr; + + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd < 0) { + perror("Can't create socket"); + exit(1); + } + + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(INADDR_ANY); + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + perror("Can't bind socket"); + exit(1); + } + + /* Build the socket address structure for sending packets */ + his_addr.sin_family = AF_INET; + his_addr.sin_addr.s_addr = htonl(get_address(hostname)); + + /* Eventually the port number needs to be a command line param */ + his_addr.sin_port = htons(port); + + return; +} + +/* ================================================== */ + +static void +close_io(void) +{ + + close(sock_fd); + +} + +/* ================================================== */ + +static int +read_mask_address(char *line, unsigned long *mask, unsigned long *address) +{ + unsigned int ma, mb, mc, md, aa, ab, ac, ad; + int ok = 0; + char *p; + + p = line; + while (*p && isspace((unsigned char)*p)) p++; + if (!*p) { + *mask = *address = 0; + ok = 1; + } else { + + if (sscanf(line, "%u.%u.%u.%u/%u.%u.%u.%u", + &ma, &mb, &mc, &md, + &aa, &ab, &ac, &ad) != 8) { + fprintf(stderr, "Invalid syntax for mask/address\n"); + ok = 0; + } else { + *mask = (ma << 24) | (mb << 16) | (mc << 8) | md; + *address = (aa << 24) | (ab << 16) | (ac << 8) | ad; + ok = 1; + } + } + + return ok; + +} + +/* ================================================== */ + +static int +process_cmd_offline(CMD_Request *msg, char *line) +{ + unsigned long mask, address; + int ok; + + if (read_mask_address(line, &mask, &address)) { + msg->data.offline.mask = htonl(mask); + msg->data.offline.address = htonl(address); + msg->command = htons(REQ_OFFLINE); + ok = 1; + } else { + ok = 0; + } + + return ok; + +} + +/* ================================================== */ + + +static int +process_cmd_online(CMD_Request *msg, char *line) +{ + unsigned long mask, address; + int ok; + + if (read_mask_address(line, &mask, &address)) { + msg->data.online.mask = htonl(mask); + msg->data.online.address = htonl(address); + msg->command = htons(REQ_ONLINE); + ok = 1; + } else { + ok = 0; + } + + return ok; + +} + +/* ================================================== */ + +static int +read_address_integer(char *line, unsigned long *address, int *value) +{ + char hostname[2048]; + int ok = 0; + + if (sscanf(line, "%2047s %d", hostname, value) != 2) { + fprintf(stderr, "Invalid syntax for address value\n"); + ok = 0; + } else { + *address = DNS_Name2IPAddress(hostname); + if (*address == DNS_Failed_Address) { + fprintf(stderr, "Could not get address for hostname\n"); + ok = 0; + } else { + ok = 1; + } + } + + return ok; + +} + + +/* ================================================== */ + +static int +read_address_double(char *line, unsigned long *address, double *value) +{ + char hostname[2048]; + int ok = 0; + + if (sscanf(line, "%2047s %lf", hostname, value) != 2) { + fprintf(stderr, "Invalid syntax for address value\n"); + ok = 0; + } else { + *address = DNS_Name2IPAddress(hostname); + if (*address == DNS_Failed_Address) { + fprintf(stderr, "Could not get address for hostname\n"); + ok = 0; + } else { + ok = 1; + } + } + + return ok; + +} + + +/* ================================================== */ + +static int +process_cmd_minpoll(CMD_Request *msg, char *line) +{ + unsigned long address; + int minpoll; + int ok; + + if (read_address_integer(line, &address, &minpoll)) { + msg->data.modify_minpoll.address = htonl(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) +{ + unsigned long address; + int maxpoll; + int ok; + + if (read_address_integer(line, &address, &maxpoll)) { + msg->data.modify_maxpoll.address = htonl(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) +{ + unsigned long address; + double max_delay; + int ok; + + if (read_address_double(line, &address, &max_delay)) { + msg->data.modify_maxdelay.address = htonl(address); + msg->data.modify_maxdelay.new_max_delay = REAL2WIRE(max_delay); + msg->command = htons(REQ_MODIFY_MAXDELAY); + ok = 1; + } else { + ok = 0; + } + + return ok; + +} + +/* ================================================== */ + +static int +process_cmd_maxdelayratio(CMD_Request *msg, char *line) +{ + unsigned long address; + double max_delay_ratio; + int ok; + + if (read_address_double(line, &address, &max_delay_ratio)) { + msg->data.modify_maxdelayratio.address = htonl(address); + msg->data.modify_maxdelayratio.new_max_delay_ratio = REAL2WIRE(max_delay_ratio); + msg->command = htons(REQ_MODIFY_MAXDELAYRATIO); + 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 = REAL2WIRE(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); +} + +/* ================================================== */ + +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 ok; + int n_good_samples, n_total_samples; + unsigned int ma, mb, mc, md, aa, ab, ac, ad; + int n_parsed; + + n_parsed = sscanf(line, "%d/%d %u.%u.%u.%u/%u.%u.%u.%u", + &n_good_samples, + &n_total_samples, + &ma, &mb, &mc, &md, + &aa, &ab, &ac, &ad); + + 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); + + if (n_parsed == 10) { + msg->data.burst.mask = htonl((ma << 24) | (mb << 16) | (mc << 8) | md); + msg->data.burst.address = htonl((aa << 24) | (ab << 16) | (ac << 8) | ad); + ok = 1; + } else if (n_parsed == 2) { + msg->data.burst.mask = 0; + msg->data.burst.address = 0; + ok = 1; + } else { + ok = 0; + fprintf(stderr, "Invalid syntax for burst command\n"); + } + + return ok; + +} + +/* ================================================== */ + +static int +process_cmd_local(CMD_Request *msg, const char *line) +{ + const char *p; + int stratum; + + p = line; + while (*p && isspace((unsigned char)*p)) p++; + + if (!*p) { + return 0; + } else if (!strncmp(p, "off", 3)) { + msg->data.local.on_off = htonl(0); + } else if (sscanf(p, "stratum%d", &stratum) == 1) { + msg->data.local.on_off = htonl(1); + msg->data.local.stratum = htonl(stratum); + } else { + fprintf(stderr, "Invalid syntax for local command\n"); + return 0; + } + + msg->command = htons(REQ_LOCAL); + return 1; +} + +/* ================================================== */ + +static int +process_cmd_manual(CMD_Request *msg, const char *line) +{ + const char *p; + + p = line; + while (*p && isspace((unsigned char)*p)) p++; + + if (!*p) { + return 0; + } else if (!strncmp(p, "off", 3)) { + msg->data.manual.option = htonl(0); + } else if (!strncmp(p, "on", 2)) { + msg->data.manual.option = htonl(1); + } else if (!strncmp(p, "reset", 5)) { + msg->data.manual.option = htonl(2); + } else if (!strncmp(p, "list", 4)) { + process_cmd_manual_list(p); + return 0; + } else if (!strncmp(p, "delete", 6)) { + process_cmd_manual_delete(p+6); + return 0; + } else { + return 0; + } + msg->command = htons(REQ_MANUAL); + + return 1; +} + +/* ================================================== */ + +static int +parse_allow_deny(CMD_Request *msg, char *line) +{ + unsigned long a, b, c, d, n, ip; + char *p, *q; + + p = line; + while (*p && isspace((unsigned char)*p)) p++; + if (!*p) { + /* blank line - applies to all addresses */ + msg->data.allow_deny.ip = htonl(0); + msg->data.allow_deny.subnet_bits = htonl(0); + } else { + char *slashpos; + slashpos = strchr(p, '/'); + if (slashpos) *slashpos = 0; + + n = sscanf(p, "%lu.%lu.%lu.%lu", &a, &b, &c, &d); + + if (n == 0) { + /* Try to parse as the name of a machine */ + q = p; + while (*q) { + if (*q == '\n') *q = 0; + q++; + } + ip = DNS_Name2IPAddress(p); + if (ip == DNS_Failed_Address) { + fprintf(stderr, "Could not read address\n"); + return 0; + } else { + msg->data.allow_deny.ip = htonl(ip); + msg->data.allow_deny.subnet_bits = htonl(32); + } + } else { + + a &= 0xff; + b &= 0xff; + c &= 0xff; + d &= 0xff; + + switch (n) { + case 1: + msg->data.allow_deny.ip = htonl((a<<24)); + msg->data.allow_deny.subnet_bits = htonl(8); + break; + case 2: + msg->data.allow_deny.ip = htonl((a<<24) | (b<<16)); + msg->data.allow_deny.subnet_bits = htonl(16); + break; + case 3: + msg->data.allow_deny.ip = htonl((a<<24) | (b<<16) | (c<<8)); + msg->data.allow_deny.subnet_bits = htonl(24); + break; + case 4: + msg->data.allow_deny.ip = htonl((a<<24) | (b<<16) | (c<<8) | d); + msg->data.allow_deny.subnet_bits = htonl(32); + break; + default: + assert(0); + + } + + if (slashpos) { + int specified_subnet_bits, n; + n = sscanf(slashpos+1, "%d", &specified_subnet_bits); + if (n == 1) { + msg->data.allow_deny.subnet_bits = htonl(specified_subnet_bits); + } else { + fprintf(stderr, "Warning: badly formatted subnet size, using %ld\n", (long) ntohl(msg->data.allow_deny.subnet_bits)); + } + } + } + } + return 1; +} + +/* ================================================== */ + +static int +process_cmd_allow(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_ALLOW); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +process_cmd_allowall(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_ALLOWALL); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +process_cmd_deny(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_DENY); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +process_cmd_denyall(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_DENYALL); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +process_cmd_cmdallow(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_CMDALLOW); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +process_cmd_cmdallowall(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_CMDALLOWALL); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +process_cmd_cmddeny(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_CMDDENY); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +process_cmd_cmddenyall(CMD_Request *msg, char *line) +{ + int status; + msg->command = htons(REQ_CMDDENYALL); + status = parse_allow_deny(msg, line); + return status; +} + +/* ================================================== */ + +static int +accheck_getaddr(char *line, unsigned long *addr) +{ + unsigned long a, b, c, d, ip; + unsigned char *p, *q; + p = line; + while (*p && isspace(*p)) p++; + if (!*p) { + return 0; + } else { + if (sscanf(p, "%lu.%lu.%lu.%lu", &a, &b, &c, &d) == 4) { + *addr = (a<<24) | (b<<16) | (c<<8) | d; + return 1; + } else { + q = p; + while (*q) { + if (*q == '\n') *q = 0; + q++; + } + ip = DNS_Name2IPAddress(p); + if (ip == DNS_Failed_Address) { + return 0; + } else { + *addr = ip; + return 1; + } + } + } +} + +/* ================================================== */ + +static int +process_cmd_accheck(CMD_Request *msg, char *line) +{ + unsigned long ip; + msg->command = htons(REQ_ACCHECK); + if (accheck_getaddr(line, &ip)) { + msg->data.ac_check.ip = htonl(ip); + return 1; + } else { + fprintf(stderr, "Could not read address\n"); + return 0; + } +} + +/* ================================================== */ + +static int +process_cmd_cmdaccheck(CMD_Request *msg, char *line) +{ + unsigned long ip; + msg->command = htons(REQ_CMDACCHECK); + if (accheck_getaddr(line, &ip)) { + msg->data.ac_check.ip = htonl(ip); + return 1; + } else { + fprintf(stderr, "Could not read address\n"); + return 0; + } +} + +/* ================================================== */ + +static void +process_cmd_dfreq(CMD_Request *msg, char *line) +{ + double dfreq; + msg->command = htons(REQ_DFREQ); + if (sscanf(line, "%lf", &dfreq) == 1) { + msg->data.dfreq.dfreq = REAL2WIRE(dfreq); + } else { + msg->data.dfreq.dfreq = REAL2WIRE(0.0); + } +} + +/* ================================================== */ + +static void +cvt_to_sec_usec(double x, long *sec, long *usec) { + long s, us; + s = (long) x; + us = (long)(0.5 + 1.0e6 * (x - (double) s)); + while (us >= 1000000) { + us -= 1000000; + s += 1; + } + while (us < 0) { + us += 1000000; + s -= 1; + } + + *sec = s; + *usec = us; + return; +} + +/* ================================================== */ + +static void +process_cmd_doffset(CMD_Request *msg, char *line) +{ + double doffset; + long sec, usec; + msg->command = htons(REQ_DOFFSET); + if (sscanf(line, "%lf", &doffset) == 1) { + cvt_to_sec_usec(doffset, &sec, &usec); + msg->data.doffset.sec = htonl(sec); + msg->data.doffset.usec = htonl(usec); + } else { + msg->data.doffset.sec = htonl(0); + msg->data.doffset.usec = htonl(0); + } +} + +/* ================================================== */ + +static int +process_cmd_add_server_or_peer(CMD_Request *msg, char *line) +{ + CPS_NTP_Source data; + CPS_Status status; + int result = 0; + + status = CPS_ParseNTPSourceAdd(line, &data); + switch (status) { + case CPS_Success: + msg->data.ntp_source.port = htonl((unsigned long) data.port); + msg->data.ntp_source.ip_addr = htonl(data.ip_addr); + msg->data.ntp_source.minpoll = htonl(data.params.minpoll); + msg->data.ntp_source.maxpoll = htonl(data.params.maxpoll); + msg->data.ntp_source.presend_minpoll = htonl(data.params.presend_minpoll); + msg->data.ntp_source.online = htonl(data.params.online); + msg->data.ntp_source.auto_offline = htonl(data.params.auto_offline); + msg->data.ntp_source.authkey = htonl(data.params.authkey); + msg->data.ntp_source.max_delay = REAL2WIRE(data.params.max_delay); + msg->data.ntp_source.max_delay_ratio = REAL2WIRE(data.params.max_delay_ratio); + result = 1; + + break; + case CPS_BadOption: + fprintf(stderr, "Unrecognized subcommand\n"); + break; + case CPS_BadHost: + fprintf(stderr, "Invalid host/IP address\n"); + break; + case CPS_BadPort: + fprintf(stderr, "Unreadable port number\n"); + break; + case CPS_BadMinpoll: + fprintf(stderr, "Unreadable minpoll value\n"); + break; + case CPS_BadMaxpoll: + fprintf(stderr, "Unreadable maxpoll value\n"); + break; + case CPS_BadPresend: + fprintf(stderr, "Unreadable presend value\n"); + break; + case CPS_BadMaxdelayratio: + fprintf(stderr, "Unreadable max delay ratio value\n"); + break; + case CPS_BadMaxdelay: + fprintf(stderr, "Unreadable max delay value\n"); + break; + case CPS_BadKey: + fprintf(stderr, "Unreadable key value\n"); + break; + } + + return result; +} + +/* ================================================== */ + +static int +process_cmd_add_server(CMD_Request *msg, char *line) +{ + msg->command = htons(REQ_ADD_SERVER); + return process_cmd_add_server_or_peer(msg, line); +} + +/* ================================================== */ + +static int +process_cmd_add_peer(CMD_Request *msg, char *line) +{ + msg->command = htons(REQ_ADD_PEER); + return process_cmd_add_server_or_peer(msg, line); +} + +/* ================================================== */ + +static int +process_cmd_delete(CMD_Request *msg, char *line) +{ + char hostname[2048]; + int ok = 0; + unsigned long address = 0UL; + + msg->command = htons(REQ_DEL_SOURCE); + + if (sscanf(line, "%2047s", hostname) != 1) { + fprintf(stderr, "Invalid syntax for address\n"); + ok = 0; + } else { + address = DNS_Name2IPAddress(hostname); + if (address == DNS_Failed_Address) { + fprintf(stderr, "Could not get address for hostname\n"); + ok = 0; + } else { + ok = 1; + } + } + + msg->data.del_source.ip_addr = htonl(address); + + return ok; + +} + +/* ================================================== */ + +static int password_seen = 0; +static MD5_CTX md5_after_just_password; + +/* ================================================== */ + +static int +process_cmd_password(CMD_Request *msg, char *line) +{ + char *p, *q; + char *password; + struct timezone tz; + + p = line; + while (*p && isspace((unsigned char)*p)) + p++; + + /* Get rid of trailing newline */ + for (q=p; *q; q++) { + if (isspace((unsigned char)*q)) *q = 0; + } + + if (*p) { + password = p; + } else { + /* blank line, prompt for password */ + password = getpass("Password: "); + } + + if (!*password) { + password_seen = 0; + } else { + password_seen = 1; + } + + /* Generate MD5 initial context */ + MD5Init(&md5_after_just_password); + MD5Update(&md5_after_just_password, (unsigned char *) password, strlen(password)); + + /* Blank the password for security */ + for (p = password; *p; p++) { + *p = 0; + } + + if (gettimeofday(&msg->data.logon.ts, &tz) < 0) { + printf("500 - Could not read time of day\n"); + return 0; + } else { + msg->command = htons(REQ_LOGON); /* Just force a round trip so that we get tokens etc */ + msg->data.logon.ts.tv_sec = htonl(msg->data.logon.ts.tv_sec); + msg->data.logon.ts.tv_usec = htonl(msg->data.logon.ts.tv_usec); + return 1; + } +} + +/* ================================================== */ + +static void +generate_auth(CMD_Request *msg) +{ + MD5_CTX ctx; + int pkt_len; + + pkt_len = PKL_CommandLength(msg); + ctx = md5_after_just_password; + MD5Update(&ctx, (unsigned char *) msg, offsetof(CMD_Request, auth)); + if (pkt_len > offsetof(CMD_Request, data)) { + MD5Update(&ctx, (unsigned char *) &(msg->data), pkt_len - offsetof(CMD_Request, data)); + } + MD5Final(&ctx); + memcpy(&(msg->auth), &ctx.digest, 16); +} + +/* ================================================== */ + +static int +check_reply_auth(CMD_Reply *msg) +{ + int pkt_len; + MD5_CTX ctx; + + pkt_len = PKL_ReplyLength(msg); + ctx = md5_after_just_password; + MD5Update(&ctx, (unsigned char *) msg, offsetof(CMD_Request, auth)); + if (pkt_len > offsetof(CMD_Reply, data)) { + MD5Update(&ctx, (unsigned char *) &(msg->data), pkt_len - offsetof(CMD_Reply, data)); + } + MD5Final(&ctx); + + if (!memcmp((void *) &ctx.digest, (void *) &(msg->auth), 16)) { + return 1; + } else { + return 0; + } +} + +/* ================================================== */ + +static void +give_help(void) +{ + printf("205 - Help follows\n"); + printf("accheck
: Check whether NTP access is allowed to
\n"); + printf("activity : Check how many NTP sources are online/offline\n"); + printf("allow [] : Allow NTP access to that subnet as a default\n"); + printf("allow all [] : Allow NTP access to that subnet and all children\n"); + printf("burst / [/] : Start a rapid set of measurements\n"); + printf("clients : Report on clients that have accessed the server\n"); + printf("cmdaccheck
: Check whether command access is allowed to
\n"); + printf("cmdallow [] : Allow command access to that subnet as a default\n"); + printf("cmdallow all [] : Allow command access to that subnet and all children\n"); + printf("cmddeny [] : Deny command access to that subnet as a default\n"); + printf("cmddeny all [] : Deny command access to that subnet and all children\n"); + printf("cyclelogs : Close and re-open logs files\n"); + printf("deny [] : Deny NTP access to that subnet as a default\n"); + printf("deny all [] : Deny NTP access to that subnet and all children\n"); + printf("dump : Dump all measurements to save files\n"); + printf("exit : Leave the program\n"); + printf("help : Generate this help\n"); + printf("local off : Disable server capability for unsynchronised clock\n"); + printf("local stratum : Enable server capability for unsynchronised clock\n"); + printf("makestep : Jump the time to remove any correction being slewed\n"); + printf("manual off|on|reset : Disable/enable/reset settime command and statistics\n"); + printf("manual list : Show previous settime entries\n"); + printf("minpoll
: Modify minimum polling interval of source\n"); + printf("maxdelay
: Modify maximum round-trip valid sample delay for source\n"); + printf("maxdelayratio
: Modify max round-trip delay ratio for source\n"); + printf("maxpoll
: Modify maximum polling interval of source\n"); + printf("maxupdateskew : Modify maximum skew for a clock frequency update to be made\n"); + printf("offline [/] : Set sources in subnet to offline status\n"); + printf("online [/] : Set sources in subnet to online status\n"); + printf("password [] : Set command authentication password\n"); + printf("quit : Leave the program\n"); + printf("rtcdata : Print current RTC performance parameters\n"); + printf("settime : Manually set the daemon time\n"); + printf("sources [-v] : Display information about current sources\n"); + printf("sourcestats [-v] : Display estimation information about current sources\n"); + printf("tracking : Display system time information\n"); + printf("trimrtc : Correct RTC relative to system clock\n"); + printf("writertc : Save RTC parameters to file\n"); + printf(".\n"); +} + +/* ================================================== */ + +static unsigned long sequence = 0; +static unsigned long utoken = 0; +static unsigned long token = 0; + +#define MAX_ATTEMPTS 5 + + +/* 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 *reply_auth_ok) +{ + unsigned long tx_sequence; + int where_from_len; + struct sockaddr_in where_from; + int bad_length, bad_sender, bad_sequence, bad_header; + int select_status; + int recvfrom_status; + int read_length; + int expected_length; + int command_length; + struct timeval timeout; + int timeout_seconds; + int n_attempts; + fd_set rdfd, wrfd, exfd; + + request->version = PROTO_VERSION_NUMBER; + request->pkt_type = PKT_TYPE_CMD_REQUEST; + request->res1 = 0; + request->res2 = 0; + tx_sequence = sequence++; + request->sequence = htonl(tx_sequence); + request->attempt = 0; + request->utoken = htonl(utoken); + request->token = htonl(token); + + + timeout_seconds = 120; + + n_attempts = 0; + + do { + + /* Decide whether to authenticate */ + if (password_seen) { + if (!utoken || (request->command == htons(REQ_LOGON))) { + /* Otherwise, the daemon won't bother authenticating our + packet and we won't get a token back */ + request->utoken = htonl(SPECIAL_UTOKEN); + } + generate_auth(request); + } + + command_length = PKL_CommandLength(request); +#if 0 + printf("Sent command length=%d bytes\n", command_length); +#endif + + if (sendto(sock_fd, (void *) request, command_length, 0, + (struct sockaddr *) &his_addr, sizeof(his_addr)) < 0) { + + +#if 0 + perror("Could not send packet"); +#endif + return 0; + } + + /* Increment this for next time */ + ++ request->attempt; + + timeout.tv_sec = timeout_seconds; + timeout.tv_usec = 0; + + timeout_seconds += 1; + FD_ZERO(&rdfd); + FD_ZERO(&wrfd); + FD_ZERO(&exfd); + + FD_SET(sock_fd, &rdfd); + + select_status = select(sock_fd + 1, &rdfd, &wrfd, &exfd, &timeout); + + if (select_status < 0) { +#if 0 + perror("Select returned negative status"); +#endif + } else if (select_status == 0) { + /* Timeout must have elapsed, try a resend? */ + n_attempts ++; + if (n_attempts == MAX_ATTEMPTS) { + return 0; + } + + /* Back to top of loop to do resend */ + continue; + + } else { + + where_from_len = sizeof(where_from); + recvfrom_status = recvfrom(sock_fd, (void *) reply, sizeof(CMD_Reply), 0, + (struct sockaddr *) &where_from, &where_from_len); + + +#if 0 + printf("Received packet, status=%d\n", recvfrom_status); +#endif + + if (recvfrom_status < 0) { + /* If we get connrefused here, it suggests the sendto is + going to a dead port - but only if the daemon machine is + running Linux (Solaris doesn't return anything) */ + n_attempts++; + if (n_attempts == MAX_ATTEMPTS) { + return 0; + } + } else { + + read_length = recvfrom_status; + expected_length = PKL_ReplyLength(reply); + + bad_length = (read_length != expected_length); + bad_sender = ((where_from.sin_addr.s_addr != his_addr.sin_addr.s_addr) || + (where_from.sin_port != his_addr.sin_port)); + + if (!bad_length) { + bad_sequence = (ntohl(reply->sequence) != tx_sequence); + } else { + bad_sequence = 0; + } + + if (bad_length || bad_sender || bad_sequence) { + n_attempts++; + if (n_attempts == MAX_ATTEMPTS) { + return 0; + } + continue; + } + + bad_header = ((reply->version != PROTO_VERSION_NUMBER) || + (reply->pkt_type != PKT_TYPE_CMD_REPLY) || + (reply->res1 != 0) || + (reply->res2 != 0)); + + if (bad_header) { + n_attempts++; + if (n_attempts == MAX_ATTEMPTS) { + return 0; + } + continue; + } + + /* Good packet received, print out results */ +#if 0 + printf("Reply cmd=%d reply=%d stat=%d num=%d tot=%d seq=%d utok=%08lx tok=%d\n", + ntohs(reply->command), ntohs(reply->reply), + ntohs(reply->status), ntohs(reply->number), + ntohs(reply->total), + ntohl(reply->sequence), + ntohl(reply->utoken), + ntohl(reply->token)); +#endif + + if (password_seen) { + *reply_auth_ok = check_reply_auth(reply); + } else { + /* Assume in this case that the reply is always considered + to be authentic */ + *reply_auth_ok = 1; + } + + utoken = ntohl(reply->utoken); + + if (*reply_auth_ok) { + /* If we're in authenticated mode, only acquire the utoken + and new token values if the reply authenticated properly. + This protects against forged packets with bogus tokens + in. We won't accept a repeat of an old message with a + stale token in it, due to bad_sequence processing + earlier. */ + utoken = ntohl(reply->utoken); + token = ntohl(reply->token); + } + + break; + + } + } + } while (1); + + return 1; +} + +/* ================================================== */ + +static void +print_seconds(unsigned long s) +{ + unsigned long d; + if (s <= 1024) { + printf("%4ld", s); + } else if (s < 36000) { + printf("%3ldm", s / 60); + } else if (s < 345600) { + printf("%3ldh", s / 3600); + } else { + d = s / 86400; + if (d > 999) { + printf("%3ldy", d / 365); + } else { + printf("%3ldd", d); + } + } +} + +/* ================================================== */ + +static void +print_microseconds(unsigned long us) +{ + if (us <= 9999) { + printf("%4ldus", us); + } else if (us <= 9999999) { + printf("%4ldms", us / 1000); + } else if (us <= 999999999) { + printf("%3ld.%01lds", us / 1000000, (us/100000) % 10); + } else { + printf("%5lds", us / 1000000); + } +} + +/* ================================================== */ + +static void +print_signed_microseconds(long us) +{ + long x = abs(us); + if (x <= 9999) { + printf("%+5ldus", us); + } else if (x <= 9999999) { + printf("%+5ldms", us / 1000); + } else { + printf("%+6lds", us / 1000000); + } +} + +/* ================================================== */ + +static int +check_for_verbose_flag(char *line) +{ + char *p = line; + while (*p && isspace((unsigned char)*p)) p++; + if (!strncmp(p, "-v", 2)) { + return 1; + } else { + return 0; + } +} + +/* ================================================== */ + +static void +process_cmd_sources(char *line) +{ + int submit_ok; + int auth_ok; + CMD_Request request; + CMD_Reply reply; + int n_sources, i; + int verbose = 0; + + long orig_latest_meas, latest_meas, est_offset; + unsigned long ip_addr; + unsigned long latest_meas_err, est_offset_err; + unsigned long latest_meas_ago; + unsigned short poll, stratum; + unsigned short state, mode; + double resid_freq, resid_skew; + const char *dns_lookup; + char hostname_buf[32]; + unsigned short status; + + /* Check whether to output verbose headers */ + verbose = check_for_verbose_flag(line); + + request.command = htons(REQ_N_SOURCES); + submit_ok = submit_request(&request, &reply, &auth_ok); + + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_INVALID: + printf("502 Invalid command\n"); + return; + break; + case STT_NOHOSTACCESS: + printf("510 No command access from this host\n"); + return; + break; + default: + break; + } + + n_sources = ntohl(reply.data.n_sources.n_sources); + printf("210 Number of sources = %d\n", n_sources); + if (verbose) { + printf("\n"); + printf(" .-- Source mode '^' = server, '=' = peer, '#' = local clock.\n"); + printf(" / .- Source state '*' = current synced, '+' = OK for sync, '?' = unreachable,\n"); + printf("| / 'x' = time may be in error, '~' = time is too variable.\n"); + printf("|| .- xxxx [ yyyy ] +/- zzzz\n"); + printf("|| / xxxx = adjusted offset,\n"); + printf("|| Log2(Polling interval) -. | yyyy = measured offset,\n"); + printf("|| \\ | zzzz = estimated error.\n"); + printf("|| | | \n"); + } + + printf("MS Name/IP address Stratum Poll LastRx Last sample\n"); + printf("============================================================================\n"); + + /* "MS NNNNNNNNNNNNNNNNNNNNNNNNN SS PP RRRR SSSSSSS[SSSSSSS] +/- SSSSSS" */ + + for (i=0; i> 24); + b = (ref_id >> 16) & 0xff; + c = (ref_id >> 8) & 0xff; + d = (ref_id) & 0xff; + printf("Reference ID : %lu.%lu.%lu.%lu (%s)\n", + a, b, c, d, + (no_dns) ? UTI_IPToDottedQuad(ref_id) : DNS_IPAddress2Name(ref_id)); + printf("Stratum : %lu\n", (unsigned long) ntohl(reply.data.tracking.stratum)); + ref_time.tv_sec = ntohl(reply.data.tracking.ref_time_s); + ref_time.tv_usec = ntohl(reply.data.tracking.ref_time_us); + ref_time_tm = *gmtime((time_t *)&ref_time.tv_sec); + printf("Ref time (UTC) : %s", asctime(&ref_time_tm)); + correction_tv.tv_sec = ntohl(reply.data.tracking.current_correction_s); + correction_tv.tv_usec = ntohl(reply.data.tracking.current_correction_us); + correction = (double) correction_tv.tv_sec + 1.0e-6 * correction_tv.tv_usec; + printf("System time : %.6f seconds %s of NTP time\n", fabs(correction), + (correction > 0.0) ? "slow" : "fast"); + freq_ppm = WIRE2REAL(reply.data.tracking.freq_ppm); + resid_freq_ppm = WIRE2REAL(reply.data.tracking.resid_freq_ppm); + skew_ppm = WIRE2REAL(reply.data.tracking.skew_ppm); + root_delay = WIRE2REAL(reply.data.tracking.root_delay); + root_dispersion = WIRE2REAL(reply.data.tracking.root_dispersion); + printf("Frequency : %.3f ppm %s\n", fabs(freq_ppm), (freq_ppm < 0.0) ? "slow" : "fast"); + printf("Residual freq : %.3f ppm\n", resid_freq_ppm); + printf("Skew : %.3f ppm\n", skew_ppm); + printf("Root delay : %.6f seconds\n", root_delay); + printf("Root dispersion : %.6f seconds\n", root_dispersion); + } else { + printf("506 Cannot talk to daemon\n"); + } +} +/* ================================================== */ + +static void +process_cmd_rtcreport(char *line) +{ + int status; + int submit_ok; + int auth_ok; + CMD_Request request; + CMD_Reply reply; + time_t ref_time; + struct tm ref_time_tm; + unsigned short n_samples; + unsigned short n_runs; + unsigned long span_seconds; + double coef_seconds_fast; + double coef_gain_rate_ppm; + + request.command = htons(REQ_RTCREPORT); + submit_ok = submit_request(&request, &reply, &auth_ok); + + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_INVALID: + printf("502 Invalid command\n"); + return; + break; + case STT_NORTC: + printf("513 No RTC driver\n"); + return; + break; + case STT_NOHOSTACCESS: + printf("510 No command access from this host\n"); + return; + break; + default: + break; + } + ref_time = (time_t) ntohl(reply.data.rtc.ref_time); + ref_time_tm = *gmtime(&ref_time); + n_samples = ntohs(reply.data.rtc.n_samples); + n_runs = ntohs(reply.data.rtc.n_runs); + span_seconds = ntohl(reply.data.rtc.span_seconds); + coef_seconds_fast = WIRE2REAL(reply.data.rtc.rtc_seconds_fast); + coef_gain_rate_ppm = WIRE2REAL(reply.data.rtc.rtc_gain_rate_ppm); + printf("RTC ref time (UTC) : %s", asctime(&ref_time_tm)); + printf("Number of samples : %d\n", n_samples); + printf("Number of runs : %d\n", n_runs); + printf("Sample span period : "); + print_seconds(span_seconds); + printf("\n"); + printf("RTC is fast by : %12.6f seconds\n", coef_seconds_fast); + printf("RTC gains time at : %9.3f ppm\n", coef_gain_rate_ppm); + } else { + printf("506 Cannot talk to daemon\n"); + } +} + +/* ================================================== */ + +#if 0 + +/* This is a previous attempt at implementing the clients command. It + could be re-instated sometime as a way of looking at all clients in a + particular subnet. The problem with it is that is requires at least 5 + round trips to the server even if the server only has one client to + report. */ + +typedef struct XSubnetToDo { + struct XSubnetToDo *next; + unsigned long ip; + unsigned long bits; +} SubnetToDo; + +static void +process_cmd_clients(char *line) +{ + CMD_Request request; + CMD_Reply reply; + SubnetToDo *head, *todo, *tail, *p, *next_node, *new_node; + int submit_ok, auth_ok; + int status; + int i, j, nets_looked_up, clients_looked_up; + int word; + unsigned long mask; + unsigned long ip, bits; + unsigned long client_hits; + unsigned long peer_hits; + unsigned long cmd_hits_auth; + unsigned long cmd_hits_normal; + unsigned long cmd_hits_bad; + unsigned long last_ntp_hit_ago; + unsigned long last_cmd_hit_ago; + char hostname_buf[32]; + const char *dns_lookup; + + int n_replies; + + head = todo = MallocNew(SubnetToDo); + todo->next = NULL; + /* Set up initial query = root subnet */ + todo->ip = 0; + todo->bits = 0; + tail = todo; + + do { + + request.command = htons(REQ_SUBNETS_ACCESSED); + /* Build list of subnets to examine */ + i=0; + p=todo; + while((i < MAX_SUBNETS_ACCESSED) && + p && + (p->bits < 32)) { + + request.data.subnets_accessed.subnets[i].ip = htonl(p->ip); + request.data.subnets_accessed.subnets[i].bits_specd = htonl(p->bits); + p = p->next; + i++; + } + + nets_looked_up = i; + + if (nets_looked_up == 0) { + /* No subnets need examining */ + break; + } + + request.data.subnets_accessed.n_subnets = htonl(nets_looked_up); + + submit_ok = submit_request(&request, &reply, &auth_ok); + + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_SUCCESS: + n_replies = ntohl(reply.data.subnets_accessed.n_subnets); + for (j=0; jnext = NULL; + new_node->bits = bits + 8; + new_node->ip = ip | (i << (24 - bits)); + tail->next = new_node; + tail = new_node; +#if 0 + printf("%08lx %2d %3d %08lx\n", ip, bits, i, new_node->ip); +#endif + } + } + } + + /* Skip the todo pointer forwards by the number of nets looked + up. Can't do this earlier, because we might have to point + at the next layer of subnets that have only just been + concatenated to the linked list. */ + for (i=0; inext; + } + + break; + case STT_BADSUBNET: + /* We should never generate any bad subnet messages */ + assert(0); + break; + case STT_INACTIVE: + printf("519 Client logging is not active in the daemon\n"); + goto cleanup; + case STT_NOHOSTACCESS: + printf("510 No command access from this host\n"); + goto cleanup; + default: + printf("520 Got unexpected error from daemon\n"); + goto cleanup; + } + + } else { + printf("506 Cannot talk to daemon\n"); + return; + } + } while (1); /* keep going until all subnets have been expanded, + down to single nodes */ + + /* Now the todo list consists of client records */ + request.command = htons(REQ_CLIENT_ACCESSES); + +#if 0 + printf("%d %d\n", sizeof (RPY_ClientAccesses_Client), offsetof(CMD_Reply, data.client_accesses.clients)); +#endif + + printf("Hostname Client Peer CmdAuth CmdNorm CmdBad LstN LstC\n" + "========================= ====== ====== ====== ====== ====== ==== ====\n"); + + do { + + i = 0; + p = todo; + while ((i < MAX_CLIENT_ACCESSES) && + p) { + + request.data.client_accesses.client_ips[i] = htonl(p->ip); + p = p->next; + i++; + } + + clients_looked_up = i; + + if (clients_looked_up == 0) { + /* No more clients to do */ + break; + } + + request.data.client_accesses.n_clients = htonl(clients_looked_up); + + submit_ok = submit_request(&request, &reply, &auth_ok); + + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_SUCCESS: + n_replies = ntohl(reply.data.client_accesses.n_clients); + for (j=0; jnext; + } + + break; + + case STT_BADSUBNET: + /* We should never generate any bad subnet messages */ + assert(0); + break; + case STT_INACTIVE: + printf("519 Client logging is not active in the daemon\n"); + goto cleanup; + case STT_NOHOSTACCESS: + printf("510 No command access from this host\n"); + goto cleanup; + default: + printf("520 Got unexpected error from daemon\n"); + goto cleanup; + } + + } + + } while (1); + +cleanup: + for (p = head; p; ) { + next_node = p->next; + Free(p); + p = next_node; + } + +} + +#endif + +/* New implementation of clients command */ + +static void +process_cmd_clients(char *line) +{ + CMD_Request request; + CMD_Reply reply; + int submit_ok, auth_ok; + int status; + unsigned long next_index; + int j; + unsigned long ip; + unsigned long client_hits; + unsigned long peer_hits; + unsigned long cmd_hits_auth; + unsigned long cmd_hits_normal; + unsigned long cmd_hits_bad; + unsigned long last_ntp_hit_ago; + unsigned long last_cmd_hit_ago; + char hostname_buf[32]; + const char *dns_lookup; + + int n_replies; + int n_indices_in_table; + + next_index = 0; + + printf("Hostname Client Peer CmdAuth CmdNorm CmdBad LstN LstC\n" + "========================= ====== ====== ====== ====== ====== ==== ====\n"); + + do { + + request.command = htons(REQ_CLIENT_ACCESSES_BY_INDEX); + request.data.client_accesses_by_index.first_index = htonl(next_index); + request.data.client_accesses_by_index.n_indices = htonl(MAX_CLIENT_ACCESSES); + + submit_ok = submit_request(&request, &reply, &auth_ok); + + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_SUCCESS: + n_replies = ntohl(reply.data.client_accesses_by_index.n_clients); + n_indices_in_table = ntohl(reply.data.client_accesses_by_index.n_indices); + if (n_replies == 0) { + goto finished; + } + for (j=0; j= n_indices_in_table) { + goto finished; + } + + break; + case STT_BADSUBNET: + /* We should never generate any bad subnet messages */ + assert(0); + break; + case STT_INACTIVE: + printf("519 Client logging is not active in the daemon\n"); + goto finished; + case STT_NOHOSTACCESS: + printf("510 No command access from this host\n"); + goto finished; + default: + printf("520 Got unexpected error from daemon\n"); + goto finished; + } + + } else { + printf("506 Cannot talk to daemon\n"); + return; + } + } while (1); /* keep going until all subnets have been expanded, + down to single nodes */ + +finished: + return; + +} + + +/* ================================================== */ +/* Process the manual list command */ +static void +process_cmd_manual_list(const char *line) +{ + CMD_Request request; + CMD_Reply reply; + int submit_ok, auth_ok; + int status; + int n_samples; + RPY_ManualListSample *sample; + int i; + time_t when; + double slewed_offset, orig_offset, residual; + + request.command = htons(REQ_MANUAL_LIST); + submit_ok = submit_request(&request, &reply, &auth_ok); + + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_SUCCESS: + n_samples = ntohl(reply.data.manual_list.n_samples); + printf("210 n_samples = %d\n", n_samples); + printf("# Date Time(UTC) Slewed Original Residual\n" + "====================================================\n"); + for (i=0; iwhen); + slewed_offset = WIRE2REAL(sample->slewed_offset); + orig_offset = WIRE2REAL(sample->orig_offset); + residual = WIRE2REAL(sample->residual); + printf("%2d %s %10.2f %10.2f %10.2f\n", i, time_to_log_form(when), slewed_offset, orig_offset, residual); + } + break; + case STT_NOHOSTACCESS: + printf("510 No command access from this host\n"); + break; + default: + printf("520 Got unexpected error from daemon\n"); + break; + } + } + +} + +/* ================================================== */ + +static void +process_cmd_manual_delete(const char *line) +{ + int index; + CMD_Request request; + CMD_Reply reply; + int submit_ok, auth_ok; + int status; + + if (sscanf(line, "%d", &index) != 1) { + fprintf(stderr, "Bad syntax for manual delete command\n"); + return; + + } + + request.command = htons(REQ_MANUAL_DELETE); + request.data.manual_delete.index = htonl(index); + + submit_ok = submit_request(&request, &reply, &auth_ok); + + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_SUCCESS: + printf("200 OK\n"); + break; + case STT_BADSAMPLE: + printf("516 Sample index out of range\n"); + break; + case STT_NOHOSTACCESS: + printf("510 No command access from this host\n"); + break; + default: + printf("520 Got unexpected error from daemon\n"); + break; + } + } + +} + +/* ================================================== */ + +static void +process_cmd_settime(char *line) +{ + time_t now, new_time; + CMD_Request request; + CMD_Reply reply; + int submit_ok, reply_auth_ok; + long offset_cs; + double dfreq_ppm, new_afreq_ppm; + double offset; + int status; + + now = time(NULL); + new_time = get_date(line, &now); + + if (new_time == -1) { + printf("510 - Could not parse date string\n"); + } else { + request.data.settime.ts.tv_sec = htonl(new_time); + request.data.settime.ts.tv_usec = htonl(0); + request.command = htons(REQ_SETTIME); + submit_ok = submit_request(&request, &reply, &reply_auth_ok); + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_SUCCESS: + offset_cs = ntohl(reply.data.manual_timestamp.centiseconds); + offset = 0.01 * (double) offset_cs; + dfreq_ppm = WIRE2REAL(reply.data.manual_timestamp.dfreq_ppm); + new_afreq_ppm = WIRE2REAL(reply.data.manual_timestamp.new_afreq_ppm); + printf("200 OK : Clock was %.2f seconds fast. Frequency change = %.2fppm, new frequency = %.2fppm", + offset, dfreq_ppm, new_afreq_ppm); + 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_NOTENABLED: + printf("505 Facility not enabled in daemon"); + break; + case STT_NOHOSTACCESS: + printf("510 No command access from this host"); + break; + } + if (reply_auth_ok) { + printf("\n"); + } else { + printf(" --- Reply not authenticated\n"); + } + } else { + printf("506 Could not submit settime command\n"); + } + } + +} + +/* ================================================== */ + +static void +process_cmd_rekey(CMD_Request *msg, char *line) +{ + msg->command = htons(REQ_REKEY); +} + +/* ================================================== */ + +static void +process_cmd_makestep(CMD_Request *msg, char *line) +{ + msg->command = htons(REQ_MAKESTEP); +} + +/* ================================================== */ + +static void +process_cmd_activity(const char *line) +{ + CMD_Request request; + CMD_Reply reply; + int submit_ok, status, reply_auth_ok; + request.command = htons(REQ_ACTIVITY); + submit_ok = submit_request(&request, &reply, &reply_auth_ok); + if (submit_ok) { + status = ntohs(reply.status); + switch (status) { + case STT_SUCCESS: + printf("200 OK\n" + "%ld sources online\n" + "%ld sources offline\n" + "%ld sources doing burst (return to online)\n" + "%ld sources doing burst (return to offline)\n", + (long) ntohl(reply.data.activity.online), + (long) ntohl(reply.data.activity.offline), + (long) ntohl(reply.data.activity.burst_online), + (long) ntohl(reply.data.activity.burst_offline)); + break; + default: + printf("Unexpected error returned\n"); + break; + } + if (!reply_auth_ok) { + printf(" --- Reply not authenticated\n"); + } + } else { + printf("506 Could not submit activity command\n"); + } +} + +/* ================================================== */ + +static int +process_line(char *line) +{ + char *p; + int quit; + int do_normal_submit; + CMD_Request tx_message; + CMD_Reply rx_message; + int reply_auth_ok, request_submitted_ok; + + quit = 0; + + do_normal_submit = 1; + + /* Check for line being blank */ + p = line; + while (*p && isspace((unsigned char)*p)) p++; + if (!*p) return quit; + + if (!strncmp(p, "offline", 7)) { + do_normal_submit = process_cmd_offline(&tx_message, p+7); + } else if (!strncmp(p, "online", 6)) { + do_normal_submit = process_cmd_online(&tx_message, p+6); + } else if (!strncmp(p, "burst", 5)) { + do_normal_submit = process_cmd_burst(&tx_message, p+5); + } else if (!strncmp(p, "password", 8)) { + do_normal_submit = process_cmd_password(&tx_message, p+8); + } else if (!strncmp(p, "minpoll", 7)) { + do_normal_submit = process_cmd_minpoll(&tx_message, p+7); + } else if (!strncmp(p, "maxpoll", 7)) { + do_normal_submit = process_cmd_maxpoll(&tx_message, p+7); + } else if (!strncmp(p, "dump", 4)) { + process_cmd_dump(&tx_message, p+4); + } else if (!strncmp(p, "maxdelayratio", 13)) { + do_normal_submit = process_cmd_maxdelayratio(&tx_message, p+13); + } else if (!strncmp(p, "maxdelay", 8)) { + do_normal_submit = process_cmd_maxdelay(&tx_message, p+8); + } else if (!strncmp(p, "maxupdateskew", 13)) { + do_normal_submit = process_cmd_maxupdateskew(&tx_message, p+13); + } else if (!strncmp(p, "settime", 7)) { + do_normal_submit = 0; + process_cmd_settime(p+7); + } else if (!strncmp(p, "local", 5)) { + do_normal_submit = process_cmd_local(&tx_message, p+5); + } else if (!strncmp(p, "manual", 6)) { + do_normal_submit = process_cmd_manual(&tx_message, p+6); + } else if (!strncmp(p, "sourcestats", 11)) { + do_normal_submit = 0; + process_cmd_sourcestats(p+11); + } else if (!strncmp(p, "sources", 7)) { + do_normal_submit = 0; + process_cmd_sources(p+7); + } else if (!strncmp(p, "rekey", 5)) { + process_cmd_rekey(&tx_message, p+5); + } else if (!strncmp(p, "allow all", 9)) { + do_normal_submit = process_cmd_allowall(&tx_message, p+9); + } else if (!strncmp(p, "allow", 5)) { + do_normal_submit = process_cmd_allow(&tx_message, p+5); + } else if (!strncmp(p, "deny all", 8)) { + do_normal_submit = process_cmd_denyall(&tx_message, p+8); + } else if (!strncmp(p, "deny", 4)) { + do_normal_submit = process_cmd_deny(&tx_message, p+4); + } else if (!strncmp(p, "cmdallow all", 12)) { + do_normal_submit = process_cmd_cmdallowall(&tx_message, p+12); + } else if (!strncmp(p, "cmdallow", 8)) { + do_normal_submit = process_cmd_cmdallow(&tx_message, p+8); + } else if (!strncmp(p, "cmddeny all", 11)) { + do_normal_submit = process_cmd_cmddenyall(&tx_message, p+11); + } else if (!strncmp(p, "cmddeny", 7)) { + do_normal_submit = process_cmd_cmddeny(&tx_message, p+7); + } else if (!strncmp(p, "accheck", 7)) { + do_normal_submit = process_cmd_accheck(&tx_message, p+7); + } else if (!strncmp(p, "cmdaccheck", 10)) { + do_normal_submit = process_cmd_cmdaccheck(&tx_message, p+10); + } else if (!strncmp(p, "add server", 10)) { + do_normal_submit = process_cmd_add_server(&tx_message, p+10); + } else if (!strncmp(p, "add peer", 8)) { + do_normal_submit = process_cmd_add_peer(&tx_message, p+8); + } else if (!strncmp(p, "delete", 6)) { + do_normal_submit = process_cmd_delete(&tx_message, p+6); + } else if (!strncmp(p, "writertc", 7)) { + process_cmd_writertc(&tx_message, p+7); + } else if (!strncmp(p, "rtcdata", 7)) { + do_normal_submit = 0; + process_cmd_rtcreport(p); + } else if (!strncmp(p, "trimrtc", 7)) { + process_cmd_trimrtc(&tx_message, p); + } else if (!strncmp(p, "cyclelogs", 9)) { + process_cmd_cyclelogs(&tx_message, p); + } else if (!strncmp(p, "dfreq", 5)) { + process_cmd_dfreq(&tx_message, p+5); + } else if (!strncmp(p, "doffset", 7)) { + process_cmd_doffset(&tx_message, p+7); + } else if (!strncmp(p, "tracking", 8)) { + process_cmd_tracking(p+8); + do_normal_submit = 0; + } else if (!strncmp(p, "clients", 7)) { + process_cmd_clients(p+7); + do_normal_submit = 0; + } else if (!strncmp(p, "makestep", 8)) { + process_cmd_makestep(&tx_message, p+8); + } else if (!strncmp(p, "activity", 8)) { + process_cmd_activity(p+8); + do_normal_submit = 0; + } else if (!strncmp(p, "help", 4)) { + do_normal_submit = 0; + give_help(); + } else if (!strncmp(p, "quit", 4)) { + do_normal_submit = 0; + quit = 1; + } else if (!strncmp(p, "exit", 4)) { + do_normal_submit = 0; + quit = 1; + } else { + fprintf(stderr, "Unrecognized command\n"); + do_normal_submit = 0; + } + + if (do_normal_submit) { + + request_submitted_ok = submit_request(&tx_message, &rx_message, &reply_auth_ok); + + if (request_submitted_ok) { + switch(ntohs(rx_message.status)) { + case STT_SUCCESS: + printf("200 OK"); + 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_ACCESSALLOWED: + printf("208 Access allowed"); + break; + case STT_ACCESSDENIED: + printf("209 Access denied"); + 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; + } + + if (reply_auth_ok) { + printf("\n"); + } else { + printf(" --- Reply not authenticated\n"); + } + } + } + + return quit; +} + +/* ================================================== */ + +static void +process_args(int argc, char **argv) +{ + int total_length, i; + char *line; + + total_length = 0; + for(i=0; i] [-p ] [-n] [command]\n", progname); + exit(1); + } else { + break; /* And process remainder of line as a command */ + } + } + + if (isatty(0) && isatty(1) && isatty(2)) { + on_terminal = 1; + } + + if (on_terminal && (argc == 0)) { + display_gpl(); + } + + open_io(hostname, port); + + if (argc > 0) { + process_args(argc, argv); + } else { + do { + line = read_line(); + if (line) { + quit = process_line(line); + }else { + /* supply the final '\n' when user exits via ^D */ + if( on_terminal ) printf("\n"); + } + } while (line && !quit); + } + + close_io(); + + return 0; +} + + diff --git a/clientlog.c b/clientlog.c new file mode 100644 index 0000000..fec7f37 --- /dev/null +++ b/clientlog.c @@ -0,0 +1,392 @@ +/* + $Header: /cvs/src/chrony/clientlog.c,v 1.10 2003/01/20 22:24:20 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "sysincl.h" +#include "clientlog.h" +#include "conf.h" +#include "memory.h" +#include "reports.h" +#include "util.h" + +/* Number of bits of address per layer of the table. This value has + been chosen on the basis that a server will predominantly be serving + a lot of hosts in a few subnets, rather than a few hosts scattered + across many subnets. */ + +#define NBITS 8 + +/* Number of entries in each subtable */ +#define TABLE_SIZE (1UL<entry[i] = NULL; + } +} + +/* ================================================== */ + +static void +clear_node(Node *node) +{ + node->client_hits = 0; + node->peer_hits = 0; + node->cmd_hits_auth = 0; + node->cmd_hits_normal = 0; + node->cmd_hits_bad = 0; + node->last_ntp_hit = (time_t) 0; + node->last_cmd_hit = (time_t) 0; +} + +/* ================================================== */ + +void +CLG_Initialise(void) +{ + clear_subnet(&top_subnet); + if (CNF_GetNoClientLog()) { + active = 0; + } else { + active = 1; + } + + nodes = NULL; + max_nodes = 0; + n_nodes = 0; + +} + +/* ================================================== */ + +void +CLG_Finalise(void) +{ + return; +} + +/* ================================================== */ + +static void +create_subnet(Subnet *parent_subnet, int the_entry) +{ + parent_subnet->entry[the_entry] = (void *) MallocNew(Subnet); + clear_subnet((Subnet *) parent_subnet->entry[the_entry]); +} + +/* ================================================== */ + +static void +create_node(Subnet *parent_subnet, int the_entry) +{ + Node *new_node; + new_node = MallocNew(Node); + parent_subnet->entry[the_entry] = (void *) new_node; + clear_node(new_node); + + if (n_nodes == max_nodes) { + if (nodes) { + max_nodes += NODE_TABLE_INCREMENT; + nodes = ReallocArray(Node *, max_nodes, nodes); + } else { + if (max_nodes != 0) { + CROAK("max_nodes should be 0"); + } + max_nodes = NODE_TABLE_INCREMENT; + nodes = MallocArray(Node *, max_nodes); + } + } + nodes[n_nodes++] = (Node *) new_node; +} + +/* ================================================== */ +/* Recursively seek out the Node entry for a particular address, + expanding subnet tables and node entries as we go if necessary. */ + +static void * +find_subnet(Subnet *subnet, CLG_IP_Addr addr, int bits_left) +{ + unsigned long this_subnet, new_subnet, mask, shift; + unsigned long new_bits_left; + + shift = 32 - NBITS; + mask = (1UL<> shift; + new_subnet = (addr & mask) << NBITS; + new_bits_left = bits_left - NBITS; + +#if 0 + fprintf(stderr, "fs addr=%08lx bl=%d ma=%08lx this=%08lx newsn=%08lx nbl=%d\n", + addr, bits_left, mask, this_subnet, new_subnet, new_bits_left); +#endif + + if (new_bits_left > 0) { + if (!subnet->entry[this_subnet]) { + create_subnet(subnet, this_subnet); + } + return find_subnet((Subnet *) subnet->entry[this_subnet], new_subnet, new_bits_left); + } else { + if (!subnet->entry[this_subnet]) { + create_node(subnet, this_subnet); + } + return subnet->entry[this_subnet]; + } +} + + +/* ================================================== */ +/* Search for the record for a particular subnet, but return NULL if + one of the parents does not exist - never open a node out */ + +static void * +find_subnet_dont_open(Subnet *subnet, CLG_IP_Addr addr, int bits_left) +{ + unsigned long this_subnet, new_subnet, mask, shift; + unsigned long new_bits_left; + + if (bits_left == 0) { + return subnet; + } else { + + shift = 32 - NBITS; + mask = (1UL<> shift; + new_subnet = (addr & mask) << NBITS; + new_bits_left = bits_left - NBITS; + +#if 0 + fprintf(stderr, "fsdo addr=%08lx bl=%d this=%08lx newsn=%08lx nbl=%d\n", + addr, bits_left, this_subnet, new_subnet, new_bits_left); +#endif + + if (!subnet->entry[this_subnet]) { + return NULL; + } else { + return find_subnet_dont_open((Subnet *) subnet->entry[this_subnet], new_subnet, new_bits_left); + } + } +} + +/* ================================================== */ + +void +CLG_LogNTPClientAccess (CLG_IP_Addr client, time_t now) +{ + Node *node; + if (active) { + node = (Node *) find_subnet(&top_subnet, client, 32); + node->ip_addr = client; + ++node->client_hits; + node->last_ntp_hit = now; + } +} + +/* ================================================== */ + +void +CLG_LogNTPPeerAccess(CLG_IP_Addr client, time_t now) +{ + Node *node; + if (active) { + node = (Node *) find_subnet(&top_subnet, client, 32); + node->ip_addr = client; + ++node->peer_hits; + node->last_ntp_hit = now; + } +} + +/* ================================================== */ + +void +CLG_LogCommandAccess(CLG_IP_Addr client, CLG_Command_Type type, time_t now) +{ + Node *node; + if (active) { + node = (Node *) find_subnet(&top_subnet, client, 32); + node->ip_addr = client; + node->last_cmd_hit = now; + switch (type) { + case CLG_CMD_AUTH: + ++node->cmd_hits_auth; + break; + case CLG_CMD_NORMAL: + ++node->cmd_hits_normal; + break; + case CLG_CMD_BAD_PKT: + ++node->cmd_hits_bad; + break; + default: + CROAK("Impossible"); + break; + } + } +} + +/* ================================================== */ + +CLG_Status +CLG_GetSubnetBitmap(CLG_IP_Addr subnet, int bits, CLG_Bitmap result) +{ + Subnet *s; + unsigned long i; + unsigned long word, bit, mask; + + if ((bits == 0) || (bits == 8) || (bits == 16) || (bits == 24)) { + memset (result, 0, TABLE_SIZE/8); + if (active) { + s = find_subnet_dont_open(&top_subnet, subnet, bits); + if (s) { + for (i=0; i<256; i++) { + if (s->entry[i]) { + word = i / 32; + bit = i % 32; + mask = 1UL << bit; + result[word] |= mask; + } + } + return CLG_SUCCESS; + } else { + return CLG_EMPTYSUBNET; + } + } else { + return CLG_INACTIVE; + } + } else { + return CLG_BADSUBNET; + } +} + +/* ================================================== */ + +CLG_Status +CLG_GetClientAccessReportByIP(unsigned long ip, RPT_ClientAccess_Report *report, time_t now) +{ + Node *node; + + if (!active) { + return CLG_INACTIVE; + } else { + node = (Node *) find_subnet_dont_open(&top_subnet, ip, 32); + + if (!node) { + return CLG_EMPTYSUBNET; + } else { + report->client_hits = node->client_hits; + report->peer_hits = node->peer_hits; + report->cmd_hits_auth = node->cmd_hits_auth; + report->cmd_hits_normal = node->cmd_hits_normal; + report->cmd_hits_bad = node->cmd_hits_bad; + report->last_ntp_hit_ago = now - node->last_ntp_hit; + report->last_cmd_hit_ago = now - node->last_cmd_hit; + + return CLG_SUCCESS; + } + } +} + +/* ================================================== */ + +CLG_Status +CLG_GetClientAccessReportByIndex(int index, RPT_ClientAccessByIndex_Report *report, + time_t now, unsigned long *n_indices) +{ + Node *node; + + *n_indices = n_nodes; + + if (!active) { + return CLG_INACTIVE; + } else { + + if ((index < 0) || (index >= n_nodes)) { + return CLG_INDEXTOOLARGE; + } + + node = nodes[index]; + + report->ip_addr = node->ip_addr; + report->client_hits = node->client_hits; + report->peer_hits = node->peer_hits; + report->cmd_hits_auth = node->cmd_hits_auth; + report->cmd_hits_normal = node->cmd_hits_normal; + report->cmd_hits_bad = node->cmd_hits_bad; + report->last_ntp_hit_ago = now - node->last_ntp_hit; + report->last_cmd_hit_ago = now - node->last_cmd_hit; + + return CLG_SUCCESS; + } + +} diff --git a/clientlog.h b/clientlog.h new file mode 100644 index 0000000..7242745 --- /dev/null +++ b/clientlog.h @@ -0,0 +1,91 @@ +/* + $Header: /cvs/src/chrony/clientlog.h,v 1.8 2003/04/01 20:54:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 unsigned long CLG_IP_Addr; + +/* Enough to hold flags for 256 hosts in a class C */ +typedef uint32_t CLG_Bitmap[8]; + +extern void CLG_Initialise(void); +extern void CLG_Finalise(void); +extern void CLG_LogNTPClientAccess(CLG_IP_Addr client, time_t now); +extern void CLG_LogNTPPeerAccess(CLG_IP_Addr client, time_t now); + +/* When logging command packets, there are several subtypes */ + +typedef enum { + CLG_CMD_AUTH, /* authenticated */ + CLG_CMD_NORMAL, /* normal */ + CLG_CMD_BAD_PKT /* bad version or packet length */ +} CLG_Command_Type; + +extern void CLG_LogCommandAccess(CLG_IP_Addr client, CLG_Command_Type type, time_t now); + +/* And some reporting functions, for use by chronyc. */ +/* TBD */ + +typedef enum { + CLG_SUCCESS, /* All is well */ + CLG_EMPTYSUBNET, /* No hosts logged in requested subnet */ + CLG_BADSUBNET, /* Subnet requested is not 0, 8, 16 or 24 bits */ + CLG_INACTIVE, /* Facility not active */ + CLG_INDEXTOOLARGE /* Node index is higher than number of nodes present */ +} CLG_Status; + +/* For bits=0, 8, 16, flag which immediate subnets of that subnet are + known. For bits=24, flag which hosts in that subnet are known. + Other values, return 0 (failed) */ + +extern CLG_Status CLG_GetSubnetBitmap(CLG_IP_Addr subnet, int bits, CLG_Bitmap result); + +extern CLG_Status +CLG_GetClientAccessReportByIP(unsigned long ip, RPT_ClientAccess_Report *report, time_t now); + +CLG_Status +CLG_GetClientAccessReportByIndex(int index, RPT_ClientAccessByIndex_Report *report, + time_t now, unsigned long *n_indices); + +/* And an iterating function, to call 'fn' for each client or peer + that has accessed us since 'since'. */ + +extern void CLG_IterateNTPClients +(void (*fn)(CLG_IP_Addr client, void *arb), + void *arb, + time_t since); + + +#endif /* GOT_CLIENTLOG_H */ diff --git a/cmdmon.c b/cmdmon.c new file mode 100644 index 0000000..5f99d99 --- /dev/null +++ b/cmdmon.c @@ -0,0 +1,2119 @@ +/* + $Header: /cvs/src/chrony/cmdmon.c,v 1.54 2003/04/01 20:54:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Command and monitoring module in the main program + */ + +#include "sysincl.h" + +#include "cmdmon.h" +#include "candm.h" +#include "sched.h" +#include "util.h" +#include "logging.h" +#include "md5.h" +#include "keys.h" +#include "ntp_sources.h" +#include "ntp_core.h" +#include "sources.h" +#include "sourcestats.h" +#include "reference.h" +#include "manual.h" +#include "memory.h" +#include "local.h" +#include "addrfilt.h" +#include "conf.h" +#include "rtc.h" +#include "pktlength.h" +#include "clientlog.h" + +/* ================================================== */ + +/* File descriptor for command and monitoring socket */ +static int sock_fd; + +/* Flag indicating whether this module has been initialised or not */ +static int initialised = 0; + +/* Token which is unique every time the daemon is run */ +static unsigned long utoken; + +/* The register of issued tokens */ +static unsigned long issued_tokens; + +/* The register of received tokens */ +static unsigned long returned_tokens; + +/* The token number corresponding to the base of the registers */ +static unsigned long token_base; + +/* The position of the next free token to issue in the issue register */ +static unsigned long issue_pointer; + +/* Type and linked list for buffering responses */ +typedef struct _ResponseCell { + struct _ResponseCell *next; + unsigned long tok; /* The token that the client sent in the message + to which this was the reply */ + unsigned long next_tok; /* The next token issued to the same client. + If we receive a request with this token, + it implies the reply stored in this cell + was successfully received */ + unsigned long msg_seq; /* Client's sequence number used in request + to which this is the response. */ + unsigned long attempt; /* Attempt number that we saw in the last request + with this sequence number (prevents attacker + firing the same request at us to make us + keep generating the same reply). */ + struct timeval ts; /* Time we saved the reply - allows purging based + on staleness. */ + CMD_Reply rpy; +} ResponseCell; + +static ResponseCell kept_replies; +static ResponseCell *free_replies; + +/* ================================================== */ +/* Array of permission levels for command types */ + +static int 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_OPEN, /* SUBNETS_ACCESSED */ + PERMIT_OPEN, /* CLIENT_ACCESSES (by subnet) */ + PERMIT_OPEN, /* CLIENT_ACCESSES_BY_INDEX */ + PERMIT_OPEN, /* MANUAL_LIST */ + PERMIT_AUTH, /* MANUAL_DELETE */ + PERMIT_AUTH, /* MAKESTEP */ + PERMIT_OPEN /* ACTIVITY */ +}; + +/* ================================================== */ + +/* 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(void *anything); + +/* ================================================== */ + +void +CAM_Initialise(void) +{ + int port_number; + struct sockaddr_in my_addr; + unsigned long bind_address; + int on_off; + + if (initialised) { + CROAK("Shouldn't be initialised"); + } + + initialised = 1; + + if ((sizeof(permissions)/sizeof(permissions[0])) != N_REQUEST_TYPES) { + CROAK("Permissions table size wrong"); + } + + utoken = (unsigned long) time(NULL); + + issued_tokens = returned_tokens = issue_pointer = 0; + token_base = 1; /* zero is the value used when the previous command was + unauthenticated */ + + free_replies = NULL; + kept_replies.next = NULL; + + port_number = CNF_GetCommandPort(); + if (port_number < 0) { + port_number = DEFAULT_CANDM_PORT; + } + + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd < 0) { + LOG_FATAL(LOGF_CmdMon, "Could not open socket : %s", strerror(errno)); + } + + /* Allow reuse of port number */ + if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on_off, sizeof(on_off)) < 0) { + LOG(LOGS_ERR, LOGF_CmdMon, "Could not set socket options"); + /* Don't quit - we might survive anyway */ + } + + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons((unsigned short) port_number); + + CNF_GetBindCommandAddress(&bind_address); + + if (bind_address != 0UL) { + my_addr.sin_addr.s_addr = htonl(bind_address); + } else { + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + } + + if (bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + LOG_FATAL(LOGF_CmdMon, "Could not bind socket : %s", strerror(errno)); + } + + /* Register handler for read events on the socket */ + SCH_AddInputFileHandler(sock_fd, read_from_cmd_socket, NULL); + + access_auth_table = ADF_CreateTable(); + +} + +/* ================================================== */ + +void +CAM_Finalise(void) +{ + SCH_RemoveInputFileHandler(sock_fd); + close(sock_fd); + sock_fd = -1; + + ADF_DestroyTable(access_auth_table); + + initialised = 0; + return; +} + +/* ================================================== */ +/* This function checks whether the authenticator field of the packet + checks correctly against what we would compute locally given the + rest of the packet */ + +static int +check_rx_packet_auth(CMD_Request *packet) +{ + + char *key; + int keylen; + int pkt_len; + MD5_CTX ctx; + + pkt_len = PKL_CommandLength(packet); + + KEY_CommandKey(&key, &keylen); + + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char *) key, keylen); + MD5Update(&ctx, (unsigned char *) packet, offsetof(CMD_Request, auth)); + if (pkt_len > offsetof(CMD_Request, data)) { + MD5Update(&ctx, (unsigned char *) &(packet->data), pkt_len - offsetof(CMD_Request, data)); + } + MD5Final(&ctx); + + if (!memcmp((void *) &ctx.digest, (void *) &(packet->auth), 16)) { + return 1; + } else { + return 0; + } +} + +/* ================================================== */ + +static void +generate_tx_packet_auth(CMD_Reply *packet) +{ + char *key; + int keylen; + MD5_CTX ctx; + int pkt_len; + + pkt_len = PKL_ReplyLength(packet); + + KEY_CommandKey(&key, &keylen); + + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char *) key, keylen); + MD5Update(&ctx, (unsigned char *) packet, offsetof(CMD_Request, auth)); + if (pkt_len > offsetof(CMD_Reply, data)) { + MD5Update(&ctx, (unsigned char *) &(packet->data), pkt_len - offsetof(CMD_Reply, data)); + } + MD5Final(&ctx); + + memcpy(&(packet->auth), &ctx.digest, 16); + +} + +/* ================================================== */ + +static void +shift_tokens(void) +{ + do { + issued_tokens >>= 1; + returned_tokens >>= 1; + token_base++; + issue_pointer--; + } while ((issued_tokens & 1) && (returned_tokens & 1)); +} + +/* ================================================== */ + +static unsigned long +get_token(void) +{ + unsigned long result; + + if (issue_pointer == 32) { + /* The lowest number open token has not been returned - bad luck + to that command client */ + shift_tokens(); + } + + result = token_base + issue_pointer; + issued_tokens |= (1UL << issue_pointer); + issue_pointer++; + + return result; +} + +/* ================================================== */ + +static int +check_token(unsigned long token) +{ + int result; + unsigned long pos; + + if (token < token_base) { + /* Token too old */ + result = 0; + } else { + pos = token - token_base; + if (pos >= issue_pointer) { + /* Token hasn't been issued yet */ + result = 0; + } else { + if (returned_tokens & (1UL << pos)) { + /* Token has already been returned */ + result = 0; + } else { + /* Token is OK */ + result = 1; + returned_tokens |= (1UL << pos); + if (pos == 0) { + shift_tokens(); + } + } + } + } + + return result; + +} + +/* ================================================== */ + +#define TS_MARGIN 20 + +/* ================================================== */ + +typedef struct _TimestampCell { + struct _TimestampCell *next; + struct timeval ts; +} TimestampCell; + +static struct _TimestampCell seen_ts_list={NULL}; +static struct _TimestampCell *free_ts_list=NULL; + +#define EXTEND_QUANTUM 32 + +/* ================================================== */ + +static TimestampCell * +allocate_ts_cell(void) +{ + TimestampCell *result; + int i; + if (free_ts_list == NULL) { + free_ts_list = MallocArray(TimestampCell, EXTEND_QUANTUM); + for (i=0; inext; + return result; +} + +/* ================================================== */ + +static void +release_ts_cell(TimestampCell *node) +{ + node->next = free_ts_list; + free_ts_list = node; +} + +/* ================================================== */ +/* Return 1 if not found, 0 if found (i.e. not unique). Prune out any + stale entries. */ + +static int +check_unique_ts(struct timeval *ts, struct timeval *now) +{ + TimestampCell *last_valid, *cell, *next; + int ok; + + ok = 1; + last_valid = &(seen_ts_list); + cell = last_valid->next; + + while (cell) { + next = cell->next; + /* Check if stale */ + if ((now->tv_sec - cell->ts.tv_sec) > TS_MARGIN) { + release_ts_cell(cell); + last_valid->next = next; + } else { + /* Timestamp in cell is still within window */ + last_valid->next = cell; + last_valid = cell; + if ((cell->ts.tv_sec == ts->tv_sec) && (cell->ts.tv_usec == ts->tv_usec)) { + ok = 0; + } + } + cell = next; + } + + if (ok) { + /* Need to add this timestamp to the list */ + cell = allocate_ts_cell(); + last_valid->next = cell; + cell->next = NULL; + cell->ts = *ts; + } + + return ok; +} + +/* ================================================== */ + +static int +ts_is_unique_and_not_stale(struct timeval *ts, struct timeval *now) +{ + long tv_sec; + struct timeval host_order_ts; + int within_margin=0; + int is_unique=0; + long diff; + + host_order_ts.tv_sec = tv_sec = ntohl(ts->tv_sec); + host_order_ts.tv_usec = ntohl(ts->tv_usec); + + diff = now->tv_sec - tv_sec; + if ((diff < TS_MARGIN) && (diff > -TS_MARGIN)) { + within_margin = 1; + } else { + within_margin = 0; + } + is_unique = check_unique_ts(&host_order_ts, now); + + return within_margin && is_unique; +} + +/* ================================================== */ + +#define REPLY_EXTEND_QUANTUM 32 + +static void +get_more_replies(void) +{ + ResponseCell *new_replies; + int i; + + if (!free_replies) { + new_replies = MallocArray(ResponseCell, REPLY_EXTEND_QUANTUM); + for (i=1; inext; + return result; +} + +/* ================================================== */ + +static void +free_reply_slot(ResponseCell *cell) +{ + cell->next = free_replies; + free_replies = cell; +} + +/* ================================================== */ + +static void +save_reply(CMD_Reply *msg, + unsigned long tok_reply_to, + unsigned long new_tok_issued, + unsigned long client_msg_seq, + unsigned short attempt, + struct timeval *now) +{ + ResponseCell *cell; + + cell = get_reply_slot(); + + cell->ts = *now; + memcpy(&cell->rpy, msg, sizeof(CMD_Reply)); + cell->tok = tok_reply_to; + cell->next_tok = new_tok_issued; + cell->msg_seq = client_msg_seq; + cell->attempt = (unsigned long) attempt; + + cell->next = kept_replies.next; + kept_replies.next = cell; + +} + +/* ================================================== */ + +static CMD_Reply * +lookup_reply(unsigned long prev_msg_token, unsigned long client_msg_seq, unsigned short attempt) +{ + ResponseCell *ptr; + + ptr = kept_replies.next; + while (ptr) { + if ((ptr->tok == prev_msg_token) && + (ptr->msg_seq == client_msg_seq) && + ((unsigned long) attempt > ptr->attempt)) { + + /* Set the attempt field to remember the highest number we have + had so far */ + ptr->attempt = (unsigned long) attempt; + return &ptr->rpy; + } + ptr = ptr->next; + } + + return NULL; +} + + +/* ================================================== */ + +#define REPLY_MAXAGE 300 + +static void +token_acknowledged(unsigned long token, struct timeval *now) +{ + ResponseCell *last_valid, *cell, *next; + + last_valid = &kept_replies; + cell = kept_replies.next; + + while(cell) { + next = cell->next; + + /* Discard if it's the one or if the reply is stale */ + if ((cell->next_tok == token) || + ((now->tv_sec - cell->ts.tv_sec) > REPLY_MAXAGE)) { + free_reply_slot(cell); + last_valid->next = next; + } else { + last_valid->next = cell; + last_valid = cell; + } + cell = next; + } +} + +/* ================================================== */ + +#if 0 +/* These two routines are not legal if the program is operating as a daemon, since + stderr is no longer open */ + +static void +print_command_packet(CMD_Request *pkt, int length) +{ + unsigned char *x; + int i; + x = (unsigned char *) pkt; + for (i=0; isin_addr.s_addr); + remote_port = ntohs(where_to->sin_port); + LOG(LOGS_WARN, LOGF_CmdMon, "Could not send response to %s:%hu", UTI_IPToDottedQuad(remote_ip), remote_port); + } + + return; +} + + +/* ================================================== */ + +static void +handle_null(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + tx_message->status = htons(STT_SUCCESS); +} + +/* ================================================== */ + +static void +handle_online(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = NSR_TakeSourcesOnline(ntohl(rx_message->data.online.mask), ntohl(rx_message->data.online.address)); + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_offline(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = NSR_TakeSourcesOffline(ntohl(rx_message->data.offline.mask), ntohl(rx_message->data.offline.address)); + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_burst(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = NSR_InitiateSampleBurst(ntohl(rx_message->data.burst.n_good_samples), + ntohl(rx_message->data.burst.n_total_samples), + ntohl(rx_message->data.burst.mask), + ntohl(rx_message->data.burst.address)); + + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_modify_minpoll(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = NSR_ModifyMinpoll(ntohl(rx_message->data.modify_minpoll.address), + ntohl(rx_message->data.modify_minpoll.new_minpoll)); + + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_modify_maxpoll(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = NSR_ModifyMaxpoll(ntohl(rx_message->data.modify_minpoll.address), + ntohl(rx_message->data.modify_minpoll.new_minpoll)); + + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_modify_maxdelay(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = NSR_ModifyMaxdelay(ntohl(rx_message->data.modify_maxdelay.address), + WIRE2REAL(rx_message->data.modify_maxdelay.new_max_delay)); + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_modify_maxdelayratio(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = NSR_ModifyMaxdelayratio(ntohl(rx_message->data.modify_maxdelayratio.address), + WIRE2REAL(rx_message->data.modify_maxdelayratio.new_max_delay_ratio)); + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_modify_maxupdateskew(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + REF_ModifyMaxupdateskew(WIRE2REAL(rx_message->data.modify_maxupdateskew.new_max_update_skew)); + tx_message->status = htons(STT_SUCCESS); +} + +/* ================================================== */ + +static void +handle_settime(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + struct timeval ts; + long offset_cs; + double dfreq_ppm, new_afreq_ppm; + ts.tv_sec = ntohl(rx_message->data.settime.ts.tv_sec); + ts.tv_usec = ntohl(rx_message->data.settime.ts.tv_usec); + if (MNL_AcceptTimestamp(&ts, &offset_cs, &dfreq_ppm, &new_afreq_ppm)) { + tx_message->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_MANUAL_TIMESTAMP); + tx_message->data.manual_timestamp.centiseconds = htonl(offset_cs); + tx_message->data.manual_timestamp.dfreq_ppm = REAL2WIRE(dfreq_ppm); + tx_message->data.manual_timestamp.new_afreq_ppm = REAL2WIRE(new_afreq_ppm); + } else { + tx_message->status = htons(STT_NOTENABLED); + } +} + +/* ================================================== */ + +static void +handle_local(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int on_off, stratum; + on_off = ntohl(rx_message->data.local.on_off); + if (on_off) { + stratum = ntohl(rx_message->data.local.stratum); + REF_EnableLocal(stratum); + } else { + REF_DisableLocal(); + } + tx_message->status = htons(STT_SUCCESS); +} + +/* ================================================== */ + +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; + } + tx_message->status = htons(STT_SUCCESS); +} + +/* ================================================== */ + +static void +handle_n_sources(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int n_sources; + n_sources = SRC_ReadNumberOfSources(); + tx_message->status = htons(STT_SUCCESS); + 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 timeval now_corr; + double local_clock_err; + + /* Get data */ + LCL_ReadCookedTime(&now_corr, &local_clock_err); + if (SRC_ReportSource(ntohl(rx_message->data.source_data.index), &report, &now_corr)) { + NSR_ReportSource(&report, &now_corr); + + tx_message->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_SOURCE_DATA); + + tx_message->data.source_data.ip_addr = htonl(report.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_SYNC: + tx_message->data.source_data.state = htons(RPY_SD_ST_SYNC); + break; + case RPT_UNREACH: + tx_message->data.source_data.state = htons(RPY_SD_ST_UNREACH); + 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_OTHER: + tx_message->data.source_data.state = htons(RPY_SD_ST_OTHER); + 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.since_sample = htonl(report.latest_meas_ago); + tx_message->data.source_data.orig_latest_meas = htonl(report.orig_latest_meas); + tx_message->data.source_data.latest_meas = htonl(report.latest_meas); + tx_message->data.source_data.latest_meas_err = htonl(report.latest_meas_err); + tx_message->data.source_data.est_offset = htonl(report.est_offset); + tx_message->data.source_data.est_offset_err = htonl(report.est_offset_err); + tx_message->data.source_data.resid_freq = htonl(report.resid_freq); + tx_message->data.source_data.resid_skew = htonl(report.resid_skew); + + } else { + tx_message->status = htons(STT_NOSUCHSOURCE); + } +} + +/* ================================================== */ + +static void +handle_rekey(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + tx_message->status = htons(STT_SUCCESS); + KEY_Reload(); +} + +/* ================================================== */ + +static void +handle_allow(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (NCR_AddAccessRestriction(ip, subnet_bits, 1, 0)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_allowall(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (NCR_AddAccessRestriction(ip, subnet_bits, 1, 1)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_deny(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (NCR_AddAccessRestriction(ip, subnet_bits, 0, 0)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_denyall(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (NCR_AddAccessRestriction(ip, subnet_bits, 0, 1)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_cmdallow(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (CAM_AddAccessRestriction(ip, subnet_bits, 1, 0)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_cmdallowall(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (CAM_AddAccessRestriction(ip, subnet_bits, 1, 1)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_cmddeny(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (CAM_AddAccessRestriction(ip, subnet_bits, 0, 0)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_cmddenyall(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + int subnet_bits; + ip = ntohl(rx_message->data.allow_deny.ip); + subnet_bits = ntohl(rx_message->data.allow_deny.subnet_bits); + if (CAM_AddAccessRestriction(ip, subnet_bits, 0, 1)) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_BADSUBNET); + } +} + +/* ================================================== */ + +static void +handle_accheck(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + unsigned long ip; + ip = ntohl(rx_message->data.ac_check.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) +{ + unsigned long ip; + ip = ntohl(rx_message->data.ac_check.ip); + if (CAM_CheckAccessRestriction(ip)) { + tx_message->status = htons(STT_ACCESSALLOWED); + } else { + tx_message->status = htons(STT_ACCESSDENIED); + } +} + +/* ================================================== */ + +static void +handle_add_server(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + NTP_Remote_Address rem_addr; + SourceParameters params; + NSR_Status status; + + rem_addr.ip_addr = ntohl(rx_message->data.ntp_source.ip_addr); + rem_addr.port = (unsigned short)(ntohl(rx_message->data.ntp_source.port)); + params.minpoll = ntohl(rx_message->data.ntp_source.minpoll); + params.maxpoll = ntohl(rx_message->data.ntp_source.maxpoll); + params.presend_minpoll = ntohl(rx_message->data.ntp_source.presend_minpoll); + params.authkey = ntohl(rx_message->data.ntp_source.authkey); + params.online = ntohl(rx_message->data.ntp_source.online); + params.auto_offline = ntohl(rx_message->data.ntp_source.auto_offline); + params.max_delay = WIRE2REAL(rx_message->data.ntp_source.max_delay); + params.max_delay_ratio = WIRE2REAL(rx_message->data.ntp_source.max_delay_ratio); + status = NSR_AddServer(&rem_addr, ¶ms); + switch (status) { + case NSR_Success: + tx_message->status = htons(STT_SUCCESS); + break; + case NSR_AlreadyInUse: + tx_message->status = htons(STT_SOURCEALREADYKNOWN); + break; + case NSR_TooManySources: + tx_message->status = htons(STT_TOOMANYSOURCES); + break; + case NSR_NoSuchSource: + CROAK("Impossible"); + break; + } +} + +/* ================================================== */ + +static void +handle_add_peer(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + NTP_Remote_Address rem_addr; + SourceParameters params; + NSR_Status status; + + rem_addr.ip_addr = ntohl(rx_message->data.ntp_source.ip_addr); + rem_addr.port = (unsigned short)(ntohl(rx_message->data.ntp_source.port)); + params.minpoll = ntohl(rx_message->data.ntp_source.minpoll); + params.maxpoll = ntohl(rx_message->data.ntp_source.maxpoll); + params.presend_minpoll = ntohl(rx_message->data.ntp_source.presend_minpoll); + params.authkey = ntohl(rx_message->data.ntp_source.authkey); + params.online = ntohl(rx_message->data.ntp_source.online); + params.max_delay = WIRE2REAL(rx_message->data.ntp_source.max_delay); + params.max_delay_ratio = WIRE2REAL(rx_message->data.ntp_source.max_delay_ratio); + status = NSR_AddPeer(&rem_addr, ¶ms); + switch (status) { + case NSR_Success: + tx_message->status = htons(STT_SUCCESS); + break; + case NSR_AlreadyInUse: + tx_message->status = htons(STT_SOURCEALREADYKNOWN); + break; + case NSR_TooManySources: + tx_message->status = htons(STT_TOOMANYSOURCES); + break; + case NSR_NoSuchSource: + CROAK("Impossible"); + break; + } +} + +/* ================================================== */ + +static void +handle_del_source(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + NTP_Remote_Address rem_addr; + NSR_Status status; + + rem_addr.ip_addr = ntohl(rx_message->data.del_source.ip_addr); + rem_addr.port = 0; + + status = NSR_RemoveSource(&rem_addr); + switch (status) { + case NSR_Success: + tx_message->status = htons(STT_SUCCESS); + break; + case NSR_NoSuchSource: + tx_message->status = htons(STT_NOSUCHSOURCE); + break; + case NSR_TooManySources: + case NSR_AlreadyInUse: + CROAK("Impossible"); + break; + } +} + +/* ================================================== */ + +static void +handle_writertc(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + switch (RTC_WriteParameters()) { + case RTC_ST_OK: + tx_message->status = htons(STT_SUCCESS); + 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 = WIRE2REAL(rx_message->data.dfreq.dfreq); + LCL_AccumulateDeltaFrequency(dfreq * 1.0e-6); + LOG(LOGS_INFO, LOGF_CmdMon, "Accumulated delta freq of %.3fppm", dfreq); +} + +/* ================================================== */ + +static void +handle_doffset(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + long sec, usec; + double doffset; + sec = (long)(ntohl(rx_message->data.doffset.sec)); + usec = (long)(ntohl(rx_message->data.doffset.usec)); + doffset = (double) sec + 1.0e-6 * (double) usec; + LOG(LOGS_INFO, LOGF_CmdMon, "Accumulated delta offset of %.6f seconds", doffset); + LCL_AccumulateOffset(doffset); +} + +/* ================================================== */ + +static void +handle_tracking(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + RPT_TrackingReport rpt; + + REF_GetTrackingReport(&rpt); + tx_message->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_TRACKING); + tx_message->data.tracking.ref_id = htonl(rpt.ref_id); + tx_message->data.tracking.stratum = htonl(rpt.stratum); + tx_message->data.tracking.ref_time_s = htonl(rpt.ref_time.tv_sec); + tx_message->data.tracking.ref_time_us = htonl(rpt.ref_time.tv_usec); + tx_message->data.tracking.current_correction_s = htonl(rpt.current_correction.tv_sec); + tx_message->data.tracking.current_correction_us = htonl(rpt.current_correction.tv_usec); + tx_message->data.tracking.freq_ppm = REAL2WIRE(rpt.freq_ppm); + tx_message->data.tracking.resid_freq_ppm = REAL2WIRE(rpt.resid_freq_ppm); + tx_message->data.tracking.skew_ppm = REAL2WIRE(rpt.skew_ppm); + tx_message->data.tracking.root_delay = REAL2WIRE(rpt.root_delay); + tx_message->data.tracking.root_dispersion = REAL2WIRE(rpt.root_dispersion); +} + +/* ================================================== */ + +static void +handle_sourcestats(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + RPT_SourcestatsReport report; + status = SRC_ReportSourcestats(ntohl(rx_message->data.sourcestats.index), + &report); + + if (status) { + tx_message->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_SOURCESTATS); + tx_message->data.sourcestats.ip_addr = htonl(report.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 = REAL2WIRE(report.resid_freq_ppm); + tx_message->data.sourcestats.skew_ppm = REAL2WIRE(report.skew_ppm); + tx_message->data.sourcestats.sd_us = htonl((unsigned long) (0.5 + report.sd_us)); + } 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->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_RTC); + tx_message->data.rtc.ref_time = htonl(report.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 = REAL2WIRE(report.rtc_seconds_fast); + tx_message->data.rtc.rtc_gain_rate_ppm = REAL2WIRE(report.rtc_gain_rate_ppm); + } else { + tx_message->status = htons(STT_NORTC); + } + return; +} + +/* ================================================== */ + +static void +handle_trimrtc(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = RTC_Trim(); + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NORTC); + } + return; +} + +/* ================================================== */ + +static void +handle_cyclelogs(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + NCR_CycleLogFile(); + SST_CycleLogFile(); + REF_CycleLogFile(); + RTC_CycleLogFile(); + + tx_message->status = htons(STT_SUCCESS); + return; +} + +/* ================================================== */ + +#define FLIPL(X) ((X) = htonl(X)) + +static void +handle_subnets_accessed(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int i, j; + unsigned long ns; + unsigned long ip, bits_specd; + CLG_Status result; + + ns = ntohl(rx_message->data.subnets_accessed.n_subnets); + tx_message->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_SUBNETS_ACCESSED); + tx_message->data.subnets_accessed.n_subnets = htonl(ns); + + for (i=0; idata.subnets_accessed.subnets[i].ip); + bits_specd = ntohl(rx_message->data.subnets_accessed.subnets[i].bits_specd); + + tx_message->data.subnets_accessed.subnets[i].ip = htonl(ip); + tx_message->data.subnets_accessed.subnets[i].bits_specd = htonl(bits_specd); + + result = CLG_GetSubnetBitmap(ip, bits_specd, tx_message->data.subnets_accessed.subnets[i].bitmap); + switch (result) { + case CLG_SUCCESS: + case CLG_EMPTYSUBNET: + /* Flip endianness of each 4 byte word. Don't care if subnet + is empty - just return an all-zero bitmap. */ + for (j=0; j<8; j++) { + FLIPL(tx_message->data.subnets_accessed.subnets[i].bitmap[j]); + } + break; + case CLG_BADSUBNET: + tx_message->status = htons(STT_BADSUBNET); + return; + case CLG_INACTIVE: + tx_message->status = htons(STT_INACTIVE); + return; + default: + CROAK("Impossible"); + break; + } + } + + return; +} + +/* ================================================== */ + +static void +handle_client_accesses(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + CLG_Status result; + RPT_ClientAccess_Report report; + unsigned long nc; + unsigned long ip; + int i; + struct timeval now; + double local_time_error; + + LCL_ReadCookedTime(&now, &local_time_error); + + nc = ntohl(rx_message->data.client_accesses.n_clients); + tx_message->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_CLIENT_ACCESSES); + tx_message->data.client_accesses.n_clients = htonl(nc); + + printf("%d %d\n", (int)sizeof(RPY_ClientAccesses_Client), (int)offsetof(CMD_Reply, data.client_accesses.clients)); + + for (i=0; idata.client_accesses.client_ips[i]); + tx_message->data.client_accesses.clients[i].ip = htonl(ip); + + result = CLG_GetClientAccessReportByIP(ip, &report, now.tv_sec); + switch (result) { + case CLG_SUCCESS: + tx_message->data.client_accesses.clients[i].client_hits = htonl(report.client_hits); + tx_message->data.client_accesses.clients[i].peer_hits = htonl(report.peer_hits); + tx_message->data.client_accesses.clients[i].cmd_hits_auth = htonl(report.cmd_hits_auth); + tx_message->data.client_accesses.clients[i].cmd_hits_normal = htonl(report.cmd_hits_normal); + tx_message->data.client_accesses.clients[i].cmd_hits_bad = htonl(report.cmd_hits_bad); + tx_message->data.client_accesses.clients[i].last_ntp_hit_ago = htonl(report.last_ntp_hit_ago); + tx_message->data.client_accesses.clients[i].last_cmd_hit_ago = htonl(report.last_cmd_hit_ago); + printf("%08lx %lu %lu %lu %lu %lu %lu %lu\n", ip, report.client_hits, report.peer_hits, report.cmd_hits_auth, report.cmd_hits_normal, report.cmd_hits_bad, report.last_ntp_hit_ago, report.last_cmd_hit_ago); + break; + case CLG_EMPTYSUBNET: + /* Signal back to the client that this single client address + was unknown, by specifying the zero ip address, which will + always be invalid (hopefully) */ + tx_message->data.client_accesses.clients[i].ip = htonl(0); + break; + case CLG_INACTIVE: + tx_message->status = htons(STT_INACTIVE); + return; + default: + CROAK("Impossible"); + break; + } + } + +} + +/* ================================================== */ + +static void +handle_client_accesses_by_index(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + CLG_Status result; + RPT_ClientAccessByIndex_Report report; + unsigned long first_index, n_indices, last_index, n_indices_in_table; + int i, j; + struct timeval now; + double local_time_error; + + LCL_ReadCookedTime(&now, &local_time_error); + + first_index = ntohl(rx_message->data.client_accesses_by_index.first_index); + n_indices = ntohl(rx_message->data.client_accesses_by_index.n_indices); + last_index = first_index + n_indices - 1; + + tx_message->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_CLIENT_ACCESSES_BY_INDEX); + + for (i = first_index, j = 0; + (i <= last_index) && (j < MAX_CLIENT_ACCESSES); + i++) { + + result = CLG_GetClientAccessReportByIndex(i, &report, now.tv_sec, &n_indices_in_table); + tx_message->data.client_accesses_by_index.n_indices = htonl(n_indices_in_table); + + switch (result) { + case CLG_SUCCESS: + tx_message->data.client_accesses_by_index.clients[j].ip = htonl(report.ip_addr); + tx_message->data.client_accesses_by_index.clients[j].client_hits = htonl(report.client_hits); + tx_message->data.client_accesses_by_index.clients[j].peer_hits = htonl(report.peer_hits); + tx_message->data.client_accesses_by_index.clients[j].cmd_hits_auth = htonl(report.cmd_hits_auth); + tx_message->data.client_accesses_by_index.clients[j].cmd_hits_normal = htonl(report.cmd_hits_normal); + tx_message->data.client_accesses_by_index.clients[j].cmd_hits_bad = htonl(report.cmd_hits_bad); + tx_message->data.client_accesses_by_index.clients[j].last_ntp_hit_ago = htonl(report.last_ntp_hit_ago); + tx_message->data.client_accesses_by_index.clients[j].last_cmd_hit_ago = htonl(report.last_cmd_hit_ago); + j++; + break; + case CLG_INDEXTOOLARGE: + break; /* ignore this index */ + case CLG_INACTIVE: + tx_message->status = htons(STT_INACTIVE); + return; + default: + CROAK("Impossible"); + break; + } + } + + 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->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_MANUAL_LIST); + + MNL_ReportSamples(report, MAX_MANUAL_LIST_SAMPLES, &n_samples); + tx_message->data.manual_list.n_samples = htonl(n_samples); + for (i=0; idata.manual_list.samples[i]; + sample->when = htonl(report[i].when); + sample->slewed_offset = REAL2WIRE(report[i].slewed_offset); + sample->orig_offset = REAL2WIRE(report[i].orig_offset); + sample->residual = REAL2WIRE(report[i].residual); + } +} + +/* ================================================== */ + +static void +handle_manual_delete(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + int index; + + index = ntohl(rx_message->data.manual_delete.index); + status = MNL_DeleteSample(index); + if (!status) { + tx_message->status = htons(STT_BADSAMPLE); + } else { + tx_message->status = htons(STT_SUCCESS); + } +} + +/* ================================================== */ + +static void +handle_make_step(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; + status = LCL_MakeStep(); + if (status) { + tx_message->status = htons(STT_SUCCESS); + } else { + tx_message->status = htons(STT_NOTENABLED); + } + return; +} + +/* ================================================== */ + +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->status = htons(STT_SUCCESS); + tx_message->reply = htons(RPY_ACTIVITY); +} + +/* ================================================== */ + +#if 0 +/* ================================================== */ + +static void +handle_(CMD_Request *rx_message, CMD_Reply *tx_message) +{ + int status; +} + + +#endif + +/* ================================================== */ +/* Read a packet and process it */ + +static void +read_from_cmd_socket(void *anything) +{ + int status; + int read_length; /* Length of packet read */ + int expected_length; /* Expected length of packet */ + unsigned long flags; + CMD_Request rx_message; + CMD_Reply tx_message, *prev_tx_message; + int rx_message_length, tx_message_length; + struct sockaddr_in where_from; + int from_length; + unsigned long remote_ip; + unsigned short remote_port; + int md5_ok; + int utoken_ok, token_ok; + int issue_token; + int valid_ts; + int authenticated; + int localhost; + unsigned short rx_command; + unsigned long rx_message_token; + unsigned long tx_message_token; + unsigned long rx_message_seq; + unsigned long rx_attempt; + struct timeval now; + struct timeval cooked_now; + double local_clock_err; + + flags = 0; + rx_message_length = sizeof(rx_message); + from_length = sizeof(where_from); + + status = recvfrom(sock_fd, (char *)&rx_message, rx_message_length, flags, + (struct sockaddr *)&where_from, &from_length); + + if (status < 0) { + LOG(LOGS_WARN, LOGF_CmdMon, "Error [%s] reading from control socket (IP=%s port=%d)", + strerror(errno), + UTI_IPToDottedQuad(ntohl(where_from.sin_addr.s_addr)), + ntohs(where_from.sin_port)); + } + + read_length = status; + expected_length = PKL_CommandLength(&rx_message); + + LCL_ReadRawTime(&now); + LCL_ReadCookedTime(&cooked_now, &local_clock_err); + + tx_message.version = PROTO_VERSION_NUMBER; + tx_message.pkt_type = PKT_TYPE_CMD_REPLY; + tx_message.res1 = 0; + tx_message.res2 = 0; + tx_message.command = rx_message.command; + tx_message.sequence = rx_message.sequence; + tx_message.reply = htons(RPY_NULL); + tx_message.number = htons(1); + tx_message.total = htons(1); + tx_message.utoken = htonl(utoken); + /* Set this to a default (invalid) value. This protects against the + token field being set to an arbitrary value if we reject the + message, e.g. due to the host failing the access check. */ + tx_message.token = htonl(0xffffffffUL); + + remote_ip = ntohl(where_from.sin_addr.s_addr); + remote_port = ntohs(where_from.sin_port); + + localhost = (remote_ip == 0x7f000001UL); + + if ((!ADF_IsAllowed(access_auth_table, remote_ip)) && + (!localhost)) { + /* The client is not allowed access, so don't waste any more time + on him. Note that localhost is always allowed access + regardless of the defined access rules - otherwise, we could + shut ourselves out completely! */ + + /* We ought to find another way to log this, there is an attack + here against the host because an adversary can just keep + hitting us with bad packets until our log file(s) fill up. */ + + LOG(LOGS_WARN, LOGF_CmdMon, "Command packet received from unauthorised host %s port %d", + UTI_IPToDottedQuad(remote_ip), + remote_port); + + tx_message.status = htons(STT_NOHOSTACCESS); + transmit_reply(&tx_message, &where_from); + + return; + } + + + if (read_length != expected_length) { + LOG(LOGS_WARN, LOGF_CmdMon, "Read incorrectly sized packet from %s:%hu", UTI_IPToDottedQuad(remote_ip), remote_port); + CLG_LogCommandAccess(remote_ip, CLG_CMD_BAD_PKT, cooked_now.tv_sec); + /* For now, just ignore the packet. We may want to send a reply + back eventually */ + return; + } + + if ((rx_message.version != PROTO_VERSION_NUMBER) || + (rx_message.pkt_type != PKT_TYPE_CMD_REQUEST) || + (rx_message.res1 != 0) || + (rx_message.res2 != 0)) { + + /* We don't know how to process anything like this */ + CLG_LogCommandAccess(remote_ip, CLG_CMD_BAD_PKT, cooked_now.tv_sec); + + return; + } + + rx_command = ntohs(rx_message.command); + + /* OK, we have a valid message. Now dispatch on message type and process it. */ + + /* Do authentication stuff and command tokens here. Well-behaved + clients will set their utokens to 0 to save us wasting our time + if the packet is unauthenticatable. */ + if (rx_message.utoken != 0) { + md5_ok = check_rx_packet_auth(&rx_message); + } else { + md5_ok = 0; + } + + /* All this malarky is to protect the system against various forms + of attack. + + Simple packet forgeries are blocked by requiring the packet to + authenticate properly with MD5. (The assumption is that the + command key is in a read-only keys file read by the daemon, and + is known only to administrators.) + + Replay attacks are prevented by 2 fields in the packet. The + 'token' field is where the client plays back to us a token that + he was issued in an earlier reply. Each time we reply to a + suitable packet, we issue a new token. The 'utoken' field is set + to a new (hopefully increasing) value each time the daemon is + run. This prevents packets from a previous incarnation being + played back at us when the same point in the 'token' sequence + comes up. (The token mechanism also prevents a non-idempotent + command from being executed twice from the same client, if the + client fails to receive our reply the first time and tries a + resend.) + + The problem is how a client should get its first token. Our + token handling only remembers a finite number of issued tokens + (actually 32) - if a client replies with a (legitimate) token + older than that, it will be treated as though a duplicate token + has been supplied. If a simple token-request protocol were used, + the whole thing would be vulnerable to a denial of service + attack, where an attacker just replays valid token-request + packets at us, causing us to keep issuing new tokens, + invalidating all the ones we have given out to true clients + already. + + To protect against this, the token-request (REQ_LOGON) packet + includes a timestamp field. To issue a token, we require that + this field is different from any we have processed before. To + bound our storage, we require that the timestamp is within a + certain period of our current time. For clients running on the + same host this will be easily satisfied. + + */ + + utoken_ok = (ntohl(rx_message.utoken) == utoken); + + /* Avoid binning a valid user's token if we merely get a forged + packet */ + rx_message_token = ntohl(rx_message.token); + rx_message_seq = ntohl(rx_message.sequence); + rx_attempt = ntohs(rx_message.attempt); + + if (md5_ok && utoken_ok) { + token_ok = check_token(rx_message_token); + } else { + token_ok = 0; + } + + if (md5_ok && utoken_ok && !token_ok) { + /* This might be a resent message, due to the client not getting + our reply to the first attempt. See if we can find the message. */ + prev_tx_message = lookup_reply(rx_message_token, rx_message_seq, rx_attempt); + if (prev_tx_message) { + /* Just send this message again */ + tx_message_length = PKL_ReplyLength(prev_tx_message); + status = sendto(sock_fd, (void *) prev_tx_message, tx_message_length, 0, + (struct sockaddr *) &where_from, sizeof(where_from)); + if (status < 0) { + LOG(LOGS_WARN, LOGF_CmdMon, "Could not send response to %s:%hu", UTI_IPToDottedQuad(remote_ip), remote_port); + } + return; + } + /* Otherwise, just fall through into normal processing */ + + } + + if (md5_ok && utoken_ok && token_ok) { + /* See whether we can discard the previous reply from storage */ + token_acknowledged(rx_message_token, &now); + } + + valid_ts = 0; + + if (md5_ok && ((utoken_ok && token_ok) || + ((ntohl(rx_message.utoken) == SPECIAL_UTOKEN) && + (rx_command == REQ_LOGON) && + (valid_ts = ts_is_unique_and_not_stale(&rx_message.data.logon.ts, &now))))) { + issue_token = 1; + } else { + issue_token = 0; + } + + authenticated = md5_ok & utoken_ok & token_ok; + + if (authenticated) { + CLG_LogCommandAccess(remote_ip, CLG_CMD_AUTH, cooked_now.tv_sec); + } else { + CLG_LogCommandAccess(remote_ip, CLG_CMD_NORMAL, cooked_now.tv_sec); + } + + if (issue_token) { + /* Only command clients where the user has apparently 'logged on' + get a token to allow them to emit an authenticated command next + time */ + tx_message_token = get_token(); + } else { + tx_message_token = 0xffffffffUL; + } + + tx_message.token = htonl(tx_message_token); + + + /* If command type is invalid, send back reply */ + if (rx_command >= N_REQUEST_TYPES) { + tx_message.status = htons(STT_INVALID); + tx_message.reply = htons(RPY_NULL); + } else { + int allowed = 0; + + /* Check level of authority required to issue the command */ + switch(permissions[rx_command]) { + case PERMIT_AUTH: + if (authenticated) { + allowed = 1; + } else { + allowed = 0; + } + break; + case PERMIT_LOCAL: + if (authenticated || localhost) { + allowed = 1; + } else { + allowed = 0; + } + break; + case PERMIT_OPEN: + allowed = 1; + break; + default: + CROAK("Impossible"); + } + + if (allowed) { + switch(rx_command) { + case REQ_NULL: + handle_null(&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_DUMP: + SRC_DumpSources(); + tx_message.status = htons(STT_SUCCESS); + 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_MAXUPDATESKEW: + handle_modify_maxupdateskew(&rx_message, &tx_message); + break; + + case REQ_LOGON: + /* If the log-on fails, record the reason why */ + if (!issue_token) { + LOG(LOGS_WARN, LOGF_CmdMon, + "Bad command logon from %s port %d (md5_ok=%d valid_ts=%d)\n", + UTI_IPToDottedQuad(remote_ip), + remote_port, + md5_ok, valid_ts); + } + + if (issue_token == 1) { + tx_message.status = htons(STT_SUCCESS); + } else if (!md5_ok) { + tx_message.status = htons(STT_UNAUTH); + } else if (!valid_ts) { + tx_message.status = htons(STT_INVALIDTS); + } else { + tx_message.status = htons(STT_FAILED); + } + + break; + + case REQ_SETTIME: + handle_settime(&rx_message, &tx_message); + break; + + case REQ_LOCAL: + 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_allow(&rx_message, &tx_message); + break; + + case REQ_ALLOWALL: + handle_allowall(&rx_message, &tx_message); + break; + + case REQ_DENY: + handle_deny(&rx_message, &tx_message); + break; + + case REQ_DENYALL: + handle_denyall(&rx_message, &tx_message); + break; + + case REQ_CMDALLOW: + handle_cmdallow(&rx_message, &tx_message); + break; + + case REQ_CMDALLOWALL: + handle_cmdallowall(&rx_message, &tx_message); + break; + + case REQ_CMDDENY: + handle_cmddeny(&rx_message, &tx_message); + break; + + case REQ_CMDDENYALL: + handle_cmddenyall(&rx_message, &tx_message); + break; + + case REQ_ACCHECK: + handle_accheck(&rx_message, &tx_message); + break; + + case REQ_CMDACCHECK: + handle_cmdaccheck(&rx_message, &tx_message); + break; + + case REQ_ADD_SERVER: + handle_add_server(&rx_message, &tx_message); + break; + + case REQ_ADD_PEER: + handle_add_peer(&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_DOFFSET: + handle_doffset(&rx_message, &tx_message); + break; + + case REQ_TRACKING: + handle_tracking(&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_SUBNETS_ACCESSED: + handle_subnets_accessed(&rx_message, &tx_message); + break; + + case REQ_CLIENT_ACCESSES: + handle_client_accesses(&rx_message, &tx_message); + break; + + case REQ_CLIENT_ACCESSES_BY_INDEX: + 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; + + default: + /* Ignore message */ + break; + } + } else { + tx_message.status = htons(STT_UNAUTH); + } + } + + if (md5_ok) { + generate_tx_packet_auth(&tx_message); + } + + if (token_ok) { + save_reply(&tx_message, + rx_message_token, + tx_message_token, + rx_message_seq, + rx_attempt, + &now); + } + + /* Transmit the response */ + { + /* Include a simple way to lose one message in three to test resend */ + + static int do_it=1; + + if (do_it) { + transmit_reply(&tx_message, &where_from); + } + +#if 0 + do_it = ((do_it + 1) % 3); +#endif + } + + return; + +} + +/* ================================================== */ + +int +CAM_AddAccessRestriction(unsigned long 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(unsigned long ip_addr) +{ + return ADF_IsAllowed(access_auth_table, ip_addr); +} + + +/* ================================================== */ +/* ================================================== */ diff --git a/cmdmon.h b/cmdmon.h new file mode 100644 index 0000000..7dcd539 --- /dev/null +++ b/cmdmon.h @@ -0,0 +1,41 @@ +/* + $Header: /cvs/src/chrony/cmdmon.h,v 1.8 2002/02/28 23:27:09 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for the control and monitoring module in the software + */ + +#ifndef GOT_CMDMON_H +#define GOT_CMDMON_H + +extern void CAM_Initialise(void); + +extern void CAM_Finalise(void); + +extern int CAM_AddAccessRestriction(unsigned long ip_addr, int subnet_bits, int allow, int all); +extern int CAM_CheckAccessRestriction(unsigned long ip_addr); + +#endif /* GOT_CMDMON_H */ diff --git a/cmdparse.c b/cmdparse.c new file mode 100644 index 0000000..3353ac1 --- /dev/null +++ b/cmdparse.c @@ -0,0 +1,163 @@ +/* + $Header: /cvs/src/chrony/cmdparse.c,v 1.11 2003/01/20 22:52:07 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Module for parsing various forms of directive and command lines that + are common to the configuration file and to the command client. + + */ + +#include "sysincl.h" + +#include "cmdparse.h" +#include "nameserv.h" + +#define MAXLEN 2047 +#define SMAXLEN "2047" + +/* ================================================== */ + +CPS_Status +CPS_ParseNTPSourceAdd(const char *line, CPS_NTP_Source *src) +{ + int ok, n, done; + char cmd[MAXLEN+1], hostname[MAXLEN+1]; + CPS_Status result; + + src->port = 123; + src->params.minpoll = 6; + src->params.maxpoll = 10; + src->params.presend_minpoll = 0; + src->params.authkey = INACTIVE_AUTHKEY; + src->params.max_delay = 16.0; + src->params.max_delay_ratio = 16384.0; + src->params.online = 1; + src->params.auto_offline = 0; + + result = CPS_Success; + + ok = 0; + if (sscanf(line, "%" SMAXLEN "s%n", hostname, &n) == 1) { + src->ip_addr = DNS_Name2IPAddress(hostname); + if (src->ip_addr != DNS_Failed_Address) { + ok = 1; + } + } + + if (!ok) { + result = CPS_BadHost; + } else { + + line += n; + + /* Parse subfields */ + ok = 1; + done = 0; + do { + if (sscanf(line, "%" SMAXLEN "s%n", cmd, &n) == 1) { + + line += n; + + if (!strncasecmp(cmd, "port", 4)) { + if (sscanf(line, "%hu%n", &src->port, &n) != 1) { + result = CPS_BadPort; + ok = 0; + done = 1; + } else { + line += n; + } + } else if (!strncasecmp(cmd, "minpoll", 7)) { + if (sscanf(line, "%d%n", &src->params.minpoll, &n) != 1) { + result = CPS_BadMinpoll; + ok = 0; + done = 1; + } else { + line += n; + } + } else if (!strncasecmp(cmd, "maxpoll", 7)) { + if (sscanf(line, "%d%n", &src->params.maxpoll, &n) != 1) { + result = CPS_BadMaxpoll; + ok = 0; + done = 1; + } else { + line += n; + } + } else if (!strncasecmp(cmd, "presend", 7)) { + if (sscanf(line, "%d%n", &src->params.presend_minpoll, &n) != 1) { + result = CPS_BadPresend; + ok = 0; + done = 1; + } else { + line += n; + } + /* This MUST come before the following one ! */ + } else if (!strncasecmp(cmd, "maxdelayratio", 13)) { + if (sscanf(line, "%lf%n", &src->params.max_delay_ratio, &n) != 1) { + result = CPS_BadMaxdelayratio; + ok = 0; + done = 1; + } else { + line += n; + } + } else if (!strncasecmp(cmd, "maxdelay", 8)) { + if (sscanf(line, "%lf%n", &src->params.max_delay, &n) != 1) { + result = CPS_BadMaxdelay; + ok = 0; + done = 1; + } else { + line += n; + } + } else if (!strncasecmp(cmd, "key", 3)) { + if (sscanf(line, "%lu%n", &src->params.authkey, &n) != 1) { + result = CPS_BadKey; + ok = 0; + done = 1; + } else { + line += n; + } + } else if (!strncasecmp(cmd, "offline", 7)) { + src->params.online = 0; + + } else if (!strncasecmp(cmd, "auto_offline", 12)) { + src->params.auto_offline = 1; + + } else { + result = CPS_BadOption; + ok = 0; + done = 1; + } + } else { + done = 1; + } + } while (!done); + } + + return result; + +} + +/* ================================================== */ + diff --git a/cmdparse.h b/cmdparse.h new file mode 100644 index 0000000..38cc74c --- /dev/null +++ b/cmdparse.h @@ -0,0 +1,60 @@ +/* + $Header: /cvs/src/chrony/cmdparse.h,v 1.7 2002/02/28 23:27:09 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for the command parser + */ + +#ifndef GOT_CMDPARSE_H +#define GOT_CMDPARSE_H + +#include "srcparams.h" + +typedef enum { + CPS_Success, + CPS_BadOption, + CPS_BadHost, + CPS_BadPort, + CPS_BadMinpoll, + CPS_BadMaxpoll, + CPS_BadPresend, + CPS_BadMaxdelayratio, + CPS_BadMaxdelay, + CPS_BadKey +} CPS_Status; + +typedef struct { + unsigned long ip_addr; + unsigned short port; + SourceParameters params; +} CPS_NTP_Source; + +/* Parse a command to add an NTP server or peer */ +extern CPS_Status CPS_ParseNTPSourceAdd(const char *line, CPS_NTP_Source *src); + + + +#endif /* GOT_CMDPARSE_H */ diff --git a/conf.c b/conf.c new file mode 100644 index 0000000..48f9436 --- /dev/null +++ b/conf.c @@ -0,0 +1,1235 @@ +/* + $Header: /cvs/src/chrony/conf.c,v 1.42 2003/03/27 23:45:47 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Module that reads and processes the configuration file. + +1999-12-19 Kalle Olavi Niemitalo + + * conf.c: Added a new configuration setting "acquisitionport" and + a function CNF_GetAcquisitionPort to read its value. + (acquisition_port): New variable. + (parse_port): Delegate most work to new function parse_some_port. + (parse_acquisitionport): New function. + (commands): Added "acquisitionport". + (CNF_GetAcquisitionPort): New function. + + */ + +#include "sysincl.h" + +#include "conf.h" +#include "ntp_sources.h" +#include "ntp_core.h" +#include "cmdmon.h" +#include "srcparams.h" +#include "logging.h" +#include "nameserv.h" +#include "memory.h" +#include "acquire.h" +#include "cmdparse.h" +#include "broadcast.h" + +/* ================================================== */ + +#define DEFAULT_CONF_FILE "/etc/chrony.conf" + +/* ================================================== */ +/* Forward prototypes */ + +static void parse_commandkey(const char *); +static void parse_driftfile(const char *); +static void parse_dumpdir(const char *); +static void parse_dumponexit(const char *); +static void parse_keyfile(const char *); +static void parse_rtcfile(const char *); +static void parse_log(const char *); +static void parse_logdir(const char *); +static void parse_maxupdateskew(const char *); +static void parse_peer(const char *); +static void parse_acquisitionport(const char *); +static void parse_port(const char *); +static void parse_server(const char *); +static void parse_local(const char *); +static void parse_manual(const char *); +static void parse_initstepslew(const char *); +static void parse_allow(const char *); +static void parse_deny(const char *); +static void parse_cmdallow(const char *); +static void parse_cmddeny(const char *); +static void parse_cmdport(const char *); +static void parse_rtconutc(const char *); +static void parse_noclientlog(const char *); +static void parse_logchange(const char *); +static void parse_mailonchange(const char *); +static void parse_bindaddress(const char *); +static void parse_bindcmdaddress(const char *); +static void parse_rtcdevice(const char *); +static void parse_pidfile(const char *); +static void parse_broadcast(const char *); +static void parse_linux_hz(const char *); +static void parse_linux_freq_scale(const char *); + +/* ================================================== */ +/* Configuration variables */ + +static char *rtc_device = "/dev/rtc"; +static int acquisition_port = 0; /* 0 means let kernel choose port */ +static int ntp_port = 123; +static char *keys_file = NULL; +static char *drift_file = NULL; +static char *rtc_file = NULL; +static unsigned long command_key_id; +static double max_update_skew = DBL_MAX; + +static int cmd_port = -1; + +static int do_log_measurements = 0; +static int do_log_statistics = 0; +static int do_log_tracking = 0; +static int do_log_rtc = 0; +static int do_dump_on_exit = 0; +static char *logdir = "."; +static char *dumpdir = "."; + +static int enable_local=0; +#define DEFAULT_LOCAL_STRATUM 8 +static int local_stratum; + +static int do_init_stepslew = 0; +static int n_init_srcs; + +/* Threshold (in seconds) - if absolute value of initial error is less + than this, slew instead of stepping */ +static int init_slew_threshold = -1; +#define MAX_INIT_SRCS 8 +static unsigned long init_srcs_ip[MAX_INIT_SRCS]; + +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; + +/* Flag set if we should log to syslog when a time adjustment + exceeding the threshold is initiated */ +static int do_log_change = 0; +static double log_change_threshold = 0.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; + +/* IP address (host order) for binding the NTP socket to. 0 means INADDR_ANY + will be used */ +static unsigned long bind_address = 0UL; + +/* IP address (host order) for binding the command socket to. 0 means + use the value of bind_address */ +static unsigned long bind_cmd_address = 0UL; + +/* Filename to use for storing pid of running chronyd, to prevent multiple + * chronyds being started. */ +static char *pidfile = "/var/run/chronyd.pid"; + +/* Boolean for whether the Linux HZ value has been overridden, and the + * new value. */ +static int set_linux_hz = 0; +static int linux_hz; + +/* Boolean for whether the Linux frequency scaling value (i.e. the one that's + * approx (1<= MAX_INIT_SRCS) { + return; + } + + } else { + /* If we get invalid trailing syntax, forget it ... */ + return; + } + + p += n; + } + +} + +/* ================================================== */ + +static void +parse_manual(const char *line) +{ + enable_manual = 1; +} + +/* ================================================== */ + +static void +parse_rtconutc(const char *line) +{ + rtc_on_utc = 1; +} + +/* ================================================== */ + +static void +parse_noclientlog(const char *line) +{ + no_client_log = 1; +} + +/* ================================================== */ + +static void +parse_logchange(const char *line) +{ + if (sscanf(line, "%lf", &log_change_threshold) == 1) { + do_log_change = 1; + } else { + do_log_change = 0; + LOG(LOGS_WARN, LOGF_Configure, + "Could not read threshold for logging clock changes at line %d\n", + line_number); + } +} + + +/* ================================================== */ + +#define BUFLEN 127 +#define SBUFLEN "127" + +static void +parse_mailonchange(const char *line) +{ + char buffer[BUFLEN+1]; + if (sscanf(line, "%" SBUFLEN "s%lf", buffer, &mail_change_threshold) == 2) { + mail_user_on_change = MallocArray(char, strlen(buffer)+1); + strcpy(mail_user_on_change, buffer); + } else { + mail_user_on_change = NULL; + LOG(LOGS_WARN, LOGF_Configure, + "Could not read user or threshold for clock change mail notify at line %d\n", + line_number); + } +} + +/* ================================================== */ + +static void +parse_allow_deny(const char *line, AllowDeny *list, int allow) +{ + const char *p; + unsigned long a, b, c, d, n; + int all = 0; + AllowDeny *new_node = NULL; + unsigned long ip_addr; + + p = line; + + while (*p && isspace((unsigned char)*p)) p++; + + if (!strncmp(p, "all", 3)) { + all = 1; + p += 3; + } + + while (*p && isspace((unsigned char)*p)) p++; + if (!*p) { + /* Empty line applies to all addresses */ + new_node = MallocNew(AllowDeny); + new_node->allow = allow; + new_node->all = all; + new_node->ip = 0UL; + new_node->subnet_bits = 0; + } else { + char *slashpos; + slashpos = strchr(p, '/'); + if (slashpos) *slashpos = 0; + + n = sscanf(p, "%lu.%lu.%lu.%lu", &a, &b, &c, &d); + + if (n >= 1) { + new_node = MallocNew(AllowDeny); + new_node->allow = allow; + new_node->all = all; + + a &= 0xff; + b &= 0xff; + c &= 0xff; + d &= 0xff; + + switch (n) { + case 1: + new_node->ip = (a<<24); + new_node->subnet_bits = 8; + break; + case 2: + new_node->ip = (a<<24) | (b<<16); + new_node->subnet_bits = 16; + break; + case 3: + new_node->ip = (a<<24) | (b<<16) | (c<<8); + new_node->subnet_bits = 24; + break; + case 4: + new_node->ip = (a<<24) | (b<<16) | (c<<8) | d; + new_node->subnet_bits = 32; + break; + default: + assert(0); + + } + + if (slashpos) { + int specified_subnet_bits, n; + n = sscanf(slashpos+1, "%d", &specified_subnet_bits); + if (n == 1) { + new_node->subnet_bits = specified_subnet_bits; + } else { + LOG(LOGS_WARN, LOGF_Configure, "Could not read subnet size at line %d", line_number); + } + } + + } else { + ip_addr = DNS_Name2IPAddress(p); + if (ip_addr != DNS_Failed_Address) { + new_node = MallocNew(AllowDeny); + new_node->allow = allow; + new_node->all = all; + new_node->ip = ip_addr; + new_node->subnet_bits = 32; + } else { + LOG(LOGS_WARN, LOGF_Configure, "Could not read address at line %d", line_number); + } + } + } + + if (new_node) { + new_node->prev = list->prev; + new_node->next = list; + list->prev->next = new_node; + list->prev = new_node; + } + +} + + +/* ================================================== */ + +static void +parse_allow(const char *line) +{ + parse_allow_deny(line, &ntp_auth_list, 1); +} + + +/* ================================================== */ + +static void +parse_deny(const char *line) +{ + parse_allow_deny(line, &ntp_auth_list, 0); +} + +/* ================================================== */ + +static void +parse_cmdallow(const char *line) +{ + parse_allow_deny(line, &cmd_auth_list, 1); +} + + +/* ================================================== */ + +static void +parse_cmddeny(const char *line) +{ + parse_allow_deny(line, &cmd_auth_list, 0); +} + +/* ================================================== */ + +static unsigned long +parse_an_address(const char *line, const char *errmsg) +{ + unsigned long a, b, c, d; + int n; + n = sscanf(line, "%lu.%lu.%lu.%lu", &a, &b, &c, &d); + if (n == 4) { + return (((a&0xff)<<24) | ((b&0xff)<<16) | + ((c&0xff)<<8) | (d&0xff)); + } else { + LOG(LOGS_WARN, LOGF_Configure, errmsg, line_number); + return 0UL; + } +} + +/* ================================================== */ + +static void +parse_bindaddress(const char *line) +{ + bind_address = parse_an_address(line, "Could not read bind address at line %d\n"); +} + +/* ================================================== */ + +static void +parse_bindcmdaddress(const char *line) +{ + bind_cmd_address = parse_an_address(line, "Could not read bind command address at line %d\n"); +} + +/* ================================================== */ + +static void +parse_pidfile(const char *line) +{ + pidfile = MallocArray(char, 1 + strlen(line)); + sscanf(line, "%s", pidfile); + strip_trailing_spaces(pidfile); +} + +/* ================================================== */ + +typedef struct { + /* Both in host (not necessarily network) order */ + unsigned long addr; + unsigned short port; + int interval; +} NTP_Broadcast_Destination; + +static NTP_Broadcast_Destination *broadcasts = NULL; +static int max_broadcasts = 0; +static int n_broadcasts = 0; + +/* ================================================== */ + +static void +parse_broadcast(const char *line) +{ + /* Syntax : broadcast [] */ + int port; + unsigned int a, b, c, d; + int n; + int interval; + unsigned long addr; + + n = sscanf(line, "%d %u.%u.%u.%u %d", &interval, &a, &b, &c, &d, &port); + if (n < 5) { + LOG(LOGS_WARN, LOGF_Configure, "Could not parse broadcast directive at line %d", line_number); + return; + } else if (n == 5) { + /* default port */ + port = 123; + } else if (n > 6) { + LOG(LOGS_WARN, LOGF_Configure, "Too many fields in broadcast directive at line %d", line_number); + } + + addr = ((unsigned long) a << 24) | + ((unsigned long) b << 16) | + ((unsigned long) c << 8) | + ((unsigned long) d ); + + if (max_broadcasts == n_broadcasts) { + /* Expand array */ + max_broadcasts += 8; + if (broadcasts) { + broadcasts = ReallocArray(NTP_Broadcast_Destination, max_broadcasts, broadcasts); + } else { + broadcasts = MallocArray(NTP_Broadcast_Destination, max_broadcasts); + } + } + + broadcasts[n_broadcasts].addr = addr; + broadcasts[n_broadcasts].port = port; + broadcasts[n_broadcasts].interval = interval; + ++n_broadcasts; +} + +/* ================================================== */ + +static void +parse_linux_hz(const char *line) +{ + if (1 == sscanf(line, "%d", &linux_hz)) { + set_linux_hz = 1; + } else { + LOG(LOGS_WARN, LOGF_Configure, "Could not parse linux_hz directive at line %d", line_number); + } +} + +/* ================================================== */ + +static void +parse_linux_freq_scale(const char *line) +{ + if (1 == sscanf(line, "%lf", &linux_freq_scale)) { + set_linux_freq_scale = 1; + } else { + LOG(LOGS_WARN, LOGF_Configure, "Could not parse linux_freq_scale directive at line %d", line_number); + } +} + +/* ================================================== */ + +void +CNF_ProcessInitStepSlew(void (*after_hook)(void *), void *anything) +{ + if (do_init_stepslew) { + ACQ_StartAcquisition(n_init_srcs, init_srcs_ip, init_slew_threshold, after_hook, anything); + } else { + (after_hook)(anything); + } +} + +/* ================================================== */ + +void +CNF_AddSources(void) { + NTP_Remote_Address server; + int i; + + for (i=0; inext) { + status = NCR_AddAccessRestriction(node->ip, node->subnet_bits, node->allow, node->all); + if (!status) { + LOG(LOGS_WARN, LOGF_Configure, "Bad subnet for %08lx", node->ip); + } + } + + for (node = cmd_auth_list.next; node != &cmd_auth_list; node = node->next) { + status = CAM_AddAccessRestriction(node->ip, node->subnet_bits, node->allow, node->all); + if (!status) { + LOG(LOGS_WARN, LOGF_Configure, "Bad subnet for %08lx", node->ip); + } + } + + return; +} + +/* ================================================== */ + +int +CNF_GetNoClientLog(void) +{ + return no_client_log; +} + +/* ================================================== */ + +void +CNF_GetBindAddress(unsigned long *addr) +{ + *addr = bind_address; +} + +/* ================================================== */ + +void +CNF_GetBindCommandAddress(unsigned long *addr) +{ + *addr = bind_cmd_address ? bind_cmd_address : bind_address; +} + +/* ================================================== */ + +char * +CNF_GetPidFile(void) +{ + return pidfile; +} + +/* ================================================== */ + +void +CNF_GetLinuxHz(int *set, int *hz) +{ + *set = set_linux_hz; + *hz = linux_hz; +} + +/* ================================================== */ + +void +CNF_GetLinuxFreqScale(int *set, double *freq_scale) +{ + *set = set_linux_freq_scale; + *freq_scale = linux_freq_scale ; +} + diff --git a/conf.h b/conf.h new file mode 100644 index 0000000..1dfe0d0 --- /dev/null +++ b/conf.h @@ -0,0 +1,74 @@ +/* + $Header: /cvs/src/chrony/conf.h,v 1.24 2003/03/27 23:45:47 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for configuration module + */ + +#ifndef GOT_CONF_H +#define GOT_CONF_H + +extern char *CNF_GetRtcDevice(void); + +extern void CNF_ReadFile(const char *filename); + +extern void CNF_AddSources(void); +extern void CNF_AddBroadcasts(void); + +extern void CNF_ProcessInitStepSlew(void (*after_hook)(void *), void *anything); + +extern unsigned short CNF_GetAcquisitionPort(void); +extern unsigned short CNF_GetNTPPort(void); +extern char *CNF_GetDriftFile(void); +extern char *CNF_GetLogDir(void); +extern char *CNF_GetDumpDir(void); +extern int CNF_GetLogMeasurements(void); +extern int CNF_GetLogStatistics(void); +extern int CNF_GetLogTracking(void); +extern int CNF_GetLogRtc(void); +extern char *CNF_GetKeysFile(void); +extern char *CNF_GetRtcFile(void); +extern unsigned long CNF_GetCommandKey(void); +extern int CNF_GetDumpOnExit(void); +extern int CNF_GetManualEnabled(void); +extern int CNF_GetCommandPort(void); +extern int CNF_GetRTCOnUTC(void); +extern void CNF_GetLogChange(int *enabled, double *threshold); +extern void CNF_GetMailOnChange(int *enabled, double *threshold, char **user); +extern int CNF_GetNoClientLog(void); +extern void CNF_GetBindAddress(unsigned long *addr); +extern void CNF_GetBindCommandAddress(unsigned long *addr); +extern char *CNF_GetPidFile(void); +extern void CNF_GetLinuxHz(int *set, int *hz); +extern void CNF_GetLinuxFreqScale(int *set, double *freq_scale); + +/* Value returned in ppm, as read from file */ +extern double CNF_GetMaxUpdateSkew(void); +extern int CNF_AllowLocalReference(int *stratum); + +extern void CNF_SetupAccessRestrictions(void); + +#endif /* GOT_CONF_H */ diff --git a/configure b/configure new file mode 100755 index 0000000..5672f9a --- /dev/null +++ b/configure @@ -0,0 +1,313 @@ +#!/bin/sh +# +# $Header: /cvs/src/chrony/configure,v 1.28 2003/07/01 20:53:00 richard Exp $ +# +# ======================================================================= +# +# chronyd/chronyc - Programs for keeping computer clocks accurate. +# +# #COPYRIGHT# +# +# ======================================================================= + +# This configure script determines the operating system type and version + +if [ "x${CC}" = "x" ]; then + MYCC="gcc" +else + MYCC="${CC}" +fi + +if [ "x${CFLAGS}" = "x" ]; then + MYCFLAGS="-O2 -g" +else + MYCFLAGS="${CFLAGS}" +fi + +# ====================================================================== +# FUNCTIONS + +#{{{ test_for_sqrt +test_for_sqrt () { +# 0 : doesn't need -lm +# 1 : needs -lm +# 2 : doesn't even link with -lm + + cat >docheck.c < +int main(int argc, char **argv) { + return (int) sqrt((double)argc); +} +EOF + + ${MYCC} ${MYCFLAGS} -c -o docheck.o docheck.c >/dev/null 2>&1 + if [ $? -eq 0 ] + then + ${MYCC} ${MYCFLAGS} -o docheck docheck.o >/dev/null 2>&1 + if [ $? -eq 0 ] + then + result=0 + else + ${MYCC} ${MYCFLAGS} -o docheck docheck.o -lm >/dev/null 2>&1 + if [ $? -eq 0 ] + then + result=1 + else + result=2 + fi + fi + else + result=2 + fi + + rm -f docheck.c docheck.o docheck + echo $result +} +#}}} +#{{{ test_for_stdint_h +test_for_stdint_h () { + cat >docheck.c < +int main(int argc, char **argv) { + return 0; +} +EOF + + ${MYCC} ${MYCFLAGS} -c -o docheck.o docheck.c >/dev/null 2>&1 + if [ $? -eq 0 ] + then + result=0 + else + result=1 + fi + + rm -f docheck.c docheck.o + echo $result +} +#}}} +#{{{ test_for_inttypes_h +test_for_inttypes_h () { + cat >docheck.c < +int main(int argc, char **argv) { + return 0; +} +EOF + + ${MYCC} ${MYCFLAGS} -c -o docheck.o docheck.c >/dev/null 2>&1 + if [ $? -eq 0 ] + then + result=0 + else + result=1 + fi + + rm -f docheck.c docheck.o + echo $result +} +#}}} + +# ====================================================================== + + + +OPERATINGSYSTEM=`uname -s` +VERSION=`uname -r` +MACHINE=`uname -m` + +SYSTEM=${OPERATINGSYSTEM}-${MACHINE} + +EXTRA_LIBS="" +EXTRA_CLI_LIBS="" +EXTRA_OBJECTS="" +EXTRA_DEFS="" +INSTALL_PREFIX=/usr/local +SYSDEFS="" + +# Support for readline (on by default) +feat_readline=1 +readline_lib="" +readline_inc="" +ncurses_lib="" + +SETINFODIR="" +SETMANDIR="" + +for option +do + case "$option" in + --prefix=* | --install_prefix=* ) + INSTALL_PREFIX=`echo $option | sed -e 's/[^=]*=//;'` + ;; + --trace ) + EXTRA_DEFS="-DTRACEON" + ;; + --disable-readline ) + feat_readline=0 + ;; + --with-readline-library=* ) + readline_lib=-L`echo $option | sed -e 's/^.*=//;'` + ;; + --with-readline-includes=* ) + readline_inc=-I`echo $option | sed -e 's/^.*=//;'` + ;; + --with-ncurses-library=* ) + ncurses_lib=-L`echo $option | sed -e 's/^.*=//;'` + ;; + --infodir=* ) + SETINFODIR=`echo $option | sed -e 's/^.*=//;'` + ;; + --mandir=* ) + SETMANDIR=`echo $option | sed -e 's/^.*=//;'` + ;; + * ) + echo "Unrecognized option : " $option + esac +done + +case $SYSTEM in + SunOS-sun4* ) + case $VERSION in + 4.* ) + EXTRA_OBJECTS="sys_sunos.o strerror.o" + EXTRA_LIBS="-lkvm" + SYSDEFS="-DSUNOS" + echo "Configuring for SunOS (" $SYSTEM "version" $VERSION ")" + ;; + 5.* ) + EXTRA_OBJECTS="sys_solaris.o" + EXTRA_LIBS="-lsocket -lnsl -lkvm -lelf" + EXTRA_CLI_LIBS="-lsocket -lnsl" + SYSDEFS="-DSOLARIS" + echo "Configuring for Solaris (" $SYSTEM "SunOS version" $VERSION ")" + if [ $VERSION = "5.3" ]; then + SYSDEFS="$SYSDEFS -DHAS_NO_BZERO" + echo "Using memset() instead of bzero()" + fi + ;; + esac + ;; + Linux* ) + EXTRA_OBJECTS="sys_linux.o wrap_adjtimex.o rtc_linux.o" + SYSDEFS="-DLINUX" + echo "Configuring for " $SYSTEM + if [ -r /usr/include/linux/spinlock.h ]; then + SYSDEFS="$SYSDEFS -DHAS_SPINLOCK_H" + echo "The system has , using that" + else + echo "The system does not have , using private definition for spinlock_t" + fi + if [ "${MACHINE}" = "alpha" ]; then + echo "Enabling -mieee" + # FIXME: Should really test for GCC + SYSDEFS="$SYSDEFS -mieee -DALPHA" + fi + ;; + + BSD/386-i[3456]86 ) + # Antti Jrvinen reported that this system can + # be supported with the SunOS 4.x driver files. + EXTRA_OBJECTS="sys_sunos.o strerror.o" + EXTRA_LIBS="-lkvm" + SYSDEFS="-DSUNOS" + echo "Configuring for BSD/386 (using SunOS driver)" + ;; + NetBSD-* ) + EXTRA_OBJECTS="sys_netbsd.o" + EXTRA_LIBS="-lkvm" + SYSDEFS="" + echo "Configuring for $SYSTEM" + ;; + SunOS-i86pc* ) + # Doug Woodward reported that this configuration + # works for Solaris 2.8 / SunOS 5.8 on x86 platforms + EXTRA_OBJECTS="sys_solaris.o" + EXTRA_LIBS="-lsocket -lnsl -lkvm -lelf" + EXTRA_CLI_LIBS="-lsocket -lnsl" + SYSDEFS="-DSOLARIS" + echo "Configuring for Solaris (" $SYSTEM "SunOS version" $VERSION ")" + ;; + CYGWIN32_NT-i[3456]86 ) + EXTRA_OBJECTS="sys_winnt.o" + EXTRA_LIBS="" + SYSDEFS="-DWINNT" + echo "Configuring for Windows NT (Cygwin32)" + ;; + * ) + echo "Sorry, I don't know how to build this software on your system." + exit 1 + ;; +esac + +printf "Checking if sqrt() needs -lm : " +case `test_for_sqrt` +in +0) + printf "No\n" + LIBS="" +;; +1) + printf "Yes\n" + LIBS="-lm" +;; +*) + printf "\nCan't compile/link a program which uses sqrt(), bailing out\n" + exit 1 +;; +esac + +printf "Checking for : " +if [ `test_for_stdint_h` -eq 0 ]; then + printf "Yes\n" + SYSDEFS="${SYSDEFS} -DHAS_STDINT_H" +else + printf "No\n" +fi + +printf "Checking for : " +if [ `test_for_inttypes_h` -eq 0 ]; then + printf "Yes\n" + SYSDEFS="${SYSDEFS} -DHAS_INTTYPES_H" +else + printf "No\n" +fi + +if [ "x${MYCC}" = "xgcc" ]; then + CCWARNFLAGS="-Wmissing-prototypes -Wall" +else + CCWARNFLAGS="" +fi + +if [ $feat_readline = "1" ]; then + READLINE_COMPILE="-DFEAT_READLINE=1 $readline_inc" + READLINE_LINK="$readline_lib $ncurses_lib -lreadline -lncurses" +else + READLINE_COMPILE="" + READLINE_LINK="" +fi + +MANDIR=${INSTALL_PREFIX}/man +INFODIR=${INSTALL_PREFIX}/info +if [ "x$SETINFODIR" != "x" ]; then + INFODIR=$SETINFODIR +fi +if [ "x$SETMANDIR" != "x" ]; then + MANDIR=$SETMANDIR +fi + +sed -e "s%@EXTRA_OBJECTS@%${EXTRA_OBJECTS}%;\ + s%@CC@%${MYCC}%;\ + s%@CFLAGS@%${MYCFLAGS}%;\ + s%@CCWARNFLAGS@%${CCWARNFLAGS}%;\ + s%@LIBS@%${LIBS}%;\ + s%@EXTRA_LIBS@%${EXTRA_LIBS}%;\ + s%@SYSDEFS@%${SYSDEFS}%;\ + s%@EXTRA_DEFS@%${EXTRA_DEFS}%;\ + s%@EXTRA_CLI_LIBS@%${EXTRA_CLI_LIBS}%;\ + s%@READLINE_COMPILE@%${READLINE_COMPILE}%;\ + s%@READLINE_LINK@%${READLINE_LINK}%;\ + s%@INSTALL_PREFIX@%${INSTALL_PREFIX}%;\ + s%@MANDIR@%${MANDIR}%;\ + s%@INFODIR@%${INFODIR}%;"\ + < Makefile.in > Makefile + diff --git a/contrib/DNSchrony/COPYING b/contrib/DNSchrony/COPYING new file mode 100644 index 0000000..916d1f0 --- /dev/null +++ b/contrib/DNSchrony/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 Library 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 + + Appendix: 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. + + + Copyright (C) 19yy + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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) 19yy 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. + + , 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 Library General +Public License instead of this License. diff --git a/contrib/DNSchrony/DNSchrony.pl b/contrib/DNSchrony/DNSchrony.pl new file mode 100755 index 0000000..4c1cb46 --- /dev/null +++ b/contrib/DNSchrony/DNSchrony.pl @@ -0,0 +1,583 @@ +#!/usr/bin/perl +# Copyright (C) Paul Elliott 2002 +my($copyrighttext) = <<'EOF'; +# Copyright (C) Paul Elliott 2002 +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# SEE COPYING FOR DETAILS +EOF + +#modules we use. + +use Socket; +use Getopt::Std; +use Net::DNS; +use Tie::Syslog; +use File::Temp qw/ :mktemp /; +use File::Copy; + +local($res) = new Net::DNS::Resolver; + +#dns lookup of IP address. +#returns ip or errorstring. +sub gethostaddr($) #get ip address from host +{ + my($host) = shift; + $query = $res->search($host); + if ($query) { + foreach $rr ($query->answer) { + next unless $rr->type eq "A"; + print $rr->address, "\n" if $pedebug; + return $rr->address; + } + } else { + print "query failed: ", $res->errorstring, "\n" if $pedebug; + return $res->errorstring; + } + +} + +#send messages to syslog + +sub Log($$) + { + if ($log) { + my($level) = shift; + my($mess) =shift; + + tie *MYLOG, 'Tie::Syslog',$level,$0,'pid','unix'; + print MYLOG $mess; + + untie *MYLOG; + } + } + +#send message to output or syslog +#and die. + +sub BadDie($) +{ + my($myerr) =$!; + my($mess)=shift; + + if($log){ + tie *MYLOG, 'Tie::Syslog','local0.err',$0,'pid','unix'; + print MYLOG $mess; + print MYLOG $myerr; + + untie *MYLOG; + + } else { + print "$mess\n$myerr\n"; + } + die $mess; +} + +sub isIpAddr($) #return true if looks like ip address +{ + my($ip) = shift; + return 1 if ( $ip =~ m/$ipOnlyPAT/ ); + return 0; +} +sub isHostname($) #return true if looks like ip address +{ + my($ip) = shift; + return 1 if ( $ip =~ m/$hostnameOnlyPAT/ ); + return 0; +} + +#send commands to chronyc by piping. +sub chronyc($) #send commands to chronyc +{ + my($command) = shift; + my($err) = "/var/tmp/chronyc.log"; + my($chronyP) = "/usr/local/bin/chronyc"; + open(CHRONY, "| $chronyP 1>$err 2>&1"); + + print CHRONY "$passwd$command\n"; + + close(CHRONY); + + Log('local0.info',"chronyc command issued=$command"); + #look at status lines till return bad. + open( IN, "<$err"); + my($status); + while () { + $status = $_; + + unless ( m/\A200 OK/ ) { + last; + } + + } + + $status ="" if ( $status =~ m/\A200 OK/ ); + close(IN); + unlink $err; + Log('local0.info',"chronyc results=$status"); + return $status; + +} + +#common patterns + +# an ip address patern +local($ipPAT) = qr/\d{1,3}(?:\.\d{1,3}){3}/; +# an hostname pattern +local($hostnamePAT) = qr/\w+(?:\.\w+)*/; +#line with hostname only +local($hostnameOnlyPAT) = qr/\A$hostnamePAT\Z/; +#line with ip address only +local($ipOnlyPAT) =qr/\A$ipPAT\Z/; + +#options hash +my(%opts); + + +getopts('nuadslPSC', \%opts); + +local($log) = ( $opts{'l'} ) ? 1 : 0; + +my($offline) = !( $opts{'n'} ) ; +my($offlineS) = ( $opts{'n'} ) ? " " : " offline" ; + +# paul elliotts secret debug var. no one will ever find out about it. +local($pedebug)=( ($ENV{"PAULELLIOTTDEBUG"}) or ($opts{P}) ); + +if ($opts{C}) { + + print $copyrighttext; + exit 0; +} + + +print <<"EOF" unless $opts{'S'}; +$0, Copyright (C) 2002 Paul Elliott +$0 comes with ABSOLUTELY NO WARRANTY; for details +invoke $0 -C. This is free software, and you are welcome +to redistribute it under certain conditions; invoke $0 -C +for details. +EOF + + + +local($passwd); + +# password to send to chronyc +my($pl) = $ENV{"CHRONYPASSWORD"}; + +#password comand to send to chronyc +if ( $pl ) { + $passwd = "password $pl\n"; +} else { + $passwd = ""; +} +print "passwd=$passwd\n" if ($pedebug); + +my(%host2ip); + +# hash of arrays. host2ip{$host}[0] is ip address for this host +# host2ip{$host}[1] is rest of paramenters for this host exc offline. + +#if debuging do chrony.conf in current directory. +my($listfile) =( ($pedebug) ? "./chrony.conf" : "/etc/chrony.conf") ; + +# This section reads in the old data about +# hostnames IP addresses and server parameters +# data is stored as it would be in chrony.conf +# file i.e.: +#># HOSTNAME +#>server IPADDR minpoll 5 maxpoll 10 maxdelay 0.4 offline +# +# the parameter offline is omitted if the -n switch is specified. +# first parameter is the filename of the file usually +# is /etc/DNSchrony.conf +# this is where we store the list of DNS hosts. +# hosts with static IP address shold be kept in chrony.conf + +# this is header that marks dnyamic host section +my($noedithead)=<<'EOF'; +## DNSchrony dynamic dns server section. DO NOT EDIT +## per entry FORMAT: +## |--------------------------------------------| +## |#HOSTNAME | +## |server IP-ADDRESS extra-params [ offline ] | +## |--------------------------------------------| +EOF +#patern that recognizes above. +my($noeditheadPAT) = +qr/\#\#\s+DNSchrony\s+dynamic\s+dns\s+server\s+section\.\s+DO\s+NOT\s+EDIT\s*/; + +#end of header marker. +my($noeditheadend)=<<'EOF'; +## END OF DNSchrony dynamic dns server section. +EOF + +#pattern that matches above. +my($noeditheadendPAT)= +qr/\#\#\s+END\s+OF\s+DNSchrony\s+dynamic\s+dns\s+server\s+section.\s*/; + +#array to hold non dns portion of chrony.conf +my(@chronyDconf); + + +my($ip); +my($rest); +my($host); + +# for each entry in the list of hosts.... +open(READIN, "<$listfile") or BadDie("Can not open $listfile"); + +# read till dynamic patern read save in @chronyDconf + +while ( ) { + + my($line) = $_; + + last if ( m/\A$noeditheadPAT\Z/ ); + + push(@chronyDconf,$line); + +} + +while ( ) { + + #end loop when end of header encountered + last if ( m/\A$noeditheadendPAT/ ); + + # parse the line giving ip address, extra pamamters, and host + #do host comment line first + ($host) = m{ + \A\#\s* + ($hostnamePAT) + \s*\z + }xio; + + #no match skip this line. + next unless ( $host ); + + # read next line + $_ = ; + + # parse out ip address extra parameters. + ($ip,$rest) = + m{ + \A + \s* + server #server comand + \s+ + ($ipPAT) #ip address + (?ixo: \s ) + \s* + ( + (?(?! + (?iox: offline )? #skip to offline # + \s* #or # + \Z + ).)* + ) + (?ixo: + \s* + (?ixo: offline )? #consume to # + \s* + \Z + ) + }xio ; + + #if failure again. + next unless ( $ip ); + + $rest =~ s/\s*\z//; #remove trail blanks + #from parameters + # store the data in the list + # key is host name value is + # array [0] is ip address + # [1] is other parameters + $host2ip{$host} = [$ip,$rest] ; + print "ip=$ip rest=$rest host=$host<\n" if $pedebug; + +} +#read trailing line into @chronyDconf +while ( ) { + + push(@chronyDconf,$_); + +} + +close(READIN) or BadDie("can not close $listfile"); + +#if the add command: +# command can be HOST=IPADDRESS OTHER_PARAMETERS +# means add the server trust the ip address geven with out a dns lookup +# good for when dns is down but we know the ip addres +# or +# HOST OTHER_PARAMETERS +#we lookup the ip address with dns. + +if ($opts{'a'}) { + my($param)= shift; + + + # parse the param is it hostname + if ( ($host,$ip) = $param =~ m/\A($hostnamePAT)=($ipPAT)\Z/ ) { + printf "ip=$ip host=$host\n" if ($pedebug); + } else { + + $host = $param; + + # get the ip address + $ip = gethostaddr($host); + + if ( ! isIpAddr($ip) or ! isHostname($host) ) { + print "query failed: ", $ip, "host=$host\n" if $pedebug; + exit 1; + } + } + printf "ip=$ip host=$host\n" if ($pedebug); + + # add the server using chronyc + my($status) = chronyc("add server $ip $rest"); + if ($status) { #chronyc error + print "chronyc failed, status=$status\n"; + exit 1; + } + + # get rest of arguements + $rest = join( ' ', @ARGV); + print "rest=$rest\n" if ($pedebug); + + #save node in hash + $host2ip{$host} = [$ip,$rest] ; + print "ip=$ip rest=$rest host=$host<\n" if $pedebug; + +} + +#delete command if arguement is ip address +#just delete it +#if a hostname look it up +#then delete it. + +if ($opts{'d'}) { + $host = shift; + + #get host name is it ap address + if ( isIpAddr($host) ) { # if ip address + my($hostIT); + my($found) =0; + foreach $hostIT (keys(%host2ip) ) { #search for match + if ( $host2ip{$hostIT}[0] eq $host) { + $found=1; #record match + } + } #end of search + if ($found) { #if match found + my($status) = chronyc("delete $host"); #chronyc + if ($status) { #chronyc error + print "chronyc failed, status=$status\n"; + exit 1; + } else { #reiterate + foreach $hostIT (keys(%host2ip) ) { + if ( $host2ip{$hostIT}[0] eq $host) { + delete $host2ip{$hostIT}; #deleting match hosts + } + } + + } + + } + } else { #else not ip address + #must be hostname + if ( ! $host2ip{$host} ) { + print "No such host as $host listed\n"; + exit 1; + } + #get ip address + $ip=gethostaddr($host); + if ( ! isIpAddr($ip) ) { #no ip address + print "query failed: ", $ip, "\n" if $pedebug; + exit 1; + } + + printf "ip=$ip host=$host\n" if ($pedebug); + + my($listed_host_ip) = $host2ip{$host}[0]; # get the ip address saved + + if ( $ip ne $listed_host_ip) { + print + "Info: listed host ip=>$listed_host_ip". + "< is different from DNS ip=>$ip<\n"; + $ip = $listed_host_ip; + } + + # delete the server + my($status) = chronyc("delete $listed_host_ip\n"); + + if ($status) { + print "chronyc failed, status=$status\n"; + exit 1; + } + #delete table entry + delete$host2ip{$host}; + } + +} + +#update for each host who's dns ip address has changed +#delete the old server and add the new. update the record. +if ($opts{'u'}) { + my($command); + + my(%prospective); # store new IP address we + #are thinking of changing. + + Log('local0.info', + "Now searching for modified DNS entries."); + + foreach $host (keys(%host2ip)) { #for each listed host + my($old_ip) = $host2ip{$host}[0]; #get old ip + $rest = $host2ip{$host}[1]; #extra params + + $ip = gethostaddr($host); #get new ip from dns + #if error + if ( ! isIpAddr($ip) or ! isHostname($host) ) { + print "query failed: ", $ip, "host=$host\n"; + + Log('local0.err',"query failed: ". $ip . "host=$host"); + + exit 1; + } + + next if($ip eq $old_ip); #if ip not changed, skip + + Log('local0.info',"Ip address for $host has changed. Old IP address=". + "$old_ip, new IP address=$ip"); + # add command to delete old host, add the new. + $command = $command . "delete $old_ip\n" . + "add server $ip $rest\n"; + + # we are now thinking about changing this host ip + $prospective{$host} = [$ip,$rest]; + } + # submit all the accumulated chronyc commands if any. + if ($command) { + $status = chronyc($command); + if ($status) { + print "chronyc failed, status=$status\n"; + Log('local0.err',"query failed: ". $ip . "host=$host"); + exit 1; + } + } else { #if no commands exit + exit 0; #because no rewrite of file needed + } + + #copy prospective modifications back into main table. + #we now know that all these mods were done with chronyc + foreach $host (keys(%prospective)) { + my($ip) = $prospective{$host}[0]; + $rest = $prospective{$host}[1]; + $host2ip{$host} = [$ip,$rest]; + } +} + +#starting for each entry we have read in from the old list +# add the server in chronyc +# this option is seldom used. + +if ($opts{'s'}) { + my($command)=""; + + foreach $host (keys(%host2ip)) { + $command = $command . "add server $host2ip{$host}[0] ". + "$host2ip{$host}[1]\n"; + } + my($status) = chronyc($command); + if ($status) { + print "chronyc failed, status=$status\n"; + exit 1; + } + +} +# write out the data file in format +#># HOSTNAME +#>server IPADDRESS extra parameters [offline] +# offline is omitted if -n switch is specified. + +my(@value); +my($such); +{ + # to start out we write to temporary file. + (my($writeout) , my($outname)) = mkstemp( "${listfile}.outXXXXXXX"); + + $outname or BadDie("can not open for $listfile"); + + + # save the chrony.conf part! + # and write the DYNAMIC header + print $writeout @chronyDconf, $noedithead; + + + # for each entry + foreach $host (keys(%host2ip) ){ + + #write the record + + # write the comment that indicates the hostname + # and the server command. + print $writeout + "\# $host\nserver $host2ip{$host}[0] $host2ip{$host}[1]${offlineS}\n" ; + + print + "server $host2ip{$host}[0] $host2ip{$host}[1]${offlineS}\# $host\n" + if $pedebug; + + } + + #WRITE THE end of dnyamic marker comment + print $writeout $noeditheadend; + + # close the output file which was a temporary file. + close($writeout) or BadDie("can not close $outname"); + + # we now begin a intracate dance to make the the temporary + # the main chrony.conf + # + # if there is a chrony.conf.BAK save it to a temporary. + # rename chrony.conf to chrony.conf.BAK + # rename the temporary to chrony.conf + # if there already was a chrony.conf.BAK, unlink the copy of this. + + my($backname) = "$listfile\.BAK"; + my($backplain) = ( -f $backname ); + my($saveback); + #if chrony.conf.BAK exists rename to a temporary. + if ($backplain ) { + + $saveback = mktemp("${backname}.bakXXXXXXX"); + move($backname,$saveback) or + BadDie "unable to move $backname to $savename"; + + } + + # rename old chrony.conf to chrony.conf.BAK + move($listfile,$backname) or + BadDie "unable to move $listfile to $backname"; + + # rename our output to chrony.conf + move($outname,$listfile) or + BadDie "unable to move $outname to $listfile"; + + #if there was a temporary chrony.conf.BAK that we saved to temp + #unlink it + unlink($saveback) or BadDie "unable to unlink $saveback" if($backplain); + +} diff --git a/contrib/DNSchrony/DNSchronyADD b/contrib/DNSchrony/DNSchronyADD new file mode 100755 index 0000000..fc9858b --- /dev/null +++ b/contrib/DNSchrony/DNSchronyADD @@ -0,0 +1,21 @@ +#!/usr/bin/bash + +# $1 is chrony password. +# $2 is hostname to add or hostname=ipaddres +# $3-$9 is rest of extra server parameters + +FIRST="$1" +HOST="$2" +shift 2 + +#remaining parameters a the other paramaters to server command +#excluding "offline" +ARGS="$*" + +#if none use default taken from chrony documentation. +DEF="minpoll 5 maxpoll 10 maxdelay 0.4" + +DARGS=${ARGS:-$DEF} + +CHRONYPASSWORD=$FIRST \ +/usr/local/bin/DNSchrony.pl -a "$HOST" "$DARGS" diff --git a/contrib/DNSchrony/DNSchronyDELETE b/contrib/DNSchrony/DNSchronyDELETE new file mode 100755 index 0000000..e443c9b --- /dev/null +++ b/contrib/DNSchrony/DNSchronyDELETE @@ -0,0 +1,7 @@ +#!/usr/bin/bash + +# $1 is chrony password. +# $2 host to be deleted if ip nn.n.n.n then no DNS used + +CHRONYPASSWORD=$1 \ +/usr/local/bin/DNSchrony.pl -d $2 diff --git a/contrib/DNSchrony/DNSchronyUPDATE b/contrib/DNSchrony/DNSchronyUPDATE new file mode 100755 index 0000000..14cbceb --- /dev/null +++ b/contrib/DNSchrony/DNSchronyUPDATE @@ -0,0 +1,7 @@ +#!/usr/bin/bash + +# $1 is chrony password. + + +CHRONYPASSWORD=$1 \ +/usr/local/bin/DNSchrony.pl -ulS diff --git a/contrib/DNSchrony/README b/contrib/DNSchrony/README new file mode 100644 index 0000000..8c35348 --- /dev/null +++ b/contrib/DNSchrony/README @@ -0,0 +1,166 @@ + Copyright (C) Paul Elliott 2002 + + +DNSchrony.pl version -2.0 + +Problem: If you look at the list of secondary NTP servers: +http://www.eecis.udel.edu/~mills/ntp/clock2.htm + +you will find statements like this: + +"Note: IP addresses are subject to change; please use DNS" + +These servers represent a problem for chrony. Chrony is a program +designed to work on hosts with an intermittent connection to the +internet. Often no DNS is available when chrony starts. As chrony +is currently designed, chronyd never sees a DNS host name. If a +user specifies one when using chronyc's "add server" command, the +DNS lookup is done by chronyc and an IP address is passed to chronyd. + +One can imagine I suppose, a redesign to chrony in which chronyd +keeps track of DNS changes. But this has problems, all the time +chronyd is fooling around with DNS, it would not be keeping track +of its prime function, what the clocks and NTP servers are saying. +This could result in poorer performance. Or perhaps you say that +chronyd should be multi threaded. One thread to fool with DNS +and another to keep track of time. But this introduces a great +deal of complexity, and complexity is the enemy of elegant robust +code. Besides, Richard probably has better things to do. + +I have attempted to address this problem with a humble perl script, +which I now release under the GPL: DNSchrony.pl + +PLEA FOR HELP FROM EXPERIENCED PERL HACKERS. + +Please go thru the code and find errors and improvements. +I am not quite an polished perl hacker. Please fix bugs and +make improvements. It needs better documentation. Someone +who knows how, put in some POD. + +END OF PLEA + +Philosophy of DNSchrony.pl: keep a list of servers that use +DNS. From time to time, hopefully when DNS is up, go thru +the list lookup all the hostnames and see if any ip addresses have +changed. If any have changed, update our list and do chronyc +"delete" and "add server" commands so that chronyd now talks to +the right NTP server. + +Additional nuance: keep the list in /etc/chrony.conf in the +form of comments starting with "#" and "server" commands +legal in a chrony.conf file. Format of a list entry: + +# hostname +server IP-ADDRESS extra server parameters + +These entries are delimited by special comments that allow +DNSchrony.pl to find them and also tell humans not to mess with them. + +Example of such a section of a chrony.conf file: + +dumpdir /var/log/chrony +rtcfile /etc/chrony.rtc + +## DNSchrony dynamic dns server section. DO NOT EDIT +## per entry FORMAT: +## |--------------------------------------------| +## |#HOSTNAME | +## |server IP-ADDRESS extra-params [ offline ] | +## |--------------------------------------------| +# tock.greyware.com +server 208.14.208.44 minpoll 5 maxpoll 10 maxdelay 0.4 offline +# tick.greyware.com +server 208.14.208.19 minpoll 5 maxpoll 10 maxdelay 0.4 offline +# ntppub.tamu.edu +server 128.194.254.9 minpoll 5 maxpoll 10 maxdelay 0.4 offline +## END OF DNSchrony dynamic dns server section. + +This allows the list of dynamic DNS servers to be preserved +when chronyd is stoped/started. + +All servers that do not have ip addresses subject to change +should be put in the regular part of chrony.conf as described +in the chrony documentation. + +Security philosophy: DNSchrony does no security checking but +relies on other security factors. + +Users without the privilege to modify /etc/chrony.conf and the +directory /etc will be unable to use DNSchrony to do so, because +of file protections. DNSchrony passes thru passwords to chronyc. +Users that do not know the correct chronyc password will be +unable to get chronyd do do anything. Thus, DNSchrony passes +the buck to these other security features. + +INSTALLATION: + +copy the files: DNSchronyADD DNSchronyUPDATE DNSchronyDELETE DNSchrony.pl +to /usr/local/bin. Backup the file /etc/chrony.conf leave hosts +with static ip addresses in this file. + +DNSchrony uses the following perl modules. See that they are installed. +Get them from CPAN if needed. + +Net::DNS, Tie::Syslog, Getopt::Std, Socket, File. + +Cause DNSchronyUPDATE bash script to run from time to time when DNS +is working. If you have a dialup, one way to do this would be to +modify your /etc/ppp/ip-up.local file as follows: + +cat </dev/null 2>&1 & + +Since this file contains the chronyc password you will want to set the +file permissions so that just everybody will not be able to read +it. But you already did that when you put in the chronyc command. Any +other way to make DNSchronyUPDATE run perodicly when DNS is up will +also work. + +To add a server with a varying IP address one could run: +/usr/local/bin/DNSchronyADD mysecret tock.greyware.com + +or if you want to specify different server parameters you +could say: + +/usr/local/bin/DNSchronyADD mysecret tock.greyware.com "minpoll 10 maxpoll 20 maxdelay 0.8" + +The DNSchronyADD's default for these parameters is: +"minpoll 5 maxpoll 10 maxdelay 0.4" values that are often shown +as examples in the chrony documentation. + +If DNS is not running now but you know the IP address, you can say: +/usr/local/bin/DNSchronyADD mysecret tock.greyware.com=208.14.208.44 + +Of course, the IP address will be checked next time DNSchronyUPDATE +runs. + +To delete dynamic DNS a server: +/usr/local/bin/DNSchronyDELETE mysecret tock.greyware.com + +To change parameters delete and re-add. + +Of course, in all of the above "mysecret" is your chronyc password +which SHOULD NOT BE "mysecret". +---------------------------------------------- +DNSchrony.pl is covered by the GPL +# Copyright (C) Paul Elliott 2002 +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# SEE COPYING FOR DETAILS \ No newline at end of file diff --git a/contrib/DNSchrony/ip-up.local b/contrib/DNSchrony/ip-up.local new file mode 100644 index 0000000..34f6e76 --- /dev/null +++ b/contrib/DNSchrony/ip-up.local @@ -0,0 +1,22 @@ +#example file /etc/ppp/ip-up.local +#originally from SuSE distribution +#modified for chrony +cat </dev/null 2>&1 & +#other stuff who knows? + +# The following lines added for Linux-HA support # Heartbeat +DEVFILE=`echo $DEVICE | sed -e 's!^/dev/!!' -e 's!/!.!g'` # Heartbeat +OUTFILE=/var/run/ppp.d/$DEVFILE # Heartbeat +( # Heartbeat +echo "$IPREMOTE" # Heartbeat +echo "$IFNAME" # Heartbeat +echo "$PPPD_PID" # Heartbeat +echo "$IPLOCAL" # Heartbeat +) > $OUTFILE # Heartbeat 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 +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=_+4F9R +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<9X5-C:IL9J:LYR^V2BP) +M3>J*HJS`@Z],IG2Q*NJ*M0(SJ!1[TK-4FA7RL(&-DN;H +MC5&%*>'/#&:K"XC'EH%".O>6"42,GBJ=G>\@F_*%B>S"1DJ7RSIKK0*-25Q< +M0.*RN""P(@1 +M:(;4:ASM37EK(Z.V;JU6.Y`TVH',VQRKF<-9FY.:FG0:DT+P!MA6^J-(J`O0 +M%*4E[<54HO@[T,/E69&:_: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=BXAOL34[#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@J +M)`]Q!A.\<[!':>,8``27$HH-\L)HN=HQ5;0#9XK_=ICY=`B.;Q8Z]::5Y.+J +MXFQVG*]SS+QUT&H%D+I6V?C34T'7$+;X8&!%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=U2IN(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\#^->&'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%`8J1 +M,(`R]TJ,'W107WT5L,G$JQ=T7/WQATKN5C\C7Q"DX`D/U2;3L#_MEDQ%I2%S')8QJX&",HVX+B<"F:0K]!%% +M#ZFTB*`9W\G&=S+,HL=UX.+Y=BFH,M)G]%REC$(_6=9OB9JYAT?O@Q#,'M$RRYJ%I-BP6S*-*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-/7Q8\UM$)7OR)B"3DA>$*A +ME/K-+N4-C6_6#H8>3#@,?655:YI6*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,`PQ[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\*!.B<=/5A>V1-ODQT9^ +MRD?F?6#?+!`9287V2!]E%-8(C&:F'C7XG[`O,4-@*_4%2.R3V;>9K@:K0[N` +MJ2^WQNG9TX>EHC=?!^_^1H1MSAXLNKBT=/TDW23^@N'L*06S;,KB.^G+ZA +M9+VS]`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 +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" +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/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 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 +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 +# +# 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 = ; + 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/examples/chrony.conf.example b/examples/chrony.conf.example new file mode 100644 index 0000000..5488e72 --- /dev/null +++ b/examples/chrony.conf.example @@ -0,0 +1,289 @@ +####################################################################### +# $Header: /cvs/src/chrony/examples/chrony.conf.example,v 1.2 2002/02/03 21:46:29 richard Exp $ +# +# 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. I have not included the more obscure options. Refer +# to the documentation for these. +# +# Copyright 2002 Richard P. Curnow +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# +# +####################################################################### +### 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://www.eecis.udel.edu/~mills/ntp/servers.htm. + +! server ntp0.your-isp.com +! server ntp1.your-isp.com +! server ntp.public-server.org + +# However, for dial-up use you probably want these instead. The word +# 'offline' means that the server is not visible at boot time. Use +# chronyc's 'online' command to tell chronyd that these servers have +# become visible after you go on-line. + +! server ntp0.your-isp.com offline +! server ntp1.your-isp.com offline +! server ntp.public-server.org offline + +# You may want to specify NTP 'peers' instead. If you run a network +# with a lot of computers and want several computers running chrony to +# have the 'front-line' interface to the public NTP servers, you can +# 'peer' these machines together to increase robustness. + +! peer ntp0.my-company.com + +# There are other options to the 'server' and 'peer' directives that you +# might want to use. For example, you can ignore measurements whose +# round-trip-time is too large (indicating that the measurement is +# probably useless, because you don't know which way the measurement +# message got held up.) Consult the full documentation for details. + +####################################################################### +### 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 for dial-up (or +# other high-latency connections like slow leased lines), the second +# seems OK for a LAN environment. + +! maxupdateskew 100 +! maxupdateskew 5 + +####################################################################### +### 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 /etc/chrony.drift + +# If you want to use the program called chronyc to configure aspects of +# chronyd's operation once it is running (e.g. tell it the Internet link +# has gone up or down), you need a password. This is stored in the +# following keys file. (You also need keys to support authenticated NTP +# exchanges between cooperating machines.) Again, this option is +# assumed by default. + +keyfile /etc/chrony.keys + +# Tell chronyd which numbered key in the file is used as the password +# for chronyc. (You can pick any integer up to 2**32-1. '1' is just a +# default. Using another value will _NOT_ increase security.) + +commandkey 1 + +# chronyd can save the measurement history for the servers to files when +# it it exits. This is useful in 2 situations: +# +# 1. On Linux, if you stop chronyd and restart it with '-r' (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, especially with a dial-up link. +# +# 2. Again 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). +# +# Enable these two options to use this. + +! dumponexit +! dumpdir /var/log/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/chronyd.pid + +####################################################################### +### INITIAL CLOCK CORRECTION +# This option is only useful if your NTP servers are visible at boot +# time. This probably means you are on a LAN. If so, the following +# option will choose the best-looking of the servers and correct the +# system time to that. The value '10' means that if the error is less +# than 10 seconds, it will be gradually removed by speeding up or +# slowing down your computer's clock until it is correct. If the error +# is above 10 seconds, an immediate time jump will be applied to correct +# it. Some software can get upset if the system clock jumps (especially +# backwards), so be careful! + +! initstepslew 10 ntp0.your-company.com ntp1.your-company.com ntp2.your-company.com + +####################################################################### +### 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. +# e.g. you might be running chronyd on a dial-up machine that has a LAN +# sitting behind it with several 'satellite' computers on it. +# +# 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 chronyd to act as an NTP broadcast server, enable and edit +# (and maybe copy) the following line. This means that a broadcast +# packet is sent to the address 192.168.1.255 every 60 seconds. The +# address MUST correspond to the broadcast address of one of the network +# interfaces on your machine. If you have multiple network interfaces, +# add a broadcast line for each. + +! broadcast 60 192.168.1.255 + +# 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. + +! noclientlog + +####################################################################### +### 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@foobar.org 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. + +# 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. + +# NOTE, even if the host where you run chronyc is granted access, you +# still need a command key set up and you have to know the password to +# put into chronyc to allow you to modify chronyd's parameters. By +# default all you can do is view information about chronyd's operation. + +# Some people have reported that the need the following line to allow +# chronyc to work even on the same machine. This should not be +# necessary, and the problem is being investigated. You can leave this +# line enabled, as it's benign otherwise. + +cmdallow 127.0.0.1 + +####################################################################### +### 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 /etc/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 + +####################################################################### diff --git a/examples/chrony.keys.example b/examples/chrony.keys.example new file mode 100644 index 0000000..181e96e --- /dev/null +++ b/examples/chrony.keys.example @@ -0,0 +1,27 @@ +####################################################################### +# $Header: /cvs/src/chrony/examples/chrony.keys.example,v 1.1 2002/01/31 00:00:08 richard Exp $ +# +# This is an example chrony keys file. You should copy it to /etc/chrony.keys +# after editing it to set up the key(s) you want to use. In most situations, +# you will require a single key (the 'commandkey') so that you can supply a +# password to chronyc to enable you to modify chronyd's operation whilst it is +# running. +# +# Copyright 2002 Richard P. Curnow +# +####################################################################### +# A valid key line looks like this + +1 a_key + +# It must consist of an integer, followed by whitespace, followed by a block of +# text with no spaces in it. (You cannot put a space in a key). If you wanted +# to use the above line as your commandkey (i.e. chronyc password), you would +# put the following line into chrony.conf (remove the # from the start): + +# commandkey 1 + +# You might want to define more keys if you use the MD5 authentication facility +# in the network time protocol to authenticate request/response packets between +# trusted clients and servers. + diff --git a/faq.txt b/faq.txt new file mode 100644 index 0000000..659519f --- /dev/null +++ b/faq.txt @@ -0,0 +1,384 @@ +@@PROLOGUE + + +Frequently asked questions + + + + + + + + +

+This is a set of questions and answers to common problems and issues. +

+As I receive more emails about the software, I will add new questions +to this page. + +


+

+The author can be reached by email + + +

+PLEASE +include the word "chrony" in your subject line if possible (so that my +mail reader can keep my mail sorted by topic)! +


+ +
+@@ENDPROLOGUE +S: Administrative issues + +Q: Where can I get chrony source code? +Via the home page, see below. + +Q: Are there any packaged versions of chrony? +I am aware of packages for Debian, Mandrake and Redhat. I am not personally +involved with how these are built or distributed. + +Q: Where is the home page? +It is currently at http://chrony.sunsite.dk/. + +Q: Is there a mailing list? +Yes, it's currently at chrony-users@sunsite.dk. There is a low-volume +list called chrony-announce which is just for announcements of new releases or +similar matters of high importance. You can join the lists by sending a +message to chrony-users-subscribe@sunsite.dk or +chrony-announce-subscribe@sunsite.dk respectively. + +For those who want to contribute to the development of chrony, there is a +developers' mailing list. You can subscribe by sending mail to +chrony-dev-subscribe@sunsite.dk. + +Q: What licence is applied to chrony? +Starting from version 1.15, chrony is licensed under the GNU General Public +License. Versions prior to 1.15 were licensed under a custom BSD-like +license. + +If you want to use parts of chrony in non-free software, you will need to use +older versions of the source code. Alternatively, contact me - I may be +prepared to licence parts of the source code to suit your purposes. I am quite +sympathetic to projects licensed under other free/open-source (but non-GPL) +licences, as well as to commercial projects which are of a single-customer +"turnkey" nature (as opposed to mass-market "shrink-wrap" or "floating-licence" +products). + +S: Chrony compared to other programs +Q: How does chrony compare to xntpd? +If your computer is permenently connected, or connected for long periods (that +is, for the several hours it takes xntpd to settle down), or you need to +support hardware reference clocks to your computer, then xntpd will work fine. +Apart from not supporting hardware clocks, chrony will work fine too. + +If your computer connects to the 'net for 5 minutes once a day (or something +like that), or you turn your (Linux v2.0) computer off when you're not using +it, or you want to use NTP on an isolated network with no hardware clocks in +sight, chrony will work much better for you. + +The reason I wrote chrony was that I could not get xntpd to do +anything sensible on my PC at home, which is connected to the 'net for +about 5 minutes once or twice a day, mainly to upload/download email +and news. Nowadays it is also turned off for 22-23 hours a day, when +not in use. I wanted a program which would : + +- slew the time to correct it when I go online and NTP servers become visible + +- determine the rate at which the computer gains or loses time and use this + information to keep it reasonably correct between connects to the 'net. This + has to be done using a method that does not care about the intermittent + availability of the references or the fact the computer is turned off between + groups of measurements.. + +- maintain the time across reboots, by working out the error and drift rate of + the computer's real-time clock and using this information to set the system + clock correctly at boot up. (In the last few months, it became impossible for + me to leave my computer powered permanently.) + +Also, when working with isolated networks with no true time references +at all, I found xntpd gave me no help with managing the local clock's +gain/loss rate on the NTP master node (which I set from my watch). I +added some automated support in chrony to deal with this. + +S: Compilation issues +Q:How do I apply source patches? +Sometimes I release source patches rather than a full version when I need to +provide a fix for small problems. Supposing you have chrony-1.X.tar.gz and a +source patch chrony-1.X-1.X.1.gz. The steps required are: + + tar xzvf ../chrony-1.X.tar.gz + cd chrony-1.X + gunzip < ../../chrony-1.X-1.X.1.gz | patch -p1 + ./configure + make + make install + +Q:Can I compile chrony with an ANSI-C compiler that is not GCC v2.x? +I have had reports that chrony can be compiled with GCC v1.42, by using the +following trick when running make + + make CC='gcc -D__FUNCTION__=\"function_not_available\"' + +(this gets around the lack of a __FUNCTION__ macro in GCC v1.) + +The same trick may be enough to allow other compilers to be used. + +Q: I get errors like 'client.c:44: readline/readline.h: file not found' +Read the section about 'readline' in the INSTALL file or in chrony.txt. You +may need to disable readline support (e.g. if you haven't got readline +installed at all, or just don't want it), or specify the location of the +readline files (e.g. if you've installed them in a non-standard place). + +Q: I have RedHat 7.3 and can't compile rtc_linux.c (error in spinlock.h) +The following solution has been found for this. Enter the following 3 commands +(as root): + + cd /usr/include/ + mv linux linux.rh + ln -s /usr/src/linux/include/linux ./linux + +The problem seems to be that RedHat provide their own kernel header files in +/usr/include/linux. Besides differing from those used by your current kernel, +if you compiled it yourself, they also seem to have been changed in a way that +causes a problem compiling chrony. Chrony compiles fine with standard kernel +header files. + +There have also been reports that just replacing the file +/usr/src/linux/spinlock.h by the equivalent file from a vanilla kernel source +tree is sufficient to fix the problem. + +S: Selection of NTP servers +Q: I have several computers on a LAN. Should I make one the master, or make them all clients of an external server? +I think the best configuration is to make one computer the master, with the +others as clients of it. Add a 'local' directive to the master'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. + +S: Addressing issues +Q: I get the following error message : "Could not get IP adress for localhost" +Add a line like the following to your /etc/hosts file + 127.0.0.1 localhost + +Q: I have problems if I put the names of my NTP servers in the chrony.conf file. +If you have no connection to the Internet at boot time, chrony won't be able to +turn the names into IP addresses when it starts. There seem to be 2 solutions: + +1. Put the numeric IP addresses in the chrony.conf file +or +2. Put the server->IP address mappings in your /etc/hosts file and ensure that + /etc/host.conf reads 'order hosts,bind'. + +The problem is that chronyd (currently) isn't designed in a way that allows +hostname->IP address lookups during normal operation. I hope to work on this +problem very soon. + +S: My computer is not synchronising. +This is the most common problem. There are a number of reasons, see the +following questions. + +Q: Behind a firewall? +If there is a firewall between you and the NTP server you're trying to use, +the packets may be blocked. Try using a tool like etherfind or tcpdump to see +if you're getting responses from the server. If you have an external modem, +see if the receive light blinks straight after the transmit light (when the +link is quiet apart from the NTP traffic.) Try adding 'log measurements' to +the chrony.conf file and look in the measurements.log file after chrony has +been running for a short period. See if any measurements appear. + +Most people run chronyd on the firewall itself, to avoid all issues of UDP +packet forwarding and/or masquerading. + +Q: Do you have a non-permanant (i.e. intermittent) Internet connection? +Check that you're using chronyc's 'online' and 'offline' commands +appropriately. Again, check in measurements.log to see if you're getting any +data back from the server. + +Q: In measurements.log, do the '7' and '8' flag columns always show zero? +Do you have a 'local stratum X' directive in the chrony.conf file? If X is +lower than the stratum of the server you're trying to use, this situation will +arise. You should always make X quite high (e.g. 10) in this directive. + +S: Issues with chronyd + +Q: chronyd crashes after a syslog message "adjtimex failed for set frequency" +The usual cause is that the kernel is running with a different value of 'HZ' +(the timer interrupt rate) than the value that was found in the kernel header +files when chrony was compiled. The chrony.conf file can include options to +modify the HZ value (see the discussion of linux_hz and linux_freq_scale in the +documentation), however the problem is to find the value of HZ being used. + +At the end of the chrony v1.18 section of the download page +you'll find instructions on how to do this. + +This will be fixed in version 1.19, by getting chronyd to auto-detect the +kernel's value rather than relying on the compiled-in default. + +S: Issues with chronyc + +Q: I keep getting the error '510 No command access from this host --- Reply not authenticated'. +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. This +shouldn't be necessary for localhost, but some people still seem to need an +entry like 'cmdallow 127.0.0.1'. (It would be good to understand why problem +only affects some people). + +Q: I cannot log in from chronyc to carry out privileged tasks. +This is the second most common problem. + +Perhaps your /etc/chrony.keys file is badly formatted. Make sure that the +final line has a line feed at the end, otherwise the key on that line will work +as though the last character is missing. (Note, this bug was fixed in version +1.16.) + +Q: When I enter a command and hit <Return>, chronyc hangs +This probably means that chronyc cannot communicate with chronyd. + +Perhaps chronyd is not running. Try using the ps command (e.g. on Linux, 'ps +-auxw') to see if it's running. Or try 'netstat -a' and see if the ports +123/udp and 323/udp are listening. If chronyd is not running, you may 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 port +323/udp. You need to amend the firewall configuration in this case. + +Q: Is the chronyc<->chronyd protocol documented anywhere? +Only by the source code :-) See cmdmon.c (chronyd side) and client.c (chronyc +side). + +S: Real-time clock issues. +Q: What is the real-time clock (RTC)? +This is the clock which keeps the time even when your computer is turned off. +It works with 1 second resolution. chronyd can monitor the rate at which the +real-time clock gains or loses time, and compensate for it when you set the +system time from it at the next reboot. See the documentation for details. + +Q: I want to use chronyd's real-time clock support. Must I disable hwclock? +The hwclock program is often set-up by default in the boot and shutdown scripts +with many Linux installations. If you want to use chronyd's real-time clock +support, the important thing is to disable hwclock in the shutdown +procedure. If you don't, it will over-write the RTC with a new value, unknown +to chronyd. At the next reboot, chronyd 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. + +Q: 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: + +* a kernel that is supported (e.g. 2.2 onwards) +* enhanced RTC support compiled into the kernel +* an 'rtcfile' directive in your chrony.conf file. + +S: Problems with isolated networks. + +Q: When I use the 'settime' command, chronyd crashes. +If you enter times that are too far away from the real time, chronyd will +think the system clock runs fast or slow by an excessive amount. The required +compensation factor will be outside the bounds for the adjtimex() system call. +chronyd will crash when it tries to apply such an excessive adjustment. + +S: Microsoft Windows + +Q: 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 an NT service. + +Q: Are there any plans to support Windows? +I have no personal plans to do this. I have neither the time nor the +Windows programming expertise. Some time ago I did start work on a port which +I was developing under Cygwin. Anyone is welcome to pick this work up and +contribute it back to the project. + +Q: What alternative NTP clients are there for Windows? +Some of the names I've seen mentioned are + - Automachron + - NetTime (nettime.sourceforge.net) + +S: NTP-specific issues +Q: Can chrony be driven from broadcast NTP servers? +No. I remember looking at how they worked when I was first writing chrony. +Since the 'target market' then was dial-up systems, broadcast packets were not +relevant so I didn't bother working out how to deal with the complexities of +doing the delay estimation. + +I no longer have root access to a LAN environment to develop and test broadcast +server support. Neither have I the time to work on this. I would be very +happy to accept a patch from anyone who can develop, test and debug the +necessary changes! + +Q: Can chronyd transmit broadcast NTP packets (e.g. to synchronise other computers on a private LAN)? +Yes. Starting from version 1.17, chrony has this capability. + +Q: Can chrony keep the system clock a fixed offset away from real time? +I have not experimented much, but I don't believe this would be possible as +the program currently stands. + +Q: What happens if the network connection is dropped without using chronyc's 'offline' command first? +In this case chronyd will keep trying to access the server(s) that it thinks +are online. Eventually it will decide that they are unreachable and no longer +consider itself synchronised to them. If you have other computers on your LAN +accessing the computer that is affected this way, they too will become +'unsynchronised', unless you have the 'local' directive set up on the master +computer. + +The 'auto_offline' option to the 'server' entry in the chrony.conf file may be +useful to avoid this situation. + +S: Development + +Q: Can I get the source via CVS from anywhere? +Yes. See http://chrony.sunsite.dk/cvs.php for information. Currently there is +only anonymous read-only access. I keep the master copy on my own PC, which is +more convenient for me because I don't have to connect to the Internet to do +CVS operations on the files. So for now, there is no read-write access for +other developers. Please email me your patches + documentation instead. + +S: Linux-specific issues + +Q: Why does the source code include kernel header files? +The program needs to see the definitions of structures used to interact with +the real time clock (via /dev/rtc) and with the adjtimex() system call. Sadly +this has led to a number of compilation problems with newer kernels which have +been increasingly hard to fix in a way that makes the code compilable on all +Linux kernel versions (from 2.0 up anyway, I doubt 1.x still works.) Hopefully +the situation will not deteriorate further with future kernel versions. + +Q: I get "Could not open /dev/rtc, Device or resource busy" in my syslog file. +Check that you haven't accidentally got two copies of chronyd running (perhaps +defined in different start-up scripts.) + +S: Solaris-specific issues +Q: On Solaris 2.8, I get an error message about not being able to open kvm to change dosynctodr. +(The dosynctodr variable controls whether Solaris couples the equivalent of its +BIOS clock into its system clock at regular intervals). The Solaris port of +chrony was developed in the Solaris 2.5 era. Some aspect of the Solaris kernel +has changed which prevents the same technique working. I no longer have root +access to any Solaris machines to work on this, and am reliant on somebody +developing the patch and testing it. A good starting point would be to see if +xntpd has been modified to work for Solaris 2.8. + +@@EPILOGUE +
+ +Back to +the author's +main page + + +@@ENDEPILOGUE diff --git a/faqgen.pl b/faqgen.pl new file mode 100644 index 0000000..8151feb --- /dev/null +++ b/faqgen.pl @@ -0,0 +1,140 @@ +#!/usr/bin/env perl + +# $Header + +# Copyright 2001 Richard P. Curnow +# LICENCE + +# A script to generate an HTML FAQ page from a text input file. The input is assumed to consist of the following: +# Lines starting with 'S:'. These introduce sections. +# Lines starting with 'Q:'. These are the topics of questions. +# Body text (either as an introduction to the sections, or as answers to the questions. +# The body text is set as pre-formatted. + +$| = 1; + +@prologue = (); +@epilogue = (); + +@sections=(); # section titles +@sect_text=(); # introductory text in sections + +@questions=(); # questions in sections +@answers=(); # answers to questions + +$sn = -1; +$had_q = 0; + +#{{{ Parse input +while (<>) { + if (m/\@\@PROLOG/o) { + while (<>) { + last if (m/^\@\@ENDPROLOG/); + push (@prologue, $_); + } + } elsif (m/\@\@EPILOG/o) { + while (<>) { + last if (m/^\@\@ENDEPILOG/); + push (@epilogue, $_); + } + } elsif (m/^[sS]:[ \t]*(.*)$/) { + chomp; + $qn = -1; + ++$sn; + $sections[$sn] = &guard($1); + $sect_text[$sn] = ""; + $questions[$sn] = [ ]; + $answers[$sn] = [ ]; + $had_q = 0; + } elsif (/^[qQ]:[ \t]*(.*)$/) { + chomp; + die unless ($sn >= 0); + ++$qn; + $questions[$sn]->[$qn] = &guard($1); + $had_q = 1; + } else { + if ($had_q) { + if ($qn >= 0) { + $answers[$sn]->[$qn] .= $_; + } + } else { + if ($sect_text[$sn] ne "" || $_ !~ /^\s*$/) { + $sect_text[$sn] .= $_; + } + } + } +} +#}}} + +# Emit file header +if ($#prologue >= 0) { + print @prologue; +} else { +print < + + +Chrony Frequently Asked Questions + + + +Table of contents +EOF +} + +# Emit table of contents +print "
    \n"; +for $sn (0 .. $#sections) { + print "
  • ".($sn+1).". ".$sections[$sn]."\n"; + print "
      \n"; + for $qn (0 .. $#{$questions[$sn]}) { + $sq = ($sn+1).".".($qn+1); + print "
    • ".$sq.". ".$questions[$sn]->[$qn]."\n"; + #print "
    • ".$sq.". ".$questions[$sn]->[$qn]."\n"; + } + print "
    \n"; +} +print "
\n"; + +# Emit main sections +for $sn (0 .. $#sections) { + print "
\n"; + print "\n"; + #print "".($sn+1).". ".$sections[$sn]."\n"; + print "\n"; + if ($sect_text[$sn] ne "") { + print "
\n";
+        print $sect_text[$sn];
+        print "
\n"; + } + for $qn (0 .. $#{$questions[$sn]}) { + $sq = ($sn+1).".".($qn+1); + print "

\n"; + print "\n"; + print "".$sq.". ".$questions[$sn]->[$qn]."\n"; + print "

\n";
+        print $answers[$sn]->[$qn];
+        print "
\n"; + } +} + +# Print footer +if ($#epilogue >= 0) { + print @epilogue; +} else { +print < + +EOF +} + + +#{{{ sub guard { +sub guard { +# Hide wierd tags etc + my ($x) = @_; + return $x; +} +#}}} + + diff --git a/getdate.c b/getdate.c new file mode 100644 index 0000000..e4819bd --- /dev/null +++ b/getdate.c @@ -0,0 +1,2019 @@ + +/* A Bison parser, made from getdate.y + by GNU Bison version 1.25 + */ + +#define YYBISON 1 /* Identify Bison output. */ + +#define tAGO 258 +#define tDAY 259 +#define tDAY_UNIT 260 +#define tDAYZONE 261 +#define tDST 262 +#define tHOUR_UNIT 263 +#define tID 264 +#define tMERIDIAN 265 +#define tMINUTE_UNIT 266 +#define tMONTH 267 +#define tMONTH_UNIT 268 +#define tSEC_UNIT 269 +#define tSNUMBER 270 +#define tUNUMBER 271 +#define tYEAR_UNIT 272 +#define tZONE 273 + +#line 1 "getdate.y" + +/* +** Originally written by Steven M. Bellovin while +** at the University of North Carolina at Chapel Hill. Later tweaked by +** a couple of people on Usenet. Completely overhauled by Rich $alz +** and Jim Berets in August, 1990; +** +** This grammar has 13 shift/reduce conflicts. +** +** This code is in the public domain and has no copyright. +*/ + +#ifdef HAVE_CONFIG_H +# include +# ifdef FORCE_ALLOCA_H +# include +# endif +#endif + +/* 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 +#include + +#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) + +#include "getdate.h" + +#if defined (STDC_HEADERS) || defined (USG) +# include +#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 + +extern struct tm *gmtime (); +extern struct tm *localtime (); +extern time_t mktime (); + +/* 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 (); +static int yyerror (); + +#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 175 "getdate.y" +typedef union { + int Number; + enum _MERIDIAN Meridian; +} YYSTYPE; +#include + +#ifndef __cplusplus +#ifndef __STDC__ +#define const +#endif +#endif + + + +#define YYFINAL 61 +#define YYFLAG -32768 +#define YYNTBASE 22 + +#define YYTRANSLATE(x) ((unsigned)(x) <= 273 ? yytranslate[x] : 32) + +static const char 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 != 0 +static const short yyprhs[] = { 0, + 0, 1, 4, 6, 8, 10, 12, 14, 16, 19, + 24, 29, 36, 43, 45, 47, 50, 52, 55, 58, + 62, 68, 72, 76, 79, 84, 87, 91, 94, 96, + 99, 102, 104, 107, 110, 112, 115, 118, 120, 123, + 126, 128, 131, 134, 136, 139, 142, 144, 146, 147 +}; + +static const short yyrhs[] = { -1, + 22, 23, 0, 24, 0, 25, 0, 27, 0, 26, + 0, 28, 0, 30, 0, 16, 10, 0, 16, 19, + 16, 31, 0, 16, 19, 16, 15, 0, 16, 19, + 16, 19, 16, 31, 0, 16, 19, 16, 19, 16, + 15, 0, 18, 0, 6, 0, 18, 7, 0, 4, + 0, 4, 20, 0, 16, 4, 0, 16, 21, 16, + 0, 16, 21, 16, 21, 16, 0, 16, 15, 15, + 0, 16, 12, 15, 0, 12, 16, 0, 12, 16, + 20, 16, 0, 16, 12, 0, 16, 12, 16, 0, + 29, 3, 0, 29, 0, 16, 17, 0, 15, 17, + 0, 17, 0, 16, 13, 0, 15, 13, 0, 13, + 0, 16, 5, 0, 15, 5, 0, 5, 0, 16, + 8, 0, 15, 8, 0, 8, 0, 16, 11, 0, + 15, 11, 0, 11, 0, 16, 14, 0, 15, 14, + 0, 14, 0, 16, 0, 0, 10, 0 +}; + +#endif + +#if YYDEBUG != 0 +static const short yyrline[] = { 0, + 191, 192, 195, 198, 201, 204, 207, 210, 213, 219, + 225, 234, 240, 252, 255, 258, 264, 268, 272, 278, + 282, 300, 306, 312, 316, 321, 325, 332, 340, 343, + 346, 349, 352, 355, 358, 361, 364, 367, 370, 373, + 376, 379, 382, 385, 388, 391, 394, 399, 432, 436 +}; +#endif + + +#if YYDEBUG != 0 || defined (YYERROR_VERBOSE) + +static const char * const yytname[] = { "$","error","$undefined.","tAGO","tDAY", +"tDAY_UNIT","tDAYZONE","tDST","tHOUR_UNIT","tID","tMERIDIAN","tMINUTE_UNIT", +"tMONTH","tMONTH_UNIT","tSEC_UNIT","tSNUMBER","tUNUMBER","tYEAR_UNIT","tZONE", +"':'","','","'/'","spec","item","time","zone","day","date","rel","relunit","number", +"o_merid", NULL +}; +#endif + +static const short yyr1[] = { 0, + 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, + 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, + 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 30, 31, 31 +}; + +static const short yyr2[] = { 0, + 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 +}; + +static const short yydefact[] = { 1, + 0, 17, 38, 15, 41, 44, 0, 35, 47, 0, + 48, 32, 14, 2, 3, 4, 6, 5, 7, 29, + 8, 18, 24, 37, 40, 43, 34, 46, 31, 19, + 36, 39, 9, 42, 26, 33, 45, 0, 30, 0, + 0, 16, 28, 0, 23, 27, 22, 49, 20, 25, + 50, 11, 0, 10, 0, 49, 21, 13, 12, 0, + 0 +}; + +static const short yydefgoto[] = { 1, + 14, 15, 16, 17, 18, 19, 20, 21, 54 +}; + +static const short yypact[] = {-32768, + 0, -19,-32768,-32768,-32768,-32768, -13,-32768,-32768, 30, + 15,-32768, 14,-32768,-32768,-32768,-32768,-32768,-32768, 19, +-32768,-32768, 4,-32768,-32768,-32768,-32768,-32768,-32768,-32768, +-32768,-32768,-32768,-32768, -6,-32768,-32768, 16,-32768, 17, + 23,-32768,-32768, 24,-32768,-32768,-32768, 27, 28,-32768, +-32768,-32768, 29,-32768, 32, -8,-32768,-32768,-32768, 50, +-32768 +}; + +static const short yypgoto[] = {-32768, +-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768, -5 +}; + + +#define YYLAST 51 + + +static const short yytable[] = { 60, + 22, 51, 23, 2, 3, 4, 58, 5, 45, 46, + 6, 7, 8, 9, 10, 11, 12, 13, 30, 31, + 42, 43, 32, 44, 33, 34, 35, 36, 37, 38, + 47, 39, 48, 40, 24, 41, 51, 25, 49, 50, + 26, 52, 27, 28, 56, 53, 29, 57, 55, 61, + 59 +}; + +static const short 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, 0, + 56 +}; +/* -*-C-*- Note some compilers choke on comments on `#line' lines. */ +#line 3 "/usr/local/share/bison.simple" + +/* Skeleton output parser for bison, + Copyright (C) 1984, 1989, 1990 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +#ifndef alloca +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not GNU C. */ +#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi) +#include +#else /* not sparc */ +#if defined (MSDOS) && !defined (__TURBOC__) +#include +#else /* not MSDOS, or __TURBOC__ */ +#if defined(_AIX) +#include + #pragma alloca +#else /* not MSDOS, __TURBOC__, or _AIX */ +#ifdef __hpux +#include +#ifdef _ULONG_T /* defined in on HP-UX 10 */ +#include +#else /* earlier HP-UX versions have alloca as a library function */ +#ifdef __cplusplus +extern "C" { +void *alloca (unsigned int); +}; +#else /* not __cplusplus */ +#define alloca __builtin_alloca +#endif /* not __cplusplus */ +#endif /* HP-UX <= 9 */ +#endif /* __hpux */ +#endif /* not _AIX */ +#endif /* not MSDOS, or __TURBOC__ */ +#endif /* not sparc. */ +#endif /* not GNU C. */ +#endif /* alloca not defined. */ + +/* This is the parser code that is written into each bison parser + when the %semantic_parser declaration is not specified in the grammar. + It was written by Richard Stallman by simplifying the hairy parser + used when %semantic_parser is specified. */ + +/* Note: there must be only one dollar sign in this file. + It is replaced by the list of actions, each action + as one case of the switch. */ + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY -2 +#define YYEOF 0 +#define YYACCEPT return(0) +#define YYABORT return(1) +#define YYERROR goto yyerrlab1 +/* Like YYERROR except do call yyerror. + This remains here temporarily to ease the + transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ +#define YYFAIL goto yyerrlab +#define YYRECOVERING() (!!yyerrstatus) +#define YYBACKUP(token, value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { yychar = (token), yylval = (value); \ + yychar1 = YYTRANSLATE (yychar); \ + YYPOPSTACK; \ + goto yybackup; \ + } \ + else \ + { yyerror ("syntax error: cannot back up"); YYERROR; } \ +while (0) + +#define YYTERROR 1 +#define YYERRCODE 256 + +#ifndef YYPURE +#define YYLEX yylex() +#endif + +#ifdef YYPURE +#ifdef YYLSP_NEEDED +#ifdef YYLEX_PARAM +#define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM) +#else +#define YYLEX yylex(&yylval, &yylloc) +#endif +#else /* not YYLSP_NEEDED */ +#ifdef YYLEX_PARAM +#define YYLEX yylex(&yylval, YYLEX_PARAM) +#else +#define YYLEX yylex(&yylval) +#endif +#endif /* not YYLSP_NEEDED */ +#endif + +/* If nonreentrant, generate the variables here */ + +#ifndef YYPURE + +int yychar; /* the lookahead symbol */ +YYSTYPE yylval; /* the semantic value of the */ + /* lookahead symbol */ + +#ifdef YYLSP_NEEDED +YYLTYPE yylloc; /* location data for the lookahead */ + /* symbol */ +#endif + +int yynerrs; /* number of parse errors so far */ +#endif /* not YYPURE */ + +#if YYDEBUG != 0 +int yydebug; /* nonzero means print parse trace */ +/* Since this is uninitialized, it does not stop multiple parsers + from coexisting. */ +#endif + +/* YYINITDEPTH indicates the initial size of the parser's stacks */ + +#ifndef YYINITDEPTH +#define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH is the maximum size the stacks can grow to + (effective only if the built-in stack extension method is used). */ + +#if YYMAXDEPTH == 0 +#undef YYMAXDEPTH +#endif + +#ifndef YYMAXDEPTH +#define YYMAXDEPTH 10000 +#endif + +/* Prevent warning if -Wstrict-prototypes. */ +#ifdef __GNUC__ +int yyparse (void); +#endif + +/* Define __yy_memcpy. Note that the size argument + should be passed with type unsigned int, because that is what the non-GCC + definitions require. With GCC, __builtin_memcpy takes an arg + of type size_t, but it can handle unsigned int. */ + +#if __GNUC__ > 1 /* GNU C and GNU C++ define this. */ +#define __yy_memcpy(TO,FROM,COUNT) __builtin_memcpy(TO,FROM,COUNT) +#else /* not GNU C or C++ */ +#ifndef __cplusplus + +/* This is the most reliable way to avoid incompatibilities + in available built-in functions on various systems. */ +static void +__yy_memcpy (to, from, count) + char *to; + char *from; + unsigned int count; +{ + register char *f = from; + register char *t = to; + register int i = count; + + while (i-- > 0) + *t++ = *f++; +} + +#else /* __cplusplus */ + +/* This is the most reliable way to avoid incompatibilities + in available built-in functions on various systems. */ +static void +__yy_memcpy (char *to, char *from, unsigned int count) +{ + register char *f = from; + register char *t = to; + register int i = count; + + while (i-- > 0) + *t++ = *f++; +} + +#endif +#endif + +#line 196 "/usr/local/share/bison.simple" + +/* The user can define YYPARSE_PARAM as the name of an argument to be passed + into yyparse. The argument should have type void *. + It should actually point to an object. + Grammar actions can access the variable by casting it + to the proper pointer type. */ + +#ifdef YYPARSE_PARAM +#ifdef __cplusplus +#define YYPARSE_PARAM_ARG void *YYPARSE_PARAM +#define YYPARSE_PARAM_DECL +#else /* not __cplusplus */ +#define YYPARSE_PARAM_ARG YYPARSE_PARAM +#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM; +#endif /* not __cplusplus */ +#else /* not YYPARSE_PARAM */ +#define YYPARSE_PARAM_ARG +#define YYPARSE_PARAM_DECL +#endif /* not YYPARSE_PARAM */ + +int +yyparse(YYPARSE_PARAM_ARG) + YYPARSE_PARAM_DECL +{ + register int yystate; + register int yyn; + register short *yyssp; + register YYSTYPE *yyvsp; + int yyerrstatus; /* number of tokens to shift before error messages enabled */ + int yychar1 = 0; /* lookahead token as an internal (translated) token number */ + + short yyssa[YYINITDEPTH]; /* the state stack */ + YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */ + + short *yyss = yyssa; /* refer to the stacks thru separate pointers */ + YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */ + +#ifdef YYLSP_NEEDED + YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */ + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + +#define YYPOPSTACK (yyvsp--, yyssp--, yylsp--) +#else +#define YYPOPSTACK (yyvsp--, yyssp--) +#endif + + int yystacksize = YYINITDEPTH; + +#ifdef YYPURE + int yychar; + YYSTYPE yylval; + int yynerrs; +#ifdef YYLSP_NEEDED + YYLTYPE yylloc; +#endif +#endif + + YYSTYPE yyval; /* the variable used to return */ + /* semantic values from the action */ + /* routines */ + + int yylen; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Starting parse\n"); +#endif + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss - 1; + yyvsp = yyvs; +#ifdef YYLSP_NEEDED + yylsp = yyls; +#endif + +/* Push a new state, which is found in yystate . */ +/* In all cases, when you get here, the value and location stacks + have just been pushed. so pushing a state here evens the stacks. */ +yynewstate: + + *++yyssp = yystate; + + if (yyssp >= yyss + yystacksize - 1) + { + /* Give user a chance to reallocate the stack */ + /* Use copies of these so that the &'s don't force the real ones into memory. */ + YYSTYPE *yyvs1 = yyvs; + short *yyss1 = yyss; +#ifdef YYLSP_NEEDED + YYLTYPE *yyls1 = yyls; +#endif + + /* Get the current used size of the three stacks, in elements. */ + int size = yyssp - yyss + 1; + +#ifdef yyoverflow + /* Each stack pointer address is followed by the size of + the data in use in that stack, in bytes. */ +#ifdef YYLSP_NEEDED + /* This used to be a conditional around just the two extra args, + but that might be undefined if yyoverflow is a macro. */ + yyoverflow("parser stack overflow", + &yyss1, size * sizeof (*yyssp), + &yyvs1, size * sizeof (*yyvsp), + &yyls1, size * sizeof (*yylsp), + &yystacksize); +#else + yyoverflow("parser stack overflow", + &yyss1, size * sizeof (*yyssp), + &yyvs1, size * sizeof (*yyvsp), + &yystacksize); +#endif + + yyss = yyss1; yyvs = yyvs1; +#ifdef YYLSP_NEEDED + yyls = yyls1; +#endif +#else /* no yyoverflow */ + /* Extend the stack our own way. */ + if (yystacksize >= YYMAXDEPTH) + { + yyerror("parser stack overflow"); + return 2; + } + yystacksize *= 2; + if (yystacksize > YYMAXDEPTH) + yystacksize = YYMAXDEPTH; + yyss = (short *) alloca (yystacksize * sizeof (*yyssp)); + __yy_memcpy ((char *)yyss, (char *)yyss1, + (unsigned int) size * sizeof (*yyssp)); + yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp)); + __yy_memcpy ((char *)yyvs, (char *)yyvs1, + (unsigned int) size * sizeof (*yyvsp)); +#ifdef YYLSP_NEEDED + yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp)); + __yy_memcpy ((char *)yyls, (char *)yyls1, + (unsigned int) size * sizeof (*yylsp)); +#endif +#endif /* no yyoverflow */ + + yyssp = yyss + size - 1; + yyvsp = yyvs + size - 1; +#ifdef YYLSP_NEEDED + yylsp = yyls + size - 1; +#endif + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Stack size increased to %d\n", yystacksize); +#endif + + if (yyssp >= yyss + yystacksize - 1) + YYABORT; + } + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Entering state %d\n", yystate); +#endif + + goto yybackup; + yybackup: + +/* Do appropriate processing given the current state. */ +/* Read a lookahead token if we need one and don't already have one. */ +/* yyresume: */ + + /* First try to decide what to do without reference to lookahead token. */ + + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* yychar is either YYEMPTY or YYEOF + or a valid token in external form. */ + + if (yychar == YYEMPTY) + { +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Reading a token: "); +#endif + yychar = YYLEX; + } + + /* Convert token to internal form (in yychar1) for indexing tables with */ + + if (yychar <= 0) /* This means end of input. */ + { + yychar1 = 0; + yychar = YYEOF; /* Don't call YYLEX any more */ + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Now at end of input.\n"); +#endif + } + else + { + yychar1 = YYTRANSLATE(yychar); + +#if YYDEBUG != 0 + if (yydebug) + { + fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]); + /* Give the individual parser a way to print the precise meaning + of a token, for further debugging info. */ +#ifdef YYPRINT + YYPRINT (stderr, yychar, yylval); +#endif + fprintf (stderr, ")\n"); + } +#endif + } + + yyn += yychar1; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1) + goto yydefault; + + yyn = yytable[yyn]; + + /* yyn is what to do for this token type in this state. + Negative => reduce, -yyn is rule number. + Positive => shift, yyn is new state. + New state is final state => don't bother to shift, + just return success. + 0, or most negative number => error. */ + + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrlab; + + if (yyn == YYFINAL) + YYACCEPT; + + /* Shift the lookahead token. */ + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]); +#endif + + /* Discard the token being shifted unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + *++yyvsp = yylval; +#ifdef YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + /* count tokens shifted since error; after three, turn off error status. */ + if (yyerrstatus) yyerrstatus--; + + yystate = yyn; + goto yynewstate; + +/* Do the default action for the current state. */ +yydefault: + + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + +/* Do a reduction. yyn is the number of a rule to reduce with. */ +yyreduce: + yylen = yyr2[yyn]; + if (yylen > 0) + yyval = yyvsp[1-yylen]; /* implement default value of the action */ + +#if YYDEBUG != 0 + if (yydebug) + { + int i; + + fprintf (stderr, "Reducing via rule %d (line %d), ", + yyn, yyrline[yyn]); + + /* Print the symbols being reduced, and their result. */ + for (i = yyprhs[yyn]; yyrhs[i] > 0; i++) + fprintf (stderr, "%s ", yytname[yyrhs[i]]); + fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]); + } +#endif + + + switch (yyn) { + +case 3: +#line 195 "getdate.y" +{ + yyHaveTime++; + ; + break;} +case 4: +#line 198 "getdate.y" +{ + yyHaveZone++; + ; + break;} +case 5: +#line 201 "getdate.y" +{ + yyHaveDate++; + ; + break;} +case 6: +#line 204 "getdate.y" +{ + yyHaveDay++; + ; + break;} +case 7: +#line 207 "getdate.y" +{ + yyHaveRel++; + ; + break;} +case 9: +#line 213 "getdate.y" +{ + yyHour = yyvsp[-1].Number; + yyMinutes = 0; + yySeconds = 0; + yyMeridian = yyvsp[0].Meridian; + ; + break;} +case 10: +#line 219 "getdate.y" +{ + yyHour = yyvsp[-3].Number; + yyMinutes = yyvsp[-1].Number; + yySeconds = 0; + yyMeridian = yyvsp[0].Meridian; + ; + break;} +case 11: +#line 225 "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)); + ; + break;} +case 12: +#line 234 "getdate.y" +{ + yyHour = yyvsp[-5].Number; + yyMinutes = yyvsp[-3].Number; + yySeconds = yyvsp[-1].Number; + yyMeridian = yyvsp[0].Meridian; + ; + break;} +case 13: +#line 240 "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)); + ; + break;} +case 14: +#line 252 "getdate.y" +{ + yyTimezone = yyvsp[0].Number; + ; + break;} +case 15: +#line 255 "getdate.y" +{ + yyTimezone = yyvsp[0].Number - 60; + ; + break;} +case 16: +#line 259 "getdate.y" +{ + yyTimezone = yyvsp[-1].Number - 60; + ; + break;} +case 17: +#line 264 "getdate.y" +{ + yyDayOrdinal = 1; + yyDayNumber = yyvsp[0].Number; + ; + break;} +case 18: +#line 268 "getdate.y" +{ + yyDayOrdinal = 1; + yyDayNumber = yyvsp[-1].Number; + ; + break;} +case 19: +#line 272 "getdate.y" +{ + yyDayOrdinal = yyvsp[-1].Number; + yyDayNumber = yyvsp[0].Number; + ; + break;} +case 20: +#line 278 "getdate.y" +{ + yyMonth = yyvsp[-2].Number; + yyDay = yyvsp[0].Number; + ; + break;} +case 21: +#line 282 "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; + } + ; + break;} +case 22: +#line 300 "getdate.y" +{ + /* ISO 8601 format. yyyy-mm-dd. */ + yyYear = yyvsp[-2].Number; + yyMonth = -yyvsp[-1].Number; + yyDay = -yyvsp[0].Number; + ; + break;} +case 23: +#line 306 "getdate.y" +{ + /* e.g. 17-JUN-1992. */ + yyDay = yyvsp[-2].Number; + yyMonth = yyvsp[-1].Number; + yyYear = -yyvsp[0].Number; + ; + break;} +case 24: +#line 312 "getdate.y" +{ + yyMonth = yyvsp[-1].Number; + yyDay = yyvsp[0].Number; + ; + break;} +case 25: +#line 316 "getdate.y" +{ + yyMonth = yyvsp[-3].Number; + yyDay = yyvsp[-2].Number; + yyYear = yyvsp[0].Number; + ; + break;} +case 26: +#line 321 "getdate.y" +{ + yyMonth = yyvsp[0].Number; + yyDay = yyvsp[-1].Number; + ; + break;} +case 27: +#line 325 "getdate.y" +{ + yyMonth = yyvsp[-1].Number; + yyDay = yyvsp[-2].Number; + yyYear = yyvsp[0].Number; + ; + break;} +case 28: +#line 332 "getdate.y" +{ + yyRelSeconds = -yyRelSeconds; + yyRelMinutes = -yyRelMinutes; + yyRelHour = -yyRelHour; + yyRelDay = -yyRelDay; + yyRelMonth = -yyRelMonth; + yyRelYear = -yyRelYear; + ; + break;} +case 30: +#line 343 "getdate.y" +{ + yyRelYear += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 31: +#line 346 "getdate.y" +{ + yyRelYear += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 32: +#line 349 "getdate.y" +{ + yyRelYear += yyvsp[0].Number; + ; + break;} +case 33: +#line 352 "getdate.y" +{ + yyRelMonth += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 34: +#line 355 "getdate.y" +{ + yyRelMonth += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 35: +#line 358 "getdate.y" +{ + yyRelMonth += yyvsp[0].Number; + ; + break;} +case 36: +#line 361 "getdate.y" +{ + yyRelDay += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 37: +#line 364 "getdate.y" +{ + yyRelDay += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 38: +#line 367 "getdate.y" +{ + yyRelDay += yyvsp[0].Number; + ; + break;} +case 39: +#line 370 "getdate.y" +{ + yyRelHour += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 40: +#line 373 "getdate.y" +{ + yyRelHour += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 41: +#line 376 "getdate.y" +{ + yyRelHour += yyvsp[0].Number; + ; + break;} +case 42: +#line 379 "getdate.y" +{ + yyRelMinutes += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 43: +#line 382 "getdate.y" +{ + yyRelMinutes += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 44: +#line 385 "getdate.y" +{ + yyRelMinutes += yyvsp[0].Number; + ; + break;} +case 45: +#line 388 "getdate.y" +{ + yyRelSeconds += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 46: +#line 391 "getdate.y" +{ + yyRelSeconds += yyvsp[-1].Number * yyvsp[0].Number; + ; + break;} +case 47: +#line 394 "getdate.y" +{ + yyRelSeconds += yyvsp[0].Number; + ; + break;} +case 48: +#line 400 "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; + } + } + ; + break;} +case 49: +#line 433 "getdate.y" +{ + yyval.Meridian = MER24; + ; + break;} +case 50: +#line 437 "getdate.y" +{ + yyval.Meridian = yyvsp[0].Meridian; + ; + break;} +} + /* the action file gets copied in in place of this dollarsign */ +#line 498 "/usr/local/share/bison.simple" + + yyvsp -= yylen; + yyssp -= yylen; +#ifdef YYLSP_NEEDED + yylsp -= yylen; +#endif + +#if YYDEBUG != 0 + if (yydebug) + { + short *ssp1 = yyss - 1; + fprintf (stderr, "state stack now"); + while (ssp1 != yyssp) + fprintf (stderr, " %d", *++ssp1); + fprintf (stderr, "\n"); + } +#endif + + *++yyvsp = yyval; + +#ifdef YYLSP_NEEDED + yylsp++; + if (yylen == 0) + { + yylsp->first_line = yylloc.first_line; + yylsp->first_column = yylloc.first_column; + yylsp->last_line = (yylsp-1)->last_line; + yylsp->last_column = (yylsp-1)->last_column; + yylsp->text = 0; + } + else + { + yylsp->last_line = (yylsp+yylen-1)->last_line; + yylsp->last_column = (yylsp+yylen-1)->last_column; + } +#endif + + /* 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. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTBASE] + *yyssp; + if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTBASE]; + + goto yynewstate; + +yyerrlab: /* here on detecting error */ + + if (! yyerrstatus) + /* If not already recovering from an error, report this error. */ + { + ++yynerrs; + +#ifdef YYERROR_VERBOSE + yyn = yypact[yystate]; + + if (yyn > YYFLAG && yyn < YYLAST) + { + int size = 0; + char *msg; + int x, count; + + count = 0; + /* Start X at -yyn if nec to avoid negative indexes in yycheck. */ + for (x = (yyn < 0 ? -yyn : 0); + x < (sizeof(yytname) / sizeof(char *)); x++) + if (yycheck[x + yyn] == x) + size += strlen(yytname[x]) + 15, count++; + msg = (char *) malloc(size + 15); + if (msg != 0) + { + strcpy(msg, "parse error"); + + if (count < 5) + { + count = 0; + for (x = (yyn < 0 ? -yyn : 0); + x < (sizeof(yytname) / sizeof(char *)); x++) + if (yycheck[x + yyn] == x) + { + strcat(msg, count == 0 ? ", expecting `" : " or `"); + strcat(msg, yytname[x]); + strcat(msg, "'"); + count++; + } + } + yyerror(msg); + free(msg); + } + else + yyerror ("parse error; also virtual memory exceeded"); + } + else +#endif /* YYERROR_VERBOSE */ + yyerror("parse error"); + } + + goto yyerrlab1; +yyerrlab1: /* here on error raised explicitly by an action */ + + if (yyerrstatus == 3) + { + /* if just tried and failed to reuse lookahead token after an error, discard it. */ + + /* return failure if at end of input */ + if (yychar == YYEOF) + YYABORT; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]); +#endif + + yychar = YYEMPTY; + } + + /* Else will try to reuse lookahead token + after shifting the error token. */ + + yyerrstatus = 3; /* Each real token shifted decrements this */ + + goto yyerrhandle; + +yyerrdefault: /* current state does not do anything special for the error token. */ + +#if 0 + /* This is wrong; only states that explicitly want error tokens + should shift them. */ + yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/ + if (yyn) goto yydefault; +#endif + +yyerrpop: /* pop the current state because it cannot handle the error token */ + + if (yyssp == yyss) YYABORT; + yyvsp--; + yystate = *--yyssp; +#ifdef YYLSP_NEEDED + yylsp--; +#endif + +#if YYDEBUG != 0 + if (yydebug) + { + short *ssp1 = yyss - 1; + fprintf (stderr, "Error: state stack now"); + while (ssp1 != yyssp) + fprintf (stderr, " %d", *++ssp1); + fprintf (stderr, "\n"); + } +#endif + +yyerrhandle: + + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yyerrdefault; + + yyn += YYTERROR; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR) + goto yyerrdefault; + + yyn = yytable[yyn]; + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrpop; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrpop; + + if (yyn == YYFINAL) + YYACCEPT; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Shifting error token, "); +#endif + + *++yyvsp = yylval; +#ifdef YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + yystate = yyn; + goto yynewstate; +} +#line 442 "getdate.y" + + +/* 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 } +}; + +/* 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 } +}; + +/* 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, 2 }, + { "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 } +}; + +/* 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 } +}; + +/* 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 } +}; + + + + +/* ARGSUSED */ +static int +yyerror (s) + char *s; +{ + 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 (*p)) + *p = tolower (*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 (*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 char c; + register char *p; + char buff[20]; + int Count; + int sign; + + for (;;) + { + while (ISSPACE (*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 (a, b) + struct tm *a, *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 (p, now) + 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); + 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; + 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; + 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 = yyTimezone * 60L + difftm (&tm, gmtime (&Start)); + 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..d2d8722 --- /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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* Modified from the original to add stdlib.h and string.h */ + +#ifndef GOT_GETDATE_H +#define GOT_GETDATE_H + +#include +#include +#include + +time_t get_date (const char *p, const time_t *now); + +#endif /* GOT_GETDATE_H */ diff --git a/keys.c b/keys.c new file mode 100644 index 0000000..7f27fd4 --- /dev/null +++ b/keys.c @@ -0,0 +1,243 @@ +/* + $Header: /cvs/src/chrony/keys.c,v 1.11 2003/01/20 22:52:07 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Module for managing keys used for authenticating NTP packets and commands + + */ + +#include +#include +#include + +#include "keys.h" +#include "conf.h" +#include "memory.h" + +typedef struct { + unsigned long id; + char *val; + int len; +} Key; + +#define MAX_KEYS 256 + +static int n_keys; +static Key keys[MAX_KEYS]; + +static int command_key_valid; +static int command_key_pos; +static int cache_valid; +static unsigned long cache_key_id; +static int cache_key_pos; + +/* ================================================== */ + +void +KEY_Initialise(void) +{ + n_keys = 0; + command_key_valid = 0; + cache_valid = 0; + KEY_Reload(); + return; +} + +/* ================================================== */ + +void +KEY_Finalise(void) +{ + /* Nothing to do */ + return; +} + +/* ================================================== */ + +/* 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; + } + +} + +/* ================================================== */ + + +#define KEYLEN 2047 +#define SKEYLEN "2047" + +void +KEY_Reload(void) +{ + int i, len1; + char *key_file; + FILE *in; + unsigned long key_id; + char line[KEYLEN+1], keyval[KEYLEN+1]; + + for (i=0; i= 0) { + *key = keys[command_key_pos].val; + *len = keys[command_key_pos].len; + } else { + *key = ""; + *len = 0; + } +} + +/* ================================================== */ + +int +KEY_GetKey(unsigned long key_id, char **key, int *len) +{ + if (!cache_valid || key_id != cache_key_id) { + cache_valid = 1; + cache_key_pos = lookup_key(key_id); + cache_key_id = key_id; + } + + if (cache_key_pos >= 0) { + *key = keys[cache_key_pos].val; + *len = keys[cache_key_pos].len; + return 1; + } else { + *key = ""; + *len = 0; + return 0; + } +} + +/* ================================================== */ + +int +KEY_KeyKnown(unsigned long key_id) +{ + int position; + + if (cache_valid && (key_id == cache_key_id)) { + return 1; + } else { + + position = lookup_key(key_id); + + if (position >= 0) { + /* Store key in cache, we will probably be using it in a + minute... */ + cache_valid = 1; + cache_key_pos = position; + cache_key_id = key_id; + return 1; + } else { + return 0; + } + } +} diff --git a/keys.h b/keys.h new file mode 100644 index 0000000..9348c08 --- /dev/null +++ b/keys.h @@ -0,0 +1,44 @@ +/* + $Header: /cvs/src/chrony/keys.h,v 1.8 2002/02/28 23:27:10 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header for key management module + */ + +#ifndef GOT_KEYS_H +#define GOT_KEYS_H + +extern void KEY_Initialise(void); +extern void KEY_Finalise(void); + +extern void KEY_Reload(void); + +extern void KEY_CommandKey(char **key, int *len); + +extern int KEY_GetKey(unsigned long key_id, char **key, int *len); +extern int KEY_KeyKnown(unsigned long key_id); + +#endif /* GOT_KEYS_H */ diff --git a/local.c b/local.c new file mode 100644 index 0000000..59c8e07 --- /dev/null +++ b/local.c @@ -0,0 +1,574 @@ +/* + $Header: /cvs/src/chrony/local.c,v 1.20 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 +#include + +#include "local.h" +#include "localp.h" +#include "memory.h" +#include "util.h" +#include "logging.h" + +/* ================================================== */ + +/* Variable to store the current frequency, in ppm */ +static double current_freq_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_ImmediateStepDriver drv_immediate_step; + +/* ================================================== */ + +/* 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; + +/* ================================================== */ + +/* 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 + +static void +calculate_sys_precision(void) +{ + struct timeval tv, old_tv, first_tv; + struct timezone tz; + int dusec, best_dusec; + int iters; + + gettimeofday(&old_tv, &tz); + first_tv = old_tv; + best_dusec = 1000000; /* Assume we must be better than a second */ + iters = 0; + do { + gettimeofday(&tv, &tz); + dusec = 1000000*(tv.tv_sec - old_tv.tv_sec) + (tv.tv_usec - old_tv.tv_usec); + old_tv = tv; + if (dusec > 0) { + if (dusec < best_dusec) { + best_dusec = dusec; + } + iters++; + } + } while (iters < NITERS); + if (!(best_dusec > 0)) { + CROAK("best_dusec should be positive"); + } + precision_log = 0; + while (best_dusec < 500000) { + precision_log--; + best_dusec *= 2; + } + + precision_quantum = 1.0 / (double)(1<<(-precision_log)); + + return; +} + +/* ================================================== */ + +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; + + calculate_sys_precision(); +} + +/* ================================================== */ + +void +LCL_Finalise(void) +{ + return; +} + +/* ================================================== */ + +/* 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; +} + +/* ================================================== */ + +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)) { + CROAK("a handler is already registered"); + } + } + + 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; + + return; +} + +/* ================================================== */ + +/* Remove a handler */ +extern +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; + } + } + + if (!ok) { + CROAK("did not find a matching handler"); + } + + /* Unlink entry from the list */ + ptr->next->prev = ptr->prev; + ptr->prev->next = ptr->next; + + free(ptr); + + return; +} + +/* ================================================== */ + +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)) { + CROAK("a handler is already registered"); + } + } + + 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; + + return; +} + +/* ================================================== */ + +/* 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; + } + } + + if (!ok) { + CROAK("no matching handler found"); + } + + /* Unlink entry from the list */ + ptr->next->prev = ptr->prev; + ptr->prev->next = ptr->next; + + free(ptr); + + return; +} + +/* ================================================== */ +/* At the moment, this is just gettimeofday(), because + I can't think of a Unix system where it would not be */ + +void +LCL_ReadRawTime(struct timeval *result) +{ + struct timezone tz; + + if (!(gettimeofday(result, &tz) >= 0)) { + CROAK("Could not get time of day"); + } + return; + +} + +/* ================================================== */ + +void +LCL_ReadCookedTime(struct timeval *result, double *err) +{ + struct timeval raw; + double correction; + + LCL_ReadRawTime(&raw); + + /* For now, cheat and set the error to zero in all cases. + */ + + *err = 0.0; + + /* Call system specific driver to get correction */ + (*drv_offset_convert)(&raw, &correction); + UTI_AddDoubleToTimeval(&raw, correction, result); + + return; +} + +/* ================================================== */ + +double +LCL_GetOffsetCorrection(struct timeval *raw) +{ + double correction; + (*drv_offset_convert)(raw, &correction); + return correction; +} + +/* ================================================== */ +/* This is just a simple passthrough of the system specific routine */ + +double +LCL_ReadAbsoluteFrequency(void) +{ + return (*drv_read_freq)(); +} + +/* ================================================== */ +/* 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) +{ + ChangeListEntry *ptr; + struct timeval raw, cooked; + double correction; + double dfreq; + + /* Call the system-specific driver for setting the frequency */ + + (*drv_set_freq)(afreq_ppm); + + dfreq = 1.0e-6 * (afreq_ppm - current_freq_ppm) / (1.0 - 1.0e-6 * current_freq_ppm); + + LCL_ReadRawTime(&raw); + (drv_offset_convert)(&raw, &correction); + UTI_AddDoubleToTimeval(&raw, correction, &cooked); + + /* Dispatch to all handlers */ + for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) { + (ptr->handler)(&raw, &cooked, dfreq, afreq_ppm, 0.0, 0, ptr->anything); + } + + current_freq_ppm = afreq_ppm; + +} + +/* ================================================== */ + +void +LCL_AccumulateDeltaFrequency(double dfreq) +{ + ChangeListEntry *ptr; + struct timeval raw, cooked; + double correction; + + /* 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 = (1.0 - dfreq) * current_freq_ppm + + (1.0e6 * dfreq); + + /* Call the system-specific driver for setting the frequency */ + (*drv_set_freq)(current_freq_ppm); + + LCL_ReadRawTime(&raw); + (drv_offset_convert)(&raw, &correction); + UTI_AddDoubleToTimeval(&raw, correction, &cooked); + + /* Dispatch to all handlers */ + for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) { + (ptr->handler)(&raw, &cooked, dfreq, current_freq_ppm, 0.0, 0, ptr->anything); + } + +} + +/* ================================================== */ + +void +LCL_AccumulateOffset(double offset) +{ + ChangeListEntry *ptr; + struct timeval raw, cooked; + double correction; + + /* 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); + (drv_offset_convert)(&raw, &correction); + UTI_AddDoubleToTimeval(&raw, correction, &cooked); + + (*drv_accrue_offset)(offset); + + /* Dispatch to all handlers */ + for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) { + (ptr->handler)(&raw, &cooked, 0.0, current_freq_ppm, offset, 0, ptr->anything); + } + +} + +/* ================================================== */ + +void +LCL_ApplyStepOffset(double offset) +{ + ChangeListEntry *ptr; + struct timeval raw, cooked; + double correction; + + /* 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); + (drv_offset_convert)(&raw, &correction); + UTI_AddDoubleToTimeval(&raw, correction, &cooked); + + (*drv_apply_step_offset)(offset); + + /* Dispatch to all handlers */ + for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) { + (ptr->handler)(&raw, &cooked, 0.0, current_freq_ppm, offset, 1, ptr->anything); + } + +} + +/* ================================================== */ + +void +LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset) +{ + ChangeListEntry *ptr; + struct timeval raw, cooked; + double correction; + double old_freq_ppm; + + LCL_ReadRawTime(&raw); + (drv_offset_convert)(&raw, &correction); + /* Due to modifying the offset, this has to be the cooked time prior + to the change we are about to make */ + UTI_AddDoubleToTimeval(&raw, correction, &cooked); + + 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 = (1.0 - dfreq) * old_freq_ppm + + (1.0e6 * dfreq); + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_Local, "old_freq=%.3fppm new_freq=%.3fppm offset=%.6fsec", + old_freq_ppm, current_freq_ppm, doffset); +#endif + + /* Call the system-specific driver for setting the frequency */ + (*drv_set_freq)(current_freq_ppm); + (*drv_accrue_offset)(doffset); + + /* Dispatch to all handlers */ + for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) { + (ptr->handler)(&raw, &cooked, dfreq, current_freq_ppm, doffset, 0, ptr->anything); + } + + +} + +/* ================================================== */ + +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_ImmediateStepDriver immediate_step) +{ + 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_immediate_step = immediate_step; + + current_freq_ppm = (*drv_read_freq)(); + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_Local, "Local freq=%.3fppm", current_freq_ppm); +#endif + + return; +} + +/* ================================================== */ +/* Look at the current difference between the system time and the NTP + time, and make a step to cancel it. */ + +int +LCL_MakeStep(void) +{ + if (drv_immediate_step) { + (drv_immediate_step)(); +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_Local, "Made step to system time to apply remaining slew"); +#endif + return 1; + } + + return 0; +} + +/* ================================================== */ diff --git a/local.h b/local.h new file mode 100644 index 0000000..857d8b7 --- /dev/null +++ b/local.h @@ -0,0 +1,184 @@ +/* + $Header: /cvs/src/chrony/local.h,v 1.16 2002/02/28 23:27:10 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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. This is analogous to gettimeofday(), + but with the timezone information ignored */ +extern void LCL_ReadRawTime(struct timeval *); + +/* 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 timeval *t, double *err); + +/* Read the current offset between the system clock and true time + (i.e. 'cooked' - 'raw') (in seconds). Only intended for use in + status reporting, really. */ + +extern double LCL_GetOffsetCorrection(struct timeval *raw); + +/* 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) + + afreq : absolute frequency relative to uncompensated system (in + terms of ppm seconds gained by system clock per unit of the + uncalibrated system clock) + + doffset : delta offset applied (positive => make local system fast + by that amount, negative => make it slow by that amount) + + is_step_change : true if change is being applied as a jump (using + settimeofday rather than adjtime) + + anything : Passthrough argument from call to registration routine */ + + +typedef void (*LCL_ParameterChangeHandler) + (struct timeval *raw, struct timeval *cooked, + double dfreq, double afreq_ppm, + double doffset, int is_step_change, + 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); + +/* 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). */ + +extern void LCL_AccumulateOffset(double offset); + +/* 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 void LCL_ApplyStepOffset(double offset); + +/* Perform the combination of modifying the frequency and applying + a slew, in one easy step */ +extern void LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset); + +/* 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 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); + +#endif /* GOT_LOCAL_H */ diff --git a/localp.h b/localp.h new file mode 100644 index 0000000..3e01a33 --- /dev/null +++ b/localp.h @@ -0,0 +1,73 @@ +/* + $Header: /cvs/src/chrony/localp.h,v 1.9 2002/02/28 23:27:10 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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. */ +typedef void (*lcl_SetFrequencyDriver)(double freq_ppm); + +/* System driver to accrue an offset. A positive argument means slew + the clock forwards. */ +typedef void (*lcl_AccrueOffsetDriver)(double offset); + +/* System driver to apply a step offset. A positive argument means step + the clock forwards. */ +typedef void (*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 timeval *raw, double *corr); + +/* System driver to stop slewing the current offset and to apply is + as an immediate step instead */ +typedef void (*lcl_ImmediateStepDriver)(void); + +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_ImmediateStepDriver immediate_step_driver); + +#endif /* GOT_LOCALP_H */ diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..974c7df --- /dev/null +++ b/logging.c @@ -0,0 +1,216 @@ +/* + $Header: /cvs/src/chrony/logging.c,v 1.13 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Module to handle logging of diagnostic information + */ + +#include "sysincl.h" + +#include "main.h" +#include "logging.h" +#include "version.h" + +/* ================================================== */ +/* Flag indicating we have initialised */ +static int initialised = 0; + +static int is_detached = 0; + +#ifdef WINNT +static FILE *logfile; +#endif + +/* ================================================== */ +/* Init function */ + +void +LOG_Initialise(void) +{ + initialised = 1; + +#ifdef WINNT + logfile = fopen("./chronyd.err", "a"); +#endif + + return; +} + +/* ================================================== */ +/* Fini function */ + +void +LOG_Finalise(void) +{ +#ifdef WINNT + if (logfile) { + fclose(logfile); + } +#else + if (is_detached) { + closelog(); + } +#endif + + initialised = 0; + return; +} + +/* ================================================== */ + +void +LOG_Line_Function(LOG_Severity severity, LOG_Facility facility, const char *format, ...) +{ + char buf[2048]; + va_list other_args; + va_start(other_args, format); + vsprintf(buf, format, other_args); + va_end(other_args); +#ifdef WINNT + if (logfile) { + fprintf(logfile, "%s\n", buf); + } +#else + if (is_detached) { + switch (severity) { + case LOGS_INFO: + syslog(LOG_INFO, "%s", buf); + break; + case LOGS_WARN: + syslog(LOG_WARNING, "%s", buf); + break; + case LOGS_ERR: + default: + syslog(LOG_ERR, "%s", buf); + break; + } + } else { + fprintf(stderr, "%s\n", buf); + } +#endif + return; +} + +/* ================================================== */ + +volatile void +LOG_Fatal_Function(LOG_Facility facility, const char *format, ...) +{ + char buf[2048]; + va_list other_args; + va_start(other_args, format); + vsprintf(buf, format, other_args); + va_end(other_args); + +#ifdef WINNT + if (logfile) { + fprintf(logfile, "Fatal error : %s\n", buf); + } +#else + if (is_detached) { + syslog(LOG_CRIT, "Fatal error : %s", buf); + } else { + fprintf(stderr, "Fatal error : %s\n", buf); + } +#endif + + MAI_CleanupAndExit(); + + return; +} + +/* ================================================== */ + +void +LOG_Position(const char *filename, int line_number, const char *function_name) +{ +#ifdef WINNT +#else + time_t t; + struct tm stm; + char buf[64]; + if (!is_detached) { + /* Don't clutter up syslog with internal debugging info */ + time(&t); + stm = *gmtime(&t); + strftime(buf, sizeof(buf), "%d-%H:%M:%S", &stm); + fprintf(stderr, "%s:%d:(%s)[%s] ", filename, line_number, function_name, buf); + } +#endif + return; +} + +/* ================================================== */ + +void +LOG_GoDaemon(void) +{ +#ifdef WINNT + + +#else + + int pid, fd; + + /* Does this preserve existing signal handlers? */ + pid = fork(); + + if (pid < 0) { + LOG(LOGS_ERR, LOGF_Logging, "Could not detach, fork failed : %s", strerror(errno)); + } else if (pid > 0) { + exit(0); /* In the 'grandparent' */ + } else { + + setsid(); + + /* Do 2nd fork, as-per recommended practice for launching daemons. */ + pid = fork(); + + if (pid < 0) { + LOG(LOGS_ERR, LOGF_Logging, "Could not detach, 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 */ + + /* Don't keep stdin/out/err from before. */ + for (fd=0; fd<1024; fd++) { + close(fd); + } + + is_detached = 1; + + openlog("chronyd", LOG_PID, LOG_DAEMON); + + LOG(LOGS_INFO, LOGF_Logging, "chronyd version %s starting", PROGRAM_VERSION_STRING); + + } + } + +#endif +} + +/* ================================================== */ diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..33a792d --- /dev/null +++ b/logging.h @@ -0,0 +1,97 @@ +/* + $Header: /cvs/src/chrony/logging.h,v 1.15 2002/02/28 23:27:10 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for diagnostic logging module + + */ + +#ifndef GOT_LOGGING_H +#define GOT_LOGGING_H + +/* Definition of severity */ +typedef enum { + LOGS_INFO, + LOGS_WARN, + LOGS_ERR +} LOG_Severity; + +/* Definition of facility. Each message is tagged with who generated + it, so that the user can customise what level of reporting he gets + for each area of the software */ +typedef enum { + LOGF_Reference, + LOGF_NtpIO, + LOGF_NtpCore, + LOGF_NtpSources, + LOGF_Scheduler, + LOGF_SourceStats, + LOGF_Sources, + LOGF_Local, + LOGF_Util, + LOGF_Main, + LOGF_Configure, + LOGF_CmdMon, + LOGF_Acquire, + LOGF_Manual, + LOGF_Logging, + LOGF_Rtc, + LOGF_Regress, + LOGF_SysLinux, + LOGF_SysSolaris, + LOGF_SysSunOS, + LOGF_SysWinnt, + LOGF_RtcLinux +} LOG_Facility; + +/* Init function */ +extern void LOG_Initialise(void); + +/* Fini function */ +extern void LOG_Finalise(void); + +/* Line logging function */ +extern void LOG_Line_Function(LOG_Severity severity, LOG_Facility facility, const char *format, ...); + +/* Logging function for fatal errors */ +extern volatile void LOG_Fatal_Function(LOG_Facility facility, const char *format, ...); + +/* Position in code reporting function */ +extern void LOG_Position(const char *filename, int line_number, const char *function_name); + +extern void LOG_GoDaemon(void); + +/* Line logging macro. If the compiler is GNU C, we take advantage of + being able to get the function name also. */ +#if defined(__GNUC__) +#define LOG LOG_Position(__FILE__, __LINE__, __FUNCTION__); LOG_Line_Function +#define LOG_FATAL LOG_Position(__FILE__, __LINE__, __FUNCTION__); LOG_Fatal_Function +#else +#define LOG LOG_Position(__FILE__, __LINE__, ""); LOG_Line_Function +#define LOG_FATAL LOG_Position(__FILE__, __LINE__, ""); LOG_Fatal_Function +#endif /* defined (__GNUC__) */ + +#endif /* GOT_LOGGING_H */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..5424d1d --- /dev/null +++ b/main.c @@ -0,0 +1,311 @@ +/* + $Header: /cvs/src/chrony/main.c,v 1.30 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + The main program + */ + +#include "sysincl.h" + +#include "main.h" +#include "sched.h" +#include "local.h" +#include "sys.h" +#include "ntp_io.h" +#include "ntp_sources.h" +#include "ntp_core.h" +#include "sources.h" +#include "sourcestats.h" +#include "reference.h" +#include "logging.h" +#include "conf.h" +#include "cmdmon.h" +#include "keys.h" +#include "acquire.h" +#include "manual.h" +#include "version.h" +#include "rtc.h" +#include "clientlog.h" +#include "broadcast.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 reload = 0; + +/* ================================================== */ + +static void +delete_pidfile(void) +{ + const char *pidfile = CNF_GetPidFile(); + /* Don't care if this fails, there's not a lot we can do */ + unlink(pidfile); +} + +/* ================================================== */ + +volatile void +MAI_CleanupAndExit(void) +{ + if (!initialised) exit(0); + + if (CNF_GetDumpOnExit()) { + SRC_DumpSources(); + } + + RTC_Finalise(); + MNL_Finalise(); + ACQ_Finalise(); + CAM_Finalise(); + KEY_Finalise(); + CLG_Finalise(); + NIO_Finalise(); + NSR_Finalise(); + NCR_Finalise(); + BRD_Finalise(); + SRC_Finalise(); + SST_Finalise(); + REF_Finalise(); + SYS_Finalise(); + SCH_Finalise(); + LCL_Finalise(); + + delete_pidfile(); + + LOG_Finalise(); + + exit(0); +} + +/* ================================================== */ + +static void +signal_cleanup(int x) +{ + LOG(LOGS_WARN, LOGF_Main, "chronyd exiting on signal"); + MAI_CleanupAndExit(); +} + +/* ================================================== */ + +static void +post_acquire_hook(void *anything) +{ + + CNF_AddSources(); + CNF_AddBroadcasts(); + 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(); + } + CNF_SetupAccessRestrictions(); + + RTC_StartMeasurements(); +} + +/* ================================================== */ + +static void +post_init_rtc_hook(void *anything) +{ + CNF_ProcessInitStepSlew(post_acquire_hook, NULL); +} + +/* ================================================== */ +/* Return 1 if the process exists on the system. */ + +static int +does_process_exist(int pid) +{ + int status; + status = getsid(pid); + if (status >= 0) { + return 1; + } else { + return 0; + } +} + +/* ================================================== */ + +static int +maybe_another_chronyd_running(int *other_pid) +{ + const char *pidfile = CNF_GetPidFile(); + FILE *in; + int pid, count; + + *other_pid = 0; + + in = fopen(pidfile, "r"); + if (!in) return 0; + + count = fscanf(in, "%d", &pid); + fclose(in); + + if (count != 1) return 0; + + *other_pid = pid; + return does_process_exist(pid); + +} + +/* ================================================== */ + +static void +write_lockfile(void) +{ + const char *pidfile = CNF_GetPidFile(); + FILE *out; + + out = fopen(pidfile, "w"); + if (!out) { + LOG(LOGS_ERR, LOGF_Main, "could not open lockfile %s for writing", pidfile); + } else { + fprintf(out, "%d\n", getpid()); + fclose(out); + } +} + +/* ================================================== */ + +int main +(int argc, char **argv) +{ + char *conf_file = NULL; + int debug = 0; + int do_init_rtc = 0; + int other_pid; + + LOG_Initialise(); + + /* Parse command line options */ + while (++argv, (--argc)>0) { + + if (!strcmp("-f", *argv)) { + ++argv, --argc; + conf_file = *argv; + } else if (!strcmp("-r", *argv)) { + reload = 1; + } else if (!strcmp("-s", *argv)) { + do_init_rtc = 1; + } else if (!strcmp("-v", *argv) || !strcmp("--version",*argv)) { + /* This write to the terminal is OK, it comes before we turn into a daemon */ + printf("chronyd (chrony) version %s\n", PROGRAM_VERSION_STRING); + exit(0); + } else if (!strcmp("-d", *argv)) { + debug = 1; + } else { + LOG(LOGS_WARN, LOGF_Main, "Unrecognized command line option [%s]", *argv); + } + } + +#ifndef SYS_WINNT + if (getuid() != 0) { + /* This write to the terminal is OK, it comes before we turn into a daemon */ + fprintf(stderr,"Not superuser\n"); + exit(1); + } + + + /* Turn into a daemon */ + if (!debug) { + LOG_GoDaemon(); + } + + /* Check whether another chronyd may already be running. Do this after + * forking, so that message logging goes to the right place (i.e. syslog), in + * case this chronyd is being run from a boot script. */ + if (maybe_another_chronyd_running(&other_pid)) { + LOG_FATAL(LOGF_Main, "Another chronyd may already be running (pid=%d), check lockfile (%s)", + other_pid, CNF_GetPidFile()); + exit(1); + } + + /* Write our lockfile to prevent other chronyds running. This has *GOT* to + * be done *AFTER* the daemon-creation fork() */ + write_lockfile(); +#endif + + CNF_ReadFile(conf_file); + + if (do_init_rtc) { + RTC_TimePreInit(); + } + + LCL_Initialise(); + SCH_Initialise(); + SYS_Initialise(); + REF_Initialise(); + SST_Initialise(); + SRC_Initialise(); + BRD_Initialise(); + NCR_Initialise(); + NSR_Initialise(); + NIO_Initialise(); + CLG_Initialise(); + KEY_Initialise(); + CAM_Initialise(); + ACQ_Initialise(); + MNL_Initialise(); + RTC_Initialise(); + + /* From now on, it is safe to do finalisation on exit */ + initialised = 1; + + if (do_init_rtc) { + RTC_TimeInit(post_init_rtc_hook, NULL); + } else { + post_init_rtc_hook(NULL); + } + + signal(SIGINT, signal_cleanup); + signal(SIGTERM, signal_cleanup); +#if !defined(WINNT) + signal(SIGQUIT, signal_cleanup); + signal(SIGHUP, signal_cleanup); +#endif /* WINNT */ + + /* The program normally runs under control of the main loop in + the scheduler. */ + SCH_MainLoop(); + + MAI_CleanupAndExit(); + + return 0; +} + +/* ================================================== */ diff --git a/main.h b/main.h new file mode 100644 index 0000000..aaf071a --- /dev/null +++ b/main.h @@ -0,0 +1,39 @@ +/* + $Header: /cvs/src/chrony/main.h,v 1.8 2002/02/28 23:27:10 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for main routine + */ + +#ifndef GOT_MAIN_H +#define GOT_MAIN_H + +/* Function to clean up at end of run */ +extern volatile void MAI_CleanupAndExit(void); + +#endif /* GOT_MAIN_H */ + + diff --git a/manual.c b/manual.c new file mode 100644 index 0000000..83f7ceb --- /dev/null +++ b/manual.c @@ -0,0 +1,331 @@ +/* + $Header: /cvs/src/chrony/manual.c,v 1.20 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 + +#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 timeval 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 MAX_SAMPLES 16 + +static Sample samples[16]; +static int n_samples; + +static int replace_margin; +static int error; + +/* Eventually these constants need to be user-defined in conf file */ +#define REPLACE_MARGIN 300 +#define ERROR_MARGIN 0.2 + +/* ================================================== */ + +static void +slew_samples(struct timeval *raw, + struct timeval *cooked, + double dfreq, + double afreq, + double doffset, + int is_step_change, + void *not_used); + +/* ================================================== */ + +void +MNL_Initialise(void) +{ + if (CNF_GetManualEnabled()) { + enabled = 1; + } else { + enabled = 0; + } + + n_samples = 0; + + replace_margin = REPLACE_MARGIN; + error = ERROR_MARGIN; + + LCL_AddParameterChangeHandler(slew_samples, NULL); + + return; +} + +/* ================================================== */ + +void +MNL_Finalise(void) +{ + return; +} + +/* ================================================== */ + +static void +estimate_and_set_system(struct timeval *now, int offset_provided, double offset, long *offset_cs, 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; + + if (n_samples > 1) { + for (i=0; i max) { + *n = max; + } else { + *n = n_samples; + } + + for (i=0; i= 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, &local_clock_err); + 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..86045ee --- /dev/null +++ b/manual.h @@ -0,0 +1,49 @@ +/* + $Header: /cvs/src/chrony/manual.h,v 1.12 2002/02/28 23:27:11 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 timeval *ts, long *offset_cs, double *dfreq_ppm, double *new_afreq_ppm); + +extern void MNL_Enable(void); +extern void MNL_Disable(void); +extern void MNL_Reset(void); + +extern void MNL_ReportSamples(RPT_ManualSamplesReport *report, int max, int *n); +extern int MNL_DeleteSample(int index); + +#endif /* GOT_MANUAL_H */ diff --git a/md5.c b/md5.c new file mode 100644 index 0000000..f997a6e --- /dev/null +++ b/md5.c @@ -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) ******************************** + */ diff --git a/md5.h b/md5.h new file mode 100644 index 0000000..aaf9548 --- /dev/null +++ b/md5.h @@ -0,0 +1,60 @@ +/* + *********************************************************************** + ** 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. ** + *********************************************************************** + */ + +#ifdef HAS_STDINT_H +#include +#elif defined(HAS_INTTYPES_H) +#include +#endif + +/* 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.h b/memory.h new file mode 100644 index 0000000..bf4d48d --- /dev/null +++ b/memory.h @@ -0,0 +1,43 @@ +/* + $Header: /cvs/src/chrony/memory.h,v 1.7 2002/02/28 23:27:11 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for memory functions + */ + +#ifndef GOT_MEMORY_H +#define GOT_MEMORY_H + +#include + +#define Malloc(x) malloc(x) +#define MallocNew(T) ((T *) malloc(sizeof(T))) +#define MallocArray(T, n) ((T *) malloc((n) * sizeof(T))) +#define Realloc(x,y) realloc(x,y) +#define ReallocArray(T,n,x) ((T *) realloc((void *)(x), (n)*sizeof(T))) +#define Free(x) free(x) + +#endif /* GOT_MEMORY_H */ diff --git a/mkdirpp.c b/mkdirpp.c new file mode 100644 index 0000000..5e6af7d --- /dev/null +++ b/mkdirpp.c @@ -0,0 +1,135 @@ +/* + $Header: /cvs/src/chrony/mkdirpp.c,v 1.10 2002/11/03 22:49:17 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + A function for creating a directory and any parent directories that + don't exist. + + */ + +#include "sysincl.h" + +#include "mkdirpp.h" + +static int +do_dir(char *p) +{ + int status; + struct stat buf; + +#if defined(TEST) + fprintf(stderr, "do_dir(%s)\n", p); +#endif + + /* See if directory exists */ + status = stat(p, &buf); + + if (status < 0) { + if (errno == ENOENT) { + /* Try to create directory */ + status = mkdir(p, 0755); + return status; + } else { + return status; + } + } + + if (!S_ISDIR(buf.st_mode)) { + return -1; + } + + return 0; +} + +/* ================================================== */ +/* Return 0 if the directory couldn't be created, 1 if it could (or + already existed) */ + +int +mkdir_and_parents(const char *path) +{ + char *p; + int len; + int i, j, k, last; + len = strlen(path); + + p = (char *) malloc(1 + len); + + i = k = 0; + while (1) { + p[i++] = path[k++]; + + if (path[k] == '/' || !path[k]) { + p[i] = 0; + + if (do_dir(p) < 0) { + return 0; + } + + if (!path[k]) { + /* End of the string */ + break; + } + + /* check whether its a trailing / or group of / */ + last = 1; + j = k+1; + while (path[j]) { + if (path[j] != '/') { + k = j - 1; /* Pick up a / into p[] thru the assignment at the top of the loop */ + last = 0; + break; + } + j++; + } + + if (last) { + break; + } + } + + if (!path[k]) break; + + } + + free(p); + return 1; + +} + +/* ================================================== */ + +#if defined(TEST) +int main(int argc, char **argv) { + if (argc > 1) { + /* Invert sense of result */ + return mkdir_and_parents(argv[1]) ? 0 : 1; + } else { + return 1; + } +} +#endif + diff --git a/mkdirpp.h b/mkdirpp.h new file mode 100644 index 0000000..632a7da --- /dev/null +++ b/mkdirpp.h @@ -0,0 +1,35 @@ +/* + $Header: /cvs/src/chrony/mkdirpp.h,v 1.6 2002/02/28 23:27:11 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + */ + +#ifndef GOT_MKDIRPP_H +#define GOT_MKDIRPP_H + +extern int mkdir_and_parents(const char *path); + +#endif diff --git a/nameserv.c b/nameserv.c new file mode 100644 index 0000000..6a4d6e9 --- /dev/null +++ b/nameserv.c @@ -0,0 +1,92 @@ +/* + $Header: /cvs/src/chrony/nameserv.c,v 1.13 2002/02/28 23:27:11 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Functions to do name to IP address conversion + + */ + +#include "sysincl.h" + +#include "nameserv.h" + +/* ================================================== */ + +unsigned long +DNS_Name2IPAddress(const char *name) +{ + struct hostent *host; + unsigned char *address0; + unsigned long result; + + host = gethostbyname(name); + if (host == NULL) { + result = DNS_Failed_Address; + } else { + address0 = host->h_addr_list[0]; + result = ((((unsigned long)address0[0])<<24) | + (((unsigned long)address0[1])<<16) | + (((unsigned long)address0[2])<<8) | + (((unsigned long)address0[3]))); + } + + return result; + +} + +/* ================================================== */ + +const char * +DNS_IPAddress2Name(unsigned long ip_addr) +{ + struct hostent *host; + static char buffer[16]; + unsigned int a, b, c, d; + unsigned long addr; + + addr = htonl(ip_addr); + if (addr == 0UL) { + /* Catch this as a special case that will never resolve to + anything */ + strcpy(buffer, "0.0.0.0"); + return buffer; + } else { + host = gethostbyaddr((const char *) &addr, sizeof(ip_addr), AF_INET); + if (!host) { + a = (ip_addr >> 24) & 0xff; + b = (ip_addr >> 16) & 0xff; + c = (ip_addr >> 8) & 0xff; + d = (ip_addr) & 0xff; + sprintf(buffer, "%u.%u.%u.%u", a, b, c, d); + return buffer; + } else { + return host->h_name; + } + } +} + +/* ================================================== */ + diff --git a/nameserv.h b/nameserv.h new file mode 100644 index 0000000..e62f334 --- /dev/null +++ b/nameserv.h @@ -0,0 +1,42 @@ +/* + $Header: /cvs/src/chrony/nameserv.h,v 1.8 2002/02/28 23:27:11 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Module header for nameserver functions + */ + + +#ifndef GOT_NAMESERV_H +#define GOT_NAMESERV_H + +static const unsigned long DNS_Failed_Address = 0x0UL; + +extern unsigned long DNS_Name2IPAddress(const char *name); + +const char *DNS_IPAddress2Name(unsigned long ip_addr); + +#endif /* GOT_NAMESERV_H */ + diff --git a/ntp.h b/ntp.h new file mode 100644 index 0000000..b0e0bb9 --- /dev/null +++ b/ntp.h @@ -0,0 +1,116 @@ +/* + $Header: /cvs/src/chrony/ntp.h,v 1.11 2003/04/10 21:28:11 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file containing common NTP bits and pieces + */ + +#ifndef GOT_NTP_H +#define GOT_NTP_H + +#ifdef HAS_STDINT_H +#include +#elif defined(HAS_INTTYPES_H) +#include +#endif + +typedef struct { + uint32_t hi; + uint32_t lo; +} NTP_int64; + +typedef uint32_t NTP_int32; + +#define AUTH_DATA_LEN 16 + +/* 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; + NTP_int32 auth_keyid; + uint8_t auth_data[AUTH_DATA_LEN]; +} NTP_Packet; + +/* We have to declare a buffer type to hold a datagram read from the + network. Even though we won't be using them (yet?!), this must be + large enough to hold NTP control messages. */ + +/* Define the maximum number of bytes that can be read in a single + message. (This is cribbed from ntp.h in the xntpd source code). */ + +#define MAX_NTP_MESSAGE_SIZE (468+12+16+4) + +typedef union { + NTP_Packet ntp_pkt; + uint8_t arbitrary[MAX_NTP_MESSAGE_SIZE]; +} ReceiveBuffer; + +#define NTP_NORMAL_PACKET_SIZE (sizeof(NTP_Packet) - (sizeof(NTP_int32) + AUTH_DATA_LEN)) + +/* ================================================== */ + +inline static double +int32_to_double(NTP_int32 x) +{ + return (double) ntohl(x) / 65536.0; +} + +/* ================================================== */ + +inline static NTP_int32 +double_to_int32(double x) +{ + return htonl((NTP_int32)(0.5 + 65536.0 * x)); +} + +/* ================================================== */ + +#endif /* GOT_NTP_H */ diff --git a/ntp_core.c b/ntp_core.c new file mode 100644 index 0000000..6d5852a --- /dev/null +++ b/ntp_core.c @@ -0,0 +1,1867 @@ +/* + $Header: /cvs/src/chrony/ntp_core.c,v 1.49 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Core NTP protocol engine + */ + +#include "sysincl.h" + +#include "ntp_core.h" +#include "ntp_io.h" +#include "memory.h" +#include "sched.h" +#include "reference.h" +#include "local.h" +#include "sources.h" +#include "util.h" +#include "conf.h" +#include "logging.h" +#include "keys.h" +#include "md5.h" +#include "addrfilt.h" +#include "mkdirpp.h" +#include "clientlog.h" + +/* ================================================== */ +/* File handle for file to which we write the measurement log */ +static FILE *logfile = NULL; + +static char *logfilename = NULL; +static unsigned long logwrites=0; + +#define MEASUREMENTS_LOG "measurements.log" + +/* ================================================== */ + +/* Day number of 1 Jan 1970 */ +#define MJD_1970 40587 + +/* ================================================== */ + +#define ZONE_WIDTH 4 + +/* ================================================== */ +/* 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_Mode mode; /* The source's NTP mode + (client/server or symmetric active peer) */ + OperatingMode opmode; /* Whether we are sampling this source + or not and in what way */ + int timer_running; /* Boolean indicating whether we have a timeout + pending to transmit to the source */ + SCH_TimeoutID timeout_id; /* Scheduler's timeout ID, if we are + running on a timer. */ + + int auto_offline; /* If 1, automatically go offline if server/peer + isn't responding */ + + 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 presend_minpoll; /* If the current polling interval is + at least this, an echo datagram + will be send some time before every + 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 */ + + 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 delta / + min_delay_in_register that we can + tolerate. */ + + int do_auth; /* Flag indicating whether we + authenticate packets we send to + this machine (if it's serving us or + the association is symmetric). Note + : we don't authenticate if we can't + find the key in our database. */ + unsigned long auth_key_id; /* The ID of the authentication key to + use. */ + + /* Count of how many packets we have transmitted since last successful + receive from this peer */ + int tx_count; + + /* Timestamp in tx field of last received packet. We have to + reproduce this exactly as the orig field or our outgoing + packet. */ + NTP_int64 remote_orig; + + /* Local timestamp when the last packet 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). */ + struct timeval 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 timeval 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; + struct timeval local_tx; + + /* The instance record in the main source management module. This + performs the statistical analysis on the samples we generate */ + + SRC_Instance source; + + int score; + + int burst_good_samples_to_go; + int burst_total_samples_to_go; + +}; + +/* ================================================== */ +/* Initial delay period before first packet is transmitted (in seconds) */ +#define INITIAL_DELAY 2.0 + +/* Spacing required between samples for any two servers/peers (to + minimise risk of network collisions) (in seconds) */ +#define SAMPLING_SEPARATION 2.0 + +/* Time to wait before retransmitting in burst mode, if we did not get + a reply to the previous probe */ +#define BURST_TIMEOUT 8.0 + +/* The NTP protocol version that we support */ +#define NTP_VERSION 3 + +/* Maximum allowed dispersion - as defined in RFC1305 (16 seconds) */ +#define NTP_MAX_DISPERSION 16.0 + +/* Maximum allowed age of a reference to be supplied to a client (1 day) */ +#define NTP_MAXAGE 86400 + +/* Maximum allowed stratum */ +#define NTP_MAX_STRATUM 15 + +/* ================================================== */ + +static ADF_AuthTable access_auth_table; + +static int md5_offset_usecs; + +/* ================================================== */ +/* Forward prototypes */ + +static void transmit_timeout(void *arg); +static void determine_md5_delay(void); + +/* ================================================== */ + +void +NCR_Initialise(void) +{ + char *direc; + + if (CNF_GetLogMeasurements()) { + direc = CNF_GetLogDir(); + if (!mkdir_and_parents(direc)) { + LOG(LOGS_ERR, LOGF_NtpCore, "Could not create directory %s", direc); + logfile = NULL; + } else { + logfilename = MallocArray(char, 2 + strlen(direc) + strlen(MEASUREMENTS_LOG)); + strcpy(logfilename, direc); + strcat(logfilename, "/"); + strcat(logfilename, MEASUREMENTS_LOG); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_NtpCore, "Couldn't open logfile %s for update", logfilename); + } + } + } + + access_auth_table = ADF_CreateTable(); + + determine_md5_delay(); + +} + +/* ================================================== */ + +void +NCR_Finalise(void) +{ + if (logfile) { + fclose(logfile); + } + + ADF_DestroyTable(access_auth_table); + +} + +/* ================================================== */ + +static void +start_initial_timeout(NCR_Instance inst) +{ + + /* Start timer for first transmission */ + inst->timeout_id = SCH_AddTimeoutInClass(INITIAL_DELAY, SAMPLING_SEPARATION, + SCH_NtpSamplingClass, + transmit_timeout, (void *)inst); + inst->timer_running = 1; + + return; +} + +/* ================================================== */ + +static NCR_Instance +create_instance(NTP_Remote_Address *remote_addr, NTP_Mode mode, SourceParameters *params) +{ + NCR_Instance result; + + result = MallocNew(struct NCR_Instance_Record); + + result->remote_addr = *remote_addr; + result->mode = mode; + + result->minpoll = params->minpoll; + result->maxpoll = params->maxpoll; + + result->presend_minpoll = params->presend_minpoll; + result->presend_done = 0; + + if (params->authkey == INACTIVE_AUTHKEY) { + result->do_auth = 0; + result->auth_key_id = 0UL; + } else { + result->do_auth = 1; + result->auth_key_id = params->authkey; + } + + result->max_delay = params->max_delay; + result->max_delay_ratio = params->max_delay_ratio; + + result->tx_count = 0; + + result->score = 0; + + if (params->online) { + start_initial_timeout(result); + result->opmode = MD_ONLINE; + } else { + result->timer_running = 0; + result->timeout_id = 0; + result->opmode = MD_OFFLINE; + } + + result->auto_offline = params->auto_offline; + + result->local_poll = params->minpoll; + + /* Create a source instance for this NTP source */ + result->source = SRC_CreateNewInstance(remote_addr->ip_addr); /* Will need extra params eventually */ + + result->local_rx.tv_sec = 0; + result->local_rx.tv_usec = 0; + + return result; + +} + +/* ================================================== */ + +/* Get a new instance for a server */ +NCR_Instance +NCR_GetServerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params) +{ + return create_instance(remote_addr, MODE_CLIENT, params); +} + +/* ================================================== */ + +/* Get a new instance for a peer */ +NCR_Instance +NCR_GetPeerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params) +{ + return create_instance(remote_addr, MODE_ACTIVE, params); +} + +/* ================================================== */ + +/* Destroy an instance */ +void +NCR_DestroyInstance(NCR_Instance instance) +{ + /* This will destroy the source instance inside the + structure, which will cause reselection if this was the + synchronising source etc. */ + SRC_DestroyInstance(instance->source); + + /* Cancel any pending timeouts */ + if (instance->timer_running) { + SCH_RemoveTimeout(instance->timeout_id); + instance->timer_running = 0; + } + + /* Free the data structure */ + Free(instance); + return; +} + +/* ================================================== */ + +/* ================================================== */ + +static int +generate_packet_auth(NTP_Packet *pkt, unsigned long keyid) +{ + int keylen; + char *keytext; + int keyok; + MD5_CTX ctx; + + keyok = KEY_GetKey(keyid, &keytext, &keylen); + if (keyok) { + pkt->auth_keyid = htonl(keyid); + MD5Init(&ctx); + MD5Update(&ctx, keytext, keylen); + MD5Update(&ctx, (unsigned char *) pkt, offsetof(NTP_Packet, auth_keyid)); + MD5Final(&ctx); + memcpy(&(pkt->auth_data), &ctx.digest, 16); + return 1; + } else { + pkt->auth_keyid = htonl(0); + return 0; + } +} + +/* ================================================== */ + +static void +determine_md5_delay(void) +{ + NTP_Packet pkt; + struct timeval before, after; + unsigned long usecs, min_usecs=0; + MD5_CTX ctx; + static const char *example_key = "#a0,243asd=-b ds"; + int slen; + int i; + + slen = strlen(example_key); + + for (i=0; i<10; i++) { + LCL_ReadRawTime(&before); + MD5Init(&ctx); + MD5Update(&ctx, (unsigned const char *) example_key, slen); + MD5Update(&ctx, (unsigned const char *) &pkt, offsetof(NTP_Packet, auth_keyid)); + MD5Final(&ctx); + LCL_ReadRawTime(&after); + + usecs = (after.tv_sec - before.tv_sec) * 1000000 + (after.tv_usec - before.tv_usec); + + if (i == 0) { + min_usecs = usecs; + } else { + if (usecs < min_usecs) { + min_usecs = usecs; + } + } + + } + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_NtpCore, "MD5 took %d useconds", min_usecs); +#endif + + /* Add on a bit extra to allow for copying, conversions etc */ + md5_offset_usecs = min_usecs + (min_usecs >> 4); + +} + +/* ================================================== */ + +static int +check_packet_auth(NTP_Packet *pkt, unsigned long keyid) +{ + int keylen; + char *keytext; + int keyok; + MD5_CTX ctx; + + keyok = KEY_GetKey(keyid, &keytext, &keylen); + if (keyok) { + pkt->auth_keyid = htonl(keyid); + MD5Init(&ctx); + MD5Update(&ctx, keytext, keylen); + MD5Update(&ctx, (unsigned char *) pkt, offsetof(NTP_Packet, auth_keyid)); + MD5Final(&ctx); + if (!memcmp((void *) &ctx.digest, (void *) &(pkt->auth_data), 16)) { + return 1; + } else { + return 0; + } + } else { + return 0; + } +} + +/* ================================================== */ + +static void +normalise_score(NCR_Instance inst) +{ + + while (inst->score >= ZONE_WIDTH) { + ++inst->local_poll; + inst->score -= ZONE_WIDTH; + } + while (inst->score < 0) { + if (inst->local_poll > 0) { + --inst->local_poll; + } + inst->score += ZONE_WIDTH; + } + + /* Clamp polling interval to defined range */ + if (inst->local_poll < inst->minpoll) { + inst->local_poll = inst->minpoll; + inst->score = 0; + } else if (inst->local_poll > inst->maxpoll) { + inst->local_poll = inst->maxpoll; + inst->score = ZONE_WIDTH - 1; + } + +} + +/* ================================================== */ + +static void +transmit_packet(NTP_Mode my_mode, /* The mode this machine wants to be */ + int my_poll, /* The log2 of the local poll interval */ + int do_auth, /* Boolean indicating whether to authenticate the packet or not */ + unsigned long key_id, /* The authentication key ID */ + NTP_int64 *orig_ts, /* Originate timestamp (from received packet) */ + struct timeval *local_rx, /* Local time request packet was received */ + struct timeval *local_tx, /* RESULT : Time this reply + is sent as local time, or + NULL if don't want to + know */ + NTP_int64 *local_ntp_tx, /* RESULT : Time reply sent + as NTP timestamp + (including adjustment to + reference), ignored if + NULL */ + NTP_Remote_Address *where_to /* Where to address the reponse to */ + ) +{ + NTP_Packet message; + int version; + int leap; + double local_time_err; + struct timeval local_transmit; + + /* Parameters read from reference module */ + int are_we_synchronised, our_stratum; + NTP_Leap leap_status; + unsigned long our_ref_id; + struct timeval our_ref_time; + double our_root_delay, our_root_dispersion; + + version = 3; + + LCL_ReadCookedTime(&local_transmit, &local_time_err); + REF_GetReferenceParams(&local_transmit, + &are_we_synchronised, &leap_status, + &our_stratum, + &our_ref_id, &our_ref_time, + &our_root_delay, &our_root_dispersion); + + if (are_we_synchronised) { + leap = (int) leap_status; + } else { + leap = 3; + } + + /* Generate transmit packet */ + message.lvm = ((leap << 6) &0xc0) | ((version << 3) & 0x38) | (my_mode & 0x07); + message.stratum = our_stratum; + message.poll = my_poll; + message.precision = LCL_GetSysPrecisionAsLog(); + + /* If we're sending a client mode packet and we aren't synchronized yet, + we might have to set up artificial values for some of these parameters */ + message.root_delay = double_to_int32(our_root_delay); + message.root_dispersion = double_to_int32(our_root_dispersion); + + message.reference_id = htonl((NTP_int32) our_ref_id); + + /* Now fill in timestamps */ + UTI_TimevalToInt64(&our_ref_time, &message.reference_ts); + + /* Originate - this comes from the last packet the source sent us */ + message.originate_ts = *orig_ts; + + /* 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_TimevalToInt64(local_rx, &message.receive_ts); + + /* Transmit - this our local time right now! Also, we might need to + store this for our own use later, next time we receive a message + from the source we're sending to now. */ + LCL_ReadCookedTime(&local_transmit, &local_time_err); + + /* Authenticate */ + if (do_auth) { + /* Pre-compensate the transmit time by approx. how long it will + take to generate the MD5 authentication bytes. */ + local_transmit.tv_usec += md5_offset_usecs; + UTI_NormaliseTimeval(&local_transmit); + UTI_TimevalToInt64(&local_transmit, &message.transmit_ts); + generate_packet_auth(&message, key_id); + NIO_SendAuthenticatedPacket(&message, where_to); + } else { + UTI_TimevalToInt64(&local_transmit, &message.transmit_ts); + NIO_SendNormalPacket(&message, where_to); + } + + if (local_tx) { + *local_tx = local_transmit; + } + + if (local_ntp_tx) { + *local_ntp_tx = message.transmit_ts; + } + +} + + +/* ================================================== */ + +/* ================================================== */ + +#define WARM_UP_DELAY 4.0 + +/* ================================================== */ +/* Timeout handler for transmitting to a source. */ + +static void +transmit_timeout(void *arg) +{ + NCR_Instance inst = (NCR_Instance) arg; + NTP_Mode my_mode; + double timeout_delay=0.0; + int do_timer = 0; + int do_auth; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_NtpCore, "Transmit timeout for [%s:%d]", + UTI_IPToDottedQuad(inst->remote_addr.ip_addr), inst->remote_addr.port); +#endif + + /* Check whether we need to 'warm up' the link to the other end by + sending an echo exchange to ensure both ends' ARP caches are + primed. On loaded systems this might also help ensure that bits + of the program are paged in properly before we start. */ + + if ((inst->presend_minpoll > 0) && + (inst->presend_minpoll <= inst->local_poll) && + !inst->presend_done) { + + /* Send */ + NIO_SendEcho(&inst->remote_addr); + + inst->presend_done = 1; + + /* Requeue timeout */ + inst->timer_running = 1; + inst->timeout_id = SCH_AddTimeoutInClass(WARM_UP_DELAY, SAMPLING_SEPARATION, + SCH_NtpSamplingClass, + transmit_timeout, (void *)inst); + + return; + } + + inst->presend_done = 0; /* Reset for next time */ + + ++inst->tx_count; + if (inst->tx_count >= 9) { + /* Mark source unreachable */ + SRC_UnsetReachable(inst->source); + } else if (inst->tx_count >= 3) { + if (inst->auto_offline) { + NCR_TakeSourceOffline(inst); + } + /* Do reselection */ + SRC_SelectSource(0); + } else { + /* Nothing */ + } + + /* If the source to which we are currently locked starts to lose + connectivity, increase the sampling rate to try and bring it + back. If any other source loses connectivity, back off the + sampling rate to reduce wasted sampling. */ + + if (SRC_IsSyncPeer(inst->source)) { + if (inst->tx_count >= 2) { + /* Implies we have missed at least one transmission */ + inst->score -= 3; + normalise_score(inst); + } + } else { + if (inst->tx_count >= 2) { + inst->score += 1; + normalise_score(inst); + } + } + + my_mode = inst->mode; + + if (inst->do_auth && KEY_KeyKnown(inst->auth_key_id)) { + do_auth = 1; + } else { + do_auth = 0; + } + + transmit_packet(my_mode, inst->local_poll, + do_auth, inst->auth_key_id, + &inst->remote_orig, + &inst->local_rx, &inst->local_tx, &inst->local_ntp_tx, + &inst->remote_addr); + + + switch (inst->opmode) { + case MD_BURST_WAS_OFFLINE: + --inst->burst_total_samples_to_go; + if (inst->burst_total_samples_to_go <= 0) { + inst->opmode = MD_OFFLINE; + } + break; + case MD_BURST_WAS_ONLINE: + --inst->burst_total_samples_to_go; + if (inst->burst_total_samples_to_go <= 0) { + inst->opmode = MD_ONLINE; + } + break; + default: + break; + } + + + /* Restart timer for this message */ + switch (inst->opmode) { + case MD_ONLINE: + timeout_delay = (double)(1 << inst->local_poll); + do_timer = 1; + break; + case MD_OFFLINE: + do_timer = 0; + break; + case MD_BURST_WAS_ONLINE: + case MD_BURST_WAS_OFFLINE: + timeout_delay = BURST_TIMEOUT; + do_timer = 1; + break; + } + + if (do_timer) { + inst->timer_running = 1; + inst->timeout_id = SCH_AddTimeoutInClass(timeout_delay, SAMPLING_SEPARATION, + SCH_NtpSamplingClass, + transmit_timeout, (void *)inst); + } else { + inst->timer_running = 0; + } + + /* And we're done */ + return; +} + + +/* ================================================== */ + +static void +receive_packet(NTP_Packet *message, struct timeval *now, NCR_Instance inst, int do_auth) +{ + int pkt_leap; + int source_is_synchronized; + double pkt_root_delay; + double pkt_root_dispersion; + + unsigned long auth_key_id; + + /* The local time to which the (offset, delay, dispersion) triple will + be taken to relate. For client/server operation this is practically + the same as either the transmit or receive time. The difference comes + in symmetric active mode, when the receive may come minutes after the transmit, and this time + will be midway between the two */ + struct timeval sample_time; + + /* The estimated offset (nomenclature from RFC1305 section 3.4.4). + In seconds, a positive value indicates that the local clock is + SLOW of the remote source and a negative value indicates that the local + clock is FAST of the remote source. */ + double theta; + + /* The estimated round delay in seconds */ + double delta; + + /* The estimated peer dispersion in seconds */ + double epsilon; + + /* The estimated peer distance in seconds */ + double peer_distance; + + /* The total root delay */ + double root_delay; + + /* The total root dispersion */ + double root_dispersion; + + /* The skew relative to the remote source */ + double skew; + + /* The estimated skew relative to the remote source. */ + double source_freq_lo, source_freq_hi; + + /* These are the timeval equivalents of the remote epochs */ + struct timeval remote_receive_tv, remote_transmit_tv; + struct timeval remote_reference_tv; + struct timeval local_average, remote_average; + double local_interval, remote_interval; + + int test1, test2, test3, test4, test5, test6, test7, test8; + + int test4a, test4b; + + /* In the words of section 3.4.4 of RFC1305, valid_data means + that the NTP protocol association with the peer/server is + properly synchronised. valid_header means that the measurement + obtained from the packet is suitable for use in synchronising + our local clock. Wierd choice of terminology. */ + + int valid_data; + int valid_header; + + /* Variables used for doing logging */ + static char sync_stats[4] = {'N', '-', '+', '?'}; + + /* 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 'theta' is defined. */ + double estimated_offset; + + /* The absolute difference between the offset estimate and + measurement in seconds */ + double error_in_estimate; + int poll_to_use; + double delay_time = 0; + int requeue_transmit = 0; + int delta_score; + + /* ==================== */ + + /* Save local receive timestamp */ + inst->local_rx = *now; + + pkt_leap = (message->lvm >> 6) & 0x3; + if (pkt_leap == 0x3) { + source_is_synchronized = 0; + } else { + source_is_synchronized = 1; + } + + pkt_root_delay = int32_to_double(message->root_delay); + pkt_root_dispersion = int32_to_double(message->root_dispersion); + + /* Perform packet validity tests */ + + /* Test 1 requires that pkt.xmt != peer.org. This protects + against receiving a duplicate packet */ + + if ((message->transmit_ts.hi == inst->remote_orig.hi) && + (message->transmit_ts.lo == inst->remote_orig.lo)) { + test1 = 0; /* Failed */ + } else { + test1 = 1; /* Success */ + } + + /* Test 2 requires pkt.org == peer.xmt. This ensures the source + is responding to the latest packet we sent to it. */ + if ((message->originate_ts.hi != inst->local_ntp_tx.hi) || + (message->originate_ts.lo != inst->local_ntp_tx.lo)) { + test2 = 0; /* Failed */ + } else { + test2 = 1; /* Success */ + } + + /* Regardless of any validity checks we apply, we are required to + save these two fields from the packet into the ntp source + instance record. See RFC1305 section 3.4.4, peer.org <- pkt.xmt + & peer.peerpoll <- pkt.poll. Note we can't do this assignment + before test1 has been carried out!! */ + + inst->remote_orig = message->transmit_ts; + inst->remote_poll = message->poll; + + /* Test 3 requires that pkt.org != 0 and pkt.rec != 0. If + either of these are true it means the association is not properly + 'up'. */ + + if (((message->originate_ts.hi == 0) && (message->originate_ts.lo == 0)) || + ((message->receive_ts.hi == 0) && (message->receive_ts.lo == 0))) { + test3 = 0; /* Failed */ + } else { + test3 = 1; /* Success */ + } + + SRC_GetFrequencyRange(inst->source, &source_freq_lo, &source_freq_hi); + + UTI_Int64ToTimeval(&message->receive_ts, &remote_receive_tv); + UTI_Int64ToTimeval(&message->transmit_ts, &remote_transmit_tv); + + if (test3) { + + UTI_AverageDiffTimevals(&remote_receive_tv, &remote_transmit_tv, + &remote_average, &remote_interval); + + UTI_AverageDiffTimevals(&inst->local_tx, now, + &local_average, &local_interval); + + /* In our case, we work out 'delta' as the worst case delay, + assuming worst case frequency error between us and the other + source */ + + delta = local_interval - remote_interval / (1.0 - source_freq_lo); + + /* Calculate theta. Following the NTP definition, this is negative + if we are fast of the remote source. */ + + theta = (double) (remote_average.tv_sec - local_average.tv_sec) + + (double) (remote_average.tv_usec - local_average.tv_usec) * 1.0e-6; + + /* 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; + + /* Calculate skew */ + skew = source_freq_hi - source_freq_lo; + + /* and then calculate peer dispersion */ + epsilon = LCL_GetSysPrecisionAsQuantum() + skew * local_interval; + + } else { + /* If test3 failed, we probably can't calculate these quantities + properly (e.g. for the first sample received in a peering + connection). */ + theta = delta = epsilon = 0.0; + + } + + peer_distance = epsilon + 0.5 * fabs(delta); + + /* Test 4 requires that the round trip delay to the source and the + source (RFC1305 'peer') dispersion are less than a cutoff value */ + + if ((fabs(delta) >= NTP_MAX_DISPERSION) || + (epsilon >= NTP_MAX_DISPERSION)) { + test4 = 0; /* Failed */ + } else { + test4 = 1; /* Success */ + } + + /* Test 4a (additional to RFC1305) requires that the round trip + delay is less than an administrator-defined value */ + + if (fabs(delta) > inst->max_delay) { + test4a = 0; /* Failed */ + } else { + test4a = 1; /* Success */ + } + + /* Test 4b (additional to RFC1305) requires 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 */ + + if (fabs(delta/SRC_MinRoundTripDelay(inst->source)) > inst->max_delay_ratio) { + test4b = 0; /* Failed */ + } else { + test4b = 1; /* Success */ + } + + /* Test 5 relates to authentication. */ + if (inst->do_auth) { + if (do_auth) { + auth_key_id = ntohl(message->auth_keyid); + if (!KEY_KeyKnown(auth_key_id)) { + test5 = 0; + } else { + test5 = check_packet_auth(message, auth_key_id); + } + } else { + /* If we expect authenticated info from this peer/server and the packet + doesn't have it, it's got to fail */ + test5 = 0; + } + } else { + /* If the peer or server sends us an authenticated frame, but + we're not bothered about whether he authenticates or not, just + ignore the test. */ + test5 = 1; + } + + /* Test 6 checks that (i) the remote clock is synchronised (ii) the + transmit timestamp is not before the time it was synchronized (clearly + bogus if it is), and (iii) that it was not synchronised too long ago + */ + UTI_Int64ToTimeval(&message->reference_ts, &remote_reference_tv); + if ((!source_is_synchronized) || + (UTI_CompareTimevals(&remote_reference_tv, &remote_transmit_tv) == 1) || + ((remote_reference_tv.tv_sec + NTP_MAXAGE - remote_transmit_tv.tv_sec) < 0)) { + test6 = 0; /* Failed */ + } else { + test6 = 1; /* Succeeded */ + } + + /* Test 7 checks that the stratum in the packet is appropriate */ + if ((message->stratum > REF_GetOurStratum()) || + (message->stratum > NTP_MAX_STRATUM)) { + test7 = 0; /* Failed */ + } else { + test7 = 1; + } + + /* Test 8 checks that the root delay and dispersion quoted in + the packet are appropriate */ + if ((fabs(pkt_root_delay) >= NTP_MAX_DISPERSION) || + (pkt_root_dispersion >= NTP_MAX_DISPERSION)) { + test8 = 0; /* Failed */ + } else { + test8 = 1; + } + + valid_data = test1 && test2 && test3 && test4 && test4a && test4b; + valid_header = test5 && test6 && test7 && test8; + + root_delay = pkt_root_delay + delta; + root_dispersion = pkt_root_dispersion + epsilon; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_NtpCore, "lvm=%o stratum=%d poll=%d prec=%d", + message->lvm, message->stratum, message->poll, message->precision); + LOG(LOGS_INFO, LOGF_NtpCore, "Root delay=%08lx (%f), dispersion=%08lx (%f)", + message->root_delay, pkt_root_delay, message->root_dispersion, pkt_root_dispersion); + LOG(LOGS_INFO, LOGF_NtpCore, "Ref id=[%s], ref_time=%08lx.%08lx [%s]", + UTI_IPToDottedQuad(ntohl(message->reference_id)), + message->reference_ts.hi, message->reference_ts.lo, + UTI_TimestampToString(&message->reference_ts)); + LOG(LOGS_INFO, LOGF_NtpCore, "Originate=%08lx.%08lx [%s]", + message->originate_ts.hi, message->originate_ts.lo, + UTI_TimestampToString(&message->originate_ts)); + LOG(LOGS_INFO, LOGF_NtpCore, "Message receive=%08lx.%08lx [%s]", + message->receive_ts.hi, message->receive_ts.lo, + UTI_TimestampToString(&message->receive_ts)); + + LOG(LOGS_INFO, LOGF_NtpCore, "Transmit=%08lx.%08lx [%s]", + message->transmit_ts.hi, message->transmit_ts.lo, + UTI_TimestampToString(&message->transmit_ts)); + + LOG(LOGS_INFO, LOGF_NtpCore, "theta=%f delta=%f epsilon=%f root_delay=%f root_dispersion=%f", + theta, delta, epsilon, root_delay, root_dispersion); + + LOG(LOGS_INFO, LOGF_NtpCore, "test1=%d test2=%d test3=%d test4=%d valid_data=%d", + test1, test2, test3, test4, valid_data); + + LOG(LOGS_INFO, LOGF_NtpCore, "test5=%d test6=%d test7=%d test8=%d valid_header=%d", + test5, test6, test7, test8, valid_header); +#endif + + if (valid_header) { + inst->tx_count = 0; + SRC_SetReachable(inst->source); + } + + /* Do this before we accumulate a new sample into the stats registers, obviously */ + estimated_offset = SRC_PredictOffset(inst->source, &sample_time); + + if (valid_data) { + SRC_AccumulateSample(inst->source, + &sample_time, + theta, delta, epsilon, + root_delay, root_dispersion, + message->stratum, (NTP_Leap) pkt_leap); + } + + + /* Only do performance monitoring if we got valid data! */ + + if (valid_data) { + + /* Now examine the registers. First though, 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. */ + error_in_estimate = fabs(-theta - estimated_offset); + /* Now update the polling interval */ + + if (error_in_estimate > peer_distance) { + int shift = 0; + unsigned long temp = (int)(error_in_estimate / peer_distance); + do { + shift++; + temp>>=1; + } while (temp); + + inst->local_poll -= shift; + inst->score = 0; + + } else { + + switch (SRC_LastSkewChange(inst->source)) { + case SRC_Skew_Decrease: + delta_score = 1; + break; + case SRC_Skew_Nochange: + delta_score = 0; + break; + case SRC_Skew_Increase: + delta_score = -2; + break; + default: /* Should not happen! */ + delta_score = 0; + break; + } + + inst->score += delta_score; + } + + normalise_score(inst); + + } + + /* 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: + if (valid_data) { + --inst->burst_good_samples_to_go; + } + + if (inst->burst_good_samples_to_go <= 0) { + inst->opmode = MD_ONLINE; + } + break; + + case MD_BURST_WAS_OFFLINE: + if (valid_data) { + --inst->burst_good_samples_to_go; + } + + if (inst->burst_good_samples_to_go <= 0) { + inst->opmode = MD_OFFLINE; + if (inst->timer_running) { + SCH_RemoveTimeout(inst->timeout_id); + } + inst->timer_running = 0; + } + break; + + default: + break; + } + + /* And now, requeue the timer. + + 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. */ + + switch (inst->opmode) { + case MD_OFFLINE: + requeue_transmit = 0; + break; /* Even if we've received something, we don't want to + transmit back. This might be a symmetric active peer + that is trying to talk to us. */ + + case MD_ONLINE: + /* Normal processing, depending on whether we're in + client/server or symmetric mode */ + + requeue_transmit = 1; + + switch(inst->mode) { + case MODE_CLIENT: + /* Client/server association - aim at some randomised time + approx the poll interval away */ + poll_to_use = inst->local_poll; + + delay_time = (double) (1UL<local_poll < inst->remote_poll) ? inst->local_poll : inst->remote_poll; + + /* Limit by min and max poll */ + if (poll_to_use < inst->minpoll) poll_to_use = inst->minpoll; + if (poll_to_use > inst->maxpoll) poll_to_use = inst->maxpoll; + + delay_time = (double) (1UL<<(poll_to_use - 1)); + + break; + default: + CROAK("Impossible"); + break; + } + + break; + + case MD_BURST_WAS_ONLINE: + case MD_BURST_WAS_OFFLINE: + + requeue_transmit = 1; + delay_time = 0.0; + break; + + default: + CROAK("Impossible"); + break; + + } + + + if (requeue_transmit) { + /* Get rid of old timeout and start a new one */ + SCH_RemoveTimeout(inst->timeout_id); + inst->timeout_id = SCH_AddTimeoutInClass(delay_time, SAMPLING_SEPARATION, + SCH_NtpSamplingClass, + transmit_timeout, (void *)inst); + } + + /* Do measurement logging */ + if (logfile) { + if (((logwrites++) % 32) == 0) { + fprintf(logfile, + "=====================================================================================================================\n" + " Date (UTC) Time IP Address L St 1234 ab 5678 LP RP SC Offset Peer del. Peer disp. Root del. Root disp.\n" + "=====================================================================================================================\n"); + } + + fprintf(logfile, "%s %-15s %1c %2d %1d%1d%1d%1d %1d%1d %1d%1d%1d%1d %2d %2d %2d %10.3e %10.3e %10.3e %10.3e %10.3e\n", + UTI_TimeToLogForm(sample_time.tv_sec), + UTI_IPToDottedQuad(inst->remote_addr.ip_addr), + sync_stats[pkt_leap], + message->stratum, + test1, test2, test3, test4, + test4a, test4b, + test5, test6, test7, test8, + inst->local_poll, inst->remote_poll, + (inst->score), + theta, delta, epsilon, + pkt_root_delay, pkt_root_dispersion); + fflush(logfile); + } + + + /* At this point we will have to do something about trimming the + poll interval for the source and requeueing the polling timeout. + + Left until the source statistics management has been written */ + + return; +} + +/* ================================================== */ +/* From RFC1305, the standard handling of receive packets, depending + on the mode of the packet and of the source, is : + + Source mode>>> + Packet + mode active passive client server bcast + vvv + + active recv pkt recv xmit xmit + passive recv error recv error error + client xmit xmit error xmit xmit + server recv error recv error error + bcast recv error recv error error + + We ignore broadcasts in this implementation - they create too many + problems. + + */ + +/* ================================================== */ + +static void +process_known +(NTP_Packet *message, /* the received message */ + struct timeval *now, /* timestamp at time of receipt */ + NCR_Instance inst, /* the instance record for this peer/server */ + int do_auth /* whether the received packet allegedly contains + authentication info*/ + ) +{ + int pkt_mode; + int version; + int valid_auth, valid_key; + int authenticate_reply; + unsigned long auth_key_id; + unsigned long reply_auth_key_id; + + /* Check version */ + version = (message->lvm >> 3) & 0x7; + if (version != NTP_VERSION) { + /* Ignore packet, but might want to log it */ + return; + } + + /* Perform tests mentioned in RFC1305 to validate packet contents */ + pkt_mode = (message->lvm >> 0) & 0x7; + + /* Now, depending on the mode we decide what to do */ + switch (pkt_mode) { + 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 is an extension to RFC1305, as we don't bother to check + whether we are a client of the remote machine. + + 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. */ + + if (ADF_IsAllowed(access_auth_table, inst->remote_addr.ip_addr)) { + + CLG_LogNTPClientAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec); + + if (do_auth) { + auth_key_id = ntohl(message->auth_keyid); + valid_key = KEY_KeyKnown(auth_key_id); + if (valid_key) { + valid_auth = check_packet_auth(message, auth_key_id); + } else { + valid_auth = 0; + } + + if (valid_key && valid_auth) { + authenticate_reply = 1; + reply_auth_key_id = auth_key_id; + } else { + authenticate_reply = 0; + reply_auth_key_id = 0UL; + } + } else { + authenticate_reply = 0; + reply_auth_key_id = 0UL; + } + + transmit_packet(MODE_SERVER, inst->local_poll, + authenticate_reply, reply_auth_key_id, + &message->transmit_ts, + now, + &inst->local_tx, + &inst->local_ntp_tx, + &inst->remote_addr); + + } else { + LOG(LOGS_WARN, LOGF_NtpCore, "NTP packet received from unauthorised host %s port %d", + UTI_IPToDottedQuad(inst->remote_addr.ip_addr), + inst->remote_addr.port); + } + + break; + + case MODE_ACTIVE: + + switch(inst->mode) { + case MODE_ACTIVE: + /* Ordinary symmetric peering */ + CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec); + receive_packet(message, now, inst, do_auth); + 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 - fair enough. */ + CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec); + receive_packet(message, now, inst, do_auth); + break; + case MODE_SERVER: + /* Nonsense - we can't have a preconfigured server */ + break; + case MODE_BROADCAST: + /* We don't handle broadcasts */ + break; + default: + /* Obviously ignore */ + break; + } + + break; + + case MODE_SERVER: + + switch(inst->mode) { + case MODE_ACTIVE: + /* Slightly bizarre combination, but we can still process it */ + CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec); + receive_packet(message, now, inst, do_auth); + break; + case MODE_PASSIVE: + /* We have no passive peers in this software */ + break; + case MODE_CLIENT: + /* Standard case where he's a server and we're the client */ + receive_packet(message, now, inst, do_auth); + break; + case MODE_SERVER: + /* RFC1305 error condition. */ + break; + case MODE_BROADCAST: + /* RFC1305 error condition */ + break; + default: + /* Obviously ignore */ + 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 */ + CLG_LogNTPPeerAccess(inst->remote_addr.ip_addr, (time_t) now->tv_sec); + receive_packet(message, now, inst, do_auth); + break; + case MODE_PASSIVE: + /* Error condition in RFC1305. Also, we can't have any + non-transient PASSIVE sources in this version, we only + allow configured peers! */ + break; + case MODE_CLIENT: + /* This is a wierd combination - how could it arise? */ + receive_packet(message, now, inst, do_auth); + break; + case MODE_SERVER: + /* Error condition in RFC1305 */ + break; + case MODE_BROADCAST: + /* Error condition in RFC1305 */ + break; + default: + /* Obviously ignore */ + break; + } + break; + + case MODE_BROADCAST: + /* Just ignore these, but might want to log them */ + break; + + default: + /* Obviously ignore */ + break; + + } + + + +} + +/* ================================================== */ +/* 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 */ + +void +NCR_ProcessNoauthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance inst) +{ + + process_known(message, now, inst, 0); + +} + +/* ================================================== */ +/* This routine is called when a new packet arrives off the network, + and we do not recognize its source */ + +void +NCR_ProcessNoauthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr) +{ + + NTP_Mode his_mode; + NTP_Mode my_mode; + int my_poll; + + if (ADF_IsAllowed(access_auth_table, remote_addr->ip_addr)) { + + his_mode = message->lvm & 0x07; + + if (his_mode == MODE_CLIENT) { + /* We are server */ + my_mode = MODE_SERVER; + CLG_LogNTPClientAccess(remote_addr->ip_addr, (time_t) now->tv_sec); + + } else if (his_mode == MODE_ACTIVE) { + /* We are symmetric passive, even though we don't ever lock to him */ + my_mode = MODE_PASSIVE; + CLG_LogNTPPeerAccess(remote_addr->ip_addr, (time_t) now->tv_sec); + + } else { + my_mode = MODE_UNDEFINED; + } + + /* If we can't determine a sensible mode to reply with, it means + he has supplied a wierd mode in his request, so ignore it. */ + + if (my_mode != MODE_UNDEFINED) { + + my_poll = message->poll; /* What should this be set to? Does the client actually care? */ + + transmit_packet(my_mode, my_poll, + 0, 0UL, + &message->transmit_ts, /* Originate (for us) is the transmit time for the client */ + now, /* Time we received the packet */ + NULL, /* Don't care when we send reply, we aren't maintaining state about this client */ + NULL, /* Ditto */ + remote_addr); + + } + } else { + LOG(LOGS_WARN, LOGF_NtpCore, "NTP packet received from unauthorised host %s port %d", + UTI_IPToDottedQuad(remote_addr->ip_addr), + remote_addr->port); + } + + return; + +} + +/* ================================================== */ +/* This routine is called when a new authenticated packet arrives off + the network, and it relates to a source we have an ongoing protocol + exchange with */ + +void +NCR_ProcessAuthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance data) +{ + process_known(message, now, data, 1); + +} + +/* ================================================== */ +/* This routine is called when a new authenticated packet arrives off + the network, and we do not recognize its source */ + +void +NCR_ProcessAuthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr) +{ + + NTP_Mode his_mode; + NTP_Mode my_mode; + int my_poll; + int valid_key, valid_auth; + unsigned long key_id; + + if (ADF_IsAllowed(access_auth_table, remote_addr->ip_addr)) { + + his_mode = message->lvm & 0x07; + + if (his_mode == MODE_CLIENT) { + /* We are server */ + my_mode = MODE_SERVER; + CLG_LogNTPClientAccess(remote_addr->ip_addr, (time_t) now->tv_sec); + + } else if (his_mode == MODE_ACTIVE) { + /* We are symmetric passive, even though we don't ever lock to him */ + my_mode = MODE_PASSIVE; + CLG_LogNTPPeerAccess(remote_addr->ip_addr, (time_t) now->tv_sec); + + } else { + my_mode = MODE_UNDEFINED; + } + + /* If we can't determine a sensible mode to reply with, it means + he has supplied a wierd mode in his request, so ignore it. */ + + if (my_mode != MODE_UNDEFINED) { + + /* Only reply if we know the key and the packet authenticates + properly. */ + key_id = ntohl(message->auth_keyid); + valid_key = KEY_KeyKnown(key_id); + + if (valid_key) { + valid_auth = check_packet_auth(message, key_id); + } else { + valid_auth = 0; + } + + if (valid_key && valid_auth) { + my_poll = message->poll; /* What should this be set to? Does the client actually care? */ + + transmit_packet(my_mode, my_poll, + 1, key_id, + &message->transmit_ts, /* Originate (for us) is the transmit time for the client */ + now, /* Time we received the packet */ + NULL, /* Don't care when we send reply, we aren't maintaining state about this client */ + NULL, /* Ditto */ + remote_addr); + } + } + } + return; + + +} + +/* ================================================== */ + +void +NCR_SlewTimes(NCR_Instance inst, struct timeval *when, double dfreq, double doffset) +{ + struct timeval prev; + prev = inst->local_rx; + UTI_AdjustTimeval(&inst->local_rx, when, &inst->local_rx, dfreq, doffset); +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_NtpCore, "rx prev=[%s] new=[%s]", + UTI_TimevalToString(&prev), UTI_TimevalToString(&inst->local_rx)); +#endif + prev = inst->local_tx; + UTI_AdjustTimeval(&inst->local_tx, when, &inst->local_tx, dfreq, doffset); +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_NtpCore, "tx prev=[%s] new=[%s]", + UTI_TimevalToString(&prev), UTI_TimevalToString(&inst->local_tx)); +#endif +} + +/* ================================================== */ + +void +NCR_TakeSourceOnline(NCR_Instance inst) +{ + switch (inst->opmode) { + case MD_ONLINE: + /* Nothing to do */ + break; + case MD_OFFLINE: + if (!inst->timer_running) { + /* We are not already actively polling it */ + LOG(LOGS_INFO, LOGF_NtpCore, "Source %s online", UTI_IPToDottedQuad(inst->remote_addr.ip_addr)); + inst->local_poll = inst->minpoll; + inst->score = (ZONE_WIDTH >> 1); + inst->opmode = MD_ONLINE; + start_initial_timeout(inst); + } + break; + case MD_BURST_WAS_ONLINE: + /* Will revert */ + break; + case MD_BURST_WAS_OFFLINE: + inst->opmode = MD_BURST_WAS_ONLINE; + break; + } +} + +/* ================================================== */ + +void +NCR_TakeSourceOffline(NCR_Instance inst) +{ + switch (inst->opmode) { + case MD_ONLINE: + if (inst->timer_running) { + LOG(LOGS_INFO, LOGF_NtpCore, "Source %s offline", UTI_IPToDottedQuad(inst->remote_addr.ip_addr)); + SCH_RemoveTimeout(inst->timeout_id); + inst->timer_running = 0; + inst->opmode = MD_OFFLINE; + } + break; + case MD_OFFLINE: + break; + case MD_BURST_WAS_ONLINE: + inst->opmode = MD_BURST_WAS_OFFLINE; + break; + case MD_BURST_WAS_OFFLINE: + break; + } + +} + +/* ================================================== */ + +void +NCR_ModifyMinpoll(NCR_Instance inst, int new_minpoll) +{ + inst->minpoll = new_minpoll; + LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new minpoll %d", UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_minpoll); +} + +/* ================================================== */ + +void +NCR_ModifyMaxpoll(NCR_Instance inst, int new_maxpoll) +{ + inst->maxpoll = new_maxpoll; + LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new maxpoll %d", UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_maxpoll); +} + +/* ================================================== */ + +void +NCR_ModifyMaxdelay(NCR_Instance inst, double new_max_delay) +{ + inst->max_delay = new_max_delay; + LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new max delay %f", + UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_max_delay); +} + +/* ================================================== */ + +void +NCR_ModifyMaxdelayratio(NCR_Instance inst, double new_max_delay_ratio) +{ + inst->max_delay_ratio = new_max_delay_ratio; + LOG(LOGS_INFO, LOGF_NtpCore, "Source %s new max delay ratio %f", + UTI_IPToDottedQuad(inst->remote_addr.ip_addr), new_max_delay_ratio); +} + +/* ================================================== */ + +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: + inst->opmode = MD_BURST_WAS_ONLINE; + inst->burst_good_samples_to_go = n_good_samples; + inst->burst_total_samples_to_go = n_total_samples; + if (inst->timer_running) { + SCH_RemoveTimeout(inst->timeout_id); + } + inst->timer_running = 1; + inst->timeout_id = SCH_AddTimeoutInClass(0.0, SAMPLING_SEPARATION, + SCH_NtpSamplingClass, + transmit_timeout, (void *) inst); + break; + + case MD_OFFLINE: + inst->opmode = MD_BURST_WAS_OFFLINE; + inst->burst_good_samples_to_go = n_good_samples; + inst->burst_total_samples_to_go = n_total_samples; + if (inst->timer_running) { + SCH_RemoveTimeout(inst->timeout_id); + } + inst->timer_running = 1; + inst->timeout_id = SCH_AddTimeoutInClass(0.0, SAMPLING_SEPARATION, + SCH_NtpSamplingClass, + transmit_timeout, (void *) inst); + break; + + + default: + CROAK("Impossible"); + break; + } + } + +} + +/* ================================================== */ + +void +NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timeval *now) +{ + report->poll = inst->local_poll; + + switch (inst->mode) { + case MODE_CLIENT: + report->mode = RPT_NTP_CLIENT; + break; + case MODE_ACTIVE: + report->mode = RPT_NTP_PEER; + break; + default: + CROAK("Impossible"); + } + + return; +} + +/* ================================================== */ + +int +NCR_AddAccessRestriction(unsigned long 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 +NCR_CheckAccessRestriction(unsigned long ip_addr) +{ + return ADF_IsAllowed(access_auth_table, ip_addr); +} + +/* ================================================== */ + +void +NCR_CycleLogFile(void) +{ + if (logfile && logfilename) { + fclose(logfile); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_NtpCore, "Could not reopen logfile %s", logfilename); + } + logwrites = 0; + } +} + +/* ================================================== */ + +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: + CROAK("Impossible"); + break; + } +} + +/* ================================================== */ diff --git a/ntp_core.h b/ntp_core.h new file mode 100644 index 0000000..84b21d9 --- /dev/null +++ b/ntp_core.h @@ -0,0 +1,105 @@ +/* + $Header: /cvs/src/chrony/ntp_core.h,v 1.16 2002/02/28 23:27:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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" + +/* 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 */ +extern NCR_Instance NCR_GetServerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params); + +/* Get a new instance for a peer */ +extern NCR_Instance NCR_GetPeerInstance(NTP_Remote_Address *remote_addr, SourceParameters *params); + +/* Destroy an instance */ +extern void NCR_DestroyInstance(NCR_Instance instance); + +/* 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 void NCR_ProcessNoauthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance data); + +/* This routine is called when a new packet arrives off the network, + and we do not recognize its source */ +extern void NCR_ProcessNoauthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr); + +/* This routine is called when a new authenticated packet arrives off + the network, and it relates to a source we have an ongoing protocol + exchange with */ +extern void NCR_ProcessAuthKnown(NTP_Packet *message, struct timeval *now, NCR_Instance data); + +/* This routine is called when a new authenticated packet arrives off + the network, and we do not recognize its source */ +extern void NCR_ProcessAuthUnknown(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr); + +/* Slew receive and transmit times in instance records */ +extern void NCR_SlewTimes(NCR_Instance inst, struct timeval *when, double dfreq, double doffset); + +/* Take a particular source online (i.e. start sampling it) */ +extern void NCR_TakeSourceOnline(NCR_Instance inst); + +/* Take a particular source offline (i.e. stop sampling it, without + marking it unreachable in the source selection stuff) */ +extern void NCR_TakeSourceOffline(NCR_Instance inst); + +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_InitiateSampleBurst(NCR_Instance inst, int n_good_samples, int n_total_samples); + +extern void NCR_ReportSource(NCR_Instance inst, RPT_SourceReport *report, struct timeval *now); + +extern int NCR_AddAccessRestriction(unsigned long ip_addr, int subnet_bits, int allow, int all); +extern int NCR_CheckAccessRestriction(unsigned long ip_addr); + +extern void NCR_CycleLogFile(void); + +extern void NCR_IncrementActivityCounters(NCR_Instance inst, int *online, int *offline, + int *burst_online, int *burst_offline); + +#endif /* GOT_NTP_CORE_H */ diff --git a/ntp_io.c b/ntp_io.c new file mode 100644 index 0000000..91bee1d --- /dev/null +++ b/ntp_io.c @@ -0,0 +1,297 @@ +/* + $Header: /cvs/src/chrony/ntp_io.c,v 1.23 2003/04/01 20:54:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + This file deals with the IO aspects of reading and writing NTP packets + */ + +#include "sysincl.h" + +#include "ntp_io.h" +#include "ntp_core.h" +#include "ntp_sources.h" +#include "sched.h" +#include "local.h" +#include "logging.h" +#include "conf.h" +#include "util.h" + +#include + +/* The file descriptor for the socket */ +static int sock_fd; + +/* Flag indicating that we have been initialised */ +static int initialised=0; + +/* ================================================== */ + +/* Forward prototypes */ +static void read_from_socket(void *anything); + +/* ================================================== */ + +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); + +} + +/* ================================================== */ + +void +NIO_Initialise(void) +{ + struct sockaddr_in my_addr; + unsigned short port_number; + unsigned long bind_address; + int on_off = 1; + + assert(!initialised); + initialised = 1; + + do_size_checks(); + + port_number = CNF_GetNTPPort(); + + /* Open Internet domain UDP socket for NTP message transmissions */ + +#if 0 + sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +#else + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); +#endif + if (sock_fd < 0) { + LOG_FATAL(LOGF_NtpIO, "Could not open socket : %s", strerror(errno)); + } + + /* Make the socket capable of re-using an old address */ + if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on_off, sizeof(on_off)) < 0) { + LOG(LOGS_ERR, LOGF_NtpIO, "Could not set reuseaddr socket options"); + /* Don't quit - we might survive anyway */ + } + + /* Make the socket capable of sending broadcast pkts - needed for NTP broadcast mode */ + if (setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (char *)&on_off, sizeof(on_off)) < 0) { + LOG(LOGS_ERR, LOGF_NtpIO, "Could not set broadcast socket options"); + /* Don't quit - we might survive anyway */ + } + + /* Bind the port */ + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(port_number); + + CNF_GetBindAddress(&bind_address); + + if (bind_address != 0UL) { + my_addr.sin_addr.s_addr = htonl(bind_address); + } else { + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + } + +#if 0 + LOG(LOGS_INFO, LOGF_NtpIO, "Initialising, socket fd=%d", sock_fd); +#endif + + if (bind(sock_fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + LOG_FATAL(LOGF_NtpIO, "Could not bind socket : %s", strerror(errno)); + } + + /* Register handler for read events on the socket */ + SCH_AddInputFileHandler(sock_fd, read_from_socket, NULL); + +#if 0 + if (fcntl(sock_fd, F_SETFL, O_NONBLOCK | O_NDELAY) < 0) { + LOG(LOGS_ERR, LOGF_NtpIO, "Could not make socket non-blocking"); + } + + if (ioctl(sock_fd, I_SETSIG, S_INPUT) < 0) { + LOG(LOGS_ERR, LOGF_NtpIO, "Could not enable signal"); + } +#endif + + return; +} + +/* ================================================== */ + +void +NIO_Finalise(void) +{ + if (sock_fd >= 0) { + SCH_RemoveInputFileHandler(sock_fd); + close(sock_fd); + } + sock_fd = -1; + initialised = 0; + return; +} + +/* ================================================== */ + + +/* ================================================== */ + +static void +read_from_socket(void *anything) +{ + /* This should only be called when there is something + to read, otherwise it will block. */ + + int status; + ReceiveBuffer message; + int message_length; + struct sockaddr_in where_from; + int from_length; + unsigned int flags = 0; + struct timeval now; + NTP_Remote_Address remote_addr; + double local_clock_err; + + assert(initialised); + + from_length = sizeof(where_from); + message_length = sizeof(message); + + LCL_ReadCookedTime(&now, &local_clock_err); + status = recvfrom(sock_fd, (char *)&message, message_length, flags, + (struct sockaddr *)&where_from, &from_length); + + /* Don't bother checking if read failed or why if it did. More + likely than not, it will be connection refused, resulting from a + previous sendto() directing a datagram at a port that is not + listening (which appears to generate an ICMP response, and on + some architectures e.g. Linux this is translated into an error + reponse on a subsequent recvfrom). */ + + if (status > 0) { + remote_addr.ip_addr = ntohl(where_from.sin_addr.s_addr); + remote_addr.port = ntohs(where_from.sin_port); + + if (status == NTP_NORMAL_PACKET_SIZE) { + + NSR_ProcessReceive((NTP_Packet *) &message.ntp_pkt, &now, &remote_addr); + + } else if (status == sizeof(NTP_Packet)) { + + NSR_ProcessAuthenticatedReceive((NTP_Packet *) &message.ntp_pkt, &now, &remote_addr); + + } else { + + /* Just ignore the packet if it's not of a recognized length */ + + } + } + + return; +} + +/* ================================================== */ +/* Send an unauthenticated packet to a given address */ + +void +NIO_SendNormalPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr) +{ + struct sockaddr_in remote; + + assert(initialised); + + remote.sin_family = AF_INET; + remote.sin_port = htons(remote_addr->port); + remote.sin_addr.s_addr = htonl(remote_addr->ip_addr); + + if (sendto(sock_fd, (void *) packet, NTP_NORMAL_PACKET_SIZE, 0, + (struct sockaddr *) &remote, sizeof(remote)) < 0) { + LOG(LOGS_WARN, LOGF_NtpIO, "Could not send to :%s%d : %s", + UTI_IPToDottedQuad(remote_addr->ip_addr), remote_addr->port, strerror(errno)); + } + + return; +} + +/* ================================================== */ +/* Send an authenticated packet to a given address */ + +void +NIO_SendAuthenticatedPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr) +{ + struct sockaddr_in remote; + + assert(initialised); + + remote.sin_family = AF_INET; + remote.sin_port = htons(remote_addr->port); + remote.sin_addr.s_addr = htonl(remote_addr->ip_addr); + + if (sendto(sock_fd, (void *) packet, sizeof(NTP_Packet), 0, + (struct sockaddr *) &remote, sizeof(remote)) < 0) { + LOG(LOGS_WARN, LOGF_NtpIO, "Could not send to :%s%d : %s", + UTI_IPToDottedQuad(remote_addr->ip_addr), remote_addr->port, strerror(errno)); + } + + return; +} + +/* ================================================== */ + +/* We ought to use getservbyname, but I can't really see this changing */ +#define ECHO_PORT 7 + +void +NIO_SendEcho(NTP_Remote_Address *remote_addr) +{ + unsigned long magic_message = 0xbe7ab1e7UL; + struct sockaddr_in addr; + + addr.sin_family = AF_INET; + addr.sin_port = htons(ECHO_PORT); + addr.sin_addr.s_addr = htonl(remote_addr->ip_addr); + + /* Just ignore error status on send - this is not a big deal anyway */ + sendto(sock_fd, (void *) &magic_message, sizeof(unsigned long), 0, + (struct sockaddr *) &addr, sizeof(addr)); + + +} diff --git a/ntp_io.h b/ntp_io.h new file mode 100644 index 0000000..b7a763f --- /dev/null +++ b/ntp_io.h @@ -0,0 +1,53 @@ +/* + $Header: /cvs/src/chrony/ntp_io.h,v 1.9 2002/02/28 23:27:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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" + +/* Function to initialise the module. */ +extern void NIO_Initialise(void); + +/* Function to finalise the module */ +extern void NIO_Finalise(void); + +/* Function to transmit a packet */ +extern void NIO_SendNormalPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr); + +/* Function to transmit an authenticated packet */ +extern void NIO_SendAuthenticatedPacket(NTP_Packet *packet, NTP_Remote_Address *remote_addr); + +/* Function to send a datagram to a remote machine's UDP echo port. */ +extern void NIO_SendEcho(NTP_Remote_Address *remote_addr); + +#endif /* GOT_NTP_IO_H */ diff --git a/ntp_sources.c b/ntp_sources.c new file mode 100644 index 0000000..ab3afd1 --- /dev/null +++ b/ntp_sources.c @@ -0,0 +1,499 @@ +/* + $Header: /cvs/src/chrony/ntp_sources.c,v 1.17 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Functions which manage the pool of NTP sources that we are currently + a client of or peering with. + + */ + +#include "sysincl.h" + +#include "ntp_sources.h" +#include "ntp_core.h" +#include "util.h" +#include "logging.h" +#include "local.h" + +/* ================================================== */ + +/* 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 */ + int in_use; /* Whether this slot in the table is in use */ + NCR_Instance data; /* Data for the protocol engine for this source */ +} SourceRecord; + +#define N_RECORDS 256 + +/* Fixed size table, because we use a hard coded hash algorithm. It + is rather unlikely we would have anything approaching this number + of sources. */ +static SourceRecord records[N_RECORDS]; + +static int n_sources; + +/* The largest number of sources we want to have stored in the hash table */ +#define MAX_SOURCES 64 + +/* ================================================== */ +/* Forward prototypes */ +static void +slew_sources(struct timeval *raw, + struct timeval *cooked, + double dfreq, + double afreq, + double doffset, + int is_step_change, + void *anything); + +/* ================================================== */ + +/* Flag indicating whether module is initialised */ +static int initialised = 0; + +/* ================================================== */ + +void +NSR_Initialise(void) +{ + int i; + for (i=0; i Neither IP nor port matched, empty slot returned + found = 1 => Only IP matched, port doesn't match + found = 2 => Both IP and port matched. + + It is assumed that there can only ever be one record for a + particular IP address. (If a different port comes up, it probably + means someone is running ntpdate -d or something). Thus, if we + match the IP address we stop the search regardless of whether the + port number matches. + + */ + +static void +find_slot(NTP_Remote_Address *remote_addr, int *slot, int *found) +{ + unsigned long hash; + unsigned long ip = remote_addr->ip_addr; + unsigned short port = remote_addr->port; + + assert(N_RECORDS == 256); + + /* Compute hash value just by xor'ing the 4 bytes of the address together */ + hash = ip ^ (ip >> 16); + hash = (hash ^ (hash >> 8)) & 0xff; + + while ((records[hash].in_use) && + (records[hash].remote_addr.ip_addr != ip)) { + hash++; + if (hash == 256) hash = 0; + } + + if (records[hash].in_use) { + if (records[hash].remote_addr.port == port) { + *found = 2; + } else { + *found = 1; + } + *slot = hash; + } else { + *found = 0; + *slot = hash; + } + + return; +} + +/* ================================================== */ + +/* Procedure to add a new server source (to which this machine will be + a client) */ +NSR_Status +NSR_AddServer(NTP_Remote_Address *remote_addr, SourceParameters *params) +{ + int slot, found; + + assert(initialised); + +#if 0 + LOG(LOGS_INFO, LOGF_NtpSources, "IP=%08lx port=%d", (unsigned long)remote_addr->ip_addr, remote_addr->port); +#endif + + /* Find empty bin & check that we don't have the address already */ + find_slot(remote_addr, &slot, &found); + if (found) { + return NSR_AlreadyInUse; + } else { + if (n_sources == MAX_SOURCES) { + return NSR_TooManySources; + } else { + n_sources++; + records[slot].remote_addr = *remote_addr; + records[slot].in_use = 1; + records[slot].data = NCR_GetServerInstance(remote_addr, params); /* Will need params passing through */ + return NSR_Success; + } + } +} + +/* ================================================== */ + +/* Procedure to add a new peer. */ +NSR_Status +NSR_AddPeer(NTP_Remote_Address *remote_addr, SourceParameters *params) +{ + int slot, found; + + assert(initialised); + +#if 0 + LOG(LOGS_INFO, LOGF_NtpSources, "IP=%08lx port=%d", (unsigned long) remote_addr->ip_addr, remote_addr->port); +#endif + + /* Find empty bin & check that we don't have the address already */ + find_slot(remote_addr, &slot, &found); + if (found) { + return NSR_AlreadyInUse; + } else { + if (n_sources == MAX_SOURCES) { + return NSR_TooManySources; + } else { + n_sources++; + records[slot].remote_addr = *remote_addr; + records[slot].in_use = 1; + records[slot].data = NCR_GetPeerInstance(remote_addr, params); /* Will need params passing through */ + return NSR_Success; + } + } +} + +/* ================================================== */ + +/* 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. Thus the caller can specify the port number + as zero if it wishes. */ +NSR_Status +NSR_RemoveSource(NTP_Remote_Address *remote_addr) +{ + int slot, found; + + assert(initialised); + + find_slot(remote_addr, &slot, &found); + if (!found) { + return NSR_NoSuchSource; + } else { + n_sources--; + records[slot].in_use = 0; + NCR_DestroyInstance(records[slot].data); + return NSR_Success; + } +} + +/* ================================================== */ + +/* This routine is called by ntp_io when a new packet arrives off the network.*/ +void +NSR_ProcessReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr) +{ + int slot, found; + + assert(initialised); + +#if 0 + LOG(LOGS_INFO, LOGF_NtpSources, "from (%s,%d) at %s", + UTI_IPToDottedQuad(remote_addr->ip_addr), + remote_addr->port, UTI_TimevalToString(now)); +#endif + + find_slot(remote_addr, &slot, &found); + if (found == 2) { /* Must match IP address AND port number */ + NCR_ProcessNoauthKnown(message, now, records[slot].data); + } else { + NCR_ProcessNoauthUnknown(message, now, remote_addr); + } +} + +/* ================================================== */ + +/* This routine is called by ntp_io when a new packet with an authentication tail arrives off the network */ +void +NSR_ProcessAuthenticatedReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr) +{ + int slot, found; + + assert(initialised); + + find_slot(remote_addr, &slot, &found); + if (found == 2) { + NCR_ProcessAuthKnown(message, now, records[slot].data); + } else { + NCR_ProcessAuthUnknown(message, now, remote_addr); + } +} + +/* ================================================== */ + +static void +slew_sources(struct timeval *raw, + struct timeval *cooked, + double dfreq, + double afreq, + double doffset, + int is_step_change, + void *anything) +{ + int i; + + for (i=0; iip_addr; + rem_addr.port = 0; + find_slot(&rem_addr, &slot, &found); + if (found) { + NCR_ReportSource(records[slot].data, report, now); + } else { + report->poll = 0; + report->latest_meas_ago = 0; + } +} + +/* ================================================== */ + +void +NSR_GetActivityReport(RPT_ActivityReport *report) +{ + int i; + + report->online = 0; + report->offline = 0; + report->burst_online = 0; + report->burst_offline = 0; + + for (i=0; ionline, &report->offline, + &report->burst_online, &report->burst_offline); + } + } + return; +} + + +/* ================================================== */ + diff --git a/ntp_sources.h b/ntp_sources.h new file mode 100644 index 0000000..2e8499c --- /dev/null +++ b/ntp_sources.h @@ -0,0 +1,99 @@ +/* + $Header: /cvs/src/chrony/ntp_sources.h,v 1.12 2002/02/28 23:27:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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, /* AddServer, AddPeer - attempt to add a source that is already known */ + NSR_TooManySources /* AddServer, AddPeer - too many sources already present */ +} NSR_Status; + +/* Procedure to add a new server source (to which this machine will be + a client) */ +extern NSR_Status NSR_AddServer(NTP_Remote_Address *remote_addr, SourceParameters *params); + +/* Procedure to add a new peer source. We will use symmetric active + mode packets when communicating with this source */ +extern NSR_Status NSR_AddPeer(NTP_Remote_Address *remote_addr, SourceParameters *params); + +/* Procedure to remove a source */ +extern NSR_Status NSR_RemoveSource(NTP_Remote_Address *remote_addr); + +/* This routine is called by ntp_io when a new packet arrives off the network */ +extern void NSR_ProcessReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr); + +/* This routine is called by ntp_io when a new packet with an authentication tail arrives off the network */ +extern void NSR_ProcessAuthenticatedReceive(NTP_Packet *message, struct timeval *now, NTP_Remote_Address *remote_addr); + +/* 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 again. Returns a + flag indicating whether any hosts matched the address */ +extern int NSR_TakeSourcesOnline(unsigned long mask, unsigned long address); + +/* This routine is used to indicate that sources whose IP addresses + match a particular subnet should be set offline. Returns a flag + indicating whether any hosts matched the address */ +extern int NSR_TakeSourcesOffline(unsigned long mask, unsigned long address); + +extern int NSR_ModifyMinpoll(unsigned long address, int new_minpoll); + +extern int NSR_ModifyMaxpoll(unsigned long address, int new_maxpoll); + +extern int NSR_ModifyMaxdelay(unsigned long address, double new_max_delay); + +extern int NSR_ModifyMaxdelayratio(unsigned long address, double new_max_delay_ratio); + +extern int NSR_InitiateSampleBurst(int n_good_samples, int n_total_samples, unsigned long mask, unsigned long address); + +extern void NSR_ReportSource(RPT_SourceReport *report, struct timeval *now); + +extern void NSR_GetActivityReport(RPT_ActivityReport *report); + +#endif /* GOT_NTP_SOURCES_H */ diff --git a/pktlength.c b/pktlength.c new file mode 100644 index 0000000..3f67347 --- /dev/null +++ b/pktlength.c @@ -0,0 +1,240 @@ +/* + $Header: /cvs/src/chrony/pktlength.c,v 1.12 2002/02/28 23:27:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "sysincl.h" + +#include "util.h" +#include "pktlength.h" + +/* ================================================== */ + +int +PKL_CommandLength(CMD_Request *r) +{ + int type; + type = ntohs(r->command); + if (type < 0 || type >= N_REQUEST_TYPES) { + return 0; + } else { + switch (type) { + + case REQ_NULL: + return offsetof(CMD_Request, data); + case REQ_ONLINE: + return offsetof(CMD_Request, data.online.EOR); + case REQ_OFFLINE: + return offsetof(CMD_Request, data.offline.EOR); + case REQ_BURST: + return offsetof(CMD_Request, data.burst.EOR); + case REQ_MODIFY_MINPOLL: + return offsetof(CMD_Request, data.modify_minpoll.EOR); + case REQ_MODIFY_MAXPOLL: + return offsetof(CMD_Request, data.modify_maxpoll.EOR); + case REQ_DUMP: + return offsetof(CMD_Request, data.dump.EOR); + case REQ_MODIFY_MAXDELAY: + return offsetof(CMD_Request, data.modify_maxdelay.EOR); + case REQ_MODIFY_MAXDELAYRATIO: + return offsetof(CMD_Request, data.modify_maxdelayratio.EOR); + case REQ_MODIFY_MAXUPDATESKEW: + return offsetof(CMD_Request, data.modify_maxupdateskew.EOR); + case REQ_LOGON : + return offsetof(CMD_Request, data.logon.EOR); + case REQ_SETTIME : + return offsetof(CMD_Request, data.settime.EOR); + case REQ_LOCAL : + return offsetof(CMD_Request, data.local.EOR); + case REQ_MANUAL : + return offsetof(CMD_Request, data.manual.EOR); + case REQ_N_SOURCES : + return offsetof(CMD_Request, data.n_sources.EOR); + case REQ_SOURCE_DATA : + return offsetof(CMD_Request, data.source_data.EOR); + case REQ_REKEY : + return offsetof(CMD_Request, data.rekey.EOR); + case REQ_ALLOW : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_ALLOWALL : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_DENY : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_DENYALL : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_CMDALLOW : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_CMDALLOWALL : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_CMDDENY : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_CMDDENYALL : + return offsetof(CMD_Request, data.allow_deny.EOR); + case REQ_ACCHECK : + return offsetof(CMD_Request, data.ac_check.EOR); + case REQ_CMDACCHECK : + return offsetof(CMD_Request, data.ac_check.EOR); + case REQ_ADD_SERVER : + return offsetof(CMD_Request, data.ntp_source.EOR); + case REQ_ADD_PEER : + return offsetof(CMD_Request, data.ntp_source.EOR); + case REQ_DEL_SOURCE : + return offsetof(CMD_Request, data.del_source.EOR); + case REQ_WRITERTC : + return offsetof(CMD_Request, data.writertc.EOR); + case REQ_DFREQ : + return offsetof(CMD_Request, data.dfreq.EOR); + case REQ_DOFFSET : + return offsetof(CMD_Request, data.doffset.EOR); + case REQ_TRACKING : + return offsetof(CMD_Request, data.tracking.EOR); + case REQ_SOURCESTATS : + return offsetof(CMD_Request, data.sourcestats.EOR); + case REQ_RTCREPORT : + return offsetof(CMD_Request, data.rtcreport.EOR); + case REQ_TRIMRTC : + return offsetof(CMD_Request, data.trimrtc.EOR); + case REQ_CYCLELOGS : + return offsetof(CMD_Request, data.cyclelogs.EOR); + case REQ_SUBNETS_ACCESSED : + { + unsigned long ns; + ns = ntohl(r->data.subnets_accessed.n_subnets); + return (offsetof(CMD_Request, data.subnets_accessed.subnets) + + ns * sizeof(REQ_SubnetsAccessed_Subnet)); + } + case REQ_CLIENT_ACCESSES: + { + unsigned long nc; + nc = ntohl(r->data.client_accesses.n_clients); + return (offsetof(CMD_Request, data.client_accesses.client_ips) + + nc * sizeof(unsigned long)); + } + case REQ_CLIENT_ACCESSES_BY_INDEX: + return offsetof(CMD_Request, data.client_accesses_by_index.EOR); + case REQ_MANUAL_LIST: + return offsetof(CMD_Request, data.manual_list.EOR); + case REQ_MANUAL_DELETE: + return offsetof(CMD_Request, data.manual_delete.EOR); + case REQ_MAKESTEP: + return offsetof(CMD_Request, data.make_step.EOR); + case REQ_ACTIVITY: + return offsetof(CMD_Request, data.activity.EOR); + default: + /* If we fall through the switch, it most likely means we've forgotten to implement a new case */ + assert(0); + } + } + + /* Catch-all case */ + return 0; + +} + + +/* ================================================== */ + +int +PKL_ReplyLength(CMD_Reply *r) +{ + int type; + type = ntohs(r->reply); + /* Note that reply type codes start from 1, not 0 */ + if (type < 1 || type >= N_REPLY_TYPES) { + return 0; + } else { + switch (type) { + case RPY_NULL: + return offsetof(CMD_Reply, data.null.EOR); + case RPY_N_SOURCES: + return offsetof(CMD_Reply, data.n_sources.EOR); + case RPY_SOURCE_DATA: + return offsetof(CMD_Reply, data.source_data.EOR); + case RPY_MANUAL_TIMESTAMP: + return offsetof(CMD_Reply, data.manual_timestamp.EOR); + case RPY_TRACKING: + return offsetof(CMD_Reply, data.tracking.EOR); + case RPY_SOURCESTATS: + return offsetof(CMD_Reply, data.sourcestats.EOR); + case RPY_RTC: + return offsetof(CMD_Reply, data.rtc.EOR); + case RPY_SUBNETS_ACCESSED : + { + unsigned long ns = ntohl(r->data.subnets_accessed.n_subnets); + if (r->status == htons(STT_SUCCESS)) { + return (offsetof(CMD_Reply, data.subnets_accessed.subnets) + + ns * sizeof(RPY_SubnetsAccessed_Subnet)); + } else { + return offsetof(CMD_Reply, data); + } + } + case RPY_CLIENT_ACCESSES: + { + unsigned long nc = ntohl(r->data.client_accesses.n_clients); + if (r->status == htons(STT_SUCCESS)) { + return (offsetof(CMD_Reply, data.client_accesses.clients) + + nc * sizeof(RPY_ClientAccesses_Client)); + } else { + return offsetof(CMD_Reply, data); + } + } + case RPY_CLIENT_ACCESSES_BY_INDEX: + { + unsigned long nc = ntohl(r->data.client_accesses_by_index.n_clients); + if (r->status == htons(STT_SUCCESS)) { + return (offsetof(CMD_Reply, data.client_accesses_by_index.clients) + + nc * sizeof(RPY_ClientAccesses_Client)); + } else { + return offsetof(CMD_Reply, data); + } + } + case RPY_MANUAL_LIST: + { + unsigned long ns = ntohl(r->data.manual_list.n_samples); + if (r->status == htons(STT_SUCCESS)) { + return (offsetof(CMD_Reply, data.manual_list.samples) + + ns * sizeof(RPY_ManualListSample)); + } else { + return offsetof(CMD_Reply, data); + } + } + case RPY_ACTIVITY: + return offsetof(CMD_Reply, data.activity.EOR); + + default: + assert(0); + } + } + + return 0; +} + +/* ================================================== */ + diff --git a/pktlength.h b/pktlength.h new file mode 100644 index 0000000..f8b53f6 --- /dev/null +++ b/pktlength.h @@ -0,0 +1,42 @@ +/* + $Header: /cvs/src/chrony/pktlength.h,v 1.4 2002/02/28 23:27:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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_ReplyLength(CMD_Reply *r); + +#endif /* GOT_PKTLENGTH_H */ diff --git a/reference.c b/reference.c new file mode 100644 index 0000000..92f0550 --- /dev/null +++ b/reference.c @@ -0,0 +1,709 @@ +/* + $Header: /cvs/src/chrony/reference.c,v 1.40 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "sysincl.h" + +#include "memory.h" +#include "reference.h" +#include "util.h" +#include "conf.h" +#include "logging.h" +#include "local.h" +#include "mkdirpp.h" + +/* ================================================== */ + +static int are_we_synchronised; +static int enable_local_stratum; +static int local_stratum; +static NTP_Leap our_leap_status; +static int our_stratum; +static unsigned long our_ref_id; +struct timeval our_ref_time; /* Stored relative to reference, NOT local time */ +static double our_offset; +static double our_skew; +static double our_residual_freq; +static double our_root_delay; +static double our_root_dispersion; + +static double max_update_skew; + +/* Flag indicating that we are initialised */ +static int initialised = 0; + +/* Flag and threshold for logging clock changes to syslog */ +static int do_log_change; +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; + +/* Filename of the drift file. */ +static char *drift_file=NULL; + +static void update_drift_file(double, double); + +#define MAIL_PROGRAM "/usr/lib/sendmail" + +/* ================================================== */ +/* File to which statistics are logged, NULL if none */ +static FILE *logfile = NULL; +static char *logfilename = NULL; +static unsigned long logwrites = 0; + +#define TRACKING_LOG "tracking.log" + +/* ================================================== */ + +/* Day number of 1 Jan 1970 */ +#define MJD_1970 40587 + +/* Reference ID supplied when we are locally referenced */ +#define LOCAL_REFERENCE_ID 0x7f7f0101UL + +/* ================================================== */ + +void +REF_Initialise(void) +{ + char *direc; + FILE *in; + char line[1024]; + double file_freq_ppm, file_skew_ppm; + double our_frequency_ppm; + + are_we_synchronised = 0; + our_leap_status = LEAP_Normal; + 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; + + /* Now see if we can get the drift file opened */ + drift_file = CNF_GetDriftFile(); + if (drift_file) { + in = fopen(drift_file, "r"); + if (in) { + if (fgets(line, sizeof(line), in)) { + if (sscanf(line, "%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; + } else { + LOG(LOGS_WARN, LOGF_Reference, "Could not parse valid frequency and skew from driftfile %s", + drift_file); + } + } else { + LOG(LOGS_WARN, LOGF_Reference, "Could not read valid frequency and skew from driftfile %s", + drift_file); + } + fclose(in); + } else { + LOG(LOGS_WARN, LOGF_Reference, "Could not open driftfile %s for reading", + drift_file); + } + + update_drift_file(our_frequency_ppm,our_skew); + } + + LCL_SetAbsoluteFrequency(our_frequency_ppm); + + if (CNF_GetLogTracking()) { + direc = CNF_GetLogDir(); + if (!mkdir_and_parents(direc)) { + LOG(LOGS_ERR, LOGF_Reference, "Could not create directory %s", direc); + logfile = NULL; + } else { + logfilename = MallocArray(char, 2 + strlen(direc) + strlen(TRACKING_LOG)); + strcpy(logfilename, direc); + strcat(logfilename, "/"); + strcat(logfilename, TRACKING_LOG); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_Reference, "Couldn't open logfile %s for update", logfilename); + } + } + } + + max_update_skew = fabs(CNF_GetMaxUpdateSkew()) * 1.0e-6; + + enable_local_stratum = CNF_AllowLocalReference(&local_stratum); + + CNF_GetLogChange(&do_log_change, &log_change_threshold); + CNF_GetMailOnChange(&do_mail_change, &mail_change_threshold, &mail_change_user); + + /* And just to prevent anything wierd ... */ + if (do_log_change) { + log_change_threshold = fabs(log_change_threshold); + } + + return; +} + +/* ================================================== */ + +void +REF_Finalise(void) +{ + if (logfile) { + fclose(logfile); + } + + initialised = 0; + return; +} + +/* ================================================== */ + +static double +Sqr(double x) +{ + return x*x; +} + +/* ================================================== */ +#if 0 +static double +Cube(double x) +{ + return x*x*x; +} +#endif + +/* ================================================== */ +/* Update the drift coefficients to the file. */ + +static void +update_drift_file(double freq_ppm, double skew) +{ + struct stat buf; + char *temp_drift_file; + FILE *out; + + /* Create a temporary file with a '.tmp' extension. */ + + temp_drift_file = (char*) Malloc(strlen(drift_file)+8); + + if(!temp_drift_file) { + return; + } + + strcpy(temp_drift_file,drift_file); + strcat(temp_drift_file,".tmp"); + + out = fopen(temp_drift_file, "w"); + if (!out) { + Free(temp_drift_file); + LOG(LOGS_WARN, LOGF_Reference, "Could not open temporary driftfile %s.tmp for writing", + drift_file); + return; + } + + /* Write the frequency and skew parameters in ppm */ + fprintf(out, "%20.4f %20.4f\n", freq_ppm, 1.0e6 * skew); + + fclose(out); + + /* Clone the file attributes from the existing file if there is one. */ + + if (!stat(drift_file,&buf)) { + chown(temp_drift_file,buf.st_uid,buf.st_gid); + chmod(temp_drift_file,buf.st_mode&0777); + } + + /* Rename the temporary file to the correct location (see rename(2) for details). */ + + if (rename(temp_drift_file,drift_file)) { + unlink(temp_drift_file); + Free(temp_drift_file); + LOG(LOGS_WARN, LOGF_Reference, "Could not replace old driftfile %s with new one %s.tmp (%d)", + drift_file,drift_file); + return; + } + + Free(temp_drift_file); +} + +/* ================================================== */ + +#define BUFLEN 255 +#define S_MAX_USER_LEN "128" + +static void +maybe_log_offset(double offset) +{ + double abs_offset; + FILE *p; + char buffer[BUFLEN], host[BUFLEN]; + time_t now; + struct tm stm; + + abs_offset = fabs(offset); + + if (do_log_change && + (abs_offset > log_change_threshold)) { + LOG(LOGS_WARN, LOGF_Reference, + "System clock wrong by %.6f seconds, adjustment started", + -offset); + } + + if (do_mail_change && + (abs_offset > mail_change_threshold)) { + sprintf(buffer, "%s %." S_MAX_USER_LEN "s", MAIL_PROGRAM, mail_change_user); + p = popen(buffer, "w"); + if (p) { + if (gethostname(host, sizeof(host)) < 0) { + strcpy(host, ""); + } + fprintf(p, "Subject: chronyd reports change to system clock on node [%s]\n", host); + fputs("\n", p); + now = time(NULL); + stm = *localtime(&now); + strftime(buffer, sizeof(buffer), "On %A, %d %B %Y\n with the system clock reading %H:%M:%S (%Z)", &stm); + 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, LOGF_Reference, + "Could not send mail notification to user %s\n", + mail_change_user); + } + } + +} + +/* ================================================== */ + + +void +REF_SetReference(int stratum, + NTP_Leap leap, + unsigned long ref_id, + struct timeval *ref_time, + double offset, + double frequency, + double skew, + double root_delay, + double root_dispersion + ) +{ + double previous_skew, new_skew; + double previous_freq, new_freq; + double old_weight, new_weight, sum_weight; + double delta_freq1, delta_freq2; + double skew1, skew2; + double our_frequency; + + double abs_freq_ppm; + + assert(initialised); + + /* If we get a serious rounding error in the source stats regression + processing, there is a remote chance that the skew argument is a + 'not a number'. If such a quantity gets propagated into the + machine's kernel clock variables, nasty things will happen .. + + To guard against this we need to check whether the skew argument + is a reasonable real number. I don't think isnan, isinf etc are + platform independent, so the following algorithm is used. */ + + { + double t; + t = (skew + skew) / skew; /* Skew shouldn't be zero either */ + if ((t < 1.9) || (t > 2.1)) { + LOG(LOGS_WARN, LOGF_Reference, "Bogus skew value encountered"); + return; + } + } + + + are_we_synchronised = 1; + our_stratum = stratum + 1; + our_leap_status = leap; + our_ref_id = ref_id; + our_ref_time = *ref_time; + our_offset = offset; + our_root_delay = root_delay; + our_root_dispersion = root_dispersion; + + /* Eliminate updates that are based on totally unreliable frequency + information */ + + if (fabs(skew) < max_update_skew) { + + previous_skew = our_skew; + new_skew = skew; + + previous_freq = 0.0; /* We assume that the local clock is running + according to our previously determined + value; note that this is a delta frequency + --- absolute frequencies are only known in + the local module. */ + new_freq = frequency; + + /* Set new frequency based on weighted average of old and new skew. */ + + old_weight = 1.0 / Sqr(previous_skew); + new_weight = 3.0 / Sqr(new_skew); + + sum_weight = old_weight + new_weight; + + our_frequency = (previous_freq * old_weight + new_freq * new_weight) / sum_weight; + + delta_freq1 = previous_freq - our_frequency; + delta_freq2 = new_freq - our_frequency; + + skew1 = sqrt((Sqr(delta_freq1) * old_weight + Sqr(delta_freq2) * new_weight) / sum_weight); + skew2 = (previous_skew * old_weight + new_skew * new_weight) / sum_weight; + our_skew = skew1 + skew2; + + our_residual_freq = new_freq - our_frequency; + + maybe_log_offset(our_offset); + LCL_AccumulateFrequencyAndOffset(our_frequency, our_offset); + + } else { + +#if 0 + LOG(LOGS_INFO, LOGF_Reference, "Skew %f too large to track, offset=%f", skew, our_offset); +#endif + maybe_log_offset(our_offset); + LCL_AccumulateOffset(our_offset); + + our_residual_freq = frequency; + } + + abs_freq_ppm = LCL_ReadAbsoluteFrequency(); + + if (logfile) { + + if (((logwrites++) % 32) == 0) { + fprintf(logfile, + "=======================================================================\n" + " Date (UTC) Time IP Address St Freq ppm Skew ppm Offset\n" + "=======================================================================\n"); + } + + fprintf(logfile, "%s %-15s %2d %10.3f %10.3f %10.3e\n", + UTI_TimeToLogForm(ref_time->tv_sec), + UTI_IPToDottedQuad(our_ref_id), + our_stratum, + abs_freq_ppm, + 1.0e6*our_skew, + our_offset); + + fflush(logfile); + } + + if (drift_file) { + update_drift_file(abs_freq_ppm, our_skew); + } + + /* And now set the freq and offset to zero */ + our_frequency = 0.0; + our_offset = 0.0; + + return; +} + +/* ================================================== */ + +void +REF_SetManualReference +( + struct timeval *ref_time, + double offset, + double frequency, + double skew +) +{ + int millisecond; + double abs_freq_ppm; + + /* We are not synchronised to an external source, as such. This is + only supposed to be used with the local source option, really + ... */ + are_we_synchronised = 0; + + our_skew = skew; + our_residual_freq = 0.0; + + maybe_log_offset(offset); + LCL_AccumulateFrequencyAndOffset(frequency, offset); + + abs_freq_ppm = LCL_ReadAbsoluteFrequency(); + + if (logfile) { + millisecond = ref_time->tv_usec / 1000; + + fprintf(logfile, "%5s %-15s %2d %10.3f %10.3f %10.3e\n", + UTI_TimeToLogForm(ref_time->tv_sec), + "127.127.1.1", + our_stratum, + abs_freq_ppm, + 1.0e6*our_skew, + our_offset); + + fflush(logfile); + } + + if (drift_file) { + update_drift_file(abs_freq_ppm, our_skew); + } +} + +/* ================================================== */ + +void +REF_SetUnsynchronised(void) +{ + /* Variables required for logging to statistics log */ + int millisecond; + struct timeval now; + double local_clock_err; + + assert(initialised); + + if (logfile) { + LCL_ReadCookedTime(&now, &local_clock_err); + + millisecond = now.tv_usec / 1000; + + fprintf(logfile, "%s %-15s 0 %10.3f %10.3f %10.3e\n", + UTI_TimeToLogForm(now.tv_sec), + "0.0.0.0", + LCL_ReadAbsoluteFrequency(), + 1.0e6*our_skew, + 0.0); + + fflush(logfile); + } + + are_we_synchronised = 0; +} + +/* ================================================== */ + +void +REF_GetReferenceParams +( + struct timeval *local_time, + int *is_synchronised, + NTP_Leap *leap_status, + int *stratum, + unsigned long *ref_id, + struct timeval *ref_time, + double *root_delay, + double *root_dispersion +) +{ + double elapsed; + double extra_dispersion; + + assert(initialised); + + if (are_we_synchronised) { + + *is_synchronised = 1; + + *stratum = our_stratum; + + UTI_DiffTimevalsToDouble(&elapsed, local_time, &our_ref_time); + extra_dispersion = (our_skew + fabs(our_residual_freq)) * elapsed; + + *leap_status = our_leap_status; + *ref_id = our_ref_id; + *ref_time = our_ref_time; + *root_delay = our_root_delay; + *root_dispersion = our_root_dispersion + extra_dispersion; + + } else if (enable_local_stratum) { + + *is_synchronised = 1; + + *stratum = local_stratum; + *ref_id = LOCAL_REFERENCE_ID; + /* Make the reference time be now less a second - this will + scarcely affect the client, but will ensure that the transmit + timestamp cannot come before this (which would cause test 6 to + fail in the client's read routine) if the local system clock's + read routine is broken in any way. */ + *ref_time = *local_time; + --ref_time->tv_sec; + + /* 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 = LCL_GetSysPrecisionAsQuantum(); + + } else { + + *is_synchronised = 0; + + *leap_status = LEAP_Unsynchronised; + *stratum = 0; + *ref_id = 0UL; + ref_time->tv_sec = ref_time->tv_usec = 0; + /* 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) +{ + if (are_we_synchronised) { + return our_stratum; + } else if (enable_local_stratum) { + return local_stratum; + } else { + return 16; + } +} + +/* ================================================== */ + +void +REF_ModifyMaxupdateskew(double new_max_update_skew) +{ + max_update_skew = new_max_update_skew * 1.0e-6; +#if 0 + LOG(LOGS_INFO, LOGF_Reference, "New max update skew = %.3fppm", new_max_update_skew); +#endif +} + +/* ================================================== */ + +void +REF_EnableLocal(int stratum) +{ + enable_local_stratum = 1; + local_stratum = stratum; +} + +/* ================================================== */ + +void +REF_DisableLocal(void) +{ + enable_local_stratum = 0; +} + +/* ================================================== */ + +void +REF_GetTrackingReport(RPT_TrackingReport *rep) +{ + double elapsed; + double extra_dispersion; + struct timeval now_raw, now_cooked; + double correction; + + LCL_ReadRawTime(&now_raw); + correction = LCL_GetOffsetCorrection(&now_raw); + UTI_AddDoubleToTimeval(&now_raw, correction, &now_cooked); + + if (are_we_synchronised) { + + UTI_DiffTimevalsToDouble(&elapsed, &now_cooked, &our_ref_time); + extra_dispersion = (our_skew + fabs(our_residual_freq)) * elapsed; + + rep->ref_id = our_ref_id; + rep->stratum = our_stratum; + rep->ref_time = our_ref_time; + UTI_DoubleToTimeval(correction, &rep->current_correction); + rep->freq_ppm = LCL_ReadAbsoluteFrequency(); + rep->resid_freq_ppm = 1.0e6 * our_residual_freq; + rep->skew_ppm = 1.0e6 * our_skew; + rep->root_delay = our_root_delay; + rep->root_dispersion = our_root_dispersion + extra_dispersion; + + } else if (enable_local_stratum) { + + rep->ref_id = LOCAL_REFERENCE_ID; + rep->stratum = local_stratum; + rep->ref_time = now_cooked; + UTI_DoubleToTimeval(correction, &rep->current_correction); + rep->freq_ppm = LCL_ReadAbsoluteFrequency(); + rep->resid_freq_ppm = 0.0; + rep->skew_ppm = 0.0; + rep->root_delay = 0.0; + rep->root_dispersion = LCL_GetSysPrecisionAsQuantum(); + + } else { + + rep->ref_id = 0UL; + rep->stratum = 0; + rep->ref_time.tv_sec = 0; + rep->ref_time.tv_usec = 0; + UTI_DoubleToTimeval(correction, &rep->current_correction); + rep->freq_ppm = LCL_ReadAbsoluteFrequency(); + rep->resid_freq_ppm = 0.0; + rep->skew_ppm = 0.0; + rep->root_delay = 0.0; + rep->root_dispersion = 0.0; + } + +} + +/* ================================================== */ + +void +REF_CycleLogFile(void) +{ + if (logfile && logfilename) { + fclose(logfile); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_Reference, "Could not reopen logfile %s", logfilename); + } + logwrites = 0; + } +} + +/* ================================================== */ diff --git a/reference.h b/reference.h new file mode 100644 index 0000000..19b3f0e --- /dev/null +++ b/reference.h @@ -0,0 +1,147 @@ +/* + $Header: /cvs/src/chrony/reference.h,v 1.13 2002/02/28 23:27:12 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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" + +/* Init function */ +extern void REF_Initialise(void); + +/* Fini function */ +extern void REF_Finalise(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 timeval *local_time, + int *is_synchronised, + NTP_Leap *leap, + int *stratum, + unsigned long *ref_id, + struct timeval *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, + unsigned long ref_id, + struct timeval *ref_time, + double offset, + double frequency, + double skew, + double root_delay, + double root_dispersion +); + +extern void REF_SetManualReference +( + struct timeval *ref_time, + double offset, + double frequency, + double skew +); + +/* Mark the local clock as unsynchronised */ +extern void +REF_SetUnsynchronised(void); + +/* Return the current stratum of this host or zero if the host is not + synchronised */ +extern int REF_GetOurStratum(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); + +extern void REF_EnableLocal(int stratum); +extern void REF_DisableLocal(void); + +extern void REF_GetTrackingReport(RPT_TrackingReport *rep); + +extern void REF_CycleLogFile(void); + +#endif /* GOT_REFERENCE_H */ diff --git a/regress.c b/regress.c new file mode 100644 index 0000000..4b6ab0f --- /dev/null +++ b/regress.c @@ -0,0 +1,628 @@ +/* + $Header: /cvs/src/chrony/regress.c,v 1.31 2003/01/20 22:24:20 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Regression algorithms. + + */ + +#include +#include + +#include +#include + +#include "regress.h" +#include "logging.h" +#include "util.h" + +#define MAX_POINTS 128 + +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; + + if (n<3) { + CROAK("Insufficient points"); + } + + W = U = 0; + for (i=0; i 0.0) && (resid[i] > 0.0))) { + /* Nothing to do */ + } else { + nruns++; + } + } + + return nruns; +} + +/* ================================================== */ +/* Return a boolean indicating whether we had enough points for + regression */ + +#define RESID_SIZE 1024 +#define MIN_SAMPLES_FOR_REGRESS 3 + +int +RGR_FindBestRegression +(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 */ + + 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[RESID_SIZE]; + double ss; + double a, b, u, ui, aa; + + int start, nruns, npoints, npoints_left; + int i; + + if (n < MIN_SAMPLES_FOR_REGRESS) { + return 0; + } + + start = 0; + do { + + W = U = 0; + for (i=start; i critical_runs10[npoints]) || (npoints_left < MIN_SAMPLES_FOR_REGRESS)) { + 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 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 (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; + } else { + CROAK("Impossible"); + } + } + } 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) +{ + int flags[MAX_POINTS]; + + bzero(flags, n * sizeof(int)); + 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; + int flags[MAX_POINTS]; + + memset(flags, 0, n*sizeof(int)); + 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)); + } +} + +/* ================================================== */ +/* 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 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; + + 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 0.0) { + incr = 3.0 * sb; + } else { + incr = 3.0 * tol; + } + + blo = b; + bhi = b; + + do { + blo -= incr; + bhi += 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, i.e. root not + in interval. */ + + /* OK, so the root for b lies in (blo, bhi). Start bisecting */ + do { + bmid = 0.5 * (blo + bhi); + 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 { + CROAK("Impossible"); + } + } 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 critical_runs10[n_points]) { + break; + } else { + start++; + } + + } while (1); + + *n_runs = nruns; + *best_start = start; + + return 1; + +} + +/* ================================================== */ diff --git a/regress.h b/regress.h new file mode 100644 index 0000000..fb443ff --- /dev/null +++ b/regress.h @@ -0,0 +1,108 @@ +/* + $Header: /cvs/src/chrony/regress.h,v 1.13 2002/02/28 23:27:13 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 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 */ + + /* 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); + +#endif /* GOT_REGRESS_H */ diff --git a/reports.h b/reports.h new file mode 100644 index 0000000..a2bdb97 --- /dev/null +++ b/reports.h @@ -0,0 +1,121 @@ +/* + $Header: /cvs/src/chrony/reports.h,v 1.17 2002/02/28 23:27:13 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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" + +#define REPORT_INVALID_OFFSET 0x80000000 + +typedef struct { + unsigned long ip_addr; + int stratum; + int poll; + enum {RPT_NTP_CLIENT, RPT_NTP_PEER, RPT_LOCAL_REFERENCE} mode; + enum {RPT_SYNC, RPT_UNREACH, RPT_FALSETICKER, RPT_JITTERY, RPT_OTHER} state; + + unsigned long latest_meas_ago; /* seconds */ + long orig_latest_meas; /* microseconds (us) */ + long latest_meas; /* us */ + unsigned long latest_meas_err; /* us */ + long est_offset; /* us */ + unsigned long est_offset_err; /* us */ + long resid_freq; /* ppm * 1000 */ + unsigned long resid_skew; /* ppm * 1000 */ +} RPT_SourceReport ; + +typedef struct { + unsigned long ref_id; + unsigned long stratum; + struct timeval ref_time; + struct timeval current_correction; + double freq_ppm; + double resid_freq_ppm; + double skew_ppm; + double root_delay; + double root_dispersion; +} RPT_TrackingReport; + +typedef struct { + unsigned long ip_addr; + unsigned long n_samples; + unsigned long n_runs; + unsigned long span_seconds; + double resid_freq_ppm; + double skew_ppm; + double sd_us; +} RPT_SourcestatsReport; + +typedef struct { + unsigned long ref_time; + unsigned short n_samples; + unsigned short n_runs; + unsigned long span_seconds; + double rtc_seconds_fast; + double rtc_gain_rate_ppm; +} RPT_RTC_Report; + +typedef struct { + unsigned long client_hits; + unsigned long peer_hits; + unsigned long cmd_hits_auth; + unsigned long cmd_hits_normal; + unsigned long cmd_hits_bad; + unsigned long last_ntp_hit_ago; + unsigned long last_cmd_hit_ago; +} RPT_ClientAccess_Report; + +typedef struct { + unsigned long ip_addr; + unsigned long client_hits; + unsigned long peer_hits; + unsigned long cmd_hits_auth; + unsigned long cmd_hits_normal; + unsigned long cmd_hits_bad; + unsigned long last_ntp_hit_ago; + unsigned long last_cmd_hit_ago; +} RPT_ClientAccessByIndex_Report; + +typedef struct { + time_t when; + double slewed_offset; + double orig_offset; + double residual; +} RPT_ManualSamplesReport; + +typedef struct { + int online; + int offline; + int burst_online; + int burst_offline; +} RPT_ActivityReport; + +#endif /* GOT_REPORTS_H */ diff --git a/rtc.c b/rtc.c new file mode 100644 index 0000000..fbb8884 --- /dev/null +++ b/rtc.c @@ -0,0 +1,222 @@ +/* + $Header: /cvs/src/chrony/rtc.c,v 1.13 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + */ + +#include "sysincl.h" + +#include "rtc.h" +#include "logging.h" +#include "conf.h" + +#if defined LINUX +#include "rtc_linux.h" +#endif /* defined LINUX */ + +/* ================================================== */ + +static int driver_initialised = 0; + +static struct { + int (*init)(void); + void (*fini)(void); + void (*time_pre_init)(void); + 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); + void (*cycle_logfile)(void); +} driver = +{ +#if defined LINUX + RTC_Linux_Initialise, + RTC_Linux_Finalise, + RTC_Linux_TimePreInit, + RTC_Linux_TimeInit, + RTC_Linux_StartMeasurements, + RTC_Linux_WriteParameters, + RTC_Linux_GetReport, + RTC_Linux_Trim, + RTC_Linux_CycleLogFile +#else + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +#endif +}; + +/* ================================================== */ + +void +RTC_Initialise(void) +{ + char *file_name; + int ok; + + /* 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 (driver.init) { + if ((driver.init)()) { + ok = 1; + } else { + ok = 0; + } + } else { + ok = 0; + } + + if (ok) { + driver_initialised = 1; + } else { + driver_initialised = 0; + LOG(LOGS_ERR, LOGF_Rtc, "Real time clock not supported on this operating system"); + } + + } else { + driver_initialised = 0; + } + + return; + +} + +/* ================================================== */ + +void +RTC_Finalise(void) +{ + if (driver.fini) { + (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. The + TimePreInit routine has already run, so 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.time_init)(after_hook, anything); + } else { + LOG(LOGS_ERR, LOGF_Rtc, "Can't initialise from real time clock, driver not loaded"); + (after_hook)(anything); + } +} + +/* ================================================== */ +/* Do an initial read of the RTC and set the system time to it. This + is analogous to what /sbin/clock -s -u would do on Linux. */ + +void +RTC_TimePreInit(void) +{ + if (driver.time_pre_init) { + (driver.time_pre_init)(); + } +} + +/* ================================================== */ +/* 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; + } +} + +/* ================================================== */ + +void +RTC_CycleLogFile(void) +{ + if (driver_initialised) { + (driver.cycle_logfile)(); + } +} + +/* ================================================== */ + diff --git a/rtc.h b/rtc.h new file mode 100644 index 0000000..3cfdb87 --- /dev/null +++ b/rtc.h @@ -0,0 +1,52 @@ +/* + $Header: /cvs/src/chrony/rtc.h,v 1.9 2002/02/28 23:27:13 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + */ + +#ifndef GOT_RTC_H +#define GOT_RTC_H + +#include "reports.h" + +extern void RTC_Initialise(void); +extern void RTC_Finalise(void); +extern void RTC_TimePreInit(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); + +extern void RTC_CycleLogFile(void); + +#endif /* GOT_RTC_H */ diff --git a/rtc_linux.c b/rtc_linux.c new file mode 100644 index 0000000..b01e584 --- /dev/null +++ b/rtc_linux.c @@ -0,0 +1,1174 @@ +/* + $Header: /cvs/src/chrony/rtc_linux.c,v 1.30 2003/04/01 20:07:20 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Real-time clock driver for linux. This interfaces the program with + the clock that keeps time when the machine is turned off. + + */ + +#if defined LINUX + +#ifdef sparc +#define __KERNEL__ +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef HAS_SPINLOCK_H +#include +#else +/* Include dummy definition of spinlock_t to cope with earlier kernels. */ +typedef int spinlock_t; +#endif + +/* This is a complete hack since the alpha sys/io.h needs these types + * but does not arrange them to be defined. This is almost certainly + * not how one should do these things. -- broonie + */ +#include +#ifdef __alpha__ +typedef __u8 u8; +typedef __u16 u16; +typedef __u32 u32; +typedef __u64 u64; +#endif + +#if defined(__i386__) /* || defined(__sparc__) */ +#include +#else +#include +#define RTC_UIE 0x10 /* update-finished interrupt enable */ +#endif + +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "sched.h" +#include "local.h" +#include "util.h" +#include "sys_linux.h" +#include "regress.h" +#include "rtc.h" +#include "rtc_linux.h" +#include "conf.h" +#include "memory.h" +#include "mkdirpp.h" + +/* ================================================== */ +/* Forward prototypes */ + +static void measurement_timeout(void *any); + +static void read_from_device(void *any); + +/* ================================================== */ + +typedef enum { + OM_NORMAL, + OM_INITIAL, + OM_AFTERTRIM +} OperatingMode; + +static OperatingMode operating_mode = OM_NORMAL; + +/* ================================================== */ + +static int fd = -1; + +#define LOWEST_MEASUREMENT_PERIOD 15 +#define HIGHEST_MEASUREMENT_PERIOD 480 + +/* Try to avoid doing regression after _every_ sample we accumulate */ +#define N_SAMPLES_PER_REGRESSION 4 + +static int measurement_period = LOWEST_MEASUREMENT_PERIOD; + +static int timeout_running = 0; +static SCH_TimeoutID timeout_id; + +/* ================================================== */ + +/* Maximum number of samples held */ +#define MAX_SAMPLES 64 + +/* Real time clock samples. We store the seconds count as originally + measured, together with a 'trim' that compensates these values for + any steps made to the RTC to bring it back into line + occasionally. The trim is in seconds. */ +static time_t rtc_sec[MAX_SAMPLES]; +static double rtc_trim[MAX_SAMPLES]; + +/* Reference time, against which delta times on the RTC scale are measured */ +static time_t rtc_ref; + + +/* System clock (gettimeofday) samples associated with the above + samples. */ +static struct timeval system_times[MAX_SAMPLES]; + +/* 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; + +/* 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 = 0; + +/* ================================================== */ + +static FILE *logfile=NULL; +static char *logfilename = NULL; +static unsigned long logwrites=0; + +#define RTC_LOG "rtc.log" + +/* ================================================== */ + +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; + + if (!(new_first < n_samples)) { + CROAK("new_first should be < n_samples"); + } + if (!(new_first >= 0)) { + CROAK("new_first should be non-negative"); + } + + n_to_save = n_samples - new_first; + + memmove(rtc_sec, rtc_sec + new_first, n_to_save * sizeof(time_t)); + memmove(rtc_trim, rtc_trim + new_first, n_to_save * sizeof(double)); + memmove(system_times, system_times + new_first, n_to_save * sizeof(struct timeval)); + + n_samples = n_to_save; + return; +} + +/* ================================================== */ + +#define NEW_FIRST_WHEN_FULL 4 + +static void +accumulate_sample(time_t rtc, struct timeval *sys) +{ + + if (n_samples == MAX_SAMPLES) { + /* Discard oldest samples */ + discard_samples(NEW_FIRST_WHEN_FULL); + } + + rtc_sec[n_samples] = rtc; + + /* Always use most recent sample as reference */ + rtc_ref = rtc; + + rtc_trim[n_samples] = 0.0; + system_times[n_samples] = *sys; + ++n_samples; + ++n_samples_since_regression; + return; + +} + +/* ================================================== */ +/* 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, n; + double est_intercept, est_slope; + int best_new_start; + + if (n_samples > 0) { + + n = n_samples - 1; + + for (i=0; i 0) { + discard_samples(best_new_start); + } + + + } else { + /* Keep existing coefficients. */ + } + } else { + /* Keep existing coefficients. */ + } + +} + +/* ================================================== */ + +static void +slew_samples +(struct timeval *raw, struct timeval *cooked, + double dfreq, double afreq_ppm, + double doffset, int is_step_change, + void *anything) +{ + int i; + double elapsed; + double new_freq; + double old_freq; + double delta_time; + double old_seconds_fast, old_gain_rate; + + new_freq = 1.0e-6 * afreq_ppm; + old_freq = (new_freq - dfreq) / (1.0 - dfreq); + + for (i=0; i=32 the adjtimex()/RTC behaviour was + modified, so that as long as the STA_UNSYNC flag is set the RTC + is left alone. This is the mode we exploit here, so that the RTC + continues to go its own sweet way, unless we make updates to it + from this module. + + Linux 2.1.x - don't know, haven't got a system to look at. + + Linux 2.2.x, 2.3.x and 2.4.x are believed to be OK for all + patch levels + + */ + + SYS_Linux_GetKernelVersion(&major, &minor, &patch); + + /* Obviously this test can get more elaborate when we know about + more system types. */ + if (major != 2) { + return 0; + } else { + switch (minor) { + case 0: + if (patch <= 31) { + return 0; + } + break; + case 1: + return 0; + break; + case 2: + case 3: + case 4: + break; /* OK for all patch levels */ + } + } + + /* Setup details depending on configuration options */ + setup_config(); + + /* In case it didn't get done by pre-init */ + coefs_file_name = CNF_GetRtcFile(); + + /* Try to open device */ + + fd = open (CNF_GetRtcDevice(), O_RDWR); + if (fd < 0) { + LOG(LOGS_ERR, LOGF_RtcLinux, "Could not open %s, %s", CNF_GetRtcDevice(), strerror(errno)); + return 0; + } + + 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_AddInputFileHandler(fd, read_from_device, NULL); + + /* Register slew handler */ + LCL_AddParameterChangeHandler(slew_samples, NULL); + + if (CNF_GetLogRtc()) { + direc = CNF_GetLogDir(); + if (!mkdir_and_parents(direc)) { + LOG(LOGS_ERR, LOGF_RtcLinux, "Could not create directory %s", direc); + logfile = NULL; + } else { + logfilename = MallocArray(char, 2 + strlen(direc) + strlen(RTC_LOG)); + strcpy(logfilename, direc); + strcat(logfilename, "/"); + strcat(logfilename, RTC_LOG); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_RtcLinux, "Couldn't open logfile %s for update", logfilename); + } + } + } + + return 1; +} + +/* ================================================== */ + +void +RTC_Linux_Finalise(void) +{ + if (timeout_running) { + SCH_RemoveTimeout(timeout_id); + timeout_running = 0; + } + + /* Remove input file handler */ + if (fd >= 0) { + SCH_RemoveInputFileHandler(fd); + close(fd); + + /* Save the RTC data */ + (void) RTC_Linux_WriteParameters(); + + } + + if (logfile) { + fclose(logfile); + } + +} + +/* ================================================== */ + +static void +switch_interrupts(int onoff) +{ + int status; + + if (onoff) { + status = ioctl(fd, RTC_UIE_ON, 0); + if (status < 0) { + LOG(LOGS_ERR, LOGF_RtcLinux, "Could not start measurement : %s", strerror(errno)); + return; + } + } else { + status = ioctl(fd, RTC_UIE_OFF, 0); + if (status < 0) { + LOG(LOGS_ERR, LOGF_RtcLinux, "Could not stop measurement : %s", strerror(errno)); + return; + } + } +} + +/* ================================================== */ + +static void +measurement_timeout(void *any) +{ + timeout_running = 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; + + status = ioctl(fd, RTC_SET_TIME, &rtc_raw); + if (status < 0) { + LOG(LOGS_ERR, LOGF_RtcLinux, "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; + + LOG(LOGS_INFO, LOGF_RtcLinux, "System trim from RTC = %f", sys_error_now); + LCL_AccumulateOffset(sys_error_now); + } else { + LOG(LOGS_WARN, LOGF_RtcLinux, "No valid file coefficients, cannot trim system time"); + } + + coefs_valid = 0; + + (after_init_hook)(after_init_hook_arg); + + operating_mode = OM_NORMAL; + + return; +} + +/* ================================================== */ + +static void +handle_relock_after_trim(void) +{ + int valid; + time_t ref; + double fast, slope; + + run_regression(1, &valid, &ref, &fast, &slope); + + if (valid) { + write_coefs_to_file(1,ref,fast,saved_coef_gain_rate); + } else { + LOG(LOGS_WARN, LOGF_RtcLinux, "Could not do regression after trim"); + } + + n_samples = 0; + n_samples_since_regression = 0; + operating_mode = OM_NORMAL; + measurement_period = LOWEST_MEASUREMENT_PERIOD; +} + +/* ================================================== */ + +/* Day number of 1 Jan 1970 */ +#define MJD_1970 40587 + +static void +process_reading(time_t rtc_time, struct timeval *system_time) +{ + double rtc_fast; + + accumulate_sample(rtc_time, system_time); + + switch (operating_mode) { + case OM_NORMAL: + + if (n_samples_since_regression >= /* 4 */ 1 ) { + run_regression(1, &coefs_valid, &coef_ref_time, &coef_seconds_fast, &coef_gain_rate); + n_samples_since_regression = 0; + } + + 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: + CROAK("Impossible"); + break; + } + + + if (logfile) { + rtc_fast = (double)(rtc_time - system_time->tv_sec) - 1.0e-6 * (double) system_time->tv_usec; + + if (((logwrites++) % 32) == 0) { + fprintf(logfile, + "===============================================================================\n" + " Date (UTC) Time RTC fast (s) Val Est fast (s) Slope (ppm) Ns Nr Meas\n" + "===============================================================================\n"); + } + + fprintf(logfile, "%s %14.6f %1d %14.6f %12.3f %2d %2d %4d\n", + UTI_TimeToLogForm(system_time->tv_sec), + rtc_fast, + coefs_valid, + coef_seconds_fast, coef_gain_rate * 1.0e6, n_samples, n_runs, measurement_period); + + fflush(logfile); + } + +} + +/* ================================================== */ + +static void +read_from_device(void *any) +{ + int status; + unsigned long data; + struct timeval sys_time; + struct rtc_time rtc_raw; + struct tm rtc_tm; + time_t rtc_t; + double read_err; + int error = 0; + + status = read(fd, &data, sizeof(data)); + if (status < 0) { + LOG(LOGS_ERR, LOGF_RtcLinux, "Could not read flags %s : %s", CNF_GetRtcDevice(), strerror(errno)); + error = 1; + goto turn_off_interrupt; + } + + if ((data & RTC_UIE) == RTC_UIE) { + /* Update interrupt detected */ + + /* Read RTC time, sandwiched between two polls of the system clock + so we can bound any error. */ + + LCL_ReadCookedTime(&sys_time, &read_err); + + status = ioctl(fd, RTC_RD_TIME, &rtc_raw); + if (status < 0) { + LOG(LOGS_ERR, LOGF_RtcLinux, "Could not read time from %s : %s", CNF_GetRtcDevice(), strerror(errno)); + error = 1; + goto turn_off_interrupt; + } + + /* Convert RTC time into a struct timeval */ + 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)) { + LOG(LOGS_ERR, LOGF_RtcLinux, "Could not convert RTC time to timeval"); + 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) { + LOG(LOGS_WARN, LOGF_RtcLinux, "Could not complete initial step due to errors"); + operating_mode = OM_NORMAL; + (after_init_hook)(after_init_hook_arg); + + switch_interrupts(0); + + timeout_running = 1; + timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL); + } + + break; + + case OM_AFTERTRIM: + if (error) { + LOG(LOGS_WARN, LOGF_RtcLinux, "Could not complete after trim relock due to errors"); + operating_mode = OM_NORMAL; + + switch_interrupts(0); + + timeout_running = 1; + timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL); + } + + break; + + case OM_NORMAL: + switch_interrupts(0); + + timeout_running = 1; + timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL); + + break; + default: + CROAK("Impossible"); + 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_running = 0; + switch_interrupts(1); + +} + +/* ================================================== */ + +void +RTC_Linux_StartMeasurements(void) +{ + timeout_running = 0; + 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/clock -s -u 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. */ + +void +RTC_Linux_TimePreInit(void) +{ + int fd, status; + struct rtc_time rtc_raw; + struct tm rtc_tm; + time_t rtc_t, estimated_correct_rtc_t; + long interval; + double accumulated_error = 0.0; + struct timeval new_sys_time; + + coefs_file_name = CNF_GetRtcFile(); + + setup_config(); + read_coefs_from_file(); + + fd = open(CNF_GetRtcDevice(), O_RDONLY); + + if (fd < 0) { + return; /* Can't open it, and won't be able to later */ + } + + status = ioctl(fd, RTC_RD_TIME, &rtc_raw); + + 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) { + interval = rtc_t - file_ref_time; + accumulated_error = file_ref_offset + (double)(interval) * 1.0e-6 * file_rate_ppm; + + /* Correct time */ + LOG(LOGS_INFO, LOGF_RtcLinux, "Set system time, error in RTC = %f", + accumulated_error); + estimated_correct_rtc_t = rtc_t - (long)(0.5 + accumulated_error); + } else { + estimated_correct_rtc_t = rtc_t - (long)(0.5 + accumulated_error); + } + + new_sys_time.tv_sec = estimated_correct_rtc_t; + new_sys_time.tv_usec = 0; + + /* Tough luck if this fails */ + if (settimeofday(&new_sys_time, NULL) < 0) { + LOG(LOGS_WARN, LOGF_RtcLinux, "Could not settimeofday"); + } + } else { + LOG(LOGS_WARN, LOGF_RtcLinux, "Could not convert RTC reading to seconds since 1/1/1970"); + } + } + + close(fd); +} + +/* ================================================== */ + +int +RTC_Linux_GetReport(RPT_RTC_Report *report) +{ + report->ref_time = (unsigned long) coef_ref_time; + 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]) + + (long)(rtc_trim[n_samples-1] - rtc_trim[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 timeval now; + double local_clock_err; + + + /* 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, LOGF_RtcLinux, "Trimming RTC, error = %.3f seconds", 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, &local_clock_err); + + set_rtc(now.tv_sec); + + /* All old samples will now look bogus under the new + regime. */ + n_samples = 0; + operating_mode = OM_AFTERTRIM; + + /* And start rapid sampling, interrupts on now */ + if (timeout_running) { + SCH_RemoveTimeout(timeout_id); + timeout_running = 0; + } + switch_interrupts(1); + } + + return 1; + +} + +/* ================================================== */ + +void +RTC_Linux_CycleLogFile(void) +{ + if (logfile && logfilename) { + fclose(logfile); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_RtcLinux, "Could not reopen logfile %s", logfilename); + } + logwrites = 0; + } +} + +/* ================================================== */ + +#endif /* defined LINUX */ diff --git a/rtc_linux.h b/rtc_linux.h new file mode 100644 index 0000000..7c0025a --- /dev/null +++ b/rtc_linux.h @@ -0,0 +1,53 @@ +/* + $Header: /cvs/src/chrony/rtc_linux.h,v 1.13 2002/02/28 23:27:13 richard Exp $ + + ====================================================================== + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ====================================================================== + + */ + +#ifndef _GOT_RTC_LINUX_H +#define _GOT_RTC_LINUX_H + +#include "reports.h" + +#if defined LINUX + +extern int RTC_Linux_Initialise(void); +extern void RTC_Linux_Finalise(void); +extern void RTC_Linux_TimePreInit(void); +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 /* defined LINUX */ + +#endif /* _GOT_RTC_LINUX_H */ diff --git a/sched.c b/sched.c new file mode 100644 index 0000000..2843a64 --- /dev/null +++ b/sched.c @@ -0,0 +1,601 @@ +/* + $Header: /cvs/src/chrony/sched.c,v 1.16 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + This file contains the scheduling loop and the timeout queue. + + */ + +#include "sysincl.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; + +/* ================================================== */ + +/* Variables to handle the capability to dispatch on particular file + handles becoming readable */ + +/* Each bit set in this fd set corresponds to a read descriptor that + we are watching and with which we have a handler associated in the + file_handlers array */ +static fd_set read_fds; + +/* This is the number of bits that we have set in read_fds */ +static unsigned int n_read_fds; + +/* One more than the highest file descriptor that is registered */ +static unsigned int one_highest_fd; + +/* This assumes that fd_set is implemented as a fixed size array of + bits, possibly embedded inside a record. It might therefore + somewhat non-portable. */ + +#define FD_SET_SIZE (sizeof(fd_set) * 8) + +typedef struct { + SCH_FileHandler handler; + SCH_ArbitraryArgument arg; +} FileHandlerEntry; + +static FileHandlerEntry file_handlers[FD_SET_SIZE]; + +/* ================================================== */ + +/* Variables to handler the timer queue */ + +typedef struct _TimerQueueEntry +{ + struct _TimerQueueEntry *next; /* Forward and back links in the list */ + struct _TimerQueueEntry *prev; + struct timeval tv; /* 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; + +/* ================================================== */ + +static int need_to_exit; + +/* ================================================== */ + +static void +handle_slew(struct timeval *raw, + struct timeval *cooked, + double dfreq, + double afreq, + double doffset, + int is_step_change, + void *anything); + +/* ================================================== */ + +void +SCH_Initialise(void) +{ + + FD_ZERO(&read_fds); + n_read_fds = 0; + + 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); + + initialised = 1; + + return; +} + + +/* ================================================== */ + +void +SCH_Finalise(void) { + initialised = 0; + return; /* Nothing to do for now */ +} + +/* ================================================== */ + +void +SCH_AddInputFileHandler +(int fd, SCH_FileHandler handler, SCH_ArbitraryArgument arg) +{ + + if (!initialised) { + CROAK("Should be initialised"); + } + + /* 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. */ + if (FD_ISSET(fd, &read_fds)) { + CROAK("File handler already registered"); + } + + ++n_read_fds; + + file_handlers[fd].handler = handler; + file_handlers[fd].arg = arg; + + FD_SET(fd, &read_fds); + + if ((fd + 1) > one_highest_fd) { + one_highest_fd = fd + 1; + } + + return; +} + + +/* ================================================== */ + +void +SCH_RemoveInputFileHandler(int fd) +{ + int fds_left, fd_to_check; + + if (!initialised) { + CROAK("Should be initialised"); + } + + /* Check that a handler was registered for the fd in question */ + if (!FD_ISSET(fd, &read_fds)) { + CROAK("File handler not registered"); + } + + --n_read_fds; + + FD_CLR(fd, &read_fds); + + /* Find new highest file descriptor */ + fds_left = n_read_fds; + fd_to_check = 0; + while (fds_left > 0) { + if (FD_ISSET(fd_to_check, &read_fds)) { + --fds_left; + } + ++fd_to_check; + } + + one_highest_fd = fd_to_check; + + return; + +} + +/* ================================================== */ + +#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; inext; + return result; +} + +/* ================================================== */ + +static void +release_tqe(TimerQueueEntry *node) +{ + node->next = tqe_free_list; + tqe_free_list = node; + return; +} + +/* ================================================== */ + +SCH_TimeoutID +SCH_AddTimeout(struct timeval *tv, SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg) +{ + TimerQueueEntry *new_tqe; + TimerQueueEntry *ptr; + + if (!initialised) { + CROAK("Should be initialised"); + } + + new_tqe = allocate_tqe(); + + new_tqe->id = next_tqe_id++; + new_tqe->handler = handler; + new_tqe->arg = arg; + new_tqe->tv = *tv; + 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_CompareTimevals(&new_tqe->tv, &ptr->tv) == -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 timeval now, then; + + if (!initialised) { + CROAK("Should be initialised"); + } + + LCL_ReadRawTime(&now); + UTI_AddDoubleToTimeval(&now, delay, &then); + return SCH_AddTimeout(&then, handler, arg); + +} + +/* ================================================== */ + +SCH_TimeoutID +SCH_AddTimeoutInClass(double min_delay, double separation, + SCH_TimeoutClass class, + SCH_TimeoutHandler handler, SCH_ArbitraryArgument arg) +{ + TimerQueueEntry *new_tqe; + TimerQueueEntry *ptr; + struct timeval now; + double diff; + double new_min_delay; + + if (!initialised) { + CROAK("Should be initialised"); + } + + LCL_ReadRawTime(&now); + new_min_delay = min_delay; + + /* 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) { + UTI_DiffTimevalsToDouble(&diff, &ptr->tv, &now); + if (new_min_delay > diff) { + if (new_min_delay - diff < separation) { + new_min_delay = diff + separation; + } + } + if (new_min_delay < diff) { + if (diff - new_min_delay < separation) { + new_min_delay = diff + separation; + } + } + } + } + + for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) { + UTI_DiffTimevalsToDouble(&diff, &ptr->tv, &now); + if (diff > new_min_delay) { + break; + } + } + + /* We have located the insertion point */ + new_tqe = allocate_tqe(); + + new_tqe->id = next_tqe_id++; + new_tqe->handler = handler; + new_tqe->arg = arg; + UTI_AddDoubleToTimeval(&now, new_min_delay, &new_tqe->tv); + 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; + int ok; + + if (!initialised) { + CROAK("Should be initialised"); + } + + ok = 0; + + 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); + + ok = 1; + + break; + } + } + + assert(ok); + +} + +/* ================================================== */ +/* The current time (now) has to be passed in from the + caller to avoid race conditions */ + +static int +dispatch_timeouts(struct timeval *now) { + TimerQueueEntry *ptr; + int n_done = 0; + + while ((n_timer_queue_entries > 0) && + (UTI_CompareTimevals(now, &(timer_queue.next->tv)) >= 0)) { + ptr = timer_queue.next; + + /* Dispatch the handler */ + (ptr->handler)(ptr->arg); + + /* Increment count of timeouts handled */ + ++n_done; + + /* Unlink entry from the queue */ + ptr->prev->next = ptr->next; + ptr->next->prev = ptr->prev; + + /* Decrement count of entries in queue */ + --n_timer_queue_entries; + + /* Delete entry */ + release_tqe(ptr); + } + + return n_done; + +} + +/* ================================================== */ + +/* nfh is the number of bits set in fhs */ + +static void +dispatch_filehandlers(int nfh, fd_set *fhs) +{ + int fh = 0; + + while (nfh > 0) { + if (FD_ISSET(fh, fhs)) { + + /* This descriptor can be read from, dispatch its handler */ + (file_handlers[fh].handler)(file_handlers[fh].arg); + + /* Decrement number of readable files still to find */ + --nfh; + } + + ++fh; + } + +} + +/* ================================================== */ + +static void +handle_slew(struct timeval *raw, + struct timeval *cooked, + double dfreq, + double afreq, + double doffset, + int is_step_change, + void *anything) +{ + TimerQueueEntry *ptr; + struct timeval T1; + + if (is_step_change) { + /* We're not interested in anything else - it won't affect the + functionality of timer event dispatching. If a step change + occurs, just shift all the timeouts by the offset */ + + for (ptr = timer_queue.next; ptr != &timer_queue; ptr = ptr->next) { + UTI_AddDoubleToTimeval(&ptr->tv, -doffset, &T1); + ptr->tv = T1; + } + + } +} + +/* ================================================== */ + +void +SCH_MainLoop(void) +{ + fd_set rd, wr, ex; + int status; + struct timeval tv, *ptv; + struct timeval now; + + if (!initialised) { + CROAK("Should be initialised"); + } + + while (!need_to_exit) { + + /* Copy current set of read file descriptors */ + memcpy((void *) &rd, (void *) &read_fds, sizeof(fd_set)); + + /* Blank the write and exception descriptors - we aren't very + interested */ + FD_ZERO(&wr); + FD_ZERO(&ex); + + /* 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). */ + + do { + LCL_ReadRawTime(&now); + } while (dispatch_timeouts(&now) > 0); + + /* Check whether there is a timeout and set it up */ + if (n_timer_queue_entries > 0) { + + UTI_DiffTimevals(&tv, &(timer_queue.next->tv), &now); + ptv = &tv; + + } else { + ptv = NULL; + } + + /* if there are no file descriptors being waited on and no + timeout set, this is clearly ridiculous, so stop the run */ + + if (!ptv && (n_read_fds == 0)) { + LOG_FATAL(LOGF_Scheduler, "No descriptors or timeout to wait for"); + } + + status = select(one_highest_fd, &rd, &wr, &ex, ptv); + + if (status < 0) { + CROAK("Status < 0 after select"); + } else if (status > 0) { + /* A file descriptor is ready to read */ + + dispatch_filehandlers(status, &rd); + + } else { + if (status != 0) { + CROAK("Unexpected value from select"); + } + + /* No descriptors readable, timeout must have elapsed. + Therefore, tv must be non-null */ + if (!ptv) { + CROAK("No descriptors or timeout?"); + } + + /* There's nothing to do here, since the timeouts + will be dispatched at the top of the next loop + cycle */ + + } + } + + return; + +} + +/* ================================================== */ + +void +SCH_QuitProgram(void) +{ + if (!initialised) { + CROAK("Should be initialised"); + } + need_to_exit = 1; +} + +/* ================================================== */ + diff --git a/sched.h b/sched.h new file mode 100644 index 0000000..de62430 --- /dev/null +++ b/sched.h @@ -0,0 +1,83 @@ +/* + $Header: /cvs/src/chrony/sched.h,v 1.10 2002/02/28 23:27:14 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Exported header file for sched.c + */ + +#ifndef GOT_SCHED_H +#define GOT_SCHED_H + +#include "sysincl.h" + +typedef unsigned long SCH_TimeoutID; + +typedef unsigned long SCH_TimeoutClass; +static const SCH_TimeoutClass SCH_ReservedTimeoutValue = 0; +static const SCH_TimeoutClass SCH_NtpSamplingClass = 1; +static const SCH_TimeoutClass SCH_NtpBroadcastClass = 2; + +typedef void* SCH_ArbitraryArgument; +typedef void (*SCH_FileHandler)(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); + +/* Register a handler for when select goes true on a file descriptor */ +extern void SCH_AddInputFileHandler +(int fd, /* The file descriptor */ + SCH_FileHandler, /* The handler routine */ + SCH_ArbitraryArgument /* An arbitrary passthrough argument to the handler */ +); +extern void SCH_RemoveInputFileHandler(int fd); + +/* This queues a timeout to elapse at a given (raw) local time */ +extern SCH_TimeoutID SCH_AddTimeout(struct timeval *tv, SCH_TimeoutHandler, SCH_ArbitraryArgument); + +/* 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 */ +extern SCH_TimeoutID SCH_AddTimeoutInClass(double min_delay, double separation, + SCH_TimeoutClass class, + SCH_TimeoutHandler handler, SCH_ArbitraryArgument); + +/* The next one probably ought to return a status code */ +extern void SCH_RemoveTimeout(SCH_TimeoutID); + +extern void SCH_MainLoop(void); + +extern void SCH_QuitProgram(void); + +#endif /* GOT_SCHED_H */ diff --git a/sources.c b/sources.c new file mode 100644 index 0000000..1dae611 --- /dev/null +++ b/sources.c @@ -0,0 +1,938 @@ +/* + $Header: /cvs/src/chrony/sources.c,v 1.31 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "sysincl.h" + +#include "sources.h" +#include "sourcestats.h" +#include "memory.h" +#include "ntp.h" /* For NTP_Leap */ +#include "local.h" +#include "reference.h" +#include "util.h" +#include "conf.h" +#include "logging.h" +#include "reports.h" +#include "nameserv.h" +#include "mkdirpp.h" + +/* ================================================== */ +/* Flag indicating that we are initialised */ +static int initialised = 0; + +/* ================================================== */ +/* Structure used to hold info for selecting between sources */ +struct SelectInfo { + int stratum; + int select_ok; + double variance; + double root_delay; + double root_dispersion; + double root_distance; + double best_offset; + double lo_limit; + double hi_limit; +}; + +/* ================================================== */ +/* This enum contains the flag values that are used to label + each source */ +typedef enum { + SRC_OK, /* OK so far */ + SRC_UNREACHABLE, /* Source is not reachable */ + SRC_BAD_STATS, /* Stats driver could not supply valid + data */ + SRC_FALSETICKER, /* Source is found to be a falseticker */ + SRC_JITTERY, /* Source scatter worse than other's dispersion */ + SRC_SELECTABLE, /* Source is acceptable candidate */ + SRC_SYNC /* Current synchronisation source */ +} SRC_Status; + +/* ================================================== */ +/* Define the instance structure used to hold information about each + source */ +struct SRC_Instance_Record { + SST_Stats stats; + NTP_Leap leap_status; /* Leap status */ + int index; /* Index back into the array of source */ + unsigned long ref_id; /* The reference ID of this source + (i.e. its IP address, NOT the + reference _it_ is sync'd to) */ + + /* Flag indicating that we are receiving packets with valid headers + from this source and can use it as a reference */ + int reachable; + + /* Flag indicating the status of the source */ + SRC_Status status; + + struct SelectInfo sel_info; +}; + +/* ================================================== */ +/* Structure used to build the sort list for finding falsetickers */ +struct Sort_Element { + int index; + double offset; + enum {LOW=-1, CENTRE=0, 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) */ + +/* ================================================== */ +/* Forward prototype */ + +static void +slew_sources(struct timeval *raw, struct timeval *cooked, double dfreq, double afreq, + double doffset, int is_step_change, void *anything); + +/* ================================================== */ +/* Initialisation function */ +void SRC_Initialise(void) { + sources = NULL; + sort_list = NULL; + n_sources = 0; + max_n_sources = 0; + selected_source_index = INVALID_SOURCE; + initialised = 1; + + LCL_AddParameterChangeHandler(slew_sources, NULL); + + return; +} + +/* ================================================== */ +/* Finalisation function */ +void SRC_Finalise(void) +{ + LCL_RemoveParameterChangeHandler(slew_sources, NULL); + initialised = 0; + return; +} + +/* ================================================== */ +/* Function to create a new instance. This would be called by one of + the individual source-type instance creation routines. */ + +SRC_Instance SRC_CreateNewInstance(unsigned long ref_id) +{ + SRC_Instance result; + + if (!initialised) { + CROAK("Should be initialised"); + } + + result = MallocNew(struct SRC_Instance_Record); + result->stats = SST_CreateInstance(ref_id); + + if (n_sources == max_n_sources) { + /* Reallocate memory */ + max_n_sources += 32; + 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->leap_status = LEAP_Normal; + result->ref_id = ref_id; + result->reachable = 0; + result->status = SRC_BAD_STATS; + + n_sources++; + + return result; +} + +/* ================================================== */ +/* Function to get rid of a source when it is being unconfigured. + This may cause the current reference source to be reselected, if this + was the reference source or contributed significantly to a + falseticker decision. */ + +void SRC_DestroyInstance(SRC_Instance instance) +{ + int dead_index, i; + + if (!initialised) { + CROAK("Should be initialised"); + } + + if (instance->index == selected_source_index) { + instance->reachable = 0; + SRC_SelectSource(0); + } + + SST_DeleteInstance(instance->stats); + dead_index = instance->index; + for (i=dead_index; iindex = i; + } + --n_sources; + Free(instance); + + if (selected_source_index > dead_index) { + --selected_source_index; + } +} + +/* ================================================== */ +/* Function to get the range of frequencies, relative to the given + source, that we believe the local clock lies within. The return + values are in terms of the number of seconds fast (+ve) or slow + (-ve) relative to the source that the local clock becomes after a + given amount of local time has elapsed. + + Suppose the initial offset relative to the source is U (fast +ve, + slow -ve) and a time interval T elapses measured in terms of the + local clock. Then the error relative to the source at the end of + the interval should lie in the interval [U+T*lo, U+T*hi]. */ + +void SRC_GetFrequencyRange(SRC_Instance instance, double *lo, double *hi) +{ + if (!initialised) { + CROAK("Should be initialised"); + } + + SST_GetFrequencyRange(instance->stats, lo, hi); + return; +} + +/* ================================================== */ + +/* 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. + + Parameters are described in sources.h + + */ + +void SRC_AccumulateSample +(SRC_Instance inst, + struct timeval *sample_time, + double offset, + double peer_delay, + double peer_dispersion, + double root_delay, + double root_dispersion, + int stratum, + NTP_Leap leap_status) +{ + + if (!initialised) { + CROAK("Should be initialised"); + } + + inst->leap_status = leap_status; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_Sources, "ip=[%s] t=%s ofs=%f del=%f disp=%f str=%d", + UTI_IPToDottedQuad(inst->ref_id), UTI_TimevalToString(sample_time), -offset, root_delay, root_dispersion, stratum); +#endif + + /* WE HAVE TO NEGATE OFFSET IN THIS CALL, IT IS HERE THAT THE SENSE OF OFFSET + IS FLIPPED */ + SST_AccumulateSample(inst->stats, sample_time, -offset, peer_delay, peer_dispersion, root_delay, root_dispersion, stratum); + SST_DoNewRegression(inst->stats); + /* And redo clock selection */ + SRC_SelectSource(inst->ref_id); + + return; +} + +/* ================================================== */ + +void +SRC_SetReachable(SRC_Instance inst) +{ + inst->reachable = 1; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_Sources, "%s", UTI_IPToDottedQuad(inst->ref_id)); +#endif + + /* Don't do selection at this point, though - that will come about + in due course when we get some useful data from the source */ +} + +/* ================================================== */ + +void +SRC_UnsetReachable(SRC_Instance inst) +{ + inst->reachable = 0; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_Sources, "%s%s", UTI_IPToDottedQuad(inst->ref_id), + (inst->index == selected_source_index) ? "(REF)":""); +#endif + + /* If this was the previous reference source, we have to reselect! */ + + if (inst->index == selected_source_index) { + SRC_SelectSource(0); + } + +} + +/* ================================================== */ + +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; + } +} + +/* ================================================== */ +/* This function selects the current reference from amongst the pool + of sources we are holding. + + Updates are only made to the local reference if match_addr is zero or is + equal to the selected reference source address */ + +void +SRC_SelectSource(unsigned long match_addr) +{ + int i, j, index; + struct timeval now; + double local_clock_err; + int src_select_ok; + double src_offset, src_offset_sd, src_frequency, src_skew; + double src_accrued_dispersion; + int n_endpoints, j1, j2; + double best_lo, best_hi; + int depth, best_depth; + int n_sel_sources; + double distance, min_distance; + int stratum, min_stratum; + int min_distance_index; + struct SelectInfo *si; + double total_root_dispersion; + int n_reachable_sources; + + NTP_Leap leap_status = LEAP_Normal; + + if (n_sources == 0) { + /* In this case, we clearly cannot synchronise to anything */ + if (selected_source_index != INVALID_SOURCE) { + LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no sources"); + } + selected_source_index = INVALID_SOURCE; + REF_SetUnsynchronised(); + return; + } + + LCL_ReadCookedTime(&now, &local_clock_err); + + /* Step 1 - build intervals about each source */ + n_endpoints = 0; + n_reachable_sources = 0; + for (i=0; ireachable) { + + ++n_reachable_sources; + + si = &(sources[i]->sel_info); + SST_GetSelectionData(sources[i]->stats, &now, + &(si->stratum), + &(si->best_offset), + &(si->root_delay), + &(si->root_dispersion), + &(si->variance), + &(si->select_ok)); + + /* Eventually this might be a flag indicating whether the get + selection data call was successful. For now it always is. */ + src_select_ok = 1; + + si->root_distance = si->root_dispersion + 0.5 * fabs(si->root_delay); + si->lo_limit = si->best_offset - si->root_distance; + si->hi_limit = si->best_offset + si->root_distance; + +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "%s off=%f dist=%f lo=%f hi=%f", + UTI_IPToDottedQuad(sources[i]->ref_id), + si->best_offset, si->root_distance, + si->lo_limit, si->hi_limit); +#endif + + if (src_select_ok) { + + sources[i]->status = SRC_OK; /* For now */ + + /* Otherwise it will be hard to pick this one later! However, + this test might be too strict, we might want to dump it */ + 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; + + } else { + sources[i]->status = SRC_BAD_STATS; + } + } else { + /* If the source is not reachable, there is no way we will pick + it. */ + sources[i]->status = SRC_UNREACHABLE; + } + } + +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "n_endpoints=%d", n_endpoints); +#endif + + /* Now sort the endpoint list */ + if (n_endpoints > 0) { + + /* Sort the list into order */ + 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. + */ + + depth = best_depth = 0; + best_lo = best_hi = 0.0; + + for (i=0; iref_id)); +#endif + switch(sort_list[i].tag) { + case LOW: + depth++; + if (depth > best_depth) { + best_depth = depth; + best_lo = sort_list[i].offset; + } + break; + + case CENTRE: + CROAK("CENTRE cannot occur"); + break; + + case HIGH: + if (depth == best_depth) { + best_hi = sort_list[i].offset; + } + depth--; + break; + + } + } + +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "best_depth=%d best_lo=%f best_hi=%f", + best_depth, best_lo, best_hi); +#endif + + if (best_depth <= n_reachable_sources/2) { + /* Could not even get half the reachable sources to agree - + clearly we can't synchronise. + + srcs #to agree + 1 1 + 2 2 + 3 2 + 4 3 etc + + */ + + if (selected_source_index != INVALID_SOURCE) { + LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no majority"); + } + selected_source_index = INVALID_SOURCE; + REF_SetUnsynchronised(); + + /* .. and mark all sources as falsetickers (so they appear thus + on the outputs from the command client) */ + + for (i=0; istatus = SRC_FALSETICKER; + } + + } else { + + /* 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; istatus == SRC_OK) { + /* This should be the same condition to get into the endpoint + list */ + /* Check if source's interval contains the best interval, or + is wholly contained within it */ + 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))) { + + sel_sources[n_sel_sources++] = i; +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "i=%d addr=%s is valid", i, UTI_IPToDottedQuad(sources[i]->ref_id)); +#endif + } else { + sources[i]->status = SRC_FALSETICKER; +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "i=%d addr=%s is a falseticker", i, UTI_IPToDottedQuad(sources[i]->ref_id)); +#endif + } + } + } + + /* We now have a list of indices for the sources which pass the + false-ticker test. Now go on to reject those whose variance is + greater than the minimum distance of any other */ + + /* Find minimum distance */ + index = sel_sources[0]; + min_distance = sources[index]->sel_info.root_distance; + for (i=1; isel_info.root_distance; + if (distance < min_distance) { + min_distance = distance; + } + } + +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "min_distance=%f", min_distance); +#endif + + /* Now go through and prune any sources that have excessive + variance */ + for (i=0; isel_info.variance > min_distance) { + sel_sources[i] = INVALID_SOURCE; + sources[index]->status = SRC_JITTERY; +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "i=%d addr=%s has too much variance", i, UTI_IPToDottedQuad(sources[i]->ref_id)); +#endif + } + } + + /* Now crunch the list and mark all sources as selectable */ + for (i=j=0; istatus = SRC_SELECTABLE; + sel_sources[j++] = sel_sources[i]; + index++; + } + } + n_sel_sources = j; + + /* Now find minimum stratum. If none are left now, + tough. RFC1305 is not so harsh on pruning sources due to + excess variance, which prevents this from happening */ + + if (n_sel_sources > 0) { + index = sel_sources[0]; + min_stratum = sources[index]->sel_info.stratum; + for (i=1; isel_info.stratum; + if (stratum < min_stratum) min_stratum = stratum; + } + +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "min_stratum=%d", min_stratum); +#endif + + /* Does the current source have this stratum and is it still a + survivor? */ + + if ((selected_source_index == INVALID_SOURCE) || + (sources[selected_source_index]->status != SRC_SELECTABLE) || + (sources[selected_source_index]->sel_info.stratum > min_stratum)) { + + /* We have to elect a new synchronisation source */ + min_distance_index = INVALID_SOURCE; + for (i=0; isel_info.stratum == min_stratum) { + if ((min_distance_index == INVALID_SOURCE) || + (sources[index]->sel_info.root_distance < min_distance)) { + min_distance = sources[index]->sel_info.root_distance; + min_distance_index = index; + } + } + } + + selected_source_index = min_distance_index; + LOG(LOGS_INFO, LOGF_Sources, "Selected source %s", + UTI_IPToDottedQuad(sources[selected_source_index]->ref_id)); + + +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "new_sel_index=%d", min_distance_index); +#endif + } else { + /* We retain the existing sync source, see p40 of RFC1305b.ps */ +#if 0 + LOG(LOGS_INFO, LOGF_Sources, "existing reference retained", min_distance_index); +#endif + + } + + sources[selected_source_index]->status = SRC_SYNC; + + /* Now just use the statistics of the selected source for + trimming the local clock */ + + LCL_ReadCookedTime(&now, &local_clock_err); + + SST_GetTrackingData(sources[selected_source_index]->stats, &now, + &src_offset, &src_offset_sd, + &src_accrued_dispersion, + &src_frequency, &src_skew); + + total_root_dispersion = (src_accrued_dispersion + + sources[selected_source_index]->sel_info.root_dispersion); + + if ((match_addr == 0) || + (match_addr == sources[selected_source_index]->ref_id)) { + + REF_SetReference(min_stratum, leap_status, + sources[selected_source_index]->ref_id, + &now, + src_offset, + src_frequency, + src_skew, + sources[selected_source_index]->sel_info.root_delay, + total_root_dispersion); + } + + } else { + if (selected_source_index != INVALID_SOURCE) { + LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no selectable sources"); + } + selected_source_index = INVALID_SOURCE; + REF_SetUnsynchronised(); + + } + + + } + + } else { + /* No sources provided valid endpoints */ + if (selected_source_index != INVALID_SOURCE) { + LOG(LOGS_INFO, LOGF_Sources, "Can't synchronise: no reachable sources"); + } + selected_source_index = INVALID_SOURCE; + REF_SetUnsynchronised(); + } + +} + +/* ================================================== */ + +double +SRC_PredictOffset(SRC_Instance inst, struct timeval *when) +{ + return SST_PredictOffset(inst->stats, when); +} + +/* ================================================== */ + +double +SRC_MinRoundTripDelay(SRC_Instance inst) +{ + return SST_MinRoundTripDelay(inst->stats); +} + +/* ================================================== */ +/* 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 timeval *raw, + struct timeval *cooked, + double dfreq, + double afreq, + double doffset, + int is_step_change, + void *anything) +{ + int i; + + for (i=0; istats, cooked, dfreq, doffset); + } + +} + +/* ================================================== */ +/* This is called to dump out the source measurement registers */ + +void +SRC_DumpSources(void) +{ + FILE *out; + int direc_len; + char *filename; + unsigned int a, b, c, d; + int i; + char *direc; + + direc = CNF_GetDumpDir(); + direc_len = strlen(direc); + filename = MallocArray(char, direc_len+24); /* a bit of slack */ + if (mkdir_and_parents(direc)) { + for (i=0; iref_id) >> 24; + b = ((sources[i]->ref_id) >> 16) & 0xff; + c = ((sources[i]->ref_id) >> 8) & 0xff; + d = ((sources[i]->ref_id)) & 0xff; + + sprintf(filename, "%s/%d.%d.%d.%d.dat", direc, a, b, c, d); + out = fopen(filename, "w"); + if (!out) { + LOG(LOGS_WARN, LOGF_Sources, "Could not open dump file %s", filename); + } else { + SST_SaveToFile(sources[i]->stats, out); + fclose(out); + } + } + } else { + LOG(LOGS_ERR, LOGF_Sources, "Could not create directory %s", direc); + } + Free(filename); +} + +/* ================================================== */ + +void +SRC_ReloadSources(void) +{ + FILE *in; + char *filename; + unsigned int a, b, c, d; + int i; + char *dumpdir; + int dumpdirlen; + + for (i=0; iref_id) >> 24; + b = ((sources[i]->ref_id) >> 16) & 0xff; + c = ((sources[i]->ref_id) >> 8) & 0xff; + d = ((sources[i]->ref_id)) & 0xff; + + dumpdir = CNF_GetDumpDir(); + dumpdirlen = strlen(dumpdir); + filename = MallocArray(char, dumpdirlen+24); + sprintf(filename, "%s/%d.%d.%d.%d.dat", dumpdir, a, b, c, d); + in = fopen(filename, "r"); + if (!in) { + LOG(LOGS_WARN, LOGF_Sources, "Could not open dump file %s", filename); + } else { + if (SST_LoadFromFile(sources[i]->stats, in)) { + /* We might want to use SST_DoUpdateRegression here, but we + need to check it has the same functionality */ + SST_DoNewRegression(sources[i]->stats); + } else { + LOG(LOGS_WARN, LOGF_Sources, "Problem loading from file %s", filename); + } + fclose(in); + } + Free(filename); + } +} + +/* ================================================== */ + +int +SRC_IsSyncPeer(SRC_Instance inst) +{ + if (inst->index == selected_source_index) { + return 1; + } else { + return 0; + } + +} + +/* ================================================== */ + +int +SRC_ReadNumberOfSources(void) +{ + return n_sources; +} + +/* ================================================== */ + +int +SRC_ReportSource(int index, RPT_SourceReport *report, struct timeval *now) +{ + SRC_Instance src; + if ((index >= n_sources) || (index < 0)) { + return 0; + } else { + src = sources[index]; + report->ip_addr = src->ref_id; + switch (src->status) { + case SRC_SYNC: + report->state = RPT_SYNC; + break; + case SRC_JITTERY: + report->state = RPT_JITTERY; + break; + case SRC_UNREACHABLE: + report->state = RPT_UNREACH; + break; + case SRC_FALSETICKER: + report->state = RPT_FALSETICKER; + break; + default: + report->state = RPT_OTHER; + break; + } + /* Call stats module to fill out estimates */ + SST_DoSourceReport(src->stats, report, now); + + return 1; + } + +} + +/* ================================================== */ + +int +SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report) +{ + SRC_Instance src; + + if ((index >= n_sources) || (index < 0)) { + return 0; + } else { + src = sources[index]; + report->ip_addr = src->ref_id; + SST_DoSourcestatsReport(src->stats, report); + return 1; + } +} + +/* ================================================== */ + +SRC_Skew_Direction SRC_LastSkewChange(SRC_Instance inst) +{ + SRC_Skew_Direction result = SRC_Skew_Nochange; + + switch (SST_LastSkewChange(inst->stats)) { + case SST_Skew_Decrease: + result = SRC_Skew_Decrease; + break; + case SST_Skew_Nochange: + result = SRC_Skew_Nochange; + break; + case SST_Skew_Increase: + result = SRC_Skew_Increase; + break; + } + + return result; +} + +/* ================================================== */ diff --git a/sources.h b/sources.h new file mode 100644 index 0000000..c895cac --- /dev/null +++ b/sources.h @@ -0,0 +1,155 @@ +/* + $Header: /cvs/src/chrony/sources.h,v 1.15 2002/02/28 23:27:14 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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" + +/* 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); + +/* 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(unsigned long ref_id); + +/* 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 get the range of frequencies, relative to the given + source, that we believe the local clock lies within. The return + values are in terms of the number of seconds fast (+ve) or slow + (-ve) relative to the source that the local clock becomes after a + given amount of local time has elapsed. + + Suppose the initial offset relative to the source is U (fast +ve, + slow -ve) and a time interval T elapses measured in terms of the + local clock. Then the error relative to the source at the end of + the interval should lie in the interval [U+T*lo, U+T*hi]. */ + +extern void SRC_GetFrequencyRange(SRC_Instance instance, double *lo, double *hi); + +/* 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. + + sample_time is the local time at which the sample is to be + considered to have been made, in terms of doing a regression fit of + offset against local time. + + offset is the offset at the time, in seconds. Positive indicates + that the local clock is SLOW relative to the source, negative + indicates that the local clock is FAST relative to it. + + root_delay and root_dispersion are in seconds, and are as per + RFC1305. root_dispersion only includes the peer's root dispersion + + local sampling precision + skew dispersion accrued during the + measurement. It is the job of the source statistics algorithms + + track.c to add on the extra dispersion due to the residual standard + deviation of the offsets from this source after regression, to form + the root_dispersion field in the packets transmitted to clients or + peers. + + stratum is the stratum of the source that supplied the sample. + + */ + +extern void SRC_AccumulateSample(SRC_Instance instance, struct timeval *sample_time, double offset, double peer_delay, double peer_dispersion, double root_delay, double root_dispersion, int stratum, NTP_Leap leap_status); + +/* This routine indicates that packets with valid headers are being + received from the designated source */ +extern void SRC_SetReachable(SRC_Instance instance); + +/* This routine indicates that we are no longer receiving packets with + valid headers from the designated source */ +extern void SRC_UnsetReachable(SRC_Instance instance); + +/* 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. If match_addr is zero it means we must start + tracking the (newly) selected reference unconditionally, otherwise + it is equal to the address we should track if it turns out to be + the best reference. (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(unsigned long match_addr); + +/* 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 SRC_PredictOffset(SRC_Instance inst, struct timeval *when); + +/* Return the minimum peer delay amongst the previous samples + currently held in the register */ +extern double SRC_MinRoundTripDelay(SRC_Instance inst); + +extern void SRC_DumpSources(void); + +extern void SRC_ReloadSources(void); + +extern int SRC_IsSyncPeer(SRC_Instance inst); +extern int SRC_ReadNumberOfSources(void); +extern int SRC_ReportSource(int index, RPT_SourceReport *report, struct timeval *now); + +extern int SRC_ReportSourcestats(int index, RPT_SourcestatsReport *report); + +typedef enum { + SRC_Skew_Decrease, + SRC_Skew_Nochange, + SRC_Skew_Increase +} SRC_Skew_Direction; + +extern SRC_Skew_Direction SRC_LastSkewChange(SRC_Instance inst); + +#endif /* GOT_SOURCES_H */ + diff --git a/sourcestats.c b/sourcestats.c new file mode 100644 index 0000000..1cedbb5 --- /dev/null +++ b/sourcestats.c @@ -0,0 +1,931 @@ +/* + $Header: /cvs/src/chrony/sourcestats.c,v 1.39 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "sysincl.h" + +#include "sourcestats.h" +#include "memory.h" +#include "regress.h" +#include "util.h" +#include "conf.h" +#include "logging.h" +#include "local.h" +#include "mkdirpp.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) + +/* Day number of 1 Jan 1970 */ +#define MJD_1970 40587 + +/* ================================================== */ +/* File to which statistics are logged, NULL if none */ +static FILE *logfile = NULL; +static char *logfilename = NULL; +static unsigned long logwrites = 0; + +#define STATISTICS_LOG "statistics.log" + +/* ================================================== */ +/* This data structure is used to hold the history of data from the + source */ + +struct SST_Stats_Record { + + /* Reference ID of source, used for logging to statistics log */ + unsigned long refid; + + /* Number of samples currently stored. sample[n_samples-1] is the + newest. The samples are expected to be sorted in order, but that + probably doesn't matter. */ + int n_samples; + + /* The index in the registers of the best individual sample that we + are holding, in terms of the minimum root distance at the present + time */ + int best_single_sample; + + /* This is the estimated offset (+ve => local fast) at a particular time */ + double estimated_offset; + double estimated_offset_sd; + struct timeval offset_time; + + /* Number of runs of the same sign amongst the residuals */ + int nruns; + + /* 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; + + /* 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 direction the skew went in at the last sample */ + SST_Skew_Direction skew_dirn; + + /* This is the estimated residual variance of the data points */ + double variance; + + /* This array contains the sample epochs, in terms of the local + clock. */ + struct timeval sample_times[MAX_SAMPLES]; + + /* 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; that part of the code is written to correspond with + RFC1305 conventions. */ + double offsets[MAX_SAMPLES]; + + /* 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]; + + /* 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]; + + /* This array contains the weights to be used in the regression + analysis for each of the samples. */ + double weights[MAX_SAMPLES]; + + /* This array contains the strata that were associated with the sources + at the times the samples were generated */ + int strata[MAX_SAMPLES]; + +}; + +/* ================================================== */ + +void +SST_Initialise(void) +{ + char *direc; + + if (CNF_GetLogStatistics()) { + direc = CNF_GetLogDir(); + if (!mkdir_and_parents(direc)) { + LOG(LOGS_ERR, LOGF_SourceStats, "Could not create directory %s", direc); + logfile = NULL; + } else { + logfilename = MallocArray(char, 2 + strlen(direc) + strlen(STATISTICS_LOG)); + strcpy(logfilename, direc); + strcat(logfilename, "/"); + strcat(logfilename, STATISTICS_LOG); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_SourceStats, "Couldn't open logfile %s for update", logfilename); + } + } + } +} + +/* ================================================== */ + +void +SST_Finalise(void) +{ + if (logfile) { + fclose(logfile); + } +} + +/* ================================================== */ +/* This function creates a new instance of the statistics handler */ + +SST_Stats +SST_CreateInstance(unsigned long refid) +{ + SST_Stats inst; + inst = MallocNew(struct SST_Stats_Record); + inst->refid = refid; + inst->n_samples = 0; + inst->estimated_frequency = 0; + inst->skew = 2000.0e-6; + inst->skew_dirn = SST_Skew_Nochange; + inst->estimated_offset = 0.0; + inst->estimated_offset_sd = 86400.0; /* Assume it's at least within a day! */ + inst->variance = 16.0; + inst->nruns = 0; + return inst; +} + +/* ================================================== */ +/* This function deletes an instance of the statistics handler. */ + +void +SST_DeleteInstance(SST_Stats inst) +{ + Free(inst); + return; +} + +/* ================================================== */ + +static void +move_stats_entry(SST_Stats inst, int src, int dest) +{ + inst->sample_times[dest] = inst->sample_times[src]; + inst->offsets[dest] = inst->offsets[src]; + inst->orig_offsets[dest] = inst->orig_offsets[src]; + inst->peer_delays[dest] = inst->peer_delays[src]; + inst->peer_dispersions[dest] = inst->peer_dispersions[src]; + inst->root_delays[dest] = inst->root_delays[src]; + inst->root_dispersions[dest] = inst->root_dispersions[src]; + inst->weights[dest] = inst->weights[src]; + inst->strata[dest] = inst->strata[src]; +} + +/* ================================================== */ +/* 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, int *bad_points) +{ + int i, j; + + if (!(new_oldest < inst->n_samples)) { + CROAK("new_oldest should be < n_samples"); + } + + for (i=0, j=new_oldest; jn_samples; j++) { + if (!bad_points || !bad_points[j]) { + if (j != i) { + move_stats_entry(inst, j, i); + } + i++; + } + } + inst->n_samples = i; + +} + +/* ================================================== */ + +void +SST_AccumulateSample(SST_Stats inst, struct timeval *sample_time, + double offset, + double peer_delay, double peer_dispersion, + double root_delay, double root_dispersion, + int stratum) +{ + int n; +#if 0 + double root_distance; +#endif + + if (inst->n_samples == MAX_SAMPLES) { + prune_register(inst, 1, NULL); + } + + n = inst->n_samples; + + inst->sample_times[n] = *sample_time; + inst->offsets[n] = offset; + inst->orig_offsets[n] = offset; + inst->peer_delays[n] = peer_delay; + inst->peer_dispersions[n] = peer_dispersion; + inst->root_delays[n] = root_delay; + inst->root_dispersions[n] = root_dispersion; + +#if 0 + /* The weight is worked out when we run the regression algorithm */ + root_distance = root_dispersion + 0.5 * fabs(root_delay); + + /* For now, this is the formula for the weight functions */ + inst->weights[n] = root_distance * root_distance; +#endif + + inst->strata[n] = stratum; + + ++inst->n_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 timeval *newest_tv; + int i; + + newest_tv = &(inst->sample_times[inst->n_samples - 1]); + for (i=0; in_samples; i++) { + /* The entries in times_back[] should end up negative */ + UTI_DiffTimevalsToDouble(&(times_back[i]), &(inst->sample_times[i]), newest_tv); + } +} + +/* ================================================== */ + +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, n, best_index; + + n = inst->n_samples - 1; + best_root_distance = inst->root_dispersions[n] + 0.5 * fabs(inst->root_delays[n]); + best_index = n; +#if 0 + LOG(LOGS_INFO, LOGF_SourceStats, "n=%d brd=%f", n, best_root_distance); +#endif + for (i=0; isample_times[n])), + UTI_TimevalToString(&(inst->sample_times[i])), + elapsed); +#endif + + /* Because the loop does not consider the most recent sample, this assertion must hold */ + if (elapsed <= 0.0) { + LOG(LOGS_ERR, LOGF_SourceStats, "Elapsed<0! n=%d i=%d latest=[%s] doing=[%s] elapsed=%f", + n, i, + UTI_TimevalToString(&(inst->sample_times[n])), + UTI_TimevalToString(&(inst->sample_times[i])), + elapsed); + + elapsed = fabs(elapsed); + } + + root_distance = inst->root_dispersions[i] + elapsed * inst->skew + 0.5 * fabs(inst->root_delays[i]); + if (root_distance < best_root_distance) { + best_root_distance = root_distance; + best_index = i; + } + } + + inst->best_single_sample = best_index; + +#if 0 + LOG(LOGS_INFO, LOGF_SourceStats, "n=%d best_index=%d", n, best_index); +#endif + + return; +} + +/* ================================================== */ + +/* 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 a quarter of the peer distance */ + +#define SD_TO_DIST_RATIO 8.0 + +/* ================================================== */ +/* 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]; + double peer_distances[MAX_SAMPLES]; + + int bad_points[MAX_SAMPLES]; + int degrees_of_freedom; + int best_start; + double est_intercept, est_slope, est_var, est_intercept_sd, est_slope_sd; + int i, nruns; + double min_distance; + double sd_weight; + double old_skew, old_freq, stress; + + int regression_ok; + + convert_to_intervals(inst, times_back); + + if (inst->n_samples > 0) { + for (i=0; in_samples; i++) { + peer_distances[i] = 0.5 * fabs(inst->peer_delays[i]) + inst->peer_dispersions[i]; + } + + min_distance = peer_distances[0]; + for (i=1; in_samples; i++) { + if (peer_distances[i] < min_distance) { + min_distance = peer_distances[i]; + } + } + + /* And now, work out the weight vector */ + + for (i=0; in_samples; i++) { + sd_weight = 1.0 + SD_TO_DIST_RATIO * (peer_distances[i] - min_distance) / min_distance; + inst->weights[i] = sd_weight * sd_weight; + } + } + + regression_ok = RGR_FindBestRegression(times_back, inst->offsets, inst->weights, + inst->n_samples, + &est_intercept, &est_slope, &est_var, + &est_intercept_sd, &est_slope_sd, + &best_start, &nruns, °rees_of_freedom); + + /* This is a legacy of when the regression routine found outliers + for us. We don't use it anymore. */ + memset((void *) bad_points, 0, MAX_SAMPLES * sizeof(int)); + + if (regression_ok) { + + old_skew = inst->skew; + old_freq = inst->estimated_frequency; + + inst->estimated_frequency = est_slope; + inst->skew = est_slope_sd * RGR_GetTCoef(degrees_of_freedom); + inst->estimated_offset = est_intercept; + inst->offset_time = inst->sample_times[inst->n_samples - 1]; + inst->estimated_offset_sd = est_intercept_sd; + inst->variance = est_var; + inst->nruns = nruns; + + stress = fabs(old_freq - inst->estimated_frequency) / old_skew; + + if (best_start > 0) { + /* If we are throwing old data away, retain the current + assumptions about the skew */ + inst->skew_dirn = SST_Skew_Nochange; + } else { + if (inst->skew < old_skew) { + inst->skew_dirn = SST_Skew_Decrease; + } else { + inst->skew_dirn = SST_Skew_Increase; + } + } + + if (logfile) { + + if (((logwrites++) % 32) == 0) { + fprintf(logfile, + "==============================================================================================================\n" + " Date (UTC) Time IP Address Std dev'n Est offset Offset sd Diff freq Est skew Stress Ns Bs Nr\n" + "==============================================================================================================\n"); + } + + + fprintf(logfile, "%s %-15s %10.3e %10.3e %10.3e %10.3e %10.3e %7.1e %3d %3d %3d\n", + UTI_TimeToLogForm(inst->offset_time.tv_sec), + UTI_IPToDottedQuad(inst->refid), + sqrt(inst->variance), + inst->estimated_offset, + inst->estimated_offset_sd, + inst->estimated_frequency, + inst->skew, + stress, + inst->n_samples, + best_start, nruns); + + fflush(logfile); + } + + prune_register(inst, best_start, bad_points); + + } else { +#if 0 + LOG(LOGS_INFO, LOGF_SourceStats, "too few points (%d) for regression", inst->n_samples); +#endif + inst->estimated_frequency = 0.0; + inst->skew = WORST_CASE_FREQ_BOUND; + } + + find_best_sample_index(inst, times_back); + +} + +/* ================================================== */ +/* This function does a simple regression on what is in the register, + without trying to optimise the error bounds on the frequency by + deleting old samples */ + +void +SST_DoUpdateRegression(SST_Stats inst) +{ + double times_back[MAX_SAMPLES]; + double freq_error_bound; + double est_intercept, est_slope, est_var_base, est_intercept_sd, est_slope_sd; + + convert_to_intervals(inst, times_back); + + if (inst->n_samples >= 3) { /* Otherwise, we're wasting our time - we + can't do a useful linear regression + with less than 3 points */ + + RGR_WeightedRegression(times_back, inst->offsets, inst->weights, + inst->n_samples, + &est_intercept, &est_slope, &est_var_base, + &est_intercept_sd, &est_slope_sd); + + freq_error_bound = est_slope_sd * RGR_GetTCoef(inst->n_samples - 2); + + inst->estimated_frequency = est_slope; + inst->skew = freq_error_bound; + + } else { + inst->estimated_frequency = 0.0; + inst->skew = WORST_CASE_FREQ_BOUND; + } + + find_best_sample_index(inst, times_back); + +} + +/* ================================================== */ + +void +SST_GetReferenceData(SST_Stats inst, struct timeval *now, + int *stratum, double *offset, + double *root_delay, double *root_dispersion, + double *frequency, double *skew) +{ + + double elapsed; + int n; + + *frequency = inst->estimated_frequency; + *skew = inst->skew; + + n = inst->best_single_sample; + + UTI_DiffTimevalsToDouble(&elapsed, now, &(inst->sample_times[n])); + *root_delay = inst->root_delays[n]; + *root_dispersion = inst->root_dispersions[n] + elapsed * inst->skew; + *offset = inst->offsets[n] + elapsed * inst->estimated_frequency; + *stratum = inst->strata[n]; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_SourceStats, "n=%d freq=%f skew=%f del=%f disp=%f ofs=%f str=%d", + n, *frequency, *skew, *root_delay, *root_dispersion, *offset, *stratum); +#endif + + return; +} + +/* ================================================== */ +/* 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; +} + +/* ================================================== */ + +/* ================================================== */ + +void +SST_GetSelectionData(SST_Stats inst, struct timeval *now, + int *stratum, + double *best_offset, double *best_root_delay, + double *best_root_dispersion, + double *variance, int *average_ok) +{ + double average_offset; + double sample_elapsed; + double elapsed; + int n; + double peer_distance; + + n = inst->best_single_sample; + *stratum = inst->strata[n]; + *variance = inst->variance; + + peer_distance = inst->peer_dispersions[n] + 0.5 * fabs(inst->peer_delays[n]); + UTI_DiffTimevalsToDouble(&elapsed, now, &(inst->offset_time)); + + UTI_DiffTimevalsToDouble(&sample_elapsed, now, &(inst->sample_times[n])); + *best_offset = inst->offsets[n] + sample_elapsed * inst->estimated_frequency; + *best_root_delay = inst->root_delays[n]; + *best_root_dispersion = inst->root_dispersions[n] + sample_elapsed * inst->skew; + + average_offset = inst->estimated_offset + inst->estimated_frequency * elapsed; + if (fabs(average_offset - *best_offset) <= peer_distance) { + *average_ok = 1; + } else { + *average_ok = 0; + } + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_SourceStats, "n=%d off=%f del=%f dis=%f var=%f pdist=%f avoff=%f avok=%d", + n, *best_offset, *best_root_delay, *best_root_dispersion, *variance, + peer_distance, average_offset, *average_ok); +#endif + + return; +} + +/* ================================================== */ + +void +SST_GetTrackingData(SST_Stats inst, struct timeval *now, + double *average_offset, double *offset_sd, + double *accrued_dispersion, + double *frequency, double *skew) +{ + int n; + double peer_distance; + double elapsed_offset, elapsed_sample; + + n = inst->best_single_sample; + + *frequency = inst->estimated_frequency; + *skew = inst->skew; + + peer_distance = inst->peer_dispersions[n] + 0.5 * fabs(inst->peer_delays[n]); + UTI_DiffTimevalsToDouble(&elapsed_offset, now, &(inst->offset_time)); + *average_offset = inst->estimated_offset + inst->estimated_frequency * elapsed_offset; + *offset_sd = inst->estimated_offset_sd + elapsed_offset * inst->skew; + + UTI_DiffTimevalsToDouble(&elapsed_sample, now, &(inst->sample_times[n])); + *accrued_dispersion = inst->skew * elapsed_sample; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_SourceStats, "n=%d freq=%f (%.3fppm) skew=%f (%.3fppm) pdist=%f avoff=%f offsd=%f accrdis=%f", + n, *frequency, 1.0e6* *frequency, *skew, 1.0e6* *skew, peer_distance, *average_offset, *offset_sd, *accrued_dispersion); +#endif + +} + +/* ================================================== */ + +void +SST_SlewSamples(SST_Stats inst, struct timeval *when, double dfreq, double doffset) +{ + int n, i; + double elapsed; + double delta_time; + struct timeval *sample, prev; + double prev_offset, prev_freq; + + n = inst->n_samples; + + for (i=0; isample_times[i]); + prev = *sample; +#if 0 + UTI_AdjustTimeval(sample, when, sample, dfreq, doffset); + /* Can't easily use this because we need to slew offset */ +#endif + UTI_DiffTimevalsToDouble(&elapsed, when, sample); + delta_time = elapsed * dfreq - doffset; + UTI_AddDoubleToTimeval(sample, delta_time, sample); + prev_offset = inst->offsets[i]; + inst->offsets[i] += delta_time; +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_SourceStats, "i=%d old_st=[%s] new_st=[%s] old_off=%f new_off=%f", + i, UTI_TimevalToString(&prev), UTI_TimevalToString(sample), + prev_offset, inst->offsets[i]); +#endif + } + + /* Do a half-baked update to the regression estimates */ + UTI_DiffTimevalsToDouble(&elapsed, when, &(inst->offset_time)); + prev = inst->offset_time; + delta_time = elapsed * dfreq - doffset; + UTI_AddDoubleToTimeval(&(inst->offset_time), delta_time, &(inst->offset_time)); + prev_offset = inst->estimated_offset; + prev_freq = inst->estimated_frequency; + inst->estimated_offset += delta_time; + inst->estimated_frequency -= dfreq; + +#ifdef TRACEON + LOG(LOGS_INFO, LOGF_SourceStats, "old_off_time=[%s] new=[%s] old_off=%f new_off=%f old_freq=%.3fppm new_freq=%.3fppm", + UTI_TimevalToString(&prev), UTI_TimevalToString(&(inst->offset_time)), + prev_offset, inst->estimated_offset, + 1.0e6*prev_freq, 1.0e6*inst->estimated_frequency); +#endif + + return; +} + +/* ================================================== */ + +double +SST_PredictOffset(SST_Stats inst, struct timeval *when) +{ + double elapsed; + + if (inst->n_samples < 3) { + /* 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 */ + return inst->offsets[inst->n_samples - 1]; + } else { + UTI_DiffTimevalsToDouble(&elapsed, when, &inst->offset_time); + return inst->estimated_offset + elapsed * inst->estimated_frequency; + } + +} + +/* ================================================== */ + +double +SST_MinRoundTripDelay(SST_Stats inst) +{ + double min_delay, delay; + int i; + + if (inst->n_samples == 0) { + return DBL_MAX; + } else { + min_delay = fabs(inst->peer_delays[0]); + for (i=1; in_samples; i++) { + delay = fabs(inst->peer_delays[i]); + if (delay < min_delay) { + min_delay = delay; + } + } + return min_delay; + } +} + +/* ================================================== */ +/* This is used to save the register to a file, so that we can reload + it after restarting the daemon */ + +void +SST_SaveToFile(SST_Stats inst, FILE *out) +{ + int i; + + fprintf(out, "%d\n", inst->n_samples); + + for(i=0; in_samples; i++) { + + fprintf(out, "%08lx %08lx %.6e %.6e %.6e %.6e %.6e %.6e %.6e %d\n", + (unsigned long) inst->sample_times[i].tv_sec, + (unsigned long) inst->sample_times[i].tv_usec, + inst->offsets[i], + inst->orig_offsets[i], + inst->peer_delays[i], + inst->peer_dispersions[i], + inst->root_delays[i], + inst->root_dispersions[i], + inst->weights[i], + inst->strata[i]); + + } +} + +/* ================================================== */ +/* This is used to reload samples from a file */ + +int +SST_LoadFromFile(SST_Stats inst, FILE *in) +{ + int i, line_number; + char line[1024]; + unsigned long sec, usec; + + if (fgets(line, sizeof(line), in) && + (sscanf(line, "%d", &inst->n_samples) == 1)) { + + line_number = 2; + + for (i=0; in_samples; i++) { + if (!fgets(line, sizeof(line), in) || + (sscanf(line, "%lx%lx%lf%lf%lf%lf%lf%lf%lf%d\n", + &(sec), &(usec), + &(inst->offsets[i]), + &(inst->orig_offsets[i]), + &(inst->peer_delays[i]), + &(inst->peer_dispersions[i]), + &(inst->root_delays[i]), + &(inst->root_dispersions[i]), + &(inst->weights[i]), + &(inst->strata[i])) != 10)) { + + /* This is the branch taken if the read FAILED */ + + LOG(LOGS_WARN, LOGF_SourceStats, "Failed to read data from line %d of dump file", line_number); + inst->n_samples = 0; /* Load abandoned if any sign of corruption */ + return 0; + } else { + + /* This is the branch taken if the read is SUCCESSFUL */ + inst->sample_times[i].tv_sec = sec; + inst->sample_times[i].tv_usec = usec; + + line_number++; + } + } + } else { + LOG(LOGS_WARN, LOGF_SourceStats, "Could not read number of samples from dump file"); + inst->n_samples = 0; /* Load abandoned if any sign of corruption */ + return 0; + } + + return 1; + +} + +/* ================================================== */ + +void +SST_DoSourceReport(SST_Stats inst, RPT_SourceReport *report, struct timeval *now) +{ + int n, nb; + double est_offset, est_err, elapsed, sample_elapsed; + struct timeval ago; + + if (inst->n_samples > 0) { + n = inst->n_samples - 1; + report->orig_latest_meas = (long)(0.5 + 1.0e6 * inst->orig_offsets[n]); + report->latest_meas = (long)(0.5 + 1.0e6 * inst->offsets[n]); + report->latest_meas_err = (unsigned long)(0.5 + 1.0e6 * (0.5*inst->root_delays[n] + inst->root_dispersions[n])); + report->stratum = inst->strata[n]; + + UTI_DiffTimevals(&ago, now, &inst->sample_times[n]); + report->latest_meas_ago = ago.tv_sec; + + if (inst->n_samples > 3) { + UTI_DiffTimevalsToDouble(&elapsed, now, &inst->offset_time); + nb = inst->best_single_sample; + UTI_DiffTimevalsToDouble(&sample_elapsed, now, &(inst->sample_times[nb])); + est_offset = inst->estimated_offset + elapsed * inst->estimated_frequency; + est_err = (inst->estimated_offset_sd + + sample_elapsed * inst->skew + + (0.5*inst->root_delays[nb] + inst->root_dispersions[nb])); + report->est_offset = (long)(0.5 + 1.0e6 * est_offset); + report->est_offset_err = (unsigned long) (0.5 + 1.0e6 * est_err); + report->resid_freq = (long) (0.5 * 1.0e9 * inst->estimated_frequency); + report->resid_skew = (unsigned long) (0.5 + 1.0e9 * inst->skew); + } else { + report->est_offset = report->latest_meas; + report->est_offset_err = report->latest_meas_err; + report->resid_freq = 0; + report->resid_skew = 0; + } + } else { + report->orig_latest_meas = 0; + report->latest_meas = 0; + report->latest_meas_err = 0; + report->stratum = 0; + report->est_offset = 0; + report->est_offset_err = 0; + report->resid_freq = 0; + report->resid_skew = 0; + } +} + + +/* ================================================== */ + +SST_Skew_Direction SST_LastSkewChange(SST_Stats inst) +{ + return inst->skew_dirn; +} + +/* ================================================== */ + +void +SST_DoSourcestatsReport(SST_Stats inst, RPT_SourcestatsReport *report) +{ + double dspan; + int n; + + report->n_samples = inst->n_samples; + report->n_runs = inst->nruns; + + if (inst->n_samples > 1) { + n = inst->n_samples - 1; + UTI_DiffTimevalsToDouble(&dspan, &inst->sample_times[n], &inst->sample_times[0]); + report->span_seconds = (unsigned long) (dspan + 0.5); + } else { + report->span_seconds = 0; + } + + report->resid_freq_ppm = 1.0e6 * inst->estimated_frequency; + report->skew_ppm = 1.0e6 * inst->skew; + report->sd_us = 1.0e6 * sqrt(inst->variance); +} + +/* ================================================== */ + +void +SST_CycleLogFile(void) +{ + if (logfile && logfilename) { + fclose(logfile); + logfile = fopen(logfilename, "a"); + if (!logfile) { + LOG(LOGS_WARN, LOGF_SourceStats, "Could not reopen logfile %s", logfilename); + } + logwrites = 0; + } +} + +/* ================================================== */ diff --git a/sourcestats.h b/sourcestats.h new file mode 100644 index 0000000..e335e2d --- /dev/null +++ b/sourcestats.h @@ -0,0 +1,156 @@ +/* + $Header: /cvs/src/chrony/sourcestats.h,v 1.13 2002/02/28 23:27:14 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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(unsigned long refid); + +/* This function deletes an instance of the statistics handler. */ +extern void SST_DeleteInstance(SST_Stats inst); + +/* This function accumulates a single sample into the statistics handler + + sample_time is the epoch at which the sample is to be considered to + have been made. + + offset is the offset of the local clock relative to the source in + seconds. Positive indicates that the local clock if FAST (contrary + to the NTP parts of the software) + + root_distance is the Lambda+Delta/2 term in RFC1305, but excluding + the extra dispersion due to the residual standard deviation after + we have done the regression fit. + + stratum is the stratum of the source from which the sample came. + */ + +extern void SST_AccumulateSample(SST_Stats inst, struct timeval *sample_time, double offset, double peer_delay, double peer_dispersion, double root_delay, double root_dispersion, int stratum); + +/* 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); + +/* This function does a simple regression on what is in the register, + without trying to optimise the error bounds on the frequency by + deleting old samples */ +extern void SST_DoUpdateRegression(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 timeval *now, + int *stratum, + double *best_offset, double *best_root_delay, + double *best_root_dispersion, + double *variance, + int *average_ok); + +/* Get data needed when setting up tracking on this source */ +extern void +SST_GetTrackingData(SST_Stats inst, struct timeval *now, + double *average_offset, double *offset_sd, + double *accrued_dispersion, + double *frequency, double *skew); + +/* Get parameters for using this source as the reference */ +extern void +SST_GetReferenceData(SST_Stats inst, struct timeval *now, + int *stratum, double *offset, + double *root_delay, double *root_dispersion, + double *frequency, double *skew); + + + +/* 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 timeval *when, double dfreq, double doffset); + + +/* 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 timeval *when); + +/* Find the minimum round trip delay in the register */ +extern double SST_MinRoundTripDelay(SST_Stats inst); + +extern void 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 timeval *now); + +extern void SST_DoSourcestatsReport(SST_Stats inst, RPT_SourcestatsReport *report); + +typedef enum { + SST_Skew_Decrease, + SST_Skew_Nochange, + SST_Skew_Increase +} SST_Skew_Direction; + +extern SST_Skew_Direction SST_LastSkewChange(SST_Stats inst); + +extern void SST_CycleLogFile(void); + +#endif /* GOT_SOURCESTATS_H */ + diff --git a/srcparams.h b/srcparams.h new file mode 100644 index 0000000..5d273b9 --- /dev/null +++ b/srcparams.h @@ -0,0 +1,47 @@ +/* + $Header: /cvs/src/chrony/srcparams.h,v 1.10 2002/02/28 23:27:14 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file defining parameters that can be set on a per source basis + */ + +#ifndef GOT_SRCPARAMS_H +#define GOT_SRCPARAMS_H + +typedef struct { + int minpoll; + int maxpoll; + int online; + int auto_offline; + int presend_minpoll; + unsigned long authkey; + double max_delay; + double max_delay_ratio; +} SourceParameters; + +#define INACTIVE_AUTHKEY 0UL + +#endif /* GOT_SRCPARAMS_H */ diff --git a/strerror.c b/strerror.c new file mode 100644 index 0000000..85d14e9 --- /dev/null +++ b/strerror.c @@ -0,0 +1,41 @@ +/* + $Header: /cvs/src/chrony/strerror.c,v 1.8 2002/02/28 23:27:14 richard Exp $ + + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Replacement strerror function for systems that don't have it + */ + +#ifdef SUNOS + +#include +extern char *sys_errlist[]; + +char *strerror(int n) { + return sys_errlist[n]; +} + +#endif /* SUNOS */ diff --git a/sys.c b/sys.c new file mode 100644 index 0000000..9052cf7 --- /dev/null +++ b/sys.c @@ -0,0 +1,104 @@ +/* + $Header: /cvs/src/chrony/sys.c,v 1.11 2002/02/28 23:27:14 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + This file contains all the conditionally compiled bits that pull + in the various operating-system specific modules + */ + +#include "sys.h" + +#if defined (LINUX) +#include "sys_linux.h" +#endif + +#if defined (SOLARIS) +#include "sys_solaris.h" +#endif + +#if defined (SUNOS) +#include "sys_sunos.h" +#endif + +#if defined (__NetBSD__) +#include "sys_netbsd.h" +#endif + +/* ================================================== */ + +void +SYS_Initialise(void) +{ + +#if defined(LINUX) + SYS_Linux_Initialise(); +#endif + +#if defined(SOLARIS) + SYS_Solaris_Initialise(); +#endif + +#if defined(SUNOS) + SYS_SunOS_Initialise(); +#endif + +#if defined(__NetBSD__) + SYS_NetBSD_Initialise(); +#endif + +} + +/* ================================================== */ + +void +SYS_Finalise(void) +{ + +#if defined(LINUX) + SYS_Linux_Finalise(); +#endif + +#if defined(SOLARIS) + SYS_Solaris_Finalise(); +#endif + +#if defined(SUNOS) + SYS_SunOS_Finalise(); +#endif + +#if defined(__NetBSD__) + SYS_NetBSD_Finalise(); +#endif + + return; +} + +/* ================================================== */ +/* ================================================== */ +/* ================================================== */ + + + diff --git a/sys.h b/sys.h new file mode 100644 index 0000000..973da42 --- /dev/null +++ b/sys.h @@ -0,0 +1,42 @@ +/* + $Header: /cvs/src/chrony/sys.h,v 1.7 2002/02/28 23:27:14 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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(void); + +/* Called at the end of the run to do final clean-up */ +extern void SYS_Finalise(void); + +#endif /* GOT_SYS_H */ diff --git a/sys_linux.c b/sys_linux.c new file mode 100644 index 0000000..3daa201 --- /dev/null +++ b/sys_linux.c @@ -0,0 +1,850 @@ +/* + $Header: /cvs/src/chrony/sys_linux.c,v 1.41 2003/07/01 20:56:23 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + This is the module specific to the Linux operating system. + + */ + +#ifdef LINUX + +#include +#include +#include +#include +#include +#include +#include + +#include "localp.h" +#include "sys_linux.h" +#include "sched.h" +#include "util.h" +#include "conf.h" +#include "logging.h" +#include "wrap_adjtimex.h" + +static long current_tick; + +/* This is the value of tick, in seconds, including the current vernier + frequency term */ +static double current_total_tick; + +/* This is the uncompensated system tick value */ +static int nominal_tick; + +/* This is the scaling required to go between absolute ppm and the + scaled ppm used as an argument to adjtimex. Because chronyd is to an extent + 'closed loop' maybe it doesn't matter if this is wrongly determined, UNLESS + the system's ppm error is close to a multiple of HZ, in which case the + relationship between changing the frequency and changing the value of 'tick' + will be wrong. This would (I imagine) cause the system to thrash between + two states. + + However..., if this effect was not corrected, and the system is left offline + for a long period, a substantial error would build up. e.g. with HZ==100, + the correction required is 128/128.125, giving a drift of about 84 seconds + per day). */ +static double freq_scale; + +/* The HZ value from the kernel header file (may be over-ridden from config + file, e.g. if chronyd binary is moved to a box whose kernel was built with a + different HZ value). */ +static int hz; +static double dhz; /* And dbl prec version of same for arithmetic */ + + +/* ================================================== */ + +/* The operating system kernel version */ +static int version_major; +static int version_minor; +static int version_patchlevel; + +/* Flag indicating whether adjtimex() with txc.modes equal to zero +returns the remaining time adjustment or not. If not we have to read +the outstanding adjustment by setting it to zero, examining the return +value and setting the outstanding adjustment back again. */ + +static int have_readonly_adjtime; + +/* ================================================== */ + +static void handle_end_of_slew(void *anything); + +/* ================================================== */ + +inline static int +round(double x) { + int y; + y = (int)(x + 0.5); + while ((double)y < x - 0.5) y++; + while ((double)y > x + 0.5) y--; + return y; +} + +/* ================================================== */ +/* Amount of outstanding offset to process */ +static double offset_register; + +/* Flag set true if a fast slew (one done by altering tick) is being + run at the moment */ +static int fast_slewing; + +/* The amount by which the fast slew was supposed to slew the clock */ +static double fast_slew_wanted; + +/* The value programmed into the kernel's 'tick' variable whilst + slewing a large offset */ +static long slewing_tick; + +/* The timeval (raw) at which a fast slew was started. We need to + know this for two reasons. First, if we want to change the + frequency midway through, we will want to abort the slew and return + the unprocessed portion to the offset register to start again + later. Second, when the end of the slew expires, we need to know + precisely how long we have been slewing for, so that we can negate + the excess and slew it back the other way. */ +static struct timeval slew_start_tv; + +/* This is the ID returned to use by the scheduler's timeout handler. + We need this if we subsequently wish to abort a slew, because we will have to + dequeue the timeout */ +static SCH_TimeoutID slew_timeout_id; + +/* The adjustment that we apply to 'tick', in seconds, whilst applying + a fast slew */ +static double delta_total_tick; + +/* Max amount of time that we wish to slew by using adjtime (or its + equivalent). If more than this is outstanding, we alter the value + of tick instead, for a set period. Set this according to the + amount of time that a dial-up clock might need to be shifted + assuming it is resync'ed about once per day. (TBC) */ +#define MAX_ADJUST_WITH_ADJTIME (0.2) + +/* The amount by which we alter 'tick' when doing a large slew */ +static int slew_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; + +/* ================================================== */ +/* This routine stops a fast slew, determines how long the slew has + been running for, and consequently how much adjustment has actually + been applied. It can be used both when a slew finishes naturally + due to a timeout, and when a slew is being aborted. */ + +static void +stop_fast_slew(void) +{ + struct timeval T1, T1d, T1a; + struct timezone tz; + double end_window; + double fast_slew_done; + double slew_duration; + double introduced_dispersion; + + /* Should never get here unless this is true */ + if (!fast_slewing) { + CROAK("Should be fast slewing"); + } + + /* Now set the thing off */ + if (gettimeofday(&T1, &tz) < 0) { + CROAK("gettimeofday() failed in stop_fast_slew"); + } + + if (TMX_SetTick(current_tick) < 0) { + CROAK("adjtimex() failed in stop_fast_slew"); + } + + if (gettimeofday(&T1d, &tz) < 0) { + CROAK("gettimeofday() failed in stop_fast_slew"); + } + + fast_slewing = 0; + + UTI_AverageDiffTimevals(&T1, &T1d, &T1a, &end_window); + UTI_DiffTimevalsToDouble(&slew_duration, &T1a, &slew_start_tv); + + /* Compute the dispersion we have introduced by changing tick this + way. If the two samples of gettimeofday differ, there is an + uncertainty window wrt when the frequency change actually applies + from. We handle this by adding dispersion to all statistics held + at higher levels in the system. */ + + introduced_dispersion = end_window * delta_total_tick; + lcl_InvokeDispersionNotifyHandlers(introduced_dispersion); + + fast_slew_done = delta_total_tick * slew_duration / + (current_total_tick + delta_total_tick); + + offset_register += (fast_slew_wanted + fast_slew_done); + +} + + +/* ================================================== */ +/* This routine is called to start a clock offset adjustment */ + +static void +initiate_slew(void) +{ + double dseconds; + long tick_adjust; + long offset; + struct timeval T0, T0d, T0a; + struct timeval end_of_slew; + struct timezone tz; + double start_window; + double introduced_dispersion; + + /* Don't want to get here if we already have an adjust on the go! */ + if (fast_slewing) { + CROAK("Should not be fast slewing"); + } + + if (offset_register == 0.0) { + return; + } + + if (fabs(offset_register) < MAX_ADJUST_WITH_ADJTIME) { + /* Use adjtime to do the shift */ + offset = (long)(0.5 + 1.0e6*(-offset_register)); + + if (TMX_ApplyOffset(&offset) < 0) { + CROAK("adjtimex() failed in initiate_slew"); + } + + offset_register = 0.0; + + } else { + + /* If the system clock has a high drift rate, the combination of + current_tick + slew_delta_tick could be outside the range that adjtimex + will accept. To prevent this, the tick adjustment that is used to slew + an error off the clock is clamped according to what tick_adjust is. + */ + + long min_allowed_tick, max_allowed_tick; + + min_allowed_tick = nominal_tick - max_tick_bias; + max_allowed_tick = nominal_tick + max_tick_bias; + + if (offset_register > 0) { + slewing_tick = current_tick - slew_delta_tick; + if (slewing_tick <= min_allowed_tick) { + slewing_tick = min_allowed_tick + 1; + } + } else { + slewing_tick = current_tick + slew_delta_tick; + if (slewing_tick >= max_allowed_tick) { + slewing_tick = max_allowed_tick - 1; + } + } + + tick_adjust = slewing_tick - current_tick; + + delta_total_tick = (double) tick_adjust / 1.0e6; + dseconds = - offset_register * (current_total_tick + delta_total_tick) / delta_total_tick; + + /* Now set the thing off */ + if (gettimeofday(&T0, &tz) < 0) { + CROAK("gettimeofday() failed in initiate_slew"); + } + + if (TMX_SetTick(slewing_tick) < 0) { + LOG(LOGS_INFO, LOGF_SysLinux, "c_t=%ld ta=%ld sl_t=%ld dtt=%e", + current_tick, tick_adjust, slewing_tick, delta_total_tick); + CROAK("adjtimex() failed to start big slew"); + } + + if (gettimeofday(&T0d, &tz) < 0) { + CROAK("gettimeofday() failed in initiate_slew"); + } + + /* Now work out the uncertainty in when we actually started the + slew. */ + + UTI_AverageDiffTimevals(&T0, &T0d, &T0a, &start_window); + + /* Compute the dispersion we have introduced by changing tick this + way. If the two samples of gettimeofday differ, there is an + uncertainty window wrt when the frequency change actually applies + from. We handle this by adding dispersion to all statistics held + at higher levels in the system. */ + + introduced_dispersion = start_window * delta_total_tick; + lcl_InvokeDispersionNotifyHandlers(introduced_dispersion); + + fast_slewing = 1; + slew_start_tv = T0a; + + /* Set up timeout for end of slew */ + UTI_AddDoubleToTimeval(&T0a, dseconds, &end_of_slew); + + slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL); + + fast_slew_wanted = offset_register; + offset_register = 0.0; + + } + + return; +} + +/* ================================================== */ + +/* This is the callback routine invoked by the scheduler at the end of + a slew. */ + +static void +handle_end_of_slew(void *anything) +{ + stop_fast_slew(); + initiate_slew(); /* To do any fine trimming required */ +} + +/* ================================================== */ +/* This routine is used to abort a slew that is in progress, if any */ + +static void +abort_slew(void) +{ + if (fast_slewing) { + stop_fast_slew(); + SCH_RemoveTimeout(slew_timeout_id); + } +} + +/* ================================================== */ +/* This routine accrues an offset into the offset register, and starts + a slew if required. + + The offset argument is measured in seconds. Positive means the + clock needs to be slewed backwards (i.e. is currently fast of true + time) */ + +static void +accrue_offset(double offset) +{ + long toffset; + + /* Add the new offset to the register */ + offset_register += offset; + + /* Cancel any standard adjtime that is running */ + toffset = 0; + if (TMX_ApplyOffset(&toffset) < 0) { + CROAK("adjtimex() failed in accrue_offset"); + } + + offset_register -= (double) toffset / 1.0e6; + + if (!fast_slewing) { + initiate_slew(); + } /* Otherwise, when the fast slew completes, any other stuff + in the offset register will be applied */ + +} + +/* ================================================== */ +/* Positive means currently fast of true time, i.e. jump backwards */ + +static void +apply_step_offset(double offset) +{ + struct timeval old_time, new_time; + struct timezone tz; + + if (fast_slewing) { + abort_slew(); + } + + if (gettimeofday(&old_time, &tz) < 0) { + CROAK("gettimeofday in apply_step_offset"); + } + + UTI_AddDoubleToTimeval(&old_time, -offset, &new_time); + + if (settimeofday(&new_time, &tz) < 0) { + CROAK("settimeofday in apply_step_offset"); + } + + initiate_slew(); + +} + +/* ================================================== */ +/* 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 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 void +set_frequency(double freq_ppm) { + + long required_tick; + double required_freq; /* what we use */ + double scaled_freq; /* what adjtimex & the kernel use */ + int required_delta_tick; + int neg; /* True if estimate is that local clock runs slow, + i.e. positive frequency correction required */ + + + /* If we in the middle of slewing the time by having the value of + tick altered, we have to stop doing that, because the timeout + expiry etc will change if we don't. */ + + if (fast_slewing) { + abort_slew(); + } + + if (freq_ppm < 0.0) { + neg = 1; + freq_ppm = -freq_ppm; + } else { + neg = 0; + } + + required_delta_tick = round(freq_ppm / dhz); + required_freq = freq_ppm - dhz * (double) required_delta_tick; + + if (neg) { + /* Uncompensated local clock runs slow */ + required_tick = nominal_tick + required_delta_tick; + scaled_freq = freq_scale * required_freq; + } else { + /* Uncompensated local clock runs fast */ + required_tick = nominal_tick - required_delta_tick; + scaled_freq = -freq_scale * required_freq; + } + + if (TMX_SetFrequency(scaled_freq, required_tick) < 0) { + char buffer[1024]; + sprintf(buffer, "adjtimex failed for set_frequency, freq_ppm=%10.4e scaled_freq=%10.4e required_tick=%ld", + freq_ppm, scaled_freq, required_tick); + CROAK(buffer); + } + + current_tick = required_tick; + current_total_tick = ((double)current_tick + required_freq/dhz) / 1.0e6 ; + + initiate_slew(); /* Restart any slews that need to be restarted */ + + return; + +} + +/* ================================================== */ +/* Read the ppm frequency from the kernel */ + +static double +read_frequency(void) +{ + double tick_term; + double unscaled_freq; + double freq_term; + + if (TMX_GetFrequency(&unscaled_freq) < 0) { + CROAK("adjtimex failed in read_frequency"); + } + + /* Use current_tick here rather than txc.tick, otherwise we're + thrown off course when doing a fast slew (in which case, txc.tick + is nowhere near the nominal value) */ + tick_term = dhz * (double)(nominal_tick - current_tick); + freq_term = unscaled_freq / freq_scale; + +#if 0 + LOG(LOGS_INFO, LOGF_SysLinux, "txc.tick=%ld txc.freq=%ld tick_term=%f freq_term=%f", + txc.tick, txc.freq, tick_term, freq_term); +#endif + + return tick_term - freq_term; + +} + +/* ================================================== */ +/* Given a raw time, determine the correction in seconds to generate + the 'cooked' time. The correction has to be added to the + raw time */ + +static void +get_offset_correction(struct timeval *raw, + double *corr) +{ + + /* Correction is given by these things : + 1. Any value in offset register + 2. Amount of fast slew remaining + 3. Any amount of adjtime correction remaining */ + + + double adjtime_left; + double fast_slew_duration; + double fast_slew_achieved; + double fast_slew_remaining; + long offset; + + if (have_readonly_adjtime) { + if (TMX_GetOffsetLeft(&offset) < 0) { + CROAK("adjtimex() failed in get_offset_correction"); + } + + adjtime_left = (double)offset / 1.0e6; + } else { + offset = 0; + if (TMX_ApplyOffset(&offset) < 0) { + CROAK("adjtimex() failed in get_offset_correction"); + } + + adjtime_left = (double)offset / 1.0e6; + + /* txc.offset still set from return value of last call */ + if (TMX_ApplyOffset(&offset) < 0) { + CROAK("adjtimex() failed in get_offset_correction"); + } + } + + if (fast_slewing) { + UTI_DiffTimevalsToDouble(&fast_slew_duration, raw, &slew_start_tv); + fast_slew_achieved = delta_total_tick * fast_slew_duration / + (current_total_tick + delta_total_tick); + fast_slew_remaining = fast_slew_wanted + fast_slew_achieved; + } else { + fast_slew_remaining = 0.0; + } + + *corr = - (offset_register + fast_slew_remaining) + adjtime_left; + + return; +} + +/* ================================================== */ + +static void +immediate_step(void) +{ + struct timeval old_time, new_time; + struct timezone tz; + long offset; + + if (fast_slewing) { + abort_slew(); + } + + offset = 0; + if (TMX_ApplyOffset(&offset) < 0) { + CROAK("adjtimex() failed in immediate_step"); + } + + offset_register -= (double) offset / 1.0e6; + + if (gettimeofday(&old_time, &tz) < 0) { + CROAK("gettimeofday() failed in immediate_step"); + } + + UTI_AddDoubleToTimeval(&old_time, -offset_register, &new_time); + + if (settimeofday(&new_time, &tz) < 0) { + CROAK("settimeofday() failed in immediate_step"); + } + + offset_register = 0.0; + + return; +} + +/* ================================================== */ + +/* Estimate the value of 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/HZ. */ + +static void +guess_hz_and_shift_hz(int tick, int *hz, int *shift_hz) +{ + int i, tick_lo, tick_hi, ihz; + double tick_nominal; + /* Pick off the hz=100 case first */ + if (tick >= 9000 && tick <= 11000) { + *hz = 100; + *shift_hz = 7; + return; + } + + 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) { + *hz = ihz; + *shift_hz = i; + return; + } + } + + /* oh dear. doomed. */ + *hz = 0; + *shift_hz = 0; + return; +} + +/* ================================================== */ +/* 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; + int shift_hz; + double dshift_hz; + double basic_freq_scale; /* what to use if HZ!=100 */ + int config_hz, set_config_hz; /* values of HZ from conf file */ + int set_config_freq_scale; + double config_freq_scale; + double calculated_freq_scale; + struct tmx_params tmx_params; + struct utsname uts; + + TMX_ReadCurrentParams(&tmx_params); + + guess_hz_and_shift_hz(tmx_params.tick, &hz, &shift_hz); + + if (!shift_hz) { + LOG_FATAL(LOGF_SysLinux, "Can't determine hz (txc.tick=%ld txc.freq=%ld (%.8f) txc.offset=%ld)", + tmx_params.tick, tmx_params.freq, tmx_params.dfreq, tmx_params.offset); + } else { + LOG(LOGS_INFO, LOGF_SysLinux, "Initial txc.tick=%ld txc.freq=%ld (%.8f) txc.offset=%ld => hz=%d shift_hz=%d", + tmx_params.tick, tmx_params.freq, tmx_params.dfreq, tmx_params.offset, hz, shift_hz); + } + + CNF_GetLinuxHz(&set_config_hz, &config_hz); + if (set_config_hz) hz = config_hz; + /* (If true, presumably freq_scale will be overridden anyway, making shift_hz + redundant too.) */ + + dhz = (double) hz; + dshift_hz = (double)(1UL << shift_hz); + basic_freq_scale = dshift_hz / dhz; + nominal_tick = (1000000L + (hz/2))/hz; /* Mirror declaration in kernel */ + slew_delta_tick = nominal_tick / 12; + max_tick_bias = nominal_tick / 10; + + /* The basic_freq_scale comes from: + * the kernel increments the usec counter HZ times per second (if the timer + interrupt period were perfect) + * the following code in the kernel + + time_adj (+/-)= ltemp >> + (SHIFT_USEC + SHIFT_HZ - SHIFT_SCALE); + + causes the adjtimex 'freq' value to be divided down by 1< only 0.125% error (p. 14) + * + if (time_adj < 0) + time_adj -= (-time_adj >> 2) + (-time_adj >> 5); + else + time_adj += (time_adj >> 2) + (time_adj >> 5); +#endif + + Special case that later. + */ + + if (uname(&uts) < 0) { + LOG_FATAL(LOGF_SysLinux, "Cannot uname(2) to get kernel version, sorry."); + } + if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) { + LOG_FATAL(LOGF_SysLinux, "Cannot read information from uname, sorry"); + } + + LOG(LOGS_INFO, LOGF_SysLinux, "Linux kernel major=%d minor=%d patch=%d", major, minor, patch); + + version_major = major; + version_minor = minor; + version_patchlevel = patch; + + switch (major) { + case 1: + /* Does Linux v1.x even support HZ!=100? */ + switch (minor) { + case 2: + if (patch == 13) { + freq_scale = (hz==100) ? (128.0 / 100.0) : basic_freq_scale ; /* I _think_! */ + have_readonly_adjtime = 1; + } else { + LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry."); + } + break; + case 3: + /* I guess the change from the 1.2.x scaling to the 2.0.x + scaling must have happened during 1.3 development. I + haven't a clue where though, until someone looks it + up. */ + LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry."); + break; + default: + LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry."); + break; + } + break; + case 2: + switch (minor) { + case 0: + if (patch < 32) { + freq_scale = (hz==100) ? (128.0 / 125.0) : basic_freq_scale; + have_readonly_adjtime = 1; + } else if (patch >= 32) { + freq_scale = (hz==100) ? (128.0 / 128.125) : basic_freq_scale; + + /* The functionality in kernel/time.c in the kernel source + was modified with regard to what comes back in the + txc.offset field on return from adjtimex. If txc.modes + was ADJ_OFFSET_SINGLESHOT on entry, the outstanding + adjustment is returned, however the running offset will + be modified to the passed value. */ + have_readonly_adjtime = 0; + } + break; + case 1: + /* I know that earlier 2.1 kernels were like 2.0.31, hence + the settings below. However, the 2.0.32 behaviour may + have been added late in the 2.1 series, however I have no + idea at which patch level. Leave it like this until + someone supplies some info. */ + freq_scale = (hz==100) ? (128.0 / 125.0) : basic_freq_scale; + have_readonly_adjtime = 0; /* For safety ! */ + break; + case 2: + case 3: + case 4: + case 5: + /* These seem to be like 2.0.32 */ + freq_scale = (hz==100) ? (128.0 / 128.125) : basic_freq_scale; + have_readonly_adjtime = 0; + break; + default: + LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry."); + } + break; + default: + LOG_FATAL(LOGF_SysLinux, "Kernel's major version not supported yet, sorry"); + break; + } + + /* Override freq_scale if it appears in conf file */ + CNF_GetLinuxFreqScale(&set_config_freq_scale, &config_freq_scale); + calculated_freq_scale = freq_scale; + if (set_config_freq_scale) freq_scale = config_freq_scale; + + LOG(LOGS_INFO, LOGF_SysLinux, "calculated_freq_scale=%.8f freq_scale=%.8f", + calculated_freq_scale, freq_scale); + +} + +/* ================================================== */ +/* Set denorms to flush to zero instead of trapping. */ + +#if defined(__SH5__) +static void enable_flush_denorms(void) +{ + float fpscr; + unsigned long ifpscr; + asm volatile("fgetscr %0" : "=f" (fpscr)); + asm volatile("fmov.sl %1, %0" : "=r" (ifpscr) : "f" (fpscr)); + ifpscr |= 0x40000; + asm volatile("fmov.ls %1, %0" : "=f" (fpscr) : "r" (ifpscr)); + asm volatile("fputscr %0" : : "f" (fpscr)); + return; +} +#endif + +/* ================================================== */ +/* Initialisation code for this module */ + +void +SYS_Linux_Initialise(void) +{ + offset_register = 0.0; + fast_slewing = 0; + +#if defined(__SH5__) + enable_flush_denorms(); +#endif + + get_version_specific_details(); + + current_tick = nominal_tick; + current_total_tick = 1.0 / dhz; + + lcl_RegisterSystemDrivers(read_frequency, set_frequency, + accrue_offset, apply_step_offset, + get_offset_correction, immediate_step); +} + +/* ================================================== */ +/* Finalisation code for this module */ + +void +SYS_Linux_Finalise(void) +{ + /* Must *NOT* leave a fast slew running - clock would drift way off + if the daemon is not restarted */ + abort_slew(); +} + +/* ================================================== */ + +void +SYS_Linux_GetKernelVersion(int *major, int *minor, int *patchlevel) +{ + *major = version_major; + *minor = version_minor; + *patchlevel = version_patchlevel; +} + +/* ================================================== */ + +#endif /* LINUX */ + +/* vim:ts=8 + * */ + diff --git a/sys_linux.h b/sys_linux.h new file mode 100644 index 0000000..a17e51e --- /dev/null +++ b/sys_linux.h @@ -0,0 +1,40 @@ +/* + $Header: /cvs/src/chrony/sys_linux.h,v 1.8 2002/02/28 23:27:15 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + The header file for the linux driver + */ + +#ifndef GOT_SYS_LINUX_H +#define GOT_SYS_LINUX_H + +extern void SYS_Linux_Initialise(void); + +extern void SYS_Linux_Finalise(void); + +extern void SYS_Linux_GetKernelVersion(int *major, int *minor, int *patchlevel); + +#endif /* GOT_SYS_LINUX_H */ diff --git a/sys_netbsd.c b/sys_netbsd.c new file mode 100644 index 0000000..3b275f3 --- /dev/null +++ b/sys_netbsd.c @@ -0,0 +1,326 @@ +/* + $Header: /cvs/src/chrony/sys_netbsd.c,v 1.2 2002/02/17 22:13:49 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Driver file for the NetBSD operating system. + */ + +#ifdef __NetBSD__ + +#include +#include +#include +#include +#include + +#include +#include + +#include "sys_netbsd.h" +#include "localp.h" +#include "logging.h" +#include "util.h" + +/* ================================================== */ + +/* This register contains the number of seconds by which the local + clock was estimated to be fast of reference time at the epoch when + gettimeofday() returned T0 */ + +static double offset_register; + +/* This register contains the epoch to which the offset is referenced */ + +static struct timeval 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; + +/* Kernel parameters to calculate adjtime error. */ + +static int kern_tickadj; +static long kern_bigadj; + +/* ================================================== */ + +static void +clock_initialise(void) +{ + struct timeval newadj, oldadj; + struct timezone tz; + + offset_register = 0.0; + adjustment_requested = 0.0; + current_freq = 0.0; + + if (gettimeofday(&T0, &tz) < 0) { + CROAK("gettimeofday() failed in clock_initialise()"); + } + + newadj.tv_sec = 0; + newadj.tv_usec = 0; + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in clock_initialise"); + } + +} + +/* ================================================== */ + +static void +clock_finalise(void) +{ + /* Nothing to do yet */ + +} + +/* ================================================== */ + +static void +start_adjust(void) +{ + struct timeval newadj, oldadj; + struct timeval T1; + struct timezone tz; + double elapsed, accrued_error; + double adjust_required; + struct timeval exact_newadj; + long delta, tickdelta; + double rounding_error; + double old_adjust_remaining; + + /* Determine the amount of error built up since the last adjustment */ + if (gettimeofday(&T1, &tz) < 0) { + CROAK("gettimeofday() failed in start_adjust"); + } + + UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0); + accrued_error = elapsed * current_freq; + + adjust_required = - (accrued_error + offset_register); + + UTI_DoubleToTimeval(adjust_required, &exact_newadj); + + /* At this point, we need to round the required adjustment the + same way the kernel does. */ + + delta = exact_newadj.tv_sec * 1000000 + exact_newadj.tv_usec; + if (delta > kern_bigadj || delta < -kern_bigadj) + tickdelta = 10 * kern_tickadj; + else + tickdelta = kern_tickadj; + if (delta % tickdelta) + delta = delta / tickdelta * tickdelta; + newadj.tv_sec = 0; + newadj.tv_usec = delta; + UTI_NormaliseTimeval(&newadj); + + /* Add rounding error back onto offset register. */ + UTI_DiffTimevalsToDouble(&rounding_error, &newadj, &exact_newadj); + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in start_adjust"); + } + + UTI_TimevalToDouble(&oldadj, &old_adjust_remaining); + + offset_register = rounding_error - old_adjust_remaining; + + T0 = T1; + UTI_TimevalToDouble(&newadj, &adjustment_requested); + +} + +/* ================================================== */ + +static void +stop_adjust(void) +{ + struct timeval T1; + struct timezone tz; + struct timeval zeroadj, remadj; + double adjustment_remaining, adjustment_achieved; + double elapsed, elapsed_plus_adjust; + + zeroadj.tv_sec = 0; + zeroadj.tv_usec = 0; + + if (adjtime(&zeroadj, &remadj) < 0) { + CROAK("adjtime() failed in stop_adjust"); + } + + if (gettimeofday(&T1, &tz) < 0) { + CROAK("gettimeofday() failed in stop_adjust"); + } + + UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0); + UTI_TimevalToDouble(&remadj, &adjustment_remaining); + + 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) +{ + stop_adjust(); + offset_register += offset; + start_adjust(); + +} + +/* ================================================== */ + +/* Positive offset means system clock is fast of true time, therefore + step backwards */ + +static void +apply_step_offset(double offset) +{ + struct timeval old_time, new_time, T1; + struct timezone tz; + + stop_adjust(); + + if (gettimeofday(&old_time, &tz) < 0) { + CROAK("gettimeofday in apply_step_offset"); + } + + UTI_AddDoubleToTimeval(&old_time, -offset, &new_time); + + if (settimeofday(&new_time, &tz) < 0) { + CROAK("settimeofday in apply_step_offset"); + } + + UTI_AddDoubleToTimeval(&T0, offset, &T1); + T0 = T1; + + start_adjust(); + +} + +/* ================================================== */ + +static void +set_frequency(double new_freq_ppm) +{ + stop_adjust(); + current_freq = new_freq_ppm * 1.0e-6; + start_adjust(); +} + +/* ================================================== */ + +static double +read_frequency(void) +{ + return current_freq * 1.0e6; +} + +/* ================================================== */ + +static void +get_offset_correction(struct timeval *raw, + double *corr) +{ + stop_adjust(); + *corr = -offset_register; + start_adjust(); +} + +/* ================================================== */ + +void +SYS_NetBSD_Initialise(void) +{ + static struct nlist nl[] = { + {"_tickadj"}, + {"_bigadj"}, + {NULL} + }; + + kvm_t *kt; + FILE *fp; + + kt = kvm_open(NULL, NULL, NULL, O_RDWR, NULL); + if (!kt) { + CROAK("Cannot open kvm\n"); + } + + if (kvm_nlist(kt, nl) < 0) { + CROAK("Cannot read kernel symbols\n"); + } + + if (kvm_read(kt, nl[0].n_value, (char *)(&kern_tickadj), sizeof(int)) < 0) { + CROAK("Cannot read from _tickadj\n"); + } + + if (kvm_read(kt, nl[1].n_value, (char *)(&kern_bigadj), sizeof(long)) < 0) { + CROAK("Cannot read from _bigadj\n"); + } + + kvm_close(kt); + + clock_initialise(); + + lcl_RegisterSystemDrivers(read_frequency, set_frequency, + accrue_offset, apply_step_offset, + get_offset_correction, NULL /* immediate_step */); + +} + +/* ================================================== */ + +void +SYS_NetBSD_Finalise(void) +{ + clock_finalise(); +} + +/* ================================================== */ + + +#endif /* __NetBSD__ */ diff --git a/sys_netbsd.h b/sys_netbsd.h new file mode 100644 index 0000000..22ebdb0 --- /dev/null +++ b/sys_netbsd.h @@ -0,0 +1,39 @@ +/* + $Header: /cvs/src/chrony/sys_netbsd.h,v 1.2 2002/02/17 22:13:49 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for NetBSD driver + */ + +#ifndef GOT_SYS_NETBSD_H +#define GOT_SYS_NETBSD_H + +void SYS_NetBSD_Initialise(void); + +void SYS_NetBSD_Finalise(void); + +#endif diff --git a/sys_solaris.c b/sys_solaris.c new file mode 100644 index 0000000..8fdeef3 --- /dev/null +++ b/sys_solaris.c @@ -0,0 +1,483 @@ +/* + $Header: /cvs/src/chrony/sys_solaris.c,v 1.18 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Driver file for Solaris operating system + */ + +#ifdef SOLARIS + +#include +#include +#include +#include +#include +#include + +#include + +#include "sys_solaris.h" +#include "localp.h" +#include "sched.h" +#include "logging.h" +#include "util.h" + +/* ================================================== */ + +/* This register contains the number of seconds by which the local + clock was estimated to be fast of reference time at the epoch when + gettimeofday() returned T0 */ + +static double offset_register; + +/* This register contains the epoch to which the offset is referenced */ + +static struct timeval 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; + +/* ================================================== */ +/* On Solaris 2.5 & 2.5.1, passing an argument of zero as the new + delta to adjtime does not zero out the adjustment - the remaining + adjustment is returned as the old delta arg, but the adjustment keeps + running. To get round this, we set adjustments of +/-1us when we + really want zero. Alternate adjustments are used to avoid a drift + from building up. */ + +static struct timeval zeroes[2] = { + {0, 1}, + {-1, 999999} +}; + +static int index=0; + +/* If 1, need to run dosynctodr(). If 0, don't */ +static int need_dosynctodr = -1; + + +#define GET_ZERO (zeroes[index^=1]) + +/* ================================================== */ + +static void +clock_initialise(void) +{ + struct timeval newadj, oldadj; + struct timezone tz; + + offset_register = 0.0; + adjustment_requested = 0.0; + current_freq = 0.0; + + if (gettimeofday(&T0, &tz) < 0) { + CROAK("gettimeofday() failed in clock_initialise()"); + } + + newadj = GET_ZERO; + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in clock_initialise"); + } + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in clock_initialise"); + } + + return; +} + +/* ================================================== */ + +static void +clock_finalise(void) +{ + /* Nothing to do yet */ + + return; + +} + +/* ================================================== */ + +static void +start_adjust(void) +{ + struct timeval newadj, oldadj; + struct timeval T1; + struct timezone tz; + double elapsed, accrued_error; + double adjust_required; + struct timeval exact_newadj; + double rounding_error; + double old_adjust_remaining; + + /* Determine the amount of error built up since the last adjustment */ + if (gettimeofday(&T1, &tz) < 0) { + CROAK("gettimeofday() failed in start_adjust"); + } + + UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0); + accrued_error = elapsed * current_freq; + + adjust_required = - (accrued_error + offset_register); + + UTI_DoubleToTimeval(adjust_required, &exact_newadj); + + /* At this point, we will need to call the adjustment rounding + algorithm in the system-specific layer. For now, just assume the + adjustment can be applied exactly. */ + + newadj = exact_newadj; + + /* Want to *add* rounding error back onto offset register */ + UTI_DiffTimevalsToDouble(&rounding_error, &exact_newadj, &newadj); + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in start_adjust"); + } + + UTI_TimevalToDouble(&oldadj, &old_adjust_remaining); + + offset_register = rounding_error - old_adjust_remaining; + + T0 = T1; + UTI_TimevalToDouble(&newadj, &adjustment_requested); + +} + +/* ================================================== */ + +static void +stop_adjust(void) +{ + struct timeval T1; + struct timezone tz; + struct timeval zeroadj, remadj; + double adjustment_remaining, adjustment_achieved; + double elapsed, elapsed_plus_adjust; + + + zeroadj = GET_ZERO; + + if (adjtime(&zeroadj, &remadj) < 0) { + CROAK("adjtime() failed in stop_adjust"); + } + + if (gettimeofday(&T1, &tz) < 0) { + CROAK("gettimeofday() failed in stop_adjust"); + } + + UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0); + UTI_TimevalToDouble(&remadj, &adjustment_remaining); + + 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) +{ + stop_adjust(); + offset_register += offset; + start_adjust(); + return; +} + +/* ================================================== */ + +/* Positive offset means system clock is fast of true time, therefore + step backwards */ + +static void +apply_step_offset(double offset) +{ + struct timeval old_time, new_time, rounded_new_time, T1; + double rounding_error; + struct timezone tz; + + stop_adjust(); + if (gettimeofday(&old_time, &tz) < 0) { + CROAK("gettimeofday in apply_step_offset"); + } + + UTI_AddDoubleToTimeval(&old_time, -offset, &new_time); + + /* The settimeofday function (on Solaris 2.5/Sparc20 at least) does + not work quite as we would want. The time we want to set is + rounded to the nearest second and that time is used. Also, the + clock appears to start from that second boundary plus about 4ms. + For now we'll tolerate this small error. */ + + rounded_new_time.tv_usec = 0; + if (new_time.tv_usec >= 500000) { + rounded_new_time.tv_sec = new_time.tv_sec + 1; + } else { + rounded_new_time.tv_sec = new_time.tv_sec; + } + + UTI_DiffTimevalsToDouble(&rounding_error, &rounded_new_time, &new_time); + + if (settimeofday(&new_time, &tz) < 0) { + CROAK("settimeofday in apply_step_offset"); + } + + UTI_AddDoubleToTimeval(&T0, offset, &T1); + T0 = T1; + + offset_register += rounding_error; + + start_adjust(); +} + +/* ================================================== */ + +static void +set_frequency(double new_freq_ppm) +{ + stop_adjust(); + current_freq = new_freq_ppm * 1.0e-6; + start_adjust(); +} + +/* ================================================== */ + +static double +read_frequency(void) +{ + return current_freq * 1.0e6; +} + +/* ================================================== */ + +static void +get_offset_correction(struct timeval *raw, + double *corr) +{ + stop_adjust(); + *corr = -offset_register; + start_adjust(); + return; +} + +/* ================================================== */ + +static void +immediate_step(void) +{ + return; +} + +/* ================================================== */ + +/* Interval in seconds between adjustments to cancel systematic drift */ +#define DRIFT_REMOVAL_INTERVAL (4.0) + +static int drift_removal_running = 0; +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(); + start_adjust(); + drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL); +} + +/* ================================================== */ + +static void +check_need_dosynctodr(void) +{ + struct utsname name; + int result; + int major, minor, veryminor, n_fields; + + result = uname(&name); + if (result < 0) { + LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot use uname to detect Solaris version"); + need_dosynctodr = 0; /* Assume recent Solaris where it isn't needed */ + return; + } + + n_fields = sscanf(name.release, "%d.%d.%d\n", &major, &minor, &veryminor); + + if (n_fields < 2) { + LOG(LOGS_ERR, LOGF_SysSolaris, "Solaris version doesn't appear to be of the form X.Y[.Z]"); + need_dosynctodr = 0; /* Assume recent Solaris where it isn't needed */ + return; + } + + if (major != 5) { + LOG(LOGS_ERR, LOGF_SysSolaris, "Solaris major version doesn't appear to be 5"); + need_dosynctodr = 0; /* Assume recent Solaris where it isn't needed */ + return; + } + + /* The 'rule of thumb' is that from Solaris 2.6 onwards, dosynctodr() doesn't + * need to be called, and in fact it is counter-productive to do so. For + * earlier versions, it is required. */ + + if (minor < 6) { + need_dosynctodr = 1; + } else { + need_dosynctodr = 0; + } + +} + +/* ================================================== */ + +static void +set_dosynctodr(unsigned long on_off) +{ + static struct nlist nl[] = { + {"dosynctodr"}, + {NULL} + }; + + kvm_t *kt; + unsigned long read_back; + + if (on_off!=1 && on_off!=0) { + CROAK("on_off should be 0 or 1"); + } + + kt = kvm_open(NULL, NULL, NULL, O_RDWR, NULL); + if (!kt) { + LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot open kvm to change dosynctodr"); + return; + } + + if (kvm_nlist(kt, nl) < 0) { + LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot read dosynctodr in nlist"); + kvm_close(kt); + return; + } + + if (kvm_write(kt, nl[0].n_value, (char *)(&on_off), sizeof(unsigned long)) < 0) { + LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot write to dosynctodr"); + kvm_close(kt); + return; + } + + if (kvm_read(kt, nl[0].n_value, (char *)(&read_back), sizeof(unsigned long)) < 0) { + LOG(LOGS_ERR, LOGF_SysSolaris, "Cannot read from dosynctodr"); + kvm_close(kt); + return; + } + + kvm_close(kt); + + if (read_back != on_off) { + CROAK("read_back should equal on_off"); + } + +#if 0 + LOG(LOGS_INFO, LOGF_SysSolaris, "Set value of dosynctodr to %d", on_off); +#endif + +} + +/* ================================================== */ + +void +SYS_Solaris_Initialise(void) +{ + + check_need_dosynctodr(); + + /* Need to do KVM stuff to turn off dosynctodr. */ + + clock_initialise(); + + lcl_RegisterSystemDrivers(read_frequency, set_frequency, + accrue_offset, apply_step_offset, + get_offset_correction, NULL /* immediate_step */); + + /* Turn off the kernel switch that keeps the system clock in step + with the non-volatile clock */ + if (need_dosynctodr) { + set_dosynctodr(0); + } + + drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL); + drift_removal_running = 1; +} + +/* ================================================== */ + +void +SYS_Solaris_Finalise(void) +{ + + if (drift_removal_running) { + SCH_RemoveTimeout(drift_removal_id); + } + + clock_finalise(); + + /* When exiting, we want to return the machine to its 'autonomous' + tracking mode */ + if (need_dosynctodr) { + set_dosynctodr(1); + } + + return; +} + +/* ================================================== */ + +#endif /* SOLARIS */ + diff --git a/sys_solaris.h b/sys_solaris.h new file mode 100644 index 0000000..2be4c86 --- /dev/null +++ b/sys_solaris.h @@ -0,0 +1,38 @@ +/* + $Header: /cvs/src/chrony/sys_solaris.h,v 1.7 2002/02/28 23:27:15 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for Solaris driver + */ + +#ifndef GOT_SYS_SOLARIS_H +#define GOT_SYS_SOLARIS_H + +void SYS_Solaris_Initialise(void); + +void SYS_Solaris_Finalise(void); + +#endif diff --git a/sys_sunos.c b/sys_sunos.c new file mode 100644 index 0000000..80c96dc --- /dev/null +++ b/sys_sunos.c @@ -0,0 +1,433 @@ +/* + $Header: /cvs/src/chrony/sys_sunos.c,v 1.18 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Driver file for the SunOS 4.1.x operating system. + */ + +#ifdef SUNOS + +#include +#include +#include +#include +#include + +#include +#include + +#include "sys_sunos.h" +#include "localp.h" +#include "logging.h" +#include "util.h" +#include "sched.h" + +/* ================================================== */ + +/* This register contains the number of seconds by which the local + clock was estimated to be fast of reference time at the epoch when + gettimeofday() returned T0 */ + +static double offset_register; + +/* This register contains the epoch to which the offset is referenced */ + +static struct timeval 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; + +/* Eventually, this needs to be a user-defined parameter - e.g. user + might want 5 to get much finer resolution like xntpd. We stick + with a reasonable number so that slewing can work. + + This value has to be a factor of 1 million, otherwise the noddy + method we use for rounding an adjustment to the nearest multiple of + this value won't work!! + + */ +static unsigned long our_tickadj = 100; + +/* ================================================== */ + +static void +clock_initialise(void) +{ + struct timeval newadj, oldadj; + struct timezone tz; + + offset_register = 0.0; + adjustment_requested = 0.0; + current_freq = 0.0; + + if (gettimeofday(&T0, &tz) < 0) { + CROAK("gettimeofday() failed in clock_initialise()"); + } + + newadj.tv_sec = 0; + newadj.tv_usec = 0; + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in clock_initialise"); + } + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in clock_initialise"); + } + + return; +} + +/* ================================================== */ + +static void +clock_finalise(void) +{ + /* Nothing to do yet */ + + return; + +} + +/* ================================================== */ + +static void +start_adjust(void) +{ + struct timeval newadj, oldadj; + struct timeval T1; + struct timezone tz; + double elapsed, accrued_error; + double adjust_required; + struct timeval exact_newadj; + double rounding_error; + double old_adjust_remaining; + long remainder, multiplier; + + /* Determine the amount of error built up since the last adjustment */ + if (gettimeofday(&T1, &tz) < 0) { + CROAK("gettimeofday() failed in start_adjust"); + } + + UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0); + accrued_error = elapsed * current_freq; + + adjust_required = - (accrued_error + offset_register); + + UTI_DoubleToTimeval(adjust_required, &exact_newadj); + + /* At this point, we need to round the required adjustment to the + closest multiple of _tickadj --- because SunOS can't process + other adjustments exactly and will silently discard the residual. + Obviously such behaviour can't be tolerated for us. */ + + newadj = exact_newadj; + remainder = newadj.tv_usec % our_tickadj; + multiplier = newadj.tv_usec / our_tickadj; + if (remainder >= (our_tickadj >> 1)) { + newadj.tv_usec = (multiplier + 1) * our_tickadj; + } else { + newadj.tv_usec = multiplier * our_tickadj; + } + + UTI_NormaliseTimeval(&newadj); + + /* Want to *add* rounding error back onto offset register. Note + that the exact adjustment was the offset register *negated* */ + UTI_DiffTimevalsToDouble(&rounding_error, &newadj, &exact_newadj); + + if (adjtime(&newadj, &oldadj) < 0) { + CROAK("adjtime() failed in start_adjust"); + } + + UTI_TimevalToDouble(&oldadj, &old_adjust_remaining); + + offset_register = rounding_error - old_adjust_remaining; + + T0 = T1; + UTI_TimevalToDouble(&newadj, &adjustment_requested); + +} + +/* ================================================== */ + +static void +stop_adjust(void) +{ + struct timeval T1; + struct timezone tz; + struct timeval zeroadj, remadj; + double adjustment_remaining, adjustment_achieved; + double gap; + double elapsed, elapsed_plus_adjust; + + zeroadj.tv_sec = 0; + zeroadj.tv_usec = 0; + + if (adjtime(&zeroadj, &remadj) < 0) { + CROAK("adjtime() failed in stop_adjust"); + } + + if (gettimeofday(&T1, &tz) < 0) { + CROAK("gettimeofday() failed in stop_adjust"); + } + + UTI_DiffTimevalsToDouble(&elapsed, &T1, &T0); + UTI_TimevalToDouble(&remadj, &adjustment_remaining); + + 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) +{ + stop_adjust(); + offset_register += offset; + start_adjust(); + return; +} + +/* ================================================== */ + +/* Positive offset means system clock is fast of true time, therefore + step backwards */ + +static void +apply_step_offset(double offset) +{ + struct timeval old_time, new_time, T1; + struct timezone tz; + + stop_adjust(); + if (gettimeofday(&old_time, &tz) < 0) { + CROAK("gettimeofday in apply_step_offset"); + } + + UTI_AddDoubleToTimeval(&old_time, -offset, &new_time); + + if (settimeofday(&new_time, &tz) < 0) { + CROAK("settimeofday in apply_step_offset"); + } + + UTI_AddDoubleToTimeval(&T0, offset, &T1); + T0 = T1; + + start_adjust(); + +} + +/* ================================================== */ + +static void +set_frequency(double new_freq_ppm) +{ + stop_adjust(); + current_freq = new_freq_ppm * 1.0e-6; + start_adjust(); +} + +/* ================================================== */ + +static double +read_frequency(void) +{ + return current_freq * 1.0e6; +} + +/* ================================================== */ + +static void +get_offset_correction(struct timeval *raw, + double *corr) +{ + stop_adjust(); + *corr = -offset_register; + start_adjust(); + return; +} + +/* ================================================== */ + +static void +immediate_step(void) +{ + return; +} + +/* ================================================== */ + +/* Interval in seconds between adjustments to cancel systematic drift */ +#define DRIFT_REMOVAL_INTERVAL (4.0) + +static int drift_removal_running = 0; +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(); + start_adjust(); + drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL); +} + +/* ================================================== */ + +static void +setup_kernel(unsigned long on_off) +{ + static struct nlist nl[] = { + {"_dosynctodr"}, + {"_tick"}, + {"_tickadj"}, + {NULL} + }; + + kvm_t *kt; + unsigned long read_back; + unsigned long our_tick = 10000; + unsigned long default_tickadj = 625; + + if (on_off!=1 && on_off!=0) { + CROAK("on_off should be 0 or 1"); + } + + kt = kvm_open(NULL, NULL, NULL, O_RDWR, NULL); + if (!kt) { + LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot open kvm"); + return; + } + + if (kvm_nlist(kt, nl) < 0) { + LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot read kernel symbols"); + kvm_close(kt); + return; + } + + if (kvm_write(kt, nl[0].n_value, (char *)(&on_off), sizeof(unsigned long)) < 0) { + LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot write to _dosynctodr"); + kvm_close(kt); + return; + } + + if (kvm_write(kt, nl[1].n_value, (char *)(&our_tick), sizeof(unsigned long)) < 0) { + LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot write to _tick"); + kvm_close(kt); + return; + } + + if (kvm_write(kt, nl[2].n_value, + (char *)(&(on_off ? default_tickadj : our_tickadj)), + sizeof(unsigned long)) < 0) { + LOG(LOGS_ERR, LOGF_SysSunOS, "Cannot write to _tickadj"); + kvm_close(kt); + return; + } + + kvm_close(kt); + +#if 0 + LOG(LOGS_INFO, LOGF_SysSunOS, "Set value of _dosynctodr to %d", on_off); +#endif + +} + +/* ================================================== */ + +void +SYS_SunOS_Initialise(void) +{ + + /* Need to do KVM stuff to turn off dosynctodr. */ + + clock_initialise(); + + lcl_RegisterSystemDrivers(read_frequency, set_frequency, + accrue_offset, apply_step_offset, + get_offset_correction, NULL /* immediate_step */); + + /* Turn off the kernel switch that keeps the system clock in step + with the non-volatile clock */ + setup_kernel(0); + + drift_removal_id = SCH_AddTimeoutByDelay(DRIFT_REMOVAL_INTERVAL, drift_removal_timeout, NULL); + drift_removal_running = 1; + +} + +/* ================================================== */ + +void +SYS_SunOS_Finalise(void) +{ + + if (drift_removal_running) { + SCH_RemoveTimeout(drift_removal_id); + } + + /* Turn dosynctodr back on?? */ + + clock_finalise(); + + /* When exiting, we want to return the machine to its 'autonomous' + tracking mode */ + setup_kernel(1); + + return; +} + +/* ================================================== */ + + +#endif /* SUNOS */ diff --git a/sys_sunos.h b/sys_sunos.h new file mode 100644 index 0000000..44a8062 --- /dev/null +++ b/sys_sunos.h @@ -0,0 +1,38 @@ +/* + $Header: /cvs/src/chrony/sys_sunos.h,v 1.7 2002/02/28 23:27:15 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Header file for Solaris driver + */ + +#ifndef GOT_SYS_SUNOS_H +#define GOT_SYS_SUNOS_H + +void SYS_SunOS_Initialise(void); + +void SYS_SunOS_Finalise(void); + +#endif diff --git a/sysincl.h b/sysincl.h new file mode 100644 index 0000000..afe340d --- /dev/null +++ b/sysincl.h @@ -0,0 +1,129 @@ +/* + $Header: /cvs/src/chrony/sysincl.h,v 1.10 2003/04/10 21:28:11 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + This file includes all system header files that the software + requires. This allows us to isolate system dependencies to this file + alone. + */ + +#ifndef GOT_SYSINCL_H +#define GOT_SYSINCL_H + +#if defined (SOLARIS) || defined(SUNOS) || defined(LINUX) || defined(__NetBSD__) + +#if !defined(__NetBSD__) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAS_STDINT_H +#include +#elif defined(HAS_INTTYPES_H) +#include +#else +/* Tough */ +#endif + +/* One or other of these to make getsid() visible */ +#define __EXTENSIONS__ 1 +#define __USE_XOPEN_EXTENDED 1 + +#include + +#endif + +#if defined (SOLARIS) || defined(SUNOS) +/* Only needed on these platforms, and doesn't exist on some Linux + versions. */ +#include +#endif + +#if defined (HAS_NO_BZERO) +#define bzero(ptr,n) memset(ptr,0,n) +#endif /* HAS_NO_BZERO */ + +#if defined (WINNT) + +/* Designed to work with the GCC from the GNAT-3.10 for Win32 + distribution */ + +#define Win32_Winsock +#include +#include + +#if 1 +/* Cheat and inline the necessary bits from . We don't + include it directly because it redefines some EXXX constants that + conflict with (included by ) */ + +int* _errno(); +int* __doserrno(); + +#define errno (*_errno()) +#define _doserrno (*__doserrno()) + +#define ENOENT 2 +#else + +#include +#endif + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#endif /* GOT_SYSINCL_H */ diff --git a/util.c b/util.c new file mode 100644 index 0000000..ce743c3 --- /dev/null +++ b/util.c @@ -0,0 +1,364 @@ +/* + $Header: /cvs/src/chrony/util.c,v 1.19 2003/03/24 23:35:43 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Various utility functions + */ + +#include "sysincl.h" + +#include "util.h" +#include "logging.h" + +/* ================================================== */ + +INLINE_STATIC void +UTI_TimevalToDouble(struct timeval *a, double *b) +{ + *b = (double)(a->tv_sec) + 1.0e-6 * (double)(a->tv_usec); + +} + +/* ================================================== */ + +INLINE_STATIC void +UTI_DoubleToTimeval(double a, struct timeval *b) +{ + long int_part, frac_part; + int_part = (long)(a); + frac_part = (long)(0.5 + 1.0e6 * (a - (double)(int_part))); + b->tv_sec = int_part; + b->tv_usec = frac_part; + UTI_NormaliseTimeval(b); +} + +/* ================================================== */ + +INLINE_STATIC int +UTI_CompareTimevals(struct timeval *a, struct timeval *b) +{ + if (a->tv_sec < b->tv_sec) { + return -1; + } else if (a->tv_sec > b->tv_sec) { + return +1; + } else { + if (a->tv_sec != b->tv_sec) { + CROAK("a->tv_sec != b->tv_sec"); + } + if (a->tv_usec < b->tv_usec) { + return -1; + } else if (a->tv_usec > b->tv_usec) { + return +1; + } else { + if (a->tv_usec != b->tv_usec) { + CROAK("a->tv_usec != b->tv_usec"); + } + return 0; + } + } + CROAK("Impossible"); /* Shouldn't be able to fall through. */ +} + +/* ================================================== */ + +INLINE_STATIC void +UTI_NormaliseTimeval(struct timeval *x) +{ + while (x->tv_usec >= 1000000) { + ++x->tv_sec; + x->tv_usec -= 1000000; + } + + while (x->tv_usec < 0) { + --x->tv_sec; + x->tv_usec += 1000000; + } + +} + +/* ================================================== */ + +INLINE_STATIC void +UTI_DiffTimevals(struct timeval *result, + struct timeval *a, + struct timeval *b) +{ + result->tv_sec = a->tv_sec - b->tv_sec; + result->tv_usec = a->tv_usec - b->tv_usec; + + /* Correct microseconds field to bring it into the range + [0,1000000) */ + + while (result->tv_usec < 0) { + result->tv_usec += 1000000; + --result->tv_sec; + } + + while (result->tv_usec > 999999) { + result->tv_usec -= 1000000; + ++result->tv_sec; + } + + return; +} + +/* ================================================== */ + +/* Calculate result = a - b and return as a double */ +INLINE_STATIC void +UTI_DiffTimevalsToDouble(double *result, + struct timeval *a, + struct timeval *b) +{ + *result = (double)(a->tv_sec - b->tv_sec) + + (double)(a->tv_usec - b->tv_usec) * 1.0e-6; +} + +/* ================================================== */ + +INLINE_STATIC void +UTI_AddDoubleToTimeval(struct timeval *start, + double increment, + struct timeval *end) +{ + long int_part, frac_part; + + /* Don't want to do this by using (long)(1000000 * increment), since + that will only cope with increments up to +/- 2148 seconds, which + is too marginal here. */ + + int_part = (long) increment; + frac_part = (long) (0.5 + 1.0e6 * (increment - (double)int_part)); + + end->tv_sec = int_part + start->tv_sec; + end->tv_usec = frac_part + start->tv_usec; + + UTI_NormaliseTimeval(end); +} + +/* ================================================== */ + +/* Calculate the average and difference (as a double) of two timevals */ +INLINE_STATIC void +UTI_AverageDiffTimevals (struct timeval *earlier, + struct timeval *later, + struct timeval *average, + double *diff) +{ + struct timeval tvdiff; + struct timeval tvhalf; + + UTI_DiffTimevals(&tvdiff, later, earlier); + *diff = (double)tvdiff.tv_sec + 1.0e-6 * (double)tvdiff.tv_usec; + + if (*diff < 0.0) { + /* Either there's a bug elsewhere causing 'earlier' and 'later' to + be backwards, or something wierd has happened. Maybe when we + change the frequency on Linux? */ + + /* This seems to be fairly benign, so don't bother logging it */ + +#if 0 + LOG(LOGS_INFO, LOGF_Util, "Earlier=[%s] Later=[%s]", + UTI_TimevalToString(earlier), UTI_TimevalToString(later)); +#endif + + /* Assume the required behaviour is to treat it as zero */ + *diff = 0.0; + } + + tvhalf.tv_sec = tvdiff.tv_sec / 2; + tvhalf.tv_usec = tvdiff.tv_usec / 2 + (tvdiff.tv_sec % 2); + + average->tv_sec = earlier->tv_sec + tvhalf.tv_sec; + average->tv_usec = earlier->tv_usec + tvhalf.tv_usec; + + /* Bring into range */ + UTI_NormaliseTimeval(average); + + while (average->tv_usec >= 1000000) { + ++average->tv_sec; + average->tv_usec -= 1000000; + } + + while (average->tv_usec < 0) { + --average->tv_sec; + average->tv_usec += 1000000; + } + +} + +/* ================================================== */ + +#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 timeval into a temporary string, largely for diagnostic + display */ + +char * +UTI_TimevalToString(struct timeval *tv) +{ + char buffer[64], *result; + struct tm stm; + stm = *gmtime((time_t *) &(tv->tv_sec)); + strftime(buffer, sizeof(buffer), "%a %x %X", &stm); + result = NEXT_BUFFER; + sprintf(result, "%s.%06ld", buffer, (unsigned long)(tv->tv_usec)); + return result; +} + +/* ================================================== */ +#define JAN_1970 0x83aa7e80UL + +inline static void +int64_to_timeval(NTP_int64 *src, + struct timeval *dest) +{ + dest->tv_sec = ntohl(src->hi) - JAN_1970; + + /* Until I invent a slick way to do this, just do it the obvious way */ + dest->tv_usec = (int)(0.5 + (double)(ntohl(src->lo)) / 4294.967296); +} + +/* ================================================== */ +/* Convert an NTP timestamp into a temporary string, largely + for diagnostic display */ + +char * +UTI_TimestampToString(NTP_int64 *ts) +{ + struct timeval tv; + int64_to_timeval(ts, &tv); + return UTI_TimevalToString(&tv); +} + +/* ================================================== */ + +char * +UTI_IPToDottedQuad(unsigned long ip) +{ + unsigned long a, b, c, d; + char *result; + a = (ip>>24) & 0xff; + b = (ip>>16) & 0xff; + c = (ip>> 8) & 0xff; + d = (ip>> 0) & 0xff; + result = NEXT_BUFFER; + sprintf(result, "%ld.%ld.%ld.%ld", a, b, c, d); + return result; +} + +/* ================================================== */ + +char * +UTI_TimeToLogForm(time_t t) +{ + struct tm stm; + char *result; + + result = NEXT_BUFFER; + + stm = *gmtime(&t); + strftime(result, BUFFER_LENGTH, "%Y-%m-%d %H:%M:%S", &stm); + + return result; +} + +/* ================================================== */ + +void +UTI_AdjustTimeval(struct timeval *old_tv, struct timeval *when, struct timeval *new_tv, double dfreq, double doffset) +{ + double elapsed, delta_time; + + UTI_DiffTimevalsToDouble(&elapsed, when, old_tv); + delta_time = elapsed * dfreq - doffset; + UTI_AddDoubleToTimeval(old_tv, delta_time, new_tv); +} + +/* ================================================== */ + +/* Seconds part of RFC1305 timestamp correponding to the origin of the + struct timeval format. */ +#define JAN_1970 0x83aa7e80UL + +void +UTI_TimevalToInt64(struct timeval *src, + NTP_int64 *dest) +{ + unsigned long usec = src->tv_usec; + unsigned long sec = src->tv_sec; + + /* Recognize zero as a special case - it always signifies + an 'unknown' value */ + if (!usec && !sec) { + dest->hi = dest->lo = 0; + } else { + dest->hi = htonl(src->tv_sec + JAN_1970); + + /* This formula gives an error of about 0.1us worst case */ + dest->lo = htonl(4295 * usec - (usec>>5) - (usec>>9)); + } +} + +/* ================================================== */ + +void +UTI_Int64ToTimeval(NTP_int64 *src, + struct timeval *dest) +{ + /* As yet, there is no need to check for zero - all processing that + has to detect that case is in the NTP layer */ + + dest->tv_sec = ntohl(src->hi) - JAN_1970; + + /* Until I invent a slick way to do this, just do it the obvious way */ + dest->tv_usec = (int)(0.5 + (double)(ntohl(src->lo)) / 4294.967296); +} + +/* ================================================== */ +/* Force a core dump and exit without doing abort() or assert(0). + These do funny things with the call stack in the core file that is + generated, which makes diagnosis difficult. */ + +int +croak(const char *file, int line, const char *msg) +{ + int a; + LOG(LOGS_ERR, LOGF_Util, "Unexpected condition [%s] at %s:%d, core dumped", + msg, file, line); + a = * (int *) 0; + return a; /* Can't happen - this stops the optimiser optimising the + line above */ +} + +/* ================================================== */ diff --git a/util.h b/util.h new file mode 100644 index 0000000..03bd660 --- /dev/null +++ b/util.h @@ -0,0 +1,104 @@ +/* + $Header: /cvs/src/chrony/util.h,v 1.14 2003/04/01 20:07:20 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + Various utility functions + */ + +#ifndef GOT_UTIL_H +#define GOT_UTIL_H + +#include "sysincl.h" + +#include "ntp.h" + +/* Convert a timeval into a floating point number of seconds */ +extern void UTI_TimevalToDouble(struct timeval *a, double *b); + +/* Convert a number of seconds expressed in floating point into a + timeval */ +extern void UTI_DoubleToTimeval(double a, struct timeval *b); + +/* 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_CompareTimevals(struct timeval *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); + +/* Calculate result = a - b */ +extern void UTI_DiffTimevals(struct timeval *result, struct timeval *a, struct timeval *b); + +/* Calculate result = a - b and return as a double */ +extern void UTI_DiffTimevalsToDouble(double *result, struct timeval *a, struct timeval *b); + +/* Add a double increment to a timeval 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_AddDoubleToTimeval(struct timeval *start, double increment, struct timeval *end); + +/* Calculate the average and difference (as a double) of two timevals */ +extern void UTI_AverageDiffTimevals(struct timeval *earlier, struct timeval *later, struct timeval *average, double *diff); + +/* Convert a timeval into a temporary string, largely for diagnostic + display */ +extern char *UTI_TimevalToString(struct timeval *tv); + +/* Convert an NTP timestamp into a temporary string, largely for + diagnostic display */ +extern char *UTI_TimestampToString(NTP_int64 *ts); + +/* Convert an IP address to dotted quad notation, for diagnostics */ +extern char *UTI_IPToDottedQuad(unsigned long ip); + +extern char *UTI_TimeToLogForm(time_t t); + +/* Adjust time following a frequency/offset change */ +extern void UTI_AdjustTimeval(struct timeval *old_tv, struct timeval *when, struct timeval *new_tv, double dfreq, double doffset); + + +extern void UTI_TimevalToInt64(struct timeval *src, NTP_int64 *dest); + +extern void UTI_Int64ToTimeval(NTP_int64 *src, struct timeval *dest); + +/* Like assert(0) */ + +#if defined(LINUX) && defined(__alpha__) +#define CROAK(message) assert(0) /* Added JGH Feb 24 2001 FIXME */ +#else +extern int croak(const char *file, int line, const char *msg); +#define CROAK(message) croak(__FILE__, __LINE__, message); +#endif + +#if defined (INLINE_UTILITIES) +#define INLINE_STATIC inline static +#include "util.c" +#else +#define INLINE_STATIC +#endif /* defined (INLINE_UTILITIES) */ + +#endif /* GOT_UTIL_H */ diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..d5a0b00 --- /dev/null +++ b/version.txt @@ -0,0 +1,4 @@ +#ifndef VERSION_H +#define VERSION_H +#define PROGRAM_VERSION_STRING "$Name: $" +#endif /* VERSION_H */ diff --git a/wrap_adjtimex.c b/wrap_adjtimex.c new file mode 100644 index 0000000..8d97f50 --- /dev/null +++ b/wrap_adjtimex.c @@ -0,0 +1,155 @@ +/* + $Header: /cvs/src/chrony/wrap_adjtimex.c,v 1.9 2002/11/19 21:33:42 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + This is a wrapper around the Linux adjtimex system call. It isolates the + inclusion of from the need to include other header files, + many of which conflict with those in on some recent distributions + (as of Jul 2000) using kernels around 2.2.16 onwards. + + */ + +#ifdef LINUX + +#define _LOOSE_KERNEL_NAMES + +#include +#include +#include + +#include "wrap_adjtimex.h" + +/* This doesn't seem to be in any include files !! */ + +extern int adjtimex(struct timex *); + +int +TMX_SetTick(long tick) +{ + struct timex txc; + txc.modes = ADJ_TICK; + txc.tick = tick; + + return adjtimex(&txc); +} + +int +TMX_ApplyOffset(long *offset) +{ + struct timex txc; + int result; + + txc.modes = ADJ_OFFSET_SINGLESHOT; + txc.offset = *offset; + result = adjtimex(&txc); + *offset = txc.offset; + return result; +} + +int +TMX_SetFrequency(double freq, long tick) +{ + struct timex txc; + + txc.modes = ADJ_TICK | ADJ_FREQUENCY | ADJ_STATUS; + + txc.freq = (long)(freq * (double)(1 << SHIFT_USEC)); + txc.tick = tick; + txc.status = STA_UNSYNC; /* Prevent any of the FLL/PLL stuff coming + up */ + + return adjtimex(&txc); +} + +int +TMX_GetFrequency(double *freq) +{ + struct timex txc; + int result; + txc.modes = 0; /* pure read */ + result = adjtimex(&txc); + *freq = txc.freq / (double)(1 << SHIFT_USEC); + return result; +} + +int +TMX_GetOffsetLeft(long *offset) +{ + struct timex txc; + int result; + txc.modes = 0; /* pure read */ + result = adjtimex(&txc); + *offset = txc.offset; + return result; +} + +int +TMX_ReadCurrentParams(struct tmx_params *params) +{ + struct timex txc; + int result; + + txc.modes = 0; /* pure read */ + result = adjtimex(&txc); + + params->tick = txc.tick; + params->offset = txc.offset; + params->freq = txc.freq; + params->dfreq = txc.freq / (double)(1 << SHIFT_USEC); + params->maxerror = txc.maxerror; + params->esterror = txc.esterror; + + params->sta_pll = (txc.status & STA_PLL); + params->sta_ppsfreq = (txc.status & STA_PPSFREQ); + params->sta_ppstime = (txc.status & STA_PPSTIME); + params->sta_fll = (txc.status & STA_FLL); + params->sta_ins = (txc.status & STA_INS); + params->sta_del = (txc.status & STA_DEL); + params->sta_unsync = (txc.status & STA_UNSYNC); + params->sta_freqhold = (txc.status & STA_FREQHOLD); + params->sta_ppssignal = (txc.status & STA_PPSSIGNAL); + params->sta_ppsjitter = (txc.status & STA_PPSJITTER); + params->sta_ppswander = (txc.status & STA_PPSWANDER); + params->sta_ppserror = (txc.status & STA_PPSERROR); + params->sta_clockerr = (txc.status & STA_CLOCKERR); + + params->constant = txc.constant; + params->precision = txc.precision; + params->tolerance = txc.tolerance; + params->ppsfreq = txc.ppsfreq; + params->jitter = txc.jitter; + params->shift = txc.shift; + params->stabil = txc.stabil; + params->jitcnt = txc.jitcnt; + params->calcnt = txc.calcnt; + params->errcnt = txc.errcnt; + params->stbcnt = txc.stbcnt; + + return result; +} + +#endif + diff --git a/wrap_adjtimex.h b/wrap_adjtimex.h new file mode 100644 index 0000000..7e44c6a --- /dev/null +++ b/wrap_adjtimex.h @@ -0,0 +1,79 @@ +/* + $Header: /cvs/src/chrony/wrap_adjtimex.h,v 1.6 2002/11/19 21:33:42 richard Exp $ + + ======================================================================= + + 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + The header file for the adjtimex wrapper + */ + +#ifndef GOT_WRAP_ADJTIMEX_H +#define GOT_WRAP_ADJTIMEX_H + +/* Cut-down version of struct timex */ +struct tmx_params { + long tick; + long offset; + long freq; + double dfreq; + long maxerror; + long esterror; + + unsigned sta_pll:1; + unsigned sta_ppsfreq:1; + unsigned sta_ppstime:1; + unsigned sta_fll:1; + unsigned sta_ins:1; + unsigned sta_del:1; + unsigned sta_unsync:1; + unsigned sta_freqhold:1; + unsigned sta_ppssignal:1; + unsigned sta_ppsjitter:1; + unsigned sta_ppswander:1; + unsigned sta_ppserror:1; + unsigned sta_clockerr:1; + + int status; + long constant; + long precision; + long tolerance; + long ppsfreq; + long jitter; + int shift; + long stabil; + long jitcnt; + long calcnt; + long errcnt; + long stbcnt; +}; + +int TMX_SetTick(long tick); +int TMX_ApplyOffset(long *offset); +int TMX_SetFrequency(double freq, long tick); +int TMX_GetFrequency(double *freq); +int TMX_GetOffsetLeft(long *offset); +int TMX_ReadCurrentParams(struct tmx_params *params); + +#endif /* GOT_WRAP_ADJTIMEX_H */ +