[BACK]Return to art.c CVS log [TXT][DIR] Up to [local] / src / sys / net

File: [local] / src / sys / net / art.c (download)

Revision 1.31, Sat Nov 11 12:17:50 2023 UTC (7 months ago) by bluhm
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.30: +2 -2 lines

Remove unused parameter dst from art_get().

OK mvs@

/*	$OpenBSD: art.c,v 1.31 2023/11/11 12:17:50 bluhm Exp $ */

/*
 * Copyright (c) 2015 Martin Pieuchot
 * Copyright (c) 2001 Yoichi Hariguchi
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Allotment Routing Table (ART).
 *
 * Yoichi Hariguchi paper can be found at:
 *	http://www.hariguchi.org/art/art.pdf
 */

#ifndef _KERNEL
#include "kern_compat.h"
#else
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/pool.h>
#include <sys/task.h>
#include <sys/socket.h>
#endif

#include <net/art.h>

int			 art_bindex(struct art_table *, const uint8_t *, int);
void			 art_allot(struct art_table *at, int, struct art_node *,
			     struct art_node *);
struct art_table	*art_table_get(struct art_root *, struct art_table *,
			     int);
struct art_table	*art_table_put(struct art_root *, struct art_table *);
struct art_node		*art_table_insert(struct art_root *, struct art_table *,
			     int, struct art_node *);
struct art_node		*art_table_delete(struct art_root *, struct art_table *,
			     int, struct art_node *);
struct art_table	*art_table_ref(struct art_root *, struct art_table *);
int			 art_table_free(struct art_root *, struct art_table *);
int			 art_table_walk(struct art_root *, struct art_table *,
			     int (*f)(struct art_node *, void *), void *);
int			 art_walk_apply(struct art_root *,
			     struct art_node *, struct art_node *,
			     int (*f)(struct art_node *, void *), void *);
void			 art_table_gc(void *);
void			 art_gc(void *);

struct pool		an_pool, at_pool, at_heap_4_pool, at_heap_8_pool;

struct art_table	*art_table_gc_list = NULL;
struct mutex		 art_table_gc_mtx = MUTEX_INITIALIZER(IPL_SOFTNET);
struct task		 art_table_gc_task =
			     TASK_INITIALIZER(art_table_gc, NULL);

struct art_node		*art_node_gc_list = NULL;
struct mutex		 art_node_gc_mtx = MUTEX_INITIALIZER(IPL_SOFTNET);
struct task		 art_node_gc_task = TASK_INITIALIZER(art_gc, NULL);

void
art_init(void)
{
	pool_init(&an_pool, sizeof(struct art_node), 0, IPL_SOFTNET, 0,
	    "art_node", NULL);
	pool_init(&at_pool, sizeof(struct art_table), 0, IPL_SOFTNET, 0,
	    "art_table", NULL);
	pool_init(&at_heap_4_pool, AT_HEAPSIZE(4), 0, IPL_SOFTNET, 0,
	    "art_heap4", NULL);
	pool_init(&at_heap_8_pool, AT_HEAPSIZE(8), 0, IPL_SOFTNET, 0,
	    "art_heap8", &pool_allocator_single);
}

/*
 * Per routing table initialization API function.
 */
struct art_root *
art_alloc(unsigned int rtableid, unsigned int alen, unsigned int off)
{
	struct art_root		*ar;
	int			 i;

	ar = malloc(sizeof(*ar), M_RTABLE, M_NOWAIT|M_ZERO);
	if (ar == NULL)
		return (NULL);

	switch (alen) {
	case 32:
		ar->ar_alen = 32;
		ar->ar_nlvl = 7;
		ar->ar_bits[0] = 8;
		for (i = 1; i < ar->ar_nlvl; i++)
			ar->ar_bits[i] = 4;
		break;
	case 128:
		ar->ar_alen = 128;
		ar->ar_nlvl = 32;
		for (i = 0; i < ar->ar_nlvl; i++)
			ar->ar_bits[i] = 4;
		break;
	default:
		printf("%s: incorrect address length %u\n", __func__, alen);
		free(ar, M_RTABLE, sizeof(*ar));
		return (NULL);
	}

	ar->ar_off = off;
	rw_init(&ar->ar_lock, "art");

	return (ar);
}

/*
 * Return 1 if ``old'' and ``new`` are identical, 0 otherwise.
 */
