lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Wed,  4 May 2016 20:33:29 -0700
From:	David Ahern <dsa@...ulusnetworks.com>
To:	netdev@...r.kernel.org
Cc:	David Ahern <dsa@...ulusnetworks.com>
Subject: [PATCH net-next 12/13] net: vrf: Implement get_saddr for IPv6

Right now source address selection is all screwed up for a number of
use cases. It does not properly take into account VRF centric addresses
or even valid routes for a VRF. Fix by implementating a get_saddr method
similar to what was done for IPv4. The get_saddr6 method does a full
lookup which means pulling a route from the VRF FIB table. Lookup
failures (eg., unreachable) then cause the source address selection
to fail which gets propagated back to the caller.

Since ipv6_dev_get_saddr is already exported move ip6_route_get_saddr to
the header as an inline since it only checks for a preferred source
address prior to calling ipv6_dev_get_saddr.

Signed-off-by: David Ahern <dsa@...ulusnetworks.com>
---
 drivers/net/vrf.c       | 86 +++++++++++++++++++++++++++++++++++++++----------
 include/net/ip6_route.h | 21 ++++++++++--
 include/net/l3mdev.h    | 11 +++++++
 net/ipv6/ip6_output.c   | 12 +++++--
 net/ipv6/route.c        | 17 ----------
 net/l3mdev/l3mdev.c     | 25 ++++++++++++++
 6 files changed, 133 insertions(+), 39 deletions(-)

diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index fb2d0b2052ea..d83d903dc674 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -774,20 +774,11 @@ static bool ipv6_ndisc_frame(const struct sk_buff *skb)
 	return rc;
 }
 
-static void vrf_ip6_input_dst(struct sk_buff *skb, struct net_device *vrf_dev,
-			      int ifindex)
+static struct rt6_info *vrf_ip6_route_lookup(struct net_device *dev,
+					     struct flowi6 *fl6, int ifindex)
 {
-	const struct ipv6hdr *iph = ipv6_hdr(skb);
-	struct flowi6 fl6 = {
-		.daddr		= iph->daddr,
-		.saddr		= iph->saddr,
-		.flowlabel	= ip6_flowinfo(iph),
-		.flowi6_mark	= skb->mark,
-		.flowi6_proto	= iph->nexthdr,
-		.flowi6_iif	= ifindex,
-	};
-	struct net_vrf *vrf = netdev_priv(vrf_dev);
-	struct net *net = dev_net(vrf_dev);
+	struct net_vrf *vrf = netdev_priv(dev);
+	struct net *net = dev_net(dev);
 	struct fib6_table *table = NULL;
 	struct rt6_info *rt6;
 
@@ -801,14 +792,36 @@ static void vrf_ip6_input_dst(struct sk_buff *skb, struct net_device *vrf_dev,
 	rcu_read_unlock();
 
 	if (!table)
-		return;
+		return NULL;
 
-	rt6 = ip6_pol_route(net, table, ifindex, &fl6,
-			    RT6_LOOKUP_F_HAS_SADDR | RT6_LOOKUP_F_IFACE);
+	return ip6_pol_route(net, table, ifindex, fl6,
+			     RT6_LOOKUP_F_HAS_SADDR | RT6_LOOKUP_F_IFACE);
+}
 
-	if (unlikely(&rt6->dst == &net->ipv6.ip6_null_entry->dst))
+static void vrf_ip6_input_dst(struct sk_buff *skb, struct net_device *dev,
+			      int ifindex)
+{
+	const struct ipv6hdr *iph = ipv6_hdr(skb);
+	struct flowi6 fl6 = {
+		.daddr		= iph->daddr,
+		.saddr		= iph->saddr,
+		.flowlabel	= ip6_flowinfo(iph),
+		.flowi6_mark	= skb->mark,
+		.flowi6_proto	= iph->nexthdr,
+		.flowi6_iif	= ifindex,
+	};
+	struct net *net = dev_net(dev);
+	struct rt6_info *rt6;
+
+	rt6 = vrf_ip6_route_lookup(dev, &fl6, ifindex);
+	if (unlikely(!rt6))
 		return;
 
+	if (unlikely(&rt6->dst == &net->ipv6.ip6_null_entry->dst)) {
+		dst_release(&rt6->dst);
+		return;
+	}
+
 	skb_dst_set(skb, &rt6->dst);
 }
 
@@ -836,6 +849,44 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
 	return skb;
 }
 
