diff options
author | David Ahern <dsa@cumulusnetworks.com> | 2016-06-02 16:15:12 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-06-06 18:19:02 -0400 |
commit | 625b47b507329c4d12b256bca245e70467d3b096 (patch) | |
tree | 1b58398651f8f81e4b0d4f3cf34a0981014f2349 /drivers/net/vrf.c | |
parent | 671cd19ade97e98046d0b0d1d470d4968f012401 (diff) |
net: vrf: ipv6 support for local traffic to local addresses
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 <dsa@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/vrf.c')
-rw-r--r-- | drivers/net/vrf.c | 89 |
1 files changed, 85 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 { | |||
46 | struct rtable __rcu *rth; | 46 | struct rtable __rcu *rth; |
47 | struct rtable __rcu *rth_local; | 47 | struct rtable __rcu *rth_local; |
48 | struct rt6_info __rcu *rt6; | 48 | struct rt6_info __rcu *rt6; |
49 | struct rt6_info __rcu *rt6_local; | ||
49 | u32 tb_id; | 50 | u32 tb_id; |
50 | }; | 51 | }; |
51 | 52 | ||
@@ -157,6 +158,46 @@ static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb, | |||
157 | goto err; | 158 | goto err; |
158 | 159 | ||
159 | skb_dst_drop(skb); | 160 | skb_dst_drop(skb); |
161 | |||
162 | /* if dst.dev is loopback or the VRF device again this is locally | ||
163 | * originated traffic destined to a local address. Short circuit | ||
164 | * to Rx path using our local dst | ||
165 | */ | ||
166 | if (dst->dev == net->loopback_dev || dst->dev == dev) { | ||
167 | struct net_vrf *vrf = netdev_priv(dev); | ||
168 | struct rt6_info *rt6_local; | ||
169 | |||
170 | /* release looked up dst and use cached local dst */ | ||
171 | dst_release(dst); | ||
172 | |||
173 | rcu_read_lock(); | ||
174 | |||
175 | rt6_local = rcu_dereference(vrf->rt6_local); | ||
176 | if (unlikely(!rt6_local)) { | ||
177 | rcu_read_unlock(); | ||
178 | goto err; | ||
179 | } | ||
180 | |||
181 | /* Ordering issue: cached local dst is created on newlink | ||
182 | * before the IPv6 initialization. Using the local dst | ||
183 | * requires rt6i_idev to be set so make sure it is. | ||
184 | */ | ||
185 | if (unlikely(!rt6_local->rt6i_idev)) { | ||
186 | rt6_local->rt6i_idev = in6_dev_get(dev); | ||
187 | if (!rt6_local->rt6i_idev) { | ||
188 | rcu_read_unlock(); | ||
189 | goto err; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | dst = &rt6_local->dst; | ||
194 | dst_hold(dst); | ||
195 | |||
196 | rcu_read_unlock(); | ||
197 | |||
198 | return vrf_local_xmit(skb, dev, &rt6_local->dst); | ||
199 | } | ||
200 | |||
160 | skb_dst_set(skb, dst); | 201 | skb_dst_set(skb, dst); |
161 | 202 | ||
162 | /* strip the ethernet header added for pass through VRF device */ | 203 | /* 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) | |||
336 | static void vrf_rt6_release(struct net_vrf *vrf) | 377 | static void vrf_rt6_release(struct net_vrf *vrf) |
337 | { | 378 | { |
338 | struct rt6_info *rt6 = rtnl_dereference(vrf->rt6); | 379 | struct rt6_info *rt6 = rtnl_dereference(vrf->rt6); |
380 | struct rt6_info *rt6_local = rtnl_dereference(vrf->rt6_local); | ||
339 | 381 | ||
340 | rcu_assign_pointer(vrf->rt6, NULL); | 382 | RCU_INIT_POINTER(vrf->rt6, NULL); |
383 | RCU_INIT_POINTER(vrf->rt6_local, NULL); | ||
384 | synchronize_rcu(); | ||
341 | 385 | ||
342 | if (rt6) | 386 | if (rt6) |
343 | dst_release(&rt6->dst); | 387 | dst_release(&rt6->dst); |
388 | |||
389 | if (rt6_local) { | ||
390 | if (rt6_local->rt6i_idev) | ||
391 | in6_dev_put(rt6_local->rt6i_idev); | ||
392 | |||
393 | dst_release(&rt6_local->dst); | ||
394 | } | ||
344 | } | 395 | } |
345 | 396 | ||
346 | static int vrf_rt6_create(struct net_device *dev) | 397 | static int vrf_rt6_create(struct net_device *dev) |
347 | { | 398 | { |
399 | int flags = DST_HOST | DST_NOPOLICY | DST_NOXFRM | DST_NOCACHE; | ||
348 | struct net_vrf *vrf = netdev_priv(dev); | 400 | struct net_vrf *vrf = netdev_priv(dev); |
349 | struct net *net = dev_net(dev); | 401 | struct net *net = dev_net(dev); |
350 | struct fib6_table *rt6i_table; | 402 | struct fib6_table *rt6i_table; |
351 | struct rt6_info *rt6; | 403 | struct rt6_info *rt6, *rt6_local; |
352 | int rc = -ENOMEM; | 404 | int rc = -ENOMEM; |
353 | 405 | ||
354 | rt6i_table = fib6_new_table(net, vrf->tb_id); | 406 | rt6i_table = fib6_new_table(net, vrf->tb_id); |
355 | if (!rt6i_table) | 407 | if (!rt6i_table) |
356 | goto out; | 408 | goto out; |
357 | 409 | ||
358 | rt6 = ip6_dst_alloc(net, dev, | 410 | /* create a dst for routing packets out a VRF device */ |
359 | DST_HOST | DST_NOPOLICY | DST_NOXFRM | DST_NOCACHE); | 411 | rt6 = ip6_dst_alloc(net, dev, flags); |
360 | if (!rt6) | 412 | if (!rt6) |
361 | goto out; | 413 | goto out; |
362 | 414 | ||
@@ -364,7 +416,25 @@ static int vrf_rt6_create(struct net_device *dev) | |||
364 | 416 | ||
365 | rt6->rt6i_table = rt6i_table; | 417 | rt6->rt6i_table = rt6i_table; |
366 | rt6->dst.output = vrf_output6; | 418 | rt6->dst.output = vrf_output6; |
419 | |||
420 | /* create a dst for local routing - packets sent locally | ||
421 | * to local address via the VRF device as a loopback | ||
422 | */ | ||
423 | rt6_local = ip6_dst_alloc(net, dev, flags); | ||
424 | if (!rt6_local) { | ||
425 | dst_release(&rt6->dst); | ||
426 | goto out; | ||
427 | } | ||
428 | |||
429 | dst_hold(&rt6_local->dst); | ||
430 | |||
431 | rt6_local->rt6i_idev = in6_dev_get(dev); | ||
432 | rt6_local->rt6i_flags = RTF_UP | RTF_NONEXTHOP | RTF_LOCAL; | ||
433 | rt6_local->rt6i_table = rt6i_table; | ||
434 | rt6_local->dst.input = ip6_input; | ||
435 | |||
367 | rcu_assign_pointer(vrf->rt6, rt6); | 436 | rcu_assign_pointer(vrf->rt6, rt6); |
437 | rcu_assign_pointer(vrf->rt6_local, rt6_local); | ||
368 | 438 | ||
369 | rc = 0; | 439 | rc = 0; |
370 | out: | 440 | out: |
@@ -710,6 +780,16 @@ out: | |||
710 | static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev, | 780 | static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev, |
711 | struct sk_buff *skb) | 781 | struct sk_buff *skb) |
712 | { | 782 | { |
783 | /* loopback traffic; do not push through packet taps again. | ||
784 | * Reset pkt_type for upper layers to process skb | ||
785 | */ | ||
786 | if (skb->pkt_type == PACKET_LOOPBACK) { | ||
787 | skb->dev = vrf_dev; | ||
788 | skb->skb_iif = vrf_dev->ifindex; | ||
789 | skb->pkt_type = PACKET_HOST; | ||
790 | goto out; | ||
791 | } | ||
792 | |||
713 | /* if packet is NDISC keep the ingress interface */ | 793 | /* if packet is NDISC keep the ingress interface */ |
714 | if (!ipv6_ndisc_frame(skb)) { | 794 | if (!ipv6_ndisc_frame(skb)) { |
715 | skb->dev = vrf_dev; | 795 | skb->dev = vrf_dev; |
@@ -722,6 +802,7 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev, | |||
722 | IP6CB(skb)->flags |= IP6SKB_L3SLAVE; | 802 | IP6CB(skb)->flags |= IP6SKB_L3SLAVE; |
723 | } | 803 | } |
724 | 804 | ||
805 | out: | ||
725 | return skb; | 806 | return skb; |
726 | } | 807 | } |
727 | 808 | ||