1/*
2 * Copyright (C) 2012 by Darren Reed.
3 *
4 * See the IPFILTER.LICENCE file for details on licencing.
5 */
6#if defined(KERNEL) || defined(_KERNEL)
7# undef KERNEL
8# undef _KERNEL
9# define        KERNEL	1
10# define        _KERNEL	1
11#endif
12#include <sys/errno.h>
13#include <sys/types.h>
14#include <sys/param.h>
15#include <sys/file.h>
16#if !defined(_KERNEL) && !defined(__KERNEL__)
17# include <stdio.h>
18# include <stdlib.h>
19# include <string.h>
20# define _KERNEL
21# include <sys/uio.h>
22# undef _KERNEL
23#else
24# include <sys/systm.h>
25# if defined(NetBSD) && (__NetBSD_Version__ >= 104000000)
26#  include <sys/proc.h>
27# endif
28#endif
29#include <sys/time.h>
30# include <sys/protosw.h>
31#include <sys/socket.h>
32#if defined(_KERNEL) && !defined(__SVR4)
33# include <sys/mbuf.h>
34#endif
35#if defined(__SVR4)
36# include <sys/filio.h>
37# include <sys/byteorder.h>
38# ifdef _KERNEL
39#  include <sys/dditypes.h>
40# endif
41# include <sys/stream.h>
42# include <sys/kmem.h>
43#endif
44#if defined(__FreeBSD__)
45# include <sys/malloc.h>
46#endif
47
48#include <net/if.h>
49#include <netinet/in.h>
50
51#include "netinet/ip_compat.h"
52#include "netinet/ip_fil.h"
53#include "netinet/ip_nat.h"
54#include "netinet/ip_lookup.h"
55#include "netinet/ip_dstlist.h"
56
57/* END OF INCLUDES */
58
59#ifdef HAS_SYS_MD5_H
60# include <sys/md5.h>
61#else
62# include "md5.h"
63#endif
64
65#if !defined(lint)
66static const char rcsid[] = "@(#)$Id: ip_dstlist.c,v 1.13.2.12 2012/07/20 08:40:19 darren_r Exp $";
67#endif
68
69typedef struct ipf_dstl_softc_s {
70	ippool_dst_t	*dstlist[LOOKUP_POOL_SZ];
71	ippool_dst_t	**tails[LOOKUP_POOL_SZ];
72	ipf_dstl_stat_t	stats;
73} ipf_dstl_softc_t;
74
75
76static void *ipf_dstlist_soft_create(ipf_main_softc_t *);
77static void ipf_dstlist_soft_destroy(ipf_main_softc_t *, void *);
78static int ipf_dstlist_soft_init(ipf_main_softc_t *, void *);
79static void ipf_dstlist_soft_fini(ipf_main_softc_t *, void *);
80static int ipf_dstlist_addr_find(ipf_main_softc_t *, void *, int,
81				      void *, u_int);
82static size_t ipf_dstlist_flush(ipf_main_softc_t *, void *,
83				     iplookupflush_t *);
84static int ipf_dstlist_iter_deref(ipf_main_softc_t *, void *, int, int,
85				       void *);
86static int ipf_dstlist_iter_next(ipf_main_softc_t *, void *, ipftoken_t *,
87				      ipflookupiter_t *);
88static int ipf_dstlist_node_add(ipf_main_softc_t *, void *,
89				     iplookupop_t *, int);
90static int ipf_dstlist_node_del(ipf_main_softc_t *, void *,
91				     iplookupop_t *, int);
92static int ipf_dstlist_stats_get(ipf_main_softc_t *, void *,
93				      iplookupop_t *);
94static int ipf_dstlist_table_add(ipf_main_softc_t *, void *,
95				      iplookupop_t *);
96static int ipf_dstlist_table_del(ipf_main_softc_t *, void *,
97				      iplookupop_t *);
98static int ipf_dstlist_table_deref(ipf_main_softc_t *, void *, void *);
99static void *ipf_dstlist_table_find(void *, int, char *);
100static void ipf_dstlist_table_free(ipf_dstl_softc_t *, ippool_dst_t *);
101static void ipf_dstlist_table_remove(ipf_main_softc_t *,
102					  ipf_dstl_softc_t *, ippool_dst_t *);
103static void ipf_dstlist_table_clearnodes(ipf_dstl_softc_t *,
104					      ippool_dst_t *);
105static ipf_dstnode_t *ipf_dstlist_select(fr_info_t *, ippool_dst_t *);
106static void *ipf_dstlist_select_ref(void *, int, char *);
107static void ipf_dstlist_node_free(ipf_dstl_softc_t *, ippool_dst_t *, ipf_dstnode_t *);
108static int ipf_dstlist_node_deref(void *, ipf_dstnode_t *);
109static void ipf_dstlist_expire(ipf_main_softc_t *, void *);
110static void ipf_dstlist_sync(ipf_main_softc_t *, void *);
111
112ipf_lookup_t ipf_dstlist_backend = {
113	IPLT_DSTLIST,
114	ipf_dstlist_soft_create,
115	ipf_dstlist_soft_destroy,
116	ipf_dstlist_soft_init,
117	ipf_dstlist_soft_fini,
118	ipf_dstlist_addr_find,
119	ipf_dstlist_flush,
120	ipf_dstlist_iter_deref,
121	ipf_dstlist_iter_next,
122	ipf_dstlist_node_add,
123	ipf_dstlist_node_del,
124	ipf_dstlist_stats_get,
125	ipf_dstlist_table_add,
126	ipf_dstlist_table_del,
127	ipf_dstlist_table_deref,
128	ipf_dstlist_table_find,
129	ipf_dstlist_select_ref,
130	ipf_dstlist_select_node,
131	ipf_dstlist_expire,
132	ipf_dstlist_sync
133};
134
135
136/* ------------------------------------------------------------------------ */
137/* Function:    ipf_dstlist_soft_create                                     */
138/* Returns:     int - 0 = success, else error                               */
139/* Parameters:  softc(I) - pointer to soft context main structure           */
140/*                                                                          */
141/* Allocating a chunk of memory filled with 0's is enough for the current   */
142/* soft context used with destination lists.                                */
143/* ------------------------------------------------------------------------ */
144static void *
145ipf_dstlist_soft_create(softc)
146	ipf_main_softc_t *softc;
147{
148	ipf_dstl_softc_t *softd;
149	int i;
150
151	KMALLOC(softd, ipf_dstl_softc_t *);
152	if (softd == NULL) {
153		IPFERROR(120028);
154		return NULL;
155	}
156
157	bzero((char *)softd, sizeof(*softd));
158	for (i = 0; i <= IPL_LOGMAX; i++)
159		softd->tails[i] = &softd->dstlist[i];
160
161	return softd;
162}
163
164
165/* ------------------------------------------------------------------------ */
166/* Function:    ipf_dstlist_soft_destroy                                    */
167/* Returns:     Nil                                                         */
168/* Parameters:  softc(I) - pointer to soft context main structure           */
169/*              arg(I)   - pointer to local context to use                  */
170/*                                                                          */
171/* For destination lists, the only thing we have to do when destroying the  */
172/* soft context is free it!                                                 */
173/* ------------------------------------------------------------------------ */
174static void
175ipf_dstlist_soft_destroy(softc, arg)
176	ipf_main_softc_t *softc;
177	void *arg;
178{
179	ipf_dstl_softc_t *softd = arg;
180
181	KFREE(softd);
182}
183
184
185/* ------------------------------------------------------------------------ */
186/* Function:    ipf_dstlist_soft_init                                       */
187/* Returns:     int - 0 = success, else error                               */
188/* Parameters:  softc(I) - pointer to soft context main structure           */
189/*              arg(I)   - pointer to local context to use                  */
190/*                                                                          */
191/* There is currently no soft context for destination list management.      */
192/* ------------------------------------------------------------------------ */
193static int
194ipf_dstlist_soft_init(softc, arg)
195	ipf_main_softc_t *softc;
196	void *arg;
197{
198	return 0;
199}
200
201
202/* ------------------------------------------------------------------------ */
203/* Function:    ipf_dstlist_soft_fini                                       */
204/* Returns:     Nil                                                         */
205/* Parameters:  softc(I) - pointer to soft context main structure           */
206/*              arg(I)   - pointer to local context to use                  */
207/*                                                                          */
208/* There is currently no soft context for destination list management.      */
209/* ------------------------------------------------------------------------ */
210static void
211ipf_dstlist_soft_fini(softc, arg)
212	ipf_main_softc_t *softc;
213	void *arg;
214{
215	ipf_dstl_softc_t *softd = arg;
216	int i;
217
218	for (i = -1; i <= IPL_LOGMAX; i++) {
219		while (softd->dstlist[i + 1] != NULL) {
220			ipf_dstlist_table_remove(softc, softd,
221						 softd->dstlist[i + 1]);
222		}
223	}
224
225	ASSERT(softd->stats.ipls_numderefnodes == 0);
226}
227
228
229/* ------------------------------------------------------------------------ */
230/* Function:    ipf_dstlist_addr_find                                       */
231/* Returns:     int - 0 = success, else error                               */
232/* Parameters:  softc(I) - pointer to soft context main structure           */
233/*              arg1(I)  - pointer to local context to use                  */
234/*              arg2(I)  - pointer to local context to use                  */
235/*              arg3(I)  - pointer to local context to use                  */
236/*              arg4(I)  - pointer to local context to use                  */
237/*                                                                          */
238/* There is currently no such thing as searching a destination list for an  */
239/* address so this function becomes a no-op. Its presence is required as    */
240/* ipf_lookup_res_name() stores the "addr_find" function pointer in the     */
241/* pointer passed in to it as funcptr, although it could be a generic null- */
242/* op function rather than a specific one.                                  */
243/* ------------------------------------------------------------------------ */
244/*ARGSUSED*/
245static int
246ipf_dstlist_addr_find(softc, arg1, arg2, arg3, arg4)
247	ipf_main_softc_t *softc;
248	void *arg1, *arg3;
249	int arg2;
250	u_int arg4;
251{
252	return -1;
253}
254
255
256/* ------------------------------------------------------------------------ */
257/* Function:    ipf_dstlist_flush                                           */
258/* Returns:     int      - number of objects deleted                        */
259/* Parameters:  softc(I) - pointer to soft context main structure           */
260/*              arg(I)   - pointer to local context to use                  */
261/*              fop(I)   - pointer to lookup flush operation data           */
262/*                                                                          */
263/* Flush all of the destination tables that match the data passed in with   */
264/* the iplookupflush_t. There are two ways to match objects: the device for */
265/* which they are to be used with and their name.                           */
266/* ------------------------------------------------------------------------ */
267static size_t
268ipf_dstlist_flush(softc, arg, fop)
269	ipf_main_softc_t *softc;
270	void *arg;
271	iplookupflush_t *fop;
272{
273	ipf_dstl_softc_t *softd = arg;
274	ippool_dst_t *node, *next;
275	int n, i;
276
277	for (n = 0, i = -1; i <= IPL_LOGMAX; i++) {
278		if (fop->iplf_unit != IPLT_ALL && fop->iplf_unit != i)
279			continue;
280		for (node = softd->dstlist[i + 1]; node != NULL; node = next) {
281			next = node->ipld_next;
282
283			if ((*fop->iplf_name != '\0') &&
284			    strncmp(fop->iplf_name, node->ipld_name,
285				    FR_GROUPLEN))
286				continue;
287
288			ipf_dstlist_table_remove(softc, softd, node);
289			n++;
290		}
291	}
292	return n;
293}
294
295
296/* ------------------------------------------------------------------------ */
297/* Function:    ipf_dstlist_iter_deref                                      */
298/* Returns:     int      - 0 = success, else error                          */
299/* Parameters:  softc(I) - pointer to soft context main structure           */
300/*              arg(I)   - pointer to local context to use                  */
301/*              otype(I) - type of data structure to iterate through        */
302/*              unit(I)  - device we are working with                       */
303/*              data(I)  - address of object in kernel space                */
304/*                                                                          */
305/* This function is called when the iteration token is being free'd and is  */
306/* responsible for dropping the reference count of the structure it points  */
307/* to.                                                                      */
308/* ------------------------------------------------------------------------ */
309static int
310ipf_dstlist_iter_deref(softc, arg, otype, unit, data)
311	ipf_main_softc_t *softc;
312	void *arg;
313	int otype, unit;
314	void *data;
315{
316	if (data == NULL) {
317		IPFERROR(120001);
318		return EINVAL;
319	}
320
321	if (unit < -1 || unit > IPL_LOGMAX) {
322		IPFERROR(120002);
323		return EINVAL;
324	}
325
326	switch (otype)
327	{
328	case IPFLOOKUPITER_LIST :
329		ipf_dstlist_table_deref(softc, arg, (ippool_dst_t *)data);
330		break;
331
332	case IPFLOOKUPITER_NODE :
333		ipf_dstlist_node_deref(arg, (ipf_dstnode_t *)data);
334		break;
335	}
336
337	return 0;
338}
339
340
341/* ------------------------------------------------------------------------ */
342/* Function:    ipf_dstlist_iter_next                                       */
343/* Returns:     int - 0 = success, else error                               */
344/* Parameters:  softc(I) - pointer to soft context main structure           */
345/*              arg(I)   - pointer to local context to use                  */
346/*              op(I)    - pointer to lookup operation data                 */
347/*              uid(I)   - uid of process doing the ioctl                   */
348/*                                                                          */
349/* This function is responsible for either selecting the next destination   */
350/* list or node on a destination list to be returned as a user process      */
351/* iterates through the list of destination lists or nodes.                 */
352/* ------------------------------------------------------------------------ */
353static int
354ipf_dstlist_iter_next(softc, arg, token, iter)
355	ipf_main_softc_t *softc;
356	void *arg;
357	ipftoken_t *token;
358	ipflookupiter_t *iter;
359{
360	ipf_dstnode_t zn, *nextnode = NULL, *node = NULL;
361	ippool_dst_t zero, *next = NULL, *dsttab = NULL;
362	ipf_dstl_softc_t *softd = arg;
363	int err = 0;
364	void *hint;
365
366	switch (iter->ili_otype)
367	{
368	case IPFLOOKUPITER_LIST :
369		dsttab = token->ipt_data;
370		if (dsttab == NULL) {
371			next = softd->dstlist[(int)iter->ili_unit + 1];
372		} else {
373			next = dsttab->ipld_next;
374		}
375
376		if (next != NULL) {
377			ATOMIC_INC32(next->ipld_ref);
378			token->ipt_data = next;
379			hint = next->ipld_next;
380		} else {
381			bzero((char *)&zero, sizeof(zero));
382			next = &zero;
383			token->ipt_data = NULL;
384			hint = NULL;
385		}
386		break;
387
388	case IPFLOOKUPITER_NODE :
389		node = token->ipt_data;
390		if (node == NULL) {
391			dsttab = ipf_dstlist_table_find(arg, iter->ili_unit,
392							iter->ili_name);
393			if (dsttab == NULL) {
394				IPFERROR(120004);
395				err = ESRCH;
396				nextnode = NULL;
397			} else {
398				if (dsttab->ipld_dests == NULL)
399					nextnode = NULL;
400				else
401					nextnode = *dsttab->ipld_dests;
402				dsttab = NULL;
403			}
404		} else {
405			nextnode = node->ipfd_next;
406		}
407
408		if (nextnode != NULL) {
409			MUTEX_ENTER(&nextnode->ipfd_lock);
410			nextnode->ipfd_ref++;
411			MUTEX_EXIT(&nextnode->ipfd_lock);
412			token->ipt_data = nextnode;
413			hint = nextnode->ipfd_next;
414		} else {
415			bzero((char *)&zn, sizeof(zn));
416			nextnode = &zn;
417			token->ipt_data = NULL;
418			hint = NULL;
419		}
420		break;
421	default :
422		IPFERROR(120003);
423		err = EINVAL;
424		break;
425	}
426
427	if (err != 0)
428		return err;
429
430	switch (iter->ili_otype)
431	{
432	case IPFLOOKUPITER_LIST :
433		if (dsttab != NULL)
434			ipf_dstlist_table_deref(softc, arg, dsttab);
435		err = COPYOUT(next, iter->ili_data, sizeof(*next));
436		if (err != 0) {
437			IPFERROR(120005);
438			err = EFAULT;
439		}
440		break;
441
442	case IPFLOOKUPITER_NODE :
443		if (node != NULL)
444			ipf_dstlist_node_deref(arg, node);
445		err = COPYOUT(nextnode, iter->ili_data, sizeof(*nextnode));
446		if (err != 0) {
447			IPFERROR(120006);
448			err = EFAULT;
449		}
450		break;
451	}
452
453	if (hint == NULL)
454		ipf_token_mark_complete(token);
455
456	return err;
457}
458
459
460/* ------------------------------------------------------------------------ */
461/* Function:    ipf_dstlist_node_add                                        */
462/* Returns:     int - 0 = success, else error                               */
463/* Parameters:  softc(I) - pointer to soft context main structure           */
464/*              arg(I)   - pointer to local context to use                  */
465/*              op(I)    - pointer to lookup operation data                 */
466/*              uid(I)   - uid of process doing the ioctl                   */
467/* Locks:       WRITE(ipf_poolrw)                                           */
468/*                                                                          */
469/* Add a new node to a destination list. To do this, we only copy in the    */
470/* frdest_t structure because that contains the only data required from the */
471/* application to create a new node. The frdest_t doesn't contain the name  */
472/* itself. When loading filter rules, fd_name is a 'pointer' to the name.   */
473/* In this case, the 'pointer' does not work, instead it is the length of   */
474/* the name and the name is immediately following the frdest_t structure.   */
475/* fd_name must include the trailing \0, so it should be strlen(str) + 1.   */
476/* For simple sanity checking, an upper bound on the size of fd_name is     */
477/* imposed - 128.                                                          */
478/* ------------------------------------------------------------------------ */
479static int
480ipf_dstlist_node_add(softc, arg, op, uid)
481	ipf_main_softc_t *softc;
482	void *arg;
483	iplookupop_t *op;
484	int uid;
485{
486	ipf_dstl_softc_t *softd = arg;
487	ipf_dstnode_t *node, **nodes;
488	ippool_dst_t *d;
489	frdest_t dest;
490	int err;
491
492	if (op->iplo_size < sizeof(frdest_t)) {
493		IPFERROR(120007);
494		return EINVAL;
495	}
496
497	err = COPYIN(op->iplo_struct, &dest, sizeof(dest));
498	if (err != 0) {
499		IPFERROR(120009);
500		return EFAULT;
501	}
502
503	d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name);
504	if (d == NULL) {
505		IPFERROR(120010);
506		return ESRCH;
507	}
508
509	switch (dest.fd_addr.adf_family)
510	{
511	case AF_INET :
512	case AF_INET6 :
513		break;
514	default :
515		IPFERROR(120019);
516		return EINVAL;
517	}
518
519	if (dest.fd_name < -1 || dest.fd_name > 128) {
520		IPFERROR(120018);
521		return EINVAL;
522	}
523
524	KMALLOCS(node, ipf_dstnode_t *, sizeof(*node) + dest.fd_name);
525	if (node == NULL) {
526		softd->stats.ipls_nomem++;
527		IPFERROR(120008);
528		return ENOMEM;
529	}
530	bzero((char *)node, sizeof(*node) + dest.fd_name);
531
532	bcopy(&dest, &node->ipfd_dest, sizeof(dest));
533	node->ipfd_size = sizeof(*node) + dest.fd_name;
534
535	if (dest.fd_name > 0) {
536		/*
537		 * fd_name starts out as the length of the string to copy
538		 * in (including \0) and ends up being the offset from
539		 * fd_names (0).
540		 */
541		err = COPYIN((char *)op->iplo_struct + sizeof(dest),
542			     node->ipfd_names, dest.fd_name);
543		if (err != 0) {
544			IPFERROR(120017);
545			KFREES(node, node->ipfd_size);
546			return EFAULT;
547		}
548		node->ipfd_dest.fd_name = 0;
549	} else {
550		node->ipfd_dest.fd_name = -1;
551	}
552
553	if (d->ipld_nodes == d->ipld_maxnodes) {
554		KMALLOCS(nodes, ipf_dstnode_t **,
555			 sizeof(*nodes) * (d->ipld_maxnodes + 1));
556		if (nodes == NULL) {
557			softd->stats.ipls_nomem++;
558			IPFERROR(120022);
559			KFREES(node, node->ipfd_size);
560			return ENOMEM;
561		}
562		if (d->ipld_dests != NULL) {
563			bcopy(d->ipld_dests, nodes,
564			      sizeof(*nodes) * d->ipld_maxnodes);
565			KFREES(d->ipld_dests, sizeof(*nodes) * d->ipld_nodes);
566			nodes[0]->ipfd_pnext = nodes;
567		}
568		d->ipld_dests = nodes;
569		d->ipld_maxnodes++;
570	}
571	d->ipld_dests[d->ipld_nodes] = node;
572	d->ipld_nodes++;
573
574	if (d->ipld_nodes == 1) {
575		node->ipfd_pnext = d->ipld_dests;
576	} else if (d->ipld_nodes > 1) {
577		node->ipfd_pnext = &d->ipld_dests[d->ipld_nodes - 2]->ipfd_next;
578	}
579	*node->ipfd_pnext = node;
580
581	MUTEX_INIT(&node->ipfd_lock, "ipf dst node lock");
582	node->ipfd_uid = uid;
583	node->ipfd_ref = 1;
584	if (node->ipfd_dest.fd_name == 0)
585		(void) ipf_resolvedest(softc, node->ipfd_names,
586				       &node->ipfd_dest, AF_INET);
587#ifdef USE_INET6
588	if (node->ipfd_dest.fd_name == 0 &&
589	    node->ipfd_dest.fd_ptr == (void *)-1)
590		(void) ipf_resolvedest(softc, node->ipfd_names,
591				       &node->ipfd_dest, AF_INET6);
592#endif
593
594	softd->stats.ipls_numnodes++;
595
596	return 0;
597}
598
599
600/* ------------------------------------------------------------------------ */
601/* Function:    ipf_dstlist_node_deref                                      */
602/* Returns:     int - 0 = success, else error                               */
603/* Parameters:  arg(I)  - pointer to local context to use                   */
604/*              node(I) - pointer to destionation node to free              */
605/*                                                                          */
606/* Dereference the use count by one. If it drops to zero then we can assume */
607/* that it has been removed from any lists/tables and is ripe for freeing.  */
608/* The pointer to context is required for the purpose of maintaining        */
609/* statistics.                                                              */
610/* ------------------------------------------------------------------------ */
611static int
612ipf_dstlist_node_deref(arg, node)
613	void *arg;
614	ipf_dstnode_t *node;
615{
616	ipf_dstl_softc_t *softd = arg;
617	int ref;
618
619	MUTEX_ENTER(&node->ipfd_lock);
620	ref = --node->ipfd_ref;
621	MUTEX_EXIT(&node->ipfd_lock);
622
623	if (ref > 0)
624		return 0;
625
626	if ((node->ipfd_flags & IPDST_DELETE) != 0)
627		softd->stats.ipls_numderefnodes--;
628	MUTEX_DESTROY(&node->ipfd_lock);
629	KFREES(node, node->ipfd_size);
630	softd->stats.ipls_numnodes--;
631
632	return 0;
633}
634
635
636/* ------------------------------------------------------------------------ */
637/* Function:    ipf_dstlist_node_del                                        */
638/* Returns:     int      - 0 = success, else error                          */
639/* Parameters:  softc(I) - pointer to soft context main structure           */
640/*              arg(I)   - pointer to local context to use                  */
641/*              op(I)    - pointer to lookup operation data                 */
642/*              uid(I)   - uid of process doing the ioctl                   */
643/*                                                                          */
644/* Look for a matching destination node on the named table and free it if   */
645/* found. Because the name embedded in the frdest_t is variable in length,  */
646/* it is necessary to allocate some memory locally, to complete this op.    */
647/* ------------------------------------------------------------------------ */
648static int
649ipf_dstlist_node_del(softc, arg, op, uid)
650	ipf_main_softc_t *softc;
651	void *arg;
652	iplookupop_t *op;
653	int uid;
654{
655	ipf_dstl_softc_t *softd = arg;
656	ipf_dstnode_t *node;
657	frdest_t frd, *temp;
658	ippool_dst_t *d;
659	size_t size;
660	int err;
661
662	d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name);
663	if (d == NULL) {
664		IPFERROR(120012);
665		return ESRCH;
666	}
667
668	err = COPYIN(op->iplo_struct, &frd, sizeof(frd));
669	if (err != 0) {
670		IPFERROR(120011);
671		return EFAULT;
672	}
673
674	size = sizeof(*temp) + frd.fd_name;
675	KMALLOCS(temp, frdest_t *, size);
676	if (temp == NULL) {
677		softd->stats.ipls_nomem++;
678		IPFERROR(120026);
679		return ENOMEM;
680	}
681
682	err = COPYIN(op->iplo_struct, temp, size);
683	if (err != 0) {
684		IPFERROR(120027);
685		return EFAULT;
686	}
687
688	MUTEX_ENTER(&d->ipld_lock);
689	for (node = *d->ipld_dests; node != NULL; node = node->ipfd_next) {
690		if ((uid != 0) && (node->ipfd_uid != uid))
691			continue;
692		if (node->ipfd_size != size)
693			continue;
694		if (!bcmp(&node->ipfd_dest.fd_ip6, &frd.fd_ip6,
695			  size - offsetof(frdest_t, fd_ip6))) {
696			ipf_dstlist_node_free(softd, d, node);
697			MUTEX_EXIT(&d->ipld_lock);
698			KFREES(temp, size);
699			return 0;
700		}
701	}
702	MUTEX_EXIT(&d->ipld_lock);
703	KFREES(temp, size);
704
705	return ESRCH;
706}
707
708
709/* ------------------------------------------------------------------------ */
710/* Function:    ipf_dstlist_node_free                                       */
711/* Returns:     Nil                                                         */
712/* Parameters:  softd(I) - pointer to the destination list context          */
713/*              d(I)     - pointer to destination list                      */
714/*              node(I)  - pointer to node to free                          */
715/* Locks:       MUTEX(ipld_lock) or WRITE(ipf_poolrw)                       */
716/*                                                                          */
717/* Free the destination node by first removing it from any lists and then   */
718/* checking if this was the last reference held to the object. While the    */
719/* array of pointers to nodes is compacted, its size isn't reduced (by way  */
720/* of allocating a new smaller one and copying) because the belief is that  */
721/* it is likely the array will again reach that size.                       */
722/* ------------------------------------------------------------------------ */
723static void
724ipf_dstlist_node_free(softd, d, node)
725	ipf_dstl_softc_t *softd;
726	ippool_dst_t *d;
727	ipf_dstnode_t *node;
728{
729	int i;
730
731	/*
732	 * Compact the array of pointers to nodes.
733	 */
734	for (i = 0; i < d->ipld_nodes; i++)
735		if (d->ipld_dests[i] == node)
736			break;
737	if (d->ipld_nodes - i > 1) {
738		bcopy(&d->ipld_dests[i + 1], &d->ipld_dests[i],
739		      sizeof(*d->ipld_dests) * (d->ipld_nodes - i - 1));
740	}
741	d->ipld_nodes--;
742
743	if (node->ipfd_pnext != NULL)
744		*node->ipfd_pnext = node->ipfd_next;
745	if (node->ipfd_next != NULL)
746		node->ipfd_next->ipfd_pnext = node->ipfd_pnext;
747	node->ipfd_pnext = NULL;
748	node->ipfd_next = NULL;
749
750	if ((node->ipfd_flags & IPDST_DELETE) == 0) {
751		softd->stats.ipls_numderefnodes++;
752		node->ipfd_flags |= IPDST_DELETE;
753	}
754
755	ipf_dstlist_node_deref(softd, node);
756}
757
758
759/* ------------------------------------------------------------------------ */
760/* Function:    ipf_dstlist_stats_get                                       */
761/* Returns:     int - 0 = success, else error                               */
762/* Parameters:  softc(I) - pointer to soft context main structure           */
763/*              arg(I)   - pointer to local context to use                  */
764/*              op(I)    - pointer to lookup operation data                 */
765/*                                                                          */
766/* Return the current statistics for destination lists. This may be for all */
767/* of them or just information pertaining to a particular table.            */
768/* ------------------------------------------------------------------------ */
769/*ARGSUSED*/
770static int
771ipf_dstlist_stats_get(softc, arg, op)
772	ipf_main_softc_t *softc;
773	void *arg;
774	iplookupop_t *op;
775{
776	ipf_dstl_softc_t *softd = arg;
777	ipf_dstl_stat_t stats;
778	int unit, i, err = 0;
779
780	if (op->iplo_size != sizeof(ipf_dstl_stat_t)) {
781		IPFERROR(120023);
782		return EINVAL;
783	}
784
785	stats = softd->stats;
786	unit = op->iplo_unit;
787	if (unit == IPL_LOGALL) {
788		for (i = 0; i <= IPL_LOGMAX; i++)
789			stats.ipls_list[i] = softd->dstlist[i];
790	} else if (unit >= 0 && unit <= IPL_LOGMAX) {
791		void *ptr;
792
793		if (op->iplo_name[0] != '\0')
794			ptr = ipf_dstlist_table_find(softd, unit,
795						     op->iplo_name);
796		else
797			ptr = softd->dstlist[unit + 1];
798		stats.ipls_list[unit] = ptr;
799	} else {
800		IPFERROR(120024);
801		err = EINVAL;
802	}
803
804	if (err == 0) {
805		err = COPYOUT(&stats, op->iplo_struct, sizeof(stats));
806		if (err != 0) {
807			IPFERROR(120025);
808			return EFAULT;
809		}
810	}
811	return 0;
812}
813
814
815/* ------------------------------------------------------------------------ */
816/* Function:    ipf_dstlist_table_add                                       */
817/* Returns:     int      - 0 = success, else error                          */
818/* Parameters:  softc(I) - pointer to soft context main structure           */
819/*              arg(I)   - pointer to local context to use                  */
820/*              op(I)    - pointer to lookup operation data                 */
821/*                                                                          */
822/* Add a new destination table to the list of those available for the given */
823/* device. Because we seldom operate on these objects (find/add/delete),    */
824/* they are just kept in a simple linked list.                              */
825/* ------------------------------------------------------------------------ */
826static int
827ipf_dstlist_table_add(softc, arg, op)
828	ipf_main_softc_t *softc;
829	void *arg;
830	iplookupop_t *op;
831{
832	ipf_dstl_softc_t *softd = arg;
833	ippool_dst_t user, *d, *new;
834	int unit, err;
835
836	d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name);
837	if (d != NULL) {
838		IPFERROR(120013);
839		return EEXIST;
840	}
841
842	err = COPYIN(op->iplo_struct, &user, sizeof(user));
843	if (err != 0) {
844		IPFERROR(120021);
845		return EFAULT;
846	}
847
848	KMALLOC(new, ippool_dst_t *);
849	if (new == NULL) {
850		softd->stats.ipls_nomem++;
851		IPFERROR(120014);
852		return ENOMEM;
853	}
854	bzero((char *)new, sizeof(*new));
855
856	MUTEX_INIT(&new->ipld_lock, "ipf dst table lock");
857
858	strncpy(new->ipld_name, op->iplo_name, FR_GROUPLEN);
859	unit = op->iplo_unit;
860	new->ipld_unit = unit;
861	new->ipld_policy = user.ipld_policy;
862	new->ipld_seed = ipf_random();
863	new->ipld_ref = 1;
864
865	new->ipld_pnext = softd->tails[unit + 1];
866	*softd->tails[unit + 1] = new;
867	softd->tails[unit + 1] = &new->ipld_next;
868	softd->stats.ipls_numlists++;
869
870	return 0;
871}
872
873
874/* ------------------------------------------------------------------------ */
875/* Function:    ipf_dstlist_table_del                                       */
876/* Returns:     int - 0 = success, else error                               */
877/* Parameters:  softc(I) - pointer to soft context main structure           */
878/*              arg(I)   - pointer to local context to use                  */
879/*              op(I)    - pointer to lookup operation data                 */
880/*                                                                          */
881/* Find a named destinstion list table and delete it. If there are other    */
882/* references to it, the caller isn't told.                                 */
883/* ------------------------------------------------------------------------ */
884static int
885ipf_dstlist_table_del(softc, arg, op)
886	ipf_main_softc_t *softc;
887	void *arg;
888	iplookupop_t *op;
889{
890	ippool_dst_t *d;
891
892	d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name);
893	if (d == NULL) {
894		IPFERROR(120015);
895		return ESRCH;
896	}
897
898	if (d->ipld_dests != NULL) {
899		IPFERROR(120016);
900		return EBUSY;
901	}
902
903	ipf_dstlist_table_remove(softc, arg, d);
904
905	return 0;
906}
907
908
909/* ------------------------------------------------------------------------ */
910/* Function:    ipf_dstlist_table_remove                                    */
911/* Returns:     Nil                                                         */
912/* Parameters:  softc(I) - pointer to soft context main structure           */
913/*              softd(I) - pointer to the destination list context          */
914/*              d(I)     - pointer to destination list                      */
915/*                                                                          */
916/* Remove a given destination list from existance. While the IPDST_DELETE   */
917/* flag is set every time we call this function and the reference count is  */
918/* non-zero, the "numdereflists" counter is always incremented because the  */
919/* decision about whether it will be freed or not is not made here. This    */
920/* means that the only action the code can take here is to treat it as if   */
921/* it will become a detached.                                               */
922/* ------------------------------------------------------------------------ */
923static void
924ipf_dstlist_table_remove(softc, softd, d)
925	ipf_main_softc_t *softc;
926	ipf_dstl_softc_t *softd;
927	ippool_dst_t *d;
928{
929
930	if (softd->tails[d->ipld_unit + 1] == &d->ipld_next)
931		softd->tails[d->ipld_unit + 1] = d->ipld_pnext;
932
933	if (d->ipld_pnext != NULL)
934		*d->ipld_pnext = d->ipld_next;
935	if (d->ipld_next != NULL)
936		d->ipld_next->ipld_pnext = d->ipld_pnext;
937	d->ipld_pnext = NULL;
938	d->ipld_next = NULL;
939
940	ipf_dstlist_table_clearnodes(softd, d);
941
942	softd->stats.ipls_numdereflists++;
943	d->ipld_flags |= IPDST_DELETE;
944
945	ipf_dstlist_table_deref(softc, softd, d);
946}
947
948
949/* ------------------------------------------------------------------------ */
950/* Function:    ipf_dstlist_table_free                                      */
951/* Returns:     Nil                                                         */
952/* Parameters:  softd(I) - pointer to the destination list context          */
953/*              d(I)   - pointer to destination list                        */
954/*                                                                          */
955/* Free up a destination list data structure and any other memory that was  */
956/* directly allocated as part of creating it. Individual destination list   */
957/* nodes are not freed. It is assumed the caller will have already emptied  */
958/* the destination list.                                                    */
959/* ------------------------------------------------------------------------ */
960static void
961ipf_dstlist_table_free(softd, d)
962	ipf_dstl_softc_t *softd;
963	ippool_dst_t *d;
964{
965	MUTEX_DESTROY(&d->ipld_lock);
966
967	if ((d->ipld_flags & IPDST_DELETE) != 0)
968		softd->stats.ipls_numdereflists--;
969	softd->stats.ipls_numlists--;
970
971	if (d->ipld_dests != NULL) {
972		KFREES(d->ipld_dests,
973		       d->ipld_maxnodes * sizeof(*d->ipld_dests));
974	}
975
976	KFREE(d);
977}
978
979
980/* ------------------------------------------------------------------------ */
981/* Function:    ipf_dstlist_table_deref                                     */
982/* Returns:     int - 0 = success, else error                               */
983/* Parameters:  softc(I) - pointer to soft context main structure           */
984/*              arg(I)   - pointer to local context to use                  */
985/*              op(I)    - pointer to lookup operation data                 */
986/*                                                                          */
987/* Drops the reference count on a destination list table object and free's  */
988/* it if 0 has been reached.                                                */
989/* ------------------------------------------------------------------------ */
990static int
991ipf_dstlist_table_deref(softc, arg, table)
992	ipf_main_softc_t *softc;
993	void *arg;
994	void *table;
995{
996	ippool_dst_t *d = table;
997
998	d->ipld_ref--;
999	if (d->ipld_ref > 0)
1000		return d->ipld_ref;
1001
1002	ipf_dstlist_table_free(arg, d);
1003
1004	return 0;
1005}
1006
1007
1008/* ------------------------------------------------------------------------ */
1009/* Function:    ipf_dstlist_table_clearnodes                                */
1010/* Returns:     Nil                                                         */
1011/* Parameters:  softd(I) - pointer to the destination list context          */
1012/*              dst(I)   - pointer to destination list                      */
1013/*                                                                          */
1014/* Free all of the destination nodes attached to the given table.           */
1015/* ------------------------------------------------------------------------ */
1016static void
1017ipf_dstlist_table_clearnodes(softd, dst)
1018	ipf_dstl_softc_t *softd;
1019	ippool_dst_t *dst;
1020{
1021	ipf_dstnode_t *node;
1022
1023	if (dst->ipld_dests == NULL)
1024		return;
1025
1026	while ((node = *dst->ipld_dests) != NULL) {
1027		ipf_dstlist_node_free(softd, dst, node);
1028	}
1029}
1030
1031
1032/* ------------------------------------------------------------------------ */
1033/* Function:    ipf_dstlist_table_find                                      */
1034/* Returns:     int      - 0 = success, else error                          */
1035/* Parameters:  arg(I)   - pointer to local context to use                  */
1036/*              unit(I)  - device we are working with                       */
1037/*              name(I)  - destination table name to find                   */
1038/*                                                                          */
1039/* Return a pointer to a destination table that matches the unit+name that  */
1040/* is passed in.                                                            */
1041/* ------------------------------------------------------------------------ */
1042static void *
1043ipf_dstlist_table_find(arg, unit, name)
1044	void *arg;
1045	int unit;
1046	char *name;
1047{
1048	ipf_dstl_softc_t *softd = arg;
1049	ippool_dst_t *d;
1050
1051	for (d = softd->dstlist[unit + 1]; d != NULL; d = d->ipld_next) {
1052		if ((d->ipld_unit == unit) &&
1053		    !strncmp(d->ipld_name, name, FR_GROUPLEN)) {
1054			return d;
1055		}
1056	}
1057
1058	return NULL;
1059}
1060
1061
1062/* ------------------------------------------------------------------------ */
1063/* Function:    ipf_dstlist_select_ref                                      */
1064/* Returns:     void *   - NULL = failure, else pointer to table            */
1065/* Parameters:  arg(I)   - pointer to local context to use                  */
1066/*              unit(I)  - device we are working with                       */
1067/*              name(I)  - destination table name to find                   */
1068/*                                                                          */
1069/* Attempt to find a destination table that matches the name passed in and  */
1070/* if successful, bump up the reference count on it because we intend to    */
1071/* store the pointer to it somewhere else.                                  */
1072/* ------------------------------------------------------------------------ */
1073static void *
1074ipf_dstlist_select_ref(arg, unit, name)
1075	void *arg;
1076	int unit;
1077	char *name;
1078{
1079	ippool_dst_t *d;
1080
1081	d = ipf_dstlist_table_find(arg, unit, name);
1082	if (d != NULL) {
1083		MUTEX_ENTER(&d->ipld_lock);
1084		d->ipld_ref++;
1085		MUTEX_EXIT(&d->ipld_lock);
1086	}
1087	return d;
1088}
1089
1090
1091/* ------------------------------------------------------------------------ */
1092/* Function:    ipf_dstlist_select                                          */
1093/* Returns:     void * - NULL = failure, else pointer to table              */
1094/* Parameters:  fin(I) - pointer to packet information                      */
1095/*              d(I)   - pointer to destination list                        */
1096/*                                                                          */
1097/* Find the next node in the destination list to be used according to the   */
1098/* defined policy. Of these, "connection" is the most expensive policy to   */
1099/* implement as it always looks for the node with the least number of       */
1100/* connections associated with it.                                          */
1101/*                                                                          */
1102/* The hashes exclude the port numbers so that all protocols map to the     */
1103/* same destination. Otherwise, someone doing a ping would target a         */
1104/* different server than their TCP connection, etc. MD-5 is used to         */
1105/* transform the addressese into something random that the other end could  */
1106/* not easily guess and use in an attack. ipld_seed introduces an unknown   */
1107/* into the hash calculation to increase the difficult of an attacker       */
1108/* guessing the bucket.                                                     */
1109/*                                                                          */
1110/* One final comment: mixing different address families in a single pool    */
1111/* will currently result in failures as the address family of the node is   */
1112/* only matched up with that in the packet as the last step. While this can */
1113/* be coded around for the weighted connection and round-robin models, it   */
1114/* cannot be supported for the hash/random models as they do not search and */
1115/* nor is the algorithm conducive to searching.                             */
1116/* ------------------------------------------------------------------------ */
1117static ipf_dstnode_t *
1118ipf_dstlist_select(fin, d)
1119	fr_info_t *fin;
1120	ippool_dst_t *d;
1121{
1122	ipf_dstnode_t *node, *sel;
1123	int connects;
1124	u_32_t hash[4];
1125	MD5_CTX ctx;
1126	int family;
1127	int x;
1128
1129	if (d->ipld_dests == NULL || *d->ipld_dests == NULL)
1130		return NULL;
1131
1132	family = fin->fin_family;
1133
1134	MUTEX_ENTER(&d->ipld_lock);
1135
1136	switch (d->ipld_policy)
1137	{
1138	case IPLDP_ROUNDROBIN:
1139		sel = d->ipld_selected;
1140		if (sel == NULL) {
1141			sel = *d->ipld_dests;
1142		} else {
1143			sel = sel->ipfd_next;
1144			if (sel == NULL)
1145				sel = *d->ipld_dests;
1146		}
1147		break;
1148
1149	case IPLDP_CONNECTION:
1150		if (d->ipld_selected == NULL) {
1151			sel = *d->ipld_dests;
1152			break;
1153		}
1154
1155		sel = d->ipld_selected;
1156		connects = 0x7fffffff;
1157		node = sel->ipfd_next;
1158		if (node == NULL)
1159			node = *d->ipld_dests;
1160		while (node != d->ipld_selected) {
1161			if (node->ipfd_states == 0) {
1162				sel = node;
1163				break;
1164			}
1165			if (node->ipfd_states < connects) {
1166				sel = node;
1167				connects = node->ipfd_states;
1168			}
1169			node = node->ipfd_next;
1170			if (node == NULL)
1171				node = *d->ipld_dests;
1172		}
1173		break;
1174
1175	case IPLDP_RANDOM :
1176		x = ipf_random() % d->ipld_nodes;
1177		sel = d->ipld_dests[x];
1178		break;
1179
1180	case IPLDP_HASHED :
1181		MD5Init(&ctx);
1182		MD5Update(&ctx, (u_char *)&d->ipld_seed, sizeof(d->ipld_seed));
1183		MD5Update(&ctx, (u_char *)&fin->fin_src6,
1184			  sizeof(fin->fin_src6));
1185		MD5Update(&ctx, (u_char *)&fin->fin_dst6,
1186			  sizeof(fin->fin_dst6));
1187		MD5Final((u_char *)hash, &ctx);
1188		x = hash[0] % d->ipld_nodes;
1189		sel = d->ipld_dests[x];
1190		break;
1191
1192	case IPLDP_SRCHASH :
1193		MD5Init(&ctx);
1194		MD5Update(&ctx, (u_char *)&d->ipld_seed, sizeof(d->ipld_seed));
1195		MD5Update(&ctx, (u_char *)&fin->fin_src6,
1196			  sizeof(fin->fin_src6));
1197		MD5Final((u_char *)hash, &ctx);
1198		x = hash[0] % d->ipld_nodes;
1199		sel = d->ipld_dests[x];
1200		break;
1201
1202	case IPLDP_DSTHASH :
1203		MD5Init(&ctx);
1204		MD5Update(&ctx, (u_char *)&d->ipld_seed, sizeof(d->ipld_seed));
1205		MD5Update(&ctx, (u_char *)&fin->fin_dst6,
1206			  sizeof(fin->fin_dst6));
1207		MD5Final((u_char *)hash, &ctx);
1208		x = hash[0] % d->ipld_nodes;
1209		sel = d->ipld_dests[x];
1210		break;
1211
1212	default :
1213		sel = NULL;
1214		break;
1215	}
1216
1217	if (sel->ipfd_dest.fd_addr.adf_family != family)
1218		sel = NULL;
1219	d->ipld_selected = sel;
1220
1221	MUTEX_EXIT(&d->ipld_lock);
1222
1223	return sel;
1224}
1225
1226
1227/* ------------------------------------------------------------------------ */
1228/* Function:    ipf_dstlist_select_node                                     */
1229/* Returns:     int      - -1 == failure, 0 == success                      */
1230/* Parameters:  fin(I)   - pointer to packet information                    */
1231/*              group(I) - destination pool to search                       */
1232/*              addr(I)  - pointer to store selected address                */
1233/*              pfdp(O)  - pointer to storage for selected destination node */
1234/*                                                                          */
1235/* This function is only responsible for obtaining the next IP address for  */
1236/* use and storing it in the caller's address space (addr). "addr" is only  */
1237/* used for storage if pfdp is NULL. No permanent reference is currently    */
1238/* kept on the node.                                                        */
1239/* ------------------------------------------------------------------------ */
1240int
1241ipf_dstlist_select_node(fin, group, addr, pfdp)
1242	fr_info_t *fin;
1243	void *group;
1244	u_32_t *addr;
1245	frdest_t *pfdp;
1246{
1247#ifdef USE_MUTEXES
1248	ipf_main_softc_t *softc = fin->fin_main_soft;
1249#endif
1250	ippool_dst_t *d = group;
1251	ipf_dstnode_t *node;
1252	frdest_t *fdp;
1253
1254	READ_ENTER(&softc->ipf_poolrw);
1255
1256	node = ipf_dstlist_select(fin, d);
1257	if (node == NULL) {
1258		RWLOCK_EXIT(&softc->ipf_poolrw);
1259		return -1;
1260	}
1261
1262	if (pfdp != NULL) {
1263		bcopy(&node->ipfd_dest, pfdp, sizeof(*pfdp));
1264	} else {
1265		if (fin->fin_family == AF_INET) {
1266			addr[0] = node->ipfd_dest.fd_addr.adf_addr.i6[0];
1267		} else if (fin->fin_family == AF_INET6) {
1268			addr[0] = node->ipfd_dest.fd_addr.adf_addr.i6[0];
1269			addr[1] = node->ipfd_dest.fd_addr.adf_addr.i6[1];
1270			addr[2] = node->ipfd_dest.fd_addr.adf_addr.i6[2];
1271			addr[3] = node->ipfd_dest.fd_addr.adf_addr.i6[3];
1272		}
1273	}
1274
1275	fdp = &node->ipfd_dest;
1276	if (fdp->fd_ptr == NULL)
1277		fdp->fd_ptr = fin->fin_ifp;
1278
1279	MUTEX_ENTER(&node->ipfd_lock);
1280	node->ipfd_states++;
1281	MUTEX_EXIT(&node->ipfd_lock);
1282
1283	RWLOCK_EXIT(&softc->ipf_poolrw);
1284
1285	return 0;
1286}
1287
1288
1289/* ------------------------------------------------------------------------ */
1290/* Function:    ipf_dstlist_expire                                          */
1291/* Returns:     Nil                                                         */
1292/* Parameters:  softc(I) - pointer to soft context main structure           */
1293/*              arg(I)   - pointer to local context to use                  */
1294/*                                                                          */
1295/* There are currently no objects to expire in destination lists.           */
1296/* ------------------------------------------------------------------------ */
1297static void
1298ipf_dstlist_expire(softc, arg)
1299	ipf_main_softc_t *softc;
1300	void *arg;
1301{
1302	return;
1303}
1304
1305
1306/* ------------------------------------------------------------------------ */
1307/* Function:    ipf_dstlist_sync                                            */
1308/* Returns:     Nil                                                         */
1309/* Parameters:  softc(I) - pointer to soft context main structure           */
1310/*              arg(I)   - pointer to local context to use                  */
1311/*                                                                          */
1312/* When a network interface appears or disappears, we need to revalidate    */
1313/* all of the network interface names that have been configured as a target */
1314/* in a destination list.                                                   */
1315/* ------------------------------------------------------------------------ */
1316void
1317ipf_dstlist_sync(softc, arg)
1318	ipf_main_softc_t *softc;
1319	void *arg;
1320{
1321	ipf_dstl_softc_t *softd = arg;
1322	ipf_dstnode_t *node;
1323	ippool_dst_t *list;
1324	int i;
1325	int j;
1326
1327	for (i = 0; i < IPL_LOGMAX; i++) {
1328		for (list = softd->dstlist[i]; list != NULL;
1329		     list = list->ipld_next) {
1330			for (j = 0; j < list->ipld_maxnodes; j++) {
1331				node = list->ipld_dests[j];
1332				if (node == NULL)
1333					continue;
1334				if (node->ipfd_dest.fd_name == -1)
1335					continue;
1336				(void) ipf_resolvedest(softc,
1337						       node->ipfd_names,
1338						       &node->ipfd_dest,
1339						       AF_INET);
1340			}
1341		}
1342	}
1343}
1344