Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[cascardo/linux.git] / drivers / net / vrf.c
index 8bd8c7e..1ce7420 100644 (file)
@@ -35,6 +35,7 @@
 #include <net/route.h>
 #include <net/addrconf.h>
 #include <net/l3mdev.h>
+#include <net/fib_rules.h>
 
 #define RT_FL_TOS(oldflp4) \
        ((oldflp4)->flowi4_tos & (IPTOS_RT_MASK | RTO_ONLINK))
 #define DRV_NAME       "vrf"
 #define DRV_VERSION    "1.0"
 
+#define FIB_RULE_PREF  1000       /* default preference for FIB rules */
+static bool add_fib_rules = true;
+
 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 +60,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 +108,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 +162,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 +227,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 +241,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;
 
-       if (vrf_send_v4_prep(skb, &fl4, vrf_dev))
+       rt = ip_route_output_flow(net, &fl4, NULL);
+       if (IS_ERR(rt))
+               goto err;
+
+       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 +307,6 @@ err:
 
 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);
@@ -274,45 +378,92 @@ static int vrf_output6(struct net *net, struct sock *sk, struct sk_buff *skb)
 }
 
 /* holding rtnl */
-static void vrf_rt6_release(struct net_vrf *vrf)
+static void vrf_rt6_release(struct net_device *dev, struct net_vrf *vrf)
 {
        struct rt6_info *rt6 = rtnl_dereference(vrf->rt6);
+       struct rt6_info *rt6_local = rtnl_dereference(vrf->rt6_local);
+       struct net *net = dev_net(dev);
+       struct dst_entry *dst;
 
-       rcu_assign_pointer(vrf->rt6, NULL);
+       RCU_INIT_POINTER(vrf->rt6, NULL);
+       RCU_INIT_POINTER(vrf->rt6_local, NULL);
+       synchronize_rcu();
+
+       /* move dev in dst's to loopback so this VRF device can be deleted
+        * - based on dst_ifdown
+        */
+       if (rt6) {
+               dst = &rt6->dst;
+               dev_put(dst->dev);
+               dst->dev = net->loopback_dev;
+               dev_hold(dst->dev);
+               dst_release(dst);
+       }
 
-       if (rt6)
-               dst_release(&rt6->dst);
+       if (rt6_local) {
+               if (rt6_local->rt6i_idev)
+                       in6_dev_put(rt6_local->rt6i_idev);
+
+               dst = &rt6_local->dst;
+               dev_put(dst->dev);
+               dst->dev = net->loopback_dev;
+               dev_hold(dst->dev);
+               dst_release(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;
 
+       /* IPv6 can be CONFIG enabled and then disabled runtime */
+       if (!ipv6_mod_enabled())
+               return 0;
+
        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;
+       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:
        return rc;
 }
 #else
-static void vrf_rt6_release(struct net_vrf *vrf)
+static void vrf_rt6_release(struct net_device *dev, struct net_vrf *vrf)
 {
 }
 
@@ -381,32 +532,66 @@ static int vrf_output(struct net *net, struct sock *sk, struct sk_buff *skb)
 }
 
 /* holding rtnl */
-static void vrf_rtable_release(struct net_vrf *vrf)
+static void vrf_rtable_release(struct net_device *dev, struct net_vrf *vrf)
 {
        struct rtable *rth = rtnl_dereference(vrf->rth);
+       struct rtable *rth_local = rtnl_dereference(vrf->rth_local);
+       struct net *net = dev_net(dev);
+       struct dst_entry *dst;
 
-       rcu_assign_pointer(vrf->rth, NULL);
+       RCU_INIT_POINTER(vrf->rth, NULL);
+       RCU_INIT_POINTER(vrf->rth_local, NULL);
+       synchronize_rcu();
+
+       /* move dev in dst's to loopback so this VRF device can be deleted
+        * - based on dst_ifdown
+        */
+       if (rth) {
+               dst = &rth->dst;
+               dev_put(dst->dev);
+               dst->dev = net->loopback_dev;
+               dev_hold(dst->dev);
+               dst_release(dst);
+       }
 
-       if (rth)
-               dst_release(&rth->dst);
+       if (rth_local) {
+               dst = &rth_local->dst;
+               dev_put(dst->dev);
+               dst->dev = net->loopback_dev;
+               dev_hold(dst->dev);
+               dst_release(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;
 
-       rth->dst.output = vrf_output;
+       /* 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;
 }
@@ -477,8 +662,8 @@ static void vrf_dev_uninit(struct net_device *dev)
        struct net_device *port_dev;
        struct list_head *iter;
 
-       vrf_rtable_release(vrf);
-       vrf_rt6_release(vrf);
+       vrf_rtable_release(dev, vrf);
+       vrf_rt6_release(dev, vrf);
 
        netdev_for_each_lower_dev(dev, port_dev, iter)
                vrf_del_slave(dev, port_dev);
@@ -504,10 +689,16 @@ static int vrf_dev_init(struct net_device *dev)
 
        dev->flags = IFF_MASTER | IFF_NOARP;
 
+       /* MTU is irrelevant for VRF device; set to 64k similar to lo */
+       dev->mtu = 64 * 1024;
+
+       /* similarly, oper state is irrelevant; set to up to avoid confusion */
+       dev->operstate = IF_OPER_UP;
+       netdev_lockdep_set_classes(dev);
        return 0;
 
 out_rth:
-       vrf_rtable_release(vrf);
+       vrf_rtable_release(dev, vrf);
 out_stats:
        free_percpu(dev->dstats);
        dev->dstats = NULL;
@@ -588,6 +779,25 @@ static int vrf_get_saddr(struct net_device *dev, struct flowi4 *fl4)
        return rc;
 }
 
+static int vrf_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
+{
+       return 0;
+}
+
+static struct sk_buff *vrf_rcv_nfhook(u8 pf, unsigned int hook,
+                                     struct sk_buff *skb,
+                                     struct net_device *dev)
+{
+       struct net *net = dev_net(dev);
+
+       nf_reset(skb);
+
+       if (NF_HOOK(pf, hook, net, NULL, skb, dev, NULL, vrf_rcv_finish) < 0)
+               skb = NULL;    /* kfree_skb(skb) handled by nf code */
+
+       return skb;
+}
+
 #if IS_ENABLED(CONFIG_IPV6)
 /* neighbor handling is done with actual device; do not want
  * to flip skb->dev for those ndisc packets. This really fails
@@ -623,11 +833,78 @@ out:
        return rc;
 }
 
+static struct rt6_info *vrf_ip6_route_lookup(struct net *net,
+                                            const struct net_device *dev,
+                                            struct flowi6 *fl6,
+                                            int ifindex,
+                                            int flags)
+{
+       struct net_vrf *vrf = netdev_priv(dev);
+       struct fib6_table *table = NULL;
+       struct rt6_info *rt6;
+
+       rcu_read_lock();
+
+       /* fib6_table does not have a refcnt and can not be freed */
+       rt6 = rcu_dereference(vrf->rt6);
+       if (likely(rt6))
+               table = rt6->rt6i_table;
+
+       rcu_read_unlock();
+
+       if (!table)
+               return NULL;
+
+       return ip6_pol_route(net, table, ifindex, fl6, flags);
+}
+
+static void vrf_ip6_input_dst(struct sk_buff *skb, struct net_device *vrf_dev,
+                             int ifindex)
+{
+       const struct ipv6hdr *iph = ipv6_hdr(skb);
+       struct flowi6 fl6 = {
+               .daddr          = iph->daddr,
+               .saddr          = iph->saddr,
+               .flowlabel      = ip6_flowinfo(iph),
+               .flowi6_mark    = skb->mark,
+               .flowi6_proto   = iph->nexthdr,
+               .flowi6_iif     = ifindex,
+       };
+       struct net *net = dev_net(vrf_dev);
+       struct rt6_info *rt6;
+
+       rt6 = vrf_ip6_route_lookup(net, vrf_dev, &fl6, ifindex,
+                                  RT6_LOOKUP_F_HAS_SADDR | RT6_LOOKUP_F_IFACE);
+       if (unlikely(!rt6))
+               return;
+
+       if (unlikely(&rt6->dst == &net->ipv6.ip6_null_entry->dst))
+               return;
+
+       skb_dst_set(skb, &rt6->dst);
+}
+
 static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
                                   struct sk_buff *skb)
 {
-       /* if packet is NDISC keep the ingress interface */
-       if (!ipv6_ndisc_frame(skb)) {
+       int orig_iif = skb->skb_iif;
+       bool need_strict;
+
+       /* 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 or addressed to multicast or link-local
+        * then keep the ingress interface
+        */
+       need_strict = rt6_need_strict(&ipv6_hdr(skb)->daddr);
+       if (!ipv6_ndisc_frame(skb) && !need_strict) {
                skb->dev = vrf_dev;
                skb->skb_iif = vrf_dev->ifindex;
 
@@ -638,6 +915,11 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
                IP6CB(skb)->flags |= IP6SKB_L3SLAVE;
        }
 
+       if (need_strict)
+               vrf_ip6_input_dst(skb, vrf_dev, orig_iif);
+
+       skb = vrf_rcv_nfhook(NFPROTO_IPV6, NF_INET_PRE_ROUTING, skb, vrf_dev);
+out:
        return skb;
 }
 
@@ -655,10 +937,20 @@ 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);
 
+       skb = vrf_rcv_nfhook(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, vrf_dev);
+out:
        return skb;
 }
 
