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