Skip to content

Commit

Permalink
net: add support for segmenting TCP fraglist GSO packets
Browse files Browse the repository at this point in the history
Preparation for adding TCP fraglist GRO support. It expects packets to be
combined in a similar way as UDP fraglist GSO packets.
For IPv4 packets, NAT is handled in the same way as UDP fraglist GSO.

Acked-by: Paolo Abeni <pabeni@redhat.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
Reviewed-by: David Ahern <dsahern@kernel.org>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
  • Loading branch information
Felix Fietkau authored and Paolo Abeni committed May 6, 2024
1 parent 8928756 commit bee88cd
Showing 2 changed files with 125 additions and 0 deletions.
67 changes: 67 additions & 0 deletions net/ipv4/tcp_offload.c
Original file line number Diff line number Diff line change
@@ -28,6 +28,70 @@ static void tcp_gso_tstamp(struct sk_buff *skb, unsigned int ts_seq,
}
}

static void __tcpv4_gso_segment_csum(struct sk_buff *seg,
__be32 *oldip, __be32 newip,
__be16 *oldport, __be16 newport)
{
struct tcphdr *th;
struct iphdr *iph;

if (*oldip == newip && *oldport == newport)
return;

th = tcp_hdr(seg);
iph = ip_hdr(seg);

inet_proto_csum_replace4(&th->check, seg, *oldip, newip, true);
inet_proto_csum_replace2(&th->check, seg, *oldport, newport, false);
*oldport = newport;

csum_replace4(&iph->check, *oldip, newip);
*oldip = newip;
}

static struct sk_buff *__tcpv4_gso_segment_list_csum(struct sk_buff *segs)
{
const struct tcphdr *th;
const struct iphdr *iph;
struct sk_buff *seg;
struct tcphdr *th2;
struct iphdr *iph2;

seg = segs;
th = tcp_hdr(seg);
iph = ip_hdr(seg);
th2 = tcp_hdr(seg->next);
iph2 = ip_hdr(seg->next);

if (!(*(const u32 *)&th->source ^ *(const u32 *)&th2->source) &&
iph->daddr == iph2->daddr && iph->saddr == iph2->saddr)
return segs;

while ((seg = seg->next)) {
th2 = tcp_hdr(seg);
iph2 = ip_hdr(seg);

__tcpv4_gso_segment_csum(seg,
&iph2->saddr, iph->saddr,
&th2->source, th->source);
__tcpv4_gso_segment_csum(seg,
&iph2->daddr, iph->daddr,
&th2->dest, th->dest);
}

return segs;
}

static struct sk_buff *__tcp4_gso_segment_list(struct sk_buff *skb,
netdev_features_t features)
{
skb = skb_segment_list(skb, features, skb_mac_header_len(skb));
if (IS_ERR(skb))
return skb;

return __tcpv4_gso_segment_list_csum(skb);
}

static struct sk_buff *tcp4_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
@@ -37,6 +101,9 @@ static struct sk_buff *tcp4_gso_segment(struct sk_buff *skb,
if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
return ERR_PTR(-EINVAL);

if (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST)
return __tcp4_gso_segment_list(skb, features);

if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
const struct iphdr *iph = ip_hdr(skb);
struct tcphdr *th = tcp_hdr(skb);
58 changes: 58 additions & 0 deletions net/ipv6/tcpv6_offload.c
Original file line number Diff line number Diff line change
@@ -40,6 +40,61 @@ INDIRECT_CALLABLE_SCOPE int tcp6_gro_complete(struct sk_buff *skb, int thoff)
return 0;
}

static void __tcpv6_gso_segment_csum(struct sk_buff *seg,
__be16 *oldport, __be16 newport)
{
struct tcphdr *th;

if (*oldport == newport)
return;

th = tcp_hdr(seg);
inet_proto_csum_replace2(&th->check, seg, *oldport, newport, false);
*oldport = newport;
}

static struct sk_buff *__tcpv6_gso_segment_list_csum(struct sk_buff *segs)
{
const struct tcphdr *th;
const struct ipv6hdr *iph;
struct sk_buff *seg;
struct tcphdr *th2;
struct ipv6hdr *iph2;

seg = segs;
th = tcp_hdr(seg);
iph = ipv6_hdr(seg);
th2 = tcp_hdr(seg->next);
iph2 = ipv6_hdr(seg->next);

if (!(*(const u32 *)&th->source ^ *(const u32 *)&th2->source) &&
ipv6_addr_equal(&iph->saddr, &iph2->saddr) &&
ipv6_addr_equal(&iph->daddr, &iph2->daddr))
return segs;

while ((seg = seg->next)) {
th2 = tcp_hdr(seg);
iph2 = ipv6_hdr(seg);

iph2->saddr = iph->saddr;
iph2->daddr = iph->daddr;
__tcpv6_gso_segment_csum(seg, &th2->source, th->source);
__tcpv6_gso_segment_csum(seg, &th2->dest, th->dest);
}

return segs;
}

static struct sk_buff *__tcp6_gso_segment_list(struct sk_buff *skb,
netdev_features_t features)
{
skb = skb_segment_list(skb, features, skb_mac_header_len(skb));
if (IS_ERR(skb))
return skb;

return __tcpv6_gso_segment_list_csum(skb);
}

static struct sk_buff *tcp6_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
@@ -51,6 +106,9 @@ static struct sk_buff *tcp6_gso_segment(struct sk_buff *skb,
if (!pskb_may_pull(skb, sizeof(*th)))
return ERR_PTR(-EINVAL);

if (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST)
return __tcp6_gso_segment_list(skb, features);

if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
struct tcphdr *th = tcp_hdr(skb);

0 comments on commit bee88cd

Please sign in to comment.