Coverage Report

Created: 2026-07-16 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openvswitch/lib/dp-packet-gso.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2023 Red Hat, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at:
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <config.h>
18
#include <stdlib.h>
19
#include <string.h>
20
21
#include "coverage.h"
22
#include "dp-packet.h"
23
#include "dp-packet-gso.h"
24
#include "netdev-provider.h"
25
#include "openvswitch/vlog.h"
26
27
VLOG_DEFINE_THIS_MODULE(dp_packet_gso);
28
29
COVERAGE_DEFINE(dp_packet_gso);
30
COVERAGE_DEFINE(dp_packet_partial_gso);
31
32
/* Retuns a new packet that is a segment of packet 'p'.
33
 *
34
 * The new packet is initialized with 'hdr_len' bytes from the
35
 * start of packet 'p' and then appended with 'data_len' bytes
36
 * from the packet 'p' at offset 'data_off'.
37
 *
38
 * Note: The packet headers are not updated. */
39
static struct dp_packet *
40
dp_packet_gso_seg_new(const struct dp_packet *p, size_t hdr_len,
41
                      size_t data_off, size_t data_len)
42
0
{
43
0
    struct dp_packet *seg = dp_packet_new_with_headroom(hdr_len + data_len,
44
0
                                                        dp_packet_headroom(p));
45
46
    /* Append the original packet headers and then the payload. */
47
0
    dp_packet_put(seg, dp_packet_data(p), hdr_len);
48
0
    dp_packet_put(seg, (char *) dp_packet_data(p) + data_off, data_len);
49
50
    /* The new segment should have the same offsets. */
51
0
    seg->l2_5_ofs = p->l2_5_ofs;
52
0
    seg->l3_ofs = p->l3_ofs;
53
0
    seg->l4_ofs = p->l4_ofs;
54
0
    seg->inner_l3_ofs = p->inner_l3_ofs;
55
0
    seg->inner_l4_ofs = p->inner_l4_ofs;
56
57
    /* The protocol headers remain the same, so preserve hash and mark. */
58
0
    seg->has_hash = p->has_hash;
59
0
    *dp_packet_rss_ptr(seg) = *dp_packet_rss_ptr(p);
60
0
    seg->has_mark = p->has_mark;
61
0
    *dp_packet_flow_mark_ptr(seg) = *dp_packet_flow_mark_ptr(p);
62
63
0
    seg->offloads = p->offloads;
64
65
0
    return seg;
66
0
}
67
68
/* Returns the calculated number of TCP segments in packet 'p'. */
69
unsigned int
70
dp_packet_gso_nr_segs(struct dp_packet *p)
71
0
{
72
0
    uint16_t segsz = dp_packet_get_tso_segsz(p);
73
0
    uint32_t data_length;
74
75
0
    if (dp_packet_tunnel(p)) {
76
0
        data_length = dp_packet_get_inner_tcp_payload_length(p);
77
0
    } else {
78
0
        data_length = dp_packet_get_tcp_payload_length(p);
79
0
    }
80
81
0
    return DIV_ROUND_UP(data_length, segsz);
82
0
}
83
84
/* For partial segmentation, we try to pack as much data as we can in a first
85
 * packet (up to the final number of segments on the wire).
86
 * If there is still some data left, we need an extra "little" packet
87
 * (shorter than tso_segsz). */
88
unsigned int
89
dp_packet_gso_partial_nr_segs(struct dp_packet *p)
90
0
{
91
0
    if ((dp_packet_tunnel_geneve(p) || dp_packet_tunnel_vxlan(p))
92
0
        && dp_packet_l4_checksum_partial(p)
93
0
        && dp_packet_get_inner_tcp_payload_length(p)
94
0
           != dp_packet_gso_nr_segs(p) * dp_packet_get_tso_segsz(p)) {
95
0
        return 2;
96
0
    }
97
98
0
    return 1;
99
0
}
100
101
static void
102
dp_packet_gso_update_segment(struct dp_packet *seg, unsigned int seg_no,
103
                             unsigned int n_segs, uint16_t tso_segsz,
104
                             bool udp_tnl, bool gre_tnl)
