1 /* $Id$ */
2 
3 /*
4  *   Copyright (c) 2001-2010 Aaron Turner <aturner at synfin dot net>
5  *   Copyright (c) 2013-2018 Fred Klassen <tcpreplay at appneta dot com> - AppNeta
6  *
7  *   The Tcpreplay Suite of tools is free software: you can redistribute it
8  *   and/or modify it under the terms of the GNU General Public License as
9  *   published by the Free Software Foundation, either version 3 of the
10  *   License, or with the authors permission any later version.
11  *
12  *   The Tcpreplay Suite is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with the Tcpreplay Suite.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 /*
22  * This code is heavily based on (some might even say stolen from) Mike Shiffman's
23  * checksumming code from Libnet 1.1.3
24  */
25 
26 #include "config.h"
27 #include "tcpedit.h"
28 #include "checksum.h"
29 
30 static int do_checksum_math(uint16_t *, int);
31 
32 
33 /**
34  * Returns -1 on error and 0 on success, 1 on warn
35  */
36 int
do_checksum(tcpedit_t * tcpedit,uint8_t * data,int proto,int payload_len)37 do_checksum(tcpedit_t *tcpedit, uint8_t *data, int proto, int payload_len) {
38     ipv4_hdr_t *ipv4;
39     ipv6_hdr_t *ipv6;
40     tcp_hdr_t *tcp;
41     udp_hdr_t *udp;
42     icmpv4_hdr_t *icmp;
43     icmpv6_hdr_t *icmp6;
44     u_char *layer;
45     int ip_hl;
46     int sum;
47 
48     sum = 0;
49     ipv4 = NULL;
50     ipv6 = NULL;
51     assert(data);
52 
53     if (!data || payload_len < (int)sizeof(*ipv4) || payload_len > 0xffff) {
54         tcpedit_setwarn(tcpedit, "%s", "Unable to checksum packet with no L3+ data");
55         return TCPEDIT_WARN;
56     }
57 
58     ipv4 = (ipv4_hdr_t *)data;
59     if (ipv4->ip_v == 6) {
60         if (payload_len < (int)sizeof(*ipv6)) {
61             tcpedit_setwarn(tcpedit, "%s", "Unable to checksum IPv6 packet with insufficient data");
62             return TCPEDIT_WARN;
63         }
64 
65         ipv6 = (ipv6_hdr_t *)data;
66         ipv4 = NULL;
67 
68         proto = get_ipv6_l4proto(ipv6, payload_len + sizeof(ipv6_hdr_t));
69         dbgx(3, "layer4 proto is 0x%hx", (uint16_t)proto);
70 
71         layer = (u_char*)get_layer4_v6(ipv6, payload_len + sizeof(ipv6_hdr_t));
72         if (!layer) {
73             tcpedit_setwarn(tcpedit, "%s", "Packet to short for checksum");
74             return TCPEDIT_WARN;
75         }
76 
77         ip_hl = layer - (u_char*)data;
78         dbgx(3, "ip_hl proto is 0x%d", ip_hl);
79 
80         payload_len -= (ip_hl - TCPR_IPV6_H);
81     } else {
82         ip_hl = ipv4->ip_hl << 2;
83     }
84 
85     switch (proto) {
86         case IPPROTO_TCP:
87             if (payload_len < (int)sizeof(tcp_hdr_t)) {
88                 tcpedit_setwarn(tcpedit, "%s", "Unable to checksum TCP with insufficient L4 data");
89                 return TCPEDIT_WARN;
90             }
91 
92             tcp = (tcp_hdr_t *)(data + ip_hl);
93 #ifdef STUPID_SOLARIS_CHECKSUM_BUG
94             tcp->th_sum = tcp->th_off << 2;
95             return (TCPEDIT_OK);
96 #endif
97             tcp->th_sum = 0;
98 
99             /* Note, we do both src & dst IP's at the same time, that's why the
100              * length is 2x a single IP
101              */
102             if (ipv6 != NULL) {
103                 sum = do_checksum_math((uint16_t *)&ipv6->ip_src, 32);
104             } else {
105                 sum = do_checksum_math((uint16_t *)&ipv4->ip_src, 8);
106             }
107             sum += ntohs(IPPROTO_TCP + payload_len);
108             sum += do_checksum_math((uint16_t *)tcp, payload_len);
109             tcp->th_sum = CHECKSUM_CARRY(sum);
110             break;
111 
112         case IPPROTO_UDP:
113             if (payload_len < (int)sizeof(udp_hdr_t)) {
114                 tcpedit_setwarn(tcpedit, "%s", "Unable to checksum UDP with insufficient L4 data");
115                 return TCPEDIT_WARN;
116             }
117             udp = (udp_hdr_t *)(data + ip_hl);
118             /* No need to recalculate UDP checksums if already 0 */
119             if (udp->uh_sum == 0)
120                 break;
121             udp->uh_sum = 0;
122             if (ipv6 != NULL) {
123                 sum = do_checksum_math((uint16_t *)&ipv6->ip_src, 32);
124             } else {
125                 sum = do_checksum_math((uint16_t *)&ipv4->ip_src, 8);
126             }
127             sum += ntohs(IPPROTO_UDP + payload_len);
128             sum += do_checksum_math((uint16_t *)udp, payload_len);
129             udp->uh_sum = CHECKSUM_CARRY(sum);
130             break;
131 
132         case IPPROTO_ICMP:
133             if (payload_len < (int)sizeof(icmpv4_hdr_t)) {
134                 tcpedit_setwarn(tcpedit, "%s", "Unable to checksum ICMP with insufficient L4 data");
135                 return TCPEDIT_WARN;
136             }
137             icmp = (icmpv4_hdr_t *)(data + ip_hl);
138             icmp->icmp_sum = 0;
139             if (ipv6 != NULL) {
140                 sum = do_checksum_math((uint16_t *)&ipv6->ip_src, 32);
141                 icmp->icmp_sum = CHECKSUM_CARRY(sum);
142             }
143             sum += do_checksum_math((uint16_t *)icmp, payload_len);
144             icmp->icmp_sum = CHECKSUM_CARRY(sum);
145             break;
146 
147         case IPPROTO_ICMP6:
148             if (payload_len < (int)sizeof(icmpv6_hdr_t)) {
149                 tcpedit_setwarn(tcpedit, "%s", "Unable to checksum ICMP6 with insufficient L4 data");
150                 return TCPEDIT_WARN;
151             }
152             icmp6 = (icmpv6_hdr_t *)(data + ip_hl);
153             icmp6->icmp_sum = 0;
154             if (ipv6 != NULL) {
155                 sum = do_checksum_math((u_int16_t *)&ipv6->ip_src, 32);
156             }
157             sum += ntohs(IPPROTO_ICMP6 + payload_len);
158             sum += do_checksum_math((u_int16_t *)icmp6, payload_len);
159             icmp6->icmp_sum = CHECKSUM_CARRY(sum);
160             break;
161 
162         case IPPROTO_IP:
163             if (ipv4) {
164                 ipv4->ip_sum = 0;
165                 sum = do_checksum_math((uint16_t *)data, ip_hl);
166                 ipv4->ip_sum = CHECKSUM_CARRY(sum);
167             }
168             break;
169 
170         case IPPROTO_IGMP:
171         case IPPROTO_GRE:
172         case IPPROTO_OSPF:
173         case IPPROTO_OSPF_LSA:
174         case IPPROTO_VRRP:
175         case TCPR_PROTO_CDP:
176         case TCPR_PROTO_ISL:
177         default:
178             tcpedit_setwarn(tcpedit, "Unsupported protocol for checksum: 0x%x", proto);
179             return TCPEDIT_WARN;
180     }
181 
182     return TCPEDIT_OK;
183 }
184 
185 /**
186  * code to do a ones-compliment checksum
187  */
188 static int
do_checksum_math(uint16_t * data,int len)189 do_checksum_math(uint16_t *data, int len)
190 {
191     int sum = 0;
192     union {
193         uint16_t s;
194         uint8_t b[2];
195     } pad;
196 
197     while (len > 1) {
198         sum += *data++;
199         len -= 2;
200     }
201 
202     if (len == 1) {
203         pad.b[0] = *(uint8_t *)data;
204         pad.b[1] = 0;
205         sum += pad.s;
206     }
207 
208     return (sum);
209 }
210 
211