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