static inline int
art_check_duplicate(struct art_root *ar, struct art_node *old,
    struct art_node *new)
{
	if (old == NULL)
		return (0);

	if (old->an_plen == new->an_plen)
		return (1);

	return (0);
}

/*
 * Return the base index of the part of ``addr'' and ``plen''
 * corresponding to the range covered by the table ``at''.
 *
 * In other words, this function take the multi-level (complete)
 * address ``addr'' and prefix length ``plen'' and return the
 * single level base index for the table ``at''.
 *
 * For example with an address size of 32bit divided into four
 * 8bit-long tables, there's a maximum of 4 base indexes if the
 * prefix length is > 24.
 */
int
art_bindex(struct art_table *at, const uint8_t *addr, int plen)
{
	uint8_t			boff, bend;
	uint32_t		k;

	if (plen < at->at_offset || plen > (at->at_offset + at->at_bits))
		return (-1);

	/*
	 * We are only interested in the part of the prefix length
	 * corresponding to the range of this table.
	 */
	plen -= at->at_offset;

	/*
	 * Jump to the first byte of the address containing bits
	 * covered by this table.
	 */
	addr += (at->at_offset / 8);

	/* ``at'' covers the bit range between ``boff'' & ``bend''. */
	boff = (at->at_offset % 8);
	bend = (at->at_bits + boff);

	KASSERT(bend <= 32);

	if (bend > 24) {
		k = (addr[0] & ((1 << (8 - boff)) - 1)) << (bend - 8);
		k |= addr[1] << (bend - 16);
		k |= addr[2] << (bend - 24);
		k |= addr[3] >> (32 - bend);
	} else if (bend > 16) {
		k = (addr[0] & ((1 << (8 - boff)) - 1)) << (bend - 8);
		k |= addr[1] << (bend - 16);
		k |= addr[2] >> (24 - bend);
	} else if (bend > 8) {
		k = (addr[0] & ((1 << (8 - boff)) - 1)) << (bend - 8);
		k |= addr[1] >> (16 - bend);
	} else {
		k = (addr[0] >> (8 - bend)) & ((1 << at->at_bits) - 1);
	}

	/*
	 * Single level base index formula:
	 */
	return ((k >> (at->at_bits - plen)) + (1 << plen));
}

/*
 * Single level lookup function.
 *
 * Return the fringe index of the part of ``addr''
 * corresponding to the range covered by the table ``at''.
 */
static inline int
art_findex(struct art_table *at, const uint8_t *addr)
{
	return art_bindex(at, addr, (at->at_offset + at->at_bits));
}

/*
 * (Non-perfect) lookup API function.
 *
 * Return the best existing match for a destination.
 */
struct art_node *
art_match(struct art_root *ar, const void *addr, struct srp_ref *nsr)
{
	struct srp_ref		dsr, ndsr;
	void			*entry;
	struct art_table	*at;
	struct art_node		*dflt, *ndflt;
	int			j;

	entry = srp_enter(nsr, &ar->ar_root);
	at = entry;

	if (at == NULL)
		goto done;

	/*
	 * Remember the default route of each table we visit in case
	 * we do not find a better matching route.
	 */
	dflt = srp_enter(&dsr, &at->at_default);

	/*
	 * Iterate until we find a leaf.
	 */
	while (1) {
		/* Do a single level route lookup. */
		j = art_findex(at, addr);
		entry = srp_follow(nsr, &at->at_heap[j].node);

		/* If this is a leaf (NULL is a leaf) we're done. */
		if (ISLEAF(entry))
			break;

		at = SUBTABLE(entry);

		ndflt = srp_enter(&ndsr, &at->at_default);
		if (ndflt != NULL) {
			srp_leave(&dsr);
			dsr = ndsr;
			dflt = ndflt;
		} else
			srp_leave(&ndsr);
	}

	if (entry == NULL) {
		srp_leave(nsr);
		*nsr = dsr;
		KASSERT(ISLEAF(dflt));
		return (dflt);
	}

	srp_leave(&dsr);
done:
	KASSERT(ISLEAF(entry));
	return (entry);
}

/*
 * Perfect lookup API function.
 *
 * Return a perfect match for a destination/prefix-length pair or NULL if
 * it does not exist.
 */
