[PATCH net-next v2 3/3] net: vrf: ipv6 support for local traffic to local addresses
From: David Ahern <hidden>
Date: 2016-06-07 03:51:13
Subsystem:
networking drivers, networking [general], networking [ipv4/ipv6], the rest, vrf · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
Add support for locally originated traffic to VRF-local IPv6 addresses.
Similar to IPv4 a local dst is set on the skb and the packet is
reinserted with a call to netif_rx. With this patch, ping, tcp and udp
packets to a local IPv6 address are successfully routed:
$ ip addr show dev eth1
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master red state UP group default qlen 1000
link/ether 02:e0:f9:1c:b9:74 brd ff:ff:ff:ff:ff:ff
inet 10.100.1.1/24 brd 10.100.1.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 2100:1::1/120 scope global
valid_lft forever preferred_lft forever
inet6 fe80::e0:f9ff:fe1c:b974/64 scope link
valid_lft forever preferred_lft forever
$ ping6 -c1 -I red 2100:1::1
ping6: Warning: source address might be selected on device other than red.
PING 2100:1::1(2100:1::1) from 2100:1::1 red: 56 data bytes
64 bytes from 2100:1::1: icmp_seq=1 ttl=64 time=0.098 ms
ip6_input is exported so the VRF driver can use it for the dst input
function. The dst_alloc function for IPv4 defaults to setting the input and
output functions; IPv6's does not. VRF does not need to duplicate the Rx path
so just export the ipv6 input function.
Signed-off-by: David Ahern <redacted>
---
v2
- add export of ip6_input
drivers/net/vrf.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++---
net/ipv6/ip6_input.c | 1 +
2 files changed, 86 insertions(+), 4 deletions(-)
diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index 203d3c78a6e5..1b214ea4619a 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c@@ -46,6 +46,7 @@ struct net_vrf { struct rtable __rcu *rth; struct rtable __rcu *rth_local; struct rt6_info __rcu *rt6; + struct rt6_info __rcu *rt6_local; u32 tb_id; };
@@ -157,6 +158,46 @@ static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb, goto err; skb_dst_drop(skb); + + /* if dst.dev is loopback or the VRF device again this is locally + * originated traffic destined to a local address. Short circuit + * to Rx path using our local dst + */ + if (dst->dev == net->loopback_dev || dst->dev == dev) { + struct net_vrf *vrf = netdev_priv(dev); + struct rt6_info *rt6_local; + + /* release looked up dst and use cached local dst */ + dst_release(dst); + + rcu_read_lock(); + + rt6_local = rcu_dereference(vrf->rt6_local); + if (unlikely(!rt6_local)) { + rcu_read_unlock(); + goto err; + } + + /* Ordering issue: cached local dst is created on newlink + * before the IPv6 initialization. Using the local dst + * requires rt6i_idev to be set so make sure it is. + */ + if (unlikely(!rt6_local->rt6i_idev)) { + rt6_local->rt6i_idev = in6_dev_get(dev); + if (!rt6_local->rt6i_idev) { + rcu_read_unlock(); + goto err; + } + } + + dst = &rt6_local->dst; + dst_hold(dst); + + rcu_read_unlock(); + + return vrf_local_xmit(skb, dev, &rt6_local->dst); + } + skb_dst_set(skb, dst); /* strip the ethernet header added for pass through VRF device */
@@ -336,27 +377,38 @@ static int vrf_output6(struct net *net, struct sock *sk, struct sk_buff *skb) static void vrf_rt6_release(struct net_vrf *vrf) { struct rt6_info *rt6 = rtnl_dereference(vrf->rt6); + struct rt6_info *rt6_local = rtnl_dereference(vrf->rt6_local); - rcu_assign_pointer(vrf->rt6, NULL); + RCU_INIT_POINTER(vrf->rt6, NULL); + RCU_INIT_POINTER(vrf->rt6_local, NULL); + synchronize_rcu(); if (rt6) dst_release(&rt6->dst); + + if (rt6_local) { + if (rt6_local->rt6i_idev) + in6_dev_put(rt6_local->rt6i_idev); + + dst_release(&rt6_local->dst); + } } static int vrf_rt6_create(struct net_device *dev) { + int flags = DST_HOST | DST_NOPOLICY | DST_NOXFRM | DST_NOCACHE; struct net_vrf *vrf = netdev_priv(dev); struct net *net = dev_net(dev); struct fib6_table *rt6i_table; - struct rt6_info *rt6; + struct rt6_info *rt6, *rt6_local; int rc = -ENOMEM; rt6i_table = fib6_new_table(net, vrf->tb_id); if (!rt6i_table) goto out; - rt6 = ip6_dst_alloc(net, dev, - DST_HOST | DST_NOPOLICY | DST_NOXFRM | DST_NOCACHE); + /* create a dst for routing packets out a VRF device */ + rt6 = ip6_dst_alloc(net, dev, flags); if (!rt6) goto out;
@@ -364,7 +416,25 @@ static int vrf_rt6_create(struct net_device *dev) rt6->rt6i_table = rt6i_table; rt6->dst.output = vrf_output6; + + /* create a dst for local routing - packets sent locally + * to local address via the VRF device as a loopback + */ + rt6_local = ip6_dst_alloc(net, dev, flags); + if (!rt6_local) { + dst_release(&rt6->dst); + goto out; + } + + dst_hold(&rt6_local->dst); + + rt6_local->rt6i_idev = in6_dev_get(dev); + rt6_local->rt6i_flags = RTF_UP | RTF_NONEXTHOP | RTF_LOCAL; + rt6_local->rt6i_table = rt6i_table; + rt6_local->dst.input = ip6_input; + rcu_assign_pointer(vrf->rt6, rt6); + rcu_assign_pointer(vrf->rt6_local, rt6_local); rc = 0; out:
@@ -710,6 +780,16 @@ static bool ipv6_ndisc_frame(const struct sk_buff *skb) static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev, struct sk_buff *skb) { + /* loopback traffic; do not push through packet taps again. + * Reset pkt_type for upper layers to process skb + */ + if (skb->pkt_type == PACKET_LOOPBACK) { + skb->dev = vrf_dev; + skb->skb_iif = vrf_dev->ifindex; + skb->pkt_type = PACKET_HOST; + goto out; + } + /* if packet is NDISC keep the ingress interface */ if (!ipv6_ndisc_frame(skb)) { skb->dev = vrf_dev;
@@ -722,6 +802,7 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev, IP6CB(skb)->flags |= IP6SKB_L3SLAVE; } +out: return skb; }
diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c
index 94611e450ec9..aacfb4bce153 100644
--- a/net/ipv6/ip6_input.c
+++ b/net/ipv6/ip6_input.c@@ -323,6 +323,7 @@ int ip6_input(struct sk_buff *skb) dev_net(skb->dev), NULL, skb, skb->dev, NULL, ip6_input_finish); } +EXPORT_SYMBOL_GPL(ip6_input); int ip6_mc_input(struct sk_buff *skb) {
--
2.1.4