| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #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; |
| } |
| } |
| |
| /* 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 */ |
| |
| /* The ill_index could be 0 when called from forwarding (read) path */ |
| if (ill_idx > 0) { |
| ill = ill_lookup_on_ifindex_global_instance(ill_idx, B_FALSE, |
| NULL, NULL, NULL, NULL); |
| } |
| if (ill != NULL) { |
| /* |
| * Since all IPP actions in an IPMP group are performed |
| * relative to the IPMP group interface, if this is an |
| * underlying interface in an IPMP group, use the IPMP |
| * group interface's index. |
| */ |
| if (IS_UNDER_IPMP(ill)) |
| pkt.if_index = ipmp_ill_get_ipmp_ifindex(ill); |
| else |
| pkt.if_index = ill->ill_phyint->phyint_ifindex; |
| /* Got the field from the ILL, go ahead and refrele */ |
| ill_refrele(ill); |
| } else { |
| /* unknown if_index */ |
| pkt.if_index = IPGPC_UNSPECIFIED; |
| } |
| |
| 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)); |
| } |