/*	$NetBSD: portalgo.c,v 1.15 2022/11/04 09:01:53 ozaki-r Exp $	*/

/*
 * Copyright 2011 Vlad Balan
 *
 * Written by Vlad Balan for the NetBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

/*
 * see:
 *	RFC 6056 Recommendations for Transport-Protocol Port Randomization
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: portalgo.c,v 1.15 2022/11/04 09:01:53 ozaki-r Exp $");

#ifdef _KERNEL_OPT
#include "opt_inet.h"
#endif

#include <sys/param.h>
#include <sys/errno.h>
#include <sys/kauth.h>
#include <sys/uidinfo.h>
#include <sys/md5.h>
#include <sys/cprng.h>
#include <sys/bitops.h>

#include <net/if.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>

#ifdef INET6
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet6/in6_pcb.h>
#endif

#include <netinet/tcp_vtw.h>

#include "portalgo.h"

#define NPROTO 2
#define PORTALGO_TCP 0
#define PORTALGO_UDP 1

#define NAF 2
#define PORTALGO_IPV4 0
#define PORTALGO_IPV6 1

#define NRANGES 2
#define PORTALGO_LOWPORT 0
#define PORTALGO_HIGHPORT 1

#if PORTALGO_DEBUG
static bool portalgo_debug = true;
#define DPRINTF if (portalgo_debug) printf
#else
#define DPRINTF while (/*CONSTCOND*/0) printf
#endif

#ifndef PORTALGO_INET4_DEFAULT
#define PORTALGO_INET4_DEFAULT PORTALGO_BSD
#endif
#ifndef PORTALGO_INET6_DEFAULT
#define PORTALGO_INET6_DEFAULT PORTALGO_BSD
#endif

typedef __BITMAP_TYPE(, uint32_t, 0x10000) bitmap;
#ifdef INET
static int inet4_portalgo = PORTALGO_INET4_DEFAULT;
static bitmap inet4_reserve;
#endif
#ifdef INET6
static int inet6_portalgo = PORTALGO_INET6_DEFAULT;
static bitmap inet6_reserve;
#endif

typedef struct {
	const char *name;
	int (*func)(int, uint16_t *, struct inpcb *, kauth_cred_t);
} portalgo_algorithm_t;

static int algo_bsd(int, uint16_t *, struct inpcb *, kauth_cred_t);
static int algo_random_start(int, uint16_t *, struct inpcb *, kauth_cred_t);
static int algo_random_pick(int, uint16_t *, struct inpcb *, kauth_cred_t);
static int algo_hash(int, uint16_t *, struct inpcb *, kauth_cred_t);
static int algo_doublehash(int, uint16_t *, struct inpcb *, kauth_cred_t);
static int algo_randinc(int, uint16_t *, struct inpcb *, kauth_cred_t);

static const portalgo_algorithm_t algos[] = {
	{
		.name = "bsd",
		.func = algo_bsd
	},
	{
		.name = "random_start",
		.func = algo_random_start
	},
	{
		.name = "random_pick",
		.func = algo_random_pick
	},
	{
		.name = "hash",
		.func = algo_hash
	},
	{
		.name = "doublehash",
		.func = algo_doublehash
	},
	{
		.name = "randinc",
		.func = algo_randinc
	}
};

#define NALGOS __arraycount(algos)

static uint16_t portalgo_next_ephemeral[NPROTO][NAF][NRANGES][NALGOS];

/*
 * Access the pcb and copy the values of the last port and the ends of
 * the port range.
 */