struct art_node *
art_lookup(struct art_root *ar, const void *addr, int plen, struct srp_ref *nsr)
{
	void			*entry;
	struct art_table	*at;
	int			 i, j;

	KASSERT(plen >= 0 && plen <= ar->ar_alen);

	entry = srp_enter(nsr, &ar->ar_root);
	at = entry;

	if (at == NULL)
		goto done;

	/* Default route */
	if (plen == 0) {
		entry = srp_follow(nsr, &at->at_default);
		goto done;
	}

	/*
	 * If the prefix length is smaller than the sum of
	 * the stride length at this level the entry must
	 * be in the current table.
	 */
	while (plen > (at->at_offset + at->at_bits)) {
		/* Do a single level route lookup. */
		j = art_findex(at, addr);
		entry = srp_follow(nsr, &at->at_heap[j].node);

		/* A leaf is a match, but not a perfect one, or NULL */
		if (ISLEAF(entry))
			return (NULL);

		at = SUBTABLE(entry);
	}

	i = art_bindex(at, addr, plen);
	if (i == -1)
		return (NULL);

	entry = srp_follow(nsr, &at->at_heap[i].node);
	if (!ISLEAF(entry))
		entry = srp_follow(nsr, &SUBTABLE(entry)->at_default);

done:
	KASSERT(ISLEAF(entry));
	return (entry);
}


/*
 * Insertion API function.
 *
 * Insert the given node or return an existing one if a node with the
 * same destination/mask pair is already present.
 */
struct art_node *
art_insert(struct art_root *ar, struct art_node *an, const void *addr, int plen)
{
	struct art_table	*at, *child;
	struct art_node		*node;
	int			 i, j;

	rw_assert_wrlock(&ar->ar_lock);
	KASSERT(plen >= 0 && plen <= ar->ar_alen);

	at = srp_get_locked(&ar->ar_root);
	if (at == NULL) {
		at = art_table_get(ar, NULL, -1);
		if (at == NULL)
			return (NULL);

		srp_swap_locked(&ar->ar_root, at);
	}

	/* Default route */
	if (plen == 0) {
		node = srp_get_locked(&at->at_default);
		if (node != NULL)
			return (node);

		art_table_ref(ar, at);
		srp_swap_locked(&at->at_default, an);
		return (an);
	}

	/*
	 * If the prefix length is smaller than the sum of
	 * the stride length at this level the entry must
	 * be in the current table.
	 */
	while (plen > (at->at_offset + at->at_bits)) {
		/* Do a single level route lookup. */
		j = art_findex(at, addr);
		node = srp_get_locked(&at->at_heap[j].node);

		/*
		 * If the node corresponding to the fringe index is
		 * a leaf we need to allocate a subtable.  The route
		 * entry of this node will then become the default
		 * route of the subtable.
		 */
		if (ISLEAF(node)) {
			child = art_table_get(ar, at, j);
			if (child == NULL)
				return (NULL);

			art_table_ref(ar, at);
			srp_swap_locked(&at->at_heap[j].node, ASNODE(child));
			at = child;
		} else
			at = SUBTABLE(node);
	}

	i = art_bindex(at, addr, plen);
	if (i == -1)
		return (NULL);

	return (art_table_insert(ar, at, i, an));
}

/*
 * Single level insertion.
 */
struct art_node *
art_table_insert(struct art_root *ar, struct art_table *at, int i,
    struct art_node *an)
{
	struct art_node	*prev, *node;

	node = srp_get_locked(&at->at_heap[i].node);
	if (!ISLEAF(node))
		prev = srp_get_locked(&SUBTABLE(node)->at_default);
	else
		prev = node;

	if (art_check_duplicate(ar, prev, an))
		return (prev);

	art_table_ref(ar, at);

	/*
	 * If the index `i' of the route that we are inserting is not
	 * a fringe index, we need to allot this new route pointer to
	 * all the corresponding fringe indices.
	 */
	if (i < at->at_minfringe)
		art_allot(at, i, prev, an);
	else if (!ISLEAF(node))
		srp_swap_locked(&SUBTABLE(node)->at_default, an);
	else
		srp_swap_locked(&at->at_heap[i].node, an);

	return (an);
}


/*
 * Deletion API function.
 */