+/* called under rcu_read_lock */
+static int vrf_get_saddr6(struct net_device *dev, const struct sock *sk,
+			  struct flowi6 *fl6)
+{
+	struct net *net = dev_net(dev);
+	struct dst_entry *dst;
+	struct rt6_info *rt;
+	int err;
+
+	if (rt6_need_strict(&fl6->daddr)) {
+		rt = vrf_ip6_route_lookup(dev, fl6, fl6->flowi6_oif);
+		if (unlikely(!rt))
+			return 0;
+
+		dst = &rt->dst;
+	} else {
+		__u8 flags = fl6->flowi6_flags;
+
+		fl6->flowi6_flags |= FLOWI_FLAG_L3MDEV_SRC;
+		fl6->flowi6_flags |= FLOWI_FLAG_SKIP_NH_OIF;
+
+		dst = ip6_route_output(net, sk, fl6);
+		rt = (struct rt6_info *)dst;
+
+		fl6->flowi6_flags = flags;
+	}
+
+	err = dst->error;
+	if (!err) {
+		err = ip6_route_get_saddr(net, rt, &fl6->daddr,
+					  sk ? inet6_sk(sk)->srcprefs : 0,
+					  &fl6->saddr);
+	}
+
+	dst_release(dst);
+
+	return err;
+}
 #else
 static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
 				   struct sk_buff *skb)
@@ -947,6 +998,7 @@ static const struct l3mdev_ops vrf_l3mdev_ops = {
 	.l3mdev_l3_rcv		= vrf_l3_rcv,
 #if IS_ENABLED(CONFIG_IPV6)
 	.l3mdev_get_rt6_dst	= vrf_get_rt6_dst,
+	.l3mdev_get_saddr6	= vrf_get_saddr6,
 #endif
 };
 
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index f73a65e97597..6886deb45679 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -18,6 +18,7 @@ struct route_info {
 	__u8			prefix[0];	/* 0,8 or 16 */
 };
 
+#include <net/addrconf.h>
 #include <net/flow.h>
 #include <net/ip6_fib.h>
 #include <net/sock.h>
@@ -89,9 +90,23 @@ int ip6_route_add(struct fib6_config *cfg);
 int ip6_ins_rt(struct rt6_info *);
 int ip6_del_rt(struct rt6_info *);
 
-int ip6_route_get_saddr(struct net *net, struct rt6_info *rt,
-			const struct in6_addr *daddr, unsigned int prefs,
-			struct in6_addr *saddr);
+static inline int ip6_route_get_saddr(struct net *net, struct rt6_info *rt,
+				      const struct in6_addr *daddr,
+				      unsigned int prefs,
+				      struct in6_addr *saddr)
+{
+	struct inet6_dev *idev =
+			rt ? ip6_dst_idev((struct dst_entry *)rt) : NULL;
+	int err = 0;
+
+	if (rt && rt->rt6i_prefsrc.plen)
+		*saddr = rt->rt6i_prefsrc.addr;
+	else
+		err = ipv6_dev_get_saddr(net, idev ? idev->dev : NULL,
+					 daddr, prefs, saddr);
+
+	return err;
+}
 
 struct rt6_info *rt6_lookup(struct net *net, const struct in6_addr *daddr,
 			    const struct in6_addr *saddr, int oif, int flags);
diff --git a/include/net/l3mdev.h b/include/net/l3mdev.h
index d575185600a5..6ba0a206db45 100644
--- a/include/net/l3mdev.h
+++ b/include/net/l3mdev.h
@@ -38,6 +38,9 @@ struct l3mdev_ops {
 	struct dst_entry * (*l3mdev_get_rt6_dst)(const struct net_device *dev,
 						 struct flowi6 *fl6,
 						 int flags);
+	int		   (*l3mdev_get_saddr6)(struct net_device *dev,
+						const struct sock *sk,
+						struct flowi6 *fl6);
 };
 
 #ifdef CONFIG_NET_L3_MASTER_DEV