@@ -679,13 +971,37 @@ static struct sk_buff *vrf_l3_rcv(struct net_device *vrf_dev,
 
 #if IS_ENABLED(CONFIG_IPV6)
 static struct dst_entry *vrf_get_rt6_dst(const struct net_device *dev,
-                                        const struct flowi6 *fl6)
+                                        struct flowi6 *fl6)
 {
+       bool need_strict = rt6_need_strict(&fl6->daddr);
+       struct net_vrf *vrf = netdev_priv(dev);
+       struct net *net = dev_net(dev);
        struct dst_entry *dst = NULL;
+       struct rt6_info *rt;
 
-       if (!(fl6->flowi6_flags & FLOWI_FLAG_L3MDEV_SRC)) {
-               struct net_vrf *vrf = netdev_priv(dev);
-               struct rt6_info *rt;
+       /* send to link-local or multicast address */
+       if (need_strict) {
+               int flags = RT6_LOOKUP_F_IFACE;
+
+               /* VRF device does not have a link-local address and
+                * sending packets to link-local or mcast addresses over
+                * a VRF device does not make sense
+                */
+               if (fl6->flowi6_oif == dev->ifindex) {
+                       struct dst_entry *dst = &net->ipv6.ip6_null_entry->dst;
+
+                       dst_hold(dst);
+                       return dst;
+               }
+
+               if (!ipv6_addr_any(&fl6->saddr))
+                       flags |= RT6_LOOKUP_F_HAS_SADDR;
+
+               rt = vrf_ip6_route_lookup(net, dev, fl6, fl6->flowi6_oif, flags);
+               if (rt)
+                       dst = &rt->dst;
+
+       } else if (!(fl6->flowi6_flags & FLOWI_FLAG_L3MDEV_SRC)) {
 
                rcu_read_lock();
 
@@ -698,8 +1014,52 @@ static struct dst_entry *vrf_get_rt6_dst(const struct net_device *dev,
                rcu_read_unlock();
        }
 
+       /* make sure oif is set to VRF device for lookup */
+       if (!need_strict)
+               fl6->flowi6_oif = dev->ifindex;
+
        return dst;
 }
+
+/* called under rcu_read_lock */
+static int vrf_get_saddr6(struct net_device *dev, const struct sock *sk,
+                         struct flowi6 *fl6)
+{
+       struct net *net = dev_net(dev);
+       struct dst_entry *dst;
+       struct rt6_info *rt;
+       int err;
+
+       if (rt6_need_strict(&fl6->daddr)) {
+               rt = vrf_ip6_route_lookup(net, dev, fl6, fl6->flowi6_oif,
+                                         RT6_LOOKUP_F_IFACE);
+               if (unlikely(!rt))
+                       return 0;
+
+               dst = &rt->dst;
+       } else {
+               __u8 flags = fl6->flowi6_flags;
+
+               fl6->flowi6_flags |= FLOWI_FLAG_L3MDEV_SRC;
+               fl6->flowi6_flags |= FLOWI_FLAG_SKIP_NH_OIF;
+
+               dst = ip6_route_output(net, sk, fl6);
+               rt = (struct rt6_info *)dst;
+
+               fl6->flowi6_flags = flags;
+       }
+
+       err = dst->error;
+       if (!err) {
+               err = ip6_route_get_saddr(net, rt, &fl6->daddr,
+                                         sk ? inet6_sk(sk)->srcprefs : 0,
+                                         &fl6->saddr);
+       }
+
+       dst_release(dst);
+
+       return err;
+}
 #endif
 
 static const struct l3mdev_ops vrf_l3mdev_ops = {
@@ -709,6 +1069,7 @@ static const struct l3mdev_ops vrf_l3mdev_ops = {
        .l3mdev_l3_rcv          = vrf_l3_rcv,
 #if IS_ENABLED(CONFIG_IPV6)
        .l3mdev_get_rt6_dst     = vrf_get_rt6_dst,
+       .l3mdev_get_saddr6      = vrf_get_saddr6,
 #endif
 };
 
@@ -723,6 +1084,94 @@ static const struct ethtool_ops vrf_ethtool_ops = {
        .get_drvinfo    = vrf_get_drvinfo,
 };
 
+static inline size_t vrf_fib_rule_nl_size(void)
+{
+       size_t sz;
+
+       sz  = NLMSG_ALIGN(sizeof(struct fib_rule_hdr));
+       sz += nla_total_size(sizeof(u8));       /* FRA_L3MDEV */
+       sz += nla_total_size(sizeof(u32));      /* FRA_PRIORITY */
+
+       return sz;
+}
+
+static int vrf_fib_rule(const struct net_device *dev, __u8 family, bool add_it)
+{
+       struct fib_rule_hdr *frh;
+       struct nlmsghdr *nlh;
+       struct sk_buff *skb;
+       int err;
+
+       if (family == AF_INET6 && !ipv6_mod_enabled())
+               return 0;
+
+       skb = nlmsg_new(vrf_fib_rule_nl_size(), GFP_KERNEL);
+       if (!skb)
+               return -ENOMEM;
+
+       nlh = nlmsg_put(skb, 0, 0, 0, sizeof(*frh), 0);
+       if (!nlh)
+               goto nla_put_failure;
+
+       /* rule only needs to appear once */
+       nlh->nlmsg_flags &= NLM_F_EXCL;
+
+       frh = nlmsg_data(nlh);
+       memset(frh, 0, sizeof(*frh));
+       frh->family = family;
+       frh->action = FR_ACT_TO_TBL;
+
+       if (nla_put_u32(skb, FRA_L3MDEV, 1))
+               goto nla_put_failure;
+
+       if (nla_put_u32(skb, FRA_PRIORITY, FIB_RULE_PREF))
+               goto nla_put_failure;
+
+       nlmsg_end(skb, nlh);
+
+       /* fib_nl_{new,del}rule handling looks for net from skb->sk */
+       skb->sk = dev_net(dev)->rtnl;
+       if (add_it) {
+               err = fib_nl_newrule(skb, nlh);
+               if (err == -EEXIST)
+                       err = 0;
+       } else {
+               err = fib_nl_delrule(skb, nlh);
+               if (err == -ENOENT)
+                       err = 0;
+       }
+       nlmsg_free(skb);
+
+       return err;
+
+nla_put_failure:
+       nlmsg_free(skb);
+
+       return -EMSGSIZE;
+}
+
+static int vrf_add_fib_rules(const struct net_device *dev)
+{
+       int err;
+
+       err = vrf_fib_rule(dev, AF_INET,  true);
+       if (err < 0)
+               goto out_err;
+
+       err = vrf_fib_rule(dev, AF_INET6, true);
+       if (err < 0)
+               goto ipv6_err;
+
+       return 0;
+
+ipv6_err:
+       vrf_fib_rule(dev, AF_INET,  false);
+
+out_err:
+       netdev_err(dev, "Failed to add FIB rules.\n");
+       return err;
+}
+
 static void vrf_setup(struct net_device *dev)
 {
        ether_setup(dev);
@@ -741,6 +1190,20 @@ static void vrf_setup(struct net_device *dev)
 
        /* don't allow vrf devices to change network namespaces. */
        dev->features |= NETIF_F_NETNS_LOCAL;
+
+       /* does not make sense for a VLAN to be added to a vrf device */
+       dev->features   |= NETIF_F_VLAN_CHALLENGED;
+
+       /* enable offload features */
+       dev->features   |= NETIF_F_GSO_SOFTWARE;
+       dev->features   |= NETIF_F_RXCSUM | NETIF_F_HW_CSUM;
+       dev->features   |= NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA;
+
+       dev->hw_features = dev->features;
+       dev->hw_enc_features = dev->features;
+
+       /* default to no qdisc; user can add if desired */
+       dev->priv_flags |= IFF_NO_QUEUE;
 }
 
 static int vrf_validate(struct nlattr *tb[], struct nlattr *data[])
@@ -763,6 +1226,7 @@ static int vrf_newlink(struct net *src_net, struct net_device *dev,
                       struct nlattr *tb[], struct nlattr *data[])
 {
        struct net_vrf *vrf = netdev_priv(dev);
+       int err;
 
        if (!data || !data[IFLA_VRF_TABLE])
                return -EINVAL;
@@ -771,7 +1235,21 @@ static int vrf_newlink(struct net *src_net, struct net_device *dev,
 
        dev->priv_flags |= IFF_L3MDEV_MASTER;
 
-       return register_netdevice(dev);
+       err = register_netdevice(dev);
+       if (err)
+               goto out;
+
+       if (add_fib_rules) {
+               err = vrf_add_fib_rules(dev);
+               if (err) {
+                       unregister_netdevice(dev);
+                       goto out;
+               }
+               add_fib_rules = false;
+       }
+
+out:
+       return err;
 }
 
 static size_t vrf_nl_getsize(const struct net_device *dev)