aboutsummaryrefslogtreecommitdiff
path: root/example/ipfragreass/odp_ipfragreass_fragment.c
blob: 2e9d9ad646a30aada9b65277577bcdad27594949 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright (c) 2017-2018 Linaro Limited
 */

/** @cond _ODP_HIDE_FROM_DOXYGEN_ */

#include <stdio.h>
#include <assert.h>

#include "odp_ipfragreass_ip.h"
#include "odp_ipfragreass_fragment.h"

int fragment_ipv4_packet(odp_packet_t orig_packet, odp_packet_t *out,
			 int *out_len)
{
	int fragments = 0;
	odp_bool_t first_fragment = 1;
	odph_ipv4hdr_t *orig_header = odp_packet_data(orig_packet);
	uint32_t header_len = ipv4hdr_ihl(*orig_header);
	uint32_t orig_len = odp_packet_len(orig_packet);
	uint32_t bytes_remaining = orig_len - header_len;
	uint32_t frag_offset = 0;
	odp_packet_t frag = orig_packet;
	odp_packet_t rest = ODP_PACKET_INVALID;

	if (bytes_remaining <= MTU)
		return -1;

	/*
	 * The main fragmentation loop (continue until all bytes from the
	 * original payload have been assigned to a fragment)
	 */
	while (bytes_remaining) {
		odph_ipv4hdr_t *header;
		uint32_t frag_len;

		frag_len = (bytes_remaining > MTU ? (MTU + header_len)
						  : (bytes_remaining
						     + header_len));
		bytes_remaining -= (frag_len - header_len);
		if (bytes_remaining) {
			uint32_t split_point;
			int split_success;

			split_point = first_fragment ? (frag_len)
						     : (frag_len - header_len);
			split_success = odp_packet_split(&frag, split_point,
							 &rest);
			if (split_success < 0) {
				fprintf(stderr,
					"ERROR: odp_packet_split\n");
				return -1;
			} else if (first_fragment && split_success > 0) {
				orig_header = odp_packet_data(frag);
			}
		}

		/* Add a header to the packet if necessary */
		if (first_fragment) {
			first_fragment = 0;
		} else {
			if (odp_packet_extend_head(&frag, header_len, NULL,
						   NULL) < 0) {
				fprintf(stderr,
					"ERROR: odp_packet_extend_head\n");
				return -1;
			}
			if (odp_packet_copy_from_mem(frag, 0, header_len,
						     orig_header)) {
				fprintf(stderr,
					"ERROR: odp_packet_copy_from_mem\n");
				return -1;
			}
		}

		/* Update the header */
		header = odp_packet_data(frag);
		ipv4hdr_set_more_fragments(header, bytes_remaining != 0);
		header->tot_len = odp_cpu_to_be_16(frag_len);
		ipv4hdr_set_fragment_offset_oct(header, (frag_offset / 8));
		assert((bytes_remaining == 0) ||
		       (odp_packet_len(frag) == (MTU + header_len)));
		assert((bytes_remaining != 0) ||
		       (ipv4hdr_reass_payload_len(*header) + header_len
			== orig_len));
		odp_packet_l3_offset_set(frag, 0);
		header->chksum = 0;
		odph_ipv4_csum_update(frag);

		out[fragments++] = frag;
		frag = rest;
		frag_offset += (frag_len - header_len);
	}

	if (out_len)
		*out_len = fragments;

	return 0;
}