struct art_node *
art_delete(struct art_root *ar, struct art_node *an, const void *addr, int plen)
{
	struct art_table	*at;
	struct art_node		*node;
	int			 i, j;

	rw_assert_wrlock(&ar->ar_lock);
	KASSERT(plen >= 0 && plen <= ar->ar_alen);

	at = srp_get_locked(&ar->ar_root);
	if (at == NULL)
		return (NULL);

	/* Default route */
	if (plen == 0) {
		node = srp_get_locked(&at->at_default);
		srp_swap_locked(&at->at_default, NULL);
		art_table_free(ar, at);
		return (node);
	}

	/*
	 * If the prefix length is smaller than the sum of
	 * the stride length at this level the entry must
	 * be in the current table.
	 */
	while (plen > (at->at_offset + at->at_bits)) {
		/* Do a single level route lookup. */
		j = art_findex(at, addr);
		node = srp_get_locked(&at->at_heap[j].node);

		/* If this is a leaf, there is no route to delete. */
		if (ISLEAF(node))
			return (NULL);

		at = SUBTABLE(node);
	}

	i = art_bindex(at, addr, plen);
	if (i == -1)
		return (NULL);

	return (art_table_delete(ar, at, i, an));
}

/*
 * Single level deletion.
 */
struct art_node *
art_table_delete(struct art_root *ar, struct art_table *at, int i,
    struct art_node *an)
{
	struct art_node		*next, *node;

#ifdef DIAGNOSTIC
	struct art_node		*prev;
#endif

	node = srp_get_locked(&at->at_heap[i].node);
#ifdef DIAGNOSTIC
	if (!ISLEAF(node))
		prev = srp_get_locked(&SUBTABLE(node)->at_default);
	else
		prev = node;

	KASSERT(prev == an);
#endif

	/* Get the next most specific route for the index `i'. */
	if ((i >> 1) > 1)
		next = srp_get_locked(&at->at_heap[i >> 1].node);
	else
		next = NULL;

	/*
	 * If the index `i' of the route that we are removing is not
	 * a fringe index, we need to allot the next most specific
	 * route pointer to all the corresponding fringe indices.
	 */
	if (i < at->at_minfringe)
		art_allot(at, i, an, next);
	else if (!ISLEAF(node))
		srp_swap_locked(&SUBTABLE(node)->at_default, next);
	else
		srp_swap_locked(&at->at_heap[i].node, next);

	/* We have removed an entry from this table. */
	art_table_free(ar, at);

	return (an);
}

struct art_table *
art_table_ref(struct art_root *ar, struct art_table *at)
{
	at->at_refcnt++;
	return (at);
}

static inline int
art_table_rele(struct art_table *at)
{
	if (at == NULL)
		return (0);

	return (--at->at_refcnt == 0);
}

int
art_table_free(struct art_root *ar, struct art_table *at)
{
	if (art_table_rele(at)) {
		/*
		 * Garbage collect this table and all its parents
		 * that are empty.
		 */
		do {
			at = art_table_put(ar, at);
		} while (art_table_rele(at));

		return (1);
	}

	return (0);
}

/*
 * Iteration API function.
 */
int
art_walk(struct art_root *ar, int (*f)(struct art_node *, void *), void *arg)
{
	struct srp_ref		 sr;
	struct art_table	*at;
	struct art_node		*node;
	int			 error = 0;

	rw_enter_write(&ar->ar_lock);
	at = srp_get_locked(&ar->ar_root);
	if (at != NULL) {
		art_table_ref(ar, at);

		/*
		 * The default route should be processed here because the root
		 * table does not have a parent.
		 */
		node = srp_enter(&sr, &at->at_default);
		error = art_walk_apply(ar, node, NULL, f, arg);
		srp_leave(&sr);

		if (error == 0)
			error = art_table_walk(ar, at, f, arg);

		art_table_free(ar, at);
	}
	rw_exit_write(&ar->ar_lock);

	return (error);
}

