Fix for using IPv6 addresses with RDMA:

IPv6 addresses has a scope ID which sometimes is stored in the
"sin6_scope_id" field of "struct sockaddr_in6" and sometimes as part
of the IPv6 address itself depending on the context. If the scope ID
is not in the expected location, the IPv6 address lookups in the
so-called GID table will fail. Some code factoring has been made to
achieve a clean exit of the "addr_resolve" function via a common
"done" label.

Sponsored by:	Mellanox Technologies
Submitted by:	Shani Michaeli <shanim@mellanox.com>
MFC after:	1 week
This commit is contained in:
Hans Petter Selasky 2016-04-21 16:33:42 +00:00
parent 892bfb4bb2
commit 150c88d471

View File

@ -109,6 +109,14 @@ int rdma_copy_addr(struct rdma_dev_addr *dev_addr, struct ifnet *dev,
} }
EXPORT_SYMBOL(rdma_copy_addr); EXPORT_SYMBOL(rdma_copy_addr);
#define SCOPE_ID_CACHE(_scope_id, _addr6) do { \
(_addr6)->sin6_addr.s6_addr[3] = (_scope_id); \
(_addr6)->sin6_scope_id = 0; } while (0)
#define SCOPE_ID_RESTORE(_scope_id, _addr6) do { \
(_addr6)->sin6_scope_id = (_scope_id); \
(_addr6)->sin6_addr.s6_addr[3] = 0; } while (0)
int rdma_translate_ip(struct sockaddr *addr, struct rdma_dev_addr *dev_addr, int rdma_translate_ip(struct sockaddr *addr, struct rdma_dev_addr *dev_addr,
u16 *vlan_id) u16 *vlan_id)
{ {
@ -144,12 +152,18 @@ int rdma_translate_ip(struct sockaddr *addr, struct rdma_dev_addr *dev_addr,
struct sockaddr_in6 *sin6; struct sockaddr_in6 *sin6;
struct ifaddr *ifa; struct ifaddr *ifa;
in_port_t port; in_port_t port;
uint32_t scope_id;
sin6 = (struct sockaddr_in6 *)addr; sin6 = (struct sockaddr_in6 *)addr;
port = sin6->sin6_port; port = sin6->sin6_port;
sin6->sin6_port = 0; sin6->sin6_port = 0;
scope_id = sin6->sin6_scope_id;
if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr))
SCOPE_ID_CACHE(scope_id, sin6);
ifa = ifa_ifwithaddr(addr); ifa = ifa_ifwithaddr(addr);
sin6->sin6_port = port; sin6->sin6_port = port;
if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr))
SCOPE_ID_RESTORE(scope_id, sin6);
if (ifa == NULL) { if (ifa == NULL) {
ret = -ENODEV; ret = -ENODEV;
break; break;
@ -161,6 +175,8 @@ int rdma_translate_ip(struct sockaddr *addr, struct rdma_dev_addr *dev_addr,
break; break;
} }
#endif #endif
default:
break;
} }
return ret; return ret;
} }
@ -203,7 +219,12 @@ static int addr_resolve(struct sockaddr *src_in,
struct ifaddr *ifa; struct ifaddr *ifa;
struct ifnet *ifp; struct ifnet *ifp;
struct rtentry *rte; struct rtentry *rte;
#if defined(INET) || defined(INET6)
in_port_t port; in_port_t port;
#endif
#ifdef INET6
uint32_t scope_id;
#endif
u_char edst[MAX_ADDR_LEN]; u_char edst[MAX_ADDR_LEN];
int multi; int multi;
int bcast; int bcast;
@ -219,6 +240,13 @@ static int addr_resolve(struct sockaddr *src_in,
sin6 = NULL; sin6 = NULL;
ifp = NULL; ifp = NULL;
rte = NULL; rte = NULL;
ifa = NULL;
ifp = NULL;
memset(edst, 0, sizeof(edst));
#ifdef INET6
scope_id = -1U;
#endif
switch (dst_in->sa_family) { switch (dst_in->sa_family) {
#ifdef INET #ifdef INET
case AF_INET: case AF_INET:
@ -236,6 +264,22 @@ static int addr_resolve(struct sockaddr *src_in,
port = sin->sin_port; port = sin->sin_port;
sin->sin_port = 0; sin->sin_port = 0;
memset(&sin->sin_zero, 0, sizeof(sin->sin_zero)); memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
/*
* If we have a source address to use look it
* up first and verify that it is a local
* interface:
*/
ifa = ifa_ifwithaddr(src_in);
sin->sin_port = port;
if (ifa == NULL) {
error = ENETUNREACH;
goto done;
}
ifp = ifa->ifa_ifp;
ifa_free(ifa);
if (bcast || multi)
goto mcast;
} }
break; break;
#endif #endif
@ -244,42 +288,55 @@ static int addr_resolve(struct sockaddr *src_in,
sin6 = (struct sockaddr_in6 *)dst_in; sin6 = (struct sockaddr_in6 *)dst_in;
if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))
multi = 1; multi = 1;
if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) {
/*
* The IB address comparison fails if the
* scope ID is set and not part of the addr:
*/
scope_id = sin6->sin6_scope_id;
if (scope_id < 256)
SCOPE_ID_CACHE(scope_id, sin6);
}
sin6 = (struct sockaddr_in6 *)src_in; sin6 = (struct sockaddr_in6 *)src_in;
if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
port = sin6->sin6_port; port = sin6->sin6_port;
sin6->sin6_port = 0; sin6->sin6_port = 0;
} else if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) {
src_in = NULL; if (scope_id < 256)
SCOPE_ID_CACHE(scope_id, sin6);
}
/*
* If we have a source address to use look it
* up first and verify that it is a local
* interface:
*/
ifa = ifa_ifwithaddr(src_in);
sin6->sin6_port = port;
if (ifa == NULL) {
error = ENETUNREACH;
goto done;
}
ifp = ifa->ifa_ifp;
ifa_free(ifa);
if (bcast || multi)
goto mcast;
}
break; break;
#endif #endif
default: default:
return -EINVAL; error = EINVAL;
} goto done;
/*
* If we have a source address to use look it up first and verify
* that it is a local interface.
*/
if (sin->sin_addr.s_addr != INADDR_ANY) {
ifa = ifa_ifwithaddr(src_in);
if (sin)
sin->sin_port = port;
if (sin6)
sin6->sin6_port = port;
if (ifa == NULL)
return -ENETUNREACH;
ifp = ifa->ifa_ifp;
ifa_free(ifa);
if (bcast || multi)
goto mcast;
} }
/* /*
* Make sure the route exists and has a valid link. * Make sure the route exists and has a valid link.
*/ */
rte = rtalloc1(dst_in, 1, 0); rte = rtalloc1(dst_in, 1, 0);
if (rte == NULL || rte->rt_ifp == NULL || !RT_LINK_IS_UP(rte->rt_ifp)) { if (rte == NULL || rte->rt_ifp == NULL || !RT_LINK_IS_UP(rte->rt_ifp)) {
if (rte) if (rte)
RTFREE_LOCKED(rte); RTFREE_LOCKED(rte);
return -EHOSTUNREACH; error = EHOSTUNREACH;
goto done;
} }
if (rte->rt_flags & RTF_GATEWAY) if (rte->rt_flags & RTF_GATEWAY)
is_gw = 1; is_gw = 1;
@ -297,7 +354,8 @@ static int addr_resolve(struct sockaddr *src_in,
RTFREE_LOCKED(rte); RTFREE_LOCKED(rte);
} else if (ifp && ifp != rte->rt_ifp) { } else if (ifp && ifp != rte->rt_ifp) {
RTFREE_LOCKED(rte); RTFREE_LOCKED(rte);
return -ENETUNREACH; error = ENETUNREACH;
goto done;
} else { } else {
if (ifp == NULL) { if (ifp == NULL) {
ifp = rte->rt_ifp; ifp = rte->rt_ifp;
@ -305,27 +363,29 @@ static int addr_resolve(struct sockaddr *src_in,
} }
RT_UNLOCK(rte); RT_UNLOCK(rte);
} }
#if defined(INET) || defined(INET6)
mcast: mcast:
if (bcast) #endif
return rdma_copy_addr(addr, ifp, ifp->if_broadcastaddr); if (bcast) {
if (multi) { memcpy(edst, ifp->if_broadcastaddr, ifp->if_addrlen);
goto done;
} else if (multi) {
struct sockaddr *llsa; struct sockaddr *llsa;
struct sockaddr_dl sdl; struct sockaddr_dl sdl;
sdl.sdl_len = sizeof(sdl); sdl.sdl_len = sizeof(sdl);
llsa = (struct sockaddr *)&sdl; llsa = (struct sockaddr *)&sdl;
if (ifp->if_resolvemulti == NULL) if (ifp->if_resolvemulti == NULL) {
return -EOPNOTSUPP; error = EOPNOTSUPP;
goto done;
}
error = ifp->if_resolvemulti(ifp, &llsa, dst_in); error = ifp->if_resolvemulti(ifp, &llsa, dst_in);
if (error) if (error == 0) {
return -error; memcpy(edst, LLADDR((struct sockaddr_dl *)llsa),
error = rdma_copy_addr(addr, ifp, ifp->if_addrlen);
LLADDR((struct sockaddr_dl *)llsa)); }
if (error == 0) goto done;
memcpy(src_in, ifa->ifa_addr, ip_addr_size(ifa->ifa_addr));
return error;
} }
/* /*
* Resolve the link local address. * Resolve the link local address.
@ -347,12 +407,21 @@ mcast:
break; break;
} }
RTFREE(rte); RTFREE(rte);
if (error == 0) { done:
if (error == 0)
error = -rdma_copy_addr(addr, ifp, edst);
if (error == 0)
memcpy(src_in, ifa->ifa_addr, ip_addr_size(ifa->ifa_addr)); memcpy(src_in, ifa->ifa_addr, ip_addr_size(ifa->ifa_addr));
return rdma_copy_addr(addr, ifp, edst); #ifdef INET6
if (scope_id < 256) {
sin6 = (struct sockaddr_in6 *)src_in;
SCOPE_ID_RESTORE(scope_id, sin6);
sin6 = (struct sockaddr_in6 *)dst_in;
SCOPE_ID_RESTORE(scope_id, sin6);
} }
#endif
if (error == EWOULDBLOCK) if (error == EWOULDBLOCK)
return -ENODATA; error = ENODATA;
return -error; return -error;
} }