From: Richard P. Curnow Date: Thu, 19 Jan 2006 21:34:28 +0000 (+0000) Subject: Equivalent to V1.19.99.1 X-Git-Tag: 1.20~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=88840341045de784db28205584fa1e470010c3c3;p=thirdparty%2Fchrony.git 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. --- 88840341045de784db28205584fa1e470010c3c3 diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..916d1f0f --- /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 00000000..22997808 --- /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 00000000..0a91caff --- /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 00000000..aeee89b9 --- /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 00000000..4846ec8f --- /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 00000000..26e87088 --- /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 00000000..f96f0fb0 --- /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 00000000..be217e7d --- /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 00000000..01013bc2 --- /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 00000000..30673186 --- /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 00000000..e411fc35 --- /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 00000000..81e6ee2e --- /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 00000000..968662ef --- /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 00000000..1e657855 --- /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 00000000..361617ae --- /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 00000000..4642bb71 --- /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 00000000..9ac9e952 --- /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 00000000..4f5fa062 --- /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 00000000..04d4b44e --- /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 00000000..fec7f371 --- /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 00000000..72427452 --- /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 00000000..5f99d997 --- /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 00000000..7dcd539c --- /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 00000000..3353ac1e --- /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 00000000..38cc74cb --- /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 00000000..48f9436a --- /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 00000000..1dfe0d0f --- /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 00000000..5672f9a7 --- /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 00000000..916d1f0f --- /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 00000000..4c1cb46d --- /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 00000000..fc9858bd --- /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 00000000..e443c9bc --- /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 00000000..14cbceb0 --- /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 00000000..8c353484 --- /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 00000000..34f6e761 --- /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 00000000..4c0b4375 --- /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 00000000..d3ede748 --- /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 00000000..c551dfeb --- /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 00000000..48b79995 --- /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 00000000..e5eda114 --- /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 00000000..2c417521 --- /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 00000000..5488e72d --- /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 00000000..181e96e7 --- /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 00000000..659519fd --- /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 00000000..8151febb --- /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 00000000..e4819bd0 --- /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 00000000..d2d87229 --- /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 00000000..7f27fd4d --- /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 00000000..9348c08d --- /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 00000000..59c8e07e --- /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 00000000..857d8b77 --- /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 00000000..3e01a33e --- /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 00000000..974c7dff --- /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 00000000..33a792dc --- /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 00000000..5424d1d4 --- /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 00000000..aaf071a1 --- /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 00000000..83f7ceba --- /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 00000000..86045ee1 --- /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 00000000..f997a6ec --- /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 00000000..aaf9548f --- /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 00000000..bf4d48d7 --- /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 00000000..5e6af7d6 --- /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 00000000..632a7da0 --- /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 00000000..6a4d6e98 --- /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 00000000..e62f334b --- /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 00000000..b0e0bb99 --- /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 00000000..6d5852a0 --- /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 00000000..84b21d93 --- /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 00000000..91bee1d3 --- /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 00000000..b7a763f1 --- /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 00000000..ab3afd17 --- /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 00000000..2e8499c1 --- /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 00000000..3f67347a --- /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 00000000..f8b53f6f --- /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 00000000..92f0550f --- /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 00000000..19b3f0ee --- /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 00000000..4b6ab0f0 --- /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 00000000..fb443ff8 --- /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 00000000..a2bdb97e --- /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 00000000..fbb88842 --- /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 00000000..3cfdb87c --- /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 00000000..b01e584e --- /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 00000000..7c0025a4 --- /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 00000000..2843a64c --- /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 00000000..de624301 --- /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 00000000..1dae611a --- /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 00000000..c895cacf --- /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 00000000..1cedbb52 --- /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 00000000..e335e2db --- /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 00000000..5d273b95 --- /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 00000000..85d14e99 --- /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 00000000..9052cf74 --- /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 00000000..973da423 --- /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 00000000..3daa2011 --- /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 00000000..a17e51e2 --- /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 00000000..3b275f39 --- /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 00000000..22ebdb01 --- /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 00000000..8fdeef31 --- /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 00000000..2be4c869 --- /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 00000000..80c96dcb --- /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 00000000..44a80627 --- /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 00000000..afe340d2 --- /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 00000000..ce743c39 --- /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 00000000..03bd660e --- /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 00000000..d5a0b009 --- /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 00000000..8d97f50a --- /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 00000000..7e44c6a9 --- /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 */ +