1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <sys/systm.h>
27#include <sys/socket.h>
28#include <netinet/in.h>
29#include <sys/modctl.h>
30#include <sys/sunddi.h>
31#include <ipp/ipp.h>
32#include <ipp/ipp_config.h>
33#include <ipp/ipgpc/classifier.h>
34#include <inet/ip.h>
35#include <net/if.h>
36#include <inet/ip_if.h>
37#include <inet/ipp_common.h>
38
39/* DDI file for ipgpc ipp module */
40
41/* protects against multiple configs  */
42static kmutex_t ipgpc_config_lock;
43
44static int ipgpc_create_action(ipp_action_id_t, nvlist_t **, ipp_flags_t);
45static int ipgpc_modify_action(ipp_action_id_t, nvlist_t **, ipp_flags_t);
46static int ipgpc_destroy_action(ipp_action_id_t, ipp_flags_t);
47static int ipgpc_info(ipp_action_id_t aid, int (*)(nvlist_t *, void *), void *,
48    ipp_flags_t);
49static int ipgpc_invoke_action(ipp_action_id_t, ipp_packet_t *);
50
51ipp_ops_t ipgpc_ops = {
52	IPPO_REV,
53	ipgpc_create_action,	/* ippo_action_create */
54	ipgpc_modify_action,	/* ippo_action_modify */
55	ipgpc_destroy_action,	/* ippo_action_destroy */
56	ipgpc_info,		/* ippo_action_info */
57	ipgpc_invoke_action	/* ippo_action_invoke */
58};
59
60extern struct mod_ops mod_ippops;
61
62/*
63 * Module linkage information for the kernel.
64 */
65static struct modlipp modlipp = {
66	&mod_ippops,
67	"IP Generic Packet Classifier (ipgpc) module 1.0",
68	&ipgpc_ops
69};
70
71static struct modlinkage modlinkage = {
72	MODREV_1,
73	(void *)&modlipp,
74	NULL
75};
76
77#define	__FN__	"_init"
78int
79_init(
80	void)
81{
82	int rc;
83
84	if (ipgpc_action_exist) {
85		return (EBUSY);
86	}
87	/* init mutexes */
88	mutex_init(&ipgpc_config_lock, NULL, MUTEX_DRIVER, NULL);
89	mutex_init(&ipgpc_fid_list_lock, NULL, MUTEX_DRIVER, NULL);
90	mutex_init(&ipgpc_cid_list_lock, NULL, MUTEX_DRIVER, NULL);
91	mutex_init(&ipgpc_table_list_lock, NULL, MUTEX_DRIVER, NULL);
92	mutex_init(&ipgpc_ds_table_id.lock, NULL, MUTEX_DRIVER, NULL);
93
94	if ((rc = mod_install(&modlinkage)) != 0) {
95		/* clean up after fail */
96		mutex_destroy(&ipgpc_config_lock);
97		mutex_destroy(&ipgpc_fid_list_lock);
98		mutex_destroy(&ipgpc_cid_list_lock);
99		mutex_destroy(&ipgpc_table_list_lock);
100		mutex_destroy(&ipgpc_ds_table_id.lock);
101	}
102
103	return (rc);
104}
105#undef	__FN__
106
107#define	__FN__	"_fini"
108int
109_fini(
110	void)
111{
112	int rc;
113
114	if (ipgpc_action_exist) {
115		return (EBUSY);
116	}
117
118	if ((rc = mod_remove(&modlinkage)) != 0) {
119		return (rc);
120	}
121	/* destroy mutexes */
122	mutex_destroy(&ipgpc_config_lock);
123	mutex_destroy(&ipgpc_fid_list_lock);
124	mutex_destroy(&ipgpc_cid_list_lock);
125	mutex_destroy(&ipgpc_table_list_lock);
126	mutex_destroy(&ipgpc_ds_table_id.lock);
127	return (rc);
128}
129#undef	__FN__
130
131#define	__FN__	"_info"
132int
133_info(
134	struct	modinfo *modinfop)
135{
136	return (mod_info(&modlinkage, modinfop));
137}
138#undef	__FN__
139
140/*
141 * ipgpc_create_action(aid, nvlpp, flags)
142 *
143 * creates a single instance of ipgpc, if one does not exist.  If an action
144 * instance already exists, fail with EBUSY
145 *
146 * if nvlpp contains the name IPP_ACTION_STATS_ENABLE, then process it and
147 * determine if global stats should be collected
148 *
149 * the ipgpc_config_lock is taken to block out any other creates or destroys
150 * the are issued while the create is taking place
151 */
152/* ARGSUSED */
153static int
154ipgpc_create_action(ipp_action_id_t aid, nvlist_t **nvlpp, ipp_flags_t flags)
155{
156	int rc;
157	uint32_t stat;
158	nvlist_t *nvlp;
159
160	nvlp = *nvlpp;
161	*nvlpp = NULL;		/* nvlist should be NULL when this returns */
162
163	/* only one ipgpc action instance can be loaded at once */
164	if (ipgpc_action_exist) {
165		nvlist_free(nvlp);
166		return (EBUSY);
167	} else {
168		mutex_enter(&ipgpc_config_lock);
169		if (ipgpc_action_exist) {
170			nvlist_free(nvlp);
171			mutex_exit(&ipgpc_config_lock);
172			return (EBUSY);
173		}
174		/* check for action param IPP_ACTION_STATS_ENABLE */
175		if ((rc = nvlist_lookup_uint32(nvlp, IPP_ACTION_STATS_ENABLE,
176		    &stat)) != 0) {
177			ipgpc_gather_stats = B_FALSE; /* disabled by default */
178		} else {
179			ipgpc_gather_stats = (boolean_t)stat;
180		}
181		if ((rc = ipgpc_initialize(aid)) != 0) {
182			ipgpc0dbg(("ipgpc_create_action: ipgpc_intialize " \
183			    "error %d", rc));
184			ipgpc_destroy(IPP_DESTROY_REF);
185			ipgpc_action_exist = B_FALSE;
186			nvlist_free(nvlp);
187			mutex_exit(&ipgpc_config_lock);
188			return (rc);
189		}
190		ipgpc_action_exist = B_TRUE;
191		nvlist_free(nvlp);
192		mutex_exit(&ipgpc_config_lock);
193		return (0);
194	}
195}
196
197/*
198 * ipgpc_modify_action
199 *
200 * modify an instance of ipgpc
201 *
202 * nvlpp will contain the configuration type to switch off of.  Use this
203 * to determine what modification should be made.  If the modification fails,
204 * return the appropriate error.
205 */
206/* ARGSUSED */
207static int
208ipgpc_modify_action(ipp_action_id_t aid, nvlist_t **nvlpp, ipp_flags_t flags)
209{
210	nvlist_t *nvlp;
211	int rc = 0;
212	uint8_t config_type;
213	uint32_t stat;
214	char *name;
215	int32_t filter_instance;
216	ipgpc_filter_t *filter;
217	ipgpc_class_t *aclass;
218
219	nvlp = *nvlpp;
220	*nvlpp = NULL;		/* nvlist should be NULL when this returns */
221
222	if ((rc = nvlist_lookup_byte(nvlp, IPP_CONFIG_TYPE, &config_type))
223	    != 0) {
224		nvlist_free(nvlp);
225		ipgpc0dbg(("ipgpc_modify_action: invalid configuration type"));
226		return (EINVAL);
227	}
228
229	switch (config_type) {
230	case IPP_SET:		/* set an action parameter */
231		if ((rc = nvlist_lookup_uint32(nvlp, IPP_ACTION_STATS_ENABLE,
232		    &stat)) != 0) {
233			nvlist_free(nvlp);
234			ipgpc0dbg(("ipgpc_modify_action: invalid IPP_SET " \
235			    "parameter"));
236			return (EINVAL);
237		} else {
238			ipgpc_gather_stats = (boolean_t)stat;
239		}
240		break;
241	case CLASSIFIER_ADD_FILTER: /* add a filter */
242		filter = kmem_zalloc(sizeof (ipgpc_filter_t), KM_SLEEP);
243		if ((rc = ipgpc_parse_filter(filter, nvlp)) != 0) {
244			ipgpc0dbg(("ipgpc_modify_action: invalid filter"));
245			ipgpc_filter_destructor(filter);
246			kmem_free(filter, sizeof (ipgpc_filter_t));
247			break;
248		}
249		/* parse class name */
250		if ((rc = nvlist_lookup_string(nvlp, CLASSIFIER_CLASS_NAME,
251		    &name)) != 0) {
252			ipgpc0dbg(("ipgpc_modify_action: class name missing"));
253			ipgpc_filter_destructor(filter);
254			kmem_free(filter, sizeof (ipgpc_filter_t));
255			break;
256		}
257		rc = ipgpc_addfilter(filter, name, flags);
258		if (rc != 0) {
259			ipgpc_filter_destructor(filter);
260		}
261		kmem_free(filter, sizeof (ipgpc_filter_t));
262		break;
263	case CLASSIFIER_ADD_CLASS: /* add a class */
264		aclass = kmem_zalloc(sizeof (ipgpc_class_t), KM_SLEEP);
265		if ((rc = ipgpc_parse_class(aclass, nvlp)) != 0) {
266			ipgpc0dbg(("ipgpc_modify_action: invalid class"));
267			kmem_free(aclass, sizeof (ipgpc_class_t));
268			break;
269		}
270		rc = ipgpc_addclass(aclass, flags);
271		kmem_free(aclass, sizeof (ipgpc_class_t));
272		break;
273	case CLASSIFIER_REMOVE_FILTER: /* remove a filter */
274		/* parse filter name */
275		if ((rc = nvlist_lookup_string(nvlp, CLASSIFIER_FILTER_NAME,
276		    &name)) != 0) {
277			ipgpc0dbg(("ipgpc_modify_action: filtername missing"));
278			break;
279		}
280		/* parse optional filter_instance */
281		if (nvlist_lookup_int32(nvlp, IPGPC_FILTER_INSTANCE,
282		    &filter_instance) != 0) {
283			filter_instance = -1;
284		}
285		rc = ipgpc_removefilter(name, filter_instance, flags);
286		break;
287	case CLASSIFIER_REMOVE_CLASS: /* remove a class */
288		/* parse class name */
289		if ((rc = nvlist_lookup_string(nvlp, CLASSIFIER_CLASS_NAME,
290		    &name)) != 0) {
291			ipgpc0dbg(("ipgpc_modify_action: class name missing"));
292			break;
293		}
294		rc = ipgpc_removeclass(name, flags);
295		break;
296	case CLASSIFIER_MODIFY_FILTER: /* modify a filter */
297		rc = ipgpc_modifyfilter(&nvlp, flags);
298		break;
299	case CLASSIFIER_MODIFY_CLASS: /* modify a class */
300		rc = ipgpc_modifyclass(&nvlp, flags);
301		break;
302	default:		/* invalid config type */
303		nvlist_free(nvlp);
304		ipgpc0dbg(("ipgpc_modify_action:invalid configuration type %u",
305		    config_type));
306		return (EINVAL);
307	}
308	nvlist_free(nvlp);	/* free the list */
309	return (rc);		/* nvlist is passed back NULL */
310}
311
312/*
313 * ipgpc_destroy_action(aid, flags)
314 *
315 * action destructor for ipgpc
316 *
317 * Destroys an instance of the ipgpc action, if one exists. The
318 * ipgpc_action_lock is taken to block out any other destroys or creates
319 * that might be issued while the action is being destroyed
320 */
321/* ARGSUSED */
322static int
323ipgpc_destroy_action(ipp_action_id_t aid, ipp_flags_t flags)
324{
325	/* only destroy action if it exists */
326	if (ipgpc_action_exist == B_TRUE) {
327		mutex_enter(&ipgpc_config_lock);
328		if (ipgpc_action_exist == B_FALSE) {
329			mutex_exit(&ipgpc_config_lock);
330			return (EBUSY);
331		}
332		ipgpc_action_exist = B_FALSE;
333		ipgpc_destroy(flags);
334		mutex_exit(&ipgpc_config_lock);
335	}
336	return (0);
337}
338
339/*
340 * ipgpc_info(aid, fn, arg)
341 *
342 * configuration quering function for ipgpc
343 *
344 * passes back the configuration of ipgpc through allocated nvlists
345 * all action paramaters, classes and filters are built into nvlists
346 * and passed to the function pointer fn with arg
347 */
348/* ARGSUSED */
349static int
350ipgpc_info(ipp_action_id_t aid, int (*fn)(nvlist_t *, void *), void *arg,
351    ipp_flags_t flags)
352{
353	int rc;
354
355	/* set parameters */
356	if ((rc = ipgpc_params_info(fn, arg)) != 0) {
357		return (rc);
358	}
359
360	/* set all classes */
361	if ((rc = ipgpc_classes_info(fn, arg)) != 0) {
362		return (rc);
363	}
364
365	/* set all filters */
366	if ((rc = ipgpc_filters_info(fn, arg)) != 0) {
367		return (rc);
368	}
369	return (0);
370}
371
372/*
373 * ipgpc_invoke_action(aid, packet)
374 *
375 * packet processing function for ipgpc
376 *
377 * given packet the selector information is parsed and the classify
378 * function is called with those selectors.  The classify function will
379 * return either a class or NULL, which represents a memory error and
380 * ENOMEM is returned.  If the class returned is not NULL, the class and next
381 * action, associated with that class, are added to packet
382 */
383/* ARGSUSED */
384static int
385ipgpc_invoke_action(ipp_action_id_t aid, ipp_packet_t *packet)
386{
387	ipgpc_class_t *out_class;
388	hrtime_t start, end;
389	mblk_t *mp = NULL;
390	ip_priv_t *priv = NULL;
391	ill_t *ill = NULL;
392	ipha_t *ipha;
393	ip_proc_t callout_pos;
394	int af;
395	int rc;
396	ipgpc_packet_t pkt;
397	uint_t ill_idx;
398
399	/* extract packet data */
400	mp = ipp_packet_get_data(packet);
401	ASSERT(mp != NULL);
402
403	priv = (ip_priv_t *)ipp_packet_get_private(packet);
404	ASSERT(priv != NULL);
405
406	callout_pos = priv->proc;
407	ill_idx = priv->ill_index;
408
409	/* If we don't get an M_DATA, then return an error */
410	if (mp->b_datap->db_type != M_DATA) {
411		if ((mp->b_cont != NULL) &&
412		    (mp->b_cont->b_datap->db_type == M_DATA)) {
413			mp = mp->b_cont; /* jump over the M_CTL into M_DATA */
414		} else {
415			ipgpc0dbg(("ipgpc_invoke_action: no data\n"));
416			atomic_add_64(&ipgpc_epackets, 1);
417			return (EINVAL);
418		}
419	}
420
421	/*
422	 * Translate the callout_pos into the direction the packet is traveling
423	 */
424	if (callout_pos != IPP_LOCAL_IN) {
425		if (callout_pos & IPP_LOCAL_OUT) {
426			callout_pos = IPP_LOCAL_OUT;
427		} else if (callout_pos & IPP_FWD_IN) {
428			callout_pos = IPP_FWD_IN;
429		} else {	/* IPP_FWD_OUT */
430			callout_pos = IPP_FWD_OUT;
431		}
432	}
433
434	/* parse the packet from the message block */
435	ipha = (ipha_t *)mp->b_rptr;
436	/* Determine IP Header Version */
437	if (IPH_HDR_VERSION(ipha) == IPV4_VERSION) {
438		parse_packet(&pkt, mp);
439		af = AF_INET;
440	} else {
441		parse_packet6(&pkt, mp);
442		af = AF_INET6;
443	}
444
445	pkt.direction = callout_pos; /* set packet direction */
446
447	/* The ill_index could be 0 when called from forwarding (read) path */
448	if (ill_idx > 0)
449		ill = ill_lookup_on_ifindex_global_instance(ill_idx, B_FALSE);
450
451	if (ill != NULL) {
452		/*
453		 * Since all IPP actions in an IPMP group are performed
454		 * relative to the IPMP group interface, if this is an
455		 * underlying interface in an IPMP group, use the IPMP
456		 * group interface's index.
457		 */
458		if (IS_UNDER_IPMP(ill))
459			pkt.if_index = ipmp_ill_get_ipmp_ifindex(ill);
460		else
461			pkt.if_index = ill->ill_phyint->phyint_ifindex;
462		/* Got the field from the ILL, go ahead and refrele */
463		ill_refrele(ill);
464	} else {
465		/* unknown if_index */
466		pkt.if_index = IPGPC_UNSPECIFIED;
467	}
468
469	if (ipgpc_debug > 5) {
470		/* print pkt under high debug level */
471#ifdef	IPGPC_DEBUG
472		print_packet(af, &pkt);
473#endif
474	}
475	if (ipgpc_debug > 3) {
476		start = gethrtime(); /* start timer */
477	}
478
479	/* classify this packet */
480	out_class = ipgpc_classify(af, &pkt);
481
482	if (ipgpc_debug > 3) {
483		end = gethrtime(); /* stop timer */
484	}
485
486	/* ipgpc_classify will only return NULL if a memory error occured */
487	if (out_class == NULL) {
488		atomic_add_64(&ipgpc_epackets, 1);
489		return (ENOMEM);
490	}
491
492	ipgpc1dbg(("ipgpc_invoke_action: class = %s", out_class->class_name));
493	/* print time to classify(..) */
494	ipgpc2dbg(("ipgpc_invoke_action: time = %lld nsec\n", (end - start)));
495
496	if ((rc = ipp_packet_add_class(packet, out_class->class_name,
497	    out_class->next_action)) != 0) {
498		atomic_add_64(&ipgpc_epackets, 1);
499		ipgpc0dbg(("ipgpc_invoke_action: ipp_packet_add_class " \
500		    "failed with error %d", rc));
501		return (rc);
502	}
503	return (ipp_packet_next(packet, IPP_ACTION_CONT));
504}
505