Skip to content

Commit

Permalink
Merge branch 'vrf-local'
Browse files Browse the repository at this point in the history
David Ahern says:

====================
net: vrf: Add support for local traffic to local addresses

Add support for locally originated traffic to VRF-local addresses,
be it addresses on enslaved devices or addresses on the VRF device:

$ ip addr show dev red
33: red: <NOARP,MASTER,UP,LOWER_UP> mtu 65536 qdisc pfifo_fast state UP group default qlen 1000
    link/ether be:00:53:b5:e4:25 brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.1/32 scope global red
       valid_lft forever preferred_lft forever
    inet6 1111:1::1/128 scope global
       valid_lft forever preferred_lft forever

$ ip addr show dev eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master red state UP group default qlen 1000
    link/ether 02:e0:f9:79:34:bd 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:fe79:34bd/64 scope link
       valid_lft forever preferred_lft forever

$ ping -c1 -I red 10.100.1.1
    ping: Warning: source address might be selected on device other than red.
    PING 10.100.1.1 (10.100.1.1) from 10.100.1.1 red: 56(84) bytes of data.
    64 bytes from 10.100.1.1: icmp_seq=1 ttl=64 time=0.057 ms

$ ping -c1 -I red 1.1.1.1
PING 1.1.1.1 (1.1.1.1) from 1.1.1.1 red: 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=64 time=0.136 ms

--- 1.1.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.136/0.136/0.136/0.000 ms

$ 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.167 ms

--- 2100:1::1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.167/0.167/0.167/0.000 ms

$ ping6 -c1 -I red 1111::1
PING 1111::1(1111::1) from 1111:1::1 red: 56 data bytes
64 bytes from 1111::1: icmp_seq=1 ttl=64 time=0.187 ms

--- 1111::1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.187/0.187/0.187/0.000 ms

This change also enables use of loopback address on the VRF device:
$ ip addr add dev red 127.0.0.1/8

$ ping -c1 -I red 127.0.0.1
PING 127.0.0.1 (127.0.0.1) from 127.0.0.1 red: 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.058 ms
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Jun 6, 2016
2 parents b94eb2c + 625b47b commit 2fb7ea4
Showing 1 changed file with 201 additions and 33 deletions.
234 changes: 201 additions & 33 deletions drivers/net/vrf.c
Original file line number Diff line number Diff line change
@@ -44,7 +44,9 @@

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;
};

@@ -54,9 +56,20 @@ struct pcpu_dstats {
u64 tx_drps;
u64 rx_pkts;
u64 rx_bytes;
u64 rx_drps;
struct u64_stats_sync syncp;
};

static void vrf_rx_stats(struct net_device *dev, int len)
{
struct pcpu_dstats *dstats = this_cpu_ptr(dev->dstats);

u64_stats_update_begin(&dstats->syncp);
dstats->rx_pkts++;
dstats->rx_bytes += len;
u64_stats_update_end(&dstats->syncp);
}

static void vrf_tx_error(struct net_device *vrf_dev, struct sk_buff *skb)
{
vrf_dev->stats.tx_errors++;
@@ -91,6 +104,34 @@ static struct rtnl_link_stats64 *vrf_get_stats64(struct net_device *dev,
return stats;
}

/* Local traffic destined to local address. Reinsert the packet to rx
* path, similar to loopback handling.
*/
static int vrf_local_xmit(struct sk_buff *skb, struct net_device *dev,
struct dst_entry *dst)
{
int len = skb->len;

skb_orphan(skb);

skb_dst_set(skb, dst);
skb_dst_force(skb);

/* set pkt_type to avoid skb hitting packet taps twice -
* once on Tx and again in Rx processing
*/
skb->pkt_type = PACKET_LOOPBACK;

skb->protocol = eth_type_trans(skb, dev);

if (likely(netif_rx(skb) == NET_RX_SUCCESS))
vrf_rx_stats(dev, len);
else
this_cpu_inc(dev->dstats->rx_drps);

return NETDEV_TX_OK;
}

#if IS_ENABLED(CONFIG_IPV6)
static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,
struct net_device *dev)
@@ -117,8 +158,51 @@ 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 */
__skb_pull(skb, skb_network_offset(skb));

ret = ip6_local_out(net, skb->sk, skb);
if (unlikely(net_xmit_eval(ret)))
dev->stats.tx_errors++;
@@ -139,29 +223,6 @@ static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,
}
#endif

static int vrf_send_v4_prep(struct sk_buff *skb, struct flowi4 *fl4,
struct net_device *vrf_dev)
{
struct rtable *rt;
int err = 1;

rt = ip_route_output_flow(dev_net(vrf_dev), fl4, NULL);
if (IS_ERR(rt))
goto out;

/* TO-DO: what about broadcast ? */
if (rt->rt_type != RTN_UNICAST && rt->rt_type != RTN_LOCAL) {
ip_rt_put(rt);
goto out;
}

skb_dst_drop(skb);
skb_dst_set(skb, &rt->dst);
err = 0;
out:
return err;
}