static int
pcb_getports(struct inpcb *inp, uint16_t *lastport,
    uint16_t *mymin, uint16_t *mymax, uint16_t **pnext_ephemeral, int algo)
{
	struct inpcbtable * const table = inp->inp_table;
	struct socket *so;
	int portalgo_proto;
	int portalgo_af;
	int portalgo_range;

	so = inp->inp_socket;
	switch (so->so_type) {
	case SOCK_DGRAM: /* UDP or DCCP */
	case SOCK_CONN_DGRAM:
		portalgo_proto = PORTALGO_UDP;
		break;
	case SOCK_STREAM: /* TCP or SCTP */
		portalgo_proto = PORTALGO_TCP;
		break;
	default:
		return EPFNOSUPPORT;
	}

	switch (inp->inp_af) {
#ifdef INET
	case AF_INET: {
		portalgo_af = PORTALGO_IPV4;
		if (inp->inp_flags & INP_LOWPORT) {
			*mymin = lowportmin;
			*mymax = lowportmax;
			*lastport = table->inpt_lastlow;
			portalgo_range = PORTALGO_LOWPORT;
		} else {
			*mymin = anonportmin;
			*mymax = anonportmax;
			*lastport = table->inpt_lastport;
			portalgo_range = PORTALGO_HIGHPORT;
		}
		break;
	}
#endif
#ifdef INET6
	case AF_INET6: {
		portalgo_af = PORTALGO_IPV6;
		if (inp->inp_flags & IN6P_LOWPORT) {
			*mymin = ip6_lowportmin;
			*mymax = ip6_lowportmax;
			*lastport = table->inpt_lastlow;
			portalgo_range = PORTALGO_LOWPORT;
		} else {
			*mymin = ip6_anonportmin;
			*mymax = ip6_anonportmax;
			*lastport = table->inpt_lastport;
			portalgo_range = PORTALGO_HIGHPORT;
		}
		break;
	}
#endif
	default:
		return EAFNOSUPPORT;
	}

	if (*mymin > *mymax) {	/* sanity check */
		u_int16_t swp;

		swp = *mymin;
		*mymin = *mymax;
		*mymax = swp;
	}

	DPRINTF("%s mymin:%d mymax:%d lastport:%d\n", __func__,
	    *mymin, *mymax, *lastport);

	*pnext_ephemeral = &portalgo_next_ephemeral[portalgo_proto]
	    [portalgo_af][portalgo_range][algo];

	DPRINTF("%s portalgo_proto:%d portalgo_af:%d portalgo_range:%d\n",
	    __func__, portalgo_proto, portalgo_af, portalgo_range);
	return 0;
}

/*
 * Check whether the port picked by the port randomizer is available
 * and whether KAUTH approves of our choice. This part of the code
 * shamelessly copied from in_pcb.c.
 */
