NetBSD/dist/ntp/ntpd/ntp_restrict.c

610 lines
14 KiB
C

/* $NetBSD: ntp_restrict.c,v 1.6 2007/01/06 19:45:23 kardel Exp $ */
/*
* ntp_restrict.c - determine host restrictions
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include "ntpd.h"
#include "ntp_if.h"
#include "ntp_stdlib.h"
/*
* This code keeps a simple address-and-mask list of hosts we want
* to place restrictions on (or remove them from). The restrictions
* are implemented as a set of flags which tell you what the host
* can't do. There is a subroutine entry to return the flags. The
* list is kept sorted to reduce the average number of comparisons
* and make sure you get the set of restrictions most specific to
* the address.
*
* The algorithm is that, when looking up a host, it is first assumed
* that the default set of restrictions will apply. It then searches
* down through the list. Whenever it finds a match it adopts the
* match's flags instead. When you hit the point where the sorted
* address is greater than the target, you return with the last set of
* flags you found. Because of the ordering of the list, the most
* specific match will provide the final set of flags.
*
* This was originally intended to restrict you from sync'ing to your
* own broadcasts when you are doing that, by restricting yourself from
* your own interfaces. It was also thought it would sometimes be useful
* to keep a misbehaving host or two from abusing your primary clock. It
* has been expanded, however, to suit the needs of those with more
* restrictive access policies.
*/
/*
* We will use two lists, one for IPv4 addresses and one for IPv6
* addresses. This is not protocol-independant but for now I can't
* find a way to respect this. We'll check this later... JFB 07/2001
*/
#define SET_IPV6_ADDR_MASK(dst, src, msk) \
do { \
int idx; \
for (idx = 0; idx < 16; idx++) { \
(dst)->s6_addr[idx] = \
(u_char) ((src)->s6_addr[idx] & (msk)->s6_addr[idx]); \
} \
} while (0)
/*
* Memory allocation parameters. We allocate INITRESLIST entries
* initially, and add INCRESLIST entries to the free list whenever
* we run out.
*/
#define INITRESLIST 10
#define INCRESLIST 5
#define RES_AVG 8. /* interpacket averaging factor */
/*
* The restriction list
*/
struct restrictlist *restrictlist;
struct restrictlist6 *restrictlist6;
static int restrictcount; /* count of entries in the res list */
static int restrictcount6; /* count of entries in the res list 2*/
/*
* The free list and associated counters. Also some uninteresting
* stat counters.
*/
static struct restrictlist *resfree;
static struct restrictlist6 *resfree6;
static int numresfree; /* number of structures on free list */
static int numresfree6; /* number of structures on free list 2 */
static u_long res_calls;
static u_long res_found;
static u_long res_not_found;
/*
* Parameters of the RES_LIMITED restriction option.
*/
u_long res_avg_interval = 5; /* min average interpacket interval */
u_long res_min_interval = 1; /* min interpacket interval */
/*
* Count number of restriction entries referring to RES_LIMITED controls
* activation/deactivation of monitoring (with respect to RES_LIMITED
* control)
*/
static u_long res_limited_refcnt;
static u_long res_limited_refcnt6;
/*
* Our initial allocation of lists entries.
*/
static struct restrictlist resinit[INITRESLIST];
static struct restrictlist6 resinit6[INITRESLIST];
/*
* init_restrict - initialize the restriction data structures
*/
void
init_restrict(void)
{
register int i;
/*
* Zero the list and put all but one on the free list
*/
resfree = 0;
memset((char *)resinit, 0, sizeof resinit);
resfree6 = 0;
memset((char *)resinit6, 0, sizeof resinit6);
for (i = 1; i < INITRESLIST; i++) {
resinit[i].next = resfree;
resinit6[i].next = resfree6;
resfree = &resinit[i];
resfree6 = &resinit6[i];
}
numresfree = INITRESLIST-1;
numresfree6 = INITRESLIST-1;
/*
* Put the remaining item at the head of the list as our default
* entry. Everything in here should be zero for now.
*/
resinit[0].addr = htonl(INADDR_ANY);
resinit[0].mask = 0;
memset(&resinit6[0].addr6, 0, sizeof(struct in6_addr));
memset(&resinit6[0].mask6, 0, sizeof(struct in6_addr));
restrictlist = &resinit[0];
restrictlist6 = &resinit6[0];
restrictcount = 1;
restrictcount = 2;
/*
* fix up stat counters
*/
res_calls = 0;
res_found = 0;
res_not_found = 0;
/*
* set default values for RES_LIMIT functionality
*/
res_limited_refcnt = 0;
res_limited_refcnt6 = 0;
}
/*
* restrictions - return restrictions for this host
*/
int
restrictions(
struct sockaddr_storage *srcadr,
int at_listhead
)
{
struct restrictlist *rl;
struct restrictlist *match = NULL;
struct restrictlist6 *rl6;
struct restrictlist6 *match6 = NULL;
struct in6_addr hostaddr6;
struct in6_addr hostservaddr6;
u_int32 hostaddr;
int flags = 0;
int isntpport;
res_calls++;
if (srcadr->ss_family == AF_INET) {
/*
* We need the host address in host order. Also need to
* know whether this is from the ntp port or not.
*/
hostaddr = SRCADR(srcadr);
isntpport = (SRCPORT(srcadr) == NTP_PORT);
/*
* Ignore any packets with a multicast source address
* (this should be done early in the receive process,
* later!)
*/
if (IN_CLASSD(SRCADR(srcadr)))
return (int)RES_IGNORE;
/*
* Set match to first entry, which is default entry.
* Work our way down from there.
*/
match = restrictlist;
for (rl = match->next; rl != 0 && rl->addr <= hostaddr;
rl = rl->next)
if ((hostaddr & rl->mask) == rl->addr) {
if ((rl->mflags & RESM_NTPONLY) &&
!isntpport)
continue;
match = rl;
}
match->count++;
if (match == restrictlist)
res_not_found++;
else
res_found++;
flags = match->flags;
}
/* IPv6 source address */
if (srcadr->ss_family == AF_INET6) {
/*
* Need to know whether this is from the ntp port or
* not.
*/
hostaddr6 = GET_INADDR6(*srcadr);
isntpport = (ntohs((
(struct sockaddr_in6 *)srcadr)->sin6_port) ==
NTP_PORT);
/*
* Ignore any packets with a multicast source address
* (this should be done early in the receive process,
* later!)
*/
if (IN6_IS_ADDR_MULTICAST(&hostaddr6))
return (int)RES_IGNORE;
/*
* Set match to first entry, which is default entry.
* Work our way down from there.
*/
match6 = restrictlist6;
for (rl6 = match6->next; rl6 != 0 &&
(memcmp(&(rl6->addr6), &hostaddr6,
sizeof(hostaddr6)) <= 0); rl6 = rl6->next) {
SET_IPV6_ADDR_MASK(&hostservaddr6, &hostaddr6,
&rl6->mask6);
if (memcmp(&hostservaddr6, &(rl6->addr6),
sizeof(hostservaddr6)) == 0) {
if ((rl6->mflags & RESM_NTPONLY) &&
!isntpport)
continue;
match6 = rl6;
}
}
match6->count++;
if (match6 == restrictlist6)
res_not_found++;
else
res_found++;
flags = match6->flags;
}
/*
* The following implements a generalized call gap facility.
* Douse the RES_LIMITED bit only if the interval since the last
* packet is greater than res_min_interval and the average is
* greater thatn res_avg_interval.
*/
if (!at_listhead || mon_enabled == MON_OFF) {
flags &= ~RES_LIMITED;
} else {
struct mon_data *md;
/*
* At this poin the most recent arrival is first in the
* MRU list. Let the first 10 packets in for free until
* the average stabilizes.
*/
md = mon_mru_list.mru_next;
if (md->avg_interval == 0)
md->avg_interval = md->drop_count;
else
md->avg_interval += (md->drop_count -
md->avg_interval) / RES_AVG;
if (md->count < 10 || (md->drop_count >
res_min_interval && md->avg_interval >
res_avg_interval))
flags &= ~RES_LIMITED;
md->drop_count = flags;
}
return (flags);
}
/*
* hack_restrict - add/subtract/manipulate entries on the restrict list
*/
void
hack_restrict(
int op,
struct sockaddr_storage *resaddr,
struct sockaddr_storage *resmask,
int mflags,
int flags
)
{
register u_int32 addr = 0;
register u_int32 mask = 0;
struct in6_addr addr6;
struct in6_addr mask6;
register struct restrictlist *rl = NULL;
register struct restrictlist *rlprev = NULL;
register struct restrictlist6 *rl6 = NULL;
register struct restrictlist6 *rlprev6 = NULL;
int i, addr_cmp, mask_cmp;
memset(&addr6, 0, sizeof(struct in6_addr));
memset(&mask6, 0, sizeof(struct in6_addr));
if (resaddr->ss_family == AF_INET) {
/*
* Get address and mask in host byte order
*/
addr = SRCADR(resaddr);
mask = SRCADR(resmask);
addr &= mask; /* make sure low bits zero */
/*
* If this is the default address, point at first on
* list. Else go searching for it.
*/
if (addr == 0) {
rlprev = 0;
rl = restrictlist;
} else {
rlprev = restrictlist;
rl = rlprev->next;
while (rl != 0) {
if (rl->addr > addr) {
rl = 0;
break;
} else if (rl->addr == addr) {
if (rl->mask == mask) {
if ((mflags &
RESM_NTPONLY) ==
(rl->mflags &
RESM_NTPONLY))
break;
if (!(mflags &
RESM_NTPONLY)) {
rl = 0;
break;
}
} else if (rl->mask > mask) {
rl = 0;
break;
}
}
rlprev = rl;
rl = rl->next;
}
}
}
if (resaddr->ss_family == AF_INET6) {
mask6 = GET_INADDR6(*resmask);
SET_IPV6_ADDR_MASK(&addr6,
&GET_INADDR6(*resaddr), &mask6);
if (IN6_IS_ADDR_UNSPECIFIED(&addr6)) {
rlprev6 = NULL;
rl6 = restrictlist6;
} else {
rlprev6 = restrictlist6;
rl6 = rlprev6->next;
while (rl6 != 0) {
addr_cmp = memcmp(&rl6->addr6, &addr6,
sizeof(addr6));
if (addr_cmp > 0) {
rl6 = 0;
break;
} else if (addr_cmp == 0) {
mask_cmp = memcmp(&rl6->mask6,
&mask6, sizeof(mask6));
if (mask_cmp == 0) {
if ((mflags &
RESM_NTPONLY) ==
(rl6->mflags &
RESM_NTPONLY))
break;
if (!(mflags &
RESM_NTPONLY)) {
rl6 = 0;
break;
}
} else if (mask_cmp > 0) {
rl6 = 0;
break;
}
}
rlprev6 = rl6;
rl6 = rl6->next;
}
}
}
/*
* In case the above wasn't clear :-), either rl now points
* at the entry this call refers to, or rl is zero and rlprev
* points to the entry prior to where this one should go in
* the sort.
*/
/*
* Switch based on operation
*/
if (resaddr->ss_family == AF_INET) {
switch (op) {
case RESTRICT_FLAGS:
/*
* Here we add bits to the flags. If this is a
* new restriction add it.
*/
if (rl == 0) {
if (numresfree == 0) {
rl = (struct restrictlist *)
emalloc(INCRESLIST *
sizeof(struct
restrictlist));
memset((char *)rl, 0,
INCRESLIST * sizeof(struct
restrictlist));
for (i = 0; i < INCRESLIST; i++) {
rl->next = resfree;
resfree = rl;
rl++;
}
numresfree = INCRESLIST;
}
rl = resfree;
resfree = rl->next;
numresfree--;
rl->addr = addr;
rl->mask = mask;
rl->mflags = (u_short)mflags;
if (rlprev == NULL) {
rl->next = restrictlist;
restrictlist = rl;
} else {
rl->next = rlprev->next;
rlprev->next = rl;
}
restrictcount++;
}
if ((rl->flags ^ (u_short)flags) &
RES_LIMITED) {
res_limited_refcnt++;
mon_start(MON_RES);
}
rl->flags |= (u_short)flags;
break;
case RESTRICT_UNFLAG:
/*
* Remove some bits from the flags. If we didn't
* find this one, just return.
*/
if (rl != 0) {
if ((rl->flags ^ (u_short)flags) &
RES_LIMITED) {
res_limited_refcnt--;
if (res_limited_refcnt == 0)
mon_stop(MON_RES);
}
rl->flags &= (u_short)~flags;
}
break;
case RESTRICT_REMOVE:
case RESTRICT_REMOVEIF:
/*
* Remove an entry from the table entirely if we
* found one. Don't remove the default entry and
* don't remove an interface entry.
*/
if (rl != 0
&& rl->addr != htonl(INADDR_ANY)
&& !(rl->mflags & RESM_INTERFACE && op != RESTRICT_REMOVEIF)) {
if (rlprev != NULL) {
rlprev->next = rl->next;
} else {
restrictlist = rl->next;
}
restrictcount--;
if (rl->flags & RES_LIMITED) {
res_limited_refcnt--;
if (res_limited_refcnt == 0)
mon_stop(MON_RES);
}
memset((char *)rl, 0,
sizeof(struct restrictlist));
rl->next = resfree;
resfree = rl;
numresfree++;
}
break;
default:
break;
}
} else if (resaddr->ss_family == AF_INET6) {
switch (op) {
case RESTRICT_FLAGS:
/*
* Here we add bits to the flags. If this is a
* new restriction add it.
*/
if (rl6 == 0) {
if (numresfree6 == 0) {
rl6 = (struct
restrictlist6 *)emalloc(
INCRESLIST * sizeof(struct
restrictlist6));
memset((char *)rl6, 0,
INCRESLIST * sizeof(struct
restrictlist6));
for (i = 0; i < INCRESLIST;
i++) {
rl6->next = resfree6;
resfree6 = rl6;
rl6++;
}
numresfree6 = INCRESLIST;
}
rl6 = resfree6;
resfree6 = rl6->next;
numresfree6--;
rl6->addr6 = addr6;
rl6->mask6 = mask6;
rl6->mflags = (u_short)mflags;
if (rlprev6 != NULL) {
rl6->next = rlprev6->next;
rlprev6->next = rl6;
} else {
rl6->next = restrictlist6;
restrictlist6 = rl6;
}
restrictcount6++;
}
if ((rl6->flags ^ (u_short)flags) &
RES_LIMITED) {
res_limited_refcnt6++;
mon_start(MON_RES);
}
rl6->flags |= (u_short)flags;
break;
case RESTRICT_UNFLAG:
/*
* Remove some bits from the flags. If we didn't
* find this one, just return.
*/
if (rl6 != 0) {
if ((rl6->flags ^ (u_short)flags) &
RES_LIMITED) {
res_limited_refcnt6--;
if (res_limited_refcnt6 == 0)
mon_stop(MON_RES);
}
rl6->flags &= (u_short)~flags;
}
break;
case RESTRICT_REMOVE:
case RESTRICT_REMOVEIF:
/*
* Remove an entry from the table entirely if we
* found one. Don't remove the default entry and
* don't remove an interface entry.
*/
if (rl6 != 0 &&
!IN6_IS_ADDR_UNSPECIFIED(&rl6->addr6)
&& !(rl6->mflags & RESM_INTERFACE && op != RESTRICT_REMOVEIF)) {
if (rlprev6 != NULL) {
rlprev6->next = rl6->next;
} else {
restrictlist6 = rl6->next;
}
restrictcount6--;
if (rl6->flags & RES_LIMITED) {
res_limited_refcnt6--;
if (res_limited_refcnt6 == 0)
mon_stop(MON_RES);
}
memset((char *)rl6, 0,
sizeof(struct restrictlist6));
rl6->next = resfree6;
resfree6 = rl6;
numresfree6++;
}
break;
default:
break;
}
}
}