root/sys/netinet/portalgo.c
/*      $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));
}