105
0
{
106
0
    struct tcp_header *tcp_hdr;
107
0
    struct ip_header *ip_hdr;
108
0
    uint32_t tcp_seq;
109
110
0
    if (udp_tnl) {
111
        /* Update tunnel UDP header length. */
112
0
        struct udp_header *tnl_hdr;
113
114
0
        tnl_hdr = dp_packet_l4(seg);
115
0
        tnl_hdr->udp_len = htons(dp_packet_l4_size(seg));
116
0
    }
117
118
0
    if (udp_tnl || gre_tnl) {
119
        /* Update tunnel inner L3 header. */
120
0
        ip_hdr = dp_packet_inner_l3(seg);
121
0
        if (IP_VER(ip_hdr->ip_ihl_ver) == 4) {
122
0
            ip_hdr->ip_tot_len = htons(dp_packet_inner_l3_size(seg));
123
0
            ip_hdr->ip_id = htons(ntohs(ip_hdr->ip_id) + seg_no);
124
0
            ip_hdr->ip_csum = 0;
125
0
            dp_packet_inner_ip_checksum_set_partial(seg);
126
0
        } else {
127
0
            struct ovs_16aligned_ip6_hdr *ip6_hdr;
128
129
0
            ip6_hdr = dp_packet_inner_l3(seg);
130
0
            ip6_hdr->ip6_ctlun.ip6_un1.ip6_un1_plen
131
0
                = htons(dp_packet_inner_l3_size(seg) - sizeof *ip6_hdr);
132
0
        }
133
0
    }
134
135
    /* Update L3 header. */
136
0
    ip_hdr = dp_packet_l3(seg);
137
0
    if (IP_VER(ip_hdr->ip_ihl_ver) == 4) {
138
0
        ip_hdr->ip_tot_len = htons(dp_packet_l3_size(seg));
139
0
        ip_hdr->ip_id = htons(ntohs(ip_hdr->ip_id) + seg_no);
140
0
        ip_hdr->ip_csum = 0;
141
0
        dp_packet_ip_checksum_set_partial(seg);
142
0
    } else {
143
0
        struct ovs_16aligned_ip6_hdr *ip6_hdr = dp_packet_l3(seg);
144
145
0
        ip6_hdr->ip6_ctlun.ip6_un1.ip6_un1_plen
146
0
            = htons(dp_packet_l3_size(seg) - sizeof *ip6_hdr);
147
0
    }
148
149
    /* Update L4 header. */
150
0
    if (udp_tnl || gre_tnl) {
151
0
        tcp_hdr = dp_packet_inner_l4(seg);
152
0
        dp_packet_inner_l4_checksum_set_partial(seg);
153
0
    } else {
154
0
        tcp_hdr = dp_packet_l4(seg);
155
0
        dp_packet_l4_checksum_set_partial(seg);
156
0
    }
157
0
    tcp_seq = ntohl(get_16aligned_be32(&tcp_hdr->tcp_seq));
158
0
    tcp_seq += seg_no * tso_segsz;
159
0
    put_16aligned_be32(&tcp_hdr->tcp_seq, htonl(tcp_seq));
160
161
0
    if (seg_no < (n_segs - 1) && !dp_packet_get_tso_segsz(seg)) {
162
0
        uint16_t tcp_offset = TCP_OFFSET(tcp_hdr->tcp_ctl);
163
        /* Reset flags PUSH and FIN unless it is the last segment. */
164
0
        uint16_t tcp_flags = TCP_FLAGS(tcp_hdr->tcp_ctl)
165
0
                             & ~(TCP_PSH | TCP_FIN);
166
0
        tcp_hdr->tcp_ctl = TCP_CTL(tcp_flags, tcp_offset);
167
0
    }
168
169
0
    if (gre_tnl) {
170
0
        struct gre_base_hdr *ghdr;
171
172
0
        ghdr = dp_packet_l4(seg);
173
174
0
        if (ghdr->flags & htons(GRE_CSUM)) {
175
0
            ovs_be16 *csum_opt = (ovs_be16 *) (ghdr + 1);
176
0
            *csum_opt = 0;
177
0
            *csum_opt = csum(ghdr, dp_packet_l4_size(seg));
178
0
        }
179
0
    }
180
0
}
181
182
static void
183
dp_packet_gso__(struct dp_packet *p, struct dp_packet_batch *batch,
184
                bool partial_seg)
