Wang Zhan wrote:
> A GSO skb which exceeds an egress device limit loses its GSO feature mask
> and is segmented into individual packets.  This is unnecessarily expensive
> when the device can still offload smaller TCP GSO skbs, which is easy to
> hit once one hop of a BIG TCP path raises gso_max_size and the next one
> does not.
> 
> For an unencapsulated TCP GSO skb which exceeds gso_max_size or
> gso_max_segs, work out how many MSS segments each output skb may carry and
> resegment the skb with that max_segs bound instead.  Encapsulated and
> frag-list skbs, GSO types the device cannot offload and bounds which leave
> room for a single MSS keep today's segmentation.  The output obeys the GSO
> feature and limit contract the device already advertises, so it applies
> automatically, without extra device state or a userspace control.
> 
> The output is a plain GSO skb, so its whole length lands in the 16-bit L3
> length field which inet_gso_segment() and ipv6_gso_segment() write.  An
> egress limit above 64 KiB would give outputs whose length truncates, so
> the size limit is also capped at what that field can express.
> 
> The helper runs on the skb which is handed to the driver, after
> validate_xmit_vlan() and sk_validate_xmit_skb(), and only from the
> netif_needs_gso() branch, so an skb which is not segmented pays nothing.
> An over-limit skb pays one device limit test and one ndo_features_check()
> for the bound, in exchange for keeping the output a GSO skb.
> 
> Measured on a veth -> bridge -> TAP -> guest virtio-net path, with BIG TCP
> enabled on the veth endpoints and left off in the guest, so the skbs which
> the veth hop accepts have to be segmented before the TAP device.  A single
> iperf3 TCP flow, six alternating runs per state (`-t 15 -O 5`, fixed CPU
> affinity and port tuple).  The middle column is the same tree with the
> resegmentation disabled:
> 
>   protocol  no BIG TCP   mixed, no reseg  mixed, resegmented
>   TCP/IPv4  51.550 Gbps  15.850 Gbps      52.617 Gbps
>   TCP/IPv6  52.050 Gbps  15.783 Gbps      51.933 Gbps
> 
> Coefficient of variation for the two mixed columns was 0.48% and 0.82% for
> IPv4 and 0.44% and 0.44% for IPv6.  A BIG TCP hop which feeds a 64 KiB hop
> loses 69% of the throughput of a path which never enables BIG TCP at all;
> bounded resegmentation recovers it, 3.3x over the existing segmentation
> path and within noise of the no BIG TCP baseline.
> 
> Assisted-by: LLM
> Signed-off-by: Wang Zhan <[email protected]>
> 
> ---
> v3:
> - the L3 protocol change moved to patch 1
> - fold skb_can_gso_resegment() into the helper, now skb_gso_output_max_segs()
> - pass the caller's features on unchanged, the bound only shapes the output
> - drop the scatter-gather and checksum tests skb_segment() applies itself
> - saturate the size limit with min(), drop the sub-MSS guard
> - cap the output size at GSO_LEGACY_MAX_SIZE, these outputs are not BIG TCP
> v2: https://lore.kernel.org/[email protected]/
> v1: https://lore.kernel.org/[email protected]/
> ---
>  net/core/dev.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 62 insertions(+), 1 deletion(-)
> 
> diff --git a/net/core/dev.c b/net/core/dev.c
> index d66b667071837..728260772f349 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -3933,6 +3933,63 @@ netdev_features_t netif_skb_features(struct sk_buff 
> *skb)
>  }
>  EXPORT_SYMBOL(netif_skb_features);
>  
> +static unsigned int
> +skb_gso_output_max_segs(struct sk_buff *skb, struct net_device *dev)
> +{
> +     unsigned int mss = skb_shinfo(skb)->gso_size;
> +     unsigned int gso_max_size, hdr_len, max_segs;
> +     netdev_features_t features;
> +     struct tcphdr _tcph, *th;
> +
> +     /*
> +      * The TCP frag-list path segments through skb_segment_list(), which
> +      * does not carry max_segs, so bounded calls skip those skbs.
> +      */

This comment answers only one of six conditions. And one that is
pretty straightforward. I'd drop.

In general, drop all too-obvious comments. AI has a habit of adding
a lot more, and more low information, comments than is customary in
kernel code (where we also have commit messages). Generally, repeating
what the code does is of little value.

> +     if (!skb_is_gso(skb) || !skb_is_gso_tcp(skb) ||
> +         skb->encapsulation || skb_has_frag_list(skb) ||
> +         !skb_mac_header_was_set(skb) ||
> +         !skb_transport_header_was_set(skb))

Conversely, they last two conditions are less obvious. Are they not
always true for a TSO packet?

> +             return 0;
> +
> +     th = skb_header_pointer(skb, skb_transport_offset(skb), sizeof(_tcph),
> +                             &_tcph);
> +     if (!th || th->doff < sizeof(*th) / 4)
> +             return 0;
> +
> +     hdr_len = skb_transport_header(skb) - skb_mac_header(skb) +
> +               th->doff * 4;
> +
> +     /*
> +      * The output stays a GSO skb, so the device has to offload the GSO
> +      * type.  The caller's features cannot tell that: this skb is over
> +      * the device limits, so gso_features_check() has cleared their GSO
> +      * bits.  Compute the features again without the limit checks.
> +      */
> +     features = __netif_skb_features(skb, false);
> +     if (!net_gso_ok(features | NETIF_F_GSO_ROBUST,
> +                     skb_shinfo(skb)->gso_type))
> +             return 0;
> +
> +     /*
> +      * The output is a plain GSO skb, so its whole length lands in the
> +      * 16-bit L3 length field: only a BIG TCP skb may exceed the legacy
> +      * GSO size, and this path does not emit one.
> +      */
> +     gso_max_size = netif_get_gso_max_size(dev, vlan_get_protocol(skb));

Third time this is now called in validate_xmit_skb. Not sure if that can
easily be avoided.

> +     gso_max_size = min(gso_max_size, GSO_LEGACY_MAX_SIZE);
> +
> +     /*
> +      * gso_within_dev_limits() accepts gso_segs == gso_max_segs but
> +      * rejects skb->len >= gso_max_size, so only the size bound needs - 1.
> +      * The min() keeps that subtraction from wrapping when the device
> +      * limit is smaller than the headers.
> +      */
> +     max_segs = (gso_max_size - min(gso_max_size, hdr_len + 1)) / mss;
> +     max_segs = min(max_segs, READ_ONCE(dev->gso_max_segs));
> +
> +     return max_segs;
> +}
> +
>  static int xmit_one(struct sk_buff *skb, struct net_device *dev,
>                   struct netdev_queue *txq, bool more)
>  {
> @@ -4097,9 +4154,13 @@ static struct sk_buff *validate_xmit_skb(struct 
> sk_buff *skb, struct net_device
>               goto out_null;
>  
>       if (netif_needs_gso(skb, features)) {
> +             unsigned int max_segs = 0;
>               struct sk_buff *segs;
>  
> -             segs = skb_gso_segment(skb, features);
> +             if (unlikely(!gso_within_dev_limits(skb, dev)))
> +                     max_segs = skb_gso_output_max_segs(skb, dev);
> +
> +             segs = __skb_gso_segment(skb, features, true, max_segs);
>               if (IS_ERR(segs)) {
>                       goto out_kfree_skb;
>               } else if (segs) {
> -- 
> 2.47.3
> 


_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to