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