185
0
{
186
0
    struct dp_packet *seg;
187
0
    unsigned int n_segs;
188
0
    uint16_t tso_segsz;
189
0
    size_t data_len;
190
0
    size_t hdr_len;
191
0
    bool udp_tnl;
192
0
    bool gre_tnl;
193
194
0
    tso_segsz = dp_packet_get_tso_segsz(p);
195
0
    ovs_assert(tso_segsz);
196
0
    n_segs = dp_packet_gso_nr_segs(p);
197
0
    udp_tnl = dp_packet_tunnel_vxlan(p) || dp_packet_tunnel_geneve(p);
198
0
    gre_tnl = dp_packet_tunnel_gre(p);
199
200
    /* Put back the first segment in the batch, it will be trimmed after
201
     * all segments have been copied. */
202
0
    dp_packet_batch_add(batch, p);
203
204
0
    if (n_segs <= 1) {
205
0
        return;
206
0
    }
207
208
0
    if (dp_packet_tunnel(p)) {
209
0
        hdr_len = (char *) dp_packet_get_inner_tcp_payload(p)
210
0
                  - (char *) dp_packet_eth(p);
211
0
        data_len = dp_packet_get_inner_tcp_payload_length(p);
212
0
    } else {
213
0
        hdr_len = (char *) dp_packet_get_tcp_payload(p)
214
0
                  - (char *) dp_packet_eth(p);
215
0
        data_len = dp_packet_get_tcp_payload_length(p);
216
0
    }
217
218
0
    if (partial_seg) {
219
0
        if (dp_packet_gso_partial_nr_segs(p) != 1) {
220
0
            goto last_seg;
221
0
        }
222
0
        goto first_seg;
223
0
    }
224
225
0
    for (unsigned int i = 1; i < n_segs - 1; i++) {
226
0
        seg = dp_packet_gso_seg_new(p, hdr_len, hdr_len + i * tso_segsz,
227
0
                                    tso_segsz);
228
0
        dp_packet_gso_update_segment(seg, i, n_segs, tso_segsz, udp_tnl,
229
0
                                     gre_tnl);
230
0
        dp_packet_batch_add(batch, seg);
231
0
    }
232
233
0
last_seg:
234
    /* Create the last segment. */
235
0
    seg = dp_packet_gso_seg_new(p, hdr_len, hdr_len + (n_segs - 1) * tso_segsz,
236
0
                                data_len - (n_segs - 1) * tso_segsz);
237
0
    dp_packet_gso_update_segment(seg, n_segs - 1, n_segs, tso_segsz, udp_tnl,
238
0
                                 gre_tnl);
239
0
    dp_packet_batch_add(batch, seg);
240
241
0
first_seg:
242
0
    if (partial_seg) {
243
0
        if (dp_packet_gso_partial_nr_segs(p) != 1) {
244
0
            dp_packet_set_size(p, hdr_len + (n_segs - 1) * tso_segsz);
245
0
            if (n_segs == 2) {
246
                /* No need to ask HW segmentation, we already did the job. */
247
0
                dp_packet_set_tso_segsz(p, 0);
248
0
            }
249
0
        }
250
0
    } else {
251
        /* Trim the first segment and reset TSO. */
252
0
        dp_packet_set_size(p, hdr_len + tso_segsz);
253
0
        dp_packet_set_tso_segsz(p, 0);
254
0
    }
255
0
    dp_packet_gso_update_segment(p, 0, n_segs, tso_segsz, udp_tnl, gre_tnl);
256
0
}
257
258
static void
259
dp_packet_gso_batch__(struct dp_packet_batch *batch, bool partial_seg)
260
0
{
261
0
    struct dp_packet_batch gso_batch;
262
0
    struct dp_packet *packet;
263
264
0
    dp_packet_batch_init(&gso_batch);
265
0
    gso_batch.trunc = batch->trunc;
266
267
0
    DP_PACKET_BATCH_FOR_EACH (k, packet, batch) {
268
0
        if (dp_packet_get_tso_segsz(packet)) {
269
0
            dp_packet_gso__(packet, &gso_batch, partial_seg);
270
0
            if (partial_seg) {
271
0
                COVERAGE_INC(dp_packet_partial_gso);
272
0
            } else {
273
0
                COVERAGE_INC(dp_packet_gso);
274
0
            }
275
0
        } else {
276
0
            dp_packet_batch_add(&gso_batch, packet);
277
0
        }
278
0
    }
279
280
0
    dp_packet_batch_swap(batch, &gso_batch);
281
0
    dp_packet_batch_destroy(&gso_batch);
282
0
}
283
284
/* Segment GSO packets and send them back to caller in the input batch. */
285
void
286
dp_packet_gso_batch(struct dp_packet_batch *b)
287
0
{
288
0
    dp_packet_gso_batch__(b, false);
289
0
}
290
291
/* Perform partial software segmentation on packet 'p'.
292
 *
293
 * For UDP tunnels, if the packet payload length is not aligned on the
294
 * segmentation size, segments the last segment of packet 'p' into the array
295
 * of preallocated batches in 'batches', updating the 'batches' pointer
296
 * as needed. */
297
void
298
dp_packet_gso_batch_partial(struct dp_packet_batch *b)
299
0
{
300
    dp_packet_gso_batch__(b, true);
301
0
}