commit 45af90e98af7b016f73a6f9773348ac55bed1412 Author: Poul-Henning Kamp Date: Sun Dec 21 22:31:18 2014 +0000 First preview release. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..5b4be92 --- /dev/null +++ b/README.rst @@ -0,0 +1,218 @@ +Ntimed -- Network Time Synchronization +====================================== + +What is this ? +~~~~~~~~~~~~~~ + +This is a preview/early-acces/alpha/buzzword-of-the-times release +of a new FOSS project written to gradually take over the world of +networked timekeeping. + +The first step is a NTP protocol client daemon, 'Ntimed-client', +which will synchronize a systems clock to some set of NTP servers + +If this catches on, support for slave servers, refclocks and other +protocols, such as PTP, can be added, subject to interest, skill, +time and money. + +The overall architectural goals are the same as every other FOSS +project claims to follow: Simplicity, Quality, Security etc. etc. +but I tend to think that we stick a little bit more closely to them. + +This work is sponsored by Linux Foundation, partly in response to +the HeartBleed fiasco, and after studying the 300,000+ lines of +source-code in NTPD, I concluded that while it *could* be salvaged, +it would be more economical, much faster and far more efficient to +start from scratch. + +This is the result. + + +What should you do with this +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can take this code, compile it, run it, and it will steer your +computers clock, but I am not going to encourage you to do that in +production yet -- unless you know what you are doing and why you +are doing it: This is only a preview release. + +Soon-ish, there will be full production-ready releases and +packages for your favourite operating system, but we are not +there yet. + +But if you are willing to read C-source code to figure out what the +printouts mean, if you care about quality time-keeping or quality +programming, I would love to hear your feedback, reviews and ideas. + + +Where can I read more ? +~~~~~~~~~~~~~~~~~~~~~~~ + +I maintain a blog-of-sorts about this project here: + + http://phk.freebsd.dk/time + +There you will find information about theory, practice +and the thinking that tries to bridge the gap between them. + +Updates typically happen during weekends -- that is when I work on +Ntimed. + + +Who do I yell at, and how ? +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Me, Poul-Henning Kamp. Please send email to 'phk@Ntimed.org'. + + +What happens next ? +~~~~~~~~~~~~~~~~~~~ + +The plan is to have the first production ready release in Q1/2015. + +Hopefully OS releases will then adopt Ntimed, first as alternative +to, and later as replacement for NTPD in client applications. + +It is not my intent to start and manage an entirely new FOSS project +around Ntimed, Harlan from The Network Time Foundation has agreed +to adopt Ntimed and it will run in/with/parallel to the NTPD project. +Or something. We still need to flesh out all those details. + + +How to compile +~~~~~~~~~~~~~~ + +Pull the source code over to the machine you want to play on, and:: + + sh configure + make + +(That was fast, wasn't it ? -- Amazing how slow things arean't +when you don't go hunting for 27 different FORTRAN compilers for +your C code.) + + +How to test +~~~~~~~~~~~ + +Stop ntpd if it is running, and then:: + + ./ntimed-client -t /tmp/somefile some_ntp_server some_other_ntp_server + +That should synchronize your clock to those servers. + +Because this is a preview release, the process will not "daemonize" +into the background. + +The '-t /tmp/somefile' arguments tells it to write a full blow-by-blow +tracefile, for analysis and debugging. + +If something goes wrong, I'm going to ask you for a copy the tracefile. + + +What happens when you run it ? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +After a few seconds, your clock will be stepped if necessary. +(In the final version, this is where the process daemonizes into the +background -- from which point you can trust your clock to be good.) + +In the next 30-60 seconds, the PLL will eliminate any residual phase +error and from this point in time, your computers clock should be +good to a few milliseconds, depending on the quality of the servers. + +After about 5-10 minutes, the PLL will have integrated the +frequency error of your computers crystal, and the PLL will +start to "stiffen" to minimize the amount of steering necessary +to keep the clock aligned to the servers. + +If you are using distant or very distant servers, things will +take longer. + + +Packet traces and simulations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also run the program two other ways:: + + ./Ntimed-client --poll-server some_ntp_server some_other_ntp_server + +This will *not* steer your clock, but it will query the servers as +if it should have steered your clock. (Ntpd should *not* be running +at the same time.) By default it terminates after 1800 seconds, +but you can control that with "-d 3600" for one hour etc. + +If you save the output into a file (redirect stdout or use '-t filename'), +you can use it as input for a simulation run:: + + ./Ntimed-client --sim-client -s filename -t /tmp/_ + +And then run:: + + python plotgen.py + +Then you can finally get some nice pictures to look at by:: + + gnuplot + load '/tmp/_g' + + +Tweaking parameters +~~~~~~~~~~~~~~~~~~~ + +Parameters can be examined and tweaked with '-p' arguments:: + + -p '?' + +Gives a list of available parameters, and you can get information about +each parameter:: + + -p parameter_name + +To set the parameter to a non-default value:: + + -p parameter_name=new_value + +Not everything which should be a parameter is yet, and there are +some unused dummy parameters there, just to make sure the macro-magic +works. + + +Thanks and acknowledegments +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First and foremost a big thanks to Professor Dave L. Mills. + +Thanks for being the first time-nut on the InterNETs, as we called +them back then. + +Thanks for being an all-round pleasant fellow to work with. + +Thanks for adopting my 'nanokernel' and 'refclock_oncore'. + +But in particular thanks for lending me the most cantankerous LORAN-C +receiver the world have ever seen, at a time in my life where I +badly needed that a distraction to keep me sane. + +A big thanks to the Linux Foundation for realizing that NTPD was +in dire straits after Dave Mills retired. + +Thanks for giving me money and free hands to do what I thought was +best -- even though I am a "BSD-guy". + +Thanks to Harlan Steen for keeping the NTPD flame burning, however +stormy the last decade has been. + +I trust The Network Time Foundation will take as good care of Ntimed +in the future, as it has taken care of NTPD in the past. + +A special wave of the hat to John R. Vig for his famous Quartz +Crystal Tutorial. + +And finally, a shout-out and thanks to time-nuts@ in general and +Tom Van Baak in particular, for being jolly and interesting company +for people who happen to care about nanoseconds, leap seconds, +choke-ring antennas and the finer points of SC- vs. AT-cut quartz +crystals. + +*phk* diff --git a/combine_delta.c b/combine_delta.c new file mode 100644 index 0000000..abd0c2a --- /dev/null +++ b/combine_delta.c @@ -0,0 +1,216 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Source Combiner based on delta-pdfs + * =================================== + * + * The basic principle here is that sources gives us four values: + * - The highest low value were the probability is zero. + * - The lowest high value were the probability is zero. + * - The most probable value + * - The relative trust in that value [0...1] + * Together this defines a triangular probability density function. + * + * The combiner adds all these pdfs' together weighted by trust + * and finds the highest probability which sports a quorum. + * + * See also: http://phk.freebsd.dk/time/20141107.html + * + * XXX: decay trust by age + * XXX: auto determine quorom if param not set + * XXX: param for minimum probability density + */ + +#include +#include +#include + +#include "ntimed.h" + +struct combine_delta; + +struct cd_stat { + double x; + double prob; + unsigned quorum; +}; + +struct cd_source { + unsigned magic; +#define CD_SOURCE_MAGIC 0x2799775c + TAILQ_ENTRY(cd_source) list; + + struct combiner combiner; + + struct combine_delta *cd; + double trust, low, mid, high; + int tb_gen; +}; + +struct combine_delta { + unsigned magic; +#define COMBINE_DELTA_MAGIC 0x8dc5030c + + unsigned nsrc; + TAILQ_HEAD(, cd_source) head; +}; + +struct combine_delta * +CD_New(void) +{ + struct combine_delta *cd; + + ALLOC_OBJ(cd, COMBINE_DELTA_MAGIC); + AN(cd); + + TAILQ_INIT(&cd->head); + return (cd); +} + +static void +cd_try_peak(const struct combine_delta *cd, double *mx, double *my, double x, + struct cd_stat *st) +{ + struct cd_source *cs; + + // XXX: Hack to make plots with log zscale and only one + // XXX: source look sensible. + st->x = x; + st->prob = 0.001; + st->quorum = 0; + + TAILQ_FOREACH(cs, &cd->head, list) { + if (cs->tb_gen != TB_generation) + continue; + if (x < cs->low) + continue; + if (x > cs->high) + continue; + if (cs->low >= cs->high) + continue; + + st->quorum++; + if (x < cs->mid) { + st->prob += cs->trust * 2.0 * (x - cs->low) / + ((cs->high - cs->low) * (cs->mid - cs->low)); + } else { + st->prob += cs->trust * 2.0 * (cs->high - x) / + ((cs->high - cs->low) * (cs->high - cs->mid)); + } + if (isnan(st->prob)) { + Fail(NULL, 0, "lo %.3e hi %.3e mid %.3e", + cs->low, cs->high, cs->mid); + } + } + if (st->prob > *my) { + *my = st->prob; + *mx = x; + } +} + +static int +stat_cmp(const void *p1, const void *p2) +{ + const struct cd_stat *left = p1; + const struct cd_stat *right = p2; + + /*lint -save -e514 */ + return ((left->x > right->x) - (left->x < right->x)); + /*lint -restore */ +} + +static void +cd_find_peak(struct ocx *ocx, const struct combine_delta *cd) +{ + struct cd_source *cs; + double max_x = 0; + double max_y = 1; + struct cd_stat st[cd->nsrc * 3L]; + size_t m; + + m = 0; + TAILQ_FOREACH(cs, &cd->head, list) { + if (cs->tb_gen != TB_generation) + continue; + cd_try_peak(cd, &max_x, &max_y, cs->low, &st[m++]); + cd_try_peak(cd, &max_x, &max_y, cs->mid, &st[m++]); + cd_try_peak(cd, &max_x, &max_y, cs->high, &st[m++]); + } + Put(ocx, OCX_TRACE, + " %.3e %.3e %.3e\n", max_x, max_y, log(max_y)/log(10.)); + PLL(ocx, max_x, max_y); + qsort(st, cd->nsrc * 3L, sizeof st[0], stat_cmp); +} + +static void __match_proto__(combine_f) +cd_filter(struct ocx *ocx, const struct combiner *cb, + double trust, double low, double mid, double high) +{ + struct cd_source *cs; + struct combine_delta *cd; + + CHECK_OBJ_NOTNULL(cb, COMBINER_MAGIC); + CAST_OBJ_NOTNULL(cs, cb->priv, CD_SOURCE_MAGIC); + cd = cs->cd; + CHECK_OBJ_NOTNULL(cd, COMBINE_DELTA_MAGIC); + + /* Sign: local - remote -> postive is ahead */ + assert(trust >= 0 && trust <= 1.0); + cs->trust = trust; + cs->low = low; + cs->mid = mid; + cs->high = high; + cs->tb_gen = TB_generation; + + Put(ocx, OCX_TRACE, + "Combine %s %s %.6f %.6f %.6f", cb->name1, cb->name2, + cs->low, cs->mid, cs->high); + + cd_find_peak(ocx, cd); +} + +struct combiner * +CD_AddSource(struct combine_delta *cd, const char *name1, const char *name2) +{ + struct cd_source *cs; + + CHECK_OBJ_NOTNULL(cd, COMBINE_DELTA_MAGIC); + + ALLOC_OBJ(cs, CD_SOURCE_MAGIC); + AN(cs); + cs->cd = cd; + cs->low = cs->mid = cs->high = NAN; + TAILQ_INSERT_TAIL(&cd->head, cs, list); + + INIT_OBJ(&cs->combiner, COMBINER_MAGIC); + cs->combiner.func = cd_filter; + cs->combiner.priv = cs; + cs->combiner.name1 = name1; + cs->combiner.name2 = name2; + + cd->nsrc += 1; + + return (&cs->combiner); +} diff --git a/configure b/configure new file mode 100644 index 0000000..eee1fb4 --- /dev/null +++ b/configure @@ -0,0 +1,144 @@ +#!/bin/sh +# +# Copyright (c) 2014 Poul-Henning Kamp +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Handwritten configure script. +# ============================= +# +# Before you suggest I use tools for this, please read: +# +# https://www.varnish-cache.org/docs/trunk/phk/autocrap.html +# and +# http://queue.acm.org/detail.cfm?id=2349257 +# + +set -e + +# List of .h files +# NB: These SHALL always be included with #include "..." + +HDRS=' + ntimed.h + ntimed_endian.h + ntimed_queue.h + ntimed_tricks.h + ntp.h + ntp_tbl.h + param_instance.h + param_tbl.h + udp.h +' + +# List of .c files + +SRCS=' + combine_delta.c + main.c + main_client.c + main_poll_server.c + main_sim_client.c + ntp_filter.c + ntp_packet.c + ntp_peer.c + ntp_peerset.c + ntp_tools.c + ocx_stdio.c + param.c + pll_std.c + time_sim.c + time_stuff.c + time_unix.c + todo.c + udp.c +' + +if [ -f /usr/share/mk/bsd.prog.mk ] ; then + echo "Found bsd.prog.mk, will use it." + ( + echo '# BSD-style Makefile generated by configure' + echo 'PROG = ntimed-client' + for f in ${SRCS} + do + echo "SRCS += ${f}" + done + + echo 'NO_MAN = not_yet' + echo 'LDADD += -lm' + echo 'WARNS ?= 6' + echo '.include ' + ) > Makefile + + msg=", remeber to run 'make depend'" +else + ( + echo '# Portable Makefile generated by configure' + echo '' + echo 'all: ntimed-client' + echo '' + echo "CFLAGS += -Wall -Werror" + echo '' + + for f in ${HDRS} + do + b=`basename $f .h` + i=`sed -n -e '/#include.*"/{ + s/"$// + s/.*"// + p + }' $f | sort -u` + if [ "x${i}" != "x" ] ; then + echo "${b}.h: " ${i} + echo " touch ${b}.h" + echo + fi + done + + l="" + for f in ${SRCS} + do + b=`basename $f .c` + i=`sed -n -e '/#include.*"/{ + s/"$// + s/.*"// + p + }' $f | sort -u` + echo "${b}.o: ${b}.c" ${i} + echo + l="${l} ${b}.o" + done + + echo + echo "ntimed-client: ${l}" + echo " \${CC} \${CFLAGS} -o ntimed-client ${l} -lm" + echo + echo "clean:" + echo " rm -f ${l} ntimed-client" + echo + echo "depend:" + echo " @echo Dependencies already done" + ) > Makefile +fi + +echo "Makefile generated${msg}" diff --git a/main.c b/main.c new file mode 100644 index 0000000..449ba9a --- /dev/null +++ b/main.c @@ -0,0 +1,74 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Main main() functions + * ===================== + * + */ + +#include + +#include "ntimed.h" +#include "ntp.h" + +/*************************************************************************/ + +static void +dummy(void) +{ + // Reference otherwise unused "library" functions + + NTP_Peer_Destroy(NULL); +} + +static int +main_run_tests(int argc, char * const * argv) +{ + + (void)argc; + (void)argv; + + Time_Unix_Passive(); + + TS_RunTest(NULL); + + return (0); +} + +int +main(int argc, char * const *argv) +{ + if (getpid() == 0) + dummy(); + + if (argc > 1 && !strcmp(argv[1], "--poll-server")) + return (main_poll_server(argc - 1, argv + 1)); + if (argc > 1 && !strcmp(argv[1], "--sim-client")) + return (main_sim_client(argc - 1, argv + 1)); + if (argc > 1 && !strcmp(argv[1], "--run-tests")) + return (main_run_tests(argc - 1, argv + 1)); + + return (main_client(argc, argv)); +} diff --git a/main_client.c b/main_client.c new file mode 100644 index 0000000..d1e3b97 --- /dev/null +++ b/main_client.c @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Client main function + * ==================== + * + * Steer system time based on NTP servers + * + */ + +#include +#include +#include + +#include "ntimed.h" +#include "ntp.h" +#include "udp.h" + +#define PARAM_CLIENT PARAM_INSTANCE +#define PARAM_TABLE_NAME client_param_table +#include "param_instance.h" +#undef PARAM_TABLE_NAME +#undef PARAM_CLIENT + +int +main_client(int argc, char *const *argv) +{ + int ch; + struct ntp_peer *np; + struct ntp_peerset *npl; + struct todolist *tdl; + struct combine_delta *cd; + int fd; + int npeer = 0; + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + tdl = TODO_NewList(); + Time_Unix(tdl); + + PLL_Init(); + + npl = NTP_PeerSet_New(NULL); + + Param_Register(client_param_table); + NF_Init(); + + while ((ch = getopt(argc, argv, "p:t:")) != -1) { + switch(ch) { + case 'p': + Param_Tweak(NULL, optarg); + break; + case 't': + ArgTracefile(optarg); + break; + default: + Fail(NULL, 0, + "Usage %s [-p param] [-t tracefile] servers...", + argv[0]); + break; + } + } + argc -= optind; + argv += optind; + + for (ch = 0; ch < argc; ch++) + npeer += NTP_PeerSet_Add(NULL, npl, argv[ch]); + if (npeer == 0) + Fail(NULL, 0, "No NTP peers found"); + + Put(NULL, OCX_TRACE, "# NTIMED Format client 1.0\n"); + Put(NULL, OCX_TRACE, "# Found %d peers\n", npeer); + + Param_Report(NULL, OCX_TRACE); + + fd = UdpTimedSocket(NULL, AF_INET); + if (fd < 0) + Fail(NULL, errno, "Could not open UDP socket"); + + cd = CD_New(); + + NTP_PeerSet_Foreach(np, npl) { + NF_New(np); + np->combiner = CD_AddSource(cd, np->hostname, np->ip); + } + + NTP_PeerSet_Poll(NULL, npl, fd, tdl); + + (void)TODO_Run(NULL, tdl); + + return (0); +} diff --git a/main_poll_server.c b/main_poll_server.c new file mode 100644 index 0000000..0f515ea --- /dev/null +++ b/main_poll_server.c @@ -0,0 +1,160 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * poll-server + * [-d duration] When to stop + * [-m monitor] Poll this monitor every 32 seconds + * [-t tracefile] Where to save the output (if not stdout) + * server ... What servers to poll + * + */ + +#include +#include + +#include + +#include "ntimed.h" +#include "ntp.h" +#include "udp.h" + +static int fd; + +static void +mps_filter(struct ocx *ocx, const struct ntp_peer *np) +{ + char buf[256]; + + NTP_Tool_Format(buf, sizeof buf, np->rx_pkt); + Put(ocx, OCX_TRACE, "Poll %s %s %s\n", np->hostname, np->ip, buf); +} + +static enum todo_e __match_proto__(todo_f) +mps_mon(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + char buf[256]; + struct ntp_peer *np; + int i; + + (void)ocx; + (void)tdl; + CAST_OBJ_NOTNULL(np, priv, NTP_PEER_MAGIC); + i = NTP_Peer_Poll(ocx, fd, np, 0.2); + if (i == 1) { + NTP_Tool_Format(buf, sizeof buf, np->rx_pkt); + Put(ocx, OCX_TRACE, + "Monitor %s %s %s\n", np->hostname, np->ip, buf); + } else { + Put(ocx, OCX_TRACE, + "Monitor_err %s %s %d\n", np->hostname, np->ip, i); + } + return(TODO_OK); +} + +static enum todo_e __match_proto__(todo_f) +mps_end(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + (void)tdl; + (void)priv; + Put(ocx, OCX_TRACE, "# Run completed\n"); + return(TODO_FAIL); +} + +int +main_poll_server(int argc, char *const *argv) +{ + int ch; + int npeer = 0; + char *p; + struct ntp_peerset *npl; + struct ntp_peer *mon = NULL; + struct ntp_peer *np; + struct todolist *tdl; + double duration = 1800; + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + ArgTracefile("-"); + + tdl = TODO_NewList(); + Time_Unix_Passive(); + + npl = NTP_PeerSet_New(NULL); + AN(npl); + + while ((ch = getopt(argc, argv, "d:m:t:")) != -1) { + switch(ch) { + case 'd': + duration = strtod(optarg, &p); + if (*p != '\0' || duration < 1.0) + Fail(NULL, 0, "Invalid -d argument"); + break; + case 'm': + mon = NTP_Peer_NewLookup(NULL, optarg); + if (mon == NULL) + Fail(NULL, 0, "Monitor (-m) didn't resolve."); + break; + case 't': + ArgTracefile(optarg); + break; + default: + Fail(NULL, 0, + "Usage %s [-d duration] [-m monitor] " + "[-t tracefile] server...", argv[0]); + break; + } + } + argc -= optind; + argv += optind; + + for (ch = 0; ch < argc; ch++) + npeer += NTP_PeerSet_Add(NULL, npl, argv[ch]); + Put(NULL, OCX_TRACE, "# NTIMED Format poll-server 1.0\n"); + Put(NULL, OCX_TRACE, "# Found %d peers\n", npeer); + if (npeer == 0) + Fail(NULL, 0, "No peers found"); + + NTP_PeerSet_Foreach(np, npl) { + Put(NULL, OCX_TRACE, "# Peer %s %s\n", np->hostname, np->ip); + np->filter_func = mps_filter; + } + + if (mon != NULL) + Put(NULL, OCX_TRACE, + "# Monitor %s %s\n", mon->hostname, mon->ip); + + fd = UdpTimedSocket(NULL, AF_INET); + assert(fd >= 0); + + TODO_ScheduleRel(tdl, mps_end, NULL, duration, 0, "End task"); + + if (mon != NULL) + TODO_ScheduleRel(tdl, mps_mon, mon, 0, 32, "Monitor"); + + NTP_PeerSet_Poll(NULL, npl, fd, tdl); + (void)TODO_Run(NULL, tdl); + return (0); +} diff --git a/main_sim_client.c b/main_sim_client.c new file mode 100644 index 0000000..c8a0ac4 --- /dev/null +++ b/main_sim_client.c @@ -0,0 +1,297 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * sim_client + * -s simfile Output file from poll-server + * server_numbers ... + */ + +#include +#include +#include + +#include "ntimed.h" +#include "ntp.h" + +#define PARAM_CLIENT PARAM_INSTANCE +#define PARAM_TABLE_NAME client_param_table +#include "param_instance.h" +#undef PARAM_TABLE_NAME +#undef PARAM_CLIENT + +/**********************************************************************/ + +struct sim_file { + unsigned magic; +#define SIM_FILE_MAGIC 0x7f847bd0 + char *filename; + FILE *input; + unsigned n_peer; + struct ntp_peerset *npl; + struct timestamp when; + unsigned t0; +}; + +static void +simfile_poll(struct ocx *ocx, const struct sim_file *sf, char *buf) +{ + char *hostname; + char *ip; + char *pkt; + struct ntp_peer *np; + struct ntp_packet *rxp; + struct ntp_packet *txp; + + CHECK_OBJ_NOTNULL(sf, SIM_FILE_MAGIC); + AN(buf); + + if (memcmp(buf, "Poll ", 5)) + Fail(ocx, 0, "Bad 'Poll' line (%s)\n", buf); + hostname = buf + 5; + ip = strchr(hostname, ' '); + if (ip == NULL) + Fail(ocx, 0, "Bad 'Poll' line (%s)\n", buf); + pkt = strchr(ip + 1, ' '); + if (pkt == NULL) + Fail(ocx, 0, "Bad 'Poll' line (%s)\n", buf); + + *ip++ = '\0'; + *pkt++ = '\0'; + + NTP_PeerSet_Foreach(np, sf->npl) + if (!strcmp(np->hostname, hostname) && !strcmp(np->ip, ip)) + break; + if (np == NULL) + Fail(ocx, 0, "Peer not found (%s, %s)\n", hostname, ip); + + CHECK_OBJ_NOTNULL(np, NTP_PEER_MAGIC); + + txp = np->tx_pkt; + INIT_OBJ(txp, NTP_PACKET_MAGIC); + + rxp = np->rx_pkt; + if (NTP_Tool_Scan(rxp, pkt)) + Fail(ocx, 0, "Cannot parse packet (%s, %s, %s)\n", + hostname, ip, pkt); + + TS_Add(&rxp->ntp_origin, Time_Sim_delta); + TS_Add(&rxp->ts_rx, Time_Sim_delta); + + txp->ntp_transmit = rxp->ntp_origin; + + if (np->filter_func != NULL) + np->filter_func(ocx, np); +} + +static enum todo_e +simfile_readline(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + struct sim_file *sf; + char buf[BUFSIZ], *p; + struct timestamp t0; + unsigned u1, u2; + double dt; + + AN(tdl); + CAST_OBJ_NOTNULL(sf, priv, SIM_FILE_MAGIC); + + TB_Now(&t0); + + while (1) { + if (fgets(buf, sizeof buf, sf->input) == NULL) { + Debug(ocx, "EOF on -s file (%s)", sf->filename); + exit(0); + } + p = strchr(buf, '\r'); + if (p != NULL) + *p = '\0'; + p = strchr(buf, '\n'); + if (p != NULL) + *p = '\0'; + + if (!strncmp(buf, "Now ", 4)) { + if (sscanf(buf, "Now %u.%u", &u1, &u2) != 2) + Fail(ocx, 0, "Bad 'Now' line (%s)", buf); + if (sf->t0 == 0) + sf->t0 = u1 - t0.sec; + u1 -= sf->t0; + TS_Nanosec(&sf->when, u1, u2); + dt = TS_Diff(&sf->when, &t0); + if (dt >= 1e-3) { + TODO_ScheduleAbs(tdl, simfile_readline, priv, + &sf->when, 0.0, "Readline"); + return (TODO_OK); + } + } else if (!strncmp(buf, "Poll ", 5)) { + simfile_poll(ocx, sf, buf); + } + /* We ignore things we don't understand */ + } +} + +static struct sim_file * +SimFile_Open(struct ocx *ocx, const char *fn, struct todolist *tdl, + struct ntp_peerset *npl) +{ + struct sim_file *sf; + struct ntp_peer *np; + char buf[BUFSIZ]; + char buf2[BUFSIZ]; + char buf3[BUFSIZ]; + char *e; + int s; + unsigned fpeer = 0; + + AN(fn); + AN(tdl); + AN(npl); + + ALLOC_OBJ(sf, SIM_FILE_MAGIC); + AN(sf); + + sf->input = fopen(fn, "r"); + if (sf->input == NULL) + Fail(ocx, 1, "Could not open -s file (%s)", fn); + sf->filename = strdup(fn); + AN(sf->filename); + sf->npl = npl; + + for (s = 0; s < 3; ) { + if (fgets(buf, sizeof buf, sf->input) == NULL) + Fail(ocx, 1, "Premature EOF on -s file (%s)", fn); + e = strchr(buf, '\0'); + AN(e); + if (e == buf) + continue; + if (e[-1] == '\n') + *--e = '\0'; + Debug(ocx, ">>> %s\n", buf); + switch(s) { + case 0: + if (strcmp(buf, "# NTIMED Format poll-server 1.0")) + Fail(ocx, 0, + "Wrong fileformat in -s file (%s)", fn); + s++; + break; + case 1: + if (sscanf(buf, "# Found %u peers", &sf->n_peer) != 1) + Fail(ocx, 0, + "Expected '# Found ... peers' line"); + s++; + break; + case 2: + if (sscanf(buf, "# Peer %s %s", buf2, buf3) != 2) + Fail(ocx, 0, "Expected '# Peer' line", fpeer); + + np = NTP_Peer_NewLookup(ocx, buf3); + AN(np); + np->hostname = strdup(buf2); + AN(np->hostname); + NTP_PeerSet_AddPeer(ocx, npl, np); + if (++fpeer == sf->n_peer) + s++; + break; + default: + Debug(ocx, "<%s>\n", buf); + Fail(ocx, 0, + "XXX: Wrong state (%d) in open_sim_file", s); + } + } + (void)simfile_readline(NULL, tdl, sf); + return (sf); +} + +int +main_sim_client(int argc, char *const *argv) +{ + int ch; + const char *s_filename = NULL; + struct sim_file *sf; + struct ntp_peerset *npl; + struct ntp_peer *np; + struct todolist *tdl; + struct combine_delta *cd; + double a, b, c; + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + tdl = TODO_NewList(); + Time_Sim(tdl); + + PLL_Init(); + + npl = NTP_PeerSet_New(NULL); + + Param_Register(client_param_table); + NF_Init(); + + while ((ch = getopt(argc, argv, "B:s:p:t:")) != -1) { + switch(ch) { + case 'B': + ch = sscanf(optarg, "%lg,%lg,%lg", &a, &b, &c); + if (ch != 3) + Fail(NULL, 0, + "bad -B argument \"when,freq,phase\""); + Time_Sim_Bump(tdl, a, b, c); + break; + case 's': + s_filename = optarg; + break; + case 'p': + Param_Tweak(NULL, optarg); + break; + case 't': + ArgTracefile(optarg); + break; + default: + Fail(NULL, 0, + "Usage %s [-s simfile] [-p params] [-t tracefile]" + " [-B when,freq,phase]", argv[0]); + break; + } + } + // argc -= optind; + // argv += optind; + + Param_Report(NULL, OCX_TRACE); + + if (s_filename == NULL) + Fail(NULL, 1, "You must specify -s file."); + + sf = SimFile_Open(NULL, s_filename, tdl, npl); + AN(sf); + + cd = CD_New(); + + NTP_PeerSet_Foreach(np, npl) { + NF_New(np); + np->combiner = CD_AddSource(cd, np->hostname, np->ip); + } + + (void)TODO_Run(NULL, tdl); + + return (0); +} diff --git a/ntimed.h b/ntimed.h new file mode 100644 index 0000000..f798207 --- /dev/null +++ b/ntimed.h @@ -0,0 +1,179 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Main include file + * ================= + */ + +#ifdef NTIMED_H_INCLUDED +#error "ntimed.h included multiple times" +#endif +#define NTIMED_H_INCLUDED + +#include +#include +#include "ntimed_queue.h" +#include "ntimed_tricks.h" + +struct todolist; + +/* ocx_*.c -- Operational Context *************************************/ + +struct ocx; // private + +enum ocx_chan { + OCX_DIAG, // think: stderr + OCX_TRACE, // think: /var/run/stats + OCX_DEBUG, // think: stdout +}; + +void Put(struct ocx *, enum ocx_chan, const char *, ...); +void PutHex(struct ocx *, enum ocx_chan, const void *, ssize_t len); + +/* + * Report "Failure: " + args + "\n" [+ errno-line] + "\n". exit(1); + */ +void Fail(struct ocx *, int err, const char *, ...) \ + __attribute__((__noreturn__)); + +#define Debug(ocx, ...) Put(ocx, OCX_DEBUG, __VA_ARGS__) +#define DebugHex(ocx, ptr, len) PutHex(ocx, OCX_DEBUG, ptr, len) + +void ArgTracefile(const char *fn); + +/* param.c -- Parameters **********************************************/ + +struct param_tbl { + const char *name; + double *val; + double min; + double max; + double def; + const char *doc; + TAILQ_ENTRY(param_tbl) list; +}; + +void Param_Register(struct param_tbl *pt); +void Param_Tweak(struct ocx *, const char *arg); +void Param_Report(struct ocx *ocx, enum ocx_chan); + +/* pll_std.c -- Standard PLL ******************************************/ + +typedef void pll_f(struct ocx *ocx, double offset, double weight); +extern pll_f *PLL; + +void PLL_Init(void); + +/* time_sim.c -- Simulated timebase ***********************************/ + +extern double Time_Sim_delta; +void Time_Sim(struct todolist *); +void Time_Sim_Bump(struct todolist *, double when, double freq, double phase); + +/* time_unix.c -- UNIX timebase ***************************************/ + +void Time_Unix(struct todolist *); +void Time_Unix_Passive(void); + +/* time_stuff.c -- Timebase infrastructure ****************************/ + +struct timestamp { + unsigned magic; +#define TIMESTAMP_MAGIC 0x344cd213 + uint64_t sec; // Really: time_t + uint64_t frac; +}; + +typedef void tb_sleep_f(double dur); +typedef struct timestamp *tb_now_f(struct timestamp *); +typedef void tb_step_f(struct ocx *, double offset); +typedef void tb_adjust_f(struct ocx *, double offset, double duration, + double frequency); + +extern int TB_generation; +extern tb_sleep_f *TB_Sleep; +extern tb_now_f *TB_Now; +extern tb_step_f *TB_Step; +extern tb_adjust_f *TB_Adjust; + +void TS_Add(struct timestamp *ts, double dt); +struct timestamp *TS_Nanosec(struct timestamp *storage, + int64_t sec, int64_t nsec); + +struct timestamp *TS_Double(struct timestamp *storage, double); +double TS_Diff(const struct timestamp *t1, const struct timestamp *t2); +void TS_SleepUntil(const struct timestamp *); +void TS_Format(char *buf, ssize_t len, const struct timestamp *ts); + +void TS_RunTest(struct ocx *ocx); + +/* todo.c -- todo-list scheduler **************************************/ + +struct todo; // private + +enum todo_e { + TODO_FAIL = -1, // Break out of TODO_Run() + TODO_OK = 0, + TODO_DONE = 1, // Stop repeating me +}; + +typedef enum todo_e todo_f(struct ocx *, struct todolist *, void *priv); + +struct todolist *TODO_NewList(void); + +struct todo *TODO_ScheduleRel(struct todolist *, todo_f *func, void *priv, + double when, double repeat, const char *fmt, ...); +struct todo *TODO_ScheduleAbs(struct todolist *, todo_f *func, void *priv, + const struct timestamp *when, double repeat, const char *fmt, ...); +enum todo_e TODO_Run(struct ocx *ocx, struct todolist *); + +/* combine_delta.c -- Source Combiner based on delta-pdfs *************/ + +struct combiner; + +typedef void combine_f(struct ocx *, const struct combiner *, + double trust, double low, double mid, double high); + +struct combiner { + unsigned magic; +#define COMBINER_MAGIC 0xab2b239c + + combine_f *func; + void *priv; + const char *name1; + const char *name2; +}; + +struct combine_delta *CD_New(void); +struct combiner *CD_AddSource(struct combine_delta *, + const char *name1, const char *name2); + +/********************************************************************** + * Main functions + */ + +int main_client(int argc, char *const *argv); +int main_poll_server(int argc, char *const *argv); +int main_sim_client(int argc, char *const *argv); diff --git a/ntimed_endian.h b/ntimed_endian.h new file mode 100644 index 0000000..c73dbd3 --- /dev/null +++ b/ntimed_endian.h @@ -0,0 +1,71 @@ +/*- + * Copyright (c) 2002 Thomas Moestl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Part of: + * $FreeBSD: head/sys/sys/endian.h 208331 2010-05-20 06:16:13Z phk $ + */ + +#ifdef NTIMED_ENDIAN +#error "ntimed_endian.h included multiple times" +#endif +#define NTIMED_ENDIAN + +/* Alignment-agnostic encode/decode bytestream to/from little/big endian. */ + +static __inline uint16_t +be16dec(const void *pp) +{ + uint8_t const *p = (uint8_t const *)pp; + + return ((p[0] << 8) | p[1]); +} + +static __inline uint32_t +be32dec(const void *pp) +{ + uint8_t const *p = (uint8_t const *)pp; + + return (((unsigned)p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]); +} + +static __inline void +be16enc(void *pp, uint16_t u) +{ + uint8_t *p = (uint8_t *)pp; + + p[0] = (u >> 8) & 0xff; + p[1] = u & 0xff; +} + +static __inline void +be32enc(void *pp, uint32_t u) +{ + uint8_t *p = (uint8_t *)pp; + + p[0] = (u >> 24) & 0xff; + p[1] = (u >> 16) & 0xff; + p[2] = (u >> 8) & 0xff; + p[3] = u & 0xff; +} diff --git a/ntimed_queue.h b/ntimed_queue.h new file mode 100644 index 0000000..0935909 --- /dev/null +++ b/ntimed_queue.h @@ -0,0 +1,186 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Part of: + * $FreeBSD: head/sys/sys/queue.h 251887 2013-06-18 02:57:56Z lstewart $ + */ + +#ifdef NTIMED_QUEUE_H +#error "ntimed_queue.h included multiple times" +#endif +#define NTIMED_QUEUE_H + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * Tail queue functions. + */ + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) + +#define TAILQ_FIRST(head) ((head)->tqh_first) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = TAILQ_FIRST((head)); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_FROM(var, head, field) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ + for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_INIT(head) do { \ + TAILQ_FIRST((head)) = NULL; \ + (head)->tqh_last = &TAILQ_FIRST((head)); \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else { \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + } \ + TAILQ_NEXT((listelm), field) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + TAILQ_NEXT((elm), field) = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ + TAILQ_FIRST((head))->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + TAILQ_FIRST((head)) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + TAILQ_NEXT((elm), field) = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ +} while (0) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) + +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if ((TAILQ_NEXT((elm), field)) != NULL) \ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else { \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + } \ + *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ +} while (0) + +#define TAILQ_SWAP(head1, head2, type, field) do { \ + struct type *swap_first = (head1)->tqh_first; \ + struct type **swap_last = (head1)->tqh_last; \ + (head1)->tqh_first = (head2)->tqh_first; \ + (head1)->tqh_last = (head2)->tqh_last; \ + (head2)->tqh_first = swap_first; \ + (head2)->tqh_last = swap_last; \ + if ((swap_first = (head1)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head1)->tqh_first; \ + else \ + (head1)->tqh_last = &(head1)->tqh_first; \ + if ((swap_first = (head2)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head2)->tqh_first; \ + else \ + (head2)->tqh_last = &(head2)->tqh_first; \ +} while (0) diff --git a/ntimed_tricks.h b/ntimed_tricks.h new file mode 100644 index 0000000..42d7038 --- /dev/null +++ b/ntimed_tricks.h @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef NTIMED_TRICKS_H +#error "ntimed_tricks.h included multiple times" +#endif +#define NTIMED_TRICKS_H + +#include + +/********************************************************************** + * Assert and friends + * + * We always runs with asserts enabled, and we assert liberally. + * + * Tests which are too expensive for production use should be wrapped + * in "#ifdef DIAGNOSTICS" + * + */ + +#undef NDEBUG // Asserts *always* enabled. + +#define AZ(foo) do { assert((foo) == 0); } while (0) +#define AN(foo) do { assert((foo) != 0); } while (0) + +#define WRONG(foo) \ + do { \ + /*lint -save -e506 */ \ + assert(0 == (uintptr_t)foo); \ + /*lint -restore */ \ + } while (0) + +/********************************************************************** + * Safe printfs into compile-time fixed-size buffers. + */ + +#define bprintf(buf, fmt, ...) \ + do { \ + assert(snprintf(buf, sizeof buf, fmt, __VA_ARGS__) \ + < (int)sizeof buf); \ + } while (0) + +#define vbprintf(buf, fmt, ap) \ + do { \ + assert(vsnprintf(buf, sizeof buf, fmt, ap) \ + < (int)sizeof buf); \ + } while (0) + +/********************************************************************** + * FlexeLint shutuppery + * + * Flexelint is a commercial LINT program from gimpel.com, and a very + * good one at that. We need a few special-case markups to tell it + * our intentions. + */ + +/* + * In OO-light situations, functions have to match their prototype + * even if that means not const'ing a const'able argument. + * The typedef should be specified as argument to the macro. + * + */ +#define __match_proto__(xxx) /*lint -e{818} */ + +/********************************************************************** + * Mini object type checking + * + * This is a trivial struct type checking framework I have used with + * a lot of success in a number of high-rel software projects over the + * years. It is particularly valuable when you pass things trough void + * pointers or opaque APIs. + * + * Define your struct like this: + * struct foobar { + * unsigned magic; + * #define FOOBAR_MAGIC 0x23923092 + * ... + * } + * + * The "magic" element SHALL be the first element of the struct. + * + * The MAGIC number you get from "od -x < /dev/random | head -1" or + * similar. It is important that each structure has its own distinct + * value. + * + */ + +#define INIT_OBJ(to, type_magic) \ + do { \ + (void)memset(to, 0, sizeof *to); \ + (to)->magic = (type_magic); \ + } while (0) + +#define ALLOC_OBJ(to, type_magic) \ + do { \ + (to) = calloc(1L, sizeof *(to)); \ + if ((to) != NULL) \ + (to)->magic = (type_magic); \ + } while (0) + +#define FREE_OBJ(to) \ + do { \ + (to)->magic = (0); \ + free(to); \ + } while (0) + +#define VALID_OBJ(ptr, type_magic) \ + ((ptr) != NULL && (ptr)->magic == (type_magic)) + +#define CHECK_OBJ(ptr, type_magic) \ + do { \ + assert((ptr)->magic == type_magic); \ + } while (0) + +#define CHECK_OBJ_NOTNULL(ptr, type_magic) \ + do { \ + assert((ptr) != NULL); \ + assert((ptr)->magic == type_magic); \ + } while (0) + +#define CHECK_OBJ_ORNULL(ptr, type_magic) \ + do { \ + if ((ptr) != NULL) \ + assert((ptr)->magic == type_magic); \ + } while (0) + +#define CAST_OBJ(to, from, type_magic) \ + do { \ + (to) = (from); \ + if ((to) != NULL) \ + CHECK_OBJ((to), (type_magic)); \ + } while (0) + +#define CAST_OBJ_NOTNULL(to, from, type_magic) \ + do { \ + (to) = (from); \ + assert((to) != NULL); \ + CHECK_OBJ((to), (type_magic)); \ + } while (0) diff --git a/ntp.h b/ntp.h new file mode 100644 index 0000000..11fc752 --- /dev/null +++ b/ntp.h @@ -0,0 +1,137 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * NTP protocol stuff + * ================== + * + */ + +struct sockaddr; +struct ntp_peer; + +#ifdef NTP_H_INCLUDED +#error "ntp.h included multiple times" +#endif +#define NTP_H_INCLUDED + +enum ntp_mode { +#define NTP_MODE(n, l, u) NTP_MODE_##u = n, +#include "ntp_tbl.h" +#undef NTP_MODE +}; + +enum ntp_leap { +#define NTP_LEAP(n, l, u) NTP_LEAP_##u = n, +#include "ntp_tbl.h" +#undef NTP_LEAP +}; + +/* ntp_packet.c -- [De]Serialisation **********************************/ + +struct ntp_packet { + unsigned magic; +#define NTP_PACKET_MAGIC 0x78b7f0be + + enum ntp_leap ntp_leap; + uint8_t ntp_version; + enum ntp_mode ntp_mode; + uint8_t ntp_stratum; + uint8_t ntp_poll; + int8_t ntp_precision; + struct timestamp ntp_delay; + struct timestamp ntp_dispersion; + uint8_t ntp_refid[4]; + struct timestamp ntp_reference; + struct timestamp ntp_origin; + struct timestamp ntp_receive; + struct timestamp ntp_transmit; + + struct timestamp ts_rx; +}; + +struct ntp_packet *NTP_Packet_Unpack(struct ntp_packet *dst, void *ptr, + ssize_t len); +ssize_t NTP_Packet_Pack(void *ptr, ssize_t len, struct ntp_packet *); + +/* ntp_tools.c -- Handy tools *****************************************/ + +void NTP_Tool_Client_Req(struct ntp_packet *); +void NTP_Tool_Format(char *p, ssize_t len, const struct ntp_packet *pkt); +int NTP_Tool_Scan(struct ntp_packet *pkt, const char *buf); + +/* ntp_filter.c -- NTP sanity checking ********************************/ + +typedef void ntp_filter_f(struct ocx *, const struct ntp_peer *); + +void NF_New(struct ntp_peer *); +void NF_Init(void); + +/* ntp_peer.c -- State management *************************************/ + +struct ntp_group; + +struct ntp_peer { + unsigned magic; +#define NTP_PEER_MAGIC 0xbf0740a0 + char *hostname; + char *ip; + struct sockaddr *sa; + unsigned sa_len; + struct ntp_packet *tx_pkt; + struct ntp_packet *rx_pkt; + + ntp_filter_f *filter_func; + void *filter_priv; + + struct combiner *combiner; + + // For ntp_peerset.c + TAILQ_ENTRY(ntp_peer) list; + struct ntp_group *group; +}; + +struct ntp_peer *NTP_Peer_New(const char *name, const void *, unsigned); +struct ntp_peer *NTP_Peer_NewLookup(struct ocx *ocx, const char *name); +void NTP_Peer_Destroy(struct ntp_peer *np); +int NTP_Peer_Poll(struct ocx *, int fd, const struct ntp_peer *, double tmo); + +/* ntp_peerset.c -- Peer set management ****************************/ + +struct ntp_peerset *NTP_PeerSet_New(struct ocx *); +void NTP_PeerSet_AddPeer(struct ocx *ocx, struct ntp_peerset *npl, + struct ntp_peer *np); +int NTP_PeerSet_Add(struct ocx *, struct ntp_peerset *, + const char *hostname); +void NTP_PeerSet_Poll(struct ocx *, struct ntp_peerset *, int, + struct todolist *); + +struct ntp_peer *NTP_PeerSet_Iter0(const struct ntp_peerset *); +struct ntp_peer *NTP_PeerSet_IterN(const struct ntp_peerset *, + const struct ntp_peer *); + +#define NTP_PeerSet_Foreach(var, npl) \ + for(var = NTP_PeerSet_Iter0(npl); \ + var != NULL; \ + var = NTP_PeerSet_IterN(npl, var)) diff --git a/ntp_filter.c b/ntp_filter.c new file mode 100644 index 0000000..d64f60a --- /dev/null +++ b/ntp_filter.c @@ -0,0 +1,192 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Filter incoming NTP packets + * =========================== + */ + +#include +#include + +#include "ntimed.h" + +#include "ntp.h" + +#define PARAM_NTP_FILTER PARAM_INSTANCE +#define PARAM_TABLE_NAME ntp_filter_param_table +#include "param_instance.h" +#undef PARAM_TABLE_NAME +#undef PARAM_NTP_FILTER + +struct ntp_filter { + unsigned magic; +#define NTP_FILTER_MAGIC 0xf7b7032d + + double lo, mid, hi; + double alo, amid, ahi; + double alolo, ahihi; + double navg; + double trust; +}; + +static void __match_proto__(ntp_filter_f) +nf_filter(struct ocx *ocx, const struct ntp_peer *np) +{ + struct ntp_filter *nf; + struct ntp_packet *rxp; + int branch, fail_hi, fail_lo; + double lo_noise, hi_noise; + double lo_lim, hi_lim; + double r; + char buf[256]; + + CAST_OBJ_NOTNULL(nf, np->filter_priv, NTP_FILTER_MAGIC); + + rxp = np->rx_pkt; + CHECK_OBJ_NOTNULL(rxp, NTP_PACKET_MAGIC); + + NTP_Tool_Format(buf, sizeof buf, rxp); + + Put(NULL, OCX_TRACE, "NTP_Packet %+f %s %s %s\n", + np->hostname, np->ip, buf); + + if (rxp->ntp_leap == NTP_LEAP_UNKNOWN) + return; // XXX diags + + // XXX: Check leap warnings in wrong months + // XXX: Check leap warnings against other sources + + if (rxp->ntp_version < 3 || rxp->ntp_version > 4) { + Put(ocx, OCX_TRACE, "NF Bad version %d\n", rxp->ntp_version); + return; + } + + if (rxp->ntp_mode != NTP_MODE_SERVER) { + Put(ocx, OCX_TRACE, "NF Bad mode %d\n", rxp->ntp_mode); + return; + } + + if (rxp->ntp_stratum == 0 || rxp->ntp_stratum > 15) { + Put(ocx, OCX_TRACE, "NF Bad stratum %d\n", rxp->ntp_stratum); + return; + } + + r = TS_Diff(&rxp->ntp_transmit, &rxp->ntp_receive); + if (r <= 0.0) { + Put(ocx, OCX_TRACE, "NF rx after tx %.3e\n", r); + return; + } + + r = TS_Diff(&rxp->ntp_transmit, &rxp->ntp_reference); + if (r < -2e-9) { + /* two nanoseconds to Finagle rounding errors */ + Put(ocx, OCX_TRACE, "NF ref after tx %.3e\n", r); + return; // XXX diags + } + + // This is almost never a good sign. + if (r > 2048) { + /* XXX: 2048 -> param */ + Put(ocx, OCX_TRACE, "NF ancient ref %.3e\n", r); + return; + } + + if (nf->navg < param_ntp_filter_average) + nf->navg += 1; + + nf->lo = TS_Diff(&rxp->ntp_origin, &rxp->ntp_receive); + nf->hi = TS_Diff(&rxp->ts_rx, &rxp->ntp_transmit); + nf->mid = .5 * (nf->lo + nf->hi); + + if (nf->navg > 2) { + lo_noise = sqrt(nf->alolo - nf->alo * nf->alo); + hi_noise = sqrt(nf->ahihi - nf->ahi * nf->ahi); + } else { + lo_noise = 0.0; + hi_noise = 0.0; + } + + lo_lim = nf->alo - lo_noise * param_ntp_filter_threshold; + hi_lim = nf->ahi + hi_noise * param_ntp_filter_threshold; + + fail_lo = nf->lo < lo_lim; + fail_hi = nf->hi > hi_lim; + + if (fail_lo && fail_hi) { + branch = 1; + } else if (nf->navg > 3 && fail_lo) { + nf->mid = nf->amid + (nf->hi - nf->ahi); + branch = 2; + } else if (nf->navg > 3 && fail_hi) { + nf->mid = nf->amid + nf->lo - nf->alo; + branch = 3; + } else { + branch = 4; + } + + r = nf->navg; + if (nf->navg > 2 && branch != 4) + r *= r; + + nf->alo += (nf->lo - nf->alo) / r; + nf->amid += (nf->mid - nf->amid) / r; + nf->ahi += (nf->hi - nf->ahi) / r; + nf->alolo += (nf->lo*nf->lo - nf->alolo) / r; + nf->ahihi += (nf->hi*nf->hi - nf->ahihi) / r; + + if (rxp->ntp_stratum == 0) + nf->trust = 0.0; + else if (rxp->ntp_stratum == 15) + nf->trust = 0.0; + else + nf->trust = 1.0 / rxp->ntp_stratum; + + Put(ocx, OCX_TRACE, + "NTP_Filter %s %s %d %.3e %.3e %.3e %.3e %.3e %.3e\n", + np->hostname, np->ip, branch, + nf->lo, nf->mid, nf->hi, + lo_lim, nf->amid, hi_lim); + + if (np->combiner->func != NULL) + np->combiner->func(ocx, np->combiner, + nf->trust, nf->lo, nf->mid, nf->hi); +} + +void +NF_New(struct ntp_peer *np) +{ + struct ntp_filter *nf; + + ALLOC_OBJ(nf, NTP_FILTER_MAGIC); + AN(nf); + np->filter_func = nf_filter; + np->filter_priv = nf; +} + +void +NF_Init(void) +{ + Param_Register(ntp_filter_param_table); +} diff --git a/ntp_packet.c b/ntp_packet.c new file mode 100644 index 0000000..6f12e07 --- /dev/null +++ b/ntp_packet.c @@ -0,0 +1,183 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * NTP packet (de)serialization + * ============================ + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 0 |LI | VN |Mode | Stratum | Poll | Precision | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 4 | Root Delay | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 8 | Root Dispersion | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 12 | Reference ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 16 | | + * + Reference Timestamp (64) + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 24 | | + * + Origin Timestamp (64) + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 32 | | + * + Receive Timestamp (64) + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 40 | | + * + Transmit Timestamp (64) + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +#include +#include + +#include "ntimed.h" +#include "ntp.h" +#include "ntimed_endian.h" + +/* + * Seconds between 1900 (NTP epoch) and 1970 (UNIX epoch). + * 17 is the number of leapdays. + */ +#define NTP_UNIX (((1970U - 1900U) * 365U + 17U) * 24U * 60U * 60U) + +/********************************************************************** + * Picking a NTP packet apart in a safe, byte-order agnostic manner + */ + +static void +ntp64_2ts(struct timestamp *ts, const uint8_t *ptr) +{ + + INIT_OBJ(ts, TIMESTAMP_MAGIC); + ts->sec = be32dec(ptr) - NTP_UNIX; + ts->frac = (uint64_t)be32dec(ptr + 4) << 32ULL; +} + +static void +ntp32_2ts(struct timestamp *ts, const uint8_t *ptr) +{ + + INIT_OBJ(ts, TIMESTAMP_MAGIC); + ts->sec = be16dec(ptr); + ts->frac = (uint64_t)be16dec(ptr + 2) << 48ULL; +} + + +struct ntp_packet * +NTP_Packet_Unpack(struct ntp_packet *np, void *ptr, ssize_t len) +{ + uint8_t *p = ptr; + + AN(ptr); + if (len != 48) { + /* XXX: Diagnostic */ + return (NULL); + } + + if (np == NULL) { + ALLOC_OBJ(np, NTP_PACKET_MAGIC); + AN(np); + } else { + INIT_OBJ(np, NTP_PACKET_MAGIC); + } + + np->ntp_leap = (enum ntp_leap)(p[0] >> 6); + np->ntp_version = (p[0] >> 3) & 0x7; + np->ntp_mode = (enum ntp_mode)(p[0] & 0x07); + np->ntp_stratum = p[1]; + np->ntp_poll = p[2]; + np->ntp_precision = (int8_t)p[3]; + ntp32_2ts(&np->ntp_delay, p + 4); + ntp32_2ts(&np->ntp_dispersion, p + 8); + memcpy(np->ntp_refid, p + 12, 4L); + ntp64_2ts(&np->ntp_reference, p + 16); + ntp64_2ts(&np->ntp_origin, p + 24); + ntp64_2ts(&np->ntp_receive, p + 32); + ntp64_2ts(&np->ntp_transmit, p + 40); + return (np); +} + +/********************************************************************** + * Putting a NTP packet apart in a safe, byte-order agnostic manner + */ + +static void +ts_2ntp32(uint8_t *dst, const struct timestamp *ts) +{ + + CHECK_OBJ_NOTNULL(ts, TIMESTAMP_MAGIC); + assert(ts->sec < 65536); + be16enc(dst, ts->sec); + be16enc(dst + 2, ts->frac >> 48ULL); +} + +static void +ts_2ntp64(uint8_t *dst, const struct timestamp *ts) +{ + + CHECK_OBJ_NOTNULL(ts, TIMESTAMP_MAGIC); + be32enc(dst, ts->sec + NTP_UNIX); + be32enc(dst + 4, ts->frac >> 32ULL); +} + +ssize_t +NTP_Packet_Pack(void *ptr, ssize_t len, struct ntp_packet *np) +{ + uint8_t *pbuf = ptr; + + AN(ptr); + assert(len >= 48); + CHECK_OBJ_NOTNULL(np, NTP_PACKET_MAGIC); + assert(np->ntp_version < 8); + assert(np->ntp_stratum < 15); + + pbuf[0] = (uint8_t)np->ntp_leap << 6; + pbuf[0] |= np->ntp_version << 3; + pbuf[0] |= (uint8_t)np->ntp_mode; + pbuf[1] = np->ntp_stratum; + pbuf[2] = np->ntp_poll; + pbuf[3] = (uint8_t)np->ntp_precision; + ts_2ntp32(pbuf + 4, &np->ntp_delay); + ts_2ntp32(pbuf + 8, &np->ntp_dispersion); + memcpy(pbuf + 12, np->ntp_refid, 4L); + ts_2ntp64(pbuf + 16, &np->ntp_reference); + ts_2ntp64(pbuf + 24, &np->ntp_origin); + ts_2ntp64(pbuf + 32, &np->ntp_receive); + + TB_Now(&np->ntp_transmit); + ts_2ntp64(pbuf + 40, &np->ntp_transmit); + + /* Reverse again, to avoid subsequent trouble from rounding. */ + ntp64_2ts(&np->ntp_transmit, pbuf + 40); + + return (48); +} diff --git a/ntp_peer.c b/ntp_peer.c new file mode 100644 index 0000000..af35162 --- /dev/null +++ b/ntp_peer.c @@ -0,0 +1,178 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "ntimed.h" +#include "udp.h" +#include "ntp.h" + +struct ntp_peer * +NTP_Peer_New(const char *hostname, const void *sa, unsigned salen) +{ + struct ntp_peer *np; + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + + AZ(getnameinfo(sa, salen, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)); + + ALLOC_OBJ(np, NTP_PEER_MAGIC); + AN(np); + + np->sa_len = salen; + np->sa = calloc(1, np->sa_len); + AN(np->sa); + memcpy(np->sa, sa, np->sa_len); + + np->hostname = strdup(hostname); + AN(np->hostname); + np->ip = strdup(hbuf); + AN(np->ip); + + ALLOC_OBJ(np->tx_pkt, NTP_PACKET_MAGIC); + AN(np->tx_pkt); + + ALLOC_OBJ(np->rx_pkt, NTP_PACKET_MAGIC); + AN(np->rx_pkt); + + NTP_Tool_Client_Req(np->tx_pkt); + + return (np); +} + +struct ntp_peer * +NTP_Peer_NewLookup(struct ocx *ocx, const char *hostname) +{ + struct addrinfo hints, *res0; + int error; + struct ntp_peer *np; + + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo(hostname, "ntp", &hints, &res0); + if (error) + Fail(ocx, 0, "hostname '%s', port 'ntp': %s\n", + hostname, gai_strerror(error)); + + np = NTP_Peer_New(hostname, res0->ai_addr, res0->ai_addrlen); + freeaddrinfo(res0); + return (np); +} + +void +NTP_Peer_Destroy(struct ntp_peer *np) +{ + + CHECK_OBJ_NOTNULL(np, NTP_PEER_MAGIC); + free(np->sa); + free(np->hostname); + free(np->ip); + free(np->tx_pkt); + free(np->rx_pkt); + FREE_OBJ(np); +} + +int +NTP_Peer_Poll(struct ocx *ocx, int fd, const struct ntp_peer *np, double tmo) +{ + char buf[100]; + ssize_t len; + struct sockaddr_storage rss; + socklen_t rssl; + ssize_t l; + int i, timeout_msec; + struct pollfd pfd[1]; + struct timestamp t0, t1, t2; + double d; + + assert(fd >= 0); + CHECK_OBJ_NOTNULL(np, NTP_PEER_MAGIC); + assert(tmo > 0.0 && tmo <= 1.0); + + len = NTP_Packet_Pack(buf, sizeof buf, np->tx_pkt); + + l = sendto(fd, buf, len, 0, np->sa, np->sa_len); + if (l != len) + Fail(ocx, l < 0 ? 1 : 0, + "Tx peer '%s' @%s got %d", np->hostname, np->ip, l); + + (void)TB_Now(&t0); + + while (1) { + (void)TB_Now(&t1); + d = TS_Diff(&t1, &t0); + + timeout_msec = lround(1e3 * (tmo - d)); + + pfd[0].fd = fd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + + if (timeout_msec <= 0) + i = 0; + else + i = poll(pfd, 1, timeout_msec); + + if (i < 0) + Fail(ocx, 1, "poll(2) failed\n"); + + if (i == 0) + return (0); + + i = UdpTimedRx(ocx, fd, &rss, &rssl, &t2, buf, sizeof buf); + + if (i < 0) + Fail(ocx, 1, "Rx failed\n"); + + if (i != 48) { + Debug(ocx, "Rx peer %s @%s got len=%d\n", + np->hostname, i); + continue; + } + + /* Ignore packets from other hosts */ + if (np->sa_len != rssl || memcmp(np->sa, &rss, rssl)) + continue; + + AN(NTP_Packet_Unpack(np->rx_pkt, buf, i)); + np->rx_pkt->ts_rx = t2; + + /* Ignore packets which are not replies to our packet */ + if (TS_Diff(&np->tx_pkt->ntp_transmit, + &np->rx_pkt->ntp_origin) != 0.0) { + continue; + } + + return (1); + } +} diff --git a/ntp_peerset.c b/ntp_peerset.c new file mode 100644 index 0000000..1bf6910 --- /dev/null +++ b/ntp_peerset.c @@ -0,0 +1,237 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include + +#include "ntimed.h" +#include "ntp.h" + +struct ntp_group { + unsigned magic; +#define NTP_GROUP_MAGIC 0xdd5f58de + TAILQ_ENTRY(ntp_group) list; + + char *hostname; + int npeer; +}; + +struct ntp_peerset { + unsigned magic; +#define NTP_PEERSET_MAGIC 0x0bf873d0 + + TAILQ_HEAD(,ntp_peer) head; + int npeer; + + TAILQ_HEAD(,ntp_group) group; + int ngroup; + + int fd; + double t0; + double init_duration; + double poll_period; + double init_packets; +}; + +/**********************************************************************/ + +struct ntp_peerset * +NTP_PeerSet_New(struct ocx *ocx) +{ + struct ntp_peerset *npl; + + (void)ocx; + ALLOC_OBJ(npl, NTP_PEERSET_MAGIC); + AN(npl); + + TAILQ_INIT(&npl->head); + TAILQ_INIT(&npl->group); + return (npl); +} + +/********************************************************************** + * Iterator helpers + */ + +struct ntp_peer * +NTP_PeerSet_Iter0(const struct ntp_peerset *npl) +{ + + CHECK_OBJ_NOTNULL(npl, NTP_PEERSET_MAGIC); + return (TAILQ_FIRST(&npl->head)); +} + +struct ntp_peer * +NTP_PeerSet_IterN(const struct ntp_peerset *npl, const struct ntp_peer *np) +{ + + CHECK_OBJ_NOTNULL(npl, NTP_PEERSET_MAGIC); + CHECK_OBJ_NOTNULL(np, NTP_PEER_MAGIC); + return (TAILQ_NEXT(np, list)); +} + +/**********************************************************************/ + +void +NTP_PeerSet_AddPeer(struct ocx *ocx, struct ntp_peerset *npl, + struct ntp_peer *np) +{ + + (void)ocx; + CHECK_OBJ_NOTNULL(npl, NTP_PEERSET_MAGIC); + CHECK_OBJ_NOTNULL(np, NTP_PEER_MAGIC); + TAILQ_INSERT_TAIL(&npl->head, np, list); + npl->npeer++; +} + +/**********************************************************************/ + +int +NTP_PeerSet_Add(struct ocx *ocx, struct ntp_peerset *npl, + const char *hostname) +{ + struct addrinfo hints, *res, *res0; + int error, n; + struct ntp_peer *np; + struct ntp_group *ng; + + CHECK_OBJ_NOTNULL(npl, NTP_PEERSET_MAGIC); + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo(hostname, "ntp", &hints, &res0); + if (error) + Fail(ocx, 1, "hostname '%s', port 'ntp': %s\n", + hostname, gai_strerror(error)); + ALLOC_OBJ(ng, NTP_GROUP_MAGIC); + AN(ng); + n = 0; + for (res = res0; res; res = res->ai_next) { + np = NTP_Peer_New(hostname, res->ai_addr, res->ai_addrlen); + // XXX: duplicate check + TAILQ_INSERT_TAIL(&npl->head, np, list); + npl->npeer++; + np->group = ng; + ng->npeer++; + n++; + } + freeaddrinfo(res0); + if (ng->npeer == 0) { + FREE_OBJ(ng); + } else { + ng->hostname = strdup(hostname); + AN(ng->hostname); + TAILQ_INSERT_TAIL(&npl->group, ng, list); + npl->ngroup++; + } + return (n); +} + +/********************************************************************** + * This function is responsible for polling the peers in the set. + */ + +static enum todo_e +ntp_peerset_poll(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + struct ntp_peerset *npl; + struct ntp_peer *np; + double d, dt; + + (void)ocx; + CAST_OBJ_NOTNULL(npl, priv, NTP_PEERSET_MAGIC); + AN(tdl); + + np = TAILQ_FIRST(&npl->head); + if (np == NULL) + return(TODO_DONE); + + CHECK_OBJ_NOTNULL(np, NTP_PEER_MAGIC); + TAILQ_REMOVE(&npl->head, np, list); + TAILQ_INSERT_TAIL(&npl->head, np, list); + + d = npl->poll_period / npl->npeer; + if (npl->t0 < npl->init_duration) { + dt = exp( + log(npl->init_duration) / (npl->init_packets * npl->npeer)); + if (npl->t0 * dt < npl->init_duration) + d = npl->t0 * dt - npl->t0; + } + npl->t0 += d; + TODO_ScheduleRel(tdl, ntp_peerset_poll, npl, d, 0.0, "NTP_PeerSet"); + if (NTP_Peer_Poll(ocx, npl->fd, np, 0.8)) { + if (np->filter_func != NULL) + np->filter_func(ocx, np); + } + + return (TODO_OK); +} + +/********************************************************************** + * This function will be responsible for maintaining the peers in the set + * + * XXX: Pick only the best N (3?) servers from any hostname as active. + * XXX: Re-lookup hostnames on periodic basis to keep the set current + * XXX: Implement (a future) pool.ntp.org load-balancing protocol + */ + +static enum todo_e +ntp_peerset_herd(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + (void)ocx; + (void)tdl; + (void)priv; + return (TODO_DONE); +} + +/**********************************************************************/ + +void +NTP_PeerSet_Poll(struct ocx *ocx, struct ntp_peerset *npl, int fd, + struct todolist *tdl) +{ + + (void)ocx; + CHECK_OBJ_NOTNULL(npl, NTP_PEERSET_MAGIC); + assert(fd >= 0); + AN(tdl); + + npl->fd = fd; + npl->t0 = 1.0; + npl->init_duration = 64.; + npl->init_packets = 6.; + npl->poll_period = 64.; + TODO_ScheduleRel(tdl, ntp_peerset_poll, npl, 0.0, 0.0, + "NTP_PeerSet Poll"); + if (npl->ngroup > 0) + TODO_ScheduleRel(tdl, ntp_peerset_herd, npl, + 15. * 60. / npl->ngroup, 0.0, "NTP_PeerSet Herd"); + +} diff --git a/ntp_tbl.h b/ntp_tbl.h new file mode 100644 index 0000000..afd4633 --- /dev/null +++ b/ntp_tbl.h @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Table generator file for tables related to the NTP protocol. + */ + +/*lint -save -e525 -e539 */ +#ifdef NTP_MODE +NTP_MODE(0, mode0, MODE0) +NTP_MODE(1, symact, SYMACT) +NTP_MODE(2, sympas, SYMPAS) +NTP_MODE(3, client, CLIENT) +NTP_MODE(4, server, SERVER) +NTP_MODE(5, bcast, BCAST) +NTP_MODE(6, ctrl, CTRL) +NTP_MODE(7, mode7, MODE7) +#endif +/*lint -restore */ + +/*lint -save -e525 -e539 */ +#ifdef NTP_LEAP +NTP_LEAP(0, none, NONE) +NTP_LEAP(1, ins, INS) +NTP_LEAP(2, del, DEL) +NTP_LEAP(3, unknown, UNKNOWN) +#endif +/*lint -restore */ diff --git a/ntp_tools.c b/ntp_tools.c new file mode 100644 index 0000000..74cb6de --- /dev/null +++ b/ntp_tools.c @@ -0,0 +1,201 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * NTP tools + * ========= + * + * + */ + +#include +#include +#include + +#include "ntimed.h" +#include "ntp.h" +#include "ntimed_endian.h" + +/********************************************************************** + * Build a standard client query packet + */ + +void +NTP_Tool_Client_Req(struct ntp_packet *np) +{ + AN(np); + INIT_OBJ(np, NTP_PACKET_MAGIC); + + np->ntp_leap = NTP_LEAP_UNKNOWN; + np->ntp_version = 4; + np->ntp_mode = NTP_MODE_CLIENT; + np->ntp_stratum = 0; + np->ntp_poll = 4; + np->ntp_precision = -6; + INIT_OBJ(&np->ntp_delay, TIMESTAMP_MAGIC); + np->ntp_delay.sec = 1; + INIT_OBJ(&np->ntp_dispersion, TIMESTAMP_MAGIC); + np->ntp_dispersion.sec = 1; + INIT_OBJ(&np->ntp_reference, TIMESTAMP_MAGIC); + INIT_OBJ(&np->ntp_origin, TIMESTAMP_MAGIC); + INIT_OBJ(&np->ntp_receive, TIMESTAMP_MAGIC); +} + +/********************************************************************** + * Format a NTP packet in a standardized layout for subsequent parsing. + * + * We dump absolute timestamps relative to the origin timestamp. + * + * XXX: Nanosecond precision is enough for everybody. + */ + +void +NTP_Tool_Format(char *p, ssize_t len, const struct ntp_packet *pkt) +{ + char *e; + char buf[40]; + + AN(p); + assert(len > 0); + CHECK_OBJ_NOTNULL(pkt, NTP_PACKET_MAGIC); + + e = p + len; + + p += snprintf(p, e - p, "[%u", pkt->ntp_leap); + assert(p < e); + + p += snprintf(p, e - p, " %u", pkt->ntp_version); + assert(p < e); + + p += snprintf(p, e - p, " %u", pkt->ntp_mode); + assert(p < e); + + p += snprintf(p, e - p, " %3u", pkt->ntp_stratum); + assert(p < e); + + p += snprintf(p, e - p, " %3u", pkt->ntp_poll); + assert(p < e); + + p += snprintf(p, e - p, " %4d", pkt->ntp_precision); + assert(p < e); + + TS_Format(buf, sizeof buf, &pkt->ntp_delay); + p += snprintf(p, e - p, " %s", buf); assert(p < e); + assert(p < e); + + TS_Format(buf, sizeof buf, &pkt->ntp_dispersion); + p += snprintf(p, e - p, " %s", buf); assert(p < e); + assert(p < e); + + p += snprintf(p, e - p, " 0x%02x%02x%02x%02x", + pkt->ntp_refid[0], pkt->ntp_refid[1], + pkt->ntp_refid[2], pkt->ntp_refid[3]); + assert(p < e); + + p += snprintf(p, e - p, " %.9f", + TS_Diff(&pkt->ntp_reference, &pkt->ntp_origin)); + assert(p < e); + + TS_Format(buf, sizeof buf, &pkt->ntp_origin); + p += snprintf(p, e - p, " %s", buf); assert(p < e); + assert(p < e); + + p += snprintf(p, e - p, " %.9f", + TS_Diff(&pkt->ntp_receive, &pkt->ntp_origin)); + assert(p < e); + + p += snprintf(p, e - p, " %.9f", + TS_Diff(&pkt->ntp_transmit, &pkt->ntp_receive)); + assert(p < e); + + if (pkt->ts_rx.sec && pkt->ts_rx.frac) { + p += snprintf(p, e - p, " %.9f]", + TS_Diff(&pkt->ts_rx, &pkt->ntp_transmit)); + } else { + p += snprintf(p, e - p, " %.9f]", 0.0); + } + assert(p < e); +} + +/********************************************************************** + * Scan a packet in NTP_Tool_Format layout. + */ + +int +NTP_Tool_Scan(struct ntp_packet *pkt, const char *buf) +{ + unsigned u_fields[8]; + double d_fields[7]; + char cc; + int i; + + i = sscanf(buf, + "[%u %u %u %u %u %lf %lf %lf %x %lf %u.%u %lf %lf %lf%c", + u_fields + 0, /* NTP_leap */ + u_fields + 1, /* NTP_version */ + u_fields + 2, /* NTP_mode */ + u_fields + 3, /* NTP_stratum */ + u_fields + 4, /* NTP_poll */ + d_fields + 0, /* NTP_precision */ + d_fields + 1, /* NTP_delay */ + d_fields + 2, /* NTP_dispersion */ + u_fields + 5, /* NTP_refid */ + d_fields + 3, /* NTP_reference - NTP_origin */ + u_fields + 6, /* NTP_origin:sec */ + u_fields + 7, /* NTP_origin:nsec */ + d_fields + 4, /* NTP_receive - NTP_origin */ + d_fields + 5, /* NTP_transmit - NTP_receive */ + d_fields + 6, /* ts_rx - NTP_transmit*/ + &cc); + if (i != 16 || cc != ']') + return (-1); + + INIT_OBJ(pkt, NTP_PACKET_MAGIC); + pkt->ntp_leap = (enum ntp_leap)u_fields[0]; + pkt->ntp_version = u_fields[1]; + pkt->ntp_mode = (enum ntp_mode)u_fields[2]; + pkt->ntp_stratum = u_fields[3]; + pkt->ntp_poll = u_fields[4]; + pkt->ntp_precision = (int8_t)floor(d_fields[0]); + TS_Double(&pkt->ntp_delay, d_fields[1]); + TS_Double(&pkt->ntp_dispersion, d_fields[2]); + be32enc(pkt->ntp_refid, u_fields[5]); + + TS_Nanosec(&pkt->ntp_origin, u_fields[6], u_fields[7]); + + pkt->ntp_reference = pkt->ntp_origin; + TS_Add(&pkt->ntp_reference, d_fields[3]); + + pkt->ntp_receive = pkt->ntp_origin; + TS_Add(&pkt->ntp_receive, d_fields[4]); + + pkt->ntp_transmit = pkt->ntp_receive; + TS_Add(&pkt->ntp_transmit, d_fields[5]); + + if (d_fields[6] != 0.0) { + pkt->ts_rx = pkt->ntp_transmit; + TS_Add(&pkt->ts_rx, d_fields[6]); + } + return (0); +} diff --git a/ocx_stdio.c b/ocx_stdio.c new file mode 100644 index 0000000..e95458a --- /dev/null +++ b/ocx_stdio.c @@ -0,0 +1,172 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Operational Context STDIO + * ========================= + * + * "The most effective debugging tool is still careful thought, + * coupled with judiciously placed print statements." + * -- Brian Kernighan, "Unix for Beginners" (1979) + * + * The problem with print statements is where they end up. For instance + * in a server with a CLI interface, you want the print statements to go + * to the CLI session which called the code. + * + * An "Operational Context" is a back-pointer to where the print statement + * should end up. + * + * We operate with three "channels", DIAG, TRACE and DEBUG. + * + * DIAG is mandatory output, error messages, diagnostics etc. + * This should always end up where the action was initiated. + * + * DEBUG is optional output which may be supressed. + * This should go where DIAG goes, unless specifically redirected + * by the operator. + * + * TRACE is data collection, statistics etc. + * In general this goes nowhere unless configured to end up + * somewhere. + * + * About this implementation: + * + * This is a very naiive implementation spitting things out to stdout/stderr, + * knowing that we are a single threaded program. + * + * XXX: Pull in sbufs to do it right. + */ + +#include +#include +#include +#include +#include +#include + +#include "ntimed.h" + +static FILE *tracefile; + +static FILE * +getdst(enum ocx_chan chan) +{ + if (chan == OCX_DIAG) + return (stderr); + if (chan == OCX_TRACE) + return (tracefile); + if (chan == OCX_DEBUG) + return (stdout); + WRONG("Wrong ocx_chan"); + return (NULL); +} + +static void __match_proto__() +putv(struct ocx *ocx, enum ocx_chan chan, const char *fmt, va_list ap) +{ + FILE *dst = getdst(chan); + va_list ap2; + + va_copy(ap2, ap); + AZ(ocx); + if (dst != NULL) + (void)vfprintf(dst, fmt, ap); + if (chan == OCX_DIAG) + vsyslog(LOG_ERR, fmt, ap2); +} + +/********************************************************************** + * XXX: take strftime format string to chop tracefiles in time. + */ + +void +ArgTracefile(const char *fn) +{ + + if (tracefile != NULL && tracefile != stdout) { + AZ(fclose(tracefile)); + tracefile = NULL; + } + + if (fn == NULL) + return; + + if (!strcmp(fn, "-")) { + tracefile = stdout; + return; + } + + tracefile = fopen(fn, "w"); + if (tracefile == NULL) + Fail(NULL, 1, "Could not open '%s' for writing", fn); + setbuf(tracefile, NULL); +} + +/********************************************************************** + * XXX: The stuff below is generic and really ought to be in ocx.c on + * XXX: its own. + */ + +void +Put(struct ocx *ocx, enum ocx_chan chan, const char *fmt, ...) +{ + va_list ap; + + AZ(ocx); + va_start(ap, fmt); + putv(ocx, chan, fmt, ap); + va_end(ap); +} + +void +PutHex(struct ocx *ocx, enum ocx_chan chan, const void *ptr, ssize_t len) +{ + const uint8_t *p = ptr; + const char *s = ""; + + AN(ptr); + assert(len >= 0); + + while(len--) { + Put(ocx, chan, "%s%02x", s, *p++); + s = " "; + } +} + +void +Fail(struct ocx *ocx, int err, const char *fmt, ...) +{ + va_list ap; + + if (err) + err = errno; + Put(ocx, OCX_DIAG, "Failure: "); + va_start(ap, fmt); + putv(ocx, OCX_DIAG, fmt, ap); + va_end(ap); + Put(ocx, OCX_DIAG, "\n"); + if (err) + Put(ocx, OCX_DIAG, "errno = %d (%s)\n", err, strerror(err)); + exit(1); +} diff --git a/param.c b/param.c new file mode 100644 index 0000000..6f02287 --- /dev/null +++ b/param.c @@ -0,0 +1,160 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Routines to deal with parameters + * + */ + +#include +#include +#include + +#include "ntimed.h" + +static TAILQ_HEAD(, param_tbl) param_tbl = TAILQ_HEAD_INITIALIZER(param_tbl); + +void +Param_Register(struct param_tbl *pt) +{ + + for (;pt->name != NULL; pt++) + TAILQ_INSERT_TAIL(¶m_tbl, pt, list); +} + +static void +param_wrapline(struct ocx *ocx, const char *b) +{ + int n = 0; + const char *e, *w; + const int tabs = 8; + const int wrap_at = 64; + + AN(b); + Put(ocx, OCX_DIAG, "\t"); + e = strchr(b, '\0'); + while (b < e) { + if (!isspace(*b)) { + Put(ocx, OCX_DIAG, "%c", *b); + b++; + n++; + } else if (*b == '\t') { + do { + Put(ocx, OCX_DIAG, " "); + n++; + } while ((n % tabs) != 1); + b++; + } else if (*b == '\n') { + Put(ocx, OCX_DIAG, "\n"); + param_wrapline(ocx, b + 1); + return; + } else { + assert (*b == ' '); + for (w = b + 1; w < e; w++) + if (isspace(*w)) + break; + if (n + (w - b) < wrap_at) { + Put(ocx, OCX_DIAG, "%.*s", (int)(w - b), b); + n += (w - b); + b = w; + } else { + Put(ocx, OCX_DIAG, "\n"); + param_wrapline(ocx, b + 1); + return; + } + } + } +} + +void +Param_Tweak(struct ocx *ocx, const char *arg) +{ + struct param_tbl *pt; + const char *q; + char *r; + double d; + size_t l; + + if (!strcmp(arg, "?")) { + Put(ocx, OCX_DIAG, "List of available parameters:\n"); + TAILQ_FOREACH(pt, ¶m_tbl, list) + Put(ocx, OCX_DIAG, "\t%s\n", pt->name); + Fail(ocx, 0, "Stopping after parameter query.\n"); + } + + q = strchr(arg, '='); + if (q == NULL) { + TAILQ_FOREACH(pt, ¶m_tbl, list) + if (!strcmp(pt->name, arg)) + break; + if (pt == NULL) + Fail(ocx, 0, "-p unknown parameter '%s' (try -p '?')", + arg); + Put(ocx, OCX_DIAG, "Parameter:\n\t%s\n", pt->name); + Put(ocx, OCX_DIAG, "Minimum:\n\t%.3e\n", pt->min); + Put(ocx, OCX_DIAG, "Maximum:\n\t%.3e\n", pt->max); + Put(ocx, OCX_DIAG, "Default:\n\t%.3e\n", pt->def); + Put(ocx, OCX_DIAG, "Description:\n"); + param_wrapline(ocx, pt->doc); + Put(ocx, OCX_DIAG, "\n\n"); + Fail(ocx, 0, "Stopping after parameter query.\n"); + } + + l = q - arg; + + TAILQ_FOREACH(pt, ¶m_tbl, list) { + if (strlen(pt->name) != l) + continue; + if (!strncmp(pt->name, arg, l)) + break; + } + if (pt == NULL) + Fail(ocx, 0, "-p unknown parameter '%.*s' (try -p '?')", + (int)(q - arg), arg); + + r = NULL; + d = strtod(q + 1, &r); + if (*r != '\0') + Fail(ocx, 0, "-p '%.*s' bad value '%s'\n", + (int)(q - arg), arg, q + 1); + if (d < pt->min) + Fail(ocx, 0, "-p '%.*s' below min value (%g)\n", + (int)(q - arg), arg, pt->min); + if (d > pt->max) + Fail(ocx, 0, "-p '%.*s' above max value (%g)\n", + (int)(q - arg), arg, pt->max); + Put(ocx, OCX_DIAG, "# Tweak(%s -> %.3e)\n", arg, d); + *(pt->val) = d; + return; +} + +void +Param_Report(struct ocx *ocx, enum ocx_chan chan) +{ + struct param_tbl *pt; + + TAILQ_FOREACH(pt, ¶m_tbl, list) + Put(ocx, chan, "# param %s %g # min %g, max %g, default %g\n", + pt->name, *pt->val, pt->min, pt->max, pt->def); +} diff --git a/param_instance.h b/param_instance.h new file mode 100644 index 0000000..c0c38cb --- /dev/null +++ b/param_instance.h @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Instantiate a set of parameters + * =============================== + * + * The #including file must #define the desired parameters from param_tbl.h + * into existence (PARAM_INSTANCE), and #define the desired name + * of the local parameter table (PARAM_TABLE_NAME), then include this + * file. + */ + +#define PARAM_INSTANCE(nam, vmin, vmax, vdef, docs) \ + static double param_##nam = vdef; +#include "param_tbl.h" +#undef PARAM_INSTANCE + +/*lint -save -e785 */ +static struct param_tbl PARAM_TABLE_NAME[] = { +#define PARAM_INSTANCE(nam, vmin, vmax, vdef, docs) \ + { .name = #nam, .val = ¶m_##nam, \ + .min = vmin, .max = vmax, .def = vdef, .doc = docs }, +#include "param_tbl.h" +#undef PARAM_INSTANCE + { .name = 0 } +}; +/*lint -restore */ diff --git a/param_tbl.h b/param_tbl.h new file mode 100644 index 0000000..3b70856 --- /dev/null +++ b/param_tbl.h @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Parameter Tables + * ================ + * + * All parameters tweakable with a -p argument is defined in this file + * including their minimum, maximum and default values and a not too + * terse documentation string. + * + * Things get instantiated from here by #include param_instance.h + */ + +/*lint -save -e525 -e539 */ + +/* name, min, max, default, docs */ + +#ifdef PARAM_CLIENT +PARAM_CLIENT(poll_rate, 16.0, 4096.0, 64.0, "") +PARAM_CLIENT(foo, 16.0, 4096.0, 64.0, "") +#endif + +#ifdef PARAM_NTP_FILTER + +PARAM_NTP_FILTER(ntp_filter_average, + 3, 1e3, 20, + "Exponential average divisor for average packet delays." + " The value chosen is a compromise between gliding through congestion" + "of common durations and reacting to large-scale routing changes" + "in a timely manner." + " In reality there is no way to tell the two apart." +) + +PARAM_NTP_FILTER(ntp_filter_threshold, + 0.01, 10.0, 3.00, + "Packet delays exceeding the average by this factor are untrustworthy." + " Setting this too high increases noise from (mild) congestion." + " Setting it too low throws away adequate timestamps." +) + +#endif + +/********************************************************************** + * Parameters for pll_std.c + */ + +#ifdef PARAM_PLL_STD + +PARAM_PLL_STD(pll_std_p_init, + 1e-3, 0.50, 0.33, + "Proportional term when PLL starts.\n\n" + "Reducing this will make the PLL more resistant to measurement" + " noise and jitter, but also makes it converge slower." + " Increasing this will allmost certainly cause oscillation." +) + +PARAM_PLL_STD(pll_std_i_init, + 10, 1000, 60, + "Initial P/I ratio when PLL starts.\n\n" + "Reducing this speed up convergence, but risk overshoot." + " Increasing this will slow convergence and reduce impact of noise." +) + +PARAM_PLL_STD(pll_std_capture_time, + 20, 1e6, 300, + "Capture time before stiffning PLL.\n\n" + "After this many seconds, the PLL will start to stiffen the" + " P and I terms to gain noise immunity." + " Decreasing risks that initial frequency capture is not finished," + " which will increase the offset-excursion." + " Increasing just delays this stiffning." +) + +PARAM_PLL_STD(pll_std_stiffen_rate, + 0.5, 1.0, 0.999, + "Rate of PLL P/I term stiffning.\n\n" + "The exponential stiffening per second of D and I terms." + " Decreasing makes stiffening faster." + " Increasing makes stiffening slower." +) + +PARAM_PLL_STD(pll_std_p_limit, + 1e-6, 0.50, 3e-2, + "Lower limit for Proportional term.\n\n" + "Reducing this will make the PLL more resistent to noise," + " but going to far it will not be able to steer the clock fast enough." + " Increasing this makes the PLL more agile and prone to noise." +) + +#endif + + +/*lint -restore */ diff --git a/pll_std.c b/pll_std.c new file mode 100644 index 0000000..18ba543 --- /dev/null +++ b/pll_std.c @@ -0,0 +1,160 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Standard PLL + * ============ + * + * (And the function pointer for accessing any PLL) + */ + +#include + +#include "ntimed.h" + +#define PARAM_PLL_STD PARAM_INSTANCE +#define PARAM_TABLE_NAME pll_std_param_table +#include "param_instance.h" +#undef PARAM_TABLE_NAME +#undef PARAM_PLL_STD + +static double pll_integrator; +static struct timestamp pll_last_time; +static int pll_mode; +static double pll_a, pll_b; +static struct timestamp pll_t0; + +static void __match_proto__(pll_f) +pll_std(struct ocx *ocx, double offset, double weight) +{ + double p_term, dur, dt, rt; + double used_a, used_b; + struct timestamp t0; + + TB_Now(&t0); + p_term = 0.0; + dur = .0; + dt = 0; + used_a = used_b = 0; + + switch (pll_mode) { + + case 0: /* Startup */ + + pll_t0 = t0; + pll_mode = 1; + pll_a = param_pll_std_p_init; + pll_b = 0.0; + break; + + case 1: /* Wait until we have a good estimate, then step */ + + rt = TS_Diff(&t0, &pll_t0); + if (rt > 2.0 && weight > 3) { // XXX param + if (fabs(offset) > 1e-3) // XXX param + TB_Step(ocx, -offset); + pll_mode = 2; + pll_t0 = t0; + } + break; + + case 2: /* Wait for another good estimate, then PLL */ + + rt = TS_Diff(&t0, &pll_t0); + if (rt > 6.0) { + pll_b = pll_a / param_pll_std_i_init; + pll_t0 = t0; + pll_mode = 3; + } + break; + + case 3: /* track mode */ + rt = TS_Diff(&t0, &pll_t0); + assert(rt > 0); + + dt = TS_Diff(&t0, &pll_last_time); + assert(dt > 0); + + /* + * XXX: Brute-force exploitation of the weight. + * + * Ideally, we should scale the pll_[ab] terms and the + * stiffening of them based on the weight. That is harder + * than it sounds -- or at least I have not found a good + * candidate function yet. + * In the meantime this is a simple threshold based + * prevention of horribly distant servers injecting too + * much noise into the very reactive default PLL. + * Some averaging of the weight may be required. + */ + if (weight < 50) { + used_a = 3e-2; + used_b = 5e-4; + } else if (weight < 150) { + used_a = 6e-2; + used_b = 1e-3; + } else { + + if (rt > param_pll_std_capture_time && + pll_a > param_pll_std_p_limit) { + pll_a *= pow(param_pll_std_stiffen_rate, dt); + pll_b *= pow(param_pll_std_stiffen_rate, dt); + } + used_a = pll_a; + used_b = pll_b; + } + p_term = -offset * used_a; + pll_integrator += p_term * used_b; + dur = dt; + break; + default: + WRONG("Wrong PLL state"); + } + + dur = ceil(dur); + + /* Clamp (XXX: leave to timebase to do this ?) */ + if (p_term > dur * 500e-6) + p_term = dur * 500e-6; + if (p_term < dur * -500e-6) + p_term = dur * -500e-6; + + pll_last_time = t0; + Put(ocx, OCX_TRACE, + "PLL %d %.3e %.3e %.3e -> %.3e %.3e %.3e %.3e %.3e\n", + pll_mode, dt, offset, weight, + p_term, dur, pll_integrator, + used_a, used_b); + if (dur > 0.0) + TB_Adjust(ocx, p_term, dur, pll_integrator); +} + +pll_f *PLL = NULL; + +void +PLL_Init(void) +{ + Param_Register(pll_std_param_table); + PLL = pll_std; +} diff --git a/plotgen.py b/plotgen.py new file mode 100644 index 0000000..1729cfa --- /dev/null +++ b/plotgen.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# +# Copyright (c) 2014 Poul-Henning Kamp +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Postprocess simulation output for gnuplot +# ========================================= +# +# python plotgen.py +# then +# gnuplot +# load '/tmp/_g' +# + +from __future__ import print_function + +fi = open("/tmp/_") +fg = open("/tmp/_g", "w") + +fg.write("#\n") + +plots = [] + +class plot(object): + def __init__(self, title = "-"): + self.s = [] + self.title = title + plots.append(self) + + def write(self, l): + self.s.append(l) + + def commit(self, fg, title): + if title: + fg.write("set y2label '%s'\n" % self.title) + else: + fg.write("unset y2label\n") + + for i in self.s: + fg.write(i + "\n") + +now = 0.0 + +class simpll(object): + def __init__(self): + self.fo = open("/tmp/_simpll", "w") + self.pl1 = plot('p/freq') + self.pl1.write( + "plot '/tmp/_simpll' using 1:($2*1e6) with line notitle") + self.pl2 = plot('p/off') + self.pl2.write( + "plot '/tmp/_simpll' using 1:($3*1e6/($4 == 0 ? 1 : $4)) with impulse notitle") + if False: + self.pl3 = plot('p/dur') + self.pl3.write( + "plot '/tmp/_simpll' using 1:4 with imp notitle") + + def data(self, ll): + self.fo.write("%f " % now + " " + " ".join(ll[1:]) + "\n") + +class combine(object): + def __init__(self): + self.fo = open("/tmp/_combine", "w") + self.pl1 = plot("c/in") + self.pl1.write( + "plot '/tmp/_combine' using 1:2 notitle" + ", '/tmp/_combine' using 1:4 notitle" + ", '/tmp/_combine' using 1:3 notitle" + ) + self.pl2 = plot('c/peak') + self.pl2.write( + "plot '/tmp/_combine' using 1:5 with line notitle" + ) + self.pl3 = plot("c/weight") + self.pl3.write("set yrange[0:]") + self.pl3.write( + "plot '/tmp/_combine' using 1:6 axis x1y2 with line notitle" + ) + self.pl3.write("set autoscale y") + + def data(self, ll): + self.fo.write("%f " % now + " " + " ".join(ll[3:]) + "\n") + +p_simpll = simpll() + +p_combine = combine() + +n = 0 +for l in fi: + n += 1 + if len(l) == 0 or l[0] == "#": + continue + ll = l.split() + if ll[0] == "Now": + now = float(ll[1]) - 1e6 + continue + if ll[0] == "SIMPLL": + p_simpll.data(ll) + continue + if ll[0] == "Combine": + p_combine.data(ll) + continue + +def plotset(xlo, xhi, lbl): + fg.write("set autoscale x\n") + fg.write("set xrange [%g:%g]\n" % (xlo, xhi)) + fg.write("set bmargin 0\n") + fg.write("set xtics format ''\n") + fg.write("set xtics (%g, %g)\n" % (xlo, xhi)) + for i in plots[:-1]: + i.commit(fg, lbl) + fg.write("set bmargin 2\n") + fg.write("set xtics format '%g'\n") + fg.write("set xtics (%g, %g)\n" % (xlo, xhi)) + plots[-1].commit(fg, lbl) + +fg.write("set grid\n") +fg.write("set pointsize .5\n") +fg.write("set size .8,.8\n") +fg.write("set tmargin 1\n") +fg.write("set lmargin 8\n") + +fg.write("set multiplot layout %d,3 columnsfirst\n" % len(plots)) + +fg.write("set style data point\n") +plotset(0, 64, False) + +if now > 10000: + fg.write("set pointsize .2\n") + plotset(60, 3600, False) + fg.write("set pointsize .1\n") + plotset(3600, now, True) +else: + fg.write("set pointsize .5\n") + plotset(60, 600, False) + fg.write("set pointsize .2\n") + plotset(5, now, True) + +fg.write("unset multiplot\n") +fg.write("set autoscale\n") +fg.write("set xtics auto\n") +fg.write("set ytics auto\n") diff --git a/time_sim.c b/time_sim.c new file mode 100644 index 0000000..f3d2dbe --- /dev/null +++ b/time_sim.c @@ -0,0 +1,178 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Simulated timebase + * ================== + * + * Very simple minded: Time advances when TB_Sleep() is called only. + * + */ + +#include +#include +#include + +#include "ntimed.h" + +static struct timestamp st_now; + +static double freq = 0; +static double freq0 = 0; + +static double adj_offset = 0; +static double adj_duration = 0; +static double adj_freq = 0; + +/* + * This variable is public and represent the amount of time the simulated + * clock has been tweaked by the TB_Step() and TB_Adjust() functions. + * + * This can be used to "(re-)model" previously recorded event series + * onto the ST timebase. + */ + +double Time_Sim_delta = 0; + +/**********************************************************************/ + +static struct timestamp * +st_Now(struct timestamp *storage) +{ + if (storage == NULL) + ALLOC_OBJ(storage, TIMESTAMP_MAGIC); + AN(storage); + *storage = st_now; + return (storage); +} + +/**********************************************************************/ + +static void +st_Sleep(double dur) +{ + + TS_Add(&st_now, dur); + Time_Sim_delta += dur * freq; +} + +/**********************************************************************/ + +static void __match_proto__(tb_step_f) +st_Step(struct ocx *ocx, double offset) +{ + + Debug(ocx, "SIMSTEP %.3e\n", offset); + Time_Sim_delta += offset; + TB_generation++; +} + +/**********************************************************************/ + +static void __match_proto__(tb_adjust_f) +st_Adjust(struct ocx *ocx, double offset, double duration, double frequency) +{ + + (void)ocx; + adj_offset = offset; + adj_duration = floor(duration); + if (adj_offset > 0.0 && adj_duration == 0.0) + adj_duration = 1.0; + adj_freq = frequency; +} + +/**********************************************************************/ + +static enum todo_e __match_proto__(todo_f) +st_kern_pll(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + double d; + + (void)ocx; + AN(tdl); + AZ(priv); + freq = freq0 + adj_freq; + if (adj_duration > 0.0) { + d = adj_offset / adj_duration; + freq += d; + adj_offset -= d; + adj_duration -= 1.0; + } + Put(ocx, OCX_TRACE, "SIMPLL %.3e %.3e %.3e\n", + adj_freq, adj_offset, adj_duration); + return (TODO_OK); +} + +/********************************************************************** + * Mechanism to artificially bump simulated clock around. + */ + +struct st_bump { + unsigned magic; +#define ST_BUMP_MAGIC 0xc8981be3 + double bfreq; + double bphase; +}; + +static enum todo_e __match_proto__(todo_f) +st_kern_bump(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + struct st_bump *stb; + + (void)ocx; + (void)tdl; + CAST_OBJ_NOTNULL(stb, priv, ST_BUMP_MAGIC); + + freq0 += stb->bfreq; + Time_Sim_delta += stb->bphase; + FREE_OBJ(stb); + return (TODO_OK); +} + +void +Time_Sim_Bump(struct todolist *tdl, double when, double bfreq, double bphase) +{ + struct st_bump *stb; + + ALLOC_OBJ(stb, ST_BUMP_MAGIC); + AN(stb); + stb->bfreq = bfreq; + stb->bphase = bphase; + (void)TODO_ScheduleRel(tdl, st_kern_bump, stb, when, 0.0, "BUMP"); +} + +/**********************************************************************/ + +void +Time_Sim(struct todolist *tdl) +{ + + INIT_OBJ(&st_now, TIMESTAMP_MAGIC); + TS_Add(&st_now, 1e6); + TB_Now = st_Now; + TB_Sleep = st_Sleep; + TB_Step = st_Step; + TB_Adjust = st_Adjust; + (void)TODO_ScheduleRel(tdl, st_kern_pll, NULL, 0.0, 1.0, "SIMPLL"); +} diff --git a/time_stuff.c b/time_stuff.c new file mode 100644 index 0000000..5b36d56 --- /dev/null +++ b/time_stuff.c @@ -0,0 +1,261 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Timebase infrastructure + * ======================= + * + * This file implements the generic timebase stuff, calling out to a specific + * implementation through the function pointers as required. + */ + +#include +#include +#include +#include + +#include "ntimed.h" + +#define NANO_FRAC 18446744074ULL // 2^64 / 1e9 + +/* + * Whenever the clock is stepped, we increment this generation number. + * + * XXX: Add support for stepping externally via a signal (SIGRESUME ?) + */ +int TB_generation = 41; + +/**********************************************************************/ + +static struct timestamp * +ts_fixstorage(struct timestamp *storage) +{ + if (storage == NULL) { + ALLOC_OBJ(storage, TIMESTAMP_MAGIC); + AN(storage); + } else { + AN(storage); + memset(storage, 0, sizeof *storage); + storage->magic = TIMESTAMP_MAGIC; + } + return (storage); +} + +/**********************************************************************/ + +struct timestamp * +TS_Nanosec(struct timestamp *storage, intmax_t sec, intmax_t nsec) +{ + + storage = ts_fixstorage(storage); + + assert(sec >= 0); + assert(nsec >= 0); + assert(nsec < 1000000000); + storage->sec = (uint64_t)sec; + storage->frac = (uint32_t)nsec * NANO_FRAC; + return (storage); +} + +/**********************************************************************/ + +struct timestamp * +TS_Double(struct timestamp *storage, double d) +{ + + assert(d >= 0.0); + storage = ts_fixstorage(storage); + + storage->sec += (uint64_t)floor(d); + d -= floor(d); + storage->frac = (uint64_t)ldexp(d, 64); + return (storage); +} + +/**********************************************************************/ + +void +TS_Add(struct timestamp *ts, double dt) +{ + double di; + + CHECK_OBJ_NOTNULL(ts, TIMESTAMP_MAGIC); + dt += ldexp(ts->frac, -64); + di = floor(dt); + ts->sec += (uint64_t)di; + ts->frac = (uint64_t)ldexp(dt - di, 64); +} + +/**********************************************************************/ + +double +TS_Diff(const struct timestamp *t1, const struct timestamp *t2) +{ + double d; + + CHECK_OBJ_NOTNULL(t1, TIMESTAMP_MAGIC); + CHECK_OBJ_NOTNULL(t2, TIMESTAMP_MAGIC); + d = ldexp((double)t1->frac - (double)t2->frac, -64); + d += ((double)t1->sec - (double)t2->sec); + + return (d); +} + +/**********************************************************************/ + +void +TS_SleepUntil(const struct timestamp *t) +{ + struct timestamp now; + double dt; + + TB_Now(&now); + dt = TS_Diff(t, &now); + if (dt > 0.) + TB_Sleep(dt); +} + +/**********************************************************************/ + +void +TS_Format(char *buf, ssize_t len, const struct timestamp *ts) +{ + CHECK_OBJ_NOTNULL(ts, TIMESTAMP_MAGIC); + uint64_t x, y; + + /* XXX: Nanosecond precision is enough for everybody. */ + x = ts->sec; + y = (ts->frac + NANO_FRAC / 2ULL) / NANO_FRAC; + if (y >= 1000000000ULL) { + y -= 1000000000ULL; + x += 1; + } + assert(snprintf(buf, len, "%jd.%09jd", x, y) < len); +} + +/********************************************************************** + * DUMMY TimeBase functions + */ + +static struct timestamp * +tb_Now(struct timestamp *storage) +{ + + (void)storage; + WRONG("No TB_Now"); + return (NULL); +} + +tb_now_f *TB_Now = tb_Now; + +/**********************************************************************/ + +static void +tb_Sleep(double dur) +{ + (void)dur; + WRONG("No TB_Sleep"); +} + +tb_sleep_f *TB_Sleep = tb_Sleep; + +/**********************************************************************/ + +static void __match_proto__(tb_step_f) +tb_Step(struct ocx *ocx, double offset) +{ + (void)ocx; + (void)offset; + WRONG("No TB_Step"); +} + +tb_step_f *TB_Step = tb_Step; + +/**********************************************************************/ + +static void __match_proto__(tb_adjust_f) +tb_Adjust(struct ocx *ocx, double offset, double duration, double frequency) +{ + (void)ocx; + (void)offset; + (void)duration; + (void)frequency; + WRONG("No TB_Adjust"); +} + +tb_adjust_f *TB_Adjust = tb_Adjust; + +/********************************************************************** + * Timebase test functions. + */ + +static int +ts_onetest(struct ocx *ocx, const struct timestamp *ts, double off) +{ + struct timestamp ts2; + double dt; + char buf[40]; + + TS_Format(buf, sizeof buf, ts); + ts2 = *ts; + TS_Add(&ts2, off); + Debug(ocx, "%s + %12.9f = ", buf, off); + TS_Format(buf, sizeof buf, &ts2); + dt = TS_Diff(&ts2, ts) - off; + Debug(ocx, "%s %8.1e", buf, dt); + if (fabs(dt) > 5e-10) { + Debug(ocx, " ERR\n"); + return (1); + } + Debug(ocx, " OK\n"); + return (0); +} + +void +TS_RunTest(struct ocx *ocx) +{ + struct timestamp ts; + int nf = 0; + + TB_Now(&ts); + nf += ts_onetest(ocx, &ts, 1e-9); + nf += ts_onetest(ocx, &ts, 1e-8); + nf += ts_onetest(ocx, &ts, 1e-6); + nf += ts_onetest(ocx, &ts, 1e-3); + nf += ts_onetest(ocx, &ts, 1e-1); + nf += ts_onetest(ocx, &ts, 0.999); + nf += ts_onetest(ocx, &ts, 1.001); + nf += ts_onetest(ocx, &ts, 1.999); + nf += ts_onetest(ocx, &ts, -2.000); + nf += ts_onetest(ocx, &ts, -1.999); + nf += ts_onetest(ocx, &ts, -1.000); + nf += ts_onetest(ocx, &ts, -0.999); + nf += ts_onetest(ocx, &ts, -1e-3); + nf += ts_onetest(ocx, &ts, -1e-6); + nf += ts_onetest(ocx, &ts, -1e-9); + Debug(ocx, "TS_RunTest: %d failures\n", nf); + AZ(nf); +} + + diff --git a/time_unix.c b/time_unix.c new file mode 100644 index 0000000..5787b00 --- /dev/null +++ b/time_unix.c @@ -0,0 +1,256 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * UNIX timebase + * ============= + * + * Implement the timebase functions on top of a modern UNIX kernel which + * has the some version of the Mills/Kamp kernel PLL code and either + * [gs]ettimeofday(2) or better: clock_[gs]ettime(2) API. + * + */ + +#include +#include +#include +#include +#include + +#include + +#include "ntimed.h" + +static double adj_offset = 0; +static double adj_duration = 0; +static double adj_freq = 0; + +// #undef CLOCK_REALTIME /* Test old unix code */ + +/********************************************************************** + * The NTP-pll in UNIX kernels apply the offset correction in an + * exponential-decay fashion for historical and wrong reasons. + * + * The short explanation is that this ends up confusing all PLLs I have + * ever seen, by introducing mainly odd harmonics of the PLL update period + * into all time-measurements in the system. + * + * A much more sane mode would be to tell the kernel "I want this much + * offset accumulated over this many seconds", giving a constant frequency + * over the PLL update period while still falling back to the frequency + * estimate should the time-steering userland process fail. + * + * I will add such a mode to the FreeBSD kernel as a reference implementation + * at a later date, in the mean time this code implements it by updating the + * kernel frequency from userland as needed. + * + * XXX: Optimise to only wake up when truly needed, rather than every second. + * XXX: Requires TODO cancellation. + */ + +static void +kt_setfreq(double frequency) +{ + struct timex tx; + int i; + + assert(isfinite(frequency)); + + memset(&tx, 0, sizeof tx); + tx.modes = MOD_STATUS; +#if defined(MOD_NANO) + tx.modes |= MOD_NANO; +#elif defined(MOD_MICRO) + tx.modes |= MOD_MICRO; +#endif + + tx.status = STA_PLL | STA_FREQHOLD; + tx.modes = MOD_FREQUENCY; + tx.freq = (long)floor(frequency * (65536 * 1e6)); + errno = 0; + i = ntp_adjtime(&tx); + /* XXX: what is the correct error test here ? */ + assert(i >= 0); +} + +static enum todo_e __match_proto__(todo_f) +kt_pll(struct ocx *ocx, struct todolist *tdl, void *priv) +{ + double d, freq; + struct timestamp ts; + char buf[40]; + + (void)ocx; + AN(tdl); + AZ(priv); + freq = adj_freq; + if (adj_duration > 0.0) { + d = adj_offset / adj_duration; + freq += d; + adj_offset -= d; + adj_duration -= 1.0; + } + TB_Now(&ts); + TS_Format(buf, sizeof buf, &ts); + Put(ocx, OCX_TRACE, "KERNTIME_PLL %s %.6e %.6e %.3e %.6e\n", + buf, adj_freq, adj_offset, adj_duration, freq); + kt_setfreq(freq); + return (TODO_OK); +} + +static void __match_proto__(tb_adjust_f) +kt_adjust(struct ocx *ocx, double offset, double duration, double frequency) +{ + + (void)ocx; + + adj_offset = offset; + adj_duration = floor(duration); + if (adj_offset > 0.0 && adj_duration == 0.0) + adj_duration = 1.0; + adj_freq = frequency; +} + +/**********************************************************************/ + +#ifdef CLOCK_REALTIME + +static void __match_proto__(tb_step_f) +kt_step(struct ocx *ocx, double offset) +{ + double d; + struct timespec ts; + + Put(ocx, OCX_TRACE, "KERNTIME_STEP %.3e\n", offset); + d = floor(offset); + offset -= d; + + AZ(clock_gettime(CLOCK_REALTIME, &ts)); + ts.tv_sec += (long)d; + ts.tv_nsec += (long)floor(offset * 1e9); + if (ts.tv_nsec < 0) { + ts.tv_sec -= 1; + ts.tv_nsec += 1000000000; + } else if (ts.tv_nsec >= 1000000000) { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000; + } + AZ(clock_settime(CLOCK_REALTIME, &ts)); + TB_generation++; +} + +#else + +static void __match_proto__(tb_step_f) +kt_step(struct ocx *ocx, double offset) +{ + double d; + struct timeval tv; + + Put(ocx, OCX_TRACE, "KERNTIME_STEP %.3e\n", offset); + d = floor(offset); + offset -= d; + + AZ(gettimeofday(&tv, NULL)); + tv.tv_sec += (long)d; + tv.tv_usec += (long)floor(offset * 1e6); + if (tv.tv_usec < 0) { + tv.tv_sec -= 1; + tv.tv_usec += 1000000; + } else if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + AZ(settimeofday(&tv, NULL)); + TB_generation++; +} + +#endif + +/**********************************************************************/ + +#if defined (CLOCK_REALTIME) + +static struct timestamp * __match_proto__(tb_now_f) +kt_now(struct timestamp *storage) +{ + struct timespec ts; + + AZ(clock_gettime(CLOCK_REALTIME, &ts)); + return (TS_Nanosec(storage, ts.tv_sec, ts.tv_nsec)); +} + +#else + +static struct timestamp * __match_proto__(tb_now_f) +kt_now(struct timestamp *storage) +{ + struct timeval tv; + + AZ(gettimeofday(&tv, NULL)); + return (TS_Nanosec(storage, tv.tv_sec, tv.tv_usec * 1000LL)); +} + +#endif + +/**********************************************************************/ + +static void __match_proto__(tb_sleep_f) +kt_sleep(double dur) +{ + struct pollfd fds[1]; + + AZ(poll(fds, 0, (int)floor(dur * 1e3))); +} + +/**********************************************************************/ + +void +Time_Unix(struct todolist *tdl) +{ + struct timestamp ts; + + TB_Step = kt_step; + TB_Adjust = kt_adjust; + TB_Sleep = kt_sleep; + TB_Now = kt_now; + + /* Aim the userland PLL wrangler at 125msec before the second */ + TB_Now(&ts); + ts.sec += 1; + ts.frac = 14ULL << 60ULL; + (void)TODO_ScheduleAbs(tdl, kt_pll, NULL, &ts, 1.0, "SIMPLL"); +} + +/********************************************************************** + * Non-tweaking subset. + */ + +void +Time_Unix_Passive(void) +{ + + TB_Sleep = kt_sleep; + TB_Now = kt_now; +} diff --git a/todo.c b/todo.c new file mode 100644 index 0000000..cc16510 --- /dev/null +++ b/todo.c @@ -0,0 +1,175 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * todo-list scheduler + * =================== + * + * This is a simple "TODO-list" scheduler for calling things at certain + * times. Jobs can be one-shot or repeated and repeated jobs can abort. + */ + +#include +#include +#include + +#include "ntimed.h" + +struct todo { + unsigned magic; +#define TODO_MAGIC 0x5279009a + TAILQ_ENTRY(todo) list; + + todo_f *func; + void *priv; + struct timestamp when; + double repeat; + + char what[40]; +}; + +struct todolist { + unsigned magic; +#define TODOLIST_MAGIC 0x7db66255 + TAILQ_HEAD(,todo) todolist; +}; + +struct todolist * +TODO_NewList(void) +{ + struct todolist *tdl; + + ALLOC_OBJ(tdl, TODOLIST_MAGIC); + AN(tdl); + TAILQ_INIT(&tdl->todolist); + return (tdl); +} + +static void +todo_insert(struct todolist *tdl, struct todo *tp) +{ + struct todo *tp1; + + TAILQ_FOREACH(tp1, &tdl->todolist, list) { + if (TS_Diff(&tp1->when, &tp->when) > 0.0) { + TAILQ_INSERT_BEFORE(tp1, tp, list); + return; + } + } + TAILQ_INSERT_TAIL(&tdl->todolist, tp, list); +} + +/********************************************************************** + * Return a pointer to the todo item, so the API can support cancellation. + * (Cancellation will be necessary for NTP-peer removal) + * + * For ease of debugging, TODO jobs have a name. + */ + +struct todo * +TODO_ScheduleAbs(struct todolist *tdl, todo_f *func, void *priv, + const struct timestamp *when, double repeat, const char *fmt, ...) +{ + struct todo *tp; + va_list ap; + + CHECK_OBJ_NOTNULL(tdl, TODOLIST_MAGIC); + AN(func); + CHECK_OBJ_NOTNULL(when, TIMESTAMP_MAGIC); + assert(repeat >= 0.0); + AN(fmt); + + ALLOC_OBJ(tp, TODO_MAGIC); + AN(tp); + tp->func = func; + tp->priv = priv; + tp->when = *when; + tp->repeat = repeat; + va_start(ap, fmt); + (void)vsnprintf(tp->what, sizeof tp->what, fmt, ap); + va_end(ap); + todo_insert(tdl, tp); + return (tp); +} + +struct todo * +TODO_ScheduleRel(struct todolist *tdl, todo_f *func, void *priv, + double when, double repeat, const char *fmt, ...) +{ + struct todo *tp; + va_list ap; + + CHECK_OBJ_NOTNULL(tdl, TODOLIST_MAGIC); + AN(func); + assert(when >= 0.0); + assert(repeat >= 0.0); + AN(fmt); + + ALLOC_OBJ(tp, TODO_MAGIC); + AN(tp); + tp->func = func; + tp->priv = priv; + TB_Now(&tp->when); + TS_Add(&tp->when, when); + tp->repeat = repeat; + va_start(ap, fmt); + (void)vsnprintf(tp->what, sizeof tp->what, fmt, ap); + va_end(ap); + todo_insert(tdl, tp); + return (tp); +} + +/********************************************************************** + * Schedule TODO list until failure or empty + */ + +enum todo_e +TODO_Run(struct ocx *ocx, struct todolist *tdl) +{ + struct todo *tp; + enum todo_e ret = TODO_OK; + char buf[40]; + + CHECK_OBJ_NOTNULL(tdl, TODOLIST_MAGIC); + while(!TAILQ_EMPTY(&tdl->todolist)) { + tp = TAILQ_FIRST(&tdl->todolist); + TS_SleepUntil(&tp->when); + TS_Format(buf, sizeof buf, &tp->when); + Put(ocx, OCX_TRACE, "Now %s %s\n", buf, tp->what); + ret = tp->func(ocx, tdl, tp->priv); + if (ret == TODO_FAIL) + break; + if (ret == TODO_DONE || tp->repeat == 0.0) { + TAILQ_REMOVE(&tdl->todolist, tp, list); + FREE_OBJ(tp); + } else if (ret == TODO_OK) { + TS_Add(&tp->when, tp->repeat); + TAILQ_REMOVE(&tdl->todolist, tp, list); + todo_insert(tdl, tp); + } else { + WRONG("Invalid Return from todo->func"); + } + } + return (ret); +} diff --git a/udp.c b/udp.c new file mode 100644 index 0000000..0ef1cae --- /dev/null +++ b/udp.c @@ -0,0 +1,131 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include "ntimed.h" +#include "udp.h" + +int +UdpTimedSocket(struct ocx *ocx, int fam) +{ + int fd; + int i; + + fd = socket(fam, SOCK_DGRAM, 0); + if (fd < 0) + Fail(ocx, 1, "socket(2) failed"); + +#ifdef IP_RECVDSTADDR + i = 1; + if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &i, sizeof i) != 0) { + AZ(close(fd)); + Fail(ocx, 1, "setsockopt(IP_RECVDSTADDR) failed"); + } +#endif +#ifdef SO_TIMESTAMPNS + i = 1; + (void)setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPNS, &i, sizeof i); +#elif defined(SO_TIMESTAMP) + i = 1; + (void)setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &i, sizeof i); +#endif + return (fd); +} + +ssize_t +UdpTimedRx(struct ocx *ocx, int fd, struct sockaddr_storage *ss, socklen_t *sl, + struct timestamp *ts, void *buf, ssize_t len) +{ + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + u_char ctrl[1024]; + ssize_t rl; + + assert(fd >= 0); + AN(ss); + AN(sl); + AN(ts); + AN(buf); + assert(len > 0); + + memset(&msg, 0, sizeof msg); + msg.msg_name = (void*)ss; + msg.msg_namelen = sizeof *ss; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = ctrl; + msg.msg_controllen = sizeof ctrl; + iov.iov_base = buf; + iov.iov_len = (size_t)len; + memset(ctrl, 0, sizeof ctrl); + cmsg = (void*)ctrl; + + rl = recvmsg(fd, &msg, 0); + if (rl <= 0) + return (rl); + + *sl = msg.msg_namelen; + + for(;cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { +#ifdef SCM_TIMESTAMPNS + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_TIMESTAMPNS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) { + struct timespec tsc; + memcpy(&tsc, CMSG_DATA(cmsg), sizeof tsc); + (void)TS_Nanosec(ts, tsc.tv_sec, tsc.tv_nsec); + continue; + } +#endif +#ifdef SCM_TIMESTAMP + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) { + struct timeval tv; + memcpy(&tv, CMSG_DATA(cmsg), sizeof tv); + (void)TS_Nanosec(ts, tv.tv_sec, tv.tv_usec * 1000LL); + continue; + } +#endif +#ifdef IP_RECVDSTADDR + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_RECVDSTADDR && + cmsg->cmsg_len == CMSG_LEN(sizeof(in_addr_t))) { + continue; + /* XXX */ + } +#endif + Debug(ocx, "RX-msg: %d %d %u ", + cmsg->cmsg_level, cmsg->cmsg_type, cmsg->cmsg_len); + DebugHex(ocx, CMSG_DATA(cmsg), cmsg->cmsg_len); + Debug(ocx, "\n"); + + } + return (rl); +} diff --git a/udp.h b/udp.h new file mode 100644 index 0000000..cd19c19 --- /dev/null +++ b/udp.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 2014 Poul-Henning Kamp + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef UDP_H_INCLUDED +#error "udp.h included multiple times" +#endif +#define UDP_H_INCLUDED + +/********************************************************************** + * UDP sockets + */ + +int UdpTimedSocket(struct ocx *ocx, int fam); +ssize_t UdpTimedRx(struct ocx *ocx, int fd, struct sockaddr_storage *ss, + socklen_t *sl, struct timestamp *ts, void *buf, ssize_t len); +