/* $NetBSD: ip_dstlist.c,v 1.8 2014/06/28 07:59:26 darrenr Exp $ */ /* * Copyright (C) 2012 by Darren Reed. * * See the IPFILTER.LICENCE file for details on licencing. */ #if defined(KERNEL) || defined(_KERNEL) # undef KERNEL # undef _KERNEL # define KERNEL 1 # define _KERNEL 1 #endif #if defined(__osf__) # define _PROTO_NET_H_ #endif #include #include #include #include #if !defined(_KERNEL) && !defined(__KERNEL__) # include # include # include # define _KERNEL # ifdef __OpenBSD__ struct file; # endif # include # undef _KERNEL #else # include # if defined(NetBSD) && (__NetBSD_Version__ >= 104000000) # include # endif #endif #include #if !defined(linux) # include #endif #include #if defined(_KERNEL) && (!defined(__SVR4) && !defined(__svr4__)) # include #endif #if defined(__SVR4) || defined(__svr4__) # include # include # ifdef _KERNEL # include # endif # include # include #endif #if defined(__FreeBSD_version) && (__FreeBSD_version >= 300000) # include #endif #include #include #include "netinet/ip_compat.h" #include "netinet/ip_fil.h" #include "netinet/ip_nat.h" #include "netinet/ip_lookup.h" #include "netinet/ip_dstlist.h" /* END OF INCLUDES */ #ifdef HAS_SYS_MD5_H # include #else # include "md5.h" #endif __KERNEL_RCSID(0, "Id: ip_dstlist.c,v 1.1.1.2 2012/07/22 13:45:11 darrenr Exp"); typedef struct ipf_dstl_softc_s { ippool_dst_t *dstlist[LOOKUP_POOL_SZ]; ippool_dst_t **tails[LOOKUP_POOL_SZ]; ipf_dstl_stat_t stats; } ipf_dstl_softc_t; static void *ipf_dstlist_soft_create(ipf_main_softc_t *); static void ipf_dstlist_soft_destroy(ipf_main_softc_t *, void *); static int ipf_dstlist_soft_init(ipf_main_softc_t *, void *); static void ipf_dstlist_soft_fini(ipf_main_softc_t *, void *); static int ipf_dstlist_addr_find(ipf_main_softc_t *, void *, int, void *, u_int); static size_t ipf_dstlist_flush(ipf_main_softc_t *, void *, iplookupflush_t *); static void ipf_dstlist_table_free(ipf_dstl_softc_t *, ippool_dst_t *); static int ipf_dstlist_iter_deref(ipf_main_softc_t *, void *, int, int, void *); static int ipf_dstlist_iter_next(ipf_main_softc_t *, void *, ipftoken_t *, ipflookupiter_t *); static int ipf_dstlist_node_add(ipf_main_softc_t *, void *, iplookupop_t *, int); static int ipf_dstlist_node_del(ipf_main_softc_t *, void *, iplookupop_t *, int); static int ipf_dstlist_stats_get(ipf_main_softc_t *, void *, iplookupop_t *); static int ipf_dstlist_table_add(ipf_main_softc_t *, void *, iplookupop_t *); static int ipf_dstlist_table_del(ipf_main_softc_t *, void *, iplookupop_t *); static int ipf_dstlist_table_deref(ipf_main_softc_t *, void *, void *); static void *ipf_dstlist_table_find(void *, int, char *); static void ipf_dstlist_table_remove(ipf_main_softc_t *, ipf_dstl_softc_t *, ippool_dst_t *); static void ipf_dstlist_table_clearnodes(ipf_dstl_softc_t *, ippool_dst_t *); static ipf_dstnode_t *ipf_dstlist_select(fr_info_t *, ippool_dst_t *); static void *ipf_dstlist_select_ref(void *, int, char *); static void ipf_dstlist_node_free(ipf_dstl_softc_t *, ippool_dst_t *, ipf_dstnode_t *); static int ipf_dstlist_node_deref(void *, ipf_dstnode_t *); static void ipf_dstlist_expire(ipf_main_softc_t *, void *); static void ipf_dstlist_sync(ipf_main_softc_t *, void *); ipf_lookup_t ipf_dstlist_backend = { IPLT_DSTLIST, ipf_dstlist_soft_create, ipf_dstlist_soft_destroy, ipf_dstlist_soft_init, ipf_dstlist_soft_fini, ipf_dstlist_addr_find, ipf_dstlist_flush, ipf_dstlist_iter_deref, ipf_dstlist_iter_next, ipf_dstlist_node_add, ipf_dstlist_node_del, ipf_dstlist_stats_get, ipf_dstlist_table_add, ipf_dstlist_table_del, ipf_dstlist_table_deref, ipf_dstlist_table_find, ipf_dstlist_select_ref, ipf_dstlist_select_node, ipf_dstlist_expire, ipf_dstlist_sync }; /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_soft_create */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* */ /* Allocating a chunk of memory filled with 0's is enough for the current */ /* soft context used with destination lists. */ /* ------------------------------------------------------------------------ */ static void * ipf_dstlist_soft_create(ipf_main_softc_t *softc) { ipf_dstl_softc_t *softd; int i; KMALLOC(softd, ipf_dstl_softc_t *); if (softd == NULL) { IPFERROR(120028); return NULL; } bzero((char *)softd, sizeof(*softd)); for (i = 0; i <= IPL_LOGMAX; i++) softd->tails[i] = &softd->dstlist[i]; return softd; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_soft_destroy */ /* Returns: Nil */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* */ /* For destination lists, the only thing we have to do when destroying the */ /* soft context is free it! */ /* ------------------------------------------------------------------------ */ static void ipf_dstlist_soft_destroy(ipf_main_softc_t *softc, void *arg) { ipf_dstl_softc_t *softd = arg; KFREE(softd); } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_soft_init */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* */ /* There is currently no soft context for destination list management. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_soft_init(ipf_main_softc_t *softc, void *arg) { return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_soft_fini */ /* Returns: Nil */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* */ /* There is currently no soft context for destination list management. */ /* ------------------------------------------------------------------------ */ static void ipf_dstlist_soft_fini(ipf_main_softc_t *softc, void *arg) { ipf_dstl_softc_t *softd = arg; int i; for (i = -1; i <= IPL_LOGMAX; i++) { while (softd->dstlist[i + 1] != NULL) { ipf_dstlist_table_remove(softc, softd, softd->dstlist[i + 1]); } } ASSERT(softd->stats.ipls_numderefnodes == 0); } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_addr_find */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg1(I) - pointer to local context to use */ /* arg2(I) - pointer to local context to use */ /* arg3(I) - pointer to local context to use */ /* arg4(I) - pointer to local context to use */ /* */ /* There is currently no such thing as searching a destination list for an */ /* address so this function becomes a no-op. Its presence is required as */ /* ipf_lookup_res_name() stores the "addr_find" function pointer in the */ /* pointer passed in to it as funcptr, although it could be a generic null- */ /* op function rather than a specific one. */ /* ------------------------------------------------------------------------ */ /*ARGSUSED*/ static int ipf_dstlist_addr_find(ipf_main_softc_t *softc, void *arg1, int arg2, void *arg3, u_int arg4) { return -1; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_flush */ /* Returns: int - number of objects deleted */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* fop(I) - pointer to lookup flush operation data */ /* */ /* Flush all of the destination tables that match the data passed in with */ /* the iplookupflush_t. There are two ways to match objects: the device for */ /* which they are to be used with and their name. */ /* ------------------------------------------------------------------------ */ static size_t ipf_dstlist_flush(ipf_main_softc_t *softc, void *arg, iplookupflush_t *fop) { ipf_dstl_softc_t *softd = arg; ippool_dst_t *node, *next; int n, i; for (n = 0, i = -1; i <= IPL_LOGMAX; i++) { if (fop->iplf_unit != IPLT_ALL && fop->iplf_unit != i) continue; for (node = softd->dstlist[i + 1]; node != NULL; node = next) { next = node->ipld_next; if ((*fop->iplf_name != '\0') && strncmp(fop->iplf_name, node->ipld_name, FR_GROUPLEN)) continue; ipf_dstlist_table_remove(softc, softd, node); n++; } } return n; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_iter_deref */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* otype(I) - type of data structure to iterate through */ /* unit(I) - device we are working with */ /* data(I) - address of object in kernel space */ /* */ /* This function is called when the iteration token is being free'd and is */ /* responsible for dropping the reference count of the structure it points */ /* to. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_iter_deref(ipf_main_softc_t *softc, void *arg, int otype, int unit, void *data) { if (data == NULL) { IPFERROR(120001); return EINVAL; } if (unit < -1 || unit > IPL_LOGMAX) { IPFERROR(120002); return EINVAL; } switch (otype) { case IPFLOOKUPITER_LIST : ipf_dstlist_table_deref(softc, arg, (ippool_dst_t *)data); break; case IPFLOOKUPITER_NODE : ipf_dstlist_node_deref(arg, (ipf_dstnode_t *)data); break; } return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_iter_next */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* op(I) - pointer to lookup operation data */ /* uid(I) - uid of process doing the ioctl */ /* */ /* This function is responsible for either selecting the next destination */ /* list or node on a destination list to be returned as a user process */ /* iterates through the list of destination lists or nodes. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_iter_next(ipf_main_softc_t *softc, void *arg, ipftoken_t *token, ipflookupiter_t *iter) { ipf_dstnode_t zn, *nextnode = NULL, *node = NULL; ippool_dst_t zero, *next = NULL, *dsttab = NULL; ipf_dstl_softc_t *softd = arg; int err = 0; void *hint; switch (iter->ili_otype) { case IPFLOOKUPITER_LIST : dsttab = token->ipt_data; if (dsttab == NULL) { next = softd->dstlist[(int)iter->ili_unit + 1]; } else { next = dsttab->ipld_next; } if (next != NULL) { ATOMIC_INC32(next->ipld_ref); token->ipt_data = next; hint = next->ipld_next; } else { bzero((char *)&zero, sizeof(zero)); next = &zero; token->ipt_data = NULL; hint = NULL; } break; case IPFLOOKUPITER_NODE : node = token->ipt_data; if (node == NULL) { dsttab = ipf_dstlist_table_find(arg, iter->ili_unit, iter->ili_name); if (dsttab == NULL) { IPFERROR(120004); err = ESRCH; nextnode = NULL; } else { if (dsttab->ipld_dests == NULL) nextnode = NULL; else nextnode = *dsttab->ipld_dests; dsttab = NULL; } } else { nextnode = node->ipfd_next; } if (nextnode != NULL) { MUTEX_ENTER(&nextnode->ipfd_lock); nextnode->ipfd_ref++; MUTEX_EXIT(&nextnode->ipfd_lock); token->ipt_data = nextnode; hint = nextnode->ipfd_next; } else { bzero((char *)&zn, sizeof(zn)); nextnode = &zn; token->ipt_data = NULL; hint = NULL; } break; default : IPFERROR(120003); err = EINVAL; break; } if (err != 0) return err; switch (iter->ili_otype) { case IPFLOOKUPITER_LIST : if (dsttab != NULL) ipf_dstlist_table_deref(softc, arg, dsttab); err = COPYOUT(next, iter->ili_data, sizeof(*next)); if (err != 0) { IPFERROR(120005); err = EFAULT; } break; case IPFLOOKUPITER_NODE : if (node != NULL) ipf_dstlist_node_deref(arg, node); err = COPYOUT(nextnode, iter->ili_data, sizeof(*nextnode)); if (err != 0) { IPFERROR(120006); err = EFAULT; } break; } if (hint == NULL) ipf_token_mark_complete(token); return err; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_node_add */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* op(I) - pointer to lookup operation data */ /* uid(I) - uid of process doing the ioctl */ /* Locks: WRITE(ipf_poolrw) */ /* */ /* Add a new node to a destination list. To do this, we only copy in the */ /* frdest_t structure because that contains the only data required from the */ /* application to create a new node. The frdest_t doesn't contain the name */ /* itself. When loading filter rules, fd_name is a 'pointer' to the name. */ /* In this case, the 'pointer' does not work, instead it is the length of */ /* the name and the name is immediately following the frdest_t structure. */ /* fd_name must include the trailing \0, so it should be strlen(str) + 1. */ /* For simple sanity checking, an upper bound on the size of fd_name is */ /* imposed - 128. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_node_add(ipf_main_softc_t *softc, void *arg, iplookupop_t *op, int uid) { ipf_dstl_softc_t *softd = arg; ipf_dstnode_t *node, **nodes; ippool_dst_t *d; frdest_t dest; int err; if (op->iplo_size < sizeof(frdest_t)) { IPFERROR(120007); return EINVAL; } err = COPYIN(op->iplo_struct, &dest, sizeof(dest)); if (err != 0) { IPFERROR(120009); return EFAULT; } d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name); if (d == NULL) { IPFERROR(120010); return ESRCH; } switch (dest.fd_addr.adf_family) { case AF_INET : case AF_INET6 : break; default : IPFERROR(120019); return EINVAL; } if (dest.fd_name < -1 || dest.fd_name > 128) { IPFERROR(120018); return EINVAL; } KMALLOCS(node, ipf_dstnode_t *, sizeof(*node) + dest.fd_name); if (node == NULL) { softd->stats.ipls_nomem++; IPFERROR(120008); return ENOMEM; } bzero((char *)node, sizeof(*node) + dest.fd_name); bcopy(&dest, &node->ipfd_dest, sizeof(dest)); node->ipfd_size = sizeof(*node) + dest.fd_name; if (dest.fd_name > 0) { /* * fd_name starts out as the length of the string to copy * in (including \0) and ends up being the offset from * fd_names (0). */ err = COPYIN((char *)op->iplo_struct + sizeof(dest), node->ipfd_names, dest.fd_name); if (err != 0) { IPFERROR(120017); KFREES(node, node->ipfd_size); return EFAULT; } node->ipfd_dest.fd_name = 0; } else { node->ipfd_dest.fd_name = -1; } if (d->ipld_nodes == d->ipld_maxnodes) { KMALLOCS(nodes, ipf_dstnode_t **, sizeof(*nodes) * (d->ipld_maxnodes + 1)); if (nodes == NULL) { softd->stats.ipls_nomem++; IPFERROR(120022); KFREES(node, node->ipfd_size); return ENOMEM; } if (d->ipld_dests != NULL) { bcopy(d->ipld_dests, nodes, sizeof(*nodes) * d->ipld_maxnodes); KFREES(d->ipld_dests, sizeof(*nodes) * d->ipld_nodes); nodes[0]->ipfd_pnext = nodes; } d->ipld_dests = nodes; d->ipld_maxnodes++; } d->ipld_dests[d->ipld_nodes] = node; d->ipld_nodes++; if (d->ipld_nodes == 1) { node->ipfd_pnext = d->ipld_dests; } else if (d->ipld_nodes > 1) { node->ipfd_pnext = &d->ipld_dests[d->ipld_nodes - 2]->ipfd_next; } *node->ipfd_pnext = node; MUTEX_INIT(&node->ipfd_lock, "ipf dst node lock"); node->ipfd_uid = uid; node->ipfd_ref = 1; if (node->ipfd_dest.fd_name == 0) (void) ipf_resolvedest(softc, node->ipfd_names, &node->ipfd_dest, AF_INET); #ifdef USE_INET6 if (node->ipfd_dest.fd_name == 0 && node->ipfd_dest.fd_ptr == (void *)-1) (void) ipf_resolvedest(softc, node->ipfd_names, &node->ipfd_dest, AF_INET6); #endif softd->stats.ipls_numnodes++; return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_node_deref */ /* Returns: int - 0 = success, else error */ /* Parameters: arg(I) - pointer to local context to use */ /* node(I) - pointer to destionation node to free */ /* */ /* Dereference the use count by one. If it drops to zero then we can assume */ /* that it has been removed from any lists/tables and is ripe for freeing. */ /* The pointer to context is required for the purpose of maintaining */ /* statistics. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_node_deref(void *arg, ipf_dstnode_t *node) { ipf_dstl_softc_t *softd = arg; int ref; MUTEX_ENTER(&node->ipfd_lock); ref = --node->ipfd_ref; MUTEX_EXIT(&node->ipfd_lock); if (ref > 0) return 0; if ((node->ipfd_flags & IPDST_DELETE) != 0) softd->stats.ipls_numderefnodes--; MUTEX_DESTROY(&node->ipfd_lock); KFREES(node, node->ipfd_size); softd->stats.ipls_numnodes--; return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_node_del */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* op(I) - pointer to lookup operation data */ /* uid(I) - uid of process doing the ioctl */ /* */ /* Look for a matching destination node on the named table and free it if */ /* found. Because the name embedded in the frdest_t is variable in length, */ /* it is necessary to allocate some memory locally, to complete this op. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_node_del(ipf_main_softc_t *softc, void *arg, iplookupop_t *op, int uid) { ipf_dstl_softc_t *softd = arg; ipf_dstnode_t *node; frdest_t frd, *temp; ippool_dst_t *d; size_t size; int err; d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name); if (d == NULL) { IPFERROR(120012); return ESRCH; } err = COPYIN(op->iplo_struct, &frd, sizeof(frd)); if (err != 0) { IPFERROR(120011); return EFAULT; } size = sizeof(*temp) + frd.fd_name; KMALLOCS(temp, frdest_t *, size); if (temp == NULL) { softd->stats.ipls_nomem++; IPFERROR(120026); return ENOMEM; } err = COPYIN(op->iplo_struct, temp, size); if (err != 0) { IPFERROR(120027); return EFAULT; } MUTEX_ENTER(&d->ipld_lock); for (node = *d->ipld_dests; node != NULL; node = node->ipfd_next) { if ((uid != 0) && (node->ipfd_uid != uid)) continue; if (node->ipfd_size != size) continue; if (!bcmp(&node->ipfd_dest.fd_ip6, &frd.fd_ip6, size - offsetof(frdest_t, fd_ip6))) { ipf_dstlist_node_free(softd, d, node); MUTEX_EXIT(&d->ipld_lock); KFREES(temp, size); return 0; } } MUTEX_EXIT(&d->ipld_lock); KFREES(temp, size); return ESRCH; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_node_free */ /* Returns: Nil */ /* Parameters: softd(I) - pointer to the destination list context */ /* d(I) - pointer to destination list */ /* node(I) - pointer to node to free */ /* Locks: MUTEX(ipld_lock) or WRITE(ipf_poolrw) */ /* */ /* Free the destination node by first removing it from any lists and then */ /* checking if this was the last reference held to the object. While the */ /* array of pointers to nodes is compacted, its size isn't reduced (by way */ /* of allocating a new smaller one and copying) because the belief is that */ /* it is likely the array will again reach that size. */ /* ------------------------------------------------------------------------ */ static void ipf_dstlist_node_free(ipf_dstl_softc_t *softd, ippool_dst_t *d, ipf_dstnode_t *node) { int i; /* * Compact the array of pointers to nodes. */ for (i = 0; i < d->ipld_nodes; i++) if (d->ipld_dests[i] == node) break; if (d->ipld_nodes - i > 1) { bcopy(&d->ipld_dests[i + 1], &d->ipld_dests[i], sizeof(*d->ipld_dests) * (d->ipld_nodes - i - 1)); } d->ipld_nodes--; if (node->ipfd_pnext != NULL) *node->ipfd_pnext = node->ipfd_next; if (node->ipfd_next != NULL) node->ipfd_next->ipfd_pnext = node->ipfd_pnext; node->ipfd_pnext = NULL; node->ipfd_next = NULL; if ((node->ipfd_flags & IPDST_DELETE) == 0) { softd->stats.ipls_numderefnodes++; node->ipfd_flags |= IPDST_DELETE; } ipf_dstlist_node_deref(softd, node); } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_stats_get */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* op(I) - pointer to lookup operation data */ /* */ /* Return the current statistics for destination lists. This may be for all */ /* of them or just information pertaining to a particular table. */ /* ------------------------------------------------------------------------ */ /*ARGSUSED*/ static int ipf_dstlist_stats_get(ipf_main_softc_t *softc, void *arg, iplookupop_t *op) { ipf_dstl_softc_t *softd = arg; ipf_dstl_stat_t stats; int unit, i, err = 0; if (op->iplo_size != sizeof(ipf_dstl_stat_t)) { IPFERROR(120023); return EINVAL; } stats = softd->stats; unit = op->iplo_unit; if (unit == IPL_LOGALL) { for (i = 0; i <= IPL_LOGMAX; i++) stats.ipls_list[i] = softd->dstlist[i]; } else if (unit >= 0 && unit <= IPL_LOGMAX) { void *ptr; if (op->iplo_name[0] != '\0') ptr = ipf_dstlist_table_find(softd, unit, op->iplo_name); else ptr = softd->dstlist[unit + 1]; stats.ipls_list[unit] = ptr; } else { IPFERROR(120024); err = EINVAL; } if (err == 0) { err = COPYOUT(&stats, op->iplo_struct, sizeof(stats)); if (err != 0) { IPFERROR(120025); return EFAULT; } } return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_table_add */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* op(I) - pointer to lookup operation data */ /* */ /* Add a new destination table to the list of those available for the given */ /* device. Because we seldom operate on these objects (find/add/delete), */ /* they are just kept in a simple linked list. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_table_add(ipf_main_softc_t *softc, void *arg, iplookupop_t *op) { ipf_dstl_softc_t *softd = arg; ippool_dst_t user, *d, *new; int unit, err; d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name); if (d != NULL) { IPFERROR(120013); return EEXIST; } err = COPYIN(op->iplo_struct, &user, sizeof(user)); if (err != 0) { IPFERROR(120021); return EFAULT; } KMALLOC(new, ippool_dst_t *); if (new == NULL) { softd->stats.ipls_nomem++; IPFERROR(120014); return ENOMEM; } bzero((char *)new, sizeof(*new)); MUTEX_INIT(&new->ipld_lock, "ipf dst table lock"); strncpy(new->ipld_name, op->iplo_name, FR_GROUPLEN); unit = op->iplo_unit; new->ipld_unit = unit; new->ipld_policy = user.ipld_policy; new->ipld_seed = ipf_random(); new->ipld_ref = 1; new->ipld_pnext = softd->tails[unit + 1]; *softd->tails[unit + 1] = new; softd->tails[unit + 1] = &new->ipld_next; softd->stats.ipls_numlists++; return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_table_del */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* op(I) - pointer to lookup operation data */ /* */ /* Find a named destinstion list table and delete it. If there are other */ /* references to it, the caller isn't told. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_table_del(ipf_main_softc_t *softc, void *arg, iplookupop_t *op) { ippool_dst_t *d; d = ipf_dstlist_table_find(arg, op->iplo_unit, op->iplo_name); if (d == NULL) { IPFERROR(120015); return ESRCH; } if (d->ipld_dests != NULL) { IPFERROR(120016); return EBUSY; } ipf_dstlist_table_remove(softc, arg, d); return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_table_remove */ /* Returns: Nil */ /* Parameters: softc(I) - pointer to soft context main structure */ /* softd(I) - pointer to the destination list context */ /* d(I) - pointer to destination list */ /* */ /* Remove a given destination list from existance. While the IPDST_DELETE */ /* flag is set every time we call this function and the reference count is */ /* non-zero, the "numdereflists" counter is always incremented because the */ /* decision about whether it will be freed or not is not made here. This */ /* means that the only action the code can take here is to treat it as if */ /* it will become a detached. */ /* ------------------------------------------------------------------------ */ static void ipf_dstlist_table_remove(ipf_main_softc_t *softc, ipf_dstl_softc_t *softd, ippool_dst_t *d) { if (softd->tails[d->ipld_unit + 1] == &d->ipld_next) softd->tails[d->ipld_unit + 1] = d->ipld_pnext; if (d->ipld_pnext != NULL) *d->ipld_pnext = d->ipld_next; if (d->ipld_next != NULL) d->ipld_next->ipld_pnext = d->ipld_pnext; d->ipld_pnext = NULL; d->ipld_next = NULL; ipf_dstlist_table_clearnodes(softd, d); softd->stats.ipls_numdereflists++; d->ipld_flags |= IPDST_DELETE; ipf_dstlist_table_deref(softc, softd, d); } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_table_free */ /* Returns: Nil */ /* Parameters: softd(I) - pointer to the destination list context */ /* d(I) - pointer to destination list */ /* */ /* Free up a destination list data structure and any other memory that was */ /* directly allocated as part of creating it. Individual destination list */ /* nodes are not freed. It is assumed the caller will have already emptied */ /* the destination list. */ /* ------------------------------------------------------------------------ */ static void ipf_dstlist_table_free(ipf_dstl_softc_t *softd, ippool_dst_t *d) { MUTEX_DESTROY(&d->ipld_lock); if ((d->ipld_flags & IPDST_DELETE) != 0) softd->stats.ipls_numdereflists--; softd->stats.ipls_numlists--; if (d->ipld_dests != NULL) { KFREES(d->ipld_dests, d->ipld_maxnodes * sizeof(*d->ipld_dests)); } KFREE(d); } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_table_deref */ /* Returns: int - 0 = success, else error */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* op(I) - pointer to lookup operation data */ /* */ /* Drops the reference count on a destination list table object and free's */ /* it if 0 has been reached. */ /* ------------------------------------------------------------------------ */ static int ipf_dstlist_table_deref(ipf_main_softc_t *softc, void *arg, void *table) { ippool_dst_t *d = table; d->ipld_ref--; if (d->ipld_ref > 0) return d->ipld_ref; ipf_dstlist_table_free(arg, d); return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_table_clearnodes */ /* Returns: Nil */ /* Parameters: softd(I) - pointer to the destination list context */ /* dst(I) - pointer to destination list */ /* */ /* Free all of the destination nodes attached to the given table. */ /* ------------------------------------------------------------------------ */ static void ipf_dstlist_table_clearnodes(ipf_dstl_softc_t *softd, ippool_dst_t *dst) { ipf_dstnode_t *node; if (dst->ipld_dests == NULL) return; while ((node = *dst->ipld_dests) != NULL) { ipf_dstlist_node_free(softd, dst, node); } } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_table_find */ /* Returns: int - 0 = success, else error */ /* Parameters: arg(I) - pointer to local context to use */ /* unit(I) - device we are working with */ /* name(I) - destination table name to find */ /* */ /* Return a pointer to a destination table that matches the unit+name that */ /* is passed in. */ /* ------------------------------------------------------------------------ */ static void * ipf_dstlist_table_find(void *arg, int unit, char *name) { ipf_dstl_softc_t *softd = arg; ippool_dst_t *d; for (d = softd->dstlist[unit + 1]; d != NULL; d = d->ipld_next) { if ((d->ipld_unit == unit) && !strncmp(d->ipld_name, name, FR_GROUPLEN)) { return d; } } return NULL; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_select_ref */ /* Returns: void * - NULL = failure, else pointer to table */ /* Parameters: arg(I) - pointer to local context to use */ /* unit(I) - device we are working with */ /* name(I) - destination table name to find */ /* */ /* Attempt to find a destination table that matches the name passed in and */ /* if successful, bump up the reference count on it because we intend to */ /* store the pointer to it somewhere else. */ /* ------------------------------------------------------------------------ */ static void * ipf_dstlist_select_ref(void *arg, int unit, char *name) { ippool_dst_t *d; d = ipf_dstlist_table_find(arg, unit, name); if (d != NULL) { MUTEX_ENTER(&d->ipld_lock); d->ipld_ref++; MUTEX_EXIT(&d->ipld_lock); } return d; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_select */ /* Returns: void * - NULL = failure, else pointer to table */ /* Parameters: fin(I) - pointer to packet information */ /* d(I) - pointer to destination list */ /* */ /* Find the next node in the destination list to be used according to the */ /* defined policy. Of these, "connection" is the most expensive policy to */ /* implement as it always looks for the node with the least number of */ /* connections associated with it. */ /* */ /* The hashes exclude the port numbers so that all protocols map to the */ /* same destination. Otherwise, someone doing a ping would target a */ /* different server than their TCP connection, etc. MD-5 is used to */ /* transform the addressese into something random that the other end could */ /* not easily guess and use in an attack. ipld_seed introduces an unknown */ /* into the hash calculation to increase the difficult of an attacker */ /* guessing the bucket. */ /* */ /* One final comment: mixing different address families in a single pool */ /* will currently result in failures as the address family of the node is */ /* only matched up with that in the packet as the last step. While this can */ /* be coded around for the weighted connection and round-robin models, it */ /* cannot be supported for the hash/random models as they do not search and */ /* nor is the algorithm conducive to searching. */ /* ------------------------------------------------------------------------ */ static ipf_dstnode_t * ipf_dstlist_select(fr_info_t *fin, ippool_dst_t *d) { ipf_dstnode_t *node, *sel; int connects; union { u_32_t hash[4]; unsigned char bytes[16]; } h; MD5_CTX ctx; int family; int x; if (d == NULL || d->ipld_dests == NULL || *d->ipld_dests == NULL) return NULL; family = fin->fin_family; MUTEX_ENTER(&d->ipld_lock); switch (d->ipld_policy) { case IPLDP_ROUNDROBIN: sel = d->ipld_selected; if (sel == NULL) { sel = *d->ipld_dests; } else { sel = sel->ipfd_next; if (sel == NULL) sel = *d->ipld_dests; } break; case IPLDP_CONNECTION: if (d->ipld_selected == NULL) { sel = *d->ipld_dests; break; } sel = d->ipld_selected; connects = 0x7fffffff; node = sel->ipfd_next; if (node == NULL) node = *d->ipld_dests; while (node != d->ipld_selected) { if (node->ipfd_states == 0) { sel = node; break; } if (node->ipfd_states < connects) { sel = node; connects = node->ipfd_states; } node = node->ipfd_next; if (node == NULL) node = *d->ipld_dests; } break; case IPLDP_RANDOM : x = ipf_random() % d->ipld_nodes; sel = d->ipld_dests[x]; break; case IPLDP_HASHED : MD5Init(&ctx); MD5Update(&ctx, (u_char *)&d->ipld_seed, sizeof(d->ipld_seed)); MD5Update(&ctx, (u_char *)&fin->fin_src6, sizeof(fin->fin_src6)); MD5Update(&ctx, (u_char *)&fin->fin_dst6, sizeof(fin->fin_dst6)); MD5Final(h.bytes, &ctx); x = ntohl(h.hash[0]) % d->ipld_nodes; sel = d->ipld_dests[x]; break; case IPLDP_SRCHASH : MD5Init(&ctx); MD5Update(&ctx, (u_char *)&d->ipld_seed, sizeof(d->ipld_seed)); MD5Update(&ctx, (u_char *)&fin->fin_src6, sizeof(fin->fin_src6)); MD5Final(h.bytes, &ctx); x = ntohl(h.hash[0]) % d->ipld_nodes; sel = d->ipld_dests[x]; break; case IPLDP_DSTHASH : MD5Init(&ctx); MD5Update(&ctx, (u_char *)&d->ipld_seed, sizeof(d->ipld_seed)); MD5Update(&ctx, (u_char *)&fin->fin_dst6, sizeof(fin->fin_dst6)); MD5Final(h.bytes, &ctx); x = ntohl(h.hash[0]) % d->ipld_nodes; sel = d->ipld_dests[x]; break; default : sel = NULL; break; } if (sel && sel->ipfd_dest.fd_addr.adf_family != family) sel = NULL; d->ipld_selected = sel; MUTEX_EXIT(&d->ipld_lock); return sel; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_select_node */ /* Returns: int - -1 == failure, 0 == success */ /* Parameters: fin(I) - pointer to packet information */ /* group(I) - destination pool to search */ /* addr(I) - pointer to store selected address */ /* pfdp(O) - pointer to storage for selected destination node */ /* */ /* This function is only responsible for obtaining the next IP address for */ /* use and storing it in the caller's address space (addr). "addr" is only */ /* used for storage if pfdp is NULL. No permanent reference is currently */ /* kept on the node. */ /* ------------------------------------------------------------------------ */ int ipf_dstlist_select_node(fr_info_t *fin, void *group, u_32_t *addr, frdest_t *pfdp) { #ifdef USE_MUTEXES ipf_main_softc_t *softc = fin->fin_main_soft; #endif ippool_dst_t *d = group; ipf_dstnode_t *node; frdest_t *fdp; READ_ENTER(&softc->ipf_poolrw); node = ipf_dstlist_select(fin, d); if (node == NULL) { RWLOCK_EXIT(&softc->ipf_poolrw); return -1; } if (pfdp != NULL) { bcopy(&node->ipfd_dest, pfdp, sizeof(*pfdp)); } else { if (fin->fin_family == AF_INET) { addr[0] = node->ipfd_dest.fd_addr.adf_addr.i6[0]; } else if (fin->fin_family == AF_INET6) { addr[0] = node->ipfd_dest.fd_addr.adf_addr.i6[0]; addr[1] = node->ipfd_dest.fd_addr.adf_addr.i6[1]; addr[2] = node->ipfd_dest.fd_addr.adf_addr.i6[2]; addr[3] = node->ipfd_dest.fd_addr.adf_addr.i6[3]; } } fdp = &node->ipfd_dest; if (fdp->fd_ptr == NULL) fdp->fd_ptr = fin->fin_ifp; MUTEX_ENTER(&node->ipfd_lock); node->ipfd_states++; MUTEX_EXIT(&node->ipfd_lock); RWLOCK_EXIT(&softc->ipf_poolrw); return 0; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_expire */ /* Returns: Nil */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* */ /* There are currently no objects to expire in destination lists. */ /* ------------------------------------------------------------------------ */ static void ipf_dstlist_expire(ipf_main_softc_t *softc, void *arg) { return; } /* ------------------------------------------------------------------------ */ /* Function: ipf_dstlist_sync */ /* Returns: Nil */ /* Parameters: softc(I) - pointer to soft context main structure */ /* arg(I) - pointer to local context to use */ /* */ /* When a network interface appears or disappears, we need to revalidate */ /* all of the network interface names that have been configured as a target */ /* in a destination list. */ /* ------------------------------------------------------------------------ */ void ipf_dstlist_sync(ipf_main_softc_t *softc, void *arg) { ipf_dstl_softc_t *softd = arg; ipf_dstnode_t *node; ippool_dst_t *list; int i; int j; for (i = 0; i < IPL_LOGMAX; i++) { for (list = softd->dstlist[i]; list != NULL; list = list->ipld_next) { for (j = 0; j < list->ipld_maxnodes; j++) { node = list->ipld_dests[j]; if (node == NULL) continue; if (node->ipfd_dest.fd_name == -1) continue; (void) ipf_resolvedest(softc, node->ipfd_names, &node->ipfd_dest, AF_INET); } } } }