int
art_table_walk(struct art_root *ar, struct art_table *at,
    int (*f)(struct art_node *, void *), void *arg)
{
	struct srp_ref		 sr;
	struct art_node		*node, *next;
	struct art_table	*nat;
	int			 i, j, error = 0;
	uint32_t		 maxfringe = (at->at_minfringe << 1);

	/*
	 * Iterate non-fringe nodes in ``natural'' order.
	 */
	for (j = 1; j < at->at_minfringe; j += 2) {
		/*
		 * The default route (index 1) is processed by the
		 * parent table (where it belongs) otherwise it could
		 * be processed more than once.
		 */
		for (i = max(j, 2); i < at->at_minfringe; i <<= 1) {
			next = srp_get_locked(&at->at_heap[i >> 1].node);

			node = srp_enter(&sr, &at->at_heap[i].node);
			error = art_walk_apply(ar, node, next, f, arg);
			srp_leave(&sr);

			if (error != 0)
				return (error);
		}
	}

	/*
	 * Iterate fringe nodes.
	 */
	for (i = at->at_minfringe; i < maxfringe; i++) {
		next = srp_get_locked(&at->at_heap[i >> 1].node);

		node = srp_enter(&sr, &at->at_heap[i].node);
		if (!ISLEAF(node)) {
			nat = art_table_ref(ar, SUBTABLE(node));
			node = srp_follow(&sr, &nat->at_default);
		} else
			nat = NULL;

		error = art_walk_apply(ar, node, next, f, arg);
		srp_leave(&sr);

		if (error != 0) {
			art_table_free(ar, nat);
			return (error);
		}

		if (nat != NULL) {
			error = art_table_walk(ar, nat, f, arg);
			art_table_free(ar, nat);
			if (error != 0)
				return (error);
		}
	}

	return (0);
}

int
art_walk_apply(struct art_root *ar,
    struct art_node *an, struct art_node *next,
    int (*f)(struct art_node *, void *), void *arg)
{
	int error = 0;

	if ((an != NULL) && (an != next)) {
		rw_exit_write(&ar->ar_lock);
		error = (*f)(an, arg);
		rw_enter_write(&ar->ar_lock);
	}

	return (error);
}


/*
 * Create a table and use the given index to set its default route.
 *
 * Note:  This function does not modify the root or the parent.
 */
struct art_table *
art_table_get(struct art_root *ar, struct art_table *parent, int j)
{
	struct art_table	*at;
	struct art_node		*node;
	void			*at_heap;
	uint32_t		 lvl;

	KASSERT(j != 0 && j != 1);
	KASSERT(parent != NULL || j == -1);

	if (parent != NULL)
		lvl = parent->at_level + 1;
	else
		lvl = 0;

	KASSERT(lvl < ar->ar_nlvl);

	at = pool_get(&at_pool, PR_NOWAIT|PR_ZERO);
	if (at == NULL)
		return (NULL);

	switch (AT_HEAPSIZE(ar->ar_bits[lvl])) {
	case AT_HEAPSIZE(4):
		at_heap = pool_get(&at_heap_4_pool, PR_NOWAIT|PR_ZERO);
		break;
	case AT_HEAPSIZE(8):
		at_heap = pool_get(&at_heap_8_pool, PR_NOWAIT|PR_ZERO);
		break;
	default:
		panic("incorrect stride length %u", ar->ar_bits[lvl]);
	}

	if (at_heap == NULL) {
		pool_put(&at_pool, at);
		return (NULL);
	}

	at->at_parent = parent;
	at->at_index = j;
	at->at_minfringe = (1 << ar->ar_bits[lvl]);
	at->at_level = lvl;
	at->at_bits = ar->ar_bits[lvl];
	at->at_heap = at_heap;
	at->at_refcnt = 0;

	if (parent != NULL) {
		node = srp_get_locked(&parent->at_heap[j].node);
		/* node isn't being deleted, no srp_finalize needed */
		srp_swap_locked(&at->at_default, node);
		at->at_offset = (parent->at_offset + parent->at_bits);
	}

	return (at);
}


/*
 * Delete a table and use its index to restore its parent's default route.
 *
 * Note:  Modify its parent to unlink the table from it.
 */
struct art_table *
art_table_put(struct art_root *ar, struct art_table *at)
{
	struct art_table	*parent = at->at_parent;
	struct art_node		*node;
	uint32_t		 j = at->at_index;

	KASSERT(at->at_refcnt == 0);
	KASSERT(j != 0 && j != 1);

	if (parent != NULL) {
		KASSERT(j != -1);
		KASSERT(at->at_level == parent->at_level + 1);
		KASSERT(parent->at_refcnt >= 1);

		/* Give the route back to its parent. */
		node = srp_get_locked(&at->at_default);
		srp_swap_locked(&parent->at_heap[j].node, node);
	} else {
		KASSERT(j == -1);
		KASSERT(at->at_level == 0);
		srp_swap_locked(&ar->ar_root, NULL);
	}

	mtx_enter(&art_table_gc_mtx);
	at->at_parent = art_table_gc_list;
	art_table_gc_list = at;
	mtx_leave(&art_table_gc_mtx);

	task_add(systqmp, &art_table_gc_task);

	return (parent);
}

