! ! Implementation of RFC6056 "Recommendations for Transport-Protocol ! Port Randomization" algorithms. ! ! Submitted by: Ivo Vachkov (ivo.vachkov gmail.com) (original version) ! Index: sys/netinet/in_pcb.c =================================================================== --- sys/netinet/in_pcb.c (revision 219580) +++ sys/netinet/in_pcb.c (working copy) @@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -86,6 +87,15 @@ __FBSDID("$FreeBSD$"); #include /* + * RFC6056 Port Randomization Algorithms. + */ +#define INP_RFC6056_ALG_1 1 /* Simple Port Randomization Algorithm I */ +#define INP_RFC6056_ALG_2 2 /* Simple Port Randomization Algorithm II */ +#define INP_RFC6056_ALG_3 3 /* Simple Hash-Based Port Selection Algorithm */ +#define INP_RFC6056_ALG_4 4 /* Double-Hash Port Selection Algorithm */ +#define INP_RFC6056_ALG_5 5 /* Random-Increments Port Selection Algorithm */ + +/* * These configure the range of local port addresses assigned to * "unspecified" outgoing connections/packets/whatever. */ @@ -112,7 +122,14 @@ VNET_DEFINE(int, ipport_stoprandom); /* toggled b VNET_DEFINE(int, ipport_tcpallocs); static VNET_DEFINE(int, ipport_tcplastcount); +static VNET_DEFINE(u_int, ipport_randomalg_ver) = INP_RFC6056_ALG_1; + /* user controlled via sysctl */ +static VNET_DEFINE(u_int, ipport_randomalg_alg5_tradeoff) = 500; + /* user controlled via sysctl */ + #define V_ipport_tcplastcount VNET(ipport_tcplastcount) +#define V_ipport_randomalg_ver VNET(ipport_randomalg_ver) +#define V_ipport_randomalg_alg5_tradeoff VNET(ipport_randomalg_alg5_tradeoff) #define RANGECHK(var, min, max) \ if ((var) < (min)) { (var) = (min); } \ @@ -143,7 +160,66 @@ sysctl_net_ipport_check(SYSCTL_HANDLER_ARGS) #undef RANGECHK +/* + * Updates algorithm to use for random ephemeral port (local) port allocation + * in in_pcbbind_setup() / in_pcb_lport(). + */ +static int +sysctl_net_randomalg_version_check(SYSCTL_HANDLER_ARGS) +{ + u_int algorithm; + int error; + + algorithm = *(u_int *)arg1; +#ifdef VIMAGE + error = vnet_sysctl_handle_uint(oidp, &algorithm, 0, req); +#else + error = sysctl_handle_int(oidp, &algorithm, 0, req); +#endif + if (error == 0 && req->newptr) { + switch (algorithm) { + case INP_RFC6056_ALG_1: + case INP_RFC6056_ALG_2: + case INP_RFC6056_ALG_3: + case INP_RFC6056_ALG_4: + case INP_RFC6056_ALG_5: + V_ipport_randomalg_ver = algorithm; + break; + default: + return (EINVAL); + } + } + return (error); +} + +/* + * Updates V_ipport_randomalg_alg5_tradeoff to provided value + * and ensures it is in the supported range (1 - 65536). + */ +static int +sysctl_net_randomalg_alg5_tradeoff_check(SYSCTL_HANDLER_ARGS) +{ + u_int tradeoff; + int error; + + tradeoff = *(u_int *)arg1; +#ifdef VIMAGE + error = vnet_sysctl_handle_uint(oidp, &tradeoff, 0, req); +#else + error = sysctl_handle_int(oidp, &tradeoff, 0, req); +#endif + if (error == 0 && req->newptr) { + if (tradeoff < 1 || tradeoff > 65536) + return (EINVAL); + else + V_ipport_randomalg_alg5_tradeoff = tradeoff; + } + return (error); +} + SYSCTL_NODE(_net_inet_ip, IPPROTO_IP, portrange, CTLFLAG_RW, 0, "IP Ports"); +SYSCTL_NODE(_net_inet_ip_portrange, IPPROTO_IP, randomalg, CTLFLAG_RW, 0, + "Port Randomization Algorithms"); SYSCTL_VNET_PROC(_net_inet_ip_portrange, OID_AUTO, lowfirst, CTLTYPE_INT|CTLFLAG_RW, &VNET_NAME(ipport_lowfirstauto), 0, @@ -176,6 +252,14 @@ SYSCTL_VNET_INT(_net_inet_ip_portrange, OID_AUTO, &VNET_NAME(ipport_randomtime), 0, "Minimum time to keep sequental port " "allocation before switching to a random one"); +SYSCTL_VNET_PROC(_net_inet_ip_portrange_randomalg, OID_AUTO, version, + CTLTYPE_UINT|CTLFLAG_RW, &VNET_NAME(ipport_randomalg_ver), 0, + &sysctl_net_randomalg_version_check, "IU", + "RFC 6056 Port randomization algorithm"); +SYSCTL_VNET_PROC(_net_inet_ip_portrange_randomalg, OID_AUTO, alg5_tradeoff, + CTLTYPE_UINT|CTLFLAG_RW, &VNET_NAME(ipport_randomalg_alg5_tradeoff), 0, + &sysctl_net_randomalg_alg5_tradeoff_check, "IU", + "RFC 6056 Algorithm 5 computational trade-off"); /* * in_pcb.c: manage the Protocol Control Blocks. @@ -328,6 +412,7 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *la struct in_addr laddr; #endif + KASSERT(inp != NULL, ("%s: inp NULL", __func__)); pcbinfo = inp->inp_pcbinfo; /* @@ -360,7 +445,7 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *la * ipport_tick() allows it. */ if (V_ipport_randomized && - (!V_ipport_stoprandom || pcbinfo == &V_udbinfo)) + (!V_ipport_stoprandom || pcbinfo == &V_udbinfo)) dorandom = 1; else dorandom = 0; @@ -384,48 +469,303 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *la last = aux; } + count = last - first; + #ifdef INET - /* Make the compiler happy. */ - laddr.s_addr = 0; if ((inp->inp_vflag & INP_IPV4) != 0) { KASSERT(laddrp != NULL, ("%s: laddrp NULL for v4 inp %p", __func__, inp)); laddr = *laddrp; } #endif + KASSERT(lportp != NULL, ("%s: lportp NULL for inp %p", __func__, inp)); lport = *lportp; - if (dorandom) - *lastport = first + (arc4random() % (last - first)); + if (dorandom == 0) { + do { + if (count-- < 0) /* completely used? */ + return (EADDRNOTAVAIL); + ++*lastport; + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) + tmpinp = in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild, cred); +#endif +#if defined(INET) && defined(INET6) + else +#endif +#ifdef INET + tmpinp = in_pcblookup_local(pcbinfo, + laddr, lport, wild, cred); +#endif + } while (tmpinp != NULL); - count = last - first; + goto done; + } - do { - if (count-- < 0) /* completely used? */ - return (EADDRNOTAVAIL); - ++*lastport; - if (*lastport < first || *lastport > last) - *lastport = first; - lport = htons(*lastport); + /* + * If there was only one port left (first == last), we must not get + * here as some optimizations would break ( % 0 ). + */ + KASSERT(count > 0, ("%s: hitting port randomization with <= 1 port " + "left, count=%d", __func__, count)); + /* + * RFC 6056 specifies five possible algorithms for random port + * allocation. We allow the user to change which one to use by sysctl. + */ + switch (V_ipport_randomalg_ver) { + case INP_RFC6056_ALG_5: /* Random-Increments Port Selection. */ + do { + if (count-- < 0) /* completely used? */ + return (EADDRNOTAVAIL); + *lastport = first + ((arc4random() % 65536) + + (arc4random() % V_ipport_randomalg_alg5_tradeoff) + + 1); + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); #ifdef INET6 - if ((inp->inp_vflag & INP_IPV6) != 0) - tmpinp = in6_pcblookup_local(pcbinfo, - &inp->in6p_laddr, lport, wild, cred); + if ((inp->inp_vflag & INP_IPV6) != 0) + tmpinp = in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild, cred); #endif #if defined(INET) && defined(INET6) - else + else #endif #ifdef INET - tmpinp = in_pcblookup_local(pcbinfo, laddr, - lport, wild, cred); + tmpinp = in_pcblookup_local(pcbinfo, + laddr, lport, wild, cred); #endif - } while (tmpinp != NULL); + } while (tmpinp != NULL); + break; + case INP_RFC6056_ALG_4: /* Double-Hash Port Selection Algorithm. */ + { + MD5_CTX f_ctx; + MD5_CTX g_ctx; + u_int32_t F[4] = { 0, 0, 0, 0 }; + u_int32_t G[4] = { 0, 0, 0, 0 }; + u_int32_t secret_f[4] = { 0, 0, 0, 0 }; + u_int32_t secret_g[4] = { 0, 0, 0, 0 }; + u_int16_t table[16]; + u_int32_t index = 0; + u_int32_t offset = 0; + + secret_f[0] = arc4random(); + secret_f[1] = arc4random(); + secret_f[2] = arc4random(); + secret_f[3] = arc4random(); + + secret_g[0] = arc4random(); + secret_g[1] = arc4random(); + secret_g[2] = arc4random(); + secret_g[3] = arc4random(); + + for (index = 0; index < sizeof(table); index++) + table[index] = arc4random() % 65536; + + MD5Init(&f_ctx); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) { + MD5Update(&f_ctx, (u_char *)&inp->in6p_laddr, + sizeof(inp->in6p_laddr)); + MD5Update(&f_ctx, (u_char *)&inp->in6p_faddr, + sizeof(inp->in6p_faddr)); + } +#endif +#if defined(INET) && defined(INET6) + else +#endif #ifdef INET + { + MD5Update(&f_ctx, (u_char *)&inp->inp_laddr, + sizeof(inp->inp_laddr)); + MD5Update(&f_ctx, (u_char *)&inp->inp_faddr, + sizeof(inp->inp_faddr)); + } +#endif + MD5Update(&f_ctx, (u_char *)&inp->inp_fport, + sizeof(inp->inp_fport)); + MD5Update(&f_ctx, (u_char *)secret_f, + sizeof(secret_f)); + MD5Final((u_char *)&F, &f_ctx); + offset = ((F[0] ^ F[1]) ^ (F[2] ^ F[3])); + + MD5Init(&g_ctx); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) { + MD5Update(&g_ctx, (u_char *)&inp->in6p_laddr, + sizeof(inp->in6p_laddr)); + MD5Update(&g_ctx, (u_char *)&inp->in6p_faddr, + sizeof(inp->in6p_faddr)); + } +#endif +#if defined(INET) && defined(INET6) + else +#endif +#ifdef INET + { + MD5Update(&g_ctx, (u_char *)&inp->inp_laddr, + sizeof(inp->inp_laddr)); + MD5Update(&g_ctx, (u_char *)&inp->inp_faddr, + sizeof(inp->inp_faddr)); + } +#endif + MD5Update(&g_ctx, (u_char *)&inp->inp_fport, + sizeof(inp->inp_fport)); + MD5Update(&g_ctx, (u_char *)secret_g, + sizeof(secret_g)); + MD5Final((u_char *)&G, &g_ctx); + index = ((G[0] ^ G[1]) ^ (G[2] ^ G[3])) % sizeof(table); + + do { + if (count-- < 0) /* completely used? */ + return (EADDRNOTAVAIL); + *lastport = first + + (offset + table[index]++) % count; + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) + tmpinp = in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild, + cred); +#endif +#if defined(INET) && defined(INET6) + else +#endif +#ifdef INET + tmpinp = in_pcblookup_local(pcbinfo, + laddr, lport, wild, cred); +#endif + } while (tmpinp != NULL); + } + break; + + case INP_RFC6056_ALG_3: /* Simple Hash-Based Port Selection Algorithm. */ + { + MD5_CTX f_ctx; + u_int32_t F[4] = { 0, 0, 0, 0 }; + u_int32_t secret_f[4] = { 0, 0, 0, 0 }; + u_int32_t offset = 0; + + secret_f[0] = arc4random(); + secret_f[1] = arc4random(); + secret_f[2] = arc4random(); + secret_f[3] = arc4random(); + + MD5Init(&f_ctx); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) { + MD5Update(&f_ctx, (u_char *)&inp->in6p_laddr, + sizeof(inp->in6p_laddr)); + MD5Update(&f_ctx, (u_char *)&inp->in6p_faddr, + sizeof(inp->in6p_faddr)); + } +#endif +#if defined(INET) && defined(INET6) + else +#endif +#ifdef INET + { + MD5Update(&f_ctx, (u_char *)&inp->inp_laddr, + sizeof(inp->inp_laddr)); + MD5Update(&f_ctx, (u_char *)&inp->inp_faddr, + sizeof(inp->inp_faddr)); + } +#endif + MD5Update(&f_ctx, (u_char *)&inp->inp_fport, + sizeof(inp->inp_fport)); + MD5Update(&f_ctx, (u_char *)secret_f, + sizeof(secret_f)); + MD5Final((u_char *)&F, &f_ctx); + offset = ((F[0] ^ F[1]) ^ (F[2] ^ F[3])); + + do { + if (count-- < 0) /* completely used? */ + return (EADDRNOTAVAIL); + *lastport = first + ((arc4random() % 65536) + + (offset % 65536)) % count; + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) + tmpinp = in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild, + cred); +#endif +#if defined(INET) && defined(INET6) + else +#endif +#ifdef INET + tmpinp = in_pcblookup_local(pcbinfo, + laddr, lport, wild, cred); +#endif + } while (tmpinp != NULL); + } + break; + + case INP_RFC6056_ALG_2: /* Simple Port Randomization Algorithm II. */ + do { + if (count-- < 0) /* completely used? */ + return (EADDRNOTAVAIL); + *lastport = first + + (arc4random() % (last - first)); + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) + tmpinp = in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild, cred); +#endif +#if defined(INET) && defined(INET6) + else +#endif +#ifdef INET + tmpinp = in_pcblookup_local(pcbinfo, + laddr, lport, wild, cred); +#endif + } while (tmpinp != NULL); + break; + + case INP_RFC6056_ALG_1: /* Simple Port Randomization Algorithm I. */ + default: + *lastport = first + (arc4random() % (last - first)); + do { + if (count-- < 0) /* completely used? */ + return (EADDRNOTAVAIL); + ++*lastport; + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) + tmpinp = in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild, cred); +#endif +#if defined(INET) && defined(INET6) + else +#endif +#ifdef INET + tmpinp = in_pcblookup_local(pcbinfo, + laddr, lport, wild, cred); +#endif + } while (tmpinp != NULL); + break; + } /* switch() */ + +done: +#ifdef INET if ((inp->inp_vflag & INP_IPV4) != 0) laddrp->s_addr = laddr.s_addr; -#endif +#endif *lportp = lport; return (0);