aboutsummaryrefslogtreecommitdiffstats
path: root/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c')
-rw-r--r--src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c196
1 files changed, 170 insertions, 26 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..0e57d3ec3 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(addr.ptr))
+ { /* link-local, according to RFC 4291, 2.5.3 */
+ return 2;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(addr.ptr))
+ {
+ return 2;
+ }
+ if (IN6_IS_ADDR_SITELOCAL(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 */
@@ -1580,7 +1722,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 +1741,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 ||
@@ -2294,6 +2436,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),
);