From bf93cf87269ac1ce68f856a60e7dda6e5cfaecae Mon Sep 17 00:00:00 2001 From: christos Date: Sat, 24 Sep 2011 17:18:17 +0000 Subject: [PATCH] Add inet4 part of the rfc6056 code contributed by Vlad Balan as part of Google SoC-2011 --- sys/netinet/files.netinet | 3 +- sys/netinet/in_pcb.c | 64 +-- sys/netinet/in_pcb.h | 4 +- sys/netinet/in_pcb_hdr.h | 3 +- sys/netinet/rfc6056.c | 952 ++++++++++++++++++++++++++++++++++++++ sys/netinet/rfc6056.h | 54 +++ sys/netinet/udp.h | 5 +- sys/netinet/udp_usrreq.c | 36 +- sys/netinet/udp_var.h | 6 +- 9 files changed, 1077 insertions(+), 50 deletions(-) create mode 100644 sys/netinet/rfc6056.c create mode 100644 sys/netinet/rfc6056.h diff --git a/sys/netinet/files.netinet b/sys/netinet/files.netinet index f7fb2adf1b41..e4db1330eaf7 100644 --- a/sys/netinet/files.netinet +++ b/sys/netinet/files.netinet @@ -1,4 +1,4 @@ -# $NetBSD: files.netinet,v 1.22 2011/05/03 18:28:45 dyoung Exp $ +# $NetBSD: files.netinet,v 1.23 2011/09/24 17:18:17 christos Exp $ defflag opt_tcp_debug.h TCP_DEBUG defparam opt_tcp_debug.h TCP_NDEBUG @@ -43,3 +43,4 @@ file netinet/tcp_congctl.c inet | inet6 file netinet/tcp_vtw.c inet | inet6 file netinet/udp_usrreq.c inet | inet6 +file netinet/rfc6056.c inet | inet6 diff --git a/sys/netinet/in_pcb.c b/sys/netinet/in_pcb.c index 0d7d2ad8a47a..35de4776864f 100644 --- a/sys/netinet/in_pcb.c +++ b/sys/netinet/in_pcb.c @@ -1,4 +1,4 @@ -/* $NetBSD: in_pcb.c,v 1.138 2011/05/03 18:28:45 dyoung Exp $ */ +/* $NetBSD: in_pcb.c,v 1.139 2011/09/24 17:18:17 christos Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. @@ -93,7 +93,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: in_pcb.c,v 1.138 2011/05/03 18:28:45 dyoung Exp $"); +__KERNEL_RCSID(0, "$NetBSD: in_pcb.c,v 1.139 2011/09/24 17:18:17 christos Exp $"); #include "opt_inet.h" #include "opt_ipsec.h" @@ -124,6 +124,7 @@ __KERNEL_RCSID(0, "$NetBSD: in_pcb.c,v 1.138 2011/05/03 18:28:45 dyoung Exp $"); #include #include #include +#include #ifdef INET6 #include @@ -202,11 +203,13 @@ in_pcballoc(struct socket *so, void *v) splx(s); if (inp == NULL) return (ENOBUFS); - memset((void *)inp, 0, sizeof(*inp)); + memset(inp, 0, sizeof(*inp)); inp->inp_af = AF_INET; inp->inp_table = table; inp->inp_socket = so; inp->inp_errormtu = -1; + inp->inp_rfc6056algo = RFC6056_ALGO_DEFAULT; + inp->inp_bindportonsend = false; #if defined(IPSEC) || defined(FAST_IPSEC) error = ipsec_init_pcbpolicy(so, &inp->inp_sp); if (error != 0) { @@ -232,8 +235,6 @@ in_pcbsetport(struct sockaddr_in *sin, struct inpcb *inp, kauth_cred_t cred) { struct inpcbtable *table = inp->inp_table; struct socket *so = inp->inp_socket; - int cnt; - u_int16_t mymin, mymax; u_int16_t *lastport; u_int16_t lport = 0; enum kauth_network_req req; @@ -246,14 +247,10 @@ in_pcbsetport(struct sockaddr_in *sin, struct inpcb *inp, kauth_cred_t cred) req = KAUTH_REQ_NETWORK_BIND_PORT; #endif - mymin = lowportmin; - mymax = lowportmax; lastport = &table->inpt_lastlow; } else { req = KAUTH_REQ_NETWORK_BIND_PORT; - mymin = anonportmin; - mymax = anonportmax; lastport = &table->inpt_lastport; } @@ -263,38 +260,13 @@ in_pcbsetport(struct sockaddr_in *sin, struct inpcb *inp, kauth_cred_t cred) if (error) return (EACCES); - if (mymin > mymax) { /* sanity check */ - u_int16_t swp; + /* + * Use RFC6056 randomized port selection + */ + error = rfc6056_randport(&lport, &inp->inp_head, cred); + if (error) + return error; - swp = mymin; - mymin = mymax; - mymax = swp; - } - - lport = *lastport - 1; - for (cnt = mymax - mymin + 1; cnt; cnt--, lport--) { - vestigial_inpcb_t vestigial; - - if (lport < mymin || lport > mymax) - lport = mymax; - if (!in_pcblookup_port(table, sin->sin_addr, htons(lport), 1, - &vestigial) && !vestigial.valid) { - /* We have a free port, check with the secmodel(s). */ - sin->sin_port = lport; - error = kauth_authorize_network(cred, - KAUTH_NETWORK_BIND, req, so, sin, NULL); - if (error) { - /* Secmodel says no. Keep looking. */ - continue; - } - - goto found; - } - } - - return (EAGAIN); - - found: inp->inp_flags |= INP_ANONPORT; *lastport = lport; lport = htons(lport); @@ -569,6 +541,18 @@ in_pcbconnect(void *v, struct mbuf *nam, struct lwp *l) } inp->inp_faddr = sin->sin_addr; inp->inp_fport = sin->sin_port; + + /* Late bind, if needed */ + if (inp->inp_bindportonsend) { + struct sockaddr_in lsin = *((const struct sockaddr_in *) + inp->inp_socket->so_proto->pr_domain->dom_sa_any); + lsin.sin_addr = inp->inp_laddr; + lsin.sin_port = 0; + + if ((error = in_pcbbind_port(inp, &lsin, l->l_cred)) != 0) + return error; + } + in_pcbstate(inp, INP_CONNECTED); #if defined(IPSEC) || defined(FAST_IPSEC) if (inp->inp_socket->so_type == SOCK_STREAM) diff --git a/sys/netinet/in_pcb.h b/sys/netinet/in_pcb.h index cb8db3a03676..2e5d5ab6ded2 100644 --- a/sys/netinet/in_pcb.h +++ b/sys/netinet/in_pcb.h @@ -1,4 +1,4 @@ -/* $NetBSD: in_pcb.h,v 1.48 2011/05/03 18:28:45 dyoung Exp $ */ +/* $NetBSD: in_pcb.h,v 1.49 2011/09/24 17:18:17 christos Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. @@ -80,6 +80,7 @@ struct inpcb { #define inp_af inp_head.inph_af #define inp_ppcb inp_head.inph_ppcb #define inp_state inp_head.inph_state +#define inp_rfc6056algo inp_head.inph_rfc6056algo #define inp_socket inp_head.inph_socket #define inp_table inp_head.inph_table #define inp_sp inp_head.inph_sp @@ -92,6 +93,7 @@ struct inpcb { struct ip_moptions *inp_moptions; /* IP multicast options */ int inp_errormtu; /* MTU of last xmit status = EMSGSIZE */ uint8_t inp_ip_minttl; + bool inp_bindportonsend; }; #define inp_faddr inp_ip.ip_dst diff --git a/sys/netinet/in_pcb_hdr.h b/sys/netinet/in_pcb_hdr.h index 4acffab805c5..eeb96cf104c8 100644 --- a/sys/netinet/in_pcb_hdr.h +++ b/sys/netinet/in_pcb_hdr.h @@ -1,4 +1,4 @@ -/* $NetBSD: in_pcb_hdr.h,v 1.6 2011/05/03 18:28:45 dyoung Exp $ */ +/* $NetBSD: in_pcb_hdr.h,v 1.7 2011/09/24 17:18:17 christos Exp $ */ /* * Copyright (C) 2003 WIDE Project. @@ -77,6 +77,7 @@ struct inpcb_hdr { int inph_af; /* address family - AF_INET */ void * inph_ppcb; /* pointer to per-protocol pcb */ int inph_state; /* bind/connect state */ + int inph_rfc6056algo; struct socket *inph_socket; /* back pointer to socket */ struct inpcbtable *inph_table; #if 1 /* IPSEC */ diff --git a/sys/netinet/rfc6056.c b/sys/netinet/rfc6056.c new file mode 100644 index 000000000000..7d2ef888cf11 --- /dev/null +++ b/sys/netinet/rfc6056.c @@ -0,0 +1,952 @@ +/* $NetBSD: rfc6056.c,v 1.1 2011/09/24 17:18:17 christos 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. + * + */ + +#include +__KERNEL_RCSID(0, "$NetBSD: rfc6056.c,v 1.1 2011/09/24 17:18:17 christos Exp $"); + +#include "opt_inet.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#include +#endif + +#include + +#include "rfc6056.h" + +#define NPROTO 2 +#define RFC6056_TCP 0 +#define RFC6056_UDP 1 + +#define NAF 2 +#define RFC6056_IPV4 0 +#define RFC6056_IPV6 1 + +#define NRANGES 2 +#define RFC6056_LOWPORT 0 +#define RFC6056_HIGHPORT 1 + +#define RFC6056_DEBUG 1 + +#if RFC6056_DEBUG +static bool rfc6056_debug = true; +#define DPRINTF if (rfc6056_debug) printf +#else +#define DPRINTF while (/*CONSTCOND*/0) printf +#endif + +#ifdef INET +static int inet4_rfc6056algo = RFC6056_ALGO_BSD; +#endif +#ifdef INET6 +static int inet6_rfc6056algo = RFC6056_ALGO_BSD; +#endif + +typedef struct { + const char *name; + int (*func)(int, uint16_t *, struct inpcb_hdr *, kauth_cred_t); +} rfc6056_algorithm_t; + +static int algo_bsd(int, uint16_t *, struct inpcb_hdr *, kauth_cred_t); +static int algo_random_start(int, uint16_t *, struct inpcb_hdr *, kauth_cred_t); +static int algo_random_pick(int, uint16_t *, struct inpcb_hdr *, kauth_cred_t); +static int algo_hash(int, uint16_t *, struct inpcb_hdr *, kauth_cred_t); +static int algo_doublehash(int, uint16_t *, struct inpcb_hdr *, kauth_cred_t); +static int algo_randinc(int, uint16_t *, struct inpcb_hdr *, kauth_cred_t); + +static const rfc6056_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 rfc6056_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_hdr *inp_hdr, uint16_t *lastport, + uint16_t *mymin, uint16_t *mymax, uint16_t **pnext_ephemeral, int algo) +{ + struct socket *so; + int rfc6056_proto; + int rfc6056_af; + int rfc6056_range; + + so = inp_hdr->inph_socket; + + switch (so->so_type) { + case SOCK_DGRAM: /* UDP or DCCP */ + rfc6056_proto = RFC6056_UDP; + break; + case SOCK_STREAM: /* TCP or SCTP */ + rfc6056_proto = RFC6056_TCP; + break; + default: + return EPFNOSUPPORT; + } + + switch (inp_hdr->inph_af) { +#ifdef INET + case AF_INET: { + struct inpcb *inp = (struct inpcb *)(void *)inp_hdr; + struct inpcbtable *table = inp->inp_table; + + rfc6056_af = RFC6056_IPV4; + + if (inp->inp_flags & INP_LOWPORT) { + *mymin = lowportmin; + *mymax = lowportmax; + *lastport = table->inpt_lastlow; + rfc6056_range = RFC6056_LOWPORT; + } else { + *mymin = anonportmin; + *mymax = anonportmax; + *lastport = table->inpt_lastport; + rfc6056_range = RFC6056_HIGHPORT; + } + break; + } +#endif +#ifdef INET6 + case AF_INET6: { + struct in6pcb *in6p = (struct in6pcb *)(void *)inp_hdr; + struct inpcbtable *table = in6p->in6p_table; + + rfc6056_af = RFC6056_IPV6; + + if (in6p->in6p_flags & IN6P_LOWPORT) { + *mymin = ip6_lowportmin; + *mymax = ip6_lowportmax; + *lastport = table->inpt_lastlow; + rfc6056_range = RFC6056_LOWPORT; + } else { + *mymin = ip6_anonportmin; + *mymax = ip6_anonportmax; + *lastport = table->inpt_lastport; + rfc6056_range = RFC6056_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 = &rfc6056_next_ephemeral[rfc6056_proto] + [rfc6056_af][rfc6056_range][algo]; + + DPRINTF("%s rfc6056_proto:%d rfc6056_af:%d rfc6056_range:%d\n", + __func__, rfc6056_proto, rfc6056_af, rfc6056_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_hdr *inp_hdr, +kauth_cred_t cred) +{ + struct inpcbtable *table; + struct socket *so; +#ifdef INET + vestigial_inpcb_t vestigial; +#endif + int error; + int wild = 0; + + DPRINTF("%s called for argument %d\n", __func__, port); + + + switch (inp_hdr->inph_af) { +#ifdef INET + case AF_INET: { /* IPv4 */ + struct inpcb *inp = (struct inpcb *)(void *)inp_hdr; + struct inpcb *pcb; + struct sockaddr_in sin; + enum kauth_network_req req; + + 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; + + table = inp->inp_table; + + sin.sin_addr = inp->inp_laddr; + + pcb = in_pcblookup_port(table, sin.sin_addr, htons(port), 1, + &vestigial); + + DPRINTF("%s in_pcblookup_port returned %p and " + "vestigial.valid %d\n", + __func__, pcb, vestigial.valid); + + if ((!pcb) && (!vestigial.valid)) { + 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 in6pcb *in6p = (struct in6pcb *)(void *)inp_hdr; + table = in6p->in6p_table; + struct sockaddr_in6 sin6; + enum kauth_network_req req; + void *t; + + if (in6p->in6p_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_addr = in6p->in6p_laddr; + so = in6p->in6p_socket; + + /* XXX: this is redundant when called from in6_pcbbind */ + 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 = in_pcblookup_port(table, + *(struct in_addr *)&sin6.sin6_addr.s6_addr32[3], + htons(port), wild, &vestigial); + if (!t && vestigial.valid) { + DPRINTF("%s in_pcblookup_port returned " + "a result\n", __func__); + return false; + } + } else +#endif + { + t = in6_pcblookup_port(table, &sin6.sin6_addr, + htons(port), wild, &vestigial); + if (!t && vestigial.valid) { + DPRINTF("%s in6_pcblookup_port returned " + "a result\n", __func__); + return false; + } + } + if (t == 0) { + /* We have a free port. Check with the secmodel. */ + 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_hdr *inp_hdr, +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_hdr, &lastport, &mymin, &mymax, + &next_ephemeral, algo); + if (error) + return error; + + /* Ephemeral port selection function */ + num_ephemeral = mymax - mymin + 1; + + if (*next_ephemeral == 0) + *next_ephemeral = mymax; + + count = num_ephemeral; + do { + uint16_t myport = *next_ephemeral; + if (*next_ephemeral <= mymin) + *next_ephemeral = mymax; + else + (*next_ephemeral)--; + + if (check_suitable_port(myport, inp_hdr, 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; +} + +/* + * The straightforward algorithm that calls random() in order to + * compute the increment to the next port number. + */ +static int +algo_random_start(int algo, uint16_t *port, struct inpcb_hdr *inp_hdr, + 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_hdr, &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 + (arc4random() % num_ephemeral); + + DPRINTF("next_ephemeral initially: %u\n", *next_ephemeral); + + count = num_ephemeral; + + do { + if (check_suitable_port(*next_ephemeral, inp_hdr, 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_hdr *inp_hdr, + 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_hdr, &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 + (arc4random() % num_ephemeral); + + DPRINTF("next_ephemeral initially: %u\n", *next_ephemeral); + + count = num_ephemeral; + + do { + if (check_suitable_port(*next_ephemeral, inp_hdr, cred)) { + *port = *next_ephemeral; + DPRINTF("%s returning port %d\n", __func__, *port); + return 0; + } + *next_ephemeral = mymin + + (arc4random() % 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_hdr *inp_hdr) +{ + MD5_CTX f_ctx; + uint32_t Ff[4]; + uint32_t secret_f[4]; + uint32_t offset; + uint16_t soffset[2]; + + secret_f[0] = arc4random(); + secret_f[1] = arc4random(); + secret_f[2] = arc4random(); + secret_f[3] = arc4random(); + + MD5Init(&f_ctx); + switch (inp_hdr->inph_af) { +#ifdef INET + case AF_INET: { + const struct inpcb *inp = + (const struct inpcb *)(const void *)inp_hdr; + MD5Update(&f_ctx, (const u_char *)&inp->inp_laddr, + sizeof(inp->inp_laddr)); + MD5Update(&f_ctx, (const u_char *)&inp->inp_faddr, + sizeof(inp->inp_faddr)); + MD5Update(&f_ctx, (const u_char *)&inp->inp_fport, + sizeof(inp->inp_fport)); + break; + } +#endif +#ifdef INET6 + case AF_INET6: { + const struct in6pcb *in6p = + (const struct in6pcb *)(const void *)inp_hdr; + MD5Update(&f_ctx, (const u_char *)&in6p->in6p_laddr, + sizeof(in6p->in6p_laddr)); + MD5Update(&f_ctx, (const u_char *)&in6p->in6p_faddr, + sizeof(in6p->in6p_faddr)); + MD5Update(&f_ctx, (const u_char *)&in6p->in6p_fport, + sizeof(in6p->in6p_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_hdr *inp_hdr) +{ + struct in6pcb *in6p; + + switch (inp_hdr->inph_af) { +#ifdef INET + case AF_INET: { + struct inpcb *inp = (struct inpcb *)(void *)inp_hdr; + if (inp->inp_fport == 0 || in_nullhost(inp->inp_faddr)) { + 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: { + in6p = (struct in6pcb *)(void *)inp_hdr; + if (in6p->in6p_fport == 0 || memcmp(&in6p->in6p_faddr, + &in6addr_any, sizeof(in6p->in6p_faddr)) == 0) { + DPRINTF("%s fport or faddr missing, delaying port " + "to connect/send\n", __func__); + in6p->in6p_bindportonsend = true; + return false; + } else { + in6p->in6p_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_hdr *inp_hdr, + 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_hdr, &lastport, &mymin, &mymax, + &next_ephemeral, algo); + if (error) + return error; + + if (!iscompletetuple(inp_hdr)) { + *port = 0; + return 0; + } + + /* Ephemeral port selection function */ + num_ephemeral = mymax - mymin + 1; + + DPRINTF("num_ephemeral: %d\n", num_ephemeral); + + offset = Fhash(inp_hdr); + + count = num_ephemeral; + do { + myport = mymin + (*next_ephemeral + offset) + % num_ephemeral; + + (*next_ephemeral)++; + + if (check_suitable_port(myport, inp_hdr, 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_hdr *inp_hdr, + kauth_cred_t cred) +{ + uint16_t count, num_ephemeral; + uint16_t mymin, mymax, lastport; + uint16_t *next_ephemeral; + uint16_t offset, idx, myport; + static uint16_t dhtable[8]; + int error; + + DPRINTF("%s called\n", __func__); + + error = pcb_getports(inp_hdr, &lastport, &mymin, &mymax, + &next_ephemeral, algo); + if (error) + return error; + + if (!iscompletetuple(inp_hdr)) { + *port = 0; + return 0; + } + /* first time initialization */ + if (dhtable[0] == 0) + for (size_t i = 0; i < __arraycount(dhtable); i++) + dhtable[i] = random() & 0xffff; + + /* Ephemeral port selection function */ + num_ephemeral = mymax - mymin + 1; + offset = Fhash(inp_hdr); + idx = Fhash(inp_hdr); /* G */ + count = num_ephemeral; + + do { + myport = mymin + (offset + dhtable[idx]) + % num_ephemeral; + dhtable[idx]++; + + if (check_suitable_port(myport, inp_hdr, 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_hdr *inp_hdr, + 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_hdr, &lastport, &mymin, &mymax, + &next_ephemeral, algo); + if (error) + return error; + + if (*next_ephemeral == 0) + *next_ephemeral = arc4random() & 0xffff; + + /* Ephemeral port selection function */ + num_ephemeral = mymax - mymin + 1; + + count = num_ephemeral; + do { + *next_ephemeral = *next_ephemeral + + (arc4random() % N) + 1; + myport = mymin + + (*next_ephemeral % num_ephemeral); + + if (check_suitable_port(myport, inp_hdr, 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 +rfc6056_randport(uint16_t *port, struct inpcb_hdr *inp_hdr, kauth_cred_t cred) +{ + int algo, error; + uint16_t lport; + int default_algo; + + DPRINTF("%s called\n", __func__); + + if (inp_hdr->inph_rfc6056algo == RFC6056_ALGO_DEFAULT) { + switch (inp_hdr->inph_af) { +#ifdef INET + case AF_INET: + default_algo = inet4_rfc6056algo; + break; +#endif +#ifdef INET6 + case AF_INET6: + default_algo = inet6_rfc6056algo; + break; +#endif + default: + return EINVAL; + } + + if (default_algo == RFC6056_ALGO_DEFAULT) + algo = RFC6056_ALGO_BSD; + else + algo = default_algo; + } + else /* socket specifies the algorithm */ + algo = inp_hdr->inph_rfc6056algo; + + KASSERT(algo >= 0); + KASSERT(algo < NALGOS); + + switch (inp_hdr->inph_af) { +#ifdef INET + case AF_INET: { + struct inpcb *inp = (struct inpcb *)(void *)inp_hdr; + DPRINTF("local addr: %s\n", inet_ntoa(inp->inp_laddr)); + DPRINTF("local port: %d\n", inp->inp_lport); + DPRINTF("foreign addr: %s\n", inet_ntoa(inp->inp_faddr)); + DPRINTF("foreign port: %d\n", inp->inp_fport); + break; + } +#endif +#ifdef INET6 + case AF_INET6: { + struct in6pcb *in6p = (struct in6pcb *)(void *)inp_hdr; + + DPRINTF("local addr: %s\n", ip6_sprintf(&in6p->in6p_laddr)); + DPRINTF("local port: %d\n", in6p->in6p_lport); + DPRINTF("foreign addr: %s\n", ip6_sprintf(&in6p->in6p_faddr)); + DPRINTF("foreign port: %d\n", in6p->in6p_fport); + break; + } +#endif + default: + break; + } + + DPRINTF("%s rfc6056algo = %d\n", __func__, algo); + + + error = (*algos[algo].func)(algo, &lport, inp_hdr, cred); + if (error == 0) + *port = lport; + else { + uint16_t lastport, mymin, mymax, *pnext_ephemeral; + error = pcb_getports(inp_hdr, &lastport, &mymin, + &mymax, &pnext_ephemeral, algo); + if (error) + return error; + *port = lastport - 1; + } + + return error; +} + +/* Sets the algorithm to be used globally */ +static int +rfc6056_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 +rfc6056_algo_index_select(struct inpcb_hdr *inp, int algo) +{ + DPRINTF("%s called with algo %d for pcb %p\n", __func__, algo, inp ); + + if ((algo < 0 || algo >= NALGOS) && + (algo != RFC6056_ALGO_DEFAULT)) + return EINVAL; + + inp->inph_rfc6056algo = algo; + return 0; +} + +/* + * The sysctl hook that is supposed to check that we are picking one + * of the valid algorithms. IPv4. + */ +static int +sysctl_rfc6056_helper(SYSCTLFN_ARGS, int *algo) +{ + struct sysctlnode node; + int error; + char newalgo[RFC6056_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 = rfc6056_algo_name_select(newalgo, algo); + mutex_exit(softnet_lock); + return error; +} + +/* + * The sysctl hook that is supposed to check that we are picking one + * of the valid algorithms. + */ +int +sysctl_rfc6056_selected(SYSCTLFN_ARGS) +{ + return sysctl_rfc6056_helper(SYSCTLFN_CALL(rnode), &inet6_rfc6056algo); +} + +int +sysctl_rfc6056_selected6(SYSCTLFN_ARGS) +{ + return sysctl_rfc6056_helper(SYSCTLFN_CALL(rnode), &inet4_rfc6056algo); +} + +/* + * The sysctl hook that returns the available + * algorithms. + */ +int +sysctl_rfc6056_available(SYSCTLFN_ARGS) +{ + size_t ai, len = 0; + struct sysctlnode node; + char availalgo[NALGOS * RFC6056_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)); +} diff --git a/sys/netinet/rfc6056.h b/sys/netinet/rfc6056.h new file mode 100644 index 000000000000..ff95186339ce --- /dev/null +++ b/sys/netinet/rfc6056.h @@ -0,0 +1,54 @@ +/* $NetBSD: rfc6056.h,v 1.1 2011/09/24 17:18:17 christos 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. + * + */ +#ifndef _NETINET_RFC6056_H_ +#define _NETINET_RFC6056_H_ + +#include + +int rfc6056_randport(uint16_t *, struct inpcb_hdr *, kauth_cred_t); +int sysctl_rfc6056_selected(SYSCTLFN_ARGS); +int sysctl_rfc6056_selected6(SYSCTLFN_ARGS); +int sysctl_rfc6056_available(SYSCTLFN_ARGS); +int rfc6056_algo_index_select(struct inpcb_hdr *, int); + +#define RFC6056_MAXLEN 16 + +/* + * User-settable options (used with setsockopt). + */ +#define RFC6056_ALGO_DEFAULT 0xffff +#define RFC6056_ALGO_BSD 0 +#define RFC6056_ALGO_RANDOM_START 1 +#define RFC6056_ALGO_RANDOM_PICK 2 +#define RFC6056_ALGO_HASH 3 +#define RFC6056_ALGO_DOUBLEHASH 4 +#define RFC6056_ALGO_RANDINC 5 + +#endif /* !_NETINET_RFC6056_H_ */ diff --git a/sys/netinet/udp.h b/sys/netinet/udp.h index e7d08e52a2ae..64557d1834fb 100644 --- a/sys/netinet/udp.h +++ b/sys/netinet/udp.h @@ -1,4 +1,4 @@ -/* $NetBSD: udp.h,v 1.13 2007/12/25 18:33:47 perry Exp $ */ +/* $NetBSD: udp.h,v 1.14 2011/09/24 17:18:17 christos Exp $ */ /* * Copyright (c) 1982, 1986, 1993 @@ -46,7 +46,8 @@ struct udphdr { } __packed; /* socket options for UDP */ -#define UDP_ENCAP 100 +#define UDP_ENCAP 100 +#define UDP_RFC6056ALGO 200 /* Encapsulation types */ #define UDP_ENCAP_ESPINUDP_NON_IKE 1 /* draft-ietf-ipsec-nat-t-ike-00/01 */ diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c index 9593b7b575ba..8da2e03af84d 100644 --- a/sys/netinet/udp_usrreq.c +++ b/sys/netinet/udp_usrreq.c @@ -1,4 +1,4 @@ -/* $NetBSD: udp_usrreq.c,v 1.182 2011/07/17 20:54:53 joerg Exp $ */ +/* $NetBSD: udp_usrreq.c,v 1.183 2011/09/24 17:18:17 christos Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. @@ -61,7 +61,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: udp_usrreq.c,v 1.182 2011/07/17 20:54:53 joerg Exp $"); +__KERNEL_RCSID(0, "$NetBSD: udp_usrreq.c,v 1.183 2011/09/24 17:18:17 christos Exp $"); #include "opt_inet.h" #include "opt_compat_netbsd.h" @@ -96,6 +96,7 @@ __KERNEL_RCSID(0, "$NetBSD: udp_usrreq.c,v 1.182 2011/07/17 20:54:53 joerg Exp $ #include #include #include +#include #ifdef INET6 #include @@ -1085,6 +1086,15 @@ udp_ctloutput(int op, struct socket *so, struct sockopt *sopt) break; } break; + + case UDP_RFC6056ALGO: + error = sockopt_getint(sopt, &optval); + if (error) + break; + + error = rfc6056_algo_index_select( + (struct inpcb_hdr *)inp, optval); + break; default: error = ENOPROTOOPT; @@ -1374,7 +1384,8 @@ sysctl_net_inet_udp_stats(SYSCTLFN_ARGS) static void sysctl_net_inet_udp_setup(struct sysctllog **clog) { - + const struct sysctlnode *rfc6056_node; + sysctl_createv(clog, 0, NULL, NULL, CTLFLAG_PERMANENT, CTLTYPE_NODE, "net", NULL, @@ -1434,6 +1445,25 @@ sysctl_net_inet_udp_setup(struct sysctllog **clog) sysctl_net_inet_udp_stats, 0, NULL, 0, CTL_NET, PF_INET, IPPROTO_UDP, UDPCTL_STATS, CTL_EOL); + /* RFC6056 subtree */ + sysctl_createv(clog, 0, NULL, &rfc6056_node, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "rfc6056", + SYSCTL_DESCR("RFC 6056"), + NULL, 0, NULL, 0, + CTL_NET, PF_INET, IPPROTO_UDP, CTL_CREATE, CTL_EOL); + sysctl_createv(clog, 0, &rfc6056_node, NULL, + CTLFLAG_PERMANENT, + CTLTYPE_STRING, "available", + SYSCTL_DESCR("RFC 6056 available algorithms"), + sysctl_rfc6056_available, 0, NULL, RFC6056_MAXLEN, + CTL_CREATE, CTL_EOL); + sysctl_createv(clog, 0, &rfc6056_node, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_STRING, "selected", + SYSCTL_DESCR("RFC 6056 selected algorithm"), + sysctl_rfc6056_selected, 0, NULL, RFC6056_MAXLEN, + CTL_CREATE, CTL_EOL); } #endif diff --git a/sys/netinet/udp_var.h b/sys/netinet/udp_var.h index 631232209670..b4e7fabade31 100644 --- a/sys/netinet/udp_var.h +++ b/sys/netinet/udp_var.h @@ -1,4 +1,4 @@ -/* $NetBSD: udp_var.h,v 1.36 2008/08/06 15:01:23 plunky Exp $ */ +/* $NetBSD: udp_var.h,v 1.37 2011/09/24 17:18:17 christos Exp $ */ /* * Copyright (c) 1982, 1986, 1989, 1993 @@ -75,7 +75,8 @@ struct udpiphdr { #define UDPCTL_RECVSPACE 3 /* default recv buffer */ #define UDPCTL_LOOPBACKCKSUM 4 /* do UDP checksum on loopback */ #define UDPCTL_STATS 5 /* UDP statistics */ -#define UDPCTL_MAXID 6 +#define UDPCTL_RFC6056 6 /* RFC 6056 algorithm selection */ +#define UDPCTL_MAXID 7 #define UDPCTL_NAMES { \ { 0, 0 }, \ @@ -84,6 +85,7 @@ struct udpiphdr { { "recvspace", CTLTYPE_INT }, \ { "do_loopback_cksum", CTLTYPE_INT }, \ { "stats", CTLTYPE_STRUCT }, \ + { "rfc6056", CTLTYPE_INT }, \ } #ifdef _KERNEL