net: gso: support re-segmentation of TCP GSO skbs

A caller which re-segments an oversized TCP GSO skb needs the engine to
group several MSS into one output skb, so let it pass how many MSS segments
each output skb may carry through __skb_gso_segment().  max_segs is a
per-call u8 in the skb_gso_cb scratch context, and only an unencapsulated
TCP skb without a frag_list sets it; every other caller passes zero.

Without max_segs, skb_segment() groups several MSS into one output skb only
for a device which advertises NETIF_F_GSO_PARTIAL, or for a frag_list which
splits into uniform pieces.  max_segs skips that test.

The output stays a GSO skb: gso_size keeps the original MSS and gso_segs the
number of MSS it holds, so a downstream device can still perform TSO.  The
caller keeps the output within the 64 KiB its L3 length field can express.

Reviewed-by: Willem de Bruijn <willemb@google.com>
Assisted-by: LLM
Signed-off-by: Wang Zhan <wang.zhan@smartx.com>
Link: https://patch.msgid.link/20261008100651.2534957-4-wang.zhan@smartx.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Wang Zhan authored and Paolo Abeni committed 2026-10-13 15:32:47 +02:00
1 parent 0bba48256b
commit c4076626ba
6 files changed
+21 -9

No files matched your search

+2 -1
View File
@@ -278,9 +278,10 @@ rx_handler_result_t tap_handle_frame(struct sk_buff **pskb)
if (q->flags & IFF_VNET_HDR)
features |= tap->tap_features;
if (netif_needs_gso(skb, features)) {
struct sk_buff *segs = __skb_gso_segment(skb, features, false);
struct sk_buff *segs;
struct sk_buff *next;
segs = __skb_gso_segment(skb, features, false, 0);
if (IS_ERR(segs)) {
drop_reason = SKB_DROP_REASON_SKB_GSO_SEG;
goto drop;
+5 -2
View File
@@ -21,6 +21,8 @@ struct skb_gso_cb {
__u16 csum_start;
/* Number of IPv4/IPv6 GSO handler entries for this packet. */
u8 recursion_counter;
/* Max MSS segs per output skb, 0 = no limit */
u8 max_segs;
};
#define SKB_GSO_CB_OFFSET 32
#define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET))
@@ -83,12 +85,13 @@ static inline __sum16 gso_make_checksum(struct sk_buff *skb, __wsum res)
}
struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
netdev_features_t features, bool tx_path);
netdev_features_t features, bool tx_path,
unsigned int max_segs);
static inline struct sk_buff *skb_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
return __skb_gso_segment(skb, features, true);
return __skb_gso_segment(skb, features, true, 0);
}
struct sk_buff *skb_eth_gso_segment(struct sk_buff *skb,
+1 -1
View File
@@ -613,7 +613,7 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
/* the GSO CB lays after the UDP one, no need to save and restore any
* CB fragment
*/
segs = __skb_gso_segment(skb, features, false);
segs = __skb_gso_segment(skb, features, false, 0);
if (IS_ERR_OR_NULL(segs)) {
drop_count = skb_shinfo(skb)->gso_segs;
goto drop;
+5 -1
View File
@@ -77,6 +77,8 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)
* @skb: buffer to segment
* @features: features for the output path (see dev->features)
* @tx_path: whether it is called in TX path
* @max_segs: maximum MSS segments per output GSO skb, 0 means no limit;
* set only for an unencapsulated TCP skb without a frag_list
*
* This function segments the given skb and returns a list of segments.
*
@@ -86,7 +88,8 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)
* Segmentation preserves SKB_GSO_CB_OFFSET bytes of previous skb cb.
*/
struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
netdev_features_t features, bool tx_path)
netdev_features_t features, bool tx_path,
unsigned int max_segs)
{
struct sk_buff *segs;
@@ -118,6 +121,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
SKB_GSO_CB(skb)->mac_offset = skb_headroom(skb);
SKB_GSO_CB(skb)->encap_level = 0;
SKB_GSO_CB(skb)->recursion_counter = 0;
SKB_GSO_CB(skb)->max_segs = min(max_segs, U8_MAX);
skb_reset_mac_header(skb);
skb_reset_mac_len(skb);
+7 -3
View File
@@ -4797,6 +4797,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
struct sk_buff *segs = NULL;
struct sk_buff *tail = NULL;
struct sk_buff *list_skb = skb_shinfo(head_skb)->frag_list;
unsigned int max_segs = SKB_GSO_CB(head_skb)->max_segs;
unsigned int mss = skb_shinfo(head_skb)->gso_size;
bool gso_by_frags = mss == GSO_BY_FRAGS;
unsigned int doffset = head_skb->data - skb_mac_header(head_skb);
@@ -4843,7 +4844,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
csum = !!can_checksum_protocol(features, proto);
if (sg && csum && !gso_by_frags) {
if (!(features & NETIF_F_GSO_PARTIAL)) {
if (!max_segs && !(features & NETIF_F_GSO_PARTIAL)) {
struct sk_buff *iter;
unsigned int frag_len;
@@ -4877,8 +4878,11 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
* doesn't fit into an MSS sized block, so take care of that
* now.
*/
DEBUG_NET_WARN_ON_ONCE(len / mss > GSO_MAX_SEGS);
partial_segs = min(len / mss, GSO_MAX_SEGS);
DEBUG_NET_WARN_ON_ONCE(!max_segs && len / mss > GSO_MAX_SEGS);
if (max_segs)
partial_segs = min(len / mss, max_segs);
else
partial_segs = min(len / mss, GSO_MAX_SEGS);
if (partial_segs > 1)
mss *= partial_segs;
else
+1 -1
View File
@@ -375,7 +375,7 @@ static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,
int err;
BUILD_BUG_ON(sizeof(*OVS_CB(skb)) > SKB_GSO_CB_OFFSET);
segs = __skb_gso_segment(skb, NETIF_F_SG, false);
segs = __skb_gso_segment(skb, NETIF_F_SG, false, 0);
if (IS_ERR(segs))
return PTR_ERR(segs);
if (segs == NULL)