aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/vrf.c
diff options
context:
space:
mode:
authorDavid Ahern <dsa@cumulusnetworks.com>2016-06-02 16:15:12 -0400
committerDavid S. Miller <davem@davemloft.net>2016-06-06 18:19:02 -0400
commit625b47b507329c4d12b256bca245e70467d3b096 (patch)
tree1b58398651f8f81e4b0d4f3cf34a0981014f2349 /drivers/net/vrf.c
parent671cd19ade97e98046d0b0d1d470d4968f012401 (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.c89
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)
336static void vrf_rt6_release(struct net_vrf *vrf) 377static 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
346static int vrf_rt6_create(struct net_device *dev) 397static 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;
370out: 440out:
@@ -710,6 +780,16 @@ out:
710static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev, 780static 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
805out:
725 return skb; 806 return skb;
726} 807}
727 808