struct net_vrf {
        struct rtable __rcu     *rth;
+       struct rtable __rcu     *rth_local;
        struct rt6_info __rcu   *rt6;
        u32                     tb_id;
 };
        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++;
        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)
        }
 
        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 */
 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;
 }
        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;
 }