1 /*
2  *  $Id: libnet_checksum.c,v 1.14 2004/11/09 07:05:07 mike Exp $
3  *
4  *  libnet
5  *  libnet_checksum.c - checksum routines
6  *
7  *  Copyright (c) 1998 - 2004 Mike D. Schiffman <mike@infonexus.com>
8  *  All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  */
32 
33 #if (HAVE_CONFIG_H)
34 #include "../include/config.h"
35 #endif
36 #if (!(_WIN32) || (__CYGWIN__))
37 #include "../include/libnet.h"
38 #else
39 #include "../include/win32/libnet.h"
40 #endif
41 
42 /* FIXME - unit test these - 0 is debian's version, else is -RC1's */
43 /* Note about aliasing warning:
44  *
45  *   http://mail.opensolaris.org/pipermail/tools-gcc/2005-August/000047.html
46  *
47  * See RFC 1071, and:
48  *
49  *   http://mathforum.org/library/drmath/view/54379.html
50  */
51 #undef DEBIAN
52 /* Note: len is in bytes, not 16-bit words! */
53 int
libnet_in_cksum(uint16_t * addr,int len)54 libnet_in_cksum(uint16_t *addr, int len)
55 {
56     int sum;
57 #ifdef DEBIAN
58     uint16_t last_byte;
59 
60     sum = 0;
61     last_byte = 0;
62 #else
63     union
64     {
65         uint16_t s;
66         uint8_t b[2];
67     }pad;
68 
69     sum = 0;
70 #endif
71 
72     while (len > 1)
73     {
74         sum += *addr++;
75         len -= 2;
76     }
77 #ifdef DEBIAN
78     if (len == 1)
79     {
80         *(uint8_t *)&last_byte = *(uint8_t *)addr;
81         sum += last_byte;
82 #else
83     if (len == 1)
84     {
85         pad.b[0] = *(uint8_t *)addr;
86         pad.b[1] = 0;
87         sum += pad.s;
88 #endif
89     }
90 
91     return (sum);
92 }
93 
94 int
95 libnet_toggle_checksum(libnet_t *l, libnet_ptag_t ptag, int mode)
96 {
97     libnet_pblock_t *p;
98 
99     p = libnet_pblock_find(l, ptag);
100     if (p == NULL)
101     {
102         /* err msg set in libnet_pblock_find() */
103         return (-1);
104     }
105     if (mode == LIBNET_ON)
106     {
107         if ((p->flags) & LIBNET_PBLOCK_DO_CHECKSUM)
108         {
109             return (1);
110         }
111         else
112         {
113             (p->flags) |= LIBNET_PBLOCK_DO_CHECKSUM;
114             return (1);
115         }
116     }
117     else
118     {
119         if ((p->flags) & LIBNET_PBLOCK_DO_CHECKSUM)
120         {
121             (p->flags) &= ~LIBNET_PBLOCK_DO_CHECKSUM;
122             return (1);
123         }
124         else
125         {
126             return (1);
127         }
128     }
129 }
130 
131 static int check_ip_payload_size(libnet_t*l, const uint8_t *iphdr, int ip_hl, int h_len, const uint8_t * end, const char* func)
132 {
133     if((iphdr+ip_hl+h_len) > end)
134     {
135         snprintf(l->err_buf, LIBNET_ERRBUF_SIZE,
136                 "%s(): ip payload not inside packet (pktsz %d, iphsz %d, payloadsz %d)\n", func,
137 		(int)(end - iphdr), ip_hl, h_len);
138         return -1;
139     }
140 
141     return 0;
142 }
143 
144 
145 /*
146  * For backwards binary compatibility. The calculations done here can easily
147  * result in buffer overreads and overwrites. You have been warned. And no, it
148  * is not possible to fix, the API contains no information on the buffer's
149  * boundary. libnet itself calls the safe function, libnet_inet_checksum(). So
150  * should you.
151  */
152 int
153 libnet_do_checksum(libnet_t *l, uint8_t *iphdr, int protocol, int h_len)
154 {
155     uint16_t ip_len = 0;
156     struct libnet_ipv4_hdr* ip4 = (struct libnet_ipv4_hdr *)iphdr;
157     struct libnet_ipv6_hdr* ip6 = (struct libnet_ipv6_hdr *)iphdr;
158 
159     if(ip4->ip_v == 6) {
160         ip_len = ntohs(ip6->ip_len);
161     } else {
162         ip_len = ntohs(ip4->ip_len);
163     }
164 
165     return libnet_inet_checksum(l, iphdr, protocol, h_len,
166             iphdr, iphdr + ip_len
167             );
168 }
169 
170 
171 #define CHECK_IP_PAYLOAD_SIZE() do { \
172     int e=check_ip_payload_size(l,iphdr,ip_hl, h_len, end, __func__);\
173     if(e) return e;\
174 } while(0)
175 
176 
177 /*
178  * We are checksumming pblock "q"
179  *
180  * iphdr is the pointer to it's encapsulating IP header
181  * protocol describes the type of "q", expressed as an IPPROTO_ value
182  * h_len is the h_len from "q"
183  */
184 int
185 libnet_inet_checksum(libnet_t *l, uint8_t *iphdr, int protocol, int h_len, const uint8_t *beg, const uint8_t * end)
186 {
187     /* will need to update this for ipv6 at some point */
188     struct libnet_ipv4_hdr *iph_p = (struct libnet_ipv4_hdr *)iphdr;
189     struct libnet_ipv6_hdr *ip6h_p = NULL; /* default to not using IPv6 */
190     int ip_hl   = 0;
191     int sum     = 0;
192 
193     /* Check for memory under/over reads/writes. */
194     if(iphdr < beg || (iphdr+sizeof(*iph_p)) > end)
195     {
196         snprintf(l->err_buf, LIBNET_ERRBUF_SIZE,
197             "%s(): ipv4 hdr not inside packet (where %d, size %d)\n", __func__,
198 	    (int)(iphdr-beg), (int)(end-beg));
199         return -1;
200     }
201 
202     /*
203      *  Figure out which IP version we're dealing with.  We'll assume v4
204      *  and overlay a header structure to yank out the version.
205      */
206     if (iph_p->ip_v == 6)
207     {
208         ip6h_p = (struct libnet_ipv6_hdr *)iph_p;
209         iph_p = NULL;
210         ip_hl   = 40;
211         if((uint8_t*)(ip6h_p+1) > end)
212         {
213             snprintf(l->err_buf, LIBNET_ERRBUF_SIZE,
214                     "%s(): ipv6 hdr not inside packet\n", __func__);
215             return -1;
216         }
217     }
218     else
219     {
220         ip_hl = iph_p->ip_hl << 2;
221     }
222 
223     if((iphdr+ip_hl) > end)
224     {
225         snprintf(l->err_buf, LIBNET_ERRBUF_SIZE,
226             "%s(): ip hdr len not inside packet\n", __func__);
227         return -1;
228     }
229 
230     /*
231      *  Dug Song came up with this very cool checksuming implementation
232      *  eliminating the need for explicit psuedoheader use.  Check it out.
233      */
234     switch (protocol)
235     {
236         case IPPROTO_TCP:
237         {
238             struct libnet_tcp_hdr *tcph_p =
239                 (struct libnet_tcp_hdr *)(iphdr + ip_hl);
240 
241 	    h_len = end - (uint8_t*) tcph_p; /* ignore h_len, sum the packet we've coalesced */
242 
243             CHECK_IP_PAYLOAD_SIZE();
244 
245 #if (STUPID_SOLARIS_CHECKSUM_BUG)
246             tcph_p->th_sum = tcph_p->th_off << 2;
247             return (1);
248 #endif /* STUPID_SOLARIS_CHECKSUM_BUG */
249 #if (HAVE_HPUX11)
250             if (l->injection_type != LIBNET_LINK)
251             {
252                 /*
253                  *  Similiar to the Solaris Checksum bug - but need to add
254                  *  the size of the TCP payload (only for raw sockets).
255                  */
256                 tcph_p->th_sum = (tcph_p->th_off << 2) +
257                         (h_len - (tcph_p->th_off << 2));
258                 return (1);
259             }
260 #endif
261             /* TCP checksum is over the IP pseudo header:
262              * ip src
263              * ip dst
264              * tcp protocol (IPPROTO_TCP)
265              * tcp length, including the header
266              * + the TCP header (with checksum set to zero) and data
267              */
268             tcph_p->th_sum = 0;
269             if (ip6h_p)
270             {
271                 sum = libnet_in_cksum((uint16_t *)&ip6h_p->ip_src, 32);
272             }
273             else
274             {
275                 /* 8 = src and dst */
276                 sum = libnet_in_cksum((uint16_t *)&iph_p->ip_src, 8);
277             }
278             sum += ntohs(IPPROTO_TCP + h_len);
279             sum += libnet_in_cksum((uint16_t *)tcph_p, h_len);
280             tcph_p->th_sum = LIBNET_CKSUM_CARRY(sum);
281 #if 0
282             printf("tcp sum calculated: %#x/%d h_len %d\n",
283                     ntohs(tcph_p->th_sum),
284                     ntohs(tcph_p->th_sum),
285                     h_len
286                   );
287 #endif
288             break;
289         }
290         case IPPROTO_UDP:
291         {
292             struct libnet_udp_hdr *udph_p =
293                 (struct libnet_udp_hdr *)(iphdr + ip_hl);
294 
295 	    h_len = end - (uint8_t*) udph_p; /* ignore h_len, sum the packet we've coalesced */
296 
297             CHECK_IP_PAYLOAD_SIZE();
298 
299             udph_p->uh_sum = 0;
300             if (ip6h_p)
301             {
302                 sum = libnet_in_cksum((uint16_t *)&ip6h_p->ip_src, 32);
303             }
304             else
305             {
306                 sum = libnet_in_cksum((uint16_t *)&iph_p->ip_src, 8);
307             }
308             sum += ntohs(IPPROTO_UDP + h_len);
309             sum += libnet_in_cksum((uint16_t *)udph_p, h_len);
310             udph_p->uh_sum = LIBNET_CKSUM_CARRY(sum);
311             break;
312         }
313         case IPPROTO_ICMP:
314         {
315             struct libnet_icmpv4_hdr *icmph_p =
316                 (struct libnet_icmpv4_hdr *)(iphdr + ip_hl);
317 
318             h_len = end - (uint8_t*) icmph_p; /* ignore h_len, sum the packet we've coalesced */
319 
320             CHECK_IP_PAYLOAD_SIZE();
321 
322             icmph_p->icmp_sum = 0;
323             /* Hm, is this valid? Is the checksum algorithm for ICMPv6 encapsulated in IPv4
324              * actually defined?
325              */
326             if (ip6h_p)
327             {
328                 sum = libnet_in_cksum((uint16_t *)&ip6h_p->ip_src, 32);
329                 sum += ntohs(IPPROTO_ICMP6 + h_len);
330             }
331             sum += libnet_in_cksum((uint16_t *)icmph_p, h_len);
332             icmph_p->icmp_sum = LIBNET_CKSUM_CARRY(sum);
333             break;
334         }
335         case IPPROTO_ICMPV6:
336         {
337             struct libnet_icmpv6_hdr *icmph_p =
338                 (struct libnet_icmpv6_hdr *)(iphdr + ip_hl);
339 
340             h_len = end - (uint8_t*) icmph_p; /* ignore h_len, sum the packet we've coalesced */
341 
342             CHECK_IP_PAYLOAD_SIZE();
343 
344             icmph_p->icmp_sum = 0;
345             if (ip6h_p)
346             {
347                 sum = libnet_in_cksum((uint16_t *)&ip6h_p->ip_src, 32);
348                 sum += ntohs(IPPROTO_ICMP6 + h_len);
349             }
350             sum += libnet_in_cksum((uint16_t *)icmph_p, h_len);
351             icmph_p->icmp_sum = LIBNET_CKSUM_CARRY(sum);
352             break;
353         }
354         case IPPROTO_IGMP:
355         {
356             struct libnet_igmp_hdr *igmph_p =
357                 (struct libnet_igmp_hdr *)(iphdr + ip_hl);
358 
359 	    h_len = end - (uint8_t*) igmph_p; /* ignore h_len, sum the packet we've coalesced */
360 
361             CHECK_IP_PAYLOAD_SIZE();
362 
363             igmph_p->igmp_sum = 0;
364             sum = libnet_in_cksum((uint16_t *)igmph_p, h_len);
365             igmph_p->igmp_sum = LIBNET_CKSUM_CARRY(sum);
366             break;
367         }
368 	case IPPROTO_GRE:
369 	{
370             /* checksum is always at the same place in GRE header
371              * in the multiple RFC version of the protocol ... ouf !!!
372              */
373 	    struct libnet_gre_hdr *greh_p =
374 		(struct libnet_gre_hdr *)(iphdr + ip_hl);
375 	    uint16_t fv = ntohs(greh_p->flags_ver);
376 
377             CHECK_IP_PAYLOAD_SIZE();
378 
379 	    if (!(fv & (GRE_CSUM|GRE_ROUTING | GRE_VERSION_0)) ||
380                 !(fv & (GRE_CSUM|GRE_VERSION_1)))
381 	    {
382 		snprintf(l->err_buf, LIBNET_ERRBUF_SIZE,
383                 "%s(): can't compute GRE checksum (wrong flags_ver bits: 0x%x )\n",  __func__, fv);
384 		return (-1);
385 	    }
386 	    sum = libnet_in_cksum((uint16_t *)greh_p, h_len);
387 	    greh_p->gre_sum = LIBNET_CKSUM_CARRY(sum);
388 	    break;
389 	}
390         case IPPROTO_OSPF:
391         {
392             struct libnet_ospf_hdr *oh_p =
393                 (struct libnet_ospf_hdr *)(iphdr + ip_hl);
394 
395             CHECK_IP_PAYLOAD_SIZE();
396 
397             oh_p->ospf_sum = 0;
398             sum += libnet_in_cksum((uint16_t *)oh_p, h_len);
399             oh_p->ospf_sum = LIBNET_CKSUM_CARRY(sum);
400             break;
401         }
402         case IPPROTO_OSPF_LSA:
403         {
404             struct libnet_ospf_hdr *oh_p =
405                 (struct libnet_ospf_hdr *)(iphdr + ip_hl);
406             struct libnet_lsa_hdr *lsa_p =
407                 (struct libnet_lsa_hdr *)(iphdr +
408                 ip_hl + oh_p->ospf_len);
409 
410             /* FIXME need additional length check, to account for ospf_len */
411             lsa_p->lsa_sum = 0;
412             sum += libnet_in_cksum((uint16_t *)lsa_p, h_len);
413             lsa_p->lsa_sum = LIBNET_CKSUM_CARRY(sum);
414             break;
415 #if 0
416             /*
417              *  Reworked fletcher checksum taken from RFC 1008.
418              */
419             int c0, c1;
420             struct libnet_lsa_hdr *lsa_p = (struct libnet_lsa_hdr *)buf;
421             uint8_t *p, *p1, *p2, *p3;
422 
423             c0 = 0;
424             c1 = 0;
425 
426             lsa_p->lsa_cksum = 0;
427 
428             p = buf;
429             p1 = buf;
430             p3 = buf + len;             /* beginning and end of buf */
431 
432             while (p1 < p3)
433             {
434                 p2 = p1 + LIBNET_MODX;
435                 if (p2 > p3)
436                 {
437                     p2 = p3;
438                 }
439 
440                 for (p = p1; p < p2; p++)
441                 {
442                     c0 += (*p);
443                     c1 += c0;
444                 }
445 
446                 c0 %= 255;
447                 c1 %= 255;      /* modular 255 */
448 
449                 p1 = p2;
450             }
451 
452 #if AWR_PLEASE_REWORK_THIS
453             lsa_p->lsa_cksum[0] = (((len - 17) * c0 - c1) % 255);
454             if (lsa_p->lsa_cksum[0] <= 0)
455             {
456                 lsa_p->lsa_cksum[0] += 255;
457             }
458 
459             lsa_p->lsa_cksum[1] = (510 - c0 - lsa_p->lsa_cksum[0]);
460             if (lsa_p->lsa_cksum[1] > 255)
461             {
462                 lsa_p->lsa_cksum[1] -= 255;
463             }
464 #endif
465             break;
466 #endif
467         }
468         case IPPROTO_IP:
469         {
470             if(!iph_p) {
471                 /* IPv6 doesn't have a checksum */
472             } else {
473                 iph_p->ip_sum = 0;
474                 sum = libnet_in_cksum((uint16_t *)iph_p, ip_hl);
475                 iph_p->ip_sum = LIBNET_CKSUM_CARRY(sum);
476             }
477             break;
478         }
479         case IPPROTO_VRRP:
480         {
481             struct libnet_vrrp_hdr *vrrph_p =
482                 (struct libnet_vrrp_hdr *)(iphdr + ip_hl);
483             CHECK_IP_PAYLOAD_SIZE();
484 
485             vrrph_p->vrrp_sum = 0;
486             sum = libnet_in_cksum((uint16_t *)vrrph_p, h_len);
487             vrrph_p->vrrp_sum = LIBNET_CKSUM_CARRY(sum);
488             break;
489         }
490         case LIBNET_PROTO_CDP:
491         {   /* XXX - Broken: how can we easily get the entire packet size? */
492 	    /* FIXME you can't, checksumming non-IP protocols was not supported by libnet */
493             struct libnet_cdp_hdr *cdph_p =
494                 (struct libnet_cdp_hdr *)iphdr;
495 
496             if((iphdr+h_len) > end)
497             {
498                 snprintf(l->err_buf, LIBNET_ERRBUF_SIZE,
499                         "%s(): cdp payload not inside packet\n", __func__);
500                 return -1;
501             }
502 
503             cdph_p->cdp_sum = 0;
504             sum = libnet_in_cksum((uint16_t *)cdph_p, h_len);
505             cdph_p->cdp_sum = LIBNET_CKSUM_CARRY(sum);
506             break;
507         }
508         case LIBNET_PROTO_ISL:
509         {
510 #if 0
511             struct libnet_isl_hdr *islh_p =
512                 (struct libnet_isl_hdr *)buf;
513 #endif
514             /*
515              *  Need to compute 4 byte CRC for the ethernet frame and for
516              *  the ISL frame itself.  Use the libnet_crc function.
517              */
518         }
519         default:
520         {
521             snprintf(l->err_buf, LIBNET_ERRBUF_SIZE,
522                 "%s(): unsupported protocol %d\n", __func__, protocol);
523             return (-1);
524         }
525     }
526     return (1);
527 }
528 
529 
530 uint16_t
531 libnet_ip_check(uint16_t *addr, int len)
532 {
533     int sum;
534 
535     sum = libnet_in_cksum(addr, len);
536     return (LIBNET_CKSUM_CARRY(sum));
537 }
538 
539 /* EOF */
540