Skip to content

Commit 07b26c9

Browse files
klassertdavem330
authored andcommitted
gso: Support partial splitting at the frag_list pointer
Since commit 8a29111 ("net: gro: allow to build full sized skb") gro may build buffers with a frag_list. This can hurt forwarding because most NICs can't offload such packets, they need to be segmented in software. This patch splits buffers with a frag_list at the frag_list pointer into buffers that can be TSO offloaded. Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com> Acked-by: Alexander Duyck <alexander.h.duyck@intel.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent e867e87 commit 07b26c9

File tree

6 files changed

+69
-26
lines changed

6 files changed

+69
-26
lines changed

net/core/skbuff.c

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,18 +3097,39 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
30973097
sg = !!(features & NETIF_F_SG);
30983098
csum = !!can_checksum_protocol(features, proto);
30993099

3100-
/* GSO partial only requires that we trim off any excess that
3101-
* doesn't fit into an MSS sized block, so take care of that
3102-
* now.
3103-
*/
3104-
if (sg && csum && (features & NETIF_F_GSO_PARTIAL)) {
3100+
if (sg && csum && (mss != GSO_BY_FRAGS)) {
3101+
if (!(features & NETIF_F_GSO_PARTIAL)) {
3102+
struct sk_buff *iter;
3103+
3104+
if (!list_skb ||
3105+
!net_gso_ok(features, skb_shinfo(head_skb)->gso_type))
3106+
goto normal;
3107+
3108+
/* Split the buffer at the frag_list pointer.
3109+
* This is based on the assumption that all
3110+
* buffers in the chain excluding the last
3111+
* containing the same amount of data.
3112+
*/
3113+
skb_walk_frags(head_skb, iter) {
3114+
if (skb_headlen(iter))
3115+
goto normal;
3116+
3117+
len -= iter->len;
3118+
}
3119+
}
3120+
3121+
/* GSO partial only requires that we trim off any excess that
3122+
* doesn't fit into an MSS sized block, so take care of that
3123+
* now.
3124+
*/
31053125
partial_segs = len / mss;
31063126
if (partial_segs > 1)
31073127
mss *= partial_segs;
31083128
else
31093129
partial_segs = 0;
31103130
}
31113131

3132+
normal:
31123133
headroom = skb_headroom(head_skb);
31133134
pos = skb_headlen(head_skb);
31143135

@@ -3300,21 +3321,29 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
33003321
*/
33013322
segs->prev = tail;
33023323

3303-
/* Update GSO info on first skb in partial sequence. */
33043324
if (partial_segs) {
3325+
struct sk_buff *iter;
33053326
int type = skb_shinfo(head_skb)->gso_type;
3327+
unsigned short gso_size = skb_shinfo(head_skb)->gso_size;
33063328

33073329
/* Update type to add partial and then remove dodgy if set */
3308-
type |= SKB_GSO_PARTIAL;
3330+
type |= (features & NETIF_F_GSO_PARTIAL) / NETIF_F_GSO_PARTIAL * SKB_GSO_PARTIAL;
33093331
type &= ~SKB_GSO_DODGY;
33103332

33113333
/* Update GSO info and prepare to start updating headers on
33123334
* our way back down the stack of protocols.
33133335
*/
3314-
skb_shinfo(segs)->gso_size = skb_shinfo(head_skb)->gso_size;
3315-
skb_shinfo(segs)->gso_segs = partial_segs;
3316-
skb_shinfo(segs)->gso_type = type;
3317-
SKB_GSO_CB(segs)->data_offset = skb_headroom(segs) + doffset;
3336+
for (iter = segs; iter; iter = iter->next) {
3337+
skb_shinfo(iter)->gso_size = gso_size;
3338+
skb_shinfo(iter)->gso_segs = partial_segs;
3339+
skb_shinfo(iter)->gso_type = type;
3340+
SKB_GSO_CB(iter)->data_offset = skb_headroom(iter) + doffset;
3341+
}
3342+
3343+
if (tail->len - doffset <= gso_size)
3344+
skb_shinfo(tail)->gso_size = 0;
3345+
else if (tail != segs)
3346+
skb_shinfo(tail)->gso_segs = DIV_ROUND_UP(tail->len - doffset, gso_size);
33183347
}
33193348

33203349
/* Following permits correct backpressure, for protocols

net/ipv4/af_inet.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,7 +1192,7 @@ EXPORT_SYMBOL(inet_sk_rebuild_header);
11921192
struct sk_buff *inet_gso_segment(struct sk_buff *skb,
11931193
netdev_features_t features)
11941194
{
1195-
bool udpfrag = false, fixedid = false, encap;
1195+
bool udpfrag = false, fixedid = false, gso_partial, encap;
11961196
struct sk_buff *segs = ERR_PTR(-EINVAL);
11971197
const struct net_offload *ops;
11981198
unsigned int offset = 0;
@@ -1245,6 +1245,8 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,
12451245
if (IS_ERR_OR_NULL(segs))
12461246
goto out;
12471247

1248+
gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL);
1249+
12481250
skb = segs;
12491251
do {
12501252
iph = (struct iphdr *)(skb_mac_header(skb) + nhoff);
@@ -1259,9 +1261,13 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,
12591261
iph->id = htons(id);
12601262
id += skb_shinfo(skb)->gso_segs;
12611263
}
1262-
tot_len = skb_shinfo(skb)->gso_size +
1263-
SKB_GSO_CB(skb)->data_offset +
1264-
skb->head - (unsigned char *)iph;
1264+
1265+
if (gso_partial)
1266+
tot_len = skb_shinfo(skb)->gso_size +
1267+
SKB_GSO_CB(skb)->data_offset +
1268+
skb->head - (unsigned char *)iph;
1269+
else
1270+
tot_len = skb->len - nhoff;
12651271
} else {
12661272
if (!fixedid)
12671273
iph->id = htons(id++);

net/ipv4/gre_offload.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ static struct sk_buff *gre_gso_segment(struct sk_buff *skb,
2424
__be16 protocol = skb->protocol;
2525
u16 mac_len = skb->mac_len;
2626
int gre_offset, outer_hlen;
27-
bool need_csum, ufo;
27+
bool need_csum, ufo, gso_partial;
2828

2929
if (!skb->encapsulation)
3030
goto out;
@@ -69,6 +69,8 @@ static struct sk_buff *gre_gso_segment(struct sk_buff *skb,
6969
goto out;
7070
}
7171

72+
gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL);
73+
7274
outer_hlen = skb_tnl_header_len(skb);
7375
gre_offset = outer_hlen - tnl_hlen;
7476
skb = segs;
@@ -96,7 +98,7 @@ static struct sk_buff *gre_gso_segment(struct sk_buff *skb,
9698
greh = (struct gre_base_hdr *)skb_transport_header(skb);
9799
pcsum = (__sum16 *)(greh + 1);
98100

99-
if (skb_is_gso(skb)) {
101+
if (gso_partial) {
100102
unsigned int partial_adj;
101103

102104
/* Adjust checksum to account for the fact that

net/ipv4/tcp_offload.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,6 @@ struct sk_buff *tcp_gso_segment(struct sk_buff *skb,
9090
goto out;
9191
}
9292

93-
/* GSO partial only requires splitting the frame into an MSS
94-
* multiple and possibly a remainder. So update the mss now.
95-
*/
96-
if (features & NETIF_F_GSO_PARTIAL)
97-
mss = skb->len - (skb->len % mss);
98-
9993
copy_destructor = gso_skb->destructor == tcp_wfree;
10094
ooo_okay = gso_skb->ooo_okay;
10195
/* All segments but the first should have ooo_okay cleared */
@@ -108,6 +102,13 @@ struct sk_buff *tcp_gso_segment(struct sk_buff *skb,
108102
/* Only first segment might have ooo_okay set */
109103
segs->ooo_okay = ooo_okay;
110104

105+
/* GSO partial and frag_list segmentation only requires splitting
106+
* the frame into an MSS multiple and possibly a remainder, both
107+
* cases return a GSO skb. So update the mss now.
108+
*/
109+
if (skb_is_gso(segs))
110+
mss *= skb_shinfo(segs)->gso_segs;
111+
111112
delta = htonl(oldlen + (thlen + mss));
112113

113114
skb = segs;

net/ipv4/udp_offload.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb,
2121
__be16 new_protocol, bool is_ipv6)
2222
{
2323
int tnl_hlen = skb_inner_mac_header(skb) - skb_transport_header(skb);
24-
bool remcsum, need_csum, offload_csum, ufo;
24+
bool remcsum, need_csum, offload_csum, ufo, gso_partial;
2525
struct sk_buff *segs = ERR_PTR(-EINVAL);
2626
struct udphdr *uh = udp_hdr(skb);
2727
u16 mac_offset = skb->mac_header;
@@ -88,6 +88,8 @@ static struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb,
8888
goto out;
8989
}
9090

91+
gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL);
92+
9193
outer_hlen = skb_tnl_header_len(skb);
9294
udp_offset = outer_hlen - tnl_hlen;
9395
skb = segs;
@@ -117,7 +119,7 @@ static struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb,
117119
* will be using a length value equal to only one MSS sized
118120
* segment instead of the entire frame.
119121
*/
120-
if (skb_is_gso(skb)) {
122+
if (gso_partial) {
121123
uh->len = htons(skb_shinfo(skb)->gso_size +
122124
SKB_GSO_CB(skb)->data_offset +
123125
skb->head - (unsigned char *)uh);

net/ipv6/ip6_offload.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
6969
int offset = 0;
7070
bool encap, udpfrag;
7171
int nhoff;
72+
bool gso_partial;
7273

7374
skb_reset_network_header(skb);
7475
nhoff = skb_network_header(skb) - skb_mac_header(skb);
@@ -101,9 +102,11 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
101102
if (IS_ERR(segs))
102103
goto out;
103104

105+
gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL);
106+
104107
for (skb = segs; skb; skb = skb->next) {
105108
ipv6h = (struct ipv6hdr *)(skb_mac_header(skb) + nhoff);
106-
if (skb_is_gso(skb))
109+
if (gso_partial)
107110
payload_len = skb_shinfo(skb)->gso_size +
108111
SKB_GSO_CB(skb)->data_offset +
109112
skb->head - (unsigned char *)(ipv6h + 1);

0 commit comments

Comments
 (0)