1 //--------------------------------------------------------------------------
2 // Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation. You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 //--------------------------------------------------------------------------
18 // checksum.h author Josh Rosenbaum <jrosenba@cisco.com>
19
20 #ifndef CODECS_CHECKSUM_H
21 #define CODECS_CHECKSUM_H
22
23 #include <cstddef>
24
25 #include <protocols/protocol_ids.h>
26
27 namespace checksum
28 {
29 union Pseudoheader
30 {
31 struct
32 {
33 uint32_t sip;
34 uint32_t dip;
35 uint8_t zero;
36 IpProtocol protocol;
37 uint16_t len;
38 } hdr;
39 uint16_t arr[6];
40 static_assert(sizeof(hdr) == sizeof(arr), "IPv4 pseudoheader must be 12 bytes");
41 };
42
43 union Pseudoheader6
44 {
45 struct
46 {
47 uint32_t sip[4];
48 uint32_t dip[4];
49 uint8_t zero;
50 IpProtocol protocol;
51 uint16_t len;
52 } hdr;
53 uint16_t arr[18];
54 static_assert(sizeof(hdr) == sizeof(arr), "IPv6 pseudoheader must be 36 bytes");
55 };
56
57 // calculate the checksum for this general case.
58 static uint16_t cksum_add(const uint16_t* buf, std::size_t buf_len);
59 inline uint16_t tcp_cksum(const uint16_t* buf, std::size_t len, const Pseudoheader&);
60 inline uint16_t tcp_cksum(const uint16_t* buf, std::size_t len, const Pseudoheader6&);
61 inline uint16_t udp_cksum(const uint16_t* buf, std::size_t len, const Pseudoheader&);
62 inline uint16_t udp_cksum(const uint16_t* buf, std::size_t len, const Pseudoheader6&);
63 inline uint16_t icmp_cksum(const uint16_t* buf, std::size_t len, const Pseudoheader6&);
64 inline uint16_t icmp_cksum(const uint16_t* buf, std::size_t len);
65 inline uint16_t ip_cksum(const uint16_t* buf, std::size_t len);
66
67 /*
68 * NOTE: Since multiple dynamic libraries use checksums, the choice
69 * is to either include all of the checksum details in a header,
70 * or ensure I include these symbols for every linker which
71 * can be used. Obviously, setting correct linker flags is
72 * significantly more difficult, so these functions will all
73 * stay in a header file
74 */
75
76 /*
77 * IT IS HIGHLY RECOMMENDED to use the above API. Rathern than calling
78 * any of of the following recommendations directly
79 */
80 namespace detail
81 {
cksum_add(const uint16_t * buf,std::size_t len,uint32_t cksum)82 inline uint16_t cksum_add(const uint16_t* buf, std::size_t len, uint32_t cksum)
83 {
84 const uint16_t* sp = buf;
85
86 // if pointer is 16 bit aligned calculate checksum in tight loop...
87 // gcc 5.4 -O3 generates unaligned quadword instructions that crash; fixed in gcc 8.0.1
88 if ( !( reinterpret_cast<std::uintptr_t>(sp) & 0x01 ) )
89 {
90 while ( len > 1 )
91 {
92 cksum += *sp++;
93 len -= 2;
94 }
95 }
96 else if ( len > 1 )
97 {
98 std::size_t sn = ((len / 2) & 0xF); // == len/2 % 16
99 std::size_t n = (((len / 2) + 15) / 16); // ceiling of (len / 2) / 16
100
101 switch (sn)
102 {
103 case 0:
104 sn = 16;
105 cksum += sp[15]; // fallthrough
106 case 15:
107 cksum += sp[14]; // fallthrough
108 case 14:
109 cksum += sp[13]; // fallthrough
110 case 13:
111 cksum += sp[12]; // fallthrough
112 case 12:
113 cksum += sp[11]; // fallthrough
114 case 11:
115 cksum += sp[10]; // fallthrough
116 case 10:
117 cksum += sp[9]; // fallthrough
118 case 9:
119 cksum += sp[8]; // fallthrough
120 case 8:
121 cksum += sp[7]; // fallthrough
122 case 7:
123 cksum += sp[6]; // fallthrough
124 case 6:
125 cksum += sp[5]; // fallthrough
126 case 5:
127 cksum += sp[4]; // fallthrough
128 case 4:
129 cksum += sp[3]; // fallthrough
130 case 3:
131 cksum += sp[2]; // fallthrough
132 case 2:
133 cksum += sp[1]; // fallthrough
134 case 1:
135 cksum += sp[0];
136 }
137 sp += sn;
138
139 /* unroll loop using Duff's device. */
140 while (--n > 0)
141 {
142 cksum += sp[0];
143 cksum += sp[1];
144 cksum += sp[2];
145 cksum += sp[3];
146 cksum += sp[4];
147 cksum += sp[5];
148 cksum += sp[6];
149 cksum += sp[7];
150 cksum += sp[8];
151 cksum += sp[9];
152 cksum += sp[10];
153 cksum += sp[11];
154 cksum += sp[12];
155 cksum += sp[13];
156 cksum += sp[14];
157 cksum += sp[15];
158 sp += 16;
159 }
160 }
161
162 // if len is odd, sum in the last byte...
163 if ( len & 0x01 )
164 cksum += *((const uint8_t*) sp);
165
166 cksum = (cksum >> 16) + (cksum & 0x0000ffff);
167 cksum += (cksum >> 16);
168
169 return (uint16_t)(~cksum);
170 }
171
add_ipv4_pseudoheader(const Pseudoheader & ph4,uint32_t & cksum)172 inline void add_ipv4_pseudoheader(const Pseudoheader& ph4, uint32_t& cksum)
173 {
174 const uint16_t* h = ph4.arr;
175
176 /* ipv4 pseudo header must have 12 bytes */
177 cksum += h[0];
178 cksum += h[1];
179 cksum += h[2];
180 cksum += h[3];
181 cksum += h[4];
182 cksum += h[5];
183 }
184
add_ipv6_pseudoheader(const Pseudoheader6 & ph6,uint32_t & cksum)185 inline void add_ipv6_pseudoheader(const Pseudoheader6& ph6, uint32_t& cksum)
186 {
187 const uint16_t* h = ph6.arr;
188
189 /* PseudoHeader must have 36 bytes */
190 cksum += h[0];
191 cksum += h[1];
192 cksum += h[2];
193 cksum += h[3];
194 cksum += h[4];
195 cksum += h[5];
196 cksum += h[6];
197 cksum += h[7];
198 cksum += h[8];
199 cksum += h[9];
200 cksum += h[10];
201 cksum += h[11];
202 cksum += h[12];
203 cksum += h[13];
204 cksum += h[14];
205 cksum += h[15];
206 cksum += h[16];
207 cksum += h[17];
208 }
209
add_tcp_header(const uint16_t * & d,std::size_t & len,uint32_t & cksum)210 inline void add_tcp_header(const uint16_t*& d,
211 std::size_t& len,
212 uint32_t& cksum)
213 {
214 /* TCP hdr must have 20 hdr bytes */
215 cksum += d[0];
216 cksum += d[1];
217 cksum += d[2];
218 cksum += d[3];
219 cksum += d[4];
220 cksum += d[5];
221 cksum += d[6];
222 cksum += d[7];
223 cksum += d[8];
224 cksum += d[9];
225 d += 10;
226 len -= 20;
227 }
228
add_udp_header(const uint16_t * & d,size_t & len,uint32_t & cksum)229 inline void add_udp_header(const uint16_t*& d,
230 size_t& len,
231 uint32_t& cksum)
232 {
233 /* UDP must have 8 hdr bytes */
234 cksum += d[0];
235 cksum += d[1];
236 cksum += d[2];
237 cksum += d[3];
238 len -= 8;
239 d += 4;
240 }
241
add_ip_header(const uint16_t * & d,std::size_t & len,uint32_t & cksum)242 inline void add_ip_header(const uint16_t*& d,
243 std::size_t& len,
244 uint32_t& cksum)
245 {
246 /* IP must be >= 20 bytes */
247 cksum += d[0];
248 cksum += d[1];
249 cksum += d[2];
250 cksum += d[3];
251 cksum += d[4];
252 cksum += d[5];
253 cksum += d[6];
254 cksum += d[7];
255 cksum += d[8];
256 cksum += d[9];
257 d += 10;
258 len -= 20;
259 }
260 } // namespace detail
261
icmp_cksum(const uint16_t * buf,std::size_t len,const Pseudoheader6 & ph)262 inline uint16_t icmp_cksum(const uint16_t* buf,
263 std::size_t len,
264 const Pseudoheader6& ph)
265 {
266 uint32_t cksum = 0;
267
268 detail::add_ipv6_pseudoheader(ph, cksum);
269 return detail::cksum_add(buf, len, cksum);
270 }
271
icmp_cksum(const uint16_t * buf,size_t len)272 inline uint16_t icmp_cksum(const uint16_t* buf, size_t len)
273 {
274 return detail::cksum_add(buf, len, 0);
275 }
276
tcp_cksum(const uint16_t * h,std::size_t len,const Pseudoheader & ph)277 inline uint16_t tcp_cksum(const uint16_t* h,
278 std::size_t len,
279 const Pseudoheader& ph)
280 {
281 uint32_t cksum = 0;
282
283 detail::add_ipv4_pseudoheader(ph, cksum);
284 detail::add_tcp_header(h, len, cksum);
285 return detail::cksum_add(h, len, cksum);
286 }
287
tcp_cksum(const uint16_t * buf,std::size_t len,const Pseudoheader6 & ph)288 inline uint16_t tcp_cksum(const uint16_t* buf,
289 std::size_t len,
290 const Pseudoheader6& ph)
291 {
292 uint32_t cksum = 0;
293
294 detail::add_ipv6_pseudoheader(ph, cksum);
295 detail::add_tcp_header(buf, len, cksum);
296 return detail::cksum_add(buf, len, cksum);
297 }
298
udp_cksum(const uint16_t * buf,std::size_t len,const Pseudoheader & ph)299 inline uint16_t udp_cksum(const uint16_t* buf,
300 std::size_t len,
301 const Pseudoheader& ph)
302 {
303 uint32_t cksum = 0;
304
305 detail::add_ipv4_pseudoheader(ph, cksum);
306 detail::add_udp_header(buf, len, cksum);
307 return detail::cksum_add(buf, len, cksum);
308 }
309
udp_cksum(const uint16_t * buf,std::size_t len,const Pseudoheader6 & ph)310 inline uint16_t udp_cksum(const uint16_t* buf,
311 std::size_t len,
312 const Pseudoheader6& ph)
313 {
314 uint32_t cksum = 0;
315
316 detail::add_ipv6_pseudoheader(ph, cksum);
317 detail::add_udp_header(buf, len, cksum);
318 return detail::cksum_add(buf, len, cksum);
319 }
320
ip_cksum(const uint16_t * buf,std::size_t len)321 inline uint16_t ip_cksum(const uint16_t* buf, std::size_t len)
322 {
323 uint32_t cksum = 0;
324
325 detail::add_ip_header(buf, len, cksum);
326 return detail::cksum_add(buf, len, cksum);
327 }
328
cksum_add(const uint16_t * buf,std::size_t len)329 inline uint16_t cksum_add(const uint16_t* buf, std::size_t len)
330 { return detail::cksum_add(buf, len, 0); }
331 } // namespace checksum
332
333 #endif /* CODECS_CHECKSUM_H */
334
335