ip6/nd6: route using on-link prefixes, not addresses

As laid out in RFC 5942, the assumption that a dynamically assigned
(SLAAC/DHCPv6) address implies an on-link subnet, is wrong. lwIP does
currently make that assumption, routing packets according to local
address subnets rather than the on-link prefix list. The result is
that packets may not make it to their destination due to incorrect
routing decisions.

This patch changes the routing algorithms to be (more) compliant with
RFC 5942, by implementing the following new routing policies:

- all routing decisions check the on-link prefix list first, and
  select a default router for off-link routing only if there is no
  matching entry in the on-link prefix list;
- dynamically assigned addresses (from address autoconfiguration) are
  considered /128 assignments, and thus, no routing decisions are taken
  based on matches against their (/64) subnet anymore;
- more generally, all addresses that have a lifetime are considered
  dynamically assigned and thus of size /128, which is the required
  behavior for externally implemented SLAAC clients and DHCPv6;
- statically assigned (i.e., manually configured) addresses are still
  considered /64 assignments, and thus, their associated subnet is
  considered for routing decisions, in order to behave as generally
  expected by end users and to retain backward compatibility;
- the link-local address in IPv6 address slot #0 is considered static
  and thus has no lifetime and an implied /64 subnet, although link-
  local routing is currently always handled separately anyway.

IPv6 source address selection is kept as is, as the subnet tests in
the algorithm serve as poor man's longest-common-prefix equivalent
there (RFC 6724 Sec. 5, Rule 8).
This commit is contained in:
David van Moolenbroek 2016-12-30 16:26:49 +00:00 committed by Dirk Ziegelmeier
parent 22c2fd1b58
commit 08de0e9617
3 changed files with 40 additions and 7 deletions

View File

@ -68,7 +68,7 @@
* this is a tricky case because with multiple netifs, link-local addresses only have
* meaning within a particular subnet/link.
* 3) tries to match the destination subnet to a configured address
* 4) tries to find a router
* 4) tries to find a router-announced route
* 5) tries to match the source address to the netif
* 6) returns the default netif, if configured
*
@ -130,20 +130,27 @@ ip6_route(const ip6_addr_t *src, const ip6_addr_t *dest)
}
#endif
/* See if the destination subnet matches a configured address. */
/* See if the destination subnet matches a configured address. In accordance
* with RFC 5942, dynamically configured addresses do not have an implied
* local subnet, and thus should be considered /128 assignments. However, as
* such, the destination address may still match a local address, and so we
* still need to check for exact matches here. By (lwIP) policy, statically
* configured addresses do always have an implied local /64 subnet. */
for (netif = netif_list; netif != NULL; netif = netif->next) {
if (!netif_is_up(netif) || !netif_is_link_up(netif)) {
continue;
}
for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
ip6_addr_netcmp(dest, netif_ip6_addr(netif, i)) &&
(netif_ip6_addr_isstatic(netif, i) ||
ip6_addr_nethostcmp(dest, netif_ip6_addr(netif, i)))) {
return netif;
}
}
}
/* Get the netif for a suitable router. */
/* Get the netif for a suitable router-announced route. */
netif = nd6_find_route(dest);
if (netif != NULL) {
return netif;

View File

@ -1484,7 +1484,8 @@ nd6_clear_destination_cache(void)
}
/**
* Determine whether an address matches an on-link prefix.
* Determine whether an address matches an on-link prefix or the subnet of a
* statically assigned address.
*
* @param ip6addr the IPv6 address to match
* @return 1 if the address is on-link, 0 otherwise
@ -1493,6 +1494,8 @@ static s8_t
nd6_is_prefix_in_netif(const ip6_addr_t *ip6addr, struct netif *netif)
{
s8_t i;
/* Check to see if the address matches an on-link prefix. */
for (i = 0; i < LWIP_ND6_NUM_PREFIXES; i++) {
if ((prefix_list[i].netif == netif) &&
(prefix_list[i].invalidation_timer > 0) &&
@ -1500,9 +1503,13 @@ nd6_is_prefix_in_netif(const ip6_addr_t *ip6addr, struct netif *netif)
return 1;
}
}
/* Check to see if address prefix matches a (manually?) configured address. */
/* Check to see if address prefix matches a manually configured (= static)
* address. Static addresses have an implied /64 subnet assignment. Dynamic
* addresses (from autoconfiguration) have no implied subnet assignment, and
* are thus effectively /128 assignments. See RFC 5942 for more on this. */
for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
netif_ip6_addr_isstatic(netif, i) &&
ip6_addr_netcmp(ip6addr, netif_ip6_addr(netif, i))) {
return 1;
}
@ -1586,7 +1593,8 @@ nd6_select_router(const ip6_addr_t *ip6addr, struct netif *netif)
}
/**
* Find a router-announced route to the given destination.
* Find a router-announced route to the given destination. This route may be
* based on an on-link prefix or a default router.
*
* If a suitable route is found, the returned netif is guaranteed to be in a
* suitable state (up, link up) to be used for packet transmission.
@ -1597,8 +1605,22 @@ nd6_select_router(const ip6_addr_t *ip6addr, struct netif *netif)
struct netif *
nd6_find_route(const ip6_addr_t *ip6addr)
{
struct netif *netif;
s8_t i;
/* @todo decide if it makes sense to check the destination cache first */
/* Check if there is a matching on-link prefix. There may be multiple
* matches. Pick the first one that is associated with a suitable netif. */
for (i = 0; i < LWIP_ND6_NUM_PREFIXES; ++i) {
netif = prefix_list[i].netif;
if ((netif != NULL) && ip6_addr_netcmp(&prefix_list[i].prefix, ip6addr) &&
netif_is_up(netif) && netif_is_link_up(netif)) {
return netif;
}
}
/* No on-link prefix match. Find a router that can forward the packet. */
i = nd6_select_router(ip6addr, NULL);
if (i >= 0) {
LWIP_ASSERT("selected router must have a neighbor entry",

View File

@ -133,6 +133,10 @@ typedef struct ip6_addr ip6_addr_t;
#define ip6_addr_netcmp(addr1, addr2) (((addr1)->addr[0] == (addr2)->addr[0]) && \
((addr1)->addr[1] == (addr2)->addr[1]))
/* Exact-host comparison *after* ip6_addr_netcmp() succeeded, for efficiency. */
#define ip6_addr_nethostcmp(addr1, addr2) (((addr1)->addr[2] == (addr2)->addr[2]) && \
((addr1)->addr[3] == (addr2)->addr[3]))
#define ip6_addr_cmp(addr1, addr2) (((addr1)->addr[0] == (addr2)->addr[0]) && \
((addr1)->addr[1] == (addr2)->addr[1]) && \
((addr1)->addr[2] == (addr2)->addr[2]) && \