static bool
check_suitable_port(uint16_t port, struct inpcb *inp, kauth_cred_t cred)
{
	struct inpcbtable * const table = inp->inp_table;
#ifdef INET
	vestigial_inpcb_t vestigial;
#endif
	int error;
#ifdef INET6
	struct socket *so;
	int wild = 0;
#endif

	DPRINTF("%s called for argument %d\n", __func__, port);

	switch (inp->inp_af) {
#ifdef INET
	case AF_INET: { /* IPv4 */
		struct inpcb *pcb;
		struct sockaddr_in sin;

		if (__BITMAP_ISSET(port, &inet4_reserve))
			return false;

		sin.sin_addr = in4p_laddr(inp);
		pcb = inpcb_lookup_local(table, sin.sin_addr, htons(port), 1,
		    &vestigial);

		DPRINTF("%s inpcb_lookup_local returned %p and "
		    "vestigial.valid %d\n",
		    __func__, pcb, vestigial.valid);

		if ((!pcb) && (!vestigial.valid)) {
			enum kauth_network_req req;

			/* We have a free port. Check with the secmodel. */
			if (inp->inp_flags & INP_LOWPORT) {
#ifndef IPNOPRIVPORTS
				req = KAUTH_REQ_NETWORK_BIND_PRIVPORT;
#else
				req = KAUTH_REQ_NETWORK_BIND_PORT;
#endif
			} else
				req = KAUTH_REQ_NETWORK_BIND_PORT;

			sin.sin_port = port;
			error = kauth_authorize_network(cred,
			    KAUTH_NETWORK_BIND,
			    req, inp->inp_socket, &sin, NULL);
			DPRINTF("%s kauth_authorize_network returned %d\n",
			    __func__, error);

			if (error == 0) {
				DPRINTF("%s port approved\n", __func__);
				return true;	/* KAUTH agrees */
			}
		}
		break;
	}
#endif
#ifdef INET6
	case AF_INET6: { /* IPv6 */
		struct sockaddr_in6 sin6;
		void *t;

		if (__BITMAP_ISSET(port, &inet6_reserve))
			return false;

		sin6.sin6_addr = in6p_laddr(inp);
		so = inp->inp_socket;

		/* XXX: this is redundant when called from in6pcb_bind */
		if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 &&
		    ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 ||
			(so->so_options & SO_ACCEPTCONN) == 0))
			wild = 1;

#ifdef INET
		if (IN6_IS_ADDR_V4MAPPED(&sin6.sin6_addr)) {
			t = inpcb_lookup_local(table,
			    *(struct in_addr *)&sin6.sin6_addr.s6_addr32[3],
			    htons(port), wild, &vestigial);
			if (!t && vestigial.valid) {
				DPRINTF("%s inpcb_lookup_local returned "
				    "a result\n", __func__);
				return false;
			}
		} else
#endif
		{
			t = in6pcb_lookup_local(table, &sin6.sin6_addr,
			    htons(port), wild, &vestigial);
			if (!t && vestigial.valid) {
				DPRINTF("%s in6pcb_lookup_local returned "
				    "a result\n", __func__);
				return false;
			}
		}
		if (t == NULL) {
			enum kauth_network_req req;

			/* We have a free port. Check with the secmodel. */
			if (inp->inp_flags & IN6P_LOWPORT) {
#ifndef IPNOPRIVPORTS
				req = KAUTH_REQ_NETWORK_BIND_PRIVPORT;
#else
				req = KAUTH_REQ_NETWORK_BIND_PORT;
#endif
			} else {
				req = KAUTH_REQ_NETWORK_BIND_PORT;
			}

			sin6.sin6_port = port;
			error = kauth_authorize_network(cred,
			    KAUTH_NETWORK_BIND, req, so, &sin6, NULL);
			if (error) {
				/* Secmodel says no. Keep looking. */
				DPRINTF("%s secmodel says no\n", __func__);
				return false;
			}
			DPRINTF("%s port approved\n", __func__);
			return true;
		}
		break;
	}
#endif
	default:
		DPRINTF("%s unknown address family\n", __func__);
		return false;
	}
	return false;
}

/* This is the default BSD algorithm, as described in RFC 6056 */
static int
algo_bsd(int algo, uint16_t *port, struct inpcb *inp, kauth_cred_t cred)
{
	uint16_t count;
	uint16_t mymin, mymax, lastport;
	uint16_t *next_ephemeral;
	int error;

	DPRINTF("%s called\n", __func__);
	error = pcb_getports(inp, &lastport, &mymin, &mymax,
	    &next_ephemeral, algo);
	if (error)
		return error;
	count = mymax - mymin + 1;
	do {
		uint16_t myport = *next_ephemeral;

		if (myport < mymin || mymax < myport)
			myport = mymax;
		*next_ephemeral = myport - 1;
		if (check_suitable_port(myport, inp, cred)) {
			*port = myport;
			DPRINTF("%s returning port %d\n", __func__, *port);
			return 0;
		}
		count--;
	} while (count > 0);

	DPRINTF("%s returning EAGAIN\n", __func__);
	return EAGAIN;
}

/*
 * The straightforward algorithm that increments the port number
 * by a random amount.
 */
