/* $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 __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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #include #include #endif #include #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)); }