610 lines
14 KiB
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;
|
|
}
|
|
}
|
|
}
|