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