@@ -137,6 +140,8 @@ int l3mdev_get_saddr(struct net *net, int ifindex, struct flowi4 *fl4);
 
 struct dst_entry *l3mdev_get_rt6_dst(struct net *net, struct flowi6 *fl6,
 				     int flags);
+int l3mdev_get_saddr6(struct net *net, const struct sock *sk,
+		      struct flowi6 *fl6);
 
 static inline
 struct sk_buff *l3mdev_l3_rcv(struct sk_buff *skb, u16 proto)
@@ -229,6 +234,12 @@ struct dst_entry *l3mdev_get_rt6_dst(struct net *net, struct flowi6 *fl6,
 	return NULL;
 }
 
+static inline int l3mdev_get_saddr6(struct net *net, const struct sock *sk,
+				    struct flowi6 *fl6)
+{
+	return 0;
+}
+
 static inline
 struct sk_buff *l3mdev_ip_rcv(struct sk_buff *skb)
 {
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index cbf127ae7c67..cfd01782a621 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -910,6 +910,13 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
 	int err;
 	int flags = 0;
 
+	if (ipv6_addr_any(&fl6->saddr) && fl6->flowi6_oif &&
+	    (!*dst || !(*dst)->error)) {
+		err = l3mdev_get_saddr6(net, sk, fl6);
+		if (err)
+			goto out_err;
+	}
+
 	/* The correct way to handle this would be to do
 	 * ip6_route_get_saddr, and then ip6_route_output; however,
 	 * the route-specific preferred source forces the
@@ -999,10 +1006,11 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
 	return 0;
 
 out_err_release:
-	if (err == -ENETUNREACH)
-		IP6_INC_STATS(net, NULL, IPSTATS_MIB_OUTNOROUTES);
 	dst_release(*dst);
 	*dst = NULL;
+out_err:
+	if (err == -ENETUNREACH)
+		IP6_INC_STATS(net, NULL, IPSTATS_MIB_OUTNOROUTES);
 	return err;
 }
 
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index a87e66d2284f..67ec5594be9c 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -2584,23 +2584,6 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
 	return rt;
 }
 
-int ip6_route_get_saddr(struct net *net,
-			struct rt6_info *rt,
-			const struct in6_addr *daddr,
-			unsigned int prefs,
-			struct in6_addr *saddr)
-{
-	struct inet6_dev *idev =
-		rt ? ip6_dst_idev((struct dst_entry *)rt) : NULL;
-	int err = 0;
-	if (rt && rt->rt6i_prefsrc.plen)
-		*saddr = rt->rt6i_prefsrc.addr;
-	else
-		err = ipv6_dev_get_saddr(net, idev ? idev->dev : NULL,
-					 daddr, prefs, saddr);
-	return err;
-}
-
 /* remove deleted ip from prefsrc entries */
 struct arg_dev_net_ip {
 	struct net_device *dev;
diff --git a/net/l3mdev/l3mdev.c b/net/l3mdev/l3mdev.c
index dceac272b8c4..3e08d3e27a8a 100644
--- a/net/l3mdev/l3mdev.c
+++ b/net/l3mdev/l3mdev.c
@@ -164,3 +164,28 @@ int l3mdev_get_saddr(struct net *net, int ifindex, struct flowi4 *fl4)
 	return rc;
 }
 EXPORT_SYMBOL_GPL(l3mdev_get_saddr);
+
+int l3mdev_get_saddr6(struct net *net, const struct sock *sk,
+		      struct flowi6 *fl6)
+{
+	struct net_device *dev;
+	int rc = 0;
+
+	if (fl6->flowi6_oif) {
+		rcu_read_lock();
+
+		dev = dev_get_by_index_rcu(net, fl6->flowi6_oif);
+		if (dev && netif_is_l3_slave(dev))
+			dev = netdev_master_upper_dev_get_rcu(dev);
+
+		if (dev && netif_is_l3_master(dev) &&
+		    dev->l3mdev_ops->l3mdev_get_saddr6) {
+			rc = dev->l3mdev_ops->l3mdev_get_saddr6(dev, sk, fl6);
+		}
+
+		rcu_read_unlock();
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(l3mdev_get_saddr6);
-- 
2.1.4

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