classifierddi.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/systm.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/modctl.h>
#include <sys/sunddi.h>
#include <ipp/ipp.h>
#include <ipp/ipp_config.h>
#include <ipp/ipgpc/classifier.h>
#include <inet/ip.h>
#include <net/if.h>
#include <inet/ip_if.h>
#include <inet/ipp_common.h>
/* DDI file for ipgpc ipp module */
/* protects against multiple configs */
static kmutex_t ipgpc_config_lock;
static int ipgpc_create_action(ipp_action_id_t, nvlist_t **, ipp_flags_t);
static int ipgpc_modify_action(ipp_action_id_t, nvlist_t **, ipp_flags_t);
static int ipgpc_destroy_action(ipp_action_id_t, ipp_flags_t);
static int ipgpc_info(ipp_action_id_t aid, int (*)(nvlist_t *, void *), void *,
ipp_flags_t);
static int ipgpc_invoke_action(ipp_action_id_t, ipp_packet_t *);
ipp_ops_t ipgpc_ops = {
IPPO_REV,
ipgpc_create_action, /* ippo_action_create */
ipgpc_modify_action, /* ippo_action_modify */
ipgpc_destroy_action, /* ippo_action_destroy */
ipgpc_info, /* ippo_action_info */
ipgpc_invoke_action /* ippo_action_invoke */
};
extern struct mod_ops mod_ippops;
/*
* Module linkage information for the kernel.
*/
static struct modlipp modlipp = {
&mod_ippops,
"IP Generic Packet Classifier (ipgpc) module 1.0",
&ipgpc_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modlipp,
NULL
};
#define __FN__ "_init"
int
_init(
void)
{
int rc;
if (ipgpc_action_exist) {
return (EBUSY);
}
/* init mutexes */
mutex_init(&ipgpc_config_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ipgpc_fid_list_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ipgpc_cid_list_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ipgpc_table_list_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ipgpc_ds_table_id.lock, NULL, MUTEX_DRIVER, NULL);
if ((rc = mod_install(&modlinkage)) != 0) {
/* clean up after fail */
mutex_destroy(&ipgpc_config_lock);
mutex_destroy(&ipgpc_fid_list_lock);
mutex_destroy(&ipgpc_cid_list_lock);
mutex_destroy(&ipgpc_table_list_lock);
mutex_destroy(&ipgpc_ds_table_id.lock);
}
return (rc);
}
#undef __FN__
#define __FN__ "_fini"
int
_fini(
void)
{
int rc;
if (ipgpc_action_exist) {
return (EBUSY);
}
if ((rc = mod_remove(&modlinkage)) != 0) {
return (rc);
}
/* destroy mutexes */
mutex_destroy(&ipgpc_config_lock);
mutex_destroy(&ipgpc_fid_list_lock);
mutex_destroy(&ipgpc_cid_list_lock);
mutex_destroy(&ipgpc_table_list_lock);
mutex_destroy(&ipgpc_ds_table_id.lock);
return (rc);
}
#undef __FN__
#define __FN__ "_info"
int
_info(
struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
#undef __FN__
/*
* ipgpc_create_action(aid, nvlpp, flags)
*
* creates a single instance of ipgpc, if one does not exist. If an action
* instance already exists, fail with EBUSY
*
* if nvlpp contains the name IPP_ACTION_STATS_ENABLE, then process it and
* determine if global stats should be collected
*
* the ipgpc_config_lock is taken to block out any other creates or destroys
* the are issued while the create is taking place
*/
/* ARGSUSED */
static int
ipgpc_create_action(ipp_action_id_t aid, nvlist_t **nvlpp, ipp_flags_t flags)
{
int rc;
uint32_t stat;
nvlist_t *nvlp;
nvlp = *nvlpp;
*nvlpp = NULL; /* nvlist should be NULL when this returns */
/* only one ipgpc action instance can be loaded at once */
if (ipgpc_action_exist) {
nvlist_free(nvlp);
return (EBUSY);
} else {
mutex_enter(&ipgpc_config_lock);
if (ipgpc_action_exist) {
nvlist_free(nvlp);
mutex_exit(&ipgpc_config_lock);
return (EBUSY);
}
/* check for action param IPP_ACTION_STATS_ENABLE */
if ((rc = nvlist_lookup_uint32(nvlp, IPP_ACTION_STATS_ENABLE,
&stat)) != 0) {
ipgpc_gather_stats = B_FALSE; /* disabled by default */
} else {
ipgpc_gather_stats = (boolean_t)stat;
}
if ((rc = ipgpc_initialize(aid)) != 0) {
ipgpc0dbg(("ipgpc_create_action: ipgpc_intialize " \
"error %d", rc));
ipgpc_destroy(IPP_DESTROY_REF);
ipgpc_action_exist = B_FALSE;
nvlist_free(nvlp);
mutex_exit(&ipgpc_config_lock);
return (rc);
}
ipgpc_action_exist = B_TRUE;
nvlist_free(nvlp);
mutex_exit(&ipgpc_config_lock);
return (0);
}
}
/*
* ipgpc_modify_action
*
* modify an instance of ipgpc
*
* nvlpp will contain the configuration type to switch off of. Use this
* to determine what modification should be made. If the modification fails,
* return the appropriate error.
*/
/* ARGSUSED */
static int
ipgpc_modify_action(ipp_action_id_t aid, nvlist_t **nvlpp, ipp_flags_t flags)
{
nvlist_t *nvlp;
int rc = 0;
uint8_t config_type;
uint32_t stat;
char *name;
int32_t filter_instance;
ipgpc_filter_t *filter;
ipgpc_class_t *aclass;
nvlp = *nvlpp;
*nvlpp = NULL; /* nvlist should be NULL when this returns */
if ((rc = nvlist_lookup_byte(nvlp, IPP_CONFIG_TYPE, &config_type))
!= 0) {
nvlist_free(nvlp);
ipgpc0dbg(("ipgpc_modify_action: invalid configuration type"));
return (EINVAL);
}
switch (config_type) {
case IPP_SET: /* set an action parameter */
if ((rc = nvlist_lookup_uint32(nvlp, IPP_ACTION_STATS_ENABLE,
&stat)) != 0) {
nvlist_free(nvlp);
ipgpc0dbg(("ipgpc_modify_action: invalid IPP_SET " \
"parameter"));
return (EINVAL);
} else {
ipgpc_gather_stats = (boolean_t)stat;
}
break;
case CLASSIFIER_ADD_FILTER: /* add a filter */
filter = kmem_zalloc(sizeof (ipgpc_filter_t), KM_SLEEP);
if ((rc = ipgpc_parse_filter(filter, nvlp)) != 0) {
ipgpc0dbg(("ipgpc_modify_action: invalid filter"));
ipgpc_filter_destructor(filter);
kmem_free(filter, sizeof (ipgpc_filter_t));
break;
}
/* parse class name */
if ((rc = nvlist_lookup_string(nvlp, CLASSIFIER_CLASS_NAME,
&name)) != 0) {
ipgpc0dbg(("ipgpc_modify_action: class name missing"));
ipgpc_filter_destructor(filter);
kmem_free(filter, sizeof (ipgpc_filter_t));
break;
}
rc = ipgpc_addfilter(filter, name, flags);
if (rc != 0) {
ipgpc_filter_destructor(filter);
}
kmem_free(filter, sizeof (ipgpc_filter_t));
break;
case CLASSIFIER_ADD_CLASS: /* add a class */
aclass = kmem_zalloc(sizeof (ipgpc_class_t), KM_SLEEP);
if ((rc = ipgpc_parse_class(aclass, nvlp)) != 0) {
ipgpc0dbg(("ipgpc_modify_action: invalid class"));
kmem_free(aclass, sizeof (ipgpc_class_t));
break;
}
rc = ipgpc_addclass(aclass, flags);
kmem_free(aclass, sizeof (ipgpc_class_t));
break;
case CLASSIFIER_REMOVE_FILTER: /* remove a filter */
/* parse filter name */
if ((rc = nvlist_lookup_string(nvlp, CLASSIFIER_FILTER_NAME,
&name)) != 0) {
ipgpc0dbg(("ipgpc_modify_action: filtername missing"));
break;
}
/* parse optional filter_instance */
if (nvlist_lookup_int32(nvlp, IPGPC_FILTER_INSTANCE,
&filter_instance) != 0) {
filter_instance = -1;
}
rc = ipgpc_removefilter(name, filter_instance, flags);
break;
case CLASSIFIER_REMOVE_CLASS: /* remove a class */
/* parse class name */
if ((rc = nvlist_lookup_string(nvlp, CLASSIFIER_CLASS_NAME,
&name)) != 0) {
ipgpc0dbg(("ipgpc_modify_action: class name missing"));
break;
}
rc = ipgpc_removeclass(name, flags);
break;
case CLASSIFIER_MODIFY_FILTER: /* modify a filter */
rc = ipgpc_modifyfilter(&nvlp, flags);
break;
case CLASSIFIER_MODIFY_CLASS: /* modify a class */
rc = ipgpc_modifyclass(&nvlp, flags);
break;
default: /* invalid config type */
nvlist_free(nvlp);
ipgpc0dbg(("ipgpc_modify_action:invalid configuration type %u",
config_type));
return (EINVAL);
}
nvlist_free(nvlp); /* free the list */
return (rc); /* nvlist is passed back NULL */
}
/*
* ipgpc_destroy_action(aid, flags)
*
* action destructor for ipgpc
*
* Destroys an instance of the ipgpc action, if one exists. The
* ipgpc_action_lock is taken to block out any other destroys or creates
* that might be issued while the action is being destroyed
*/
/* ARGSUSED */
static int
ipgpc_destroy_action(ipp_action_id_t aid, ipp_flags_t flags)
{
/* only destroy action if it exists */
if (ipgpc_action_exist == B_TRUE) {
mutex_enter(&ipgpc_config_lock);
if (ipgpc_action_exist == B_FALSE) {
mutex_exit(&ipgpc_config_lock);
return (EBUSY);
}
ipgpc_action_exist = B_FALSE;
ipgpc_destroy(flags);
mutex_exit(&ipgpc_config_lock);
}
return (0);
}
/*
* ipgpc_info(aid, fn, arg)
*
* configuration quering function for ipgpc
*
* passes back the configuration of ipgpc through allocated nvlists
* all action paramaters, classes and filters are built into nvlists
* and passed to the function pointer fn with arg
*/
/* ARGSUSED */
static int
ipgpc_info(ipp_action_id_t aid, int (*fn)(nvlist_t *, void *), void *arg,
ipp_flags_t flags)
{
int rc;
/* set parameters */
if ((rc = ipgpc_params_info(fn, arg)) != 0) {
return (rc);
}
/* set all classes */
if ((rc = ipgpc_classes_info(fn, arg)) != 0) {
return (rc);
}
/* set all filters */
if ((rc = ipgpc_filters_info(fn, arg)) != 0) {
return (rc);
}
return (0);
}
/*
* ipgpc_invoke_action(aid, packet)
*
* packet processing function for ipgpc
*
* given packet the selector information is parsed and the classify
* function is called with those selectors. The classify function will
* return either a class or NULL, which represents a memory error and
* ENOMEM is returned. If the class returned is not NULL, the class and next
* action, associated with that class, are added to packet
*/
/* ARGSUSED */
static int
ipgpc_invoke_action(ipp_action_id_t aid, ipp_packet_t *packet)
{
ipgpc_class_t *out_class;
hrtime_t start, end;
mblk_t *mp = NULL;
ip_priv_t *priv = NULL;
ill_t *ill = NULL;
ipha_t *ipha;
ip_proc_t callout_pos;
int af;
int rc;
ipgpc_packet_t pkt;
uint_t ill_idx;
/* extract packet data */
mp = ipp_packet_get_data(packet);
ASSERT(mp != NULL);
priv = (ip_priv_t *)ipp_packet_get_private(packet);
ASSERT(priv != NULL);
callout_pos = priv->proc;
ill_idx = priv->ill_index;
/* If we don't get an M_DATA, then return an error */
if (mp->b_datap->db_type != M_DATA) {
if ((mp->b_cont != NULL) &&
(mp->b_cont->b_datap->db_type == M_DATA)) {
mp = mp->b_cont; /* jump over the M_CTL into M_DATA */
} else {
ipgpc0dbg(("ipgpc_invoke_action: no data\n"));
atomic_add_64(&ipgpc_epackets, 1);
return (EINVAL);
}
}
/*
* Translate the callout_pos into the direction the packet is traveling
*/
if (callout_pos != IPP_LOCAL_IN) {
if (callout_pos & IPP_LOCAL_OUT) {
callout_pos = IPP_LOCAL_OUT;
} else if (callout_pos & IPP_FWD_IN) {
callout_pos = IPP_FWD_IN;
} else { /* IPP_FWD_OUT */
callout_pos = IPP_FWD_OUT;
}
}
/* The ill_index could be 0 when called from forwarding (read) path */
if (ill_idx > 0) {
ill = ill_lookup_on_ifindex(ill_idx, B_FALSE, NULL, NULL,
NULL, NULL);
}
/* parse the packet from the message block */
ipha = (ipha_t *)mp->b_rptr;
/* Determine IP Header Version */
if (IPH_HDR_VERSION(ipha) == IPV4_VERSION) {
parse_packet(&pkt, mp);
af = AF_INET;
} else {
parse_packet6(&pkt, mp);
af = AF_INET6;
}
pkt.direction = callout_pos; /* set packet direction */
if (ill != NULL) {
pkt.if_index = ill->ill_phyint->phyint_ifindex;
pkt.if_groupname_len =
ill->ill_phyint->phyint_groupname_len;
if (pkt.if_groupname_len > 0) {
pkt.if_groupname =
ill->ill_phyint->phyint_groupname;
} else {
pkt.if_groupname = NULL;
}
/* Got the fields from the ILL, go ahead and refrele */
ill_refrele(ill);
} else {
/* unknown if_index and if_group */
pkt.if_index = IPGPC_UNSPECIFIED;
pkt.if_groupname = NULL;
pkt.if_groupname_len = 0;
}
if (ipgpc_debug > 5) {
/* print pkt under high debug level */
#ifdef IPGPC_DEBUG
print_packet(af, &pkt);
#endif
}
if (ipgpc_debug > 3) {
start = gethrtime(); /* start timer */
}
/* classify this packet */
out_class = ipgpc_classify(af, &pkt);
if (ipgpc_debug > 3) {
end = gethrtime(); /* stop timer */
}
/* ipgpc_classify will only return NULL if a memory error occured */
if (out_class == NULL) {
atomic_add_64(&ipgpc_epackets, 1);
return (ENOMEM);
}
ipgpc1dbg(("ipgpc_invoke_action: class = %s", out_class->class_name));
/* print time to classify(..) */
ipgpc2dbg(("ipgpc_invoke_action: time = %lld nsec\n", (end - start)));
if ((rc = ipp_packet_add_class(packet, out_class->class_name,
out_class->next_action)) != 0) {
atomic_add_64(&ipgpc_epackets, 1);
ipgpc0dbg(("ipgpc_invoke_action: ipp_packet_add_class " \
"failed with error %d", rc));
return (rc);
}
return (ipp_packet_next(packet, IPP_ACTION_CONT));
}