static int
algo_random_start(int algo, uint16_t *port, struct inpcb *inp,
    kauth_cred_t cred)
{
	uint16_t count, num_ephemeral;
	uint16_t mymin, mymax, lastport;
	uint16_t *next_ephemeral;
	int error;

	DPRINTF("%s called\n", __func__);

	error = pcb_getports(inp, &lastport, &mymin, &mymax,
	    &next_ephemeral, algo);
	if (error)
		return error;

	num_ephemeral = mymax - mymin + 1;

	DPRINTF("num_ephemeral: %u\n", num_ephemeral);

	*next_ephemeral = mymin + (cprng_fast32() % num_ephemeral);

	DPRINTF("next_ephemeral initially: %u\n", *next_ephemeral);

	count = num_ephemeral;

	do {
		if (check_suitable_port(*next_ephemeral, inp, cred)) {
			*port = *next_ephemeral;
			DPRINTF("%s returning port %d\n", __func__, *port);
			return 0;
		}
		if (*next_ephemeral == mymax) {
			*next_ephemeral = mymin;
		} else
			(*next_ephemeral)++;

		count--;


		DPRINTF("next_ephemeral: %u count: %u\n", *next_ephemeral,
		    count);

	} while (count > 0);

	DPRINTF("%s returning EINVAL\n", __func__);

	return EINVAL;
}

/*
 * Since there is no state kept on the ports tried, we might actually
 * give up before exhausting the free ports.
 */
static int
algo_random_pick(int algo, uint16_t *port, struct inpcb *inp,
    kauth_cred_t cred)
{
	uint16_t count, num_ephemeral;
	uint16_t mymin, mymax, lastport;
	uint16_t *next_ephemeral;
	int error;

	DPRINTF("%s called\n", __func__);

	error = pcb_getports(inp, &lastport, &mymin, &mymax,
	    &next_ephemeral, algo);
	if (error)
		return error;

	num_ephemeral = mymax - mymin + 1;

	DPRINTF("num_ephemeral: %u\n", num_ephemeral);
	*next_ephemeral = mymin + (cprng_fast32() % num_ephemeral);

	DPRINTF("next_ephemeral initially: %u\n", *next_ephemeral);

	count = num_ephemeral;

	do {
		if (check_suitable_port(*next_ephemeral, inp, cred)) {
			*port = *next_ephemeral;
			DPRINTF("%s returning port %d\n", __func__, *port);
			return 0;
		}
		*next_ephemeral = mymin +
		    (cprng_fast32() % num_ephemeral);

		count--;

		DPRINTF("next_ephemeral: %u count: %u\n",
		    *next_ephemeral, count);
	} while (count > 0);

	DPRINTF("%s returning EINVAL\n", __func__);

	return EINVAL;
}

/* This is the implementation from FreeBSD, with tweaks */
static uint16_t
Fhash(const struct inpcb *inp)
{
	MD5_CTX f_ctx;
	uint32_t Ff[4];
	uint32_t secret_f[4];
	uint32_t offset;
	uint16_t soffset[2];

	cprng_fast(secret_f, sizeof(secret_f));

	MD5Init(&f_ctx);
	switch (inp->inp_af) {
#ifdef INET
	case AF_INET: {
		MD5Update(&f_ctx, (const u_char *)&const_in4p_laddr(inp),
		    sizeof(const_in4p_laddr(inp)));
		MD5Update(&f_ctx, (const u_char *)&const_in4p_faddr(inp),
		    sizeof(const_in4p_faddr(inp)));
		MD5Update(&f_ctx, (const u_char *)&inp->inp_fport,
		    sizeof(inp->inp_fport));
		break;
	}
#endif
#ifdef INET6
	case AF_INET6: {
		MD5Update(&f_ctx, (const u_char *)&const_in6p_laddr(inp),
		    sizeof(const_in6p_laddr(inp)));
		MD5Update(&f_ctx, (const u_char *)&const_in6p_faddr(inp),
		    sizeof(const_in6p_faddr(inp)));
		MD5Update(&f_ctx, (const u_char *)&inp->inp_fport,
		    sizeof(inp->inp_fport));
		break;
	}
#endif
	default:
		break;
	}
	MD5Update(&f_ctx, (const u_char *)secret_f, sizeof(secret_f));
	MD5Final((u_char *)&Ff, &f_ctx);

	offset = (Ff[0] ^ Ff[1]) ^ (Ff[2] ^ Ff[3]);

	memcpy(&soffset, &offset, sizeof(soffset));

	return soffset[0] ^ soffset[1];
}

/*
 * Checks whether the tuple is complete. If not, marks the pcb for
 * late binding.
 */
