diff options
Diffstat (limited to 'src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c')
| -rw-r--r-- | src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c | 264 | 
1 files changed, 217 insertions, 47 deletions
| diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c index 3cf317634..82b637d1e 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c @@ -1,5 +1,5 @@  /* - * Copyright (C) 2008-2013 Tobias Brunner + * Copyright (C) 2008-2014 Tobias Brunner   * Copyright (C) 2005-2008 Martin Willi   * Hochschule fuer Technik Rapperswil   * @@ -88,6 +88,9 @@ struct addr_entry_t {  	/** the ip address */  	host_t *ip; +	/** address flags */ +	u_char flags; +  	/** scope of the address */  	u_char scope; @@ -467,6 +470,11 @@ struct private_kernel_netlink_net_t {  	bool rta_prefsrc_for_ipv6;  	/** +	 * whether to prefer temporary IPv6 addresses over public ones +	 */ +	bool prefer_temporary_addrs; + +	/**  	 * list with routing tables to be excluded from route lookup  	 */  	linked_list_t *rt_exclude; @@ -653,20 +661,156 @@ static void addr_map_entry_remove(hashtable_t *map, addr_entry_t *addr,  }  /** - * get the first non-virtual ip address on the given interface. - * if a candidate address is given, we first search for that address and if not + * Determine the type or scope of the given unicast IP address.  This is not + * the same thing returned in rtm_scope/ifa_scope. + * + * We use return values as defined in RFC 6724 (referring to RFC 4291). + */ +static u_char get_scope(host_t *ip) +{ +	chunk_t addr; + +	addr = ip->get_address(ip); +	switch (addr.len) +	{ +		case 4: +			/* we use the mapping defined in RFC 6724, 3.2 */ +			if (addr.ptr[0] == 127) +			{	/* link-local, same as the IPv6 loopback address */ +				return 2; +			} +			if (addr.ptr[0] == 169 && addr.ptr[1] == 254) +			{	/* link-local */ +				return 2; +			} +			break; +		case 16: +			if (IN6_IS_ADDR_LOOPBACK((struct in6_addr*)addr.ptr)) +			{	/* link-local, according to RFC 4291, 2.5.3 */ +				return 2; +			} +			if (IN6_IS_ADDR_LINKLOCAL((struct in6_addr*)addr.ptr)) +			{ +				return 2; +			} +			if (IN6_IS_ADDR_SITELOCAL((struct in6_addr*)addr.ptr)) +			{	/* deprecated, according to RFC 4291, 2.5.7 */ +				return 5; +			} +			break; +		default: +			break; +	} +	/* global */ +	return 14; +} + +/** + * Returns the length of the common prefix in bits up to the length of a's + * prefix, defined by RFC 6724 as the portion of the address not including the + * interface ID, which is 64-bit for most unicast addresses (see RFC 4291). + */ +static u_char common_prefix(host_t *a, host_t *b) +{ +	chunk_t aa, ba; +	u_char byte, bits = 0, match; + +	aa = a->get_address(a); +	ba = b->get_address(b); +	for (byte = 0; byte < 8; byte++) +	{ +		if (aa.ptr[byte] != ba.ptr[byte]) +		{ +			match = aa.ptr[byte] ^ ba.ptr[byte]; +			for (bits = 8; match; match >>= 1) +			{ +				bits--; +			} +			break; +		} +	} +	return byte * 8 + bits; +} + +/** + * Compare two IP addresses and return TRUE if the second address is the better + * choice of the two to reach the destination. + * For IPv6 we approximately follow RFC 6724. + */ +static bool is_address_better(private_kernel_netlink_net_t *this, +							  addr_entry_t *a, addr_entry_t *b, host_t *d) +{ +	u_char sa, sb, sd, pa, pb; + +	/* rule 2: prefer appropriate scope */ +	if (d) +	{ +		sa = get_scope(a->ip); +		sb = get_scope(b->ip); +		sd = get_scope(d); +		if (sa < sb) +		{ +			return sa < sd; +		} +		else if (sb < sa) +		{ +			return sb >= sd; +		} +	} +	if (a->ip->get_family(a->ip) == AF_INET) +	{	/* stop here for IPv4, default to addresses found earlier */ +		return FALSE; +	} +	/* rule 3: avoid deprecated addresses (RFC 4862) */ +	if ((a->flags & IFA_F_DEPRECATED) != (b->flags & IFA_F_DEPRECATED)) +	{ +		return a->flags & IFA_F_DEPRECATED; +	} +	/* rule 4 is not applicable as we don't know if an address is a home or +	 * care-of addresses. +	 * rule 5 does not apply as we only compare addresses from one interface +	 * rule 6 requires a policy table (optionally configurable) to match +	 * configurable labels +	 */ +	/* rule 7: prefer temporary addresses (WE REVERSE THIS BY DEFAULT!) */ +	if ((a->flags & IFA_F_TEMPORARY) != (b->flags & IFA_F_TEMPORARY)) +	{ +		if (this->prefer_temporary_addrs) +		{ +			return b->flags & IFA_F_TEMPORARY; +		} +		return a->flags & IFA_F_TEMPORARY; +	} +	/* rule 8: use longest matching prefix */ +	if (d) +	{ +		pa = common_prefix(a->ip, d); +		pb = common_prefix(b->ip, d); +		if (pa != pb) +		{ +			return pb > pa; +		} +	} +	/* default to addresses found earlier */ +	return FALSE; +} + +/** + * Get a non-virtual IP address on the given interface. + * + * If a candidate address is given, we first search for that address and if not   * found return the address as above. - * returned host is a clone, has to be freed by caller. + * Returned host is a clone, has to be freed by caller.   * - * this->lock must be held when calling this function + * this->lock must be held when calling this function.   */  static host_t *get_interface_address(private_kernel_netlink_net_t *this, -									 int ifindex, int family, host_t *candidate) +									 int ifindex, int family, host_t *dest, +									 host_t *candidate)  {  	iface_entry_t *iface;  	enumerator_t *addrs; -	addr_entry_t *addr; -	host_t *ip = NULL; +	addr_entry_t *addr, *best = NULL;  	if (this->ifaces->find_first(this->ifaces, (void*)iface_entry_by_index,  								 (void**)&iface, &ifindex) == SUCCESS) @@ -676,29 +820,25 @@ static host_t *get_interface_address(private_kernel_netlink_net_t *this,  			addrs = iface->addrs->create_enumerator(iface->addrs);  			while (addrs->enumerate(addrs, &addr))  			{ -				if (addr->refcount) -				{	/* ignore virtual IP addresses */ +				if (addr->refcount || +					addr->ip->get_family(addr->ip) != family) +				{	/* ignore virtual IP addresses and ensure family matches */  					continue;  				} -				if (addr->ip->get_family(addr->ip) == family) +				if (candidate && candidate->ip_equals(candidate, addr->ip)) +				{	/* stop if we find the candidate */ +					best = addr; +					break; +				} +				else if (!best || is_address_better(this, best, addr, dest))  				{ -					if (!candidate || candidate->ip_equals(candidate, addr->ip)) -					{	/* stop at the first address if we don't search for a -						 * candidate or if the candidate matches */ -						ip = addr->ip; -						break; -					} -					else if (!ip) -					{	/* store the first address as fallback if candidate is -						 * not found */ -						ip = addr->ip; -					} +					best = addr;  				}  			}  			addrs->destroy(addrs);  		}  	} -	return ip ? ip->clone(ip) : NULL; +	return best ? best->ip->clone(best->ip) : NULL;  }  /** @@ -989,6 +1129,7 @@ static void process_addr(private_kernel_netlink_net_t *this,  				route_ifname = strdup(iface->ifname);  				INIT(addr,  					.ip = host->clone(host), +					.flags = msg->ifa_flags,  					.scope = msg->ifa_scope,  				);  				iface->addrs->insert_last(iface->addrs, addr); @@ -1076,7 +1217,8 @@ static void process_route(private_kernel_netlink_net_t *this, struct nlmsghdr *h  	}  	if (!host && rta_oif)  	{ -		host = get_interface_address(this, rta_oif, msg->rtm_family, NULL); +		host = get_interface_address(this, rta_oif, msg->rtm_family, +									 NULL, NULL);  	}  	if (!host || is_known_vip(this, host))  	{	/* ignore routes added for virtual IPs */ @@ -1318,9 +1460,10 @@ static int get_interface_index(private_kernel_netlink_net_t *this, char* name)  }  /** - * check if an address (chunk) addr is in subnet (net with net_len net bits) + * check if an address or net (addr with prefix net bits) is in + * subnet (net with net_len net bits)   */ -static bool addr_in_subnet(chunk_t addr, chunk_t net, int net_len) +static bool addr_in_subnet(chunk_t addr, int prefix, chunk_t net, int net_len)  {  	static const u_char mask[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };  	int byte = 0; @@ -1329,7 +1472,7 @@ static bool addr_in_subnet(chunk_t addr, chunk_t net, int net_len)  	{	/* any address matches a /0 network */  		return TRUE;  	} -	if (addr.len != net.len || net_len > 8 * net.len ) +	if (addr.len != net.len || net_len > 8 * net.len || prefix < net_len)  	{  		return FALSE;  	} @@ -1445,7 +1588,8 @@ static rt_entry_t *parse_route(struct nlmsghdr *hdr, rt_entry_t *route)   * Get a route: If "nexthop", the nexthop is returned. source addr otherwise.   */  static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest, -						 bool nexthop, host_t *candidate, u_int recursion) +						 int prefix, bool nexthop, host_t *candidate, +						 u_int recursion)  {  	netlink_buf_t request;  	struct nlmsghdr *hdr, *out, *current; @@ -1456,18 +1600,25 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  	rt_entry_t *route = NULL, *best = NULL;  	enumerator_t *enumerator;  	host_t *addr = NULL; +	bool match_net; +	int family;  	if (recursion > MAX_ROUTE_RECURSION)  	{  		return NULL;  	} +	chunk = dest->get_address(dest); +	len = chunk.len * 8; +	prefix = prefix < 0 ? len : min(prefix, len); +	match_net = prefix != len;  	memset(&request, 0, sizeof(request)); +	family = dest->get_family(dest);  	hdr = (struct nlmsghdr*)request;  	hdr->nlmsg_flags = NLM_F_REQUEST; -	if (dest->get_family(dest) == AF_INET || this->rta_prefsrc_for_ipv6 || -		this->routing_table) +	if (family == AF_INET || this->rta_prefsrc_for_ipv6 || +		this->routing_table || match_net)  	{	/* kernels prior to 3.0 do not support RTA_PREFSRC for IPv6 routes.  		 * as we want to ignore routes with virtual IPs we cannot use DUMP  		 * if these routes are not installed in a separate table */ @@ -1477,19 +1628,22 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  	hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));  	msg = (struct rtmsg*)NLMSG_DATA(hdr); -	msg->rtm_family = dest->get_family(dest); +	msg->rtm_family = family;  	if (candidate)  	{  		chunk = candidate->get_address(candidate);  		netlink_add_attribute(hdr, RTA_PREFSRC, chunk, sizeof(request));  	} -	chunk = dest->get_address(dest); -	netlink_add_attribute(hdr, RTA_DST, chunk, sizeof(request)); +	if (!match_net) +	{ +		chunk = dest->get_address(dest); +		netlink_add_attribute(hdr, RTA_DST, chunk, sizeof(request)); +	}  	if (this->socket->send(this->socket, hdr, &out, &len) != SUCCESS)  	{ -		DBG2(DBG_KNL, "getting %s to reach %H failed", -			 nexthop ? "nexthop" : "address", dest); +		DBG2(DBG_KNL, "getting %s to reach %H/%d failed", +			 nexthop ? "nexthop" : "address", dest, prefix);  		return NULL;  	}  	routes = linked_list_create(); @@ -1524,7 +1678,7 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  				{	/* interface is down */  					continue;  				} -				if (!addr_in_subnet(chunk, route->dst, route->dst_len)) +				if (!addr_in_subnet(chunk, prefix, route->dst, route->dst_len))  				{	/* route destination does not contain dest */  					continue;  				} @@ -1580,7 +1734,7 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  			else if (route->oif)  			{	/* no match yet, maybe it is assigned to the same interface */  				host_t *src = get_interface_address(this, route->oif, -													msg->rtm_family, candidate); +											msg->rtm_family, dest, candidate);  				if (src && src->ip_equals(src, candidate))  				{  					route->src_host->destroy(route->src_host); @@ -1599,7 +1753,7 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  		if (route->oif)  		{	/* no src, but an interface - get address from it */  			route->src_host = get_interface_address(this, route->oif, -													msg->rtm_family, candidate); +											msg->rtm_family, dest, candidate);  			if (route->src_host)  			{	/* we handle this address the same as the one above */  				if (!candidate || @@ -1619,7 +1773,7 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  			gtw = host_create_from_chunk(msg->rtm_family, route->gtw, 0);  			if (gtw && !gtw->ip_equals(gtw, dest))  			{ -				route->src_host = get_route(this, gtw, FALSE, candidate, +				route->src_host = get_route(this, gtw, -1, FALSE, candidate,  											recursion + 1);  			}  			DESTROY_IF(gtw); @@ -1643,7 +1797,10 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  		{  			addr = host_create_from_chunk(msg->rtm_family, best->gtw, 0);  		} -		addr = addr ?: dest->clone(dest); +		if (!addr && !match_net) +		{	/* fallback to destination address */ +			addr = dest->clone(dest); +		}  	}  	else  	{ @@ -1658,13 +1815,13 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  	if (addr)  	{ -		DBG2(DBG_KNL, "using %H as %s to reach %H", addr, -			 nexthop ? "nexthop" : "address", dest); +		DBG2(DBG_KNL, "using %H as %s to reach %H/%d", addr, +			 nexthop ? "nexthop" : "address", dest, prefix);  	}  	else if (!recursion)  	{ -		DBG2(DBG_KNL, "no %s found to reach %H", -			 nexthop ? "nexthop" : "address", dest); +		DBG2(DBG_KNL, "no %s found to reach %H/%d", +			 nexthop ? "nexthop" : "address", dest, prefix);  	}  	return addr;  } @@ -1672,13 +1829,13 @@ static host_t *get_route(private_kernel_netlink_net_t *this, host_t *dest,  METHOD(kernel_net_t, get_source_addr, host_t*,  	private_kernel_netlink_net_t *this, host_t *dest, host_t *src)  { -	return get_route(this, dest, FALSE, src, 0); +	return get_route(this, dest, -1, FALSE, src, 0);  }  METHOD(kernel_net_t, get_nexthop, host_t*, -	private_kernel_netlink_net_t *this, host_t *dest, host_t *src) +	private_kernel_netlink_net_t *this, host_t *dest, int prefix, host_t *src)  { -	return get_route(this, dest, TRUE, src, 0); +	return get_route(this, dest, prefix, TRUE, src, 0);  }  /** @@ -1711,6 +1868,17 @@ static status_t manage_ipaddr(private_kernel_netlink_net_t *this, int nlmsg_type  	netlink_add_attribute(hdr, IFA_LOCAL, chunk, sizeof(request)); +	if (ip->get_family(ip) == AF_INET6 && this->rta_prefsrc_for_ipv6) +	{	/* if source routes are possible we let the virtual IP get deprecated +		 * immediately (but mark it as valid forever) so it gets only used if +		 * forced by our route, and not by the default IPv6 address selection */ +		struct ifa_cacheinfo cache = { +			.ifa_valid = 0xFFFFFFFF, +			.ifa_prefered = 0, +		}; +		netlink_add_attribute(hdr, IFA_CACHEINFO, chunk_from_thing(cache), +							  sizeof(request)); +	}  	return this->socket->send_ack(this->socket, hdr);  } @@ -2294,6 +2462,8 @@ kernel_netlink_net_t *kernel_netlink_net_create()  						"%s.install_virtual_ip", TRUE, lib->ns),  		.install_virtual_ip_on = lib->settings->get_str(lib->settings,  						"%s.install_virtual_ip_on", NULL, lib->ns), +		.prefer_temporary_addrs = lib->settings->get_bool(lib->settings, +						"%s.prefer_temporary_addrs", FALSE, lib->ns),  		.roam_events = lib->settings->get_bool(lib->settings,  						"%s.plugins.kernel-netlink.roam_events", TRUE, lib->ns),  	); | 
