BSD 4_3_Tahoe release
[unix-history] / usr / src / etc / timed / timed.c
/*
* Copyright (c) 1985 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1985 Regents of the University of California.\n\
All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)timed.c 2.14 (Berkeley) 6/18/88";
#endif /* not lint */
#include "globals.h"
#define TSPTYPES
#include <protocols/timed.h>
#include <net/if.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <setjmp.h>
int id;
int trace;
int sock, sock_raw = -1;
int status = 0;
int backoff;
int slvcount; /* no. of slaves controlled by master */
int machup;
u_short sequence; /* sequence number */
long delay1;
long delay2;
long random();
char hostname[MAXHOSTNAMELEN];
struct host hp[NHOSTS];
char tracefile[] = "/usr/adm/timed.log";
FILE *fd;
jmp_buf jmpenv;
struct netinfo *nettab = NULL;
int nslavenets; /* Number of networks were I could be a slave */
int nmasternets; /* Number of networks were I could be a master */
int nignorednets; /* Number of ignored networks */
int nnets; /* Number of networks I am connected to */
struct netinfo *slavenet;
struct netinfo *firstslavenet();
int Mflag;
int justquit = 0;
struct nets {
char *name;
long net;
struct nets *next;
} *nets = (struct nets *)0;
/*
* The timedaemons synchronize the clocks of hosts in a local area network.
* One daemon runs as master, all the others as slaves. The master
* performs the task of computing clock differences and sends correction
* values to the slaves.
* Slaves start an election to choose a new master when the latter disappears
* because of a machine crash, network partition, or when killed.
* A resolution protocol is used to kill all but one of the masters
* that happen to exist in segments of a partitioned network when the
* network partition is fixed.
*
* Authors: Riccardo Gusella & Stefano Zatti
*/
main(argc, argv)
int argc;
char **argv;
{
int on;
int ret;
long seed;
int nflag, iflag;
struct timeval time;
struct servent *srvp;
long casual();
char *date();
int n;
int flag;
char buf[BUFSIZ];
struct ifconf ifc;
struct ifreq ifreq, *ifr;
register struct netinfo *ntp;
struct netinfo *ntip;
struct netinfo *savefromnet;
struct sockaddr_in server;
u_short port;
uid_t getuid();
#ifdef lint
ntip = NULL;
#endif
Mflag = 0;
on = 1;
backoff = 1;
trace = OFF;
nflag = OFF;
iflag = OFF;
openlog("timed", LOG_CONS|LOG_PID, LOG_DAEMON);
if (getuid() != 0) {
fprintf(stderr, "Timed: not superuser\n");
exit(1);
}
while (--argc > 0 && **++argv == '-') {
(*argv)++;
do {
switch (**argv) {
case 'M':
Mflag = 1;
break;
case 't':
trace = ON;
break;
case 'n':
argc--, argv++;
if (iflag) {
fprintf(stderr,
"timed: -i and -n make no sense together\n");
} else {
nflag = ON;
addnetname(*argv);
}
while (*(++(*argv)+1)) ;
break;
case 'i':
argc--, argv++;
if (nflag) {
fprintf(stderr,
"timed: -i and -n make no sense together\n");
} else {
iflag = ON;
addnetname(*argv);
}
while (*(++(*argv)+1)) ;
break;
default:
fprintf(stderr, "timed: -%c: unknown option\n",
**argv);
break;
}
} while (*++(*argv));
}
#ifndef DEBUG
if (fork())
exit(0);
{ int s;
for (s = getdtablesize(); s >= 0; --s)
(void) close(s);
(void) open("/dev/null", 0);
(void) dup2(0, 1);
(void) dup2(0, 2);
s = open("/dev/tty", 2);
if (s >= 0) {
(void) ioctl(s, TIOCNOTTY, (char *)0);
(void) close(s);
}
}
#endif
if (trace == ON) {
fd = fopen(tracefile, "w");
setlinebuf(fd);
fprintf(fd, "Tracing started on: %s\n\n",
date());
}
srvp = getservbyname("timed", "udp");
if (srvp == 0) {
syslog(LOG_CRIT, "unknown service 'timed/udp'");
exit(1);
}
port = srvp->s_port;
server.sin_port = srvp->s_port;
server.sin_family = AF_INET;
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
syslog(LOG_ERR, "socket: %m");
exit(1);
}
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&on,
sizeof(on)) < 0) {
syslog(LOG_ERR, "setsockopt: %m");
exit(1);
}
if (bind(sock, &server, sizeof(server))) {
if (errno == EADDRINUSE)
syslog(LOG_ERR, "server already running");
else
syslog(LOG_ERR, "bind: %m");
exit(1);
}
/* choose a unique seed for random number generation */
(void)gettimeofday(&time, (struct timezone *)0);
seed = time.tv_sec + time.tv_usec;
srandom(seed);
sequence = random(); /* initial seq number */
/* rounds kernel variable time to multiple of 5 ms. */
time.tv_sec = 0;
time.tv_usec = -((time.tv_usec/1000) % 5) * 1000;
(void)adjtime(&time, (struct timeval *)0);
id = getpid();
if (gethostname(hostname, sizeof(hostname) - 1) < 0) {
syslog(LOG_ERR, "gethostname: %m");
exit(1);
}
hp[0].name = hostname;
if (nflag || iflag) {
struct netent *getnetent();
struct netent *n;
struct nets *np;
for ( np = nets ; np ; np = np->next) {
n = getnetbyname(np->name);
if (n == NULL) {
syslog(LOG_ERR, "getnetbyname: unknown net %s",
np->name);
exit(1);
}
np->net = n->n_net;
}
}
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctl(sock, SIOCGIFCONF, (char *)&ifc) < 0) {
syslog(LOG_ERR, "get interface configuration: %m");
exit(1);
}
n = ifc.ifc_len/sizeof(struct ifreq);
ntp = NULL;
for (ifr = ifc.ifc_req; n > 0; n--, ifr++) {
if (ifr->ifr_addr.sa_family != AF_INET)
continue;
ifreq = *ifr;
if (ntp == NULL)
ntp = (struct netinfo *)malloc(sizeof(struct netinfo));
ntp->my_addr =
((struct sockaddr_in *)&ifreq.ifr_addr)->sin_addr;
if (ioctl(sock, SIOCGIFFLAGS,
(char *)&ifreq) < 0) {
syslog(LOG_ERR, "get interface flags: %m");
continue;
}
if ((ifreq.ifr_flags & IFF_UP) == 0 ||
((ifreq.ifr_flags & IFF_BROADCAST) == 0 &&
(ifreq.ifr_flags & IFF_POINTOPOINT) == 0)) {
continue;
}
if (ifreq.ifr_flags & IFF_BROADCAST)
flag = 1;
else
flag = 0;
if (ioctl(sock, SIOCGIFNETMASK,
(char *)&ifreq) < 0) {
syslog(LOG_ERR, "get netmask: %m");
continue;
}
ntp->mask = ((struct sockaddr_in *)
&ifreq.ifr_addr)->sin_addr.s_addr;
if (flag) {
if (ioctl(sock, SIOCGIFBRDADDR,
(char *)&ifreq) < 0) {
syslog(LOG_ERR, "get broadaddr: %m");
continue;
}
ntp->dest_addr = *(struct sockaddr_in *)&ifreq.ifr_broadaddr;
} else {
if (ioctl(sock, SIOCGIFDSTADDR,
(char *)&ifreq) < 0) {
syslog(LOG_ERR, "get destaddr: %m");
continue;
}
ntp->dest_addr = *(struct sockaddr_in *)&ifreq.ifr_dstaddr;
}
ntp->dest_addr.sin_port = port;
if (nflag || iflag) {
u_long addr, mask;
struct nets *n;
addr = ntohl(ntp->dest_addr.sin_addr.s_addr);
mask = ntohl(ntp->mask);
while ((mask & 1) == 0) {
addr >>= 1;
mask >>= 1;
}
for (n = nets ; n ; n = n->next)
if (addr == n->net)
break;
if (nflag && !n || iflag && n)
continue;
}
ntp->net = ntp->mask & ntp->dest_addr.sin_addr.s_addr;
ntp->next = NULL;
if (nettab == NULL) {
nettab = ntp;
} else {
ntip->next = ntp;
}
ntip = ntp;
ntp = NULL;
}
if (ntp)
(void) free((char *)ntp);
if (nettab == NULL) {
syslog(LOG_ERR, "No network usable");
exit(1);
}
for (ntp = nettab; ntp != NULL; ntp = ntp->next)
lookformaster(ntp);
setstatus();
/*
* Take care of some basic initialization.
*/
/* us. delay to be used in response to broadcast */
delay1 = casual((long)10000, 200000);
/* election timer delay in secs. */
delay2 = casual((long)MINTOUT, (long)MAXTOUT);
if (Mflag) {
/*
* number (increased by 1) of slaves controlled by master:
* used in master.c, candidate.c, networkdelta.c, and
* correct.c
*/
slvcount = 1;
ret = setjmp(jmpenv);
switch (ret) {
case 0:
makeslave(firstslavenet());
setstatus();
break;
case 1:
/* Just lost our master */
setstatus();
slavenet->status = election(slavenet);
checkignorednets();
setstatus();
if (slavenet->status == MASTER)
makeslave(firstslavenet());
else
makeslave(slavenet);
setstatus();
break;
case 2:
/* Just been told to quit */
fromnet->status = SLAVE;
setstatus();
savefromnet = fromnet;
rmnetmachs(fromnet);
checkignorednets();
if (slavenet)
makeslave(slavenet);
else
makeslave(savefromnet);
setstatus();
justquit = 1;
break;
default:
/* this should not happen */
syslog(LOG_ERR, "Attempt to enter invalid state");
break;
}
if (status & MASTER) {
/* open raw socket used to measure time differences */
if (sock_raw == -1) {
sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock_raw < 0) {
syslog(LOG_ERR, "opening raw socket: %m");
exit (1);
}
}
} else {
/* sock_raw is not being used now */
if (sock_raw != -1) {
(void)close(sock_raw);
sock_raw = -1;
}
}
if (status == MASTER)
master();
else
slave();
} else {
/* if Mflag is not set timedaemon is forced to act as a slave */
status = SLAVE;
if (setjmp(jmpenv)) {
setstatus();
checkignorednets();
}
makeslave(firstslavenet());
for (ntp = nettab; ntp != NULL; ntp = ntp->next)
if (ntp->status == MASTER)
ntp->status = IGNORE;
setstatus();
slave();
}
}
/*
* Try to become master over ignored nets..
*/
checkignorednets()
{
register struct netinfo *ntp;
for (ntp = nettab; ntp != NULL; ntp = ntp->next)
if (ntp->status == IGNORE)
lookformaster(ntp);
}
lookformaster(ntp)
register struct netinfo *ntp;
{
struct tsp resp, conflict, *answer, *readmsg(), *acksend();
struct timeval time;
char mastername[MAXHOSTNAMELEN];
struct sockaddr_in masteraddr;
ntp->status = SLAVE;
/* look for master */
resp.tsp_type = TSP_MASTERREQ;
(void)strcpy(resp.tsp_name, hostname);
answer = acksend(&resp, &ntp->dest_addr, (char *)ANYADDR,
TSP_MASTERACK, ntp);
if (answer == NULL) {
/*
* Various conditions can cause conflict: race between
* two just started timedaemons when no master is
* present, or timedaemon started during an election.
* Conservative approach is taken: give up and became a
* slave postponing election of a master until first
* timer expires.
*/
time.tv_sec = time.tv_usec = 0;
answer = readmsg(TSP_MASTERREQ, (char *)ANYADDR,
&time, ntp);
if (answer != NULL) {
ntp->status = SLAVE;
return;
}
time.tv_sec = time.tv_usec = 0;
answer = readmsg(TSP_MASTERUP, (char *)ANYADDR,
&time, ntp);
if (answer != NULL) {
ntp->status = SLAVE;
return;
}
time.tv_sec = time.tv_usec = 0;
answer = readmsg(TSP_ELECTION, (char *)ANYADDR,
&time, ntp);
if (answer != NULL) {
ntp->status = SLAVE;
return;
}
ntp->status = MASTER;
} else {
(void)strcpy(mastername, answer->tsp_name);
masteraddr = from;
/*
* If network has been partitioned, there might be other
* masters; tell the one we have just acknowledged that
* it has to gain control over the others.
*/
time.tv_sec = 0;
time.tv_usec = 300000;
answer = readmsg(TSP_MASTERACK, (char *)ANYADDR, &time,
ntp);
/*
* checking also not to send CONFLICT to ack'ed master
* due to duplicated MASTERACKs
*/
if (answer != NULL &&
strcmp(answer->tsp_name, mastername) != 0) {
conflict.tsp_type = TSP_CONFLICT;
(void)strcpy(conflict.tsp_name, hostname);
if (acksend(&conflict, &masteraddr, mastername,
TSP_ACK, (struct netinfo *)NULL) == NULL) {
syslog(LOG_ERR,
"error on sending TSP_CONFLICT");
exit(1);
}
}
}
}
/*
* based on the current network configuration, set the status, and count
* networks;
*/
setstatus()
{
register struct netinfo *ntp;
status = 0;
nmasternets = nslavenets = nnets = nignorednets = 0;
if (trace)
fprintf(fd, "Net status:\n");
for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
switch ((int)ntp->status) {
case MASTER:
nmasternets++;
break;
case SLAVE:
nslavenets++;
break;
case IGNORE:
nignorednets++;
break;
}
if (trace) {
fprintf(fd, "\t%-16s", inet_ntoa(ntp->net));
switch ((int)ntp->status) {
case MASTER:
fprintf(fd, "MASTER\n");
break;
case SLAVE:
fprintf(fd, "SLAVE\n");
break;
case IGNORE:
fprintf(fd, "IGNORE\n");
break;
default:
fprintf(fd, "invalid state %d\n",(int)ntp->status);
break;
}
}
nnets++;
status |= ntp->status;
}
status &= ~IGNORE;
if (trace)
fprintf(fd,
"\tnets = %d, masters = %d, slaves = %d, ignored = %d\n",
nnets, nmasternets, nslavenets, nignorednets);
}
makeslave(net)
struct netinfo *net;
{
register struct netinfo *ntp;
for (ntp = nettab; ntp != NULL; ntp = ntp->next)
if (ntp->status == SLAVE && ntp != net)
ntp->status = IGNORE;
slavenet = net;
}
struct netinfo *
firstslavenet()
{
register struct netinfo *ntp;
for (ntp = nettab; ntp != NULL; ntp = ntp->next)
if (ntp->status == SLAVE)
return (ntp);
return ((struct netinfo *)0);
}
/*
* `casual' returns a random number in the range [inf, sup]
*/
long
casual(inf, sup)
long inf;
long sup;
{
float value;
value = (float)(random() & 0x7fffffff) / 0x7fffffff;
return(inf + (sup - inf) * value);
}
char *
date()
{
char *ctime();
struct timeval tv;
(void)gettimeofday(&tv, (struct timezone *)0);
return (ctime(&tv.tv_sec));
}
addnetname(name)
char *name;
{
register struct nets **netlist = &nets;
while (*netlist)
netlist = &((*netlist)->next);
*netlist = (struct nets *)malloc(sizeof **netlist);
if (*netlist == (struct nets *)0) {
syslog(LOG_ERR, "malloc failed");
exit(1);
}
bzero((char *)*netlist, sizeof(**netlist));
(*netlist)->name = name;
}