filters.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2002-2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/atomic.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <ipp/ipp_config.h>
#include <ipp/ipgpc/filters.h>
#include <ipp/ipgpc/trie.h>
#include <ipp/ipgpc/table.h>
#include <ipp/ipgpc/ba_table.h>
#include <ipp/ipgpc/classifier.h>
/* Implementation for filter management and configuration support of ipgpc */
#define BITLENGTH(x) (sizeof (x) * NBBY)
/* Globals */
kmutex_t ipgpc_table_list_lock; /* table list lock */
kmutex_t ipgpc_fid_list_lock; /* filter id list lock */
kmutex_t ipgpc_cid_list_lock; /* class id list lock */
trie_id_t ipgpc_trie_list[NUM_TRIES]; /* list of all trie structures ids */
table_id_t ipgpc_table_list[NUM_TABLES]; /* list of all table ids */
ba_table_id_t ipgpc_ds_table_id; /* DiffServ field table id */
fid_t *ipgpc_fid_list = NULL; /* filter id list */
cid_t *ipgpc_cid_list = NULL; /* class id list */
kmem_cache_t *ht_node_cache = NULL; /* hashtable cache */
kmem_cache_t *ht_match_cache = NULL; /* ht_match cache */
kmem_cache_t *trie_node_cache = NULL; /* trie node cache */
kmem_cache_t *element_node_cache = NULL; /* element node cache */
boolean_t ipgpc_gather_stats; /* should stats be performed for ipgpc */
uint64_t ipgpc_npackets; /* number of packets stat */
uint64_t ipgpc_nbytes; /* number of bytes stat */
uint64_t ipgpc_epackets; /* number of packets in error */
int ipgpc_def_class_id = -1; /* class id of default class */
size_t ipgpc_num_fltrs; /* number of loaded filter */
size_t ipgpc_num_cls; /* number of loaded classes */
/* max number of allowable filters */
size_t ipgpc_max_num_filters = IPGPC_DEFAULT_MAX_FILTERS;
/* max number of allowable classes */
size_t ipgpc_max_num_classes = IPGPC_DEFAULT_MAX_CLASSES;
size_t ipgpc_max_filters = 0; /* set in /etc/system */
size_t ipgpc_max_classes = 0; /* set in /etc/system */
ipp_stat_t *ipgpc_global_stats = NULL; /* global stats structure */
/* Statics */
static trie saddr_trie; /* IPv4 source address trie */
static trie daddr_trie; /* IPv4 destination address trie */
static trie sport_trie; /* source port trie */
static trie dport_trie; /* destination port trie */
static trie saddr6_trie; /* IPv6 source address trie */
static trie daddr6_trie; /* IPv6 destination address trie */
static ht_node_t proto_table[TABLE_SIZE]; /* protocol table */
static ht_node_t uid_table[TABLE_SIZE]; /* IPGPC_UID table */
static ht_node_t projid_table[TABLE_SIZE]; /* IPGPC_PROJID table */
static ht_node_t if_table[TABLE_SIZE]; /* Interface ID table */
static ht_node_t if_grpnm_table[TABLE_SIZE]; /* Interface Group Name table */
static ht_node_t dir_table[TABLE_SIZE]; /* packet direction table */
static ipp_action_id_t ipgpc_aid; /* the action id for ipgpc */
static int global_statinit(void);
static void insert_ipgpc_trie_list_info(int, size_t, trie, uint16_t);
static int initialize_tries(void);
static void insert_ipgpc_table_list_info(int, hash_table, int, uint16_t);
static void initialize_tables(void);
static void initialize_ba_tables(void);
static void element_node_ref(element_node_t *);
static void element_node_unref(element_node_t *);
static int element_node_cache_constructor(void *, void *, int);
static int filter_name2id(unsigned *, char[], int32_t, int);
static int class_name2id(unsigned *, char[], int);
static boolean_t iscontinuousmask(uint32_t, uint8_t);
static void insertfid(int, ipgpc_filter_t *, uint_t);
static void common_addfilter(fid_t *, int);
static void v4_addfilter(fid_t *, int);
static void v6_addfilter(fid_t *, int);
static void reset_dontcare_stats(void);
static int class_statinit(ipgpc_class_t *, int);
static int insertcid(ipgpc_class_t *, int *);
static void common_removefilter(int, fid_t *);
static void v4_removefilter(int, fid_t *);
static void v6_removefilter(int, fid_t *);
static void removecid(int);
static void remove_from_cid_filter_list(int, int);
static void removeclasses(ipp_flags_t);
static void freetriev6nodes(node_t **);
static int ht_match_insert(ht_match_t *, int, uint16_t);
static int update_class_stats(ipp_stat_t *, void *, int);
static int update_global_stats(ipp_stat_t *, void *, int);
static int build_class_nvlist(nvlist_t **, ipgpc_class_t *, boolean_t);
static int build_filter_nvlist(nvlist_t **, ipgpc_filter_t *, char *);
/*
* Module initialization code
*/
/*
* global_statinit()
*
* initializes global stats for ipgpc action module.
* global include:
* - number of filters loaded
* - number of classes loaded
* - number of packets that have passed through ipgpc since action create
* - number of bytes that have passed through ipgpc since action create
* if ipp_stat_create fails, an error code is returned
* if ipp_stat_named_init fails, an error code is returned
* 0 is returned on success
*/
static int
global_statinit(void)
{
int rc;
globalstats_t *gblsnames = NULL;
/* create stat structure */
if ((rc = ipp_stat_create(ipgpc_aid, "ipgpc_global_stats", 5,
update_global_stats, NULL, &ipgpc_global_stats)) != 0) {
ipgpc0dbg(("global_statinit: error creating ipp_stat entry"));
return (rc);
}
ASSERT(ipgpc_global_stats != NULL);
gblsnames = (globalstats_t *)ipgpc_global_stats->ipps_data;
ASSERT(gblsnames != NULL);
/* add stat name entries */
if ((rc = ipp_stat_named_init(ipgpc_global_stats, "nfilters",
IPP_STAT_UINT32, &gblsnames->nfilters)) != 0) {
return (rc);
}
if ((rc = ipp_stat_named_init(ipgpc_global_stats, "nclasses",
IPP_STAT_UINT32, &gblsnames->nclasses)) != 0) {
return (rc);
}
if ((rc = ipp_stat_named_init(ipgpc_global_stats, "nbytes",
IPP_STAT_UINT64, &gblsnames->nbytes)) != 0) {
return (rc);
}
if ((rc = ipp_stat_named_init(ipgpc_global_stats, "npackets",
IPP_STAT_UINT64, &gblsnames->npackets)) != 0) {
return (rc);
}
if ((rc = ipp_stat_named_init(ipgpc_global_stats, "epackets",
IPP_STAT_UINT64, &gblsnames->epackets)) != 0) {
return (rc);
}
ipp_stat_install(ipgpc_global_stats);
return (0);
}
static void
insert_ipgpc_trie_list_info(int trie_id, size_t key_len, trie in_trie,
uint16_t mask)
{
ipgpc_trie_list[trie_id].trie = in_trie;
rw_init(&ipgpc_trie_list[trie_id].rw_lock, NULL, RW_DEFAULT, NULL);
ipgpc_trie_list[trie_id].key_len = key_len;
ipgpc_trie_list[trie_id].info.mask = mask;
ipgpc_trie_list[trie_id].info.dontcareonly = B_TRUE;
}
static int
initialize_tries(void)
{
/* IPv4 Source Address field structure */
if ((saddr_trie = create_node(KM_NOSLEEP)) == NULL) {
return (ENOMEM);
}
saddr_trie->isroot = 1;
insert_ipgpc_trie_list_info(IPGPC_TRIE_SADDRID, IP_ABITS, saddr_trie,
SADDR_MASK);
/* IPv4 Destination Address field structure */
if ((daddr_trie = create_node(KM_NOSLEEP)) == NULL) {
return (ENOMEM);
}
daddr_trie->isroot = 1;
insert_ipgpc_trie_list_info(IPGPC_TRIE_DADDRID, IP_ABITS, daddr_trie,
DADDR_MASK);
/* TCP Source Port field structure */
if ((sport_trie = create_node(KM_NOSLEEP)) == NULL) {
return (ENOMEM);
}
sport_trie->isroot = 1;
insert_ipgpc_trie_list_info(IPGPC_TRIE_SPORTID, BITLENGTH(uint16_t),
sport_trie, SPORT_MASK);
/* TCP Destination Port field structure */
if ((dport_trie = create_node(KM_NOSLEEP)) == NULL) {
return (ENOMEM);
}
dport_trie->isroot = 1;
insert_ipgpc_trie_list_info(IPGPC_TRIE_DPORTID, BITLENGTH(uint16_t),
dport_trie, DPORT_MASK);
/* IPv6 Source Address field structure */
if ((saddr6_trie = create_node(KM_NOSLEEP)) == NULL) {
return (ENOMEM);
}
saddr6_trie->isroot = 1;
insert_ipgpc_trie_list_info(IPGPC_TRIE_SADDRID6, IPV6_ABITS,
saddr6_trie, SADDR6_MASK);
/* IPv6 Destination Address field structure */
if ((daddr6_trie = create_node(KM_NOSLEEP)) == NULL) {
return (ENOMEM);
}
daddr6_trie->isroot = 1;
insert_ipgpc_trie_list_info(IPGPC_TRIE_DADDRID6, IPV6_ABITS,
daddr6_trie, DADDR6_MASK);
return (0);
}
static void
insert_ipgpc_table_list_info(int table_id, hash_table table, int wildcard,
uint16_t mask)
{
ipgpc_table_list[table_id].table = table;
ipgpc_table_list[table_id].wildcard = wildcard;
ipgpc_table_list[table_id].info.mask = mask;
ipgpc_table_list[table_id].info.dontcareonly = B_TRUE;
}
static void
initialize_tables(void)
{
/* Protocol selector structure */
insert_ipgpc_table_list_info(PROTOID_IDX, proto_table,
IPGPC_UNSPECIFIED, PROTO_MASK);
/* UID selector structure */
insert_ipgpc_table_list_info(UID_IDX, uid_table, IPGPC_WILDCARD,
UID_MASK);
/* PROJID selector structure */
insert_ipgpc_table_list_info(PROJID_IDX, projid_table, IPGPC_WILDCARD,
PROJID_MASK);
/* IF_INDEX selector structure */
insert_ipgpc_table_list_info(IF_IDX, if_table, IPGPC_UNSPECIFIED,
IF_MASK);
/* IF_GRPNM_INDEX selector structure */
insert_ipgpc_table_list_info(IF_GRPNM_IDX, if_grpnm_table,
IPGPC_WILDCARD, IF_GRPNM_MASK);
/* DIR selector structure */
insert_ipgpc_table_list_info(DIR_IDX, dir_table, IPGPC_UNSPECIFIED,
DIR_MASK);
}
static void
initialize_ba_tables(void)
{
/* DS (ToS/Traffic Class) field structure */
ipgpc_ds_table_id.info.mask = DS_MASK;
ipgpc_ds_table_id.info.dontcareonly = B_TRUE;
}
static void
element_node_ref(element_node_t *element)
{
atomic_add_32(&element->element_refcnt, 1);
ASSERT(element->element_refcnt > 1);
}
static void
element_node_unref(element_node_t *element)
{
ASSERT(element->element_refcnt > 0);
if (atomic_add_32_nv(&element->element_refcnt, -1) == 0) {
kmem_cache_free(element_node_cache, element);
}
}
/* ARGSUSED1 */
static int
element_node_cache_constructor(void *buf, void *cdrarg, int kmflags)
{
element_node_t *node = buf;
node->element_ref = element_node_ref;
node->element_unref = element_node_unref;
return (0);
}
/* prime values to be used for hashing of filter and class tables */
#define IPGPC_PRIMES() {0, 0, 0, 5, 11, 23, 47, 89, 191, 383, 503, 761, \
1009, 1531, 2003, 2503, 3067, 3511, 4001, 5003, 6143, \
10007, 12281, 15013, 20011, 24571, 49139, 98299, \
100003, 196597, 393209, 786431, 1000003, 1251409, \
1572853, 3145721, 0}
/*
* ipgpc_initialize(in_aid)
*
* initializes locks, data structures, configuration variables used and
* sets globals. Will fail on memory or initialization error.
*/
int
ipgpc_initialize(ipp_action_id_t in_aid)
{
ipgpc_class_t def_class;
int i;
int rc;
int sizes[] = IPGPC_PRIMES();
/* initialize globals */
ipgpc_aid = in_aid; /* store away action id for ipgpc */
ipgpc_num_fltrs = 0;
ipgpc_num_cls = 0;
ipgpc_npackets = 0;
ipgpc_nbytes = 0;
ipgpc_epackets = 0;
/* check for user tunable maximums (set in /etc/system) */
if (ipgpc_max_filters > 0) {
/* start with a reasonably small value to find closest prime */
for (i = 3; i < sizeof (sizes) / sizeof (*sizes) - 1; ++i) {
if (sizes[i] >= ipgpc_max_filters) {
break;
}
}
if (sizes[i] == 0) {
ipgpc0dbg(("ipgpc_initialize: ipgpc_max_filters " \
"out of range"));
/* use the largest allowable value */
ipgpc_max_num_filters = sizes[(i - 1)];
} else {
ipgpc_max_num_filters = sizes[i];
}
}
if (ipgpc_max_classes > 0) {
/* start with a reasonably small value to find closest prime */
for (i = 3; i < sizeof (sizes) / sizeof (*sizes) - 1; ++i) {
if (sizes[i] >= ipgpc_max_classes) {
break;
}
}
if (sizes[i] == 0) {
ipgpc0dbg(("ipgpc_initialize: ipgpc_max_classes " \
"out of range"));
/* use the largest allowable value */
ipgpc_max_num_classes = sizes[(i - 1)];
} else {
ipgpc_max_num_classes = sizes[i];
}
}
/* create filter id list */
ipgpc_fid_list =
kmem_zalloc(sizeof (fid_t) * ipgpc_max_num_filters, KM_NOSLEEP);
if (ipgpc_fid_list == NULL) {
ipgpc0dbg(("ipgpc_initialize: failed to create fid list"));
return (ENOMEM);
}
/* create class id list */
ipgpc_cid_list = kmem_zalloc(sizeof (cid_t) * ipgpc_max_num_classes,
KM_NOSLEEP);
if (ipgpc_cid_list == NULL) {
ipgpc0dbg(("ipgpc_initialize: failed to create cid list"));
return (ENOMEM);
}
/* create object caches */
element_node_cache = kmem_cache_create("element_node_cache",
sizeof (element_node_t), 0, element_node_cache_constructor,
NULL, NULL, NULL, NULL, 0);
trie_node_cache = kmem_cache_create("trie_node_cache",
sizeof (node_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
ht_node_cache = kmem_cache_create("ht_node_cache",
sizeof (ht_node_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
ht_match_cache = kmem_cache_create("ht_match_cache",
sizeof (ht_match_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
/* initialize tries, catch memory errors */
if ((rc = initialize_tries()) != 0) {
return (rc);
}
initialize_tables(); /* no memory is allocated here */
initialize_ba_tables(); /* no memory is allocated here */
if ((rc = global_statinit()) != 0) { /* init global stats */
ipgpc0dbg(("ipgpc_initialize: global_statinit error " \
"%d", rc));
return (rc);
}
/* create default class */
bzero(&def_class, sizeof (ipgpc_class_t));
def_class.next_action = IPP_ACTION_CONT;
def_class.gather_stats = B_FALSE; /* don't gather stats by default */
(void) strcpy(def_class.class_name, "default");
def_class.originator = IPP_CONFIG_PERMANENT; /* label as permanent */
/* add default class and record default class id */
if ((rc = insertcid(&def_class, &ipgpc_def_class_id)) != ENOENT) {
ipgpc0dbg(("ipgpc_initialize: insert of default class failed" \
" with error %d", rc));
return (rc);
}
return (0);
}
/*
* Module modify code
*/
/*
* name_hash(name, M)
*
* hash function for a string (name) of lenght M
*/
unsigned
name_hash(char *name, size_t M)
{
unsigned h;
for (h = 0; *name != '\0'; name++) {
h = ((64 * h) + *name);
}
return ((h % M));
}
/*
* ipgpc_filter_destructor(filter)
*
* frees any allocated memory pointed to in the filter structure
* this function should be run before freeing an ipgpc_filter_t
*/
void
ipgpc_filter_destructor(ipgpc_filter_t *filter)
{
if (filter->filter_comment != NULL) {
kmem_free(filter->filter_comment,
(strlen(filter->filter_comment) + 1));
}
if (filter->saddr_hostname != NULL) {
kmem_free(filter->saddr_hostname,
(strlen(filter->saddr_hostname) + 1));
}
if (filter->daddr_hostname != NULL) {
kmem_free(filter->daddr_hostname,
(strlen(filter->daddr_hostname) + 1));
}
}
/*
* filter_name2id(*out_id, name, filter_instance, in_num_filters)
*
* looks up name and instance in filter id table
* checks in_num_filters against max filter boundary
* if found, returns EEXIST and places the id in out_id
* if not found, returns ENOENT and places the new id in out_id
* if no additional filter ids are available, ENOMEM is returned
*/
static int
filter_name2id(unsigned *out_id, char name[], int32_t filter_instance,
int in_num_filters)
{
unsigned h;
int dirty = -1; /* set dirty to not found */
if (in_num_filters >= ipgpc_max_num_filters) {
return (ENOSPC); /* will exceed maximum number of filters */
}
/*
* search until fid w/ matching name is found or clean space is found
* if clean space is found, return first dirty space found or if
* none werer found, return clean space
*/
h = name_hash(name, ipgpc_max_num_filters);
while ((ipgpc_fid_list[h].info != 0) &&
((ipgpc_fid_list[h].filter.filter_instance != filter_instance) ||
(strcmp(name, ipgpc_fid_list[h].filter.filter_name) != 0))) {
if (dirty == -1) { /* this is the first dirty space */
if (ipgpc_fid_list[h].info == -1) { /* dirty */
dirty = h;
}
}
h = (h + 1) % ipgpc_max_num_filters;
}
/*
* check to see if searching stopped because a clean spot was found
* and a dirty space was seen before
*/
if ((dirty != -1) && (ipgpc_fid_list[h].info == 0)) {
*out_id = dirty;
return (ENOENT); /* name does not exist in table */
} else if (ipgpc_fid_list[h].info == 0) {
*out_id = h;
return (ENOENT); /* name does not exist in table */
} else {
*out_id = h;
if (ipgpc_fid_list[h].info == -1) {
return (ENOENT);
} else {
return (EEXIST); /* name exists in table */
}
}
}
/*
* class_name2id(*out_id, name, in_num_classes)
*
* looks up name in class id table
* checks in_num_classes against max class boundry
* if found, returns EEXIST and places the id in out_id
* if not found, returns ENOENT and places the new id in out_id
* if no additional class ids are available, ENOSPC is returned
*/
static int
class_name2id(unsigned *out_id, char name[], int in_num_classes)
{
unsigned h;
int dirty = -1; /* set dirty to not found */
if (in_num_classes >= ipgpc_max_num_classes) {
return (ENOSPC); /* will exceed maximum number of classes */
}
/*
* search until cid w/ matching name is found or clean space is found
* if clean space is found, return first dirty space found or if
* none were found, return clean space
*/
h = name_hash(name, ipgpc_max_num_classes);
while ((ipgpc_cid_list[h].info != 0) &&
(strcmp(name, ipgpc_cid_list[h].aclass.class_name) != 0)) {
if (dirty == -1) { /* this is the first dirty space */
if (ipgpc_cid_list[h].info == -1) { /* dirty */
dirty = h;
}
}
h = (h + 1) % ipgpc_max_num_classes;
}
/*
* check to see if searching stopped because a clean spot was found
* and a dirty space was seen before
*/
if ((dirty != -1) && (ipgpc_cid_list[h].info == 0)) {
*out_id = dirty;
return (ENOENT); /* name does not exist in table */
} else if (ipgpc_cid_list[h].info == 0) {
*out_id = h;
return (ENOENT); /* name does not exist in table */
} else {
*out_id = h;
if (ipgpc_cid_list[h].info == -1) { /* name did exist */
return (ENOENT); /* name does not exist in table */
} else {
return (EEXIST); /* name exists in table */
}
}
}
/*
* ipgpc_parse_filter(filter, nvlp)
*
* given a name value pair list, a filter structure is parsed. A valid
* filter must have a filter_name and originator id. Any value that is not
* present, will be given the default wildcard value for that selector
*/
int
ipgpc_parse_filter(ipgpc_filter_t *filter, nvlist_t *nvlp)
{
uint_t nelem = 4; /* an IPv6 address is an uint32_t array[4] */
uint32_t *mask;
uint32_t *addr;
char *s;
int i;
in6_addr_t zeroaddr = IN6ADDR_ANY_INIT;
/* parse filter name */
if (nvlist_lookup_string(nvlp, CLASSIFIER_FILTER_NAME, &s) != 0) {
return (EINVAL); /* filter name is missing, error */
}
/* parse originator */
if (nvlist_lookup_uint32(nvlp, IPP_CONFIG_ORIGINATOR,
&filter->originator) != 0) {
ipgpc0dbg(("ipgpc_parse_filter: originator missing"));
return (EINVAL);
}
/* check for max name length */
if ((strlen(s) + 1) > MAXNAMELEN) {
ipgpc0dbg(("ipgpc_parse_filter: filter name length > " \
"MAXNAMELEN"));
return (EINVAL);
}
bcopy(s, filter->filter_name, (strlen(s) + 1));
/* parse interface group name */
if (nvlist_lookup_string(nvlp, IPGPC_IF_GROUPNAME, &s) != 0) {
filter->if_groupname[0] = '\0';
} else {
/* check max interface group name lenght */
if ((strlen(s) + 1) > LIFNAMSIZ) {
ipgpc0dbg(("ipgpc_parse_filter: interface group name" \
" > LIFNAMSIZ"));
return (EINVAL);
}
bcopy(s, filter->if_groupname, (strlen(s) + 1));
}
/* parse uid */
if (nvlist_lookup_int32(nvlp, IPGPC_UID, &filter->uid) != 0) {
filter->uid = IPGPC_WILDCARD;
}
/* parse projid */
if (nvlist_lookup_int32(nvlp, IPGPC_PROJID, &filter->projid) != 0) {
filter->projid = IPGPC_WILDCARD;
}
/* parse if_index */
if (nvlist_lookup_uint32(nvlp, IPGPC_IF_INDEX, &filter->if_index)
!= 0) {
filter->if_index = 0;
}
/* parse direction */
if (nvlist_lookup_uint32(nvlp, IPGPC_DIR, &filter->direction) != 0) {
filter->direction = 0;
}
/* parse proto */
if (nvlist_lookup_byte(nvlp, IPGPC_PROTO, &filter->proto) != 0) {
filter->proto = 0;
}
/*
* parse dsfield mask, if mask is present and dsfield value is not,
* then this is an invalid filter configuration
*/
if (nvlist_lookup_byte(nvlp, IPGPC_DSFIELD_MASK, &filter->dsfield_mask)
== 0) {
/* parse dsfield */
if (nvlist_lookup_byte(nvlp, IPGPC_DSFIELD, &filter->dsfield)
!= 0) {
ipgpc0dbg(("ipgpc_parse_filter: dsfield missing" \
" when dsfield_mask 0x%x is present",
filter->dsfield_mask));
return (EINVAL);
}
} else {
filter->dsfield_mask = 0;
/* check to see if user added dsfield, but not dsfield_mask */
if (nvlist_lookup_byte(nvlp, IPGPC_DSFIELD, &filter->dsfield)
== 0) {
ipgpc0dbg(("ipgpc_parse_filter: dsfield_mask missing" \
" when dsfield 0x%x is present",
filter->dsfield));
return (EINVAL);
}
filter->dsfield = 0;
}
/* parse source port */
if (nvlist_lookup_uint16(nvlp, IPGPC_SPORT, &filter->sport) != 0) {
filter->sport = 0;
}
/*
* parse source port mask, mask and value must be present, or neither
*/
if (nvlist_lookup_uint16(nvlp, IPGPC_SPORT_MASK, &filter->sport_mask)
!= 0) {
if (filter->sport != 0) {
ipgpc0dbg(("ipgpc_parse_filter: sport_mask missing " \
"to mask sport %u", filter->sport));
return (EINVAL);
}
filter->sport_mask = 0;
} else { /* sport mask is present */
if (filter->sport == 0) {
ipgpc0dbg(("ipgpc_parse_filter: sport missing " \
"when sport_mask %u is present",
filter->sport_mask));
return (EINVAL);
}
}
/* check for non-continuous mask */
if (!iscontinuousmask(filter->sport_mask, BITLENGTH(uint16_t))) {
ipgpc0dbg(("ipgpc_parse_filter: sport_mask is " \
"non-continuous"));
return (EINVAL);
}
/* parse destination port */
if (nvlist_lookup_uint16(nvlp, IPGPC_DPORT, &filter->dport) != 0) {
filter->dport = 0;
}
/*
* parse destination port mask, mask and value must be present,
* or neither
*/
if (nvlist_lookup_uint16(nvlp, IPGPC_DPORT_MASK, &filter->dport_mask)
!= 0) {
if (filter->dport != 0) {
ipgpc0dbg(("ipgpc_parse_filter: dport_mask missing " \
"to mask dport %u", filter->dport));
return (EINVAL);
}
filter->dport_mask = 0;
} else { /* dport mask is present */
if (filter->dport == 0) {
ipgpc0dbg(("ipgpc_parse_filter: dport missing " \
"when dport_mask %u is present",
filter->dport_mask));
return (EINVAL);
}
}
/* check for non-continuous mask */
if (!iscontinuousmask(filter->dport_mask, BITLENGTH(uint16_t))) {
ipgpc0dbg(("ipgpc_parse_filter: dport_mask is " \
"non-continuous"));
return (EINVAL);
}
/* parse precedence */
if (nvlist_lookup_uint32(nvlp, IPGPC_PRECEDENCE, &filter->precedence)
!= 0) {
filter->precedence = UINT_MAX; /* worst precedence */
}
/* parse priority */
if (nvlist_lookup_uint32(nvlp, IPGPC_PRIORITY, &filter->priority)
!= 0) {
filter->priority = 0; /* worst priority */
}
/* parse filter type */
if (nvlist_lookup_byte(nvlp, IPGPC_FILTER_TYPE, &filter->filter_type)
!= 0) {
filter->filter_type = IPGPC_GENERIC_FLTR;
}
/* parse filter instance */
if (nvlist_lookup_int32(nvlp, IPGPC_FILTER_INSTANCE,
&filter->filter_instance) != 0) {
filter->filter_instance = -1;
}
/* parse filter private field */
if (nvlist_lookup_string(nvlp, IPGPC_FILTER_PRIVATE, &s) != 0) {
filter->filter_comment = NULL;
} else {
filter->filter_comment = kmem_alloc((strlen(s) + 1), KM_SLEEP);
(void) strcpy(filter->filter_comment, s);
}
/*
* parse source address mask, if address is present, mask must be
* present
*/
if (nvlist_lookup_uint32_array(nvlp, IPGPC_SADDR_MASK, &mask, &nelem)
!= 0) {
/* check if source address is present */
if (nvlist_lookup_uint32_array(nvlp, IPGPC_SADDR, &addr,
&nelem) == 0) {
ipgpc0dbg(("ipgpc_parse_filter: source address mask " \
"missing"));
return (EINVAL);
} else { /* both saddr and saddr_mask absent */
bcopy(zeroaddr.s6_addr32, filter->saddr.s6_addr32,
sizeof (filter->saddr.s6_addr32));
}
bcopy(zeroaddr.s6_addr32, filter->saddr_mask.s6_addr32,
sizeof (filter->saddr_mask.s6_addr32));
} else { /* saddr_mask present */
/* parse source address */
if (nvlist_lookup_uint32_array(nvlp, IPGPC_SADDR, &addr,
&nelem) != 0) {
ipgpc0dbg(("ipgpc_parse_filter: source address " \
"missing"));
return (EINVAL);
} else { /* saddr present */
bcopy(addr, filter->saddr.s6_addr32,
sizeof (filter->saddr.s6_addr32));
}
bcopy(mask, filter->saddr_mask.s6_addr32,
sizeof (filter->saddr_mask.s6_addr32));
}
/* check for non-continuous mask */
if ((filter->filter_type == IPGPC_V6_FLTR) ||
(filter->filter_type == IPGPC_GENERIC_FLTR)) {
boolean_t zero_found = B_FALSE;
for (i = 0; i < 4; ++i) {
if (filter->saddr_mask.s6_addr32[i] == 0) {
zero_found = B_TRUE;
} else {
if (zero_found) {
ipgpc0dbg(("ipgpc_parse_filter: "
"saddr_mask is non-continuous"));
return (EINVAL);
}
}
if (!iscontinuousmask(filter->saddr_mask.s6_addr32[i],
IP_ABITS)) {
ipgpc0dbg(("ipgpc_parse_filter: saddr_mask " \
"is non-continuous"));
return (EINVAL);
}
}
} else { /* IPGPC_V4_FLTR */
if (!iscontinuousmask((V4_PART_OF_V6(filter->saddr_mask)),
IP_ABITS)) {
ipgpc0dbg(("ipgpc_parse_filter: saddr_mask is " \
"non-continuous"));
return (EINVAL);
}
}
/* parse source address hostname */
if (nvlist_lookup_string(nvlp, IPGPC_SADDR_HOSTNAME, &s) != 0) {
filter->saddr_hostname = NULL;
} else {
filter->saddr_hostname = kmem_alloc((strlen(s) + 1), KM_SLEEP);
(void) strcpy(filter->saddr_hostname, s);
}
/*
* parse destination address mask, if address is present, mask must be
* present
*/
if (nvlist_lookup_uint32_array(nvlp, IPGPC_DADDR_MASK, &mask, &nelem)
!= 0) {
/* check if destination address is present */
if (nvlist_lookup_uint32_array(nvlp, IPGPC_DADDR, &addr,
&nelem) == 0) {
ipgpc0dbg(("ipgpc_parse_filter: destination address " \
"mask missing"));
return (EINVAL);
} else { /* both daddr and daddr_mask absent */
bcopy(zeroaddr.s6_addr32, filter->daddr.s6_addr32,
sizeof (filter->daddr.s6_addr32));
}
bcopy(zeroaddr.s6_addr32, filter->daddr_mask.s6_addr32,
sizeof (filter->daddr_mask.s6_addr32));
} else { /* daddr_mask present */
/* parse destination address */
if (nvlist_lookup_uint32_array(nvlp, IPGPC_DADDR, &addr,
&nelem) != 0) {
ipgpc0dbg(("ipgpc_parse_filter: destination address " \
"missing"));
return (EINVAL);
} else { /* daddr present */
bcopy(addr, filter->daddr.s6_addr32,
sizeof (filter->daddr.s6_addr32));
}
bcopy(mask, filter->daddr_mask.s6_addr32,
sizeof (filter->daddr_mask.s6_addr32));
}
/* check for non-continuous mask */
if ((filter->filter_type == IPGPC_V6_FLTR) ||
(filter->filter_type == IPGPC_GENERIC_FLTR)) {
boolean_t zero_found = B_FALSE;
for (i = 0; i < 4; ++i) {
if (filter->daddr_mask.s6_addr32[i] == 0) {
zero_found = B_TRUE;
} else {
if (zero_found) {
ipgpc0dbg(("ipgpc_parse_filter: "
"daddr_mask is non-continuous"));
return (EINVAL);
}
}
if (!iscontinuousmask(filter->daddr_mask.s6_addr32[i],
IP_ABITS)) {
ipgpc0dbg(("ipgpc_parse_filter: daddr_mask " \
"is non-continuous"));
return (EINVAL);
}
}
} else { /* IPGPC_V4_FLTR */
if (!iscontinuousmask((V4_PART_OF_V6(filter->daddr_mask)),
IP_ABITS)) {
ipgpc0dbg(("ipgpc_parse_filter: daddr_mask is " \
"non-continuous"));
return (EINVAL);
}
}
/* parse destination address hostname */
if (nvlist_lookup_string(nvlp, IPGPC_DADDR_HOSTNAME, &s) != 0) {
filter->daddr_hostname = NULL;
} else {
filter->daddr_hostname = kmem_alloc((strlen(s) + 1), KM_SLEEP);
(void) strcpy(filter->daddr_hostname, s);
}
return (0);
}
/*
* iscontinuousmask(mask, len)
*
* Searches a given mask of length len from MSB to LSB looking for a zero
* bit followed by one bit. A continuous mask must be a string of zero or
* more ones followed by a string of zero or more zeros, which would return
* B_TRUE. Otherwise, it is not continuous and this function returns B_FALSE.
*/
static boolean_t
iscontinuousmask(uint32_t mask, uint8_t len)
{
uint8_t pos;
boolean_t zero_found = B_FALSE;
for (pos = len; pos > 0; --pos) {
if (EXTRACTBIT(mask, (pos - 1), len) == 0) {
zero_found = B_TRUE;
} else {
if (zero_found) {
return (B_FALSE);
}
}
}
return (B_TRUE);
}
/*
* insertfid(filter_id, filter, class_id)
*
* creates a filter id (fid) structure for filter with filter_id.
* filter is associated with the input class id
* it is assumed that a fid will not be inserted for a filter that already
* exists by the same name.
*/
static void
insertfid(int filter_id, ipgpc_filter_t *filter, uint_t class_id)
{
ipgpc_fid_list[filter_id].info = 1;
ipgpc3dbg(("insert_fid: adding filter %s to class %s",
filter->filter_name,
ipgpc_cid_list[class_id].aclass.class_name));
ipgpc_fid_list[filter_id].class_id = class_id;
ipgpc_fid_list[filter_id].filter = *filter;
ipgpc_fid_list[filter_id].insert_map = 0;
}
static void
common_addfilter(fid_t *fid, int filter_id)
{
int if_grpnm_hv;
/* start trie inserts */
/* add source port selector */
if (t_insert(&ipgpc_trie_list[IPGPC_TRIE_SPORTID], filter_id,
fid->filter.sport, fid->filter.sport_mask) == NORMAL_VALUE) {
fid->insert_map |= SPORT_MASK;
}
/* add destination port selector */
if (t_insert(&ipgpc_trie_list[IPGPC_TRIE_DPORTID], filter_id,
fid->filter.dport, fid->filter.dport_mask) == NORMAL_VALUE) {
fid->insert_map |= DPORT_MASK;
}
/* end trie inserts */
/* add diffserv field selector */
mutex_enter(&ipgpc_ds_table_id.lock);
if (ba_insert(&ipgpc_ds_table_id, filter_id, fid->filter.dsfield,
fid->filter.dsfield_mask) == NORMAL_VALUE) {
fid->insert_map |= DS_MASK;
}
mutex_exit(&ipgpc_ds_table_id.lock);
/* start table inserts */
mutex_enter(&ipgpc_table_list_lock);
/* add protocol selector */
if (ht_insert(&ipgpc_table_list[PROTOID_IDX], filter_id,
fid->filter.proto) == NORMAL_VALUE) {
fid->insert_map |= PROTO_MASK;
}
/* add UID selector */
if (ht_insert(&ipgpc_table_list[UID_IDX], filter_id, fid->filter.uid)
== NORMAL_VALUE) {
fid->insert_map |= UID_MASK;
}
/* add PROJID selector */
if (ht_insert(&ipgpc_table_list[PROJID_IDX], filter_id,
fid->filter.projid) == NORMAL_VALUE) {
fid->insert_map |= PROJID_MASK;
}
/* add interface index selector */
if (ht_insert(&ipgpc_table_list[IF_IDX], filter_id,
fid->filter.if_index) == NORMAL_VALUE) {
fid->insert_map |= IF_MASK;
}
/* add interface groupname selector */
if (fid->filter.if_groupname[0] == '\0') {
if_grpnm_hv = IPGPC_WILDCARD;
} else {
if_grpnm_hv = name_hash(fid->filter.if_groupname, TABLE_SIZE);
}
if (ht_insert(&ipgpc_table_list[IF_GRPNM_IDX], filter_id, if_grpnm_hv)
== NORMAL_VALUE) {
fid->insert_map |= IF_GRPNM_MASK;
}
/* add direction selector */
if (ht_insert(&ipgpc_table_list[DIR_IDX], filter_id,
fid->filter.direction) == NORMAL_VALUE) {
fid->insert_map |= DIR_MASK;
}
mutex_exit(&ipgpc_table_list_lock);
/* end table inserts */
}
static void
v4_addfilter(fid_t *fid, int filter_id)
{
/* add IPv4 source address selector */
if (t_insert(&ipgpc_trie_list[IPGPC_TRIE_SADDRID], filter_id,
V4_PART_OF_V6(fid->filter.saddr),
V4_PART_OF_V6(fid->filter.saddr_mask)) == NORMAL_VALUE) {
fid->insert_map |= SADDR_MASK;
}
/* add IPv4 destination address selector */
if (t_insert(&ipgpc_trie_list[IPGPC_TRIE_DADDRID], filter_id,
V4_PART_OF_V6(fid->filter.daddr),
V4_PART_OF_V6(fid->filter.daddr_mask)) == NORMAL_VALUE) {
fid->insert_map |= DADDR_MASK;
}
}
static void
v6_addfilter(fid_t *fid, int filter_id)
{
/* add IPv6 source address selector */
if (t_insert6(&ipgpc_trie_list[IPGPC_TRIE_SADDRID6], filter_id,
fid->filter.saddr, fid->filter.saddr_mask) == NORMAL_VALUE) {
fid->insert_map |= SADDR6_MASK;
}
/* add IPv6 destination address selector */
if (t_insert6(&ipgpc_trie_list[IPGPC_TRIE_DADDRID6], filter_id,
fid->filter.daddr, fid->filter.daddr_mask) == NORMAL_VALUE) {
fid->insert_map |= DADDR6_MASK;
}
}
/*
* ipgpc_addfilter(filter, class_name, flags)
*
* add the specified filter and associate it with the specified class
* name
* - add filter id to filter list
* - add filter keys to selector structures
* - ENOENT is returned if class does not exist
* - EEXIST is returned if add failed because filter name exists
* - ENOMEM is returned if no memory is available to add a new filter
* - EINVAL if filter.filter_type is invalid
* - 0 is returned on success
* flags is unused currently
*/
/* ARGSUSED1 */
int
ipgpc_addfilter(ipgpc_filter_t *filter, char *class_name, ipp_flags_t flags)
{
unsigned filter_id;
int err = 0;
fid_t *fid;
unsigned class_id;
if ((err = class_name2id(&class_id, class_name, ipgpc_num_cls)) !=
EEXIST) {
ipgpc0dbg(("ipgpc_addfilter: class lookup error %d", err));
return (err);
}
mutex_enter(&ipgpc_fid_list_lock);
/* make sure filter does not already exist */
if ((err = filter_name2id(&filter_id, filter->filter_name,
filter->filter_instance, ipgpc_num_fltrs + 1)) == EEXIST) {
ipgpc0dbg(("ipgpc_addfilter: filter name %s already exists",
filter->filter_name));
mutex_exit(&ipgpc_fid_list_lock);
return (err);
} else if (err == ENOSPC) {
ipgpc0dbg(("ipgpc_addfilter: can not add filter %s, " \
"ipgpc_max_num_filteres has been reached",
filter->filter_name));
mutex_exit(&ipgpc_fid_list_lock);
return (err);
}
insertfid(filter_id, filter, class_id);
fid = &ipgpc_fid_list[filter_id];
/* add filter id to selector structures */
switch (fid->filter.filter_type) {
case IPGPC_GENERIC_FLTR:
/* add filter id to all selectors */
common_addfilter(fid, filter_id);
v4_addfilter(fid, filter_id);
v6_addfilter(fid, filter_id);
break;
case IPGPC_V4_FLTR:
/* add filter to common and V4 selectors */
common_addfilter(fid, filter_id);
v4_addfilter(fid, filter_id);
break;
case IPGPC_V6_FLTR:
/* add filter to common and V6 selectors */
common_addfilter(fid, filter_id);
v6_addfilter(fid, filter_id);
break;
default:
ipgpc0dbg(("ipgpc_addfilter(): invalid filter type %d",
fid->filter.filter_type));
mutex_exit(&ipgpc_fid_list_lock);
return (EINVAL);
}
/* check to see if this is a catch all filter, which we reject */
if (fid->insert_map == 0) {
ipgpc0dbg(("ipgpc_addfilter(): filter %s rejected because " \
"catch all filters are not supported\n",
filter->filter_name));
/* cleanup what we allocated */
/* remove filter from filter list */
ipgpc_fid_list[filter_id].info = -1;
ipgpc_fid_list[filter_id].filter.filter_name[0] = '\0';
reset_dontcare_stats(); /* need to fixup stats */
mutex_exit(&ipgpc_fid_list_lock);
return (EINVAL);
} else { /* associate filter with class */
mutex_enter(&ipgpc_cid_list_lock);
(void) ipgpc_list_insert(&ipgpc_cid_list[class_id].filter_list,
filter_id);
mutex_exit(&ipgpc_cid_list_lock);
}
mutex_exit(&ipgpc_fid_list_lock);
atomic_add_long(&ipgpc_num_fltrs, 1);
ipgpc3dbg(("ipgpc_addfilter: adding filter %s", filter->filter_name));
return (0);
}
/*
* reset_dontcare_stats()
*
* when an insertion fails because zero selectors are specified in a filter
* the number of dontcare's recorded for each selector structure needs to be
* decremented
*/
static void
reset_dontcare_stats(void)
{
int i;
for (i = 0; i < NUM_TRIES; ++i) {
atomic_add_32(&ipgpc_trie_list[i].stats.num_dontcare, -1);
}
for (i = 0; i < NUM_TABLES; ++i) {
atomic_add_32(&ipgpc_table_list[i].stats.num_dontcare, -1);
}
atomic_add_32(&ipgpc_ds_table_id.stats.num_dontcare, -1);
}
/*
* ipgpc_parse_class(out_class, nvlp)
*
* Given a name value pair list, a class structure will be parsed.
* To be a valid class, the class name, originator id and next action name
* must be present. gather_stats is optional, if absent default value is used
*/
int
ipgpc_parse_class(ipgpc_class_t *out_class, nvlist_t *nvlp)
{
char *name;
size_t name_len;
uint32_t gather_stats;
/* parse class name */
if (nvlist_lookup_string(nvlp, CLASSIFIER_CLASS_NAME, &name) != 0) {
return (EINVAL); /* class name missing, error */
}
name_len = strlen(name);
/* check for max name length */
if ((name_len + 1) > MAXNAMELEN) {
ipgpc0dbg(("ipgpc_parse_class: class name length > " \
"MAXNAMELEN"));
return (EINVAL);
}
bcopy(name, out_class->class_name, (name_len + 1));
/* parse originator */
if (nvlist_lookup_uint32(nvlp, IPP_CONFIG_ORIGINATOR,
&out_class->originator) != 0) {
ipgpc0dbg(("ipgpc_parse_class: originator missing"));
return (EINVAL);
}
/* parse action name */
if (nvlist_lookup_string(nvlp, CLASSIFIER_NEXT_ACTION, &name) != 0) {
return (EINVAL); /* action name missing, error */
}
if ((out_class->next_action = ipp_action_lookup(name))
== IPP_ACTION_INVAL) {
ipgpc0dbg(("ipgpc_parse_class: invalid action name %s", name));
return (EINVAL);
}
/* parse gather stats boolean */
if (nvlist_lookup_uint32(nvlp, CLASSIFIER_CLASS_STATS_ENABLE,
&gather_stats) != 0) {
/* stats turned off by default */
out_class->gather_stats = B_FALSE;
} else {
out_class->gather_stats = (boolean_t)gather_stats;
}
return (0);
}
/*
* ipgpc_addclass(in_class, flags)
*
* adds the given class to the class id list.
* - EEXIST is returned if class of same name already exists
* - ENOSPC if there is no more available memory to add class
* - 0 for success
* flags is currently unused
*/
/* ARGSUSED */
int
ipgpc_addclass(ipgpc_class_t *in_class, ipp_flags_t flags) {
int class_id;
int err;
if ((err = insertcid(in_class, &class_id)) == EEXIST) {
ipgpc0dbg(("ipgpc_addclass: class name %s already exists",
in_class->class_name));
return (err);
} else if (err == ENOSPC) {
ipgpc0dbg(("ipgpc_addclass: can not add class %s, " \
"ipgpc_max_num_classes has been reached",
in_class->class_name));
return (err);
}
/* add reference to next action */
if ((err = ipp_action_ref(ipgpc_aid, in_class->next_action, 0)) != 0) {
/*
* the action id we want to reference must have been
* destroyed before we could reference it. remove class
* and fail.
*/
removecid(class_id);
return (err);
}
return (0);
}
/*
* class_statinit(in_class, in_class_id)
*
* for the given class, create stats entries to record
* - next action id
* - number of bytes that matched this class
* - number of packets that matched this class
* - time in hrtime of last match for this class
* any failures are returned, zero on sucess
*/
static int
class_statinit(ipgpc_class_t *in_class, int in_class_id)
{
int rc;
ipp_stat_t *ipp_cl_stats;
classstats_t *clsnames = NULL;
/* create stat structure */
if ((rc = ipp_stat_create(ipgpc_aid, in_class->class_name, 3,
update_class_stats, &ipgpc_cid_list[in_class_id].stats,
&ipp_cl_stats)) != 0) {
ipgpc0dbg(("class_statinit: error creating ipp_stat entry"));
return (rc);
}
ASSERT(ipp_cl_stats != NULL);
clsnames = (classstats_t *)ipp_cl_stats->ipps_data;
ASSERT(clsnames != NULL);
/* create stats entry */
bzero(&ipgpc_cid_list[in_class_id].stats,
sizeof (ipgpc_class_stats_t));
/* set next action id */
ipgpc_cid_list[in_class_id].stats.next_action =
ipgpc_cid_list[in_class_id].aclass.next_action;
if ((rc = ipp_stat_named_init(ipp_cl_stats, "nbytes",
IPP_STAT_UINT64, &clsnames->nbytes)) != 0) {
return (rc);
}
if ((rc = ipp_stat_named_init(ipp_cl_stats, "npackets",
IPP_STAT_UINT64, &clsnames->npackets)) != 0) {
return (rc);
}
if ((rc = ipp_stat_named_init(ipp_cl_stats, "last_match",
IPP_STAT_INT64, &clsnames->last_match)) != 0) {
return (rc);
}
/* make reference to kstat structure, for removal */
ipgpc_cid_list[in_class_id].cl_stats = ipp_cl_stats;
ipp_stat_install(ipp_cl_stats);
return (0);
}
/*
* insertcid(in_class, out_class_id)
*
* creates a class id (cid) structure for in_class, if in_class name
* does not exist already. id is associated with in_class. the internal
* id of the cid associated with in_class is returned in out_class_id
* - ENOENT is returned if in_class->class_name does not already exist
* - EEXIST is returned if in_class->class_name does already exist
* - ENOSPC is returned if by adding this class, the ipgpc_max_num_classes
* will be exceeded.
*/
static int
insertcid(ipgpc_class_t *in_class, int *out_class_id)
{
int err, rc;
unsigned class_id;
mutex_enter(&ipgpc_cid_list_lock);
/* see if entry already exists for class */
if ((err = class_name2id(&class_id, in_class->class_name,
ipgpc_num_cls + 1)) == ENOENT) {
/* create new filter list for new class */
ipgpc_cid_list[class_id].info = 1;
ipgpc_cid_list[class_id].aclass = *in_class;
if (in_class->gather_stats == B_TRUE) {
/* init kstat entry */
if ((rc = class_statinit(in_class, class_id)) != 0) {
ipgpc_cid_list[class_id].info = -1;
ipgpc0dbg(("insertcid: " \
"class_statinit failed with " \
"error %d", rc));
mutex_exit(&ipgpc_cid_list_lock);
return (rc);
}
} else {
ipgpc_cid_list[class_id].cl_stats = NULL;
}
ipgpc3dbg(("insertcid: adding class %s",
in_class->class_name));
bcopy(in_class->class_name,
ipgpc_cid_list[class_id].aclass.class_name, MAXNAMELEN);
ipgpc_cid_list[class_id].filter_list = NULL;
atomic_add_long(&ipgpc_num_cls, 1);
} else {
ipgpc0dbg(("insertcid: class name lookup error %d", err));
mutex_exit(&ipgpc_cid_list_lock);
return (err);
}
mutex_exit(&ipgpc_cid_list_lock);
*out_class_id = class_id;
return (err);
}
/*
* common_removefilter(in_filter_id, fid)
*
* removes in_filter_id from each of the common selector structures
*/
static void
common_removefilter(int in_filter_id, fid_t *fid)
{
int if_grpnm_hv;
/* start trie removes */
t_remove(&ipgpc_trie_list[IPGPC_TRIE_SPORTID], in_filter_id,
fid->filter.sport, fid->filter.sport_mask);
/* remove id from destination port trie */
t_remove(&ipgpc_trie_list[IPGPC_TRIE_DPORTID], in_filter_id,
fid->filter.dport, fid->filter.dport_mask);
/* end trie revmoves */
/* remove id from DiffServ field ba table */
mutex_enter(&ipgpc_ds_table_id.lock);
ba_remove(&ipgpc_ds_table_id, in_filter_id, fid->filter.dsfield,
fid->filter.dsfield_mask);
mutex_exit(&ipgpc_ds_table_id.lock);
/* start table removes */
mutex_enter(&ipgpc_table_list_lock);
/* remove id from protocol table */
ht_remove(&ipgpc_table_list[PROTOID_IDX], in_filter_id,
fid->filter.proto);
/* remove id from UID table */
ht_remove(&ipgpc_table_list[UID_IDX], in_filter_id, fid->filter.uid);
/* remove id from PROJID table */
ht_remove(&ipgpc_table_list[PROJID_IDX], in_filter_id,
fid->filter.projid);
/* remove id from interface id table */
ht_remove(&ipgpc_table_list[IF_IDX], in_filter_id,
fid->filter.if_index);
/* remove id from interface group name table */
if (fid->filter.if_groupname[0] == '\0') {
if_grpnm_hv = IPGPC_WILDCARD;
} else {
if_grpnm_hv = name_hash(fid->filter.if_groupname, TABLE_SIZE);
}
ht_remove(&ipgpc_table_list[IF_GRPNM_IDX], in_filter_id, if_grpnm_hv);
/* remove id from direction table */
ht_remove(&ipgpc_table_list[DIR_IDX], in_filter_id,
fid->filter.direction);
mutex_exit(&ipgpc_table_list_lock);
/* end table removes */
}
/*
* v4_removefilter(in_filter_id, fid)
*
* removes id from IPV4 specific structures
*/
static void
v4_removefilter(int in_filter_id, fid_t *fid)
{
/* remove id from source address trie */
t_remove(&ipgpc_trie_list[IPGPC_TRIE_SADDRID], in_filter_id,
V4_PART_OF_V6(fid->filter.saddr),
V4_PART_OF_V6(fid->filter.saddr_mask));
/* remove id from destination address trie */
t_remove(&ipgpc_trie_list[IPGPC_TRIE_DADDRID], in_filter_id,
V4_PART_OF_V6(fid->filter.daddr),
V4_PART_OF_V6(fid->filter.daddr_mask));
}
/*
* v6_removefilter(in_filter_id, fid)
*
* removes id from IPV6 specific structures
*/
static void
v6_removefilter(int in_filter_id, fid_t *fid)
{
/* remove id from source address trie */
t_remove6(&ipgpc_trie_list[IPGPC_TRIE_SADDRID6], in_filter_id,
fid->filter.saddr, fid->filter.saddr_mask);
/* remove id from destination address trie */
t_remove6(&ipgpc_trie_list[IPGPC_TRIE_DADDRID6], in_filter_id,
fid->filter.daddr, fid->filter.daddr_mask);
}
/*
* ipgpc_removefilter(filter_name, filter_instance, flags)
*
* remove the filter associated with the specified name and instance
* - remove filter keys from all search tries
* - remove from filter id list
* - ENOENT is returned if filter name does not exist
* - returns 0 on success
*/
/* ARGSUSED */
int
ipgpc_removefilter(char *filter_name, int32_t filter_instance,
ipp_flags_t flags)
{
unsigned filter_id;
fid_t *fid;
int rc;
/* check to see if any filters are loaded */
if (ipgpc_num_fltrs == 0) {
return (ENOENT);
}
mutex_enter(&ipgpc_fid_list_lock);
/* lookup filter name, only existing filters can be removed */
if ((rc = filter_name2id(&filter_id, filter_name, filter_instance,
ipgpc_num_fltrs)) != EEXIST) {
mutex_exit(&ipgpc_fid_list_lock);
return (rc);
}
fid = &ipgpc_fid_list[filter_id];
switch (fid->filter.filter_type) {
case IPGPC_GENERIC_FLTR:
common_removefilter(filter_id, fid);
v4_removefilter(filter_id, fid);
v6_removefilter(filter_id, fid);
break;
case IPGPC_V4_FLTR:
common_removefilter(filter_id, fid);
v4_removefilter(filter_id, fid);
break;
case IPGPC_V6_FLTR:
common_removefilter(filter_id, fid);
v6_removefilter(filter_id, fid);
break;
default:
ipgpc0dbg(("ipgpc_removefilter(): invalid filter type %d",
fid->filter.filter_type));
mutex_exit(&ipgpc_fid_list_lock);
return (EINVAL);
}
/* remove filter from filter list */
ipgpc_fid_list[filter_id].info = -1;
ipgpc_fid_list[filter_id].insert_map = 0;
ipgpc_fid_list[filter_id].filter.filter_name[0] = '\0';
ipgpc_filter_destructor(&ipgpc_fid_list[filter_id].filter);
mutex_exit(&ipgpc_fid_list_lock);
/* remove filter id from class' list of filters */
remove_from_cid_filter_list(ipgpc_fid_list[filter_id].class_id,
filter_id);
atomic_add_long(&ipgpc_num_fltrs, -1);
return (0);
}
/*
* removecid(in_class_id)
*
* removes the cid entry from the cid list and frees allocated structures
*/
static void
removecid(int in_class_id)
{
ipgpc_cid_list[in_class_id].info = -1;
ipgpc_cid_list[in_class_id].aclass.class_name[0] = '\0';
ipgpc_cid_list[in_class_id].aclass.next_action = -1;
/* delete kstat entry */
if (ipgpc_cid_list[in_class_id].cl_stats != NULL) {
ipp_stat_destroy(ipgpc_cid_list[in_class_id].cl_stats);
ipgpc_cid_list[in_class_id].cl_stats = NULL;
}
/* decrement total number of classes loaded */
atomic_add_long(&ipgpc_num_cls, -1);
}
/*
* remove_from_cid_filter_list(in_class_id, in_filter_id)
*
* removes the input filter_id from the filter_list of the class associated
* with the input class_id
*/
static void
remove_from_cid_filter_list(int in_class_id, int in_filter_id)
{
cid_t *cid = &ipgpc_cid_list[in_class_id];
if (cid->filter_list != NULL) {
(void) ipgpc_list_remove(&cid->filter_list, in_filter_id);
}
}
/*
* ipgpc_removeclass(class_name)
*
* removes a class and all the filters that point to it (ouch!)
* - returns 0 on success
* - ENOENT if class name does not exist
* - ENOTSUP if class name equals 'default'
*/
int
ipgpc_removeclass(char *class_name, ipp_flags_t flags)
{
unsigned class_id;
element_node_t *anode = NULL;
element_node_t *tnode = NULL;
fid_t *fid = NULL;
ipp_action_id_t old_next_action;
int rc;
/* check to see if any classes are loaded */
if (ipgpc_num_cls == 0) {
return (ENOENT);
}
mutex_enter(&ipgpc_cid_list_lock); /* set lock */
/* lookup class name, only classes that exist can be removed */
if ((rc = class_name2id(&class_id, class_name, (ipgpc_num_cls - 1)))
!= EEXIST) {
mutex_exit(&ipgpc_cid_list_lock); /* release lock */
return (rc);
}
if (class_id == ipgpc_def_class_id) {
ipgpc0dbg(("ipgpc_removeclass(): default class may not be " \
"removed"));
mutex_exit(&ipgpc_cid_list_lock); /* release lock */
return (ENOTSUP);
}
old_next_action = ipgpc_cid_list[class_id].aclass.next_action;
anode = ipgpc_cid_list[class_id].filter_list;
while (anode != NULL) {
fid = &ipgpc_fid_list[anode->id];
if (ipgpc_fid_list[anode->id].info > 0) {
anode = anode->next;
(void) ipgpc_removefilter(fid->filter.filter_name,
fid->filter.filter_instance, flags);
} else {
tnode = anode;
anode = anode->next;
/* free this node */
kmem_cache_free(element_node_cache, tnode);
}
}
/* remove cid from ipgpc_cid_list and decrement ipgpc_num_cls */
ipgpc3dbg(("ipgpc_removeclass: class %s has been removed",
class_name));
removecid(class_id);
mutex_exit(&ipgpc_cid_list_lock); /* release lock */
rc = ipp_action_unref(ipgpc_aid, old_next_action, flags);
ASSERT(rc == 0);
return (0);
}
/*
* ipgpc_modifyfilter(nvlist, flags)
*
* modifies the input filter
* - if in_class != NULL, filter is associated with that class
* - EINVAL is returned if filter name does not exist in nvlist
* - if filter->filter_name does not exist ENOENT is returned
* - if a class name to associate with is not present in nvlist, then the
* previous class association is used
*/
int
ipgpc_modifyfilter(nvlist_t **nvlpp, ipp_flags_t flags)
{
unsigned filter_id;
int ret = 0;
int rc;
ipgpc_filter_t *filter;
ipgpc_filter_t old_filter;
char *name;
char *s;
uint_t class_id;
filter = kmem_zalloc(sizeof (ipgpc_filter_t), KM_SLEEP);
if ((ret = ipgpc_parse_filter(filter, *nvlpp)) != 0) {
ipgpc0dbg(("ipgpc_modifyfilter: error %d parsing filter",
ret));
ipgpc_filter_destructor(filter);
kmem_free(filter, sizeof (ipgpc_filter_t));
return (ret);
}
/* parse class name */
if (nvlist_lookup_string(*nvlpp, CLASSIFIER_CLASS_NAME, &name)
!= 0) {
name = NULL; /* no class specified */
}
/* modify filter entry */
if ((rc = filter_name2id(&filter_id, filter->filter_name,
filter->filter_instance, ipgpc_num_fltrs)) == EEXIST) {
if (name == NULL) {
/* set class_name to previous class_name association */
class_id = ipgpc_fid_list[filter_id].class_id;
name = ipgpc_cid_list[class_id].aclass.class_name;
} else {
if ((ret = class_name2id(&class_id, name,
ipgpc_num_cls)) != EEXIST) {
ipgpc0dbg(("ipgpc_modifyfilter: class does " \
"not exist"));
ipgpc_filter_destructor(filter);
kmem_free(filter, sizeof (ipgpc_filter_t));
return (ret);
}
}
/* copy out old filter just in case we need to revert */
old_filter = ipgpc_fid_list[filter_id].filter;
/* make copy of filter_comment */
if (ipgpc_fid_list[filter_id].filter.filter_comment != NULL) {
s = ipgpc_fid_list[filter_id].filter.filter_comment;
old_filter.filter_comment =
kmem_alloc((strlen(s) + 1), KM_SLEEP);
(void) strcpy(old_filter.filter_comment, s);
} else {
old_filter.filter_comment = NULL;
}
/* make copy of saddr_hostname */
if (ipgpc_fid_list[filter_id].filter.saddr_hostname != NULL) {
s = ipgpc_fid_list[filter_id].filter.saddr_hostname;
old_filter.saddr_hostname =
kmem_alloc((strlen(s) + 1), KM_SLEEP);
(void) strcpy(old_filter.saddr_hostname, s);
} else {
old_filter.saddr_hostname = NULL;
}
/* make copy of daddr_hostname */
if (ipgpc_fid_list[filter_id].filter.daddr_hostname != NULL) {
s = ipgpc_fid_list[filter_id].filter.daddr_hostname;
old_filter.daddr_hostname =
kmem_alloc((strlen(s) + 1), KM_SLEEP);
(void) strcpy(old_filter.daddr_hostname, s);
} else {
old_filter.daddr_hostname = NULL;
}
/* remove old filter entry */
ret = ipgpc_removefilter(filter->filter_name,
filter->filter_instance, flags);
if (ret == 0) { /* no error, add filter */
ret = ipgpc_addfilter(filter, name, flags);
if (ret != 0) {
/* error occured, free filter fields */
ipgpc0dbg(("ipgpc_modifyfilter: invalid " \
"filter given, unable to modify " \
"existing filter %s",
filter->filter_name));
ipgpc_filter_destructor(filter);
kmem_free(filter, sizeof (ipgpc_filter_t));
/* revert back to old filter */
(void) ipgpc_addfilter(&old_filter, name,
flags);
return (ret);
}
ipgpc_filter_destructor(&old_filter);
} else {
ipgpc0dbg(("ipgpc_modifyfilter: error %d occured " \
"when modifying filter", ret));
ipgpc_filter_destructor(&old_filter);
ipgpc_filter_destructor(filter);
kmem_free(filter, sizeof (ipgpc_filter_t));
return (ret);
}
} else {
ipgpc0dbg(("ipgpc_modifyfilter: filter lookup error %d", rc));
return (rc); /* filter name does not exist */
}
kmem_free(filter, sizeof (ipgpc_filter_t));
return (0);
}
/*
* ipgpc_modifyclass(in_class)
*
* if the input class exists, then the action list is modified
* if the input class does not exist, ENOENT is returned
*/
/* ARGSUSED */
int
ipgpc_modifyclass(nvlist_t **nvlpp, ipp_flags_t flags)
{
unsigned class_id;
ipp_stat_t *cl_stats;
ipgpc_class_t in_class;
char *name;
int rc;
uint32_t gather_stats;
boolean_t ref_action = B_FALSE;
ipp_action_id_t old_next_action;
size_t name_len;
/* parse class name */
if (nvlist_lookup_string(*nvlpp, CLASSIFIER_CLASS_NAME, &name) != 0) {
return (EINVAL); /* class name missing, error */
}
name_len = strlen(name);
/* check for max name length */
if ((name_len + 1) > MAXNAMELEN) {
ipgpc0dbg(("ipgpc_modifyclass: class name length > " \
"MAXNAMELEN"));
return (EINVAL);
}
bcopy(name, in_class.class_name, (name_len + 1));
mutex_enter(&ipgpc_cid_list_lock);
/* look up class name, only existing classes can be modified */
if ((rc = class_name2id(&class_id, in_class.class_name,
ipgpc_num_cls)) == EEXIST) {
/* preserve previous config if values are absent */
/* parse action name */
old_next_action = ipgpc_cid_list[class_id].aclass.next_action;
if (nvlist_lookup_string(*nvlpp, CLASSIFIER_NEXT_ACTION, &name)
!= 0) {
/* use previous config */
in_class.next_action = old_next_action;
} else { /* next action name present */
if ((in_class.next_action = ipp_action_lookup(name))
== IPP_ACTION_INVAL) {
ipgpc0dbg(("ipgpc_modifyclass: invalid " \
"action name %s", name));
mutex_exit(&ipgpc_cid_list_lock);
return (EINVAL); /* this is an error */
}
ref_action = B_TRUE;
}
/* parse gather stats byte */
if (nvlist_lookup_uint32(*nvlpp, CLASSIFIER_CLASS_STATS_ENABLE,
&gather_stats) != 0) {
/* use previous config */
in_class.gather_stats =
ipgpc_cid_list[class_id].aclass.gather_stats;
} else {
in_class.gather_stats = (boolean_t)gather_stats;
}
/* check to see if gather_stats booleans differ */
if ((ipgpc_cid_list[class_id].aclass.gather_stats !=
in_class.gather_stats)) {
if (ipgpc_cid_list[class_id].aclass.gather_stats ==
B_TRUE) {
/* delete kstat entry */
if (ipgpc_cid_list[class_id].cl_stats != NULL) {
cl_stats =
ipgpc_cid_list[class_id].cl_stats;
ipp_stat_destroy(cl_stats);
ipgpc_cid_list[class_id].cl_stats = NULL;
}
} else { /* gather_stats == B_FALSE */
if ((rc = class_statinit(&in_class, class_id))
!= 0) {
ipgpc0dbg(("ipgpc_modifyclass: " \
"class_statinit failed with " \
"error %d", rc));
mutex_exit(&ipgpc_cid_list_lock);
return (rc);
}
}
}
mutex_exit(&ipgpc_cid_list_lock);
/* check if next_action was modified */
if (ref_action == B_TRUE) {
if ((rc = ipp_action_ref(ipgpc_aid,
in_class.next_action, 0)) != 0) {
ipgpc0dbg(("ipgpc_modifyclass: error " \
"occured while adding a reference to " \
"the new next_action %d",
in_class.next_action));
mutex_exit(&ipgpc_cid_list_lock);
return (rc);
}
/* fix up references */
rc = ipp_action_unref(ipgpc_aid, old_next_action,
flags);
ASSERT(rc == 0);
}
/* preserve originator id */
in_class.originator =
ipgpc_cid_list[class_id].aclass.originator;
ipgpc_cid_list[class_id].aclass = in_class;
ipgpc_cid_list[class_id].stats.next_action =
in_class.next_action;
} else {
ipgpc0dbg(("ipgpc_modifyclass: class name lookup error %d",
rc));
mutex_exit(&ipgpc_cid_list_lock);
return (rc);
}
return (0);
}
/*
* ipgpc_list_insert(listpp, id)
*
* inserts an item, id, into the list, if item exists EEXIST is returned
*/
int
ipgpc_list_insert(linked_list *listpp, key_t id)
{
element_node_t *p;
if (*listpp == NULL) {
*listpp = kmem_cache_alloc(element_node_cache, KM_SLEEP);
(*listpp)->element_refcnt = 1;
(*listpp)->next = NULL;
(*listpp)->id = id;
} else {
for (p = *listpp; p->next != NULL; p = p->next) {
if (p->id == id) {
(*p->element_ref)(p);
return (EEXIST);
}
}
if (p->id == id) {
(*p->element_ref)(p);
return (EEXIST);
} else {
p->next =
kmem_cache_alloc(element_node_cache, KM_SLEEP);
p->next->element_refcnt = 1;
p->next->next = NULL;
p = p->next;
p->id = id;
}
}
return (0);
}
/*
* ipgpc_list_remove(listpp, id)
*
* removes an item, id, from the list if it exists and returns TRUE or FALSE
* if not removed
*/
boolean_t
ipgpc_list_remove(element_node_t **listpp, key_t id)
{
element_node_t *p = NULL;
element_node_t *t = NULL;
if (*listpp == NULL) {
return (B_FALSE);
}
if ((*listpp)->id == id) {
p = *listpp;
if ((*listpp)->element_refcnt == 1) {
*listpp = (*listpp)->next;
}
(*p->element_unref)(p);
return (B_TRUE);
} else if ((*listpp)->next != NULL) {
/* linear search for matching id */
for (p = *listpp; p->next != NULL; p = p->next) {
if (p->next->id == id) {
t = p->next;
if (p->next->element_refcnt == 1) {
p->next = p->next->next;
}
(*t->element_unref)(t);
return (B_TRUE);
}
}
}
return (B_FALSE);
}
/*
* Module destroy code
*/
static void
removeclasses(ipp_flags_t flags)
{
int i;
for (i = 0; i < ipgpc_max_num_classes; ++i) {
if (ipgpc_cid_list[i].info > 0) {
(void) ipgpc_removeclass(
ipgpc_cid_list[i].aclass.class_name, flags);
}
}
}
static void
freetriev6nodes(node_t **inNode)
{
node_t *anode = *inNode;
node_t *tnode;
node_t *s[130]; /* stack of previous nodes */
int prev_link[130]; /* stack of what the previous link was */
int sp = 0;
node_t *root = *inNode; /* pointer to root node */
s[sp] = NULL;
prev_link[sp] = -1;
/* loop until only the root node remains */
while (!((root->zero == NULL) && (root->one == NULL))) {
if (anode->zero != NULL) { /* check zero node */
tnode = anode;
anode = anode->zero;
s[++sp] = tnode; /* put node on stack */
prev_link[sp] = 0;
} else if (anode->one != NULL) { /* check one node */
tnode = anode;
anode = anode->one;
s[++sp] = tnode; /* put node on stack */
prev_link[sp] = 1;
} else { /* leaf node reached */
/* free leaf node and pop the stack */
kmem_cache_free(trie_node_cache, anode);
anode = s[sp];
if (prev_link[sp--] == 0) {
anode->zero = NULL;
} else {
anode->one = NULL;
}
if (anode == NULL) {
return;
}
}
}
}
void
ipgpc_destroy(ipp_flags_t flags)
{
int i;
int rc;
element_node_t *anode = NULL;
element_node_t *tnode = NULL;
fid_t *fid = NULL;
/* check to see if default class id was set */
if (ipgpc_def_class_id != -1) {
ipp_action_id_t next_action =
ipgpc_cid_list[ipgpc_def_class_id].aclass.next_action;
/* unreference default_class->next_action */
rc = ipp_action_unref(ipgpc_aid, next_action, flags);
ASSERT(rc == 0);
/* removing filter associated with the default class */
anode = ipgpc_cid_list[ipgpc_def_class_id].filter_list;
while (anode != NULL) {
fid = &ipgpc_fid_list[anode->id];
if (ipgpc_fid_list[anode->id].info > 0) {
anode = anode->next;
(void) ipgpc_removefilter(
fid->filter.filter_name,
fid->filter.filter_instance, flags);
} else {
tnode = anode;
anode = anode->next;
/* free this node */
kmem_cache_free(element_node_cache, tnode);
}
}
ASSERT(ipgpc_cid_list[ipgpc_def_class_id].filter_list == NULL);
removecid(ipgpc_def_class_id);
ASSERT(ipgpc_cid_list[ipgpc_def_class_id].info == -1);
ipgpc_def_class_id = -1;
}
/* remove stats entries */
if (ipgpc_global_stats != NULL) {
/* destroy global stats */
ipp_stat_destroy(ipgpc_global_stats);
ipgpc_global_stats = NULL;
}
/*
* remove all classes, which will remove all filters, stats and
* selectors
*/
if (ipgpc_cid_list != NULL) {
removeclasses(flags);
kmem_free(ipgpc_cid_list,
sizeof (cid_t) * ipgpc_max_num_classes);
ipgpc_cid_list = NULL;
}
/* all filters and classes should have been removed at this point */
ASSERT((ipgpc_num_cls == 0) && (ipgpc_num_fltrs == 0));
/* free filter id list structure */
if (ipgpc_fid_list != NULL) {
kmem_free(ipgpc_fid_list,
sizeof (fid_t) * ipgpc_max_num_filters);
ipgpc_fid_list = NULL;
}
/*
* IPv6 address tries don't implement path compression or node
* deletions, like v4/port tries. All allocated nodes must be freed
* before trie root node is destroyed
*/
if (ipgpc_trie_list[IPGPC_TRIE_SADDRID6].trie != NULL) {
freetriev6nodes(&ipgpc_trie_list[IPGPC_TRIE_SADDRID6].trie);
/* free trie root */
kmem_cache_free(trie_node_cache,
ipgpc_trie_list[IPGPC_TRIE_SADDRID6].trie);
/* destroy lock */
rw_destroy(&ipgpc_trie_list[IPGPC_TRIE_SADDRID6].rw_lock);
ipgpc_trie_list[IPGPC_TRIE_SADDRID6].trie = NULL;
}
if (ipgpc_trie_list[IPGPC_TRIE_DADDRID6].trie != NULL) {
freetriev6nodes(&ipgpc_trie_list[IPGPC_TRIE_DADDRID6].trie);
/* free trie root */
kmem_cache_free(trie_node_cache,
ipgpc_trie_list[IPGPC_TRIE_DADDRID6].trie);
/* destroy lock */
rw_destroy(&ipgpc_trie_list[IPGPC_TRIE_DADDRID6].rw_lock);
ipgpc_trie_list[IPGPC_TRIE_DADDRID6].trie = NULL;
}
/* free remaining tries structures */
for (i = 0; i < (NUM_TRIES - 2); ++i) {
if (ipgpc_trie_list[i].trie != NULL) {
/* free trie root */
kmem_cache_free(trie_node_cache,
ipgpc_trie_list[i].trie);
/* destroy lock */
rw_destroy(&ipgpc_trie_list[i].rw_lock);
ipgpc_trie_list[i].trie = NULL;
}
}
/* destroy caches */
if (ht_node_cache != NULL) {
kmem_cache_destroy(ht_node_cache);
ht_node_cache = NULL;
}
if (trie_node_cache != NULL) {
kmem_cache_destroy(trie_node_cache);
trie_node_cache = NULL;
}
if (element_node_cache != NULL) {
kmem_cache_destroy(element_node_cache);
element_node_cache = NULL;
}
if (ht_match_cache != NULL) {
kmem_cache_destroy(ht_match_cache);
ht_match_cache = NULL;
}
}
/*
* Module info code
*/
/*
* ipgpc_params_info(fn, arg)
*
* allocates, builds and passes an nvlist to fn with arg
*/
int
ipgpc_params_info(int (*fn)(nvlist_t *, void *), void *arg)
{
nvlist_t *nvlp;
int rc;
/* allocate nvlist to be passed back */
if ((rc = nvlist_alloc(&nvlp, NV_UNIQUE_NAME, KM_NOSLEEP)) != 0) {
return (rc);
}
/* add config type */
if ((rc = nvlist_add_byte(nvlp, IPP_CONFIG_TYPE, IPP_SET)) != 0) {
nvlist_free(nvlp);
return (rc);
}
/* add gather stats boolean */
if ((rc = nvlist_add_uint32(nvlp, IPP_ACTION_STATS_ENABLE,
(uint32_t)ipgpc_gather_stats)) != 0) {
nvlist_free(nvlp);
return (rc);
}
/* call back with nvlist */
rc = fn(nvlp, arg);
nvlist_free(nvlp);
return (rc);
}
/*
* build_class_nvlist(nvlpp, in_class)
*
* build an nvlist based on in_class
* if isdefault, add apporiate configuration type to nvlpp
*/
static int
build_class_nvlist(nvlist_t **nvlpp, ipgpc_class_t *in_class,
boolean_t isdefault)
{
nvlist_t *nvlp = *nvlpp;
char *next_action;
int rc;
/*
* add configuration type
* if class is the default class, config type should be
* CLASSIFIER_MODIFY_CLASS
* otherwise it should be CLASSIFIER_ADD_CLASS
*/
/* add config type */
if ((rc = nvlist_add_byte(nvlp, IPP_CONFIG_TYPE,
((isdefault) ? CLASSIFIER_MODIFY_CLASS : CLASSIFIER_ADD_CLASS)))
!= 0) {
return (rc);
}
/* add class name */
if ((rc = nvlist_add_string(nvlp, CLASSIFIER_CLASS_NAME,
in_class->class_name)) != 0) {
return (rc);
}
/* add originator */
if ((rc = nvlist_add_uint32(nvlp, IPP_CONFIG_ORIGINATOR,
in_class->originator)) != 0) {
return (rc);
}
/* look up next action name with next action id */
if ((rc = ipp_action_name(in_class->next_action, &next_action)) != 0) {
return (rc);
}
/* add next action name */
if ((rc = nvlist_add_string(nvlp, CLASSIFIER_NEXT_ACTION,
next_action)) != 0) {
kmem_free(next_action, (strlen(next_action) + 1));
return (rc);
}
kmem_free(next_action, (strlen(next_action) + 1));
/* add gather stats boolean */
if ((rc = nvlist_add_uint32(nvlp, CLASSIFIER_CLASS_STATS_ENABLE,
(uint32_t)in_class->gather_stats)) != 0) {
return (rc);
}
return (0);
}
/*
* ipgpc_classes_info(fn, arg)
*
* foreach class, allocate, build and pass an nvlist to fn with arg
*/
int
ipgpc_classes_info(int (*fn)(nvlist_t *, void *), void *arg)
{
int i;
int rc;
nvlist_t *nvlp;
for (i = 0; i < ipgpc_max_num_classes; ++i) {
if (ipgpc_cid_list[i].info <= 0) {
/* cid not allocated for this entry */
continue;
}
/* allocate an nvlist */
if ((rc = nvlist_alloc(&nvlp, NV_UNIQUE_NAME, KM_NOSLEEP))
!= 0) {
return (rc);
}
/* build an nvlist for this particular class */
if ((rc = (build_class_nvlist(&nvlp,
&ipgpc_cid_list[i].aclass,
((i == ipgpc_def_class_id) ? B_TRUE : B_FALSE)))) != 0) {
nvlist_free(nvlp);
return (rc);
}
/* call back with nvlist */
if ((rc = fn(nvlp, arg)) != 0) {
nvlist_free(nvlp);
return (rc);
}
nvlist_free(nvlp); /* free nvlist and continue */
}
return (0);
}
/*
* build_filter_nvlist(nvlpp, in_filter, class_name)
*
* build an nvlist based on in_filter and class_name.
* Only non-wildcard/dontcare selectors are added to the nvlist.
*/
static int
build_filter_nvlist(nvlist_t **nvlpp, ipgpc_filter_t *in_filter,
char *class_name)
{
nvlist_t *nvlp = *nvlpp;
int rc;
in6_addr_t zero_addr = IN6ADDR_ANY_INIT;
/* add filter name */
if ((rc = nvlist_add_string(nvlp, CLASSIFIER_FILTER_NAME,
in_filter->filter_name)) != 0) {
return (rc);
}
/* add class name */
if ((rc = nvlist_add_string(nvlp, CLASSIFIER_CLASS_NAME, class_name))
!= 0) {
return (rc);
}
/* add originator */
if ((rc = nvlist_add_uint32(nvlp, IPP_CONFIG_ORIGINATOR,
in_filter->originator)) != 0) {
return (rc);
}
/* add configuration type of CLASSIFIER_ADD_FILTER */
if ((rc = nvlist_add_byte(nvlp, IPP_CONFIG_TYPE,
CLASSIFIER_ADD_FILTER)) != 0) {
return (rc);
}
/* add interface groupname */
if (in_filter->if_groupname[0] != '\0') {
if ((rc = nvlist_add_string(nvlp, IPGPC_IF_GROUPNAME,
in_filter->if_groupname)) != 0) {
return (rc);
}
}
/* add uid */
if (in_filter->uid != IPGPC_WILDCARD) {
if ((rc = nvlist_add_int32(nvlp, IPGPC_UID, in_filter->uid))
!= 0) {
return (rc);
}
}
/* add projid */
if (in_filter->projid != IPGPC_WILDCARD) {
if ((rc = nvlist_add_int32(nvlp, IPGPC_PROJID,
in_filter->projid)) != 0) {
return (rc);
}
}
/* add interface index */
if (in_filter->if_index != IPGPC_UNSPECIFIED) {
if ((rc = nvlist_add_uint32(nvlp, IPGPC_IF_INDEX,
in_filter->if_index)) != 0) {
return (rc);
}
}
/* add direction */
if (in_filter->direction != IPGPC_UNSPECIFIED) {
if ((rc = nvlist_add_uint32(nvlp, IPGPC_DIR,
in_filter->direction)) != 0) {
return (rc);
}
}
/* add protocol */
if (in_filter->proto != IPGPC_UNSPECIFIED) {
if ((rc = nvlist_add_byte(nvlp, IPGPC_PROTO, in_filter->proto))
!= 0) {
return (rc);
}
}
/* add dsfield and mask */
if (in_filter->dsfield_mask != 0) {
if ((rc = nvlist_add_byte(nvlp, IPGPC_DSFIELD,
in_filter->dsfield)) != 0) {
return (rc);
}
if ((rc = nvlist_add_byte(nvlp, IPGPC_DSFIELD_MASK,
in_filter->dsfield_mask)) != 0) {
return (rc);
}
}
/* add source address, mask and hostname */
if (!(IN6_ARE_ADDR_EQUAL(&in_filter->saddr_mask, &zero_addr))) {
if ((rc = nvlist_add_uint32_array(nvlp, IPGPC_SADDR,
in_filter->saddr.s6_addr32, 4)) != 0) {
return (rc);
}
if ((rc = nvlist_add_uint32_array(nvlp, IPGPC_SADDR_MASK,
in_filter->saddr_mask.s6_addr32, 4)) != 0) {
return (rc);
}
if (in_filter->saddr_hostname != NULL) {
if ((rc = nvlist_add_string(nvlp, IPGPC_SADDR_HOSTNAME,
in_filter->saddr_hostname)) != 0) {
return (rc);
}
}
}
/* add destination address, mask and hostname */
if (!(IN6_ARE_ADDR_EQUAL(&in_filter->daddr_mask, &zero_addr))) {
if ((rc = nvlist_add_uint32_array(nvlp, IPGPC_DADDR,
in_filter->daddr.s6_addr32, 4)) != 0) {
return (rc);
}
if ((rc = nvlist_add_uint32_array(nvlp, IPGPC_DADDR_MASK,
in_filter->daddr_mask.s6_addr32, 4)) != 0) {
return (rc);
}
if (in_filter->daddr_hostname != NULL) {
if ((rc = nvlist_add_string(nvlp, IPGPC_DADDR_HOSTNAME,
in_filter->daddr_hostname)) != 0) {
return (rc);
}
}
}
/* add source port and mask */
if (in_filter->sport_mask != 0) {
if ((rc = nvlist_add_uint16(nvlp, IPGPC_SPORT,
in_filter->sport)) != 0) {
return (rc);
}
if ((rc = nvlist_add_uint16(nvlp, IPGPC_SPORT_MASK,
in_filter->sport_mask)) != 0) {
return (rc);
}
}
/* add destination port and mask */
if (in_filter->dport_mask != 0) {
if ((rc = nvlist_add_uint16(nvlp, IPGPC_DPORT,
in_filter->dport)) != 0) {
return (rc);
}
if ((rc = nvlist_add_uint16(nvlp, IPGPC_DPORT_MASK,
in_filter->dport_mask)) != 0) {
return (rc);
}
}
/* add precedence */
if (in_filter->precedence != UINT_MAX) {
if ((rc = nvlist_add_uint32(nvlp, IPGPC_PRECEDENCE,
in_filter->precedence)) != 0) {
return (rc);
}
}
/* add priority */
if (in_filter->priority != 0) {
if ((rc = nvlist_add_uint32(nvlp, IPGPC_PRIORITY,
in_filter->priority)) != 0) {
return (rc);
}
}
/* add filter type */
if (in_filter->filter_type != IPGPC_GENERIC_FLTR) {
if ((rc = nvlist_add_byte(nvlp, IPGPC_FILTER_TYPE,
in_filter->filter_type)) != 0) {
return (rc);
}
}
/* add filter instance */
if (in_filter->filter_instance != -1) {
if ((rc = nvlist_add_int32(nvlp, IPGPC_FILTER_INSTANCE,
in_filter->filter_instance)) != 0) {
return (rc);
}
}
/* add filter private field */
if (in_filter->filter_comment != NULL) {
if ((rc = nvlist_add_string(nvlp, IPGPC_FILTER_PRIVATE,
in_filter->filter_comment)) != 0) {
return (rc);
}
}
return (0);
}
/*
* ipgpc_filters_info(fn, arg)
*
* for each filter, allocate, build and pass an nvlist to fn with arg
*/
int
ipgpc_filters_info(int (*fn)(nvlist_t *, void *), void *arg)
{
int i;
int rc;
nvlist_t *nvlp;
int class_id;
for (i = 0; i < ipgpc_max_num_filters; ++i) {
if (ipgpc_fid_list[i].info <= 0) {
/* fid not allocated for this entry */
continue;
}
/* allocate an nvlist */
if ((rc = nvlist_alloc(&nvlp, NV_UNIQUE_NAME, KM_NOSLEEP))
!= 0) {
return (rc);
}
class_id = ipgpc_fid_list[i].class_id;
/* build an nvlist for this particular filter */
if ((rc = (build_filter_nvlist(&nvlp,
&ipgpc_fid_list[i].filter,
ipgpc_cid_list[class_id].aclass.class_name))) != 0) {
nvlist_free(nvlp);
return (rc);
}
/* call back with nvlist */
if ((rc = fn(nvlp, arg)) != 0) {
nvlist_free(nvlp);
return (rc);
}
nvlist_free(nvlp); /* free nvlist and continue */
}
return (0);
}
/*
* Module invoke code
*/
/*
* ipgpc_findfilters(in_id, key, fid_table)
*
* returns a list of matching filters for searching the given structure
* associated with the input id with the input key
* - returns DONTCARE_ONLY_MATCH if the selector structure described by
* in_id contains only dontcares
* - returns NO_MATCHES if no filters were found and no dontcares exist
* for a given selector
* - ENOMEM is returned if memory error occurs
* - NORMAL_MATCH on success
*/
int
ipgpc_findfilters(int in_id, int key, ht_match_t *fid_table)
{
int num_found = 0;
if (in_id == IPGPC_BA_DSID) { /* special search for DSFIELD */
if (ipgpc_ds_table_id.info.dontcareonly == B_TRUE) {
/* trie is loaded with only DONTCARE(*) keys */
return (DONTCARE_ONLY_MATCH);
}
num_found = ba_retrieve(&ipgpc_ds_table_id, (uint8_t)key,
fid_table);
/* check to see if no matches were made */
if ((num_found == 0) &&
(ipgpc_ds_table_id.stats.num_dontcare == 0)) {
return (NO_MATCHES);
}
} else if (in_id >= TABLE_ID_OFFSET) { /* table to search */
table_id_t *taid = &ipgpc_table_list[in_id - TABLE_ID_OFFSET];
if (taid->info.dontcareonly == B_TRUE) {
/* trie is loaded with only DONTCARE(*) keys */
return (DONTCARE_ONLY_MATCH);
}
num_found = ht_retrieve(taid, key, fid_table);
/* check to see if no matches were made */
if ((num_found == 0) && (taid->stats.num_dontcare == 0)) {
return (NO_MATCHES);
}
} else { /* trie to search */
trie_id_t *tid = &ipgpc_trie_list[in_id];
if (tid->info.dontcareonly == B_TRUE) {
/* trie is loaded with only DONTCARE(*) keys */
return (DONTCARE_ONLY_MATCH);
}
/* search the trie for matches */
num_found = t_retrieve(tid, key, fid_table);
/* check to see if no matches were made */
if ((num_found == 0) && (tid->stats.num_dontcare == 0)) {
return (NO_MATCHES);
}
}
if (num_found == -1) { /* num_found == -1 if memory error */
return (ENOMEM);
} else {
return (NORMAL_MATCH);
}
}
/*
* ipgpc_findfilters6(in_id, key, fid_table)
*
* findfilters specific to IPv6 traffic
*/
int
ipgpc_findfilters6(int in_id, in6_addr_t key, ht_match_t *fid_table)
{
trie_id_t *tid = &ipgpc_trie_list[in_id];
int num_found = 0;
if (tid->info.dontcareonly == B_TRUE) {
/* trie is loaded with only DONTCARE(*) keys */
return (DONTCARE_ONLY_MATCH);
}
/* search the trie for matches */
num_found = t_retrieve6(tid, key, fid_table);
/* check to see if no matches were made */
if ((num_found == 0) && (tid->stats.num_dontcare == 0)) {
return (NO_MATCHES);
} else if (num_found == -1) { /* num_found == -1 if memory error */
return (ENOMEM);
} else {
return (NORMAL_MATCH);
}
}
/*
* ht_match_insert(a, id, mask)
*
* inserts id into table and applies mask to match_map
* returns ENOMEM if can't allocate ht_match_t node, 0 otherwise
*/
static int
ht_match_insert(ht_match_t *a, int id, uint16_t mask)
{
int x = (id % HASH_SIZE); /* has for index */
ht_match_t *p = NULL;
if ((a[x].key == id) || (a[x].key == 0)) {
a[x].key = id;
a[x].match_map |= mask;
} else if (a[x].next == NULL) {
a[x].next = kmem_cache_alloc(ht_match_cache, KM_NOSLEEP);
if (a[x].next == NULL) {
ipgpc0dbg(("ht_match_insert(): kmem_cache_alloc " \
"error"));
return (ENOMEM);
}
a[x].next->next = NULL;
a[x].next->key = id;
a[x].next->match_map = mask;
} else {
p = a[x].next;
while (p != NULL) {
if (p->key == id) {
p->match_map |= mask;
return (0);
}
p = p->next;
}
p = kmem_cache_alloc(ht_match_cache, KM_NOSLEEP);
if (p == NULL) {
ipgpc0dbg(("ht_match_insert(): kmem_cache_alloc " \
"error"));
return (ENOMEM);
}
p->key = id;
p->match_map = mask;
p->next = a[x].next;
a[x].next = p;
}
return (0);
}
/*
* ipgpc_mark_found(mask, list, fid_table)
*
* given a list of filter ids and a mask for the selector that is being marked,
* the ids are inserted (or updated) in the fid_table to being marked as
* matched for the given selector
* return -1 if memory error
*/
int
ipgpc_mark_found(uint16_t mask, linked_list list, ht_match_t *fid_table)
{
linked_list tnode = NULL;
int num_found = 0;
for (tnode = list; tnode != NULL; tnode = tnode->next) {
/* apply the trie mask to the match map for this element */
if (ipgpc_fid_list[tnode->id].info > 0) {
if (ht_match_insert(fid_table, tnode->id, mask)
== ENOMEM) {
return (-1);
}
++num_found;
}
}
return (num_found);
}
/* updates global stats for ipgpc */
/* ARGSUSED */
static int
update_global_stats(ipp_stat_t *sp, void *arg, int rw)
{
globalstats_t *gbl_stats = (globalstats_t *)sp->ipps_data;
uint32_t num_filters = (uint32_t)ipgpc_num_fltrs;
uint32_t num_classes = (uint32_t)ipgpc_num_cls;
ASSERT(gbl_stats != NULL);
(void) ipp_stat_named_op(&gbl_stats->nfilters, &num_filters, rw);
(void) ipp_stat_named_op(&gbl_stats->nclasses, &num_classes, rw);
(void) ipp_stat_named_op(&gbl_stats->nbytes, &ipgpc_nbytes, rw);
(void) ipp_stat_named_op(&gbl_stats->npackets, &ipgpc_npackets, rw);
(void) ipp_stat_named_op(&gbl_stats->epackets, &ipgpc_epackets, rw);
return (0);
}
/* updates class stats for a specific class */
static int
update_class_stats(ipp_stat_t *sp, void *arg, int rw)
{
ipgpc_class_stats_t *stats = (ipgpc_class_stats_t *)arg;
classstats_t *cl_stats = (classstats_t *)sp->ipps_data;
ASSERT(stats != NULL);
ASSERT(cl_stats != NULL);
(void) ipp_stat_named_op(&cl_stats->nbytes, &stats->nbytes, rw);
(void) ipp_stat_named_op(&cl_stats->npackets, &stats->npackets, rw);
(void) ipp_stat_named_op(&cl_stats->last_match, &stats->last_match, rw);
return (0);
}