657 lines
12 KiB
C
657 lines
12 KiB
C
/* $NetBSD: ip_htable.c,v 1.2 2012/02/15 17:55:04 riz Exp $ */
|
|
|
|
/*
|
|
* Copyright (C) 1993-2001, 2003 by Darren Reed.
|
|
*
|
|
* See the IPFILTER.LICENCE file for details on licencing.
|
|
*
|
|
* Copyright 2008 Sun Microsystems, Inc.
|
|
*/
|
|
#if defined(KERNEL) || defined(_KERNEL)
|
|
# undef KERNEL
|
|
# undef _KERNEL
|
|
# define KERNEL 1
|
|
# define _KERNEL 1
|
|
#endif
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/time.h>
|
|
#include <sys/file.h>
|
|
#if !defined(_KERNEL)
|
|
# include <stdlib.h>
|
|
# include <string.h>
|
|
# define _KERNEL
|
|
# ifdef __OpenBSD__
|
|
struct file;
|
|
# endif
|
|
# include <sys/uio.h>
|
|
# undef _KERNEL
|
|
#endif
|
|
#include <sys/socket.h>
|
|
#if defined(__FreeBSD_version) && (__FreeBSD_version >= 300000)
|
|
# include <sys/malloc.h>
|
|
#endif
|
|
#if defined(__FreeBSD__)
|
|
# include <sys/cdefs.h>
|
|
# include <sys/proc.h>
|
|
#endif
|
|
#if !defined(__svr4__) && !defined(__SVR4) && !defined(__hpux) && \
|
|
!defined(linux)
|
|
# include <sys/mbuf.h>
|
|
#endif
|
|
#if defined(_KERNEL)
|
|
# include <sys/systm.h>
|
|
#else
|
|
# include <stdio.h>
|
|
#endif
|
|
#include <netinet/in.h>
|
|
#include <net/if.h>
|
|
|
|
#include "netinet/ip_compat.h"
|
|
#include "netinet/ip_fil.h"
|
|
#include "netinet/ip_lookup.h"
|
|
#include "netinet/ip_htable.h"
|
|
/* END OF INCLUDES */
|
|
|
|
#if !defined(lint)
|
|
static const char rcsid[] = "@(#)Id: ip_htable.c,v 2.34.2.13 2009/05/13 19:13:36 darrenr Exp";
|
|
#endif
|
|
|
|
#ifdef IPFILTER_LOOKUP
|
|
static iphtent_t *fr_iphmfind(iphtable_t *, struct in_addr *);
|
|
static u_long ipht_nomem[IPL_LOGSIZE] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
static u_long ipf_nhtables[IPL_LOGSIZE] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
static u_long ipf_nhtnodes[IPL_LOGSIZE] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
iphtable_t *ipf_htables[IPL_LOGSIZE] = { NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL };
|
|
|
|
|
|
void
|
|
fr_htable_unload(void)
|
|
{
|
|
iplookupflush_t fop;
|
|
|
|
fop.iplf_unit = IPL_LOGALL;
|
|
(void)fr_flushhtable(&fop);
|
|
}
|
|
|
|
|
|
int
|
|
fr_gethtablestat(iplookupop_t *op)
|
|
{
|
|
iphtstat_t stats;
|
|
|
|
if (op->iplo_size != sizeof(stats))
|
|
return EINVAL;
|
|
|
|
stats.iphs_tables = ipf_htables[op->iplo_unit];
|
|
stats.iphs_numtables = ipf_nhtables[op->iplo_unit];
|
|
stats.iphs_numnodes = ipf_nhtnodes[op->iplo_unit];
|
|
stats.iphs_nomem = ipht_nomem[op->iplo_unit];
|
|
|
|
return COPYOUT(&stats, op->iplo_struct, sizeof(stats));
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Create a new hash table using the template passed.
|
|
*/
|
|
int
|
|
fr_newhtable(iplookupop_t *op)
|
|
{
|
|
iphtable_t *iph, *oiph;
|
|
char name[FR_GROUPLEN];
|
|
int err, i, unit;
|
|
|
|
unit = op->iplo_unit;
|
|
if ((op->iplo_arg & IPHASH_ANON) == 0) {
|
|
iph = fr_existshtable(unit, op->iplo_name);
|
|
if (iph != NULL) {
|
|
if ((iph->iph_flags & IPHASH_DELETE) == 0)
|
|
return EEXIST;
|
|
iph->iph_flags &= ~IPHASH_DELETE;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
KMALLOC(iph, iphtable_t *);
|
|
if (iph == NULL) {
|
|
ipht_nomem[op->iplo_unit]++;
|
|
return ENOMEM;
|
|
}
|
|
err = COPYIN(op->iplo_struct, iph, sizeof(*iph));
|
|
if (err != 0) {
|
|
KFREE(iph);
|
|
return EFAULT;
|
|
}
|
|
|
|
if (iph->iph_unit != unit) {
|
|
KFREE(iph);
|
|
return EINVAL;
|
|
}
|
|
|
|
if ((op->iplo_arg & IPHASH_ANON) != 0) {
|
|
i = IPHASH_ANON;
|
|
do {
|
|
i++;
|
|
#if defined(SNPRINTF) && defined(_KERNEL)
|
|
SNPRINTF(name, sizeof(name), "%u", i);
|
|
#else
|
|
(void)sprintf(name, "%u", i);
|
|
#endif
|
|
for (oiph = ipf_htables[unit]; oiph != NULL;
|
|
oiph = oiph->iph_next)
|
|
if (strncmp(oiph->iph_name, name,
|
|
sizeof(oiph->iph_name)) == 0)
|
|
break;
|
|
} while (oiph != NULL);
|
|
|
|
(void)strncpy(iph->iph_name, name, sizeof(iph->iph_name));
|
|
(void)strncpy(op->iplo_name, name, sizeof(op->iplo_name));
|
|
iph->iph_type |= IPHASH_ANON;
|
|
}
|
|
|
|
KMALLOCS(iph->iph_table, iphtent_t **,
|
|
iph->iph_size * sizeof(*iph->iph_table));
|
|
if (iph->iph_table == NULL) {
|
|
KFREE(iph);
|
|
ipht_nomem[unit]++;
|
|
return ENOMEM;
|
|
}
|
|
|
|
bzero((char *)iph->iph_table, iph->iph_size * sizeof(*iph->iph_table));
|
|
iph->iph_masks = 0;
|
|
iph->iph_list = NULL;
|
|
|
|
iph->iph_ref = 1;
|
|
iph->iph_next = ipf_htables[unit];
|
|
iph->iph_pnext = &ipf_htables[unit];
|
|
if (ipf_htables[unit] != NULL)
|
|
ipf_htables[unit]->iph_pnext = &iph->iph_next;
|
|
ipf_htables[unit] = iph;
|
|
ipf_nhtables[unit]++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
*/
|
|
int
|
|
fr_removehtable(int unit, char *name)
|
|
{
|
|
iphtable_t *iph;
|
|
|
|
iph = fr_findhtable(unit, name);
|
|
if (iph == NULL)
|
|
return ESRCH;
|
|
|
|
if (iph->iph_unit != unit) {
|
|
return EINVAL;
|
|
}
|
|
|
|
if (iph->iph_ref != 0) {
|
|
(void) fr_clearhtable(iph);
|
|
iph->iph_flags |= IPHASH_DELETE;
|
|
return 0;
|
|
}
|
|
|
|
fr_delhtable(iph);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
fr_clearhtable(iphtable_t *iph)
|
|
{
|
|
iphtent_t *ipe;
|
|
|
|
while ((ipe = iph->iph_list) != NULL)
|
|
if (fr_delhtent(iph, ipe) != 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
fr_delhtable(iphtable_t *iph)
|
|
{
|
|
|
|
if (fr_clearhtable(iph) != 0)
|
|
return 1;
|
|
|
|
if (iph->iph_pnext != NULL)
|
|
*iph->iph_pnext = iph->iph_next;
|
|
if (iph->iph_next != NULL)
|
|
iph->iph_next->iph_pnext = iph->iph_pnext;
|
|
|
|
ipf_nhtables[iph->iph_unit]--;
|
|
|
|
return fr_derefhtable(iph);
|
|
}
|
|
|
|
|
|
/*
|
|
* Delete an entry from a hash table.
|
|
*/
|
|
int
|
|
fr_delhtent(iphtable_t *iph, iphtent_t *ipe)
|
|
{
|
|
|
|
if (ipe->ipe_phnext != NULL)
|
|
*ipe->ipe_phnext = ipe->ipe_hnext;
|
|
if (ipe->ipe_hnext != NULL)
|
|
ipe->ipe_hnext->ipe_phnext = ipe->ipe_phnext;
|
|
|
|
if (ipe->ipe_pnext != NULL)
|
|
*ipe->ipe_pnext = ipe->ipe_next;
|
|
if (ipe->ipe_next != NULL)
|
|
ipe->ipe_next->ipe_pnext = ipe->ipe_pnext;
|
|
|
|
switch (iph->iph_type & ~IPHASH_ANON)
|
|
{
|
|
case IPHASH_GROUPMAP :
|
|
if (ipe->ipe_group != NULL)
|
|
fr_delgroup(ipe->ipe_group, IPL_LOGIPF, fr_active);
|
|
break;
|
|
|
|
default :
|
|
ipe->ipe_ptr = NULL;
|
|
ipe->ipe_value = 0;
|
|
break;
|
|
}
|
|
|
|
return fr_derefhtent(ipe);
|
|
}
|
|
|
|
|
|
int
|
|
fr_derefhtable(iphtable_t *iph)
|
|
{
|
|
int refs;
|
|
|
|
iph->iph_ref--;
|
|
refs = iph->iph_ref;
|
|
|
|
if (iph->iph_ref == 0) {
|
|
KFREES(iph->iph_table, iph->iph_size * sizeof(*iph->iph_table));
|
|
KFREE(iph);
|
|
}
|
|
|
|
return refs;
|
|
}
|
|
|
|
|
|
int
|
|
fr_derefhtent(iphtent_t *ipe)
|
|
{
|
|
|
|
ipe->ipe_ref--;
|
|
if (ipe->ipe_ref == 0) {
|
|
ipf_nhtnodes[ipe->ipe_unit]--;
|
|
|
|
KFREE(ipe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return ipe->ipe_ref;
|
|
}
|
|
|
|
|
|
iphtable_t *
|
|
fr_existshtable(int unit, char *name)
|
|
{
|
|
iphtable_t *iph;
|
|
|
|
for (iph = ipf_htables[unit]; iph != NULL; iph = iph->iph_next)
|
|
if (strncmp(iph->iph_name, name, sizeof(iph->iph_name)) == 0)
|
|
break;
|
|
return iph;
|
|
}
|
|
|
|
|
|
iphtable_t *
|
|
fr_findhtable(int unit, char *name)
|
|
{
|
|
iphtable_t *iph;
|
|
|
|
iph = fr_existshtable(unit, name);
|
|
if ((iph != NULL) && (iph->iph_flags & IPHASH_DELETE) == 0)
|
|
return iph;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
size_t
|
|
fr_flushhtable(iplookupflush_t *op)
|
|
{
|
|
iphtable_t *iph;
|
|
size_t freed;
|
|
int i;
|
|
|
|
freed = 0;
|
|
|
|
for (i = 0; i <= IPL_LOGMAX; i++) {
|
|
if (op->iplf_unit == i || op->iplf_unit == IPL_LOGALL) {
|
|
while ((iph = ipf_htables[i]) != NULL) {
|
|
if (fr_delhtable(iph) == 0) {
|
|
freed++;
|
|
} else {
|
|
iph->iph_flags |= IPHASH_DELETE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return freed;
|
|
}
|
|
|
|
|
|
/*
|
|
* Add an entry to a hash table.
|
|
*/
|
|
int
|
|
fr_addhtent(iphtable_t *iph, iphtent_t *ipeo)
|
|
{
|
|
iphtent_t *ipe;
|
|
u_int hv;
|
|
int bits;
|
|
|
|
KMALLOC(ipe, iphtent_t *);
|
|
if (ipe == NULL)
|
|
return -1;
|
|
|
|
bcopy((char *)ipeo, (char *)ipe, sizeof(*ipe));
|
|
ipe->ipe_addr.in4_addr &= ipe->ipe_mask.in4_addr;
|
|
ipe->ipe_addr.in4_addr = ntohl(ipe->ipe_addr.in4_addr);
|
|
bits = count4bits(ipe->ipe_mask.in4_addr);
|
|
ipe->ipe_mask.in4_addr = ntohl(ipe->ipe_mask.in4_addr);
|
|
|
|
hv = IPE_HASH_FN(ipe->ipe_addr.in4_addr, ipe->ipe_mask.in4_addr,
|
|
iph->iph_size);
|
|
ipe->ipe_ref = 1;
|
|
ipe->ipe_hnext = iph->iph_table[hv];
|
|
ipe->ipe_phnext = iph->iph_table + hv;
|
|
|
|
if (iph->iph_table[hv] != NULL)
|
|
iph->iph_table[hv]->ipe_phnext = &ipe->ipe_hnext;
|
|
iph->iph_table[hv] = ipe;
|
|
|
|
ipe->ipe_next = iph->iph_list;
|
|
ipe->ipe_pnext = &iph->iph_list;
|
|
if (ipe->ipe_next != NULL)
|
|
ipe->ipe_next->ipe_pnext = &ipe->ipe_next;
|
|
iph->iph_list = ipe;
|
|
|
|
if ((bits >= 0) && (bits != 32))
|
|
iph->iph_masks |= 1 << bits;
|
|
|
|
switch (iph->iph_type & ~IPHASH_ANON)
|
|
{
|
|
case IPHASH_GROUPMAP :
|
|
ipe->ipe_ptr = fr_addgroup(ipe->ipe_group, NULL,
|
|
iph->iph_flags, IPL_LOGIPF,
|
|
fr_active);
|
|
break;
|
|
|
|
default :
|
|
ipe->ipe_ptr = NULL;
|
|
ipe->ipe_value = 0;
|
|
break;
|
|
}
|
|
|
|
ipe->ipe_unit = iph->iph_unit;
|
|
ipf_nhtnodes[ipe->ipe_unit]++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void *
|
|
fr_iphmfindgroup(void *tptr, void *aptr)
|
|
{
|
|
struct in_addr *addr;
|
|
iphtable_t *iph;
|
|
iphtent_t *ipe;
|
|
void *rval;
|
|
|
|
READ_ENTER(&ip_poolrw);
|
|
iph = tptr;
|
|
addr = aptr;
|
|
|
|
ipe = fr_iphmfind(iph, addr);
|
|
if (ipe != NULL)
|
|
rval = ipe->ipe_ptr;
|
|
else
|
|
rval = NULL;
|
|
RWLOCK_EXIT(&ip_poolrw);
|
|
return rval;
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/* Function: fr_iphmfindip */
|
|
/* Returns: int - 0 == +ve match, -1 == error, 1 == -ve/no match */
|
|
/* Parameters: tptr(I) - pointer to the pool to search */
|
|
/* ipversion(I) - IP protocol version (4 or 6) */
|
|
/* aptr(I) - pointer to address information */
|
|
/* */
|
|
/* Search the hash table for a given address and return a search result. */
|
|
/* ------------------------------------------------------------------------ */
|
|
int
|
|
fr_iphmfindip(void *tptr, int ipversion, void *aptr)
|
|
{
|
|
struct in_addr *addr;
|
|
iphtable_t *iph;
|
|
iphtent_t *ipe;
|
|
int rval;
|
|
|
|
if (ipversion != 4)
|
|
return -1;
|
|
|
|
if (tptr == NULL || aptr == NULL)
|
|
return -1;
|
|
|
|
iph = tptr;
|
|
addr = aptr;
|
|
|
|
READ_ENTER(&ip_poolrw);
|
|
ipe = fr_iphmfind(iph, addr);
|
|
if (ipe != NULL)
|
|
rval = 0;
|
|
else
|
|
rval = 1;
|
|
RWLOCK_EXIT(&ip_poolrw);
|
|
return rval;
|
|
}
|
|
|
|
|
|
/* Locks: ip_poolrw */
|
|
static iphtent_t *
|
|
fr_iphmfind(iphtable_t *iph, struct in_addr *addr)
|
|
{
|
|
u_32_t hmsk, msk, ips;
|
|
iphtent_t *ipe;
|
|
u_int hv;
|
|
|
|
hmsk = iph->iph_masks;
|
|
msk = 0xffffffff;
|
|
maskloop:
|
|
ips = ntohl(addr->s_addr) & msk;
|
|
hv = IPE_HASH_FN(ips, msk, iph->iph_size);
|
|
for (ipe = iph->iph_table[hv]; (ipe != NULL); ipe = ipe->ipe_hnext) {
|
|
if (ipe->ipe_mask.in4_addr != msk ||
|
|
ipe->ipe_addr.in4_addr != ips) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ((ipe == NULL) && (hmsk != 0)) {
|
|
while (hmsk != 0) {
|
|
msk <<= 1;
|
|
if (hmsk & 0x80000000)
|
|
break;
|
|
hmsk <<= 1;
|
|
}
|
|
if (hmsk != 0) {
|
|
hmsk <<= 1;
|
|
goto maskloop;
|
|
}
|
|
}
|
|
return ipe;
|
|
}
|
|
|
|
|
|
int
|
|
fr_htable_getnext(ipftoken_t *token, ipflookupiter_t *ilp)
|
|
{
|
|
iphtent_t *node, zn, *nextnode;
|
|
iphtable_t *iph, zp, *nextiph;
|
|
int err;
|
|
|
|
err = 0;
|
|
iph = NULL;
|
|
node = NULL;
|
|
nextiph = NULL;
|
|
nextnode = NULL;
|
|
|
|
READ_ENTER(&ip_poolrw);
|
|
|
|
/*
|
|
* Get "previous" entry from the token and find the next entry.
|
|
*
|
|
* If we found an entry, add a reference to it and update the token.
|
|
* Otherwise, zero out data to be returned and NULL out token.
|
|
*/
|
|
switch (ilp->ili_otype)
|
|
{
|
|
case IPFLOOKUPITER_LIST :
|
|
iph = token->ipt_data;
|
|
if (iph == NULL) {
|
|
nextiph = ipf_htables[(int)ilp->ili_unit];
|
|
} else {
|
|
nextiph = iph->iph_next;
|
|
}
|
|
|
|
if (nextiph != NULL) {
|
|
ATOMIC_INC(nextiph->iph_ref);
|
|
token->ipt_data = nextiph;
|
|
} else {
|
|
bzero((char *)&zp, sizeof(zp));
|
|
nextiph = &zp;
|
|
token->ipt_data = NULL;
|
|
}
|
|
break;
|
|
|
|
case IPFLOOKUPITER_NODE :
|
|
node = token->ipt_data;
|
|
if (node == NULL) {
|
|
iph = fr_findhtable(ilp->ili_unit, ilp->ili_name);
|
|
if (iph == NULL)
|
|
err = ESRCH;
|
|
else {
|
|
nextnode = iph->iph_list;
|
|
}
|
|
} else {
|
|
nextnode = node->ipe_next;
|
|
}
|
|
|
|
if (nextnode != NULL) {
|
|
ATOMIC_INC(nextnode->ipe_ref);
|
|
token->ipt_data = nextnode;
|
|
} else {
|
|
bzero((char *)&zn, sizeof(zn));
|
|
nextnode = &zn;
|
|
token->ipt_data = NULL;
|
|
}
|
|
break;
|
|
|
|
default :
|
|
err = EINVAL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Now that we have ref, it's save to give up lock.
|
|
*/
|
|
RWLOCK_EXIT(&ip_poolrw);
|
|
if (err != 0)
|
|
return err;
|
|
|
|
/*
|
|
* Copy out data and clean up references and token as needed.
|
|
*/
|
|
switch (ilp->ili_otype)
|
|
{
|
|
case IPFLOOKUPITER_LIST :
|
|
err = COPYOUT(nextiph, ilp->ili_data, sizeof(*nextiph));
|
|
if (err != 0)
|
|
err = EFAULT;
|
|
if (token->ipt_data != NULL) {
|
|
if (iph != NULL) {
|
|
WRITE_ENTER(&ip_poolrw);
|
|
fr_derefhtable(iph);
|
|
RWLOCK_EXIT(&ip_poolrw);
|
|
}
|
|
if (nextiph->iph_next == NULL)
|
|
token->ipt_data = NULL;
|
|
}
|
|
break;
|
|
|
|
case IPFLOOKUPITER_NODE :
|
|
err = COPYOUT(nextnode, ilp->ili_data, sizeof(*nextnode));
|
|
if (err != 0)
|
|
err = EFAULT;
|
|
if (token->ipt_data != NULL) {
|
|
if (node != NULL) {
|
|
WRITE_ENTER(&ip_poolrw);
|
|
fr_derefhtent(node);
|
|
RWLOCK_EXIT(&ip_poolrw);
|
|
}
|
|
if (nextnode->ipe_next == NULL)
|
|
token->ipt_data = NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
void
|
|
fr_htable_iterderef(u_int otype, int unit, void *data)
|
|
{
|
|
|
|
if (data == NULL)
|
|
return;
|
|
|
|
if (unit < 0 || unit > IPL_LOGMAX)
|
|
return;
|
|
|
|
switch (otype)
|
|
{
|
|
case IPFLOOKUPITER_LIST :
|
|
WRITE_ENTER(&ip_poolrw);
|
|
fr_derefhtable((iphtable_t *)data);
|
|
RWLOCK_EXIT(&ip_poolrw);
|
|
break;
|
|
|
|
case IPFLOOKUPITER_NODE :
|
|
WRITE_ENTER(&ip_poolrw);
|
|
fr_derefhtent((iphtent_t *)data);
|
|
RWLOCK_EXIT(&ip_poolrw);
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif /* IPFILTER_LOOKUP */
|