ipv4: ipmr: Don't forward packets already forwarded by hardware

Change the ipmr module to not forward packets if:
 - The packet is marked with the offload_mr_fwd_mark, and
 - Both input interface and output interface share the same parent ID.

This way, a packet can go through partial multicast forwarding in the
hardware, where it will be forwarded only to the devices that share the
same parent ID (AKA, reside inside the same hardware). The kernel will
forward the packet to all other interfaces.

To do this, add the ipmr_offload_forward helper, which per skb, ingress VIF
and egress VIF, returns whether the forwarding was offloaded to hardware.
The ipmr_queue_xmit frees the skb and does not forward it if the result is
a true value.

All the forwarding path code compiles out when the CONFIG_NET_SWITCHDEV is
not set.

Signed-off-by: Yotam Gigi <yotamg@mellanox.com>
Reviewed-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Reviewed-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Yotam Gigi 2017-10-03 09:58:08 +02:00 committed by David S. Miller
parent 5d8b3e69fc
commit a5bc9294d7
1 changed files with 32 additions and 5 deletions

View File

@ -1859,10 +1859,33 @@ static inline int ipmr_forward_finish(struct net *net, struct sock *sk,
return dst_output(net, sk, skb); return dst_output(net, sk, skb);
} }
#ifdef CONFIG_NET_SWITCHDEV
static bool ipmr_forward_offloaded(struct sk_buff *skb, struct mr_table *mrt,
int in_vifi, int out_vifi)
{
struct vif_device *out_vif = &mrt->vif_table[out_vifi];
struct vif_device *in_vif = &mrt->vif_table[in_vifi];
if (!skb->offload_mr_fwd_mark)
return false;
if (!out_vif->dev_parent_id.id_len || !in_vif->dev_parent_id.id_len)
return false;
return netdev_phys_item_id_same(&out_vif->dev_parent_id,
&in_vif->dev_parent_id);
}
#else
static bool ipmr_forward_offloaded(struct sk_buff *skb, struct mr_table *mrt,
int in_vifi, int out_vifi)
{
return false;
}
#endif
/* Processing handlers for ipmr_forward */ /* Processing handlers for ipmr_forward */
static void ipmr_queue_xmit(struct net *net, struct mr_table *mrt, static void ipmr_queue_xmit(struct net *net, struct mr_table *mrt,
struct sk_buff *skb, struct mfc_cache *c, int vifi) int in_vifi, struct sk_buff *skb,
struct mfc_cache *c, int vifi)
{ {
const struct iphdr *iph = ip_hdr(skb); const struct iphdr *iph = ip_hdr(skb);
struct vif_device *vif = &mrt->vif_table[vifi]; struct vif_device *vif = &mrt->vif_table[vifi];
@ -1883,6 +1906,9 @@ static void ipmr_queue_xmit(struct net *net, struct mr_table *mrt,
goto out_free; goto out_free;
} }
if (ipmr_forward_offloaded(skb, mrt, in_vifi, vifi))
goto out_free;
if (vif->flags & VIFF_TUNNEL) { if (vif->flags & VIFF_TUNNEL) {
rt = ip_route_output_ports(net, &fl4, NULL, rt = ip_route_output_ports(net, &fl4, NULL,
vif->remote, vif->local, vif->remote, vif->local,
@ -2060,8 +2086,8 @@ forward:
struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
if (skb2) if (skb2)
ipmr_queue_xmit(net, mrt, skb2, cache, ipmr_queue_xmit(net, mrt, true_vifi,
psend); skb2, cache, psend);
} }
psend = ct; psend = ct;
} }
@ -2072,9 +2098,10 @@ last_forward:
struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
if (skb2) if (skb2)
ipmr_queue_xmit(net, mrt, skb2, cache, psend); ipmr_queue_xmit(net, mrt, true_vifi, skb2,
cache, psend);
} else { } else {
ipmr_queue_xmit(net, mrt, skb, cache, psend); ipmr_queue_xmit(net, mrt, true_vifi, skb, cache, psend);
return; return;
} }
} }