static netdev_tx_t vrf_process_v4_outbound(struct sk_buff *skb,
struct net_device *vrf_dev)
{
@@ -176,9 +237,51 @@ static netdev_tx_t vrf_process_v4_outbound(struct sk_buff *skb,
FLOWI_FLAG_SKIP_NH_OIF,
.daddr = ip4h->daddr,
};
struct net *net = dev_net(vrf_dev);
struct rtable *rt;

rt = ip_route_output_flow(net, &fl4, NULL);
if (IS_ERR(rt))
goto err;

if (vrf_send_v4_prep(skb, &fl4, vrf_dev))
if (rt->rt_type != RTN_UNICAST && rt->rt_type != RTN_LOCAL) {
ip_rt_put(rt);
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 (rt->dst.dev == net->loopback_dev || rt->dst.dev == vrf_dev) {
struct net_vrf *vrf = netdev_priv(vrf_dev);
struct rtable *rth_local;
struct dst_entry *dst = NULL;

ip_rt_put(rt);

rcu_read_lock();

rth_local = rcu_dereference(vrf->rth_local);
if (likely(rth_local)) {
dst = &rth_local->dst;
dst_hold(dst);
}

rcu_read_unlock();

if (unlikely(!dst))
goto err;

return vrf_local_xmit(skb, vrf_dev, dst);
}

skb_dst_set(skb, &rt->dst);

/* strip the ethernet header added for pass through VRF device */
__skb_pull(skb, skb_network_offset(skb));

if (!ip4h->saddr) {
ip4h->saddr = inet_select_addr(skb_dst(skb)->dev, 0,
@@ -200,9 +303,6 @@ static netdev_tx_t vrf_process_v4_outbound(struct sk_buff *skb,

static netdev_tx_t is_ip_tx_frame(struct sk_buff *skb, struct net_device *dev)
{
/* strip the ethernet header added for pass through VRF device */
__skb_pull(skb, skb_network_offset(skb));

switch (skb->protocol) {
case htons(ETH_P_IP):
return vrf_process_v4_outbound(skb, dev);
@@ -277,35 +377,64 @@ 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;

dst_hold(&rt6->dst);

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:
@@ -384,29 +513,48 @@ static int vrf_output(struct net *net, struct sock *sk, struct sk_buff *skb)
static void vrf_rtable_release(struct net_vrf *vrf)
{
struct rtable *rth = rtnl_dereference(vrf->rth);
struct rtable *rth_local = rtnl_dereference(vrf->rth_local);

rcu_assign_pointer(vrf->rth, NULL);
RCU_INIT_POINTER(vrf->rth, NULL);
RCU_INIT_POINTER(vrf->rth_local, NULL);
synchronize_rcu();

if (rth)
dst_release(&rth->dst);

if (rth_local)
dst_release(&rth_local->dst);
}

static int vrf_rtable_create(struct net_device *dev)
{
struct net_vrf *vrf = netdev_priv(dev);
struct rtable *rth;
struct rtable *rth, *rth_local;

if (!fib_new_table(dev_net(dev), vrf->tb_id))
return -ENOMEM;

/* create a dst for routing packets out through a VRF device */
rth = rt_dst_alloc(dev, 0, RTN_UNICAST, 1, 1, 0);
if (!rth)
return -ENOMEM;

/* create a dst for local ingress routing - packets sent locally
* to local address via the VRF device as a loopback
*/
rth_local = rt_dst_alloc(dev, RTCF_LOCAL, RTN_LOCAL, 1, 1, 0);
if (!rth_local) {
dst_release(&rth->dst);
return -ENOMEM;
}

rth->dst.output = vrf_output;
rth->rt_table_id = vrf->tb_id;

rth_local->rt_table_id = vrf->tb_id;

rcu_assign_pointer(vrf->rth, rth);
rcu_assign_pointer(vrf->rth_local, rth_local);

return 0;
}
@@ -632,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;
@@ -644,6 +802,7 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
IP6CB(skb)->flags |= IP6SKB_L3SLAVE;
}

out:
return skb;
}

@@ -661,10 +820,19 @@ static struct sk_buff *vrf_ip_rcv(struct net_device *vrf_dev,
skb->dev = vrf_dev;
skb->skb_iif = vrf_dev->ifindex;

/* 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->pkt_type = PACKET_HOST;
goto out;
}

skb_push(skb, skb->mac_len);
dev_queue_xmit_nit(skb, vrf_dev);
skb_pull(skb, skb->mac_len);

out:
return skb;
}

0 comments on commit 2fb7ea4

Please sign in to comment.