static bool
iscompletetuple(struct inpcb *inp)
{

	switch (inp->inp_af) {
#ifdef INET
	case AF_INET: {
		if (inp->inp_fport == 0 || in_nullhost(in4p_faddr(inp))) {
			DPRINTF("%s fport or faddr missing, delaying port "
			    "to connect/send\n", __func__);
			inp->inp_bindportonsend = true;
			return false;
		} else {
			inp->inp_bindportonsend = false;
		}
		break;
	}
#endif
#ifdef INET6
	case AF_INET6: {
		if (inp->inp_fport == 0 || memcmp(&in6p_faddr(inp),
		    &in6addr_any, sizeof(in6p_faddr(inp))) == 0) {
			DPRINTF("%s fport or faddr missing, delaying port "
			    "to connect/send\n", __func__);
			inp->inp_bindportonsend = true;
			return false;
		} else {
			inp->inp_bindportonsend = false;
		}
		break;
	}
#endif
	default:
		DPRINTF("%s incorrect address family\n", __func__);
		return false;
	}

	return true;
}

static int
algo_hash(int algo, uint16_t *port, struct inpcb *inp,
    kauth_cred_t cred)
{
	uint16_t count, num_ephemeral;
	uint16_t mymin, mymax, lastport;
	uint16_t *next_ephemeral;
	uint16_t offset, myport;
	int error;

	DPRINTF("%s called\n", __func__);

	error = pcb_getports(inp, &lastport, &mymin, &mymax,
	    &next_ephemeral, algo);
	if (error)
		return error;

	if (!iscompletetuple(inp)) {
		*port = 0;
		return 0;
	}

	/* Ephemeral port selection function */
	num_ephemeral = mymax - mymin + 1;

	DPRINTF("num_ephemeral: %d\n", num_ephemeral);

	offset = Fhash(inp);

	count = num_ephemeral;
	do {
		myport = mymin + (*next_ephemeral + offset)
		    % num_ephemeral;

		(*next_ephemeral)++;

		if (check_suitable_port(myport, inp, cred)) {
			*port = myport;
			DPRINTF("%s returning port %d\n", __func__, *port);
			return 0;
		}
		count--;
	} while (count > 0);

	DPRINTF("%s returning EINVAL\n", __func__);

	return EINVAL;
}

static int
algo_doublehash(int algo, uint16_t *port, struct inpcb *inp,
    kauth_cred_t cred)
{
	uint16_t count, num_ephemeral;
	uint16_t mymin, mymax, lastport;
	uint16_t *next_ephemeral;
	uint16_t offset, myport;
	static uint16_t dhtable[8];
	size_t idx;
	int error;

	DPRINTF("%s called\n", __func__);

	error = pcb_getports(inp, &lastport, &mymin, &mymax,
	    &next_ephemeral, algo);
	if (error)
		return error;

	if (!iscompletetuple(inp)) {
		*port = 0;
		return 0;
	}
	/* first time initialization */
	if (dhtable[0] == 0)
		for (size_t i = 0; i < __arraycount(dhtable); i++)
			dhtable[i] = cprng_fast32() & 0xffff;

	/* Ephemeral port selection function */
	num_ephemeral = mymax - mymin + 1;
	offset = Fhash(inp);
	idx = Fhash(inp) % __arraycount(dhtable);	/* G */
	count = num_ephemeral;

	do {
		myport = mymin + (offset + dhtable[idx])
		    % num_ephemeral;
		dhtable[idx]++;

		if (check_suitable_port(myport, inp, cred)) {
			*port = myport;
			DPRINTF("%s returning port %d\n", __func__, *port);
			return 0;
		}
		count--;

	} while (count > 0);

	DPRINTF("%s returning EINVAL\n", __func__);

	return EINVAL;
}

