Coverage Report

Created: 2025-07-18 06:07

/src/openvswitch/lib/dp-packet-gso.c
Line
Count
Source (jump to first uncovered line)
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 "dp-packet.h"
22
#include "dp-packet-gso.h"
23
#include "netdev-provider.h"
24
#include "openvswitch/vlog.h"
25
26
VLOG_DEFINE_THIS_MODULE(dp_packet_gso);
27
28
/* Retuns a new packet that is a segment of packet 'p'.
29
 *
30
 * The new packet is initialized with 'hdr_len' bytes from the
31
 * start of packet 'p' and then appended with 'data_len' bytes
32
 * from the 'data' buffer.
33
 *
34
 * Note: The packet headers are not updated. */
35
static struct dp_packet *
36
dp_packet_gso_seg_new(const struct dp_packet *p, size_t hdr_len,
37
                      const char *data, size_t data_len)
38
0
{
39
0
    struct dp_packet *seg = dp_packet_new_with_headroom(hdr_len + data_len,
40
0
                                                        dp_packet_headroom(p));
41
42
    /* Append the original packet headers and then the payload. */
43
0
    dp_packet_put(seg, dp_packet_data(p), hdr_len);
44
0
    dp_packet_put(seg, data, data_len);
45
46
    /* The new segment should have the same offsets. */
47
0
    seg->l2_5_ofs = p->l2_5_ofs;
48
0
    seg->l3_ofs = p->l3_ofs;
49
0
    seg->l4_ofs = p->l4_ofs;
50
0
    seg->inner_l3_ofs = p->inner_l3_ofs;
51
0
    seg->inner_l4_ofs = p->inner_l4_ofs;
52
53
    /* The protocol headers remain the same, so preserve hash and mark. */
54
0
    seg->has_hash = p->has_hash;
55
0
    *dp_packet_rss_ptr(seg) = *dp_packet_rss_ptr(p);
56
0
    seg->has_mark = p->has_mark;
57
0
    *dp_packet_flow_mark_ptr(seg) = *dp_packet_flow_mark_ptr(p);
58
59
0
    seg->offloads = p->offloads;
60
61
0
    return seg;
62
0
}
63
64
/* Returns the calculated number of TCP segments in packet 'p'. */
65
int
66
dp_packet_gso_nr_segs(struct dp_packet *p)
67
0
{
68
0
    uint16_t segsz = dp_packet_get_tso_segsz(p);
69
0
    const char *data_tail;
70
0
    const char *data_pos;
71
72
0
    if (dp_packet_tunnel(p)) {
73
0
        data_pos = dp_packet_get_inner_tcp_payload(p);
74
0
    } else {
75
0
        data_pos = dp_packet_get_tcp_payload(p);
76
0
    }
77
0
    data_tail = (char *) dp_packet_tail(p) - dp_packet_l2_pad_size(p);
78
79
0
    return DIV_ROUND_UP(data_tail - data_pos, segsz);
80
0
}
81
82
/* Perform software segmentation on packet 'p'.
83
 *
84
 * Segments packet 'p' into the array of preallocated batches in 'batches',
85
 * updating the 'batches' pointer as needed and returns true.
86
 *
87
 * Returns false if the packet cannot be segmented. */
