|
@@ -44,6 +44,7 @@
|
|
|
|
|
|
struct net_vrf {
|
|
struct net_vrf {
|
|
struct rtable __rcu *rth;
|
|
struct rtable __rcu *rth;
|
|
|
|
+ struct rtable __rcu *rth_local;
|
|
struct rt6_info __rcu *rt6;
|
|
struct rt6_info __rcu *rt6;
|
|
u32 tb_id;
|
|
u32 tb_id;
|
|
};
|
|
};
|
|
@@ -54,9 +55,20 @@ struct pcpu_dstats {
|
|
u64 tx_drps;
|
|
u64 tx_drps;
|
|
u64 rx_pkts;
|
|
u64 rx_pkts;
|
|
u64 rx_bytes;
|
|
u64 rx_bytes;
|
|
|
|
+ u64 rx_drps;
|
|
struct u64_stats_sync syncp;
|
|
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)
|
|
static void vrf_tx_error(struct net_device *vrf_dev, struct sk_buff *skb)
|
|
{
|
|
{
|
|
vrf_dev->stats.tx_errors++;
|
|
vrf_dev->stats.tx_errors++;
|
|
@@ -91,6 +103,34 @@ static struct rtnl_link_stats64 *vrf_get_stats64(struct net_device *dev,
|
|
return stats;
|
|
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)
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,
|
|
static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
struct net_device *dev)
|
|
@@ -169,6 +209,34 @@ static netdev_tx_t vrf_process_v4_outbound(struct sk_buff *skb,
|
|
}
|
|
}
|
|
|
|
|
|
skb_dst_drop(skb);
|
|
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);
|
|
skb_dst_set(skb, &rt->dst);
|
|
|
|
|
|
/* strip the ethernet header added for pass through VRF device */
|
|
/* strip the ethernet header added for pass through VRF device */
|
|
@@ -375,29 +443,48 @@ static int vrf_output(struct net *net, struct sock *sk, struct sk_buff *skb)
|
|
static void vrf_rtable_release(struct net_vrf *vrf)
|
|
static void vrf_rtable_release(struct net_vrf *vrf)
|
|
{
|
|
{
|
|
struct rtable *rth = rtnl_dereference(vrf->rth);
|
|
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)
|
|
if (rth)
|
|
dst_release(&rth->dst);
|
|
dst_release(&rth->dst);
|
|
|
|
+
|
|
|
|
+ if (rth_local)
|
|
|
|
+ dst_release(&rth_local->dst);
|
|
}
|
|
}
|
|
|
|
|
|
static int vrf_rtable_create(struct net_device *dev)
|
|
static int vrf_rtable_create(struct net_device *dev)
|
|
{
|
|
{
|
|
struct net_vrf *vrf = netdev_priv(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))
|
|
if (!fib_new_table(dev_net(dev), vrf->tb_id))
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
+ /* create a dst for routing packets out through a VRF device */
|
|
rth = rt_dst_alloc(dev, 0, RTN_UNICAST, 1, 1, 0);
|
|
rth = rt_dst_alloc(dev, 0, RTN_UNICAST, 1, 1, 0);
|
|
if (!rth)
|
|
if (!rth)
|
|
return -ENOMEM;
|
|
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->dst.output = vrf_output;
|
|
rth->rt_table_id = vrf->tb_id;
|
|
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, rth);
|
|
|
|
+ rcu_assign_pointer(vrf->rth_local, rth_local);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
@@ -652,10 +739,19 @@ static struct sk_buff *vrf_ip_rcv(struct net_device *vrf_dev,
|
|
skb->dev = vrf_dev;
|
|
skb->dev = vrf_dev;
|
|
skb->skb_iif = vrf_dev->ifindex;
|
|
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);
|
|
skb_push(skb, skb->mac_len);
|
|
dev_queue_xmit_nit(skb, vrf_dev);
|
|
dev_queue_xmit_nit(skb, vrf_dev);
|
|
skb_pull(skb, skb->mac_len);
|
|
skb_pull(skb, skb->mac_len);
|
|
|
|
|
|
|
|
+out:
|
|
return skb;
|
|
return skb;
|
|
}
|
|
}
|
|
|
|
|