static int
algo_randinc(int algo, uint16_t *port, struct inpcb *inp,
    kauth_cred_t cred)
{
	static const uint16_t N = 500;	/* Determines the trade-off */
	uint16_t count, num_ephemeral;
	uint16_t mymin, mymax, lastport;
	uint16_t *next_ephemeral;
	uint16_t myport;
	int error;

	DPRINTF("%s called\n", __func__);

	error = pcb_getports(inp, &lastport, &mymin, &mymax,
	    &next_ephemeral, algo);
	if (error)
		return error;

	if (*next_ephemeral == 0)
		*next_ephemeral = cprng_fast32() & 0xffff;

	/* Ephemeral port selection function */
	num_ephemeral = mymax - mymin + 1;

	count = num_ephemeral;
	do {
		*next_ephemeral = *next_ephemeral +
		    (cprng_fast32() % N) + 1;
		myport = mymin +
		    (*next_ephemeral % num_ephemeral);

		if (check_suitable_port(myport, inp, cred)) {
			*port = myport;
			DPRINTF("%s returning port %d\n", __func__, *port);
			return 0;
		}
		count--;
	} while (count > 0);

	return EINVAL;
}

/* The generic function called in order to pick a port. */
int
portalgo_randport(uint16_t *port, struct inpcb *inp, kauth_cred_t cred)
{
	int algo, error;
	uint16_t lport;
	int default_algo;

	DPRINTF("%s called\n", __func__);

	if (inp->inp_portalgo == PORTALGO_DEFAULT) {
		switch (inp->inp_af) {
#ifdef INET
		case AF_INET:
			default_algo = inet4_portalgo;
			break;
#endif
#ifdef INET6
		case AF_INET6:
			default_algo = inet6_portalgo;
			break;
#endif
		default:
			return EINVAL;
		}

		if (default_algo == PORTALGO_DEFAULT)
			algo = PORTALGO_BSD;
		else
			algo = default_algo;
	}
	else /* socket specifies the algorithm */
		algo = inp->inp_portalgo;

	KASSERT(algo >= 0);
	KASSERT(algo < NALGOS);

	switch (inp->inp_af) {
#ifdef INET
	case AF_INET: {
		char buf[INET_ADDRSTRLEN];
		DPRINTF("local addr: %s\n", IN_PRINT(buf, &in4p_laddr(inp)));
		DPRINTF("local port: %d\n", inp->inp_lport);
		DPRINTF("foreign addr: %s\n", IN_PRINT(buf, &in4p_faddr(inp)));
		DPRINTF("foreign port: %d\n", inp->inp_fport);
		break;
	}
#endif
#ifdef INET6
	case AF_INET6: {
		char buf[INET6_ADDRSTRLEN];
		DPRINTF("local addr: %s\n", IN6_PRINT(buf, &in6p_laddr(inp)));
		DPRINTF("local port: %d\n", inp->inp_lport);
		DPRINTF("foreign addr: %s\n", IN6_PRINT(buf,
		    &in6p_laddr(inp)));
		DPRINTF("foreign port: %d\n", inp->inp_fport);
		break;
	}
#endif
	default:
		break;
	}

	DPRINTF("%s portalgo = %d\n", __func__, algo);

	error = (*algos[algo].func)(algo, &lport, inp, cred);
	if (error == 0) {
		*port = lport;
	} else if (error != EAGAIN) {
		uint16_t lastport, mymin, mymax, *pnext_ephemeral;

		error = pcb_getports(inp, &lastport, &mymin,
		    &mymax, &pnext_ephemeral, algo);
		if (error)
			return error;
		*port = lastport - 1;
	}
	return error;
}

/* Sets the algorithm to be used globally */
static int
portalgo_algo_name_select(const char *name, int *algo)
{
	size_t ai;

	DPRINTF("%s called\n", __func__);

	for (ai = 0; ai < NALGOS; ai++)
		if (strcmp(algos[ai].name, name) == 0) {
			DPRINTF("%s: found idx %zu\n", __func__, ai);
			*algo = ai;
			return 0;
		}
	return EINVAL;
}

/* Sets the algorithm to be used by the pcb inp. */
int
portalgo_algo_index_select(struct inpcb *inp, int algo)
{

	DPRINTF("%s called with algo %d for pcb %p\n", __func__, algo, inp );

	if ((algo < 0 || algo >= NALGOS) &&
	    (algo != PORTALGO_DEFAULT))
		return EINVAL;

	inp->inp_portalgo = algo;
	return 0;
}