88
bool
89
dp_packet_gso(struct dp_packet *p, struct dp_packet_batch **batches)
90
0
{
91
0
    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
92
0
    struct dp_packet_batch *curr_batch = *batches;
93
0
    struct tcp_header *tcp_hdr;
94
0
    struct ip_header *ip_hdr;
95
0
    uint16_t inner_ip_id = 0;
96
0
    uint16_t outer_ip_id = 0;
97
0
    struct dp_packet *seg;
98
0
    uint16_t tcp_offset;
99
0
    uint16_t tso_segsz;
100
0
    uint32_t tcp_seq;
101
0
    bool outer_ipv4;
102
0
    int hdr_len;
103
0
    int seg_len;
104
0
    bool udp_tnl = dp_packet_tunnel_vxlan(p)
105
0
                   || dp_packet_tunnel_geneve(p);
106
0
    bool gre_tnl = dp_packet_tunnel_gre(p);
107
108
0
    tso_segsz = dp_packet_get_tso_segsz(p);
109
0
    if (!tso_segsz) {
110
0
        VLOG_WARN_RL(&rl, "GSO packet with len %d with no segment size.",
111
0
                     dp_packet_size(p));
112
0
        return false;
113
0
    }
114
115
0
    if (udp_tnl || gre_tnl) {
116
0
        ip_hdr = dp_packet_inner_l3(p);
117
0
        if (IP_VER(ip_hdr->ip_ihl_ver) == 4) {
118
0
            inner_ip_id = ntohs(ip_hdr->ip_id);
119
0
        }
120
121
0
        tcp_hdr = dp_packet_inner_l4(p);
122
0
    } else {
123
0
        tcp_hdr = dp_packet_l4(p);
124
0
    }
125
126
0
    ip_hdr = dp_packet_l3(p);
127
0
    outer_ipv4 = IP_VER(ip_hdr->ip_ihl_ver) == 4;
128
0
    if (outer_ipv4) {
129
0
        outer_ip_id = ntohs(ip_hdr->ip_id);
130
0
    }
131
132
0
    tcp_offset = TCP_OFFSET(tcp_hdr->tcp_ctl);
133
0
    tcp_seq = ntohl(get_16aligned_be32(&tcp_hdr->tcp_seq));
134
0
    hdr_len = ((char *) tcp_hdr - (char *) dp_packet_eth(p))
135
0
              + tcp_offset * 4;
136
0
    const char *data_tail = (char *) dp_packet_tail(p)
137
0
                            - dp_packet_l2_pad_size(p);
138
0
    const char *data_pos = (char *) tcp_hdr + tcp_offset * 4;
139
0
    int n_segs = dp_packet_gso_nr_segs(p);
140
141
0
    for (int i = 0; i < n_segs; i++) {
142
0
        seg_len = data_tail - data_pos;
143
0
        if (seg_len > tso_segsz) {
144
0
            seg_len = tso_segsz;
145
0
        }
146
147
0
        seg = dp_packet_gso_seg_new(p, hdr_len, data_pos, seg_len);
148
0
        data_pos += seg_len;
149
150
0
        if (udp_tnl) {
151
            /* Update tunnel UDP header length. */
152
0
            struct udp_header *tnl_hdr;
153
154
0
            tnl_hdr = dp_packet_l4(seg);
155
0
            tnl_hdr->udp_len = htons(dp_packet_l4_size(seg));
156
0
            dp_packet_l4_checksum_set_partial(seg);
157
0
        }
158
159
0
        if (udp_tnl || gre_tnl) {
160
            /* Update tunnel inner L3 header. */
161
0
            ip_hdr = dp_packet_inner_l3(seg);
162
0
            if (IP_VER(ip_hdr->ip_ihl_ver) == 4) {
163
0
                ip_hdr->ip_tot_len = htons(dp_packet_inner_l3_size(seg));
164
0
                ip_hdr->ip_id = htons(inner_ip_id);
165
0
                ip_hdr->ip_csum = 0;
166
0
                dp_packet_inner_ip_checksum_set_partial(seg);
167
0
                inner_ip_id++;
168
0
            } else {
169
0
                struct ovs_16aligned_ip6_hdr *ip6_hdr;
170
171
0
                ip6_hdr = dp_packet_inner_l3(seg);
172
0
                ip6_hdr->ip6_ctlun.ip6_un1.ip6_un1_plen
173
0
                    = htons(dp_packet_inner_l3_size(seg) - sizeof *ip6_hdr);
174
0
            }
175
0
        }
176
177
        /* Update L3 header. */
178
0
        if (outer_ipv4) {
179
0
            ip_hdr = dp_packet_l3(seg);
180
0
            ip_hdr->ip_tot_len = htons(dp_packet_l3_size(seg));
181
0
            ip_hdr->ip_id = htons(outer_ip_id);
182
0
            ip_hdr->ip_csum = 0;
183
0
            dp_packet_ip_checksum_set_partial(seg);
184
0
            outer_ip_id++;
185
0
        } else {
186
0
            struct ovs_16aligned_ip6_hdr *ip6_hdr = dp_packet_l3(seg);
187
188
0
            ip6_hdr->ip6_ctlun.ip6_un1.ip6_un1_plen
189
0
                = htons(dp_packet_l3_size(seg) - sizeof *ip6_hdr);
190
0
        }
191
192
        /* Update L4 header. */
193
0
        if (udp_tnl || gre_tnl) {
194
0
            tcp_hdr = dp_packet_inner_l4(seg);
195
0
            dp_packet_inner_l4_checksum_set_partial(seg);
196
0
        } else {
197
0
            tcp_hdr = dp_packet_l4(seg);
198
0
            dp_packet_l4_checksum_set_partial(seg);
199
0
        }
200
0
        put_16aligned_be32(&tcp_hdr->tcp_seq, htonl(tcp_seq));
201
0
        tcp_seq += seg_len;
202
0
        if (OVS_LIKELY(i < (n_segs - 1))) {
203
            /* Reset flags PUSH and FIN unless it is the last segment. */
204
0
            uint16_t tcp_flags = TCP_FLAGS(tcp_hdr->tcp_ctl)
205
0
                                 & ~(TCP_PSH | TCP_FIN);
206
0
            tcp_hdr->tcp_ctl = TCP_CTL(tcp_flags, tcp_offset);
207
0
        }
208
209
0
        if (gre_tnl) {
210
0
            struct gre_base_hdr *ghdr;
211
212
0
            ghdr = dp_packet_l4(seg);
213
214
0
            if (ghdr->flags & htons(GRE_CSUM)) {
215
0
                ovs_be16 *csum_opt = (ovs_be16 *) (ghdr + 1);
216
0
                *csum_opt = 0;
217
0
                *csum_opt = csum(ghdr, dp_packet_l4_size(seg));
218
0
            }
219
0
        }
220
221
0
        if (dp_packet_batch_is_full(curr_batch)) {
222
0
            curr_batch++;
223
0
        }
224
225
0
        dp_packet_batch_add(curr_batch, seg);
226
0
    }
227
228
0
    *batches = curr_batch;
229
0
    return true;
230
0
}