Skip to content

Commit b4869aa

Browse files
David Aherndavem330
David Ahern
authored andcommitted
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 <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent afe80a4 commit b4869aa

File tree

2 files changed

+86
-4
lines changed

2 files changed

+86
-4
lines changed

drivers/net/vrf.c

+85-4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct net_vrf {
4646
struct rtable __rcu *rth;
4747
struct rtable __rcu *rth_local;
4848
struct rt6_info __rcu *rt6;
49+
struct rt6_info __rcu *rt6_local;
4950
u32 tb_id;
5051
};
5152

@@ -157,6 +158,46 @@ static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,
157158
goto err;
158159

159160
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+
160201
skb_dst_set(skb, dst);
161202

162203
/* strip the ethernet header added for pass through VRF device */
@@ -336,35 +377,64 @@ static int vrf_output6(struct net *net, struct sock *sk, struct sk_buff *skb)
336377
static void vrf_rt6_release(struct net_vrf *vrf)
337378
{
338379
struct rt6_info *rt6 = rtnl_dereference(vrf->rt6);
380+
struct rt6_info *rt6_local = rtnl_dereference(vrf->rt6_local);
339381

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();
341385

342386
if (rt6)
343387
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+
}
344395
}
345396

346397
static int vrf_rt6_create(struct net_device *dev)
347398
{
399+
int flags = DST_HOST | DST_NOPOLICY | DST_NOXFRM | DST_NOCACHE;
348400
struct net_vrf *vrf = netdev_priv(dev);
349401
struct net *net = dev_net(dev);
350402
struct fib6_table *rt6i_table;
351-
struct rt6_info *rt6;
403+
struct rt6_info *rt6, *rt6_local;
352404
int rc = -ENOMEM;
353405

354406
rt6i_table = fib6_new_table(net, vrf->tb_id);
355407
if (!rt6i_table)
356408
goto out;
357409

358-
rt6 = ip6_dst_alloc(net, dev,
359-
DST_HOST | DST_NOPOLICY | DST_NOXFRM | DST_NOCACHE);
410+
/* create a dst for routing packets out a VRF device */
411+
rt6 = ip6_dst_alloc(net, dev, flags);
360412
if (!rt6)
361413
goto out;
362414

363415
dst_hold(&rt6->dst);
364416

365417
rt6->rt6i_table = rt6i_table;
366418
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+
367436
rcu_assign_pointer(vrf->rt6, rt6);
437+
rcu_assign_pointer(vrf->rt6_local, rt6_local);
368438

369439
rc = 0;
370440
out:
@@ -710,6 +780,16 @@ static bool ipv6_ndisc_frame(const struct sk_buff *skb)
710780
static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
711781
struct sk_buff *skb)
712782
{
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+
713793
/* if packet is NDISC keep the ingress interface */
714794
if (!ipv6_ndisc_frame(skb)) {
715795
skb->dev = vrf_dev;
@@ -722,6 +802,7 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
722802
IP6CB(skb)->flags |= IP6SKB_L3SLAVE;
723803
}
724804

805+
out:
725806
return skb;
726807
}
727808

net/ipv6/ip6_input.c

+1
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ int ip6_input(struct sk_buff *skb)
323323
dev_net(skb->dev), NULL, skb, skb->dev, NULL,
324324
ip6_input_finish);
325325
}
326+
EXPORT_SYMBOL_GPL(ip6_input);
326327

327328
int ip6_mc_input(struct sk_buff *skb)
328329
{

0 commit comments

Comments
 (0)