void
art_table_gc(void *null)
{
	struct art_table *at, *next;

	mtx_enter(&art_table_gc_mtx);
	at = art_table_gc_list;
	art_table_gc_list = NULL;
	mtx_leave(&art_table_gc_mtx);

	while (at != NULL) {
		next = at->at_parent;

		if (at->at_level == 0)
			srp_finalize(at, "arttfini");
		else
			srp_finalize(ASNODE(at), "arttfini");

		switch (AT_HEAPSIZE(at->at_bits)) {
		case AT_HEAPSIZE(4):
			pool_put(&at_heap_4_pool, at->at_heap);
			break;
		case AT_HEAPSIZE(8):
			pool_put(&at_heap_8_pool, at->at_heap);
			break;
		default:
			panic("incorrect stride length %u", at->at_bits);
		}

		pool_put(&at_pool, at);

		at = next;
	}
}

/*
 * Substitute a node by another in the subtree whose root index is given.
 *
 * This function iterates on the table ``at'' at index ``i'' until no
 * more ``old'' node can be replaced by ``new''.
 *
 * This function was originally written by Don Knuth in CWEB. The
 * complicated ``goto''s are the result of expansion of the two
 * following recursions:
 *
 *	art_allot(at, i, old, new)
 *	{
 *		int k = i;
 *		if (at->at_heap[k] == old)
 *			at->at_heap[k] = new;
 *		if (k >= at->at_minfringe)
 *			 return;
 *		k <<= 1;
 *		art_allot(at, k, old, new);
 *		k++;
 *		art_allot(at, k, old, new);
 *	}
 */
void
art_allot(struct art_table *at, int i, struct art_node *old,
    struct art_node *new)
{
	struct art_node		*node, *dflt;
	int			 k = i;

	KASSERT(i < at->at_minfringe);

again:
	k <<= 1;
	if (k < at->at_minfringe)
		goto nonfringe;

	/* Change fringe nodes. */
	while (1) {
		node = srp_get_locked(&at->at_heap[k].node);
		if (!ISLEAF(node)) {
			dflt = srp_get_locked(&SUBTABLE(node)->at_default);
			if (dflt == old) {
				srp_swap_locked(&SUBTABLE(node)->at_default,
				    new);
			}
		} else if (node == old) {
			srp_swap_locked(&at->at_heap[k].node, new);
		}
		if (k % 2)
			goto moveup;
		k++;
	}

nonfringe:
	node = srp_get_locked(&at->at_heap[k].node);
	if (node == old)
		goto again;
moveon:
	if (k % 2)
		goto moveup;
	k++;
	goto nonfringe;
moveup:
	k >>= 1;
	srp_swap_locked(&at->at_heap[k].node, new);

	/* Change non-fringe node. */
	if (k != i)
		goto moveon;
}

struct art_node *
art_get(uint8_t plen)
{
	struct art_node		*an;

	an = pool_get(&an_pool, PR_NOWAIT | PR_ZERO);
	if (an == NULL)
		return (NULL);

	an->an_plen = plen;
	SRPL_INIT(&an->an_rtlist);

	return (an);
}

void
art_put(struct art_node *an)
{
	KASSERT(SRPL_EMPTY_LOCKED(&an->an_rtlist));

	mtx_enter(&art_node_gc_mtx);
	an->an_gc = art_node_gc_list;
	art_node_gc_list = an;
	mtx_leave(&art_node_gc_mtx);

	task_add(systqmp, &art_node_gc_task);
}

void
art_gc(void *null)
{
	struct art_node		*an, *next;

	mtx_enter(&art_node_gc_mtx);
	an = art_node_gc_list;
	art_node_gc_list = NULL;
	mtx_leave(&art_node_gc_mtx);

	while (an != NULL) {
		next = an->an_gc;

		srp_finalize(an, "artnfini");

		pool_put(&an_pool, an);

		an = next;
	}
}