From: Wouter Wijngaards Date: Mon, 4 Jul 2016 14:51:30 +0000 (+0000) Subject: - Fix #787: outgoing-interface netblock/64 ipv6 option to use linux X-Git-Tag: release-1.5.10~61 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1394dcba69b109b8500576e13343baf95e047efe;p=thirdparty%2Funbound.git - Fix #787: outgoing-interface netblock/64 ipv6 option to use linux freebind to use 64bits of entropy for every query with random local part. git-svn-id: file:///svn/unbound/trunk@3804 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 67cf334d2..f7c1c717b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,9 @@ 4 July 2016: Wouter - For #787: prefer-ip6 option for unbound.conf prefers to send upstream queries to ipv6 servers. + - Fix #787: outgoing-interface netblock/64 ipv6 option to use linux + freebind to use 64bits of entropy for every query with random local + part. 30 June 2016: Wouter - Document always_transparent, always_refuse, always_nxdomain types. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index f6219a32e..339380625 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -127,7 +127,7 @@ Detect source interface on UDP queries and copy them to replies. This feature is experimental, and needs support in your OS for particular socket options. Default value is no. .TP -.B outgoing\-interface: \fI +.B outgoing\-interface: \fI Interface to use to connect to the network. This interface is used to send queries to authoritative servers and receive their replies. Can be given multiple times to work on several interfaces. If none are given the @@ -137,6 +137,18 @@ and .B outgoing\-interface: lines, the interfaces are then used for both purposes. Outgoing queries are sent via a random outgoing interface to counter spoofing. +.IP +If an IPv6 netblock is specified instead of an individual IPv6 address, +outgoing UDP queries will use a randomised source address taken from the +netblock to counter spoofing. Requires the IPv6 netblock to be routed to the +host running unbound, and requires OS support for unprivileged non-local binds +(currently only supported on Linux). Several netblocks may be specified with +multiple +.B outgoing\-interface: +options, but do not specify both an individual IPv6 address and an IPv6 +netblock, or the randomisation will be compromised. Consider combining with +.B prefer\-ip6: yes +to increase the likelihood of IPv6 nameservers being selected for queries. .TP .B outgoing\-range: \fI Number of ports to open. This number of file descriptors can be opened per diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 3083876ee..63c0a5b34 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -184,14 +184,6 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, #else (void)reuseport; #endif /* defined(SO_REUSEPORT) */ -#ifdef IP_FREEBIND - if (freebind && - setsockopt(s, IPPROTO_IP, IP_FREEBIND, (void*)&on, - (socklen_t)sizeof(on)) < 0) { - log_warn("setsockopt(.. IP_FREEBIND ..) failed: %s", - strerror(errno)); - } -#endif /* IP_FREEBIND */ #ifdef IP_TRANSPARENT if (transparent && setsockopt(s, IPPROTO_IP, IP_TRANSPARENT, (void*)&on, @@ -209,6 +201,14 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, } #endif /* IP_TRANSPARENT || IP_BINDANY */ } +#ifdef IP_FREEBIND + if(freebind && + setsockopt(s, IPPROTO_IP, IP_FREEBIND, (void*)&on, + (socklen_t)sizeof(on)) < 0) { + log_warn("setsockopt(.. IP_FREEBIND ..) failed: %s", + strerror(errno)); + } +#endif /* IP_FREEBIND */ if(rcv) { #ifdef SO_RCVBUF int got; diff --git a/services/outside_network.c b/services/outside_network.c index d9e34f469..b915c6145 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -591,7 +591,9 @@ static int setup_if(struct port_if* pif, const char* addrstr, pif->avail_ports = (int*)memdup(avail, (size_t)numavail*sizeof(int)); if(!pif->avail_ports) return 0; - if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen)) + if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen) && + !netblockstrtoaddr(addrstr, UNBOUND_DNS_PORT, + &pif->addr, &pif->addrlen, &pif->pfxlen)) return 0; pif->maxout = (int)numfd; pif->inuse = 0; @@ -893,26 +895,49 @@ pending_delete(struct outside_network* outnet, struct pending* p) free(p); } +static void +sai6_putrandom(struct sockaddr_in6 *sa, int pfxlen, struct ub_randstate *rnd) +{ + int i, last; + if(!(pfxlen > 0 && pfxlen < 128)) + return; + for(i = 0; i < (128 - pfxlen) / 8; i++) { + sa->sin6_addr.s6_addr[15-i] = ub_random_max(rnd, 256); + } + last = pfxlen & 7; + if(last != 0) { + sa->sin6_addr.s6_addr[15-i] |= + ((0xFF >> last) & ub_random_max(rnd, 256)); + } +} + /** * Try to open a UDP socket for outgoing communication. * Sets sockets options as needed. * @param addr: socket address. * @param addrlen: length of address. + * @param pfxlen: length of network prefix (for address randomisation). * @param port: port override for addr. * @param inuse: if -1 is returned, this bool means the port was in use. + * @param rnd: random state (for address randomisation). * @return fd or -1 */ static int -udp_sockport(struct sockaddr_storage* addr, socklen_t addrlen, int port, - int* inuse) +udp_sockport(struct sockaddr_storage* addr, socklen_t addrlen, int pfxlen, + int port, int* inuse, struct ub_randstate* rnd) { int fd, noproto; if(addr_is_ip6(addr, addrlen)) { - struct sockaddr_in6* sa = (struct sockaddr_in6*)addr; - sa->sin6_port = (in_port_t)htons((uint16_t)port); + int freebind = 0; + struct sockaddr_in6 sa = *(struct sockaddr_in6*)addr; + sa.sin6_port = (in_port_t)htons((uint16_t)port); + if(pfxlen != 0) { + freebind = 1; + sai6_putrandom(&sa, pfxlen, rnd); + } fd = create_udp_sock(AF_INET6, SOCK_DGRAM, - (struct sockaddr*)addr, addrlen, 1, inuse, &noproto, - 0, 0, 0, NULL, 0, 0); + (struct sockaddr*)&sa, addrlen, 1, inuse, &noproto, + 0, 0, 0, NULL, 0, freebind); } else { struct sockaddr_in* sa = (struct sockaddr_in*)addr; sa->sin_port = (in_port_t)htons((uint16_t)port); @@ -978,7 +1003,8 @@ select_ifport(struct outside_network* outnet, struct pending* pend, /* try to open new port, if fails, loop to try again */ log_assert(pif->inuse < pif->maxout); portno = pif->avail_ports[my_port - pif->inuse]; - fd = udp_sockport(&pif->addr, pif->addrlen, portno, &inuse); + fd = udp_sockport(&pif->addr, pif->addrlen, pif->pfxlen, + portno, &inuse, outnet->rnd); if(fd == -1 && !inuse) { /* nonrecoverable error making socket */ return 0; diff --git a/services/outside_network.h b/services/outside_network.h index 9a3270ee1..d6a448c0b 100644 --- a/services/outside_network.h +++ b/services/outside_network.h @@ -165,6 +165,10 @@ struct port_if { /** length of addr field */ socklen_t addrlen; + /** prefix length of network address (in bits), for randomisation. + * if 0, no randomisation. */ + int pfxlen; + /** the available ports array. These are unused. * Only the first total-inuse part is filled. */ int* avail_ports; diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c index 5b763c3a8..b4536a173 100644 --- a/smallapp/unbound-checkconf.c +++ b/smallapp/unbound-checkconf.c @@ -161,6 +161,7 @@ warn_hosts(const char* typ, struct config_stub* list) static void interfacechecks(struct config_file* cfg) { + int d; struct sockaddr_storage a; socklen_t alen; int i, j; @@ -177,8 +178,8 @@ interfacechecks(struct config_file* cfg) } } for(i=0; inum_out_ifs; i++) { - if(!ipstrtoaddr(cfg->out_ifs[i], UNBOUND_DNS_PORT, - &a, &alen)) { + if(!ipstrtoaddr(cfg->out_ifs[i], UNBOUND_DNS_PORT, &a, &alen) && + !netblockstrtoaddr(cfg->out_ifs[i], UNBOUND_DNS_PORT, &a, &alen, &d)) { fatal_exit("cannot parse outgoing-interface " "specified as '%s'", cfg->out_ifs[i]); }