#include "header.h"
#include <limits.h>
#include <float.h>
#include <math.h>
#define MAIN
#include "kludges.h"
#undef MAIN
#define JAN_1970 2208988800.0
#define NTP_SCALE 4294967296.0
#define NTP_PACKET_MIN 48
#define NTP_PACKET_MAX 68
#define NTP_DISP_FIELD 8
#define NTP_REFERENCE 16
#define NTP_ORIGINATE 24
#define NTP_RECEIVE 32
#define NTP_TRANSMIT 40
#define NTP_LI_FUDGE 0
#define NTP_VERSION 3
#define NTP_VERSION_MAX 4
#define NTP_STRATUM 15
#define NTP_STRATUM_MAX 15
#define NTP_POLLING 8
#define NTP_PRECISION 0
#define NTP_ACTIVE 1
#define NTP_PASSIVE 2
#define NTP_CLIENT 3
#define NTP_SERVER 4
#define NTP_BROADCAST 5
#define NTP_INSANITY 3600.0
#define RESET_MIN 15
#define ABSCISSA 3.0
const char *argv0 = NULL;
int verbose = 0,
operation = 0;
const char *lockname = NULL;
#define COUNT_MAX 25
#define WEEBLE_FACTOR 1.2
#define ETHERNET_MAX 5
#define action_display 1
#define action_reset 2
#define action_adjust 3
#define action_broadcast 4
#define action_server 5
#define action_query 6
#define save_read_only 1
#define save_read_check 2
#define save_write 3
#define save_clear 4
static const char version[] = VERSION;
static int action = 0,
count = 0,
delay = 0,
attempts = 0,
waiting = 0,
locked = 0;
static double outgoing[2*COUNT_MAX],
minerr = 0.0,
maxerr = 0.0,
prompt = 0.0,
dispersion = 0.0;
static FILE *savefile = NULL;
typedef struct NTP_DATA {
unsigned char status, version, mode, stratum, polling, precision;
double dispersion, reference, originate, receive, transmit, current;
} ntp_data;
typedef struct {
double dispersion, weight, when, offset, error;
} data_record;
void syntax(int);
void display_data(ntp_data *);
void display_packet(unsigned char *, int);
void pack_ntp(unsigned char *, int, ntp_data *);
void unpack_ntp(ntp_data *, unsigned char *, int);
void make_packet(ntp_data *, int);
int read_packet(int, ntp_data *, double *, double *);
void format_time(char *, int, double, double, double, double);
double reset_clock(double, double, int);
void run_server(void);
double estimate_stats(int *, int *, data_record *, double, double *, double *,
double *, double *, double *, double *, int *, int);
double correct_drift(double *, double *, double);
void handle_saving(int, int *, int *, int *, data_record *, double *,
double *, double *);
void query_savefile(void);
void run_daemon(char **, int, int);
void run_client(char **, int);
void fatal (int syserr, const char *message, const char *insert) {
int k = errno;
static int called = 0;
if (message != NULL) {
fprintf(stderr,"%s: ",argv0);
fprintf(stderr,message,insert);
fprintf(stderr,"\n");
}
errno = k;
if (syserr) perror(argv0);
if (! called) {
called = 1;
if (savefile != NULL && fclose(savefile))
fatal(1,"unable to close the daemon save file",NULL);
if (locked) set_lock(0);
}
exit(EXIT_FAILURE);
}
void syntax (int halt) {
fprintf(stderr,"Syntax: %s [ --help | -h | -? ] [ -v | -V | -W ] \n",argv0);
fprintf(stderr," [ -q [ -f savefile ] |\n");
fprintf(stderr," [ { -r | -a } [ -P prompt ] [ -l lockfile ] ]\n");
fprintf(stderr," [ -c count ] [ -e minerr ] [ -E maxerr ]\n");
fprintf(stderr," [ -d delay | -x [ separation ] ");
fprintf(stderr,"[ -f savefile ] ]\n");
fprintf(stderr," [ -4 | -6 ] [ address(es) ] ]\n");
if (halt) exit(EXIT_FAILURE);
}
void display_data (ntp_data *data) {
fprintf(stderr,"sta=%d ver=%d mod=%d str=%d pol=%d dis=%.6f ref=%.6f\n",
data->status,data->version,data->mode,data->stratum,data->polling,
data->dispersion,data->reference);
fprintf(stderr,"ori=%.6f rec=%.6f\n",data->originate,data->receive);
fprintf(stderr,"tra=%.6f cur=%.6f\n",data->transmit,data->current);
}
void display_packet (unsigned char *packet, int length) {
int i;
if (length < NTP_PACKET_MIN || length > NTP_PACKET_MAX) return;
for (i = 0; i < length; ++i) {
if (i != 0 && i%32 == 0)
fprintf(stderr,"\n");
else if (i != 0 && i%4 == 0)
fprintf(stderr," ");
fprintf(stderr,"%.2x",packet[i]);
}
fprintf(stderr,"\n");
}
void pack_ntp (unsigned char *packet, int length, ntp_data *data) {
int i, k;
double d;
memset(packet,0,(size_t)length);
packet[0] = (data->status<<6)|(data->version<<3)|data->mode;
packet[1] = data->stratum;
packet[2] = data->polling;
packet[3] = data->precision;
d = data->originate/NTP_SCALE;
for (i = 0; i < 8; ++i) {
if ((k = (int)(d *= 256.0)) >= 256) k = 255;
packet[NTP_ORIGINATE+i] = k;
d -= k;
}
d = data->receive/NTP_SCALE;
for (i = 0; i < 8; ++i) {
if ((k = (int)(d *= 256.0)) >= 256) k = 255;
packet[NTP_RECEIVE+i] = k;
d -= k;
}
d = data->transmit/NTP_SCALE;
for (i = 0; i < 8; ++i) {
if ((k = (int)(d *= 256.0)) >= 256) k = 255;
packet[NTP_TRANSMIT+i] = k;
d -= k;
}
}
void unpack_ntp (ntp_data *data, unsigned char *packet, int length) {
int i;
double d;
data->current = current_time(JAN_1970);
data->status = (packet[0] >> 6);
data->version = (packet[0] >> 3)&0x07;
data->mode = packet[0]&0x07;
data->stratum = packet[1];
data->polling = packet[2];
data->precision = packet[3];
d = 0.0;
for (i = 0; i < 4; ++i) d = 256.0*d+packet[NTP_DISP_FIELD+i];
data->dispersion = d/65536.0;
d = 0.0;
for (i = 0; i < 8; ++i) d = 256.0*d+packet[NTP_REFERENCE+i];
data->reference = d/NTP_SCALE;
d = 0.0;
for (i = 0; i < 8; ++i) d = 256.0*d+packet[NTP_ORIGINATE+i];
data->originate = d/NTP_SCALE;
d = 0.0;
for (i = 0; i < 8; ++i) d = 256.0*d+packet[NTP_RECEIVE+i];
data->receive = d/NTP_SCALE;
d = 0.0;
for (i = 0; i < 8; ++i) d = 256.0*d+packet[NTP_TRANSMIT+i];
data->transmit = d/NTP_SCALE;
}
void make_packet (ntp_data *data, int mode) {
data->status = NTP_LI_FUDGE<<6;
data->stratum = NTP_STRATUM;
data->reference = data->dispersion = 0.0;
if (mode == NTP_SERVER) {
data->mode = (data->mode == NTP_CLIENT ? NTP_SERVER : NTP_PASSIVE);
data->originate = data->transmit;
data->receive = data->current;
} else {
data->version = NTP_VERSION;
data->mode = mode;
data->polling = NTP_POLLING;
data->precision = NTP_PRECISION;
data->receive = data->originate = 0.0;
}
data->current = data->transmit = current_time(JAN_1970);
}
int read_packet (int which, ntp_data *data, double *off, double *err) {
unsigned char receive[NTP_PACKET_MAX+1];
double delay1, delay2, x, y;
int response = 0, failed, length, i, k;
if ((length = read_socket(which,receive,NTP_PACKET_MAX+1,waiting)) <= 0)
return 1;
if (length < NTP_PACKET_MIN || length > NTP_PACKET_MAX) {
if (verbose)
fprintf(stderr,"%s: bad length %d for NTP packet on socket %d\n",
argv0,length,which);
return 1;
}
if (verbose > 2) {
fprintf(stderr,"Incoming packet on socket %d:\n",which);
display_packet(receive,length);
}
unpack_ntp(data,receive,length);
if (verbose > 2) display_data(data);
if (operation == op_listen)
failed = (data->mode != NTP_BROADCAST);
else {
failed = (data->mode != NTP_SERVER && data->mode != NTP_PASSIVE);
response = 1;
}
if (failed || data->status == 3 || data->version < 1 ||
data->version > NTP_VERSION_MAX ||
data->stratum > NTP_STRATUM_MAX) {
if (verbose)
fprintf(stderr,
"%s: Unusable NTP packet rejected on socket %d (f=%d, status %d, version %d, stratum %d)\n",
argv0, which,
failed, data->status, data->version, data->stratum);
return 1;
}
delay1 = data->transmit-data->receive;
delay2 = data->current-data->originate;
failed = (
( data->stratum != 0
&& data->stratum != NTP_STRATUM_MAX
&& data->reference == 0.0
)
|| data->transmit == 0.0
);
if (response &&
(data->originate == 0.0 || data->receive == 0.0 ||
(data->reference != 0.0 && data->receive < data->reference) ||
delay1 < 0.0 || delay1 > NTP_INSANITY || delay2 < 0.0 ||
data->dispersion > NTP_INSANITY))
failed = 1;
if (failed) {
if (verbose)
fprintf(stderr,
"%s: incomprehensible NTP packet rejected on socket %d\n",
argv0,which);
return 1;
}
if (response) {
k = 0;
for (i = 0; i < attempts; ++i)
if (data->originate == outgoing[i]) {
outgoing[i] = 0.0;
++k;
}
if (k != 1 || delay2 > NTP_INSANITY) {
if (verbose)
fprintf(stderr,
"%s: bad response from NTP server rejected on socket %d\n",
argv0,which);
return 1;
}
}
if (dispersion < data->dispersion) dispersion = data->dispersion;
if (operation == op_listen) {
*off = data->transmit-data->current;
*err = NTP_INSANITY;
} else {
x = data->receive-data->originate;
y = (data->transmit == 0.0 ? 0.0 : data->transmit-data->current);
*off = 0.5*(x+y);
*err = x-y;
x = data->current-data->originate;
if (0.5*x > *err) *err = 0.5*x;
}
return 0;
}
void format_time (char *text, int length, double offset, double error,
double drift, double drifterr) {
int milli, len;
time_t now;
struct tm *gmt;
static const char *months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
now = convert_time(current_time(offset),&milli);
errno = 0;
if ((gmt = localtime(&now)) == NULL)
fatal(1,"unable to work out local time",NULL);
len = 24;
if (length <= len) fatal(0,"internal error calling format_time",NULL);
errno = 0;
sprintf(text,"%.4d %s %.2d %.2d:%.2d:%.2d.%.3d",
gmt->tm_year+1900,months[gmt->tm_mon],gmt->tm_mday,
gmt->tm_hour,gmt->tm_min,gmt->tm_sec,milli);
if (strlen(text) != len)
fatal(1,"unable to format current local time",NULL);
if (error >= 0.0) {
if (length < len+30)
fatal(0,"internal error calling format_time",NULL);
errno = 0;
sprintf(&text[len]," %c %.3f +/- %.3f secs",(offset > 0.0 ? '-' : '+'),
(offset > 0.0 ? offset : -offset),dispersion+error);
if (strlen(&text[len]) < 22)
fatal(1,"unable to format clock correction",NULL);
}
if (drifterr >= 0.0) {
len = strlen(text);
if (length < len+25)
fatal(0,"internal error calling format_time",NULL);
errno = 0;
sprintf(&text[len]," %c %.1f +/- %.1f ppm",
(drift > 0.0 ? '-' : '+'),1.0e6*fabs(drift),
1.0e6*drifterr);
if (strlen(&text[len]) < 17)
fatal(1,"unable to format clock correction",NULL);
}
if (strlen(text) >= length)
fatal(0,"internal error calling format_time",NULL);
}
double reset_clock (double offset, double error, int daemon) {
double absoff = (offset < 0 ? -offset : offset);
char text[50];
if (absoff > prompt) {
if (! daemon && ftty(stdin) && ftty(stdout)) {
printf("The time correction is %.3f +/- %.3f+%.3f seconds\n",
offset,dispersion,error);
printf("Do you want to correct the time anyway? ");
fflush(stdout);
if (toupper(getchar()) != 'Y') {
printf("OK - quitting\n");
fatal(0,NULL,NULL);
}
} else {
sprintf(text,"%.3f +/- %.3f+%.3f",offset,dispersion,error);
fatal(0,"time correction too large: %s seconds",text);
}
}
if (absoff < (daemon ? 0.5 : 1.0)*minerr) {
if (daemon ? verbose > 1 : verbose)
fprintf(stderr,"%s: correction %.3f +/- %.3f+%.3f secs - ignored\n",
argv0,offset,dispersion,error);
return 0.0;
} else if (absoff < 2.0*error) {
if (daemon ? verbose > 1 : verbose)
fprintf(stderr,
"%s: correction %.3f +/- %.3f+%.3f secs - suppressed\n",
argv0,offset,dispersion,error);
return 0.0;
}
adjust_time(offset,(action == action_reset ? 1 : 0),
(daemon ? 2.0*minerr : 0.0));
if (daemon ? verbose > 1 : verbose) {
format_time(text,50,0.0,-1.0,0.0,-1.0);
fprintf(stderr,
"%s: time changed by %.3f secs to %s +/- %.3f+%.3f\n",
argv0,offset,text,dispersion,error);
}
return offset;
}
double estimate_stats (int *a_total, int *a_index, data_record *record,
double correction, double *a_disp, double *a_when, double *a_offset,
double *a_error, double *a_drift, double *a_drifterr, int *a_wait,
int update) {
double weight, disp, when, offset, error, drift, drifterr,
now, e, w, x, y, z;
int total = *a_total, index = *a_index, wait = *a_wait, i;
char text[50];
for (i = 0; i < total; ++i) {
record[i].when += correction;
record[i].offset -= correction;
}
if (update) {
record[index].dispersion = *a_disp;
record[index].when = *a_when;
record[index].offset = *a_offset;
if (verbose > 1)
fprintf(stderr,"%s: corr=%.3f when=%.3f disp=%.3f off=%.3f",
argv0,correction,*a_when,*a_disp,*a_offset);
if (operation == op_listen) {
if (verbose > 1) fprintf(stderr,"\n");
record[index].error = minerr;
record[index].weight = 1.0;
} else {
if (verbose > 1) fprintf(stderr," err=%.3f\n",*a_error);
record[index].error = x = *a_error;
record[index].weight = 1.0/(x > minerr ? x*x : minerr*minerr);
}
if (++index >= count) index = 0;
*a_index = index;
if (++total > count) total = count;
*a_total = total;
if (verbose > 2)
fprintf(stderr,"corr=%.6f tot=%d ind=%d\n",correction,total,index);
}
if ((operation == op_listen && total < count && update) || total < 3) {
*a_drift = 0.0;
*a_drifterr = -1.0;
*a_wait = delay;
return *a_when;
}
disp = weight = when = offset = y = 0.0;
for (i = 0; i < total; ++i) {
weight += w = record[i].weight;
when += w*record[i].when;
offset += w*record[i].offset;
y += w*record[i].dispersion;
if (disp < record[i].dispersion)
disp = record[i].dispersion;
}
when /= weight;
offset /= weight;
y /= weight;
if (verbose > 2)
fprintf(stderr,"disp=%.6f wgt=%.3f when=%.6f off=%.6f\n",
disp,weight,when,offset);
error = drift = x = z = 0.0;
for (i = 0; i < total; ++i) {
w = record[i].weight/weight;
x += w*(record[i].when-when)*(record[i].when-when);
drift += w*(record[i].when-when)*(record[i].offset-offset);
z += w*(record[i].offset-offset)*(record[i].offset-offset);
error += w*record[i].error*record[i].error+
2.0*w*(record[i].dispersion-y)*(record[i].dispersion-y);
}
if (verbose > 2)
fprintf(stderr,"X2=%.3f XY=%.6f Y2=%.9f E2=%.9f ",x,drift,z,error);
z -= drift*drift/x;
if (verbose > 2) fprintf(stderr,"S2=%.9f\n",z);
if (! update) {
if (z > 1.0e6)
fatal(0,"stored data too unreliable for time estimation",NULL);
} else if (operation == op_client) {
e = error+disp*disp+minerr*minerr;
if (z > e) {
if (verbose || z >= maxerr*maxerr)
fprintf(stderr,
"%s: excessively high error %.3f > %.3f > %.3f\n",
argv0,sqrt(z),sqrt(e),sqrt(error));
if (total <= 1)
return 0.0;
else if (z < maxerr*maxerr) {
sprintf(text,"resetting on error %.3g > %.3g",
sqrt(z),sqrt(e));
log_message(text);
return 0.0;
} else
fatal(0,"incompatible (i.e. erroneous) timestamps",NULL);
} else if (z > error && verbose)
fprintf(stderr,
"%s: anomalously high error %.3f > %.3f, but < %.3f\n",
argv0,sqrt(z),sqrt(error),sqrt(e));
} else {
if (z > maxerr*maxerr)
fatal(0,"broadcasts too unreliable for time estimation",NULL);
}
drift /= x;
drifterr = ABSCISSA*sqrt(z/(x*total));
error = (operation == op_listen ? minerr : 0.0)+ABSCISSA*sqrt(z/total);
if (verbose > 2)
fprintf(stderr,"err=%.6f drift=%.6f+/-%.6f\n",error,drift,drifterr);
if (error+drifterr*delay > NTP_INSANITY)
fatal(0,"unable to get a reasonable drift estimate",NULL);
wait = delay;
x = (drift < 0.0 ? -drift : drift);
if (x*delay < 0.5*minerr) {
if (verbose > 2) fprintf(stderr,"Drift too small to correct\n");
} else if (x < 2.0*drifterr) {
if (verbose > 2)
fprintf(stderr,"Drift correction suppressed\n");
} else {
if ((z = drifterr*delay) < 0.5*minerr) z = 0.5*minerr;
wait = (x < z/delay ? delay : (int)(z/x+0.5));
wait = (int)(delay/(int)(delay/(double)wait+0.999)+0.999);
if (wait > delay)
fatal(0,"internal error in drift calculation",NULL);
if (update && (drift*wait > maxerr || wait < RESET_MIN)) {
sprintf(text,"%.6f+/-%.6f",drift,drifterr);
fatal(0,"drift correction too large: %s",text);
}
}
if (wait < *a_wait/2) wait = *a_wait/2;
if (wait > *a_wait*2) wait = *a_wait*2;
now = current_time(JAN_1970);
x = now-when;
offset += x*drift;
error += x*drifterr;
for (i = 0; i < total; ++i) {
x = now-record[i].when;
z = record[i].error+x*drifterr;
if (z < error) {
when = record[i].when;
offset = record[i].offset+x*drift;
error = z;
}
}
if (verbose > 2)
fprintf(stderr,"now=%.6f when=%.6f off=%.6f err=%.6f wait=%d\n",
now,when,offset,error,wait);
*a_disp = disp;
*a_when = when;
*a_offset = offset;
*a_error = error;
*a_drift = drift;
*a_drifterr = drifterr;
*a_wait = wait;
return now;
}
double correct_drift (double *a_when, double *a_offset, double drift) {
double d, x;
d = current_time(JAN_1970)-*a_when;
*a_when += d;
x = *a_offset+d*drift;
if (verbose > 2)
fprintf(stderr,"Correction %.6f @ %.6f off=%.6f ",x,*a_when,*a_offset);
if (d >= waiting && (x < 0.0 ? -x : x) >= 0.5*minerr) {
if (verbose > 2) fprintf(stderr,"performed\n");
adjust_time(x,(action == action_reset ? 1 : 0),0.5*minerr);
*a_offset = 0.0;
return x;
} else {
if (verbose > 2) fprintf(stderr,"ignored\n");
*a_offset = x;
return 0.0;
}
}
void handle_saving (int operation, int *total, int *index, int *cycle,
data_record *record, double *previous, double *when, double *correction) {
struct {
data_record record[COUNT_MAX];
double previous, when, correction;
int operation, delay, count, total, index, cycle, waiting;
} buffer;
double x, y;
int i, j;
if (savefile == NULL) return;
if (operation == save_read_only || operation == save_read_check) {
if (fread(&buffer,sizeof(buffer),1,savefile) != 1 || ferror(savefile)) {
if (ferror(savefile))
fatal(1,"unable to read record from daemon save file",NULL);
else if (verbose)
fprintf(stderr,"%s: bad daemon restart information\n",argv0);
return;
}
if (verbose > 2) {
fprintf(stderr,"Reading prev=%.6f when=%.6f corr=%.6f\n",
buffer.previous,buffer.when,buffer.correction);
fprintf(stderr,"op=%d dly=%d cnt=%d tot=%d ind=%d cyc=%d wait=%d\n",
buffer.operation,buffer.delay,buffer.count,buffer.total,
buffer.index,buffer.cycle,buffer.waiting);
if (buffer.total < COUNT_MAX)
for (i = 0; i < buffer.total; ++i)
fprintf(stderr,
"disp=%.6f wgt=%.3f when=%.6f off=%.6f err=%.6f\n",
buffer.record[i].dispersion,buffer.record[i].weight,
buffer.record[i].when,buffer.record[i].offset,
buffer.record[i].error);
}
if (buffer.operation == 0 && buffer.delay == 0 && buffer.count == 0) {
if (operation < 0)
fatal(0,"the daemon save file has been cleared",NULL);
if (verbose)
fprintf(stderr,"%s: restarting from a cleared file\n",argv0);
return;
}
if (operation == save_read_check) {
if (buffer.operation != operation || buffer.delay != delay ||
buffer.count != count) {
if (verbose)
fprintf(stderr,"%s: different parameters for restart\n",
argv0);
return;
}
if (buffer.total < 1 || buffer.total > count || buffer.index < 0 ||
buffer.index >= count || buffer.cycle < 0 ||
buffer.cycle >= count || buffer.correction < -maxerr ||
buffer.correction > maxerr || buffer.waiting < RESET_MIN ||
buffer.waiting > delay || buffer.previous > buffer.when ||
buffer.previous < buffer.when-count*delay ||
buffer.when >= *when) {
if (verbose)
fprintf(stderr,"%s: corrupted restart information\n",argv0);
return;
}
x = *when;
y = 0.0;
for (i = 0; i < buffer.total; ++i) {
if (buffer.record[i].dispersion < 0.0 ||
buffer.record[i].dispersion > maxerr ||
buffer.record[i].weight <= 0.0 ||
buffer.record[i].weight > 1.001/(minerr*minerr) ||
buffer.record[i].offset < -count*maxerr ||
buffer.record[i].offset > count*maxerr ||
buffer.record[i].error < 0.0 ||
buffer.record[i].error > maxerr) {
if (verbose)
fprintf(stderr,"%s: corrupted restart record\n",argv0);
return;
}
if (buffer.record[i].when < x) x = buffer.record[i].when;
if (buffer.record[i].when > y) y = buffer.record[i].when;
}
if (y > buffer.when || y-x < (buffer.total-1)*delay ||
y-x > (buffer.total-1)*count*delay) {
if (verbose)
fprintf(stderr,"%s: corrupted restart times\n",argv0);
return;
}
if (buffer.when < *when-count*delay) {
if (verbose)
fprintf(stderr,"%s: restart information too old\n",argv0);
return;
}
}
memcpy(record,buffer.record,sizeof(buffer.record));
*previous = buffer.previous;
*when = buffer.when;
*correction = buffer.correction;
*total = buffer.total;
*index = buffer.index;
*cycle = buffer.cycle;
waiting = buffer.waiting;
memset(&buffer,0,sizeof(buffer));
if (verbose > 1) {
fprintf(stderr,"%s: prev=%.3f when=%.3f corr=%.3f\n",
argv0,*previous,*when,*correction);
for (i = 0; i < *total; ++i) {
if ((j = i+*index-*total) < 0) j += *total;
fprintf(stderr,"%s: when=%.3f disp=%.3f off=%.3f",
argv0,record[j].when,record[j].dispersion,record[j].offset);
if (operation == op_client)
fprintf(stderr," err=%.3f\n",record[j].error);
else
fprintf(stderr,"\n");
}
}
} else if (operation == save_write) {
memcpy(buffer.record,record,sizeof(buffer.record));
buffer.previous = *previous;
buffer.when = *when;
buffer.correction = *correction;
buffer.operation = operation;
buffer.delay = delay;
buffer.count = count;
buffer.total = *total;
buffer.index = *index;
buffer.cycle = *cycle;
buffer.waiting = waiting;
if (fseek(savefile,0l,SEEK_SET) != 0 ||
fwrite(&buffer,sizeof(buffer),1,savefile) != 1 ||
fflush(savefile) != 0 || ferror(savefile))
fatal(1,"unable to write record to daemon save file",NULL);
if (verbose > 2) {
fprintf(stderr,"Writing prev=%.6f when=%.6f corr=%.6f\n",
*previous,*when,*correction);
fprintf(stderr,"op=%d dly=%d cnt=%d tot=%d ind=%d cyc=%d wait=%d\n",
operation,delay,count,*total,*index,*cycle,waiting);
if (*total < COUNT_MAX)
for (i = 0; i < *total; ++i)
fprintf(stderr,
"disp=%.6f wgt=%.3f when=%.6f off=%.6f err=%.6f\n",
record[i].dispersion,record[i].weight,
record[i].when,record[i].offset,record[i].error);
}
} else if (operation == save_clear) {
if (fseek(savefile,0l,SEEK_SET) != 0 ||
fwrite(&buffer,sizeof(buffer),1,savefile) != 1 ||
fflush(savefile) != 0 || ferror(savefile))
fatal(1,"unable to clear daemon save file",NULL);
} else
fatal(0,"internal error in handle_saving",NULL);
}
void query_savefile (void) {
double previous, when, correction = 0.0, offset = 0.0, error = -1.0,
drift = 0.0, drifterr = -1.0;
data_record record[COUNT_MAX];
int total = 0, index = 0, cycle = 0;
char text[100];
previous = when = current_time(JAN_1970);
if (verbose > 2) {
format_time(text,50,0.0,-1.0,0.0,-1.0);
fprintf(stderr,"Started=%.6f %s\n",when,text);
}
handle_saving(save_read_only,&total,&index,&cycle,record,&previous,&when,
&correction);
estimate_stats(&total,&index,record,correction,&dispersion,
&when,&offset,&error,&drift,&drifterr,&waiting,0);
format_time(text,100,offset,error,drift,drifterr);
printf("%s\n",text);
if (fclose(savefile)) fatal(1,"unable to close daemon save file",NULL);
if (verbose > 2) fprintf(stderr,"Stopped normally\n");
exit(EXIT_SUCCESS);
}
void run_daemon (char *hostnames[], int nhosts, int initial) {
double history[COUNT_MAX], started, previous, when, correction = 0.0,
weeble = 1.0, accepts = 0.0, rejects = 0.0, flushes = 0.0,
replicates = 0.0, skips = 0.0, offset = 0.0, error = -1.0,
drift = 0.0, drifterr = -1.0, maxoff = 0.0, x;
data_record record[COUNT_MAX];
int total = 0, index = 0, item = 0, rej_level = 0, rep_level = 0,
cycle = 0, retry = 1, i, j, k;
unsigned char transmit[NTP_PACKET_MIN];
ntp_data data;
char text[100];
started = previous = when = current_time(JAN_1970);
if (verbose > 2) {
format_time(text,50,0.0,-1.0,0.0,-1.0);
fprintf(stderr,"Started=%.6f %s\n",when,text);
}
if (initial) {
handle_saving(save_read_check,&total,&index,&cycle,record,
&previous,&when,&correction);
cycle = (nhosts > 0 ? cycle%nhosts : 0);
if (total > 0 && started-previous < delay) {
if (verbose > 2) fprintf(stderr,"Last packet too recent\n");
retry = 0;
}
if (verbose > 2)
fprintf(stderr,"prev=%.6f when=%.6f retry=%d\n",
previous,when,retry);
for (i = 0; i < nhosts; ++i) open_socket(i,hostnames[i],delay);
if (action != action_display) {
set_lock(1);
locked = 1;
}
}
dispersion = 0.0;
attempts = 0;
for (i = 0; i < count; ++i) history[i] = 0.0;
while (1) {
if (verbose > 2) fprintf(stderr,"item=%d rej=%d\n",item,rej_level);
x = current_time(JAN_1970)-started;
if (verbose &&
x/3600.0+accepts+rejects+flushes+replicates+skips >= weeble) {
weeble *= WEEBLE_FACTOR;
x -= 3600.0*(i = (int)(x/3600.0));
x -= 60.0*(j = (int)(x/60.0));
if (i > 0)
fprintf(stderr,"%s: after %d hours %d mins ",argv0,i,j);
else if (j > 0)
fprintf(stderr,"%s: after %d mins %.0f secs ",argv0,j,x);
else
fprintf(stderr,"%s: after %.1f secs ",argv0,x);
fprintf(stderr,"acc. %.0f rej. %.0f flush %.0f",
accepts,rejects,flushes);
if (operation == op_listen)
fprintf(stderr," rep. %.0f skip %.0f",replicates,skips);
fprintf(stderr," max.off. %.3f corr. %.3f\n",maxoff,correction);
format_time(text,100,offset,error,drift,drifterr);
fprintf(stderr,"%s: %s\n",argv0,text);
maxoff = 0.0;
}
if (current_time(JAN_1970)-previous > count*delay) {
if (verbose)
fprintf(stderr,"%s: no packets in too long a period\n",argv0);
return;
}
if (operation == op_listen) {
flushes += flush_socket(0);
if (read_packet(0,&data,&offset,&error)) {
++rejects;
if (++rej_level > count)
fatal(0,"too many bad or lost packets",NULL);
if (action != action_display && drifterr >= 0.0) {
correction += correct_drift(&when,&offset,drift);
handle_saving(save_write,&total,&index,&cycle,record,
&previous,&when,&correction);
}
continue;
}
if ((rej_level -= (count < 5 ? count : 5)) < 0) rej_level = 0;
x = data.transmit;
for (i = 0; i < count; ++i)
if (x == history[i]) {
++replicates;
if (++rep_level > ETHERNET_MAX)
fatal(0,"too many replicated packets",NULL);
goto continue1;
}
rep_level = 0;
history[item] = x;
if (++item >= count) item = 0;
when = data.current;
if (! retry && when < previous+delay) {
if (verbose > 2) fprintf(stderr,"Skipping too recent packet\n");
++skips;
continue;
}
retry = 0;
if (verbose > 2)
fprintf(stderr,"Offset=%.6f @ %.6f disp=%.6f\n",
offset,when,dispersion);
} else {
if (! retry) {
if (verbose > 2) fprintf(stderr,"Sleeping for %d\n",waiting);
do_nothing(waiting);
}
make_packet(&data,NTP_CLIENT);
outgoing[item] = data.transmit;
if (++item >= 2*count) item = 0;
if (attempts < 2*count) ++attempts;
if (verbose > 2) {
fprintf(stderr,"Outgoing packet on socket %d:\n",cycle);
display_data(&data);
}
pack_ntp(transmit,NTP_PACKET_MIN,&data);
if (verbose > 2) display_packet(transmit,NTP_PACKET_MIN);
flushes += flush_socket(cycle);
write_socket(cycle,transmit,NTP_PACKET_MIN);
k = read_packet(cycle,&data,&offset,&error);
if (++cycle >= nhosts) cycle = 0;
if (! k)
when = (data.originate+data.current)/2.0;
else if (action != action_display && drifterr >= 0.0) {
correction += correct_drift(&when,&offset,drift);
handle_saving(save_write,&total,&index,&cycle,record,
&previous,&when,&correction);
}
if (! k && ! retry && when < previous+delay-2) {
if (verbose)
fprintf(stderr,"%s: packets out of order on socket %d\n",
argv0,cycle);
k = 1;
}
if (! k && data.current-data.originate > maxerr) {
if (verbose)
fprintf(stderr,
"%s: very slow response rejected on socket %d\n",
argv0,cycle);
k = 1;
}
if (k) {
++rejects;
if (++rej_level > count)
fatal(0,"too many bad or lost packets",NULL);
else {
retry = 1;
continue;
}
} else
retry = 0;
if ((rej_level -= (count < 5 ? count : 5)) < 0) rej_level = 0;
if (verbose > 2)
fprintf(stderr,"Offset=%.6f+/-%.6f @ %.6f disp=%.6f\n",
offset,error,when,dispersion);
}
handle_saving(save_clear,&total,&index,&cycle,record,&previous,&when,
&correction);
++accepts;
dispersion = data.dispersion;
previous = when =
estimate_stats(&total,&index,record,correction,&dispersion,
&when,&offset,&error,&drift,&drifterr,&waiting,1);
if (verbose > 2) {
fprintf(stderr,"tot=%d ind=%d dis=%.3f when=%.3f off=%.3f ",
total,index,dispersion,when,offset);
fprintf(stderr,"err=%.3f wait=%d\n",error,waiting);
}
if (when == 0.0) return;
x = (maxoff < 0.0 ? -maxoff : maxoff);
if ((offset < 0.0 ? -offset : offset) > x) maxoff = offset;
correction = 0.0;
if (operation == op_client || accepts >= count) {
if (action == action_display) {
format_time(text,100,offset,error,drift,drifterr);
printf("%s\n",text);
} else {
x = reset_clock(offset,error,1);
correction += x;
offset -= x;
}
} else
waiting = delay;
handle_saving(save_write,&total,&index,&cycle,record,&previous,&when,
&correction);
while (when < previous+delay-waiting) {
do_nothing(waiting);
if (action == action_display)
when += waiting;
else {
correction += correct_drift(&when,&offset,drift);
handle_saving(save_write,&total,&index,&cycle,record,
&previous,&when,&correction);
}
}
continue1: ;
}
}
void run_client (char *hostnames[], int nhosts) {
double history[COUNT_MAX], guesses[COUNT_MAX], offset, error, deadline,
a, b, x, y;
int accepts = 0, rejects = 0, flushes = 0, replicates = 0, cycle = 0, k;
unsigned char transmit[NTP_PACKET_MIN];
ntp_data data;
char text[100];
if (verbose > 2) {
format_time(text,50,0.0,-1.0,0.0,-1.0);
fprintf(stderr,"Started=%.6f %s\n",current_time(JAN_1970),text);
}
for (k = 0; k < nhosts; ++k) open_socket(k,hostnames[k],delay);
if (action != action_display) {
set_lock(1);
locked = 1;
}
attempts = 0;
deadline = current_time(JAN_1970)+delay;
if (operation == op_listen) {
while (accepts < count) {
if (current_time(JAN_1970) > deadline)
fatal(0,"not enough valid broadcasts received in time",NULL);
flushes += flush_socket(0);
if (read_packet(0,&data,&x,&y)) {
if (++rejects > count)
fatal(0,"too many bad or lost packets",NULL);
else
continue;
} else {
a = data.transmit;
for (k = 0; k < accepts; ++k)
if (a == history[k]) {
if (++replicates > ETHERNET_MAX*count)
fatal(0,"too many replicated packets",NULL);
goto continue1;
}
history[accepts] = a;
guesses[accepts++] = x;
}
if (verbose > 2)
fprintf(stderr,"Offset=%.6f disp=%.6f\n",x,dispersion);
else if (verbose > 1)
fprintf(stderr,"%s: offset=%.3f disp=%.3f\n",
argv0,x,dispersion);
for (k = accepts-2; k >= 0; --k)
if (guesses[k] < guesses[k+1])
break;
else {
x = guesses[k];
guesses[k] = guesses[k+1];
guesses[k+1] = x;
}
continue1: ;
}
offset = guesses[0];
error = minerr+guesses[count <= 5 ? count-1 : 5]-offset;
if (verbose > 2)
fprintf(stderr,"accepts=%d rejects=%d flushes=%d replicates=%d\n",
accepts,rejects,flushes,replicates);
} else {
offset = 0.0;
error = NTP_INSANITY;
while (accepts < count && attempts < 2*count) {
if (current_time(JAN_1970) > deadline)
fatal(0,"not enough valid responses received in time",NULL);
make_packet(&data,NTP_CLIENT);
outgoing[attempts++] = data.transmit;
if (verbose > 2) {
fprintf(stderr,"Outgoing packet on socket %d:\n",cycle);
display_data(&data);
}
pack_ntp(transmit,NTP_PACKET_MIN,&data);
if (verbose > 2) display_packet(transmit,NTP_PACKET_MIN);
flushes += flush_socket(cycle);
write_socket(cycle,transmit,NTP_PACKET_MIN);
if (read_packet(cycle,&data,&x,&y)) {
if (++rejects > count)
fatal(0,"too many bad or lost packets",NULL);
else
continue;
} else
++accepts;
if (++cycle >= nhosts) cycle = 0;
if (verbose > 2)
fprintf(stderr,"Offset=%.6f+/-%.6f disp=%.6f\n",x,y,dispersion);
else if (verbose > 1)
fprintf(stderr,"%s: offset=%.3f+/-%.3f disp=%.3f\n",
argv0,x,y,dispersion);
if ((a = x-offset) < 0.0) a = -a;
if (accepts <= 1) a = 0.0;
b = error+y;
if (y < error) {
offset = x;
error = y;
}
if (verbose > 2)
fprintf(stderr,"best=%.6f+/-%.6f\n",offset,error);
if (a > b) {
sprintf(text,"%d",cycle);
fatal(0,"inconsistent times got from NTP server on socket %s",
text);
}
if (error <= minerr) break;
}
if (verbose > 2)
fprintf(stderr,"accepts=%d rejects=%d flushes=%d\n",
accepts,rejects,flushes);
}
for (k = 0; k < nhosts; ++k) close_socket(k);
if (accepts == 0) fatal(0,"no acceptable packets received",NULL);
if (error > NTP_INSANITY)
fatal(0,"unable to get a reasonable time estimate",NULL);
if (verbose > 2)
fprintf(stderr,"Correction: %.6f +/- %.6f disp=%.6f\n",
offset,error,dispersion);
if (action == action_display) {
format_time(text,75,offset,error,0.0,-1.0);
printf("%s\n",text);
} else
(void)reset_clock(offset,error,0);
if (locked) set_lock(0);
if (verbose > 2) fprintf(stderr,"Stopped normally\n");
exit(EXIT_SUCCESS);
}
int main (int argc, char *argv[]) {
char *hostnames[MAX_SOCKETS], *savename = NULL;
int daemon = 0, nhosts = 0, help = 0, args = argc-1, k;
char c;
if (argv[0] == NULL || argv[0][0] == '\0')
argv0 = "sntp";
else if ((argv0 = strrchr(argv[0],'/')) != NULL)
++argv0;
else
argv0 = argv[0];
setvbuf(stdout,NULL,_IOLBF,BUFSIZ);
setvbuf(stderr,NULL,_IOLBF,BUFSIZ);
if (INT_MAX < 2147483647) fatal(0,"sntp requires >= 32-bit ints",NULL);
if (DBL_EPSILON > 1.0e-13)
fatal(0,"sntp requires doubles with eps <= 1.0e-13",NULL);
for (k = 0; k < MAX_SOCKETS; ++k) hostnames[k] = NULL;
while (argc > 1) {
k = 1;
if (strcmp(argv[1],"-4") == 0)
preferred_family(PREF_FAM_INET);
else if (strcmp(argv[1],"-6") == 0)
preferred_family(PREF_FAM_INET6);
else if (strcmp(argv[1],"-q") == 0 && action == 0)
action = action_query;
else if (strcmp(argv[1],"-r") == 0 && action == 0)
action = action_reset;
else if (strcmp(argv[1],"-a") == 0 && action == 0)
action = action_adjust;
else if (strcmp(argv[1],"-l") == 0 && lockname == NULL && argc > 2) {
lockname = argv[2];
k = 2;
} else if ((strcmp(argv[1],"-x") == 0) &&
daemon == 0) {
if (argc > 2 && sscanf(argv[2],"%d%c",&daemon,&c) == 1) {
if (daemon < 1 || daemon > 1440)
fatal(0,"%s option value out of range",argv[1]);
k = 2;
} else
daemon = 300;
} else if (strcmp(argv[1],"-f") == 0 && savename == NULL && argc > 2) {
savename = argv[2];
k = 2;
} else if ((strcmp(argv[1],"--help") == 0 ||
strcmp(argv[1],"-h") == 0 || strcmp(argv[1],"-?") == 0) &&
help == 0)
help = 1;
else if (strcmp(argv[1],"-v") == 0 && verbose == 0)
verbose = 1;
else if (strcmp(argv[1],"-V") == 0 && verbose == 0)
verbose = 2;
else if (strcmp(argv[1],"-W") == 0 && verbose == 0)
verbose = 3;
else if (strcmp(argv[1],"-e") == 0 && minerr == 0.0 && argc > 2) {
if (sscanf(argv[2],"%lf%c",&minerr,&c) != 1) syntax(1);
if (minerr <= 0.000999999 || minerr > 1.0)
fatal(0,"%s option value out of range","-e");
k = 2;
} else if (strcmp(argv[1],"-E") == 0 && maxerr == 0.0 && argc > 2) {
if (sscanf(argv[2],"%lf%c",&maxerr,&c) != 1) syntax(1);
if (maxerr < 1.0 || maxerr > 60.0)
fatal(0,"%s option value out of range","-E");
k = 2;
} else if (strcmp(argv[1],"-P") == 0 && prompt == 0.0 && argc > 2) {
if (strcmp(argv[2],"no") == 0)
prompt = (double)INT_MAX;
else {
if (sscanf(argv[2],"%lf%c",&prompt,&c) != 1) syntax(1);
if (prompt < 1.0 || prompt > 3600.0)
fatal(0,"%s option value out of range","-p");
}
k = 2;
} else if (strcmp(argv[1],"-d") == 0 && delay == 0 && argc > 2) {
if (sscanf(argv[2],"%d%c",&delay,&c) != 1) syntax(1);
if (delay < 1 || delay > 3600)
fatal(0,"%s option value out of range","-d");
k = 2;
} else if (strcmp(argv[1],"-c") == 0 && count == 0 && argc > 2) {
if (sscanf(argv[2],"%d%c",&count,&c) != 1) syntax(1);
if (count < 1 || count > COUNT_MAX)
fatal(0,"%s option value out of range","-c");
k = 2;
} else
break;
argc -= k;
argv += k;
}
if (action == action_query) {
if (argc != 1 || minerr != 0.0 || maxerr != 0.0 || count != 0 ||
delay != 0 || daemon != 0 || prompt != 0.0 || lockname != NULL)
syntax(1);
} else {
if (argc < 1 || argc > MAX_SOCKETS || (daemon != 0 && delay != 0))
syntax(1);
if ((prompt || lockname != NULL) &&
action != action_reset && action != action_adjust)
syntax(1);
if (count > 0 && count < argc-1)
fatal(0,"-c value less than number of addresses",NULL);
if (argc > 1) {
operation = op_client;
for (k = 1; k < argc; ++k) {
if (argv[k][0] == '\0' || argv[k][0] == '-')
fatal(0,"invalid Internet address '%s'",argv[k]);
hostnames[k-1] = argv[k];
}
nhosts = argc-1;
} else {
operation = op_listen;
nhosts = 0;
}
if (action == 0) action = action_display;
if (minerr <= 0.0) minerr = (operation == op_listen ? 0.5 : 0.1);
if (maxerr <= 0.0) maxerr = 5.0;
if (count == 0) count = (argc-1 < 5 ? 5 : argc-1);
if ((argc == 1 || (daemon != 0 && action != action_query)) && count < 5)
fatal(0,"at least 5 packets needed in this mode",NULL);
if ((action == action_reset || action == action_adjust) &&
lockname == NULL)
lockname = LOCKNAME;
if (daemon != 0) {
if (minerr >= maxerr || maxerr >= daemon)
fatal(0,"values not in order -e < -E < -x",NULL);
waiting = delay = daemon *= 60;
} else {
if (savename != NULL)
fatal(0,"-f can be specified only with -x",NULL);
if (delay == 0)
delay = (operation == op_listen ? 300 :
(2*count >= 15 ? 2*count+1 :15));
if (operation == op_listen) {
if (minerr >= maxerr || maxerr >= delay/count)
fatal(0,"values not in order -e < -E < -d/-c",NULL);
} else {
if (minerr >= maxerr || maxerr >= delay)
fatal(0,"values not in order -e < -E < -d",NULL);
}
if (2*count >= delay) fatal(0,"-c must be less than half -d",NULL);
waiting = delay/count;
}
if (prompt == 0.0) prompt = 30.0;
}
if ((daemon || action == action_query) && savename == NULL)
savename = SAVENAME;
if (help) syntax(args == 1);
if (verbose) {
fprintf(stderr,"%s options: a=%d v=%d e=%.3f E=%.3f P=%.3f\n",
argv0,action,verbose,minerr,maxerr,prompt);
fprintf(stderr," d=%d c=%d %c=%d op=%d l=%s f=%s",
delay,count,'x',daemon,operation,
(lockname == NULL ? "" : lockname),
(savename == NULL ? "" : savename));
for (k = 0; k < MAX_SOCKETS; ++k)
if (hostnames[k] != NULL) fprintf(stderr," %s",hostnames[k]);
fprintf(stderr,"\n");
}
if (nhosts == 0) nhosts = 1;
if (action == action_query) {
if (savename == NULL || savename[0] == '\0')
fatal(0,"no daemon save file specified",NULL);
else if ((savefile = fopen(savename,"rb")) == NULL)
fatal(0,"unable to open the daemon save file",NULL);
query_savefile();
} else if (daemon != 0) {
if (savename != NULL && savename[0] != '\0' &&
(savefile = fopen(savename,"rb+")) == NULL &&
(savefile = fopen(savename,"wb+")) == NULL)
fatal(0,"unable to open the daemon save file",NULL);
run_daemon(hostnames,nhosts,1);
while (1) run_daemon(hostnames,nhosts,0);
} else
run_client(hostnames,nhosts);
fatal(0,"internal error at end of main",NULL);
return EXIT_FAILURE;
}