Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright(c) 2010-2014 Intel Corporation
3 : : */
4 : :
5 : : #include <stddef.h>
6 : : #include <errno.h>
7 : :
8 : : #include <eal_export.h>
9 : : #include <rte_ether.h>
10 : :
11 : : #include "ip_frag_common.h"
12 : :
13 : : /* Fragment Offset */
14 : : #define RTE_IPV4_HDR_DF_SHIFT 14
15 : : #define RTE_IPV4_HDR_MF_SHIFT 13
16 : : #define RTE_IPV4_HDR_FO_SHIFT 3
17 : :
18 : : #define IPV4_HDR_DF_MASK (1 << RTE_IPV4_HDR_DF_SHIFT)
19 : : #define IPV4_HDR_MF_MASK (1 << RTE_IPV4_HDR_MF_SHIFT)
20 : :
21 : : #define IPV4_HDR_FO_ALIGN (1 << RTE_IPV4_HDR_FO_SHIFT)
22 : :
23 : : #define IPV4_HDR_MAX_LEN 60
24 : :
25 : 31 : static inline void __fill_ipv4hdr_frag(struct rte_ipv4_hdr *dst,
26 : : const struct rte_ipv4_hdr *src, uint16_t header_len,
27 : : uint16_t len, uint16_t fofs, uint16_t dofs, uint32_t mf)
28 : : {
29 [ - + - + ]: 62 : memcpy(dst, src, header_len);
30 : 31 : fofs = (uint16_t)(fofs + (dofs >> RTE_IPV4_HDR_FO_SHIFT));
31 : 31 : fofs = (uint16_t)(fofs | mf << RTE_IPV4_HDR_MF_SHIFT);
32 [ - + ]: 31 : dst->fragment_offset = rte_cpu_to_be_16(fofs);
33 [ - + ]: 31 : dst->total_length = rte_cpu_to_be_16(len);
34 : 31 : dst->hdr_checksum = 0;
35 : 31 : }
36 : :
37 : : static inline void __free_fragments(struct rte_mbuf *mb[], uint32_t num)
38 : : {
39 : : uint32_t i;
40 [ # # # # : 0 : for (i = 0; i != num; i++)
# # # # ]
41 : 0 : rte_pktmbuf_free(mb[i]);
42 : : }
43 : :
44 : 3 : static inline uint16_t __create_ipopt_frag_hdr(uint8_t *iph,
45 : : uint16_t ipopt_len, uint8_t *ipopt_frag_hdr)
46 : : {
47 : : uint16_t len = ipopt_len;
48 : : struct rte_ipv4_hdr *iph_opt = (struct rte_ipv4_hdr *)ipopt_frag_hdr;
49 : :
50 : : ipopt_len = 0;
51 : : memcpy(ipopt_frag_hdr, iph, sizeof(struct rte_ipv4_hdr));
52 : 3 : ipopt_frag_hdr += sizeof(struct rte_ipv4_hdr);
53 : :
54 : 3 : uint8_t *p_opt = iph + sizeof(struct rte_ipv4_hdr);
55 : :
56 [ + - ]: 8 : while (len > 0) {
57 [ - + ]: 8 : if (unlikely(*p_opt == RTE_IPV4_HDR_OPT_NOP)) {
58 : 0 : len--;
59 : 0 : p_opt++;
60 : 0 : continue;
61 [ + + ]: 8 : } else if (unlikely(*p_opt == RTE_IPV4_HDR_OPT_EOL))
62 : : break;
63 : :
64 [ + - + - ]: 5 : if (unlikely(p_opt[1] < 2 || p_opt[1] > len))
65 : : break;
66 : :
67 [ + + ]: 5 : if (RTE_IPV4_HDR_OPT_COPIED(*p_opt)) {
68 : 2 : memcpy(ipopt_frag_hdr + ipopt_len,
69 : : p_opt, p_opt[1]);
70 : 2 : ipopt_len += p_opt[1];
71 : : }
72 : :
73 : 5 : len -= p_opt[1];
74 : 5 : p_opt += p_opt[1];
75 : : }
76 : :
77 : 3 : len = RTE_ALIGN_CEIL(ipopt_len, RTE_IPV4_IHL_MULTIPLIER);
78 : 3 : memset(ipopt_frag_hdr + ipopt_len,
79 : 3 : RTE_IPV4_HDR_OPT_EOL, len - ipopt_len);
80 : : ipopt_len = len;
81 : 3 : iph_opt->ihl = (sizeof(struct rte_ipv4_hdr) + ipopt_len) /
82 : : RTE_IPV4_IHL_MULTIPLIER;
83 : :
84 : 3 : return ipopt_len;
85 : : }
86 : :
87 : : /**
88 : : * IPv4 fragmentation.
89 : : *
90 : : * This function implements the fragmentation of IPv4 packets.
91 : : *
92 : : * @param pkt_in
93 : : * The input packet.
94 : : * @param pkts_out
95 : : * Array storing the output fragments.
96 : : * @param mtu_size
97 : : * Size in bytes of the Maximum Transfer Unit (MTU) for the outgoing IPv4
98 : : * datagrams. This value includes the size of the IPv4 header.
99 : : * @param pool_direct
100 : : * MBUF pool used for allocating direct buffers for the output fragments.
101 : : * @param pool_indirect
102 : : * MBUF pool used for allocating indirect buffers for the output fragments.
103 : : * @return
104 : : * Upon successful completion - number of output fragments placed
105 : : * in the pkts_out array.
106 : : * Otherwise - (-1) * <errno>.
107 : : */
108 : : RTE_EXPORT_SYMBOL(rte_ipv4_fragment_packet)
109 : : int32_t
110 : 6 : rte_ipv4_fragment_packet(struct rte_mbuf *pkt_in,
111 : : struct rte_mbuf **pkts_out,
112 : : uint16_t nb_pkts_out,
113 : : uint16_t mtu_size,
114 : : struct rte_mempool *pool_direct,
115 : : struct rte_mempool *pool_indirect)
116 : : {
117 : : struct rte_mbuf *in_seg = NULL;
118 : : struct rte_ipv4_hdr *in_hdr;
119 : : uint32_t out_pkt_pos, in_seg_data_pos;
120 : : uint32_t more_in_segs;
121 : : uint16_t fragment_offset, flag_offset, frag_size, header_len;
122 : : uint16_t frag_bytes_remaining;
123 : : uint8_t ipopt_frag_hdr[IPV4_HDR_MAX_LEN];
124 : : uint16_t ipopt_len;
125 : :
126 : : /*
127 : : * Formal parameter checking.
128 : : */
129 [ + - + - ]: 6 : if (unlikely(pkt_in == NULL) || unlikely(pkts_out == NULL) ||
130 [ + - ]: 6 : unlikely(nb_pkts_out == 0) ||
131 [ + - + - ]: 6 : unlikely(pool_direct == NULL) || unlikely(pool_indirect == NULL) ||
132 [ + + ]: 6 : unlikely(mtu_size < RTE_ETHER_MIN_MTU))
133 : : return -EINVAL;
134 : :
135 : 5 : in_hdr = rte_pktmbuf_mtod(pkt_in, struct rte_ipv4_hdr *);
136 : 5 : header_len = (in_hdr->version_ihl & RTE_IPV4_HDR_IHL_MASK) *
137 : : RTE_IPV4_IHL_MULTIPLIER;
138 : :
139 : : /* Check IP header length */
140 [ + - ]: 5 : if (unlikely(pkt_in->data_len < header_len) ||
141 [ + - ]: 5 : unlikely(mtu_size < header_len))
142 : : return -EINVAL;
143 : :
144 : : /*
145 : : * Ensure the IP payload length of all fragments is aligned to a
146 : : * multiple of 8 bytes as per RFC791 section 2.3.
147 : : */
148 : 5 : frag_size = RTE_ALIGN_FLOOR((mtu_size - header_len),
149 : : IPV4_HDR_FO_ALIGN);
150 : :
151 [ - + ]: 5 : flag_offset = rte_cpu_to_be_16(in_hdr->fragment_offset);
152 : :
153 : : /* If Don't Fragment flag is set */
154 [ + - ]: 5 : if (unlikely ((flag_offset & IPV4_HDR_DF_MASK) != 0))
155 : : return -ENOTSUP;
156 : :
157 : : /* Check that pkts_out is big enough to hold all fragments */
158 [ + - ]: 5 : if (unlikely(frag_size * nb_pkts_out <
159 : : (uint16_t)(pkt_in->pkt_len - header_len)))
160 : : return -EINVAL;
161 : :
162 : : in_seg = pkt_in;
163 : 5 : in_seg_data_pos = header_len;
164 : : out_pkt_pos = 0;
165 : : fragment_offset = 0;
166 : :
167 : 5 : ipopt_len = header_len - sizeof(struct rte_ipv4_hdr);
168 [ + - ]: 5 : if (unlikely(ipopt_len > RTE_IPV4_HDR_OPT_MAX_LEN))
169 : : return -EINVAL;
170 : :
171 : : more_in_segs = 1;
172 [ + + ]: 20 : while (likely(more_in_segs)) {
173 : : struct rte_mbuf *out_pkt = NULL, *out_seg_prev = NULL;
174 : : uint32_t more_out_segs;
175 : : struct rte_ipv4_hdr *out_hdr;
176 : :
177 : : /* Allocate direct buffer */
178 : 15 : out_pkt = rte_pktmbuf_alloc(pool_direct);
179 [ - + ]: 15 : if (unlikely(out_pkt == NULL)) {
180 : : __free_fragments(pkts_out, out_pkt_pos);
181 : : return -ENOMEM;
182 : : }
183 : :
184 : : /* Reserve space for the IP header that will be built later */
185 : 15 : out_pkt->data_len = header_len;
186 : 15 : out_pkt->pkt_len = header_len;
187 : : frag_bytes_remaining = frag_size;
188 : :
189 : : out_seg_prev = out_pkt;
190 : : more_out_segs = 1;
191 [ + + ]: 30 : while (likely(more_out_segs && more_in_segs)) {
192 : : struct rte_mbuf *out_seg = NULL;
193 : : uint32_t len;
194 : :
195 : : /* Allocate indirect buffer */
196 : 15 : out_seg = rte_pktmbuf_alloc(pool_indirect);
197 [ - + ]: 15 : if (unlikely(out_seg == NULL)) {
198 : 0 : rte_pktmbuf_free(out_pkt);
199 : : __free_fragments(pkts_out, out_pkt_pos);
200 : : return -ENOMEM;
201 : : }
202 : 15 : out_seg_prev->next = out_seg;
203 : : out_seg_prev = out_seg;
204 : :
205 : : /* Prepare indirect buffer */
206 : 15 : rte_pktmbuf_attach(out_seg, in_seg);
207 : 15 : len = frag_bytes_remaining;
208 : 15 : if (len > (in_seg->data_len - in_seg_data_pos)) {
209 : : len = in_seg->data_len - in_seg_data_pos;
210 : : }
211 : 15 : out_seg->data_off = in_seg->data_off + in_seg_data_pos;
212 : 15 : out_seg->data_len = (uint16_t)len;
213 : 15 : out_pkt->pkt_len = (uint16_t)(len +
214 : 15 : out_pkt->pkt_len);
215 : 15 : out_pkt->nb_segs += 1;
216 : 15 : in_seg_data_pos += len;
217 : 15 : frag_bytes_remaining -= len;
218 : :
219 : : /* Current output packet (i.e. fragment) done ? */
220 [ + + ]: 15 : if (unlikely(frag_bytes_remaining == 0))
221 : : more_out_segs = 0;
222 : :
223 : : /* Current input segment done ? */
224 [ + + ]: 15 : if (unlikely(in_seg_data_pos == in_seg->data_len)) {
225 : 5 : in_seg = in_seg->next;
226 : : in_seg_data_pos = 0;
227 : :
228 [ + - ]: 5 : if (unlikely(in_seg == NULL))
229 : : more_in_segs = 0;
230 : : }
231 : : }
232 : :
233 : : /* Build the IP header */
234 : :
235 : 15 : out_hdr = rte_pktmbuf_mtod(out_pkt, struct rte_ipv4_hdr *);
236 : :
237 : 15 : __fill_ipv4hdr_frag(out_hdr, in_hdr, header_len,
238 : 15 : (uint16_t)out_pkt->pkt_len,
239 : : flag_offset, fragment_offset, more_in_segs);
240 : :
241 [ + + + + ]: 15 : if (unlikely((fragment_offset == 0) && (ipopt_len) &&
242 : : ((flag_offset & RTE_IPV4_HDR_OFFSET_MASK) == 0))) {
243 : 2 : ipopt_len = __create_ipopt_frag_hdr((uint8_t *)in_hdr,
244 : : ipopt_len, ipopt_frag_hdr);
245 : 2 : fragment_offset = (uint16_t)(fragment_offset +
246 : 2 : out_pkt->pkt_len - header_len);
247 : 2 : out_pkt->l3_len = header_len;
248 : :
249 : 2 : header_len = sizeof(struct rte_ipv4_hdr) + ipopt_len;
250 : 2 : in_hdr = (struct rte_ipv4_hdr *)ipopt_frag_hdr;
251 : : } else {
252 : 13 : fragment_offset = (uint16_t)(fragment_offset +
253 : 13 : out_pkt->pkt_len - header_len);
254 : 13 : out_pkt->l3_len = header_len;
255 : : }
256 : :
257 : : /* Write the fragment to the output list */
258 : 15 : pkts_out[out_pkt_pos] = out_pkt;
259 : 15 : out_pkt_pos ++;
260 : : }
261 : :
262 : 5 : return out_pkt_pos;
263 : : }
264 : :
265 : : /**
266 : : * IPv4 fragmentation by copy.
267 : : *
268 : : * This function implements the fragmentation of IPv4 packets by copy
269 : : * non-segmented mbuf.
270 : : * This function is mainly used to adapt Tx MBUF_FAST_FREE offload.
271 : : * MBUF_FAST_FREE: Device supports optimization for fast release of mbufs.
272 : : * When set, application must guarantee that per-queue all mbufs comes from
273 : : * the same mempool, has refcnt = 1, direct and non-segmented.
274 : : *
275 : : * @param pkt_in
276 : : * The input packet.
277 : : * @param pkts_out
278 : : * Array storing the output fragments.
279 : : * @param nb_pkts_out
280 : : * Number of fragments.
281 : : * @param mtu_size
282 : : * Size in bytes of the Maximum Transfer Unit (MTU) for the outgoing IPv4
283 : : * datagrams. This value includes the size of the IPv4 header.
284 : : * @param pool_direct
285 : : * MBUF pool used for allocating direct buffers for the output fragments.
286 : : * @return
287 : : * Upon successful completion - number of output fragments placed
288 : : * in the pkts_out array.
289 : : * Otherwise - (-1) * errno.
290 : : */
291 : : RTE_EXPORT_SYMBOL(rte_ipv4_fragment_copy_nonseg_packet)
292 : : int32_t
293 : 6 : rte_ipv4_fragment_copy_nonseg_packet(struct rte_mbuf *pkt_in,
294 : : struct rte_mbuf **pkts_out,
295 : : uint16_t nb_pkts_out,
296 : : uint16_t mtu_size,
297 : : struct rte_mempool *pool_direct)
298 : : {
299 : : struct rte_mbuf *in_seg = NULL;
300 : : struct rte_ipv4_hdr *in_hdr;
301 : : uint32_t out_pkt_pos, in_seg_data_pos;
302 : : uint32_t more_in_segs;
303 : : uint16_t fragment_offset, flag_offset, frag_size, header_len;
304 : : uint16_t frag_bytes_remaining;
305 : : uint8_t ipopt_frag_hdr[IPV4_HDR_MAX_LEN];
306 : : uint16_t ipopt_len;
307 : :
308 : : /*
309 : : * Formal parameter checking.
310 : : */
311 [ + - + - ]: 6 : if (unlikely(pkt_in == NULL) || unlikely(pkts_out == NULL) ||
312 [ + - + - ]: 6 : unlikely(nb_pkts_out == 0) || unlikely(pool_direct == NULL) ||
313 [ + - ]: 6 : unlikely(mtu_size < RTE_ETHER_MIN_MTU))
314 : : return -EINVAL;
315 : :
316 : 6 : in_hdr = rte_pktmbuf_mtod(pkt_in, struct rte_ipv4_hdr *);
317 : 6 : header_len = (in_hdr->version_ihl & RTE_IPV4_HDR_IHL_MASK) *
318 : : RTE_IPV4_IHL_MULTIPLIER;
319 : :
320 : : /* Check IP header length */
321 [ + - ]: 6 : if (unlikely(pkt_in->data_len < header_len) ||
322 [ + - ]: 6 : unlikely(mtu_size < header_len))
323 : : return -EINVAL;
324 : :
325 : : /*
326 : : * Ensure the IP payload length of all fragments is aligned to a
327 : : * multiple of 8 bytes as per RFC791 section 2.3.
328 : : */
329 : 6 : frag_size = RTE_ALIGN_FLOOR((mtu_size - header_len),
330 : : IPV4_HDR_FO_ALIGN);
331 : :
332 [ - + ]: 6 : flag_offset = rte_cpu_to_be_16(in_hdr->fragment_offset);
333 : :
334 : : /* If Don't Fragment flag is set */
335 [ + + ]: 6 : if (unlikely((flag_offset & IPV4_HDR_DF_MASK) != 0))
336 : : return -ENOTSUP;
337 : :
338 : : /* Check that pkts_out is big enough to hold all fragments */
339 [ + - ]: 5 : if (unlikely(frag_size * nb_pkts_out <
340 : : (uint16_t)(pkt_in->pkt_len - header_len)))
341 : : return -EINVAL;
342 : :
343 : : in_seg = pkt_in;
344 : 5 : in_seg_data_pos = header_len;
345 : : out_pkt_pos = 0;
346 : : fragment_offset = 0;
347 : :
348 : 5 : ipopt_len = header_len - sizeof(struct rte_ipv4_hdr);
349 [ + - ]: 5 : if (unlikely(ipopt_len > RTE_IPV4_HDR_OPT_MAX_LEN))
350 : : return -EINVAL;
351 : :
352 : : more_in_segs = 1;
353 [ + + ]: 21 : while (likely(more_in_segs)) {
354 : : struct rte_mbuf *out_pkt = NULL;
355 : : uint32_t more_out_segs;
356 : : struct rte_ipv4_hdr *out_hdr;
357 : :
358 : : /* Allocate direct buffer */
359 : 16 : out_pkt = rte_pktmbuf_alloc(pool_direct);
360 [ - + ]: 16 : if (unlikely(out_pkt == NULL)) {
361 : : __free_fragments(pkts_out, out_pkt_pos);
362 : : return -ENOMEM;
363 : : }
364 [ - + ]: 16 : if (unlikely(rte_pktmbuf_tailroom(out_pkt) < frag_size)) {
365 : 0 : rte_pktmbuf_free(out_pkt);
366 : : __free_fragments(pkts_out, out_pkt_pos);
367 : : return -EINVAL;
368 : : }
369 : :
370 : : /* Reserve space for the IP header that will be built later */
371 : 16 : out_pkt->data_len = header_len;
372 : 16 : out_pkt->pkt_len = header_len;
373 : : frag_bytes_remaining = frag_size;
374 : :
375 : : more_out_segs = 1;
376 [ + + ]: 32 : while (likely(more_out_segs && more_in_segs)) {
377 : : uint32_t len;
378 : :
379 : 16 : len = frag_bytes_remaining;
380 : 16 : if (len > (in_seg->data_len - in_seg_data_pos))
381 : : len = in_seg->data_len - in_seg_data_pos;
382 : :
383 : 16 : memcpy(rte_pktmbuf_mtod_offset(out_pkt, char *,
384 : : out_pkt->data_len),
385 [ + + ]: 16 : rte_pktmbuf_mtod_offset(in_seg, char *,
386 : : in_seg_data_pos),
387 : : len);
388 : :
389 : 16 : in_seg_data_pos += len;
390 : 16 : frag_bytes_remaining -= len;
391 : 16 : out_pkt->data_len += len;
392 : :
393 : : /* Current output packet (i.e. fragment) done ? */
394 [ + + ]: 16 : if (unlikely(frag_bytes_remaining == 0))
395 : : more_out_segs = 0;
396 : :
397 : : /* Current input segment done ? */
398 [ + + ]: 16 : if (unlikely(in_seg_data_pos == in_seg->data_len)) {
399 : 5 : in_seg = in_seg->next;
400 : : in_seg_data_pos = 0;
401 : :
402 [ + - ]: 5 : if (unlikely(in_seg == NULL))
403 : : more_in_segs = 0;
404 : : }
405 : : }
406 : :
407 : : /* Build the IP header */
408 : :
409 : 16 : out_pkt->pkt_len = out_pkt->data_len;
410 : 16 : out_hdr = rte_pktmbuf_mtod(out_pkt, struct rte_ipv4_hdr *);
411 : :
412 : 16 : __fill_ipv4hdr_frag(out_hdr, in_hdr, header_len,
413 : : (uint16_t)out_pkt->pkt_len,
414 : : flag_offset, fragment_offset, more_in_segs);
415 : :
416 [ + + + + ]: 16 : if (unlikely((fragment_offset == 0) && (ipopt_len) &&
417 : : ((flag_offset & RTE_IPV4_HDR_OFFSET_MASK) == 0))) {
418 : 1 : ipopt_len = __create_ipopt_frag_hdr((uint8_t *)in_hdr,
419 : : ipopt_len, ipopt_frag_hdr);
420 : 1 : fragment_offset = (uint16_t)(fragment_offset +
421 : 1 : out_pkt->pkt_len - header_len);
422 : 1 : out_pkt->l3_len = header_len;
423 : :
424 : 1 : header_len = sizeof(struct rte_ipv4_hdr) + ipopt_len;
425 : 1 : in_hdr = (struct rte_ipv4_hdr *)ipopt_frag_hdr;
426 : : } else {
427 : 15 : fragment_offset = (uint16_t)(fragment_offset +
428 : 15 : out_pkt->pkt_len - header_len);
429 : 15 : out_pkt->l3_len = header_len;
430 : : }
431 : :
432 : : /* Write the fragment to the output list */
433 : 16 : pkts_out[out_pkt_pos] = out_pkt;
434 : 16 : out_pkt_pos++;
435 : : }
436 : :
437 : 5 : return out_pkt_pos;
438 : : }
|