#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>
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,
ipgpc_modify_action,
ipgpc_destroy_action,
ipgpc_info,
ipgpc_invoke_action
};
extern struct mod_ops mod_ippops;
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);
}
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) {
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);
}
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__
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;
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);
}
if ((rc = nvlist_lookup_uint32(nvlp, IPP_ACTION_STATS_ENABLE,
&stat)) != 0) {
ipgpc_gather_stats = B_FALSE;
} 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);
}
}
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;
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:
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:
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;
}
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:
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:
if ((rc = nvlist_lookup_string(nvlp, CLASSIFIER_FILTER_NAME,
&name)) != 0) {
ipgpc0dbg(("ipgpc_modify_action: filtername missing"));
break;
}
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:
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:
rc = ipgpc_modifyfilter(&nvlp, flags);
break;
case CLASSIFIER_MODIFY_CLASS:
rc = ipgpc_modifyclass(&nvlp, flags);
break;
default:
nvlist_free(nvlp);
ipgpc0dbg(("ipgpc_modify_action:invalid configuration type %u",
config_type));
return (EINVAL);
}
nvlist_free(nvlp);
return (rc);
}
static int
ipgpc_destroy_action(ipp_action_id_t aid, ipp_flags_t flags)
{
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);
}
static int
ipgpc_info(ipp_action_id_t aid, int (*fn)(nvlist_t *, void *), void *arg,
ipp_flags_t flags)
{
int rc;
if ((rc = ipgpc_params_info(fn, arg)) != 0) {
return (rc);
}
if ((rc = ipgpc_classes_info(fn, arg)) != 0) {
return (rc);
}
if ((rc = ipgpc_filters_info(fn, arg)) != 0) {
return (rc);
}
return (0);
}
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;
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 (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;
} else {
ipgpc0dbg(("ipgpc_invoke_action: no data\n"));
atomic_inc_64(&ipgpc_epackets);
return (EINVAL);
}
}
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 {
callout_pos = IPP_FWD_OUT;
}
}
ipha = (ipha_t *)mp->b_rptr;
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;
if (ill_idx > 0)
ill = ill_lookup_on_ifindex_global_instance(ill_idx, B_FALSE);
if (ill != NULL) {
if (IS_UNDER_IPMP(ill))
pkt.if_index = ipmp_ill_get_ipmp_ifindex(ill);
else
pkt.if_index = ill->ill_phyint->phyint_ifindex;
ill_refrele(ill);
} else {
pkt.if_index = IPGPC_UNSPECIFIED;
}
if (ipgpc_debug > 5) {
#ifdef IPGPC_DEBUG
print_packet(af, &pkt);
#endif
}
if (ipgpc_debug > 3) {
start = gethrtime();
}
out_class = ipgpc_classify(af, &pkt);
if (ipgpc_debug > 3) {
end = gethrtime();
}
if (out_class == NULL) {
atomic_inc_64(&ipgpc_epackets);
return (ENOMEM);
}
ipgpc1dbg(("ipgpc_invoke_action: class = %s", out_class->class_name));
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_inc_64(&ipgpc_epackets);
ipgpc0dbg(("ipgpc_invoke_action: ipp_packet_add_class " \
"failed with error %d", rc));
return (rc);
}
return (ipp_packet_next(packet, IPP_ACTION_CONT));
}