/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 | } |