/*
 * The sysctl hook that is supposed to check that we are picking one
 * of the valid algorithms.
 */
static int
sysctl_portalgo_selected(SYSCTLFN_ARGS, int *algo)
{
	struct sysctlnode node;
	int error;
	char newalgo[PORTALGO_MAXLEN];

	DPRINTF("%s called\n", __func__);

	strlcpy(newalgo, algos[*algo].name, sizeof(newalgo));

	node = *rnode;
	node.sysctl_data = newalgo;
	node.sysctl_size = sizeof(newalgo);

	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	DPRINTF("newalgo: %s\n", newalgo);

	if (error || newp == NULL ||
	    strncmp(newalgo, algos[*algo].name, sizeof(newalgo)) == 0)
		return error;

#ifdef KAUTH_NETWORK_SOCKET_PORT_RANDOMIZE
	if (l != NULL && (error = kauth_authorize_system(l->l_cred,
	    KAUTH_NETWORK_SOCKET, KAUTH_NETWORK_SOCKET_PORT_RANDOMIZE, newname,
	    NULL, NULL)) != 0)
		return error;
#endif

	mutex_enter(softnet_lock);
	error = portalgo_algo_name_select(newalgo, algo);
	mutex_exit(softnet_lock);
	return error;
}

static int
sysctl_portalgo_reserve(SYSCTLFN_ARGS, bitmap *bt)
{
	struct sysctlnode node;
	int error;

	DPRINTF("%s called\n", __func__);

	node = *rnode;
	node.sysctl_data = bt;
	node.sysctl_size = sizeof(*bt);

	error = sysctl_lookup(SYSCTLFN_CALL(&node));

	if (error || newp == NULL)
		return error;

#ifdef KAUTH_NETWORK_SOCKET_PORT_RESERVE
	if (l != NULL && (error = kauth_authorize_system(l->l_cred,
	    KAUTH_NETWORK_SOCKET, KAUTH_NETWORK_SOCKET_PORT_RESERVE, bt,
	    NULL, NULL)) != 0)
		return error;
#endif
	return error;
}

#ifdef INET
/*
 * The sysctl hook that is supposed to check that we are picking one
 * of the valid algorithms.
 */
int
sysctl_portalgo_selected4(SYSCTLFN_ARGS)
{

	return sysctl_portalgo_selected(SYSCTLFN_CALL(rnode), &inet4_portalgo);
}

int
sysctl_portalgo_reserve4(SYSCTLFN_ARGS)
{

	return sysctl_portalgo_reserve(SYSCTLFN_CALL(rnode), &inet4_reserve);
}
#endif

#ifdef INET6
int
sysctl_portalgo_selected6(SYSCTLFN_ARGS)
{

	return sysctl_portalgo_selected(SYSCTLFN_CALL(rnode), &inet6_portalgo);
}

int
sysctl_portalgo_reserve6(SYSCTLFN_ARGS)
{
	return sysctl_portalgo_reserve(SYSCTLFN_CALL(rnode), &inet6_reserve);
}
#endif

/*
 * The sysctl hook that returns the available
 * algorithms.
 */
int
sysctl_portalgo_available(SYSCTLFN_ARGS)
{
	size_t ai, len = 0;
	struct sysctlnode node;
	char availalgo[NALGOS * PORTALGO_MAXLEN];

	DPRINTF("%s called\n", __func__);

	availalgo[0] = '\0';

	for (ai = 0; ai < NALGOS; ai++) {
		len = strlcat(availalgo, algos[ai].name, sizeof(availalgo));
		if (ai < NALGOS - 1)
			strlcat(availalgo, " ", sizeof(availalgo));
	}

	DPRINTF("available algos: %s\n", availalgo);

	node = *rnode;
	node.sysctl_data = availalgo;
	node.sysctl_size = len;

	return sysctl_lookup(SYSCTLFN_CALL(&node));
}