diff options
author | Tobias Brunner <tobias@strongswan.org> | 2014-03-14 15:41:52 +0100 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2014-06-19 14:16:41 +0200 |
commit | 3bf98189d7d5b1207f746c86059d241cc025179a (patch) | |
tree | 53b0aa180fa3ed2c86d0c03bb1f7e36af6eae410 /src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c | |
parent | 6364219281371eb1cd942c97f07e859431bdcde2 (diff) | |
download | strongswan-3bf98189d7d5b1207f746c86059d241cc025179a.tar.bz2 strongswan-3bf98189d7d5b1207f746c86059d241cc025179a.tar.xz |
kernel-netlink: Follow RFC 6724 when selecting IPv6 source addresses
Instead of using the first address we find on an interface we should
consider properties like an address' scope or whether it is temporary
or public.
Fixes #543.
Diffstat (limited to 'src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c')
-rw-r--r-- | src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c | 196 |
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), ); |