NetBSD/usr.sbin/xntp/xntpd/ntp_peer.c

674 lines
15 KiB
C

/* $NetBSD: ntp_peer.c,v 1.4 1998/08/12 14:11:53 christos Exp $ */
/*
* ntp_peer.c - management of data maintained for peer associations
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include "ntpd.h"
#include "ntp_stdlib.h"
/*
* These routines manage the allocation of memory to peer structures
* and the maintenance of the peer hash table. The two main entry
* points are findpeer(), which looks for corresponding peer data
* in the peer list, newpeer(), which allocates a new peer structure
* and adds it to the list, and unpeer(), which demobilizes the association
* and deallocates the structure.
*/
/*
* The peer hash table (imported by the protocol module).
*/
struct peer *peer_hash[HASH_SIZE];
int peer_hash_count[HASH_SIZE]; /* count of peers in each bucket */
/*
* The association ID hash table. Used for lookups by association ID
*/
struct peer *assoc_hash[HASH_SIZE];
int assoc_hash_count[HASH_SIZE];
/*
* The free list. Clean structures only, please.
*/
struct peer *peer_free;
int peer_free_count;
/*
* Association ID. We initialize this value randomly, the assign a new
* value every time the peer structure is incremented.
*/
u_short current_association_ID;
/*
* Memory allocation watermarks.
*/
#define INIT_PEER_ALLOC 15 /* initialize space for 15 peers */
#define INC_PEER_ALLOC 5 /* when we run out, add 5 more */
/*
* Miscellaneous statistic counters which may be queried.
*/
u_long peer_timereset; /* time stat counters were zeroed */
u_long findpeer_calls; /* number of calls to findpeer */
u_long assocpeer_calls; /* number of calls to findpeerbyassoc */
u_long peer_allocations; /* number of allocations from the free list */
u_long peer_demobilizations; /* number of structs freed to free list */
int total_peer_structs; /* number of peer structs in circulation */
/*
* default interface. Imported from the io module.
*/
extern struct interface *any_interface;
/*
* Timer queue and current time. Imported from the timer module.
*/
extern u_long current_time;
extern struct event timerqueue[];
/*
* Our initial allocation of peer space
*/
static struct peer init_peer_alloc[INIT_PEER_ALLOC];
/*
* Initialization data. When configuring peers at initialization time,
* we try to get their poll update timers initialized to different values
* to prevent us from sending big clumps of data all at once.
*/
u_long init_peer_starttime;
extern int initializing;
extern int debug;
static void getmorepeermem P((void));
/*
* init_peer - initialize peer data structures and counters
*
* N.B. We use the random number routine in here. It had better be
* initialized prior to getting here.
*/
void
init_peer()
{
register int i;
/*
* Clear hash table and counters.
*/
for (i = 0; i < HASH_SIZE; i++) {
peer_hash[i] = 0;
peer_hash_count[i] = 0;
assoc_hash[i] = 0;
assoc_hash_count[i] = 0;
}
/*
* Clear stat counters
*/
findpeer_calls = peer_allocations = 0;
assocpeer_calls = peer_demobilizations = 0;
/*
* Initialization counter.
*/
init_peer_starttime = 0;
/*
* Initialize peer memory.
*/
peer_free = 0;
for (i = 0; i < INIT_PEER_ALLOC; i++) {
init_peer_alloc[i].next = peer_free;
peer_free = &init_peer_alloc[i];
}
total_peer_structs = INIT_PEER_ALLOC;
peer_free_count = INIT_PEER_ALLOC;
/*
* Initialize our first association ID
*/
current_association_ID = (u_short)ranp2(16);
if (current_association_ID == 0)
current_association_ID = 1;
}
/*
* getmorepeermem - add more peer structures to the free list
*/
static void
getmorepeermem()
{
register int i;
register struct peer *peer;
peer = (struct peer *)emalloc(INC_PEER_ALLOC*sizeof(struct peer));
for (i = 0; i < INC_PEER_ALLOC; i++) {
peer->next = peer_free;
peer_free = peer;
peer++;
}
total_peer_structs += INC_PEER_ALLOC;
peer_free_count += INC_PEER_ALLOC;
}
/*
* findexistingpeer - return a pointer to a peer in the hash table
*/
struct peer *
findexistingpeer(addr, start_peer)
struct sockaddr_in *addr;
struct peer *start_peer;
{
register struct peer *peer;
/*
* start_peer is included so we can locate instances of the
* same peer through different interfaces in the hash table.
*/
if (start_peer == 0)
peer = peer_hash[HASH_ADDR(addr)];
else
peer = start_peer->next;
while (peer != 0) {
if (NSRCADR(addr) == NSRCADR(&peer->srcadr)
&& NSRCPORT(addr) == NSRCPORT(&peer->srcadr))
return peer;
peer = peer->next;
}
return (struct peer *)0;
}
/*
* findpeer - find and return a peer in the hash table.
*/
struct peer *
findpeer(srcadr, dstadr, fd)
struct sockaddr_in *srcadr;
struct interface *dstadr;
int fd;
{
register struct peer *any_inter_peer;
register struct peer *peer;
register struct peer *best = (struct peer *) 0;
int hash;
findpeer_calls++;
any_inter_peer = 0;
hash = HASH_ADDR(srcadr);
for (peer = peer_hash[hash]; peer != 0; peer = peer->next) {
if (NSRCADR(srcadr) == NSRCADR(&peer->srcadr)
&& NSRCPORT(srcadr) == NSRCPORT(&peer->srcadr)) {
if (peer->dstadr == dstadr) {
int rfd = (peer->cast_flags & MDF_BCAST) ?
dstadr->bfd : dstadr->fd;
if (rfd == fd)
return peer; /* got it! */
best = peer;
}
if (peer->dstadr == any_interface) {
/*
* We shouldn't have more than one
* instance of the peer in the table,
* but I don't trust this. Save this
* one for later and continue search.
*/
if (any_inter_peer == 0)
any_inter_peer = peer;
else
msyslog(LOG_ERR,
"two instances of default interface for %s in hash table",
ntoa(srcadr));
}
/*
* Multicast hacks to determine peer when a
* packet arrives and there exists an assoc.
* with src in client/server mode
*/
if (((dstadr == any_interface) || (peer->cast_flags &
MDF_MCAST)) && peer->flags & FLAG_MCAST2)
return peer;
}
}
if(best) {
return best;
}
/*
* If we didn't find the specific peer but found a wild card,
* modify the interface and return him.
*/
if (any_inter_peer != 0) {
any_inter_peer->dstadr = dstadr;
return any_inter_peer;
}
/*
* Out of luck. Return 0.
*/
return (struct peer *)0;
}
/*
* findpeerbyassocid - find and return a peer using his association ID
*/
struct peer *
findpeerbyassoc(assoc)
int assoc;
{
register struct peer *peer;
int hash;
assocpeer_calls++;
hash = assoc & HASH_MASK;
for (peer = assoc_hash[hash]; peer != 0; peer = peer->ass_next) {
if ((u_short)assoc == peer->associd)
return peer; /* got it! */
}
/*
* Out of luck. Return 0.
*/
return (struct peer *)0;
}
/*
* unpeer - remove peer structure from hash table and free structure
*/
void
unpeer(peer_to_remove)
struct peer *peer_to_remove;
{
int hash;
hash = HASH_ADDR(&peer_to_remove->srcadr);
peer_hash_count[hash]--;
peer_demobilizations++;
#ifdef REFCLOCK
/*
* If this peer is actually a clock, shut it down first
*/
if (peer_to_remove->flags & FLAG_REFCLOCK)
refclock_unpeer(peer_to_remove);
#endif
if (peer_hash[hash] == peer_to_remove)
peer_hash[hash] = peer_to_remove->next;
else {
register struct peer *peer;
peer = peer_hash[hash];
while (peer != 0 && peer->next != peer_to_remove)
peer = peer->next;
if (peer == 0) {
peer_hash_count[hash]++;
msyslog(LOG_ERR, "peer struct for %s not in table!",
ntoa(&peer_to_remove->srcadr));
} else {
peer->next = peer_to_remove->next;
}
}
/*
* Remove him from the association hash as well.
*/
hash = peer_to_remove->associd & HASH_MASK;
assoc_hash_count[hash]--;
if (assoc_hash[hash] == peer_to_remove)
assoc_hash[hash] = peer_to_remove->ass_next;
else {
register struct peer *peer;
peer = assoc_hash[hash];
while (peer != 0 && peer->ass_next != peer_to_remove)
peer = peer->ass_next;
if (peer == 0) {
assoc_hash_count[hash]++;
msyslog(LOG_ERR,
"peer struct for %s not in association table!",
ntoa(&peer_to_remove->srcadr));
} else {
peer->ass_next = peer_to_remove->ass_next;
}
}
TIMER_DEQUEUE(&peer_to_remove->event_timer);
peer_to_remove->next = peer_free;
peer_free = peer_to_remove;
peer_free_count++;
}
/*
* peer_config - configure a new peer
*/
struct peer *
peer_config(srcadr, dstadr, hmode, version, minpoll, maxpoll, flags, ttl, key)
struct sockaddr_in *srcadr;
struct interface *dstadr;
int hmode;
int version;
int minpoll;
int maxpoll;
int flags;
int ttl;
u_int32 key;
{
register struct peer *peer;
#ifdef DEBUG
if (debug)
printf("peer_config: addr %s mode %d version %d minpoll %d maxpoll %d flags %d ttl %d key %lu\n",
ntoa(srcadr), hmode, version, minpoll, maxpoll, flags,
ttl, (u_long)key);
#endif
/*
* See if we have this guy in the tables already. If
* so just mark him configured.
*/
peer = findexistingpeer(srcadr, (struct peer *)0);
if (dstadr != 0) {
while (peer != 0) {
if (peer->dstadr == dstadr)
break;
peer = findexistingpeer(srcadr, peer);
}
}
/*
* Torque the flags to make sure they're valid
*/
flags &= (FLAG_AUTHENABLE|FLAG_PREFER);
/*
* If we found one, just change his mode and mark him configured.
*/
if (peer != 0) {
peer->hmode = (u_char)hmode;
peer->version = (u_char)version;
peer->minpoll = (u_char)minpoll;
peer->maxpoll = (u_char)maxpoll;
peer->hpoll = peer->minpoll;
peer->ppoll = peer->minpoll;
peer->flags = ((u_char)(flags | FLAG_CONFIG)) |
(peer->flags & FLAG_REFCLOCK);
peer->cast_flags = (hmode == MODE_BROADCAST) ?
IN_CLASSD(ntohl(srcadr->sin_addr.s_addr)) ? MDF_MCAST : MDF_BCAST : MDF_UCAST;
peer->ttl = (u_char)ttl;
peer->keyid = key;
return peer;
}
/*
* If we're here this guy is unknown to us. Make a new peer
* structure for him.
*/
peer = newpeer(srcadr, dstadr, hmode, version, minpoll, maxpoll,
ttl, key);
if (peer != 0)
peer->flags |= (u_char)(flags|FLAG_CONFIG);
return peer;
}
/*
* newpeer - initialize a new peer association
*/
struct peer *
newpeer(srcadr, dstadr, hmode, version, minpoll, maxpoll, ttl, key)
struct sockaddr_in *srcadr;
struct interface *dstadr;
int hmode;
int version;
int minpoll;
int maxpoll;
int ttl;
u_int32 key;
{
register struct peer *peer;
register int i;
/*
* Some dirt here. Some of the initialization requires
* knowlege of our system state.
*/
extern s_fp sys_bdelay;
extern long sys_clock;
if (peer_free_count == 0)
getmorepeermem();
peer = peer_free;
peer_free = peer->next;
peer_free_count--;
/*
* Initialize the structure. This stuff is sort of part of
* the receive procedure and part of the clear procedure rolled
* into one.
*
* Zero the whole thing for now. We might be pickier later.
*/
memset((char *)peer, 0, sizeof(struct peer));
peer->srcadr = *srcadr;
if (dstadr != 0)
peer->dstadr = dstadr;
else if (hmode == MODE_BROADCAST)
peer->dstadr = findbcastinter(srcadr);
else
peer->dstadr = any_interface;
peer->cast_flags = (hmode == MODE_BROADCAST) ?
(IN_CLASSD(ntohl(srcadr->sin_addr.s_addr))) ? MDF_MCAST : MDF_BCAST :
(hmode == MODE_BCLIENT || hmode == MODE_MCLIENT) ?
(peer->dstadr->flags & INT_MULTICAST) ? MDF_MCAST : MDF_BCAST :
MDF_UCAST;
peer->hmode = (u_char)hmode;
peer->version = (u_char)version;
peer->minpoll = (u_char)minpoll;
peer->maxpoll = (u_char)maxpoll;
peer->hpoll = peer->minpoll;
peer->ppoll = peer->minpoll;
peer->ttl = ttl;
peer->keyid = key;
peer->estbdelay = sys_bdelay;
peer->leap = LEAP_NOTINSYNC;
peer->precision = DEFPRECISION;
peer->dispersion = NTP_MAXDISPERSE;
peer->stratum = STRATUM_UNSPEC;
peer->update = sys_clock;
for (i = 0; i < NTP_SHIFT; i++) {
peer->filter_order[i] = i;
peer->filter_error[i] = NTP_MAXDISPERSE;
}
/*
* Assign him an association ID and increment the system variable
*/
peer->associd = current_association_ID;
if (++current_association_ID == 0)
++current_association_ID;
/*
* Note time on statistics timers.
*/
peer->timereset = current_time;
peer->timereachable = current_time;
peer->timereceived = current_time;
#ifdef REFCLOCK
if (ISREFCLOCKADR(&peer->srcadr)) {
/*
* We let the reference clock support do clock
* dependent initialization. This includes setting
* the peer timer, since the clock may have requirements
* for this.
*/
if (!refclock_newpeer(peer)) {
/*
* Dump it, something screwed up
*/
peer->next = peer_free;
peer_free = peer;
peer_free_count++;
return 0;
}
} else {
#endif
/*
* Set up timer. If initializing, just make sure we start polling
* in different 4 second intervals.
*/
peer->event_timer.peer = peer;
peer->event_timer.event_handler = transmit;
if (initializing) {
init_peer_starttime += (1 << EVENT_TIMEOUT);
if (init_peer_starttime >= (u_long)(1 << peer->minpoll))
init_peer_starttime = (1 << EVENT_TIMEOUT);
peer->event_timer.event_time = init_peer_starttime;
} else {
/*
* First expiry is set to eight seconds from now.
*/
peer->event_timer.event_time
= (1 << (peer->minpoll - 1)) + current_time;
}
TIMER_ENQUEUE(timerqueue, &peer->event_timer);
#ifdef REFCLOCK
}
#endif
/*
* Put him in the hash tables.
*/
i = HASH_ADDR(&peer->srcadr);
peer->next = peer_hash[i];
peer_hash[i] = peer;
peer_hash_count[i]++;
i = peer->associd & HASH_MASK;
peer->ass_next = assoc_hash[i];
assoc_hash[i] = peer;
assoc_hash_count[i]++;
return peer;
}
/*
* peer_unconfig - remove the configuration bit from a peer
*/
int
peer_unconfig(srcadr, dstadr)
struct sockaddr_in *srcadr;
struct interface *dstadr;
{
register struct peer *peer;
int num_found;
num_found = 0;
peer = findexistingpeer(srcadr, (struct peer *)0);
while (peer != 0) {
if (peer->flags & FLAG_CONFIG
&& (dstadr == 0 || peer->dstadr == dstadr)) {
num_found++;
/*
* Tricky stuff here. If the peer is polling us
* in active mode, turn off the configuration bit
* and make the mode passive. This allows us to
* avoid dumping a lot of history for peers we
* might choose to keep track of in passive mode.
* The protocol will eventually terminate undesirables
* on its own.
*/
if (peer->hmode == MODE_ACTIVE
&& peer->pmode == MODE_ACTIVE) {
peer->hmode = MODE_PASSIVE;
peer->flags &= ~FLAG_CONFIG;
} else {
unpeer(peer);
peer = 0;
}
}
peer = findexistingpeer(srcadr, peer);
}
return num_found;
}
/*
* peer_clr_stats - clear peer module stat counters
*/
void
peer_clr_stats()
{
findpeer_calls = 0;
assocpeer_calls = 0;
peer_allocations = 0;
peer_demobilizations = 0;
peer_timereset = current_time;
}
/*
* peer_reset - reset stat counters in a peer structure
*/
void
peer_reset(peer)
struct peer *peer;
{
if (peer == 0)
return;
peer->sent = 0;
peer->received = 0;
peer->processed = 0;
peer->badauth = 0;
peer->bogusorg = 0;
peer->oldpkt = 0;
peer->seldisptoolarge = 0;
peer->selbroken = 0;
peer->seltooold = 0;
peer->timereset = current_time;
}
/*
* peer_all_reset - reset all peer stat counters
*/
void
peer_all_reset()
{
struct peer *peer;
int hash;
for (hash = 0; hash < HASH_SIZE; hash++)
for (peer = peer_hash[hash]; peer != 0; peer = peer->next)
peer_reset(peer);
}