summaryrefslogtreecommitdiffstats
path: root/checksum.c
blob: 23a7c02fb12b3f647d357078be448c66d4fc9dfc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
 * Copyright 2011 Daniel Drown
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * checksum.c - ipv4/ipv6 checksum calculation
 */
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>

#include "checksum.h"

/* function: ip_checksum_add
 * adds data to a checksum
 * current - the current checksum (or 0 to start a new checksum)
 * data        - the data to add to the checksum
 * len         - length of data
 */
uint32_t ip_checksum_add(uint32_t current, const void *data, int len) {
  uint32_t checksum = current;
  int left = len;
  const uint16_t *data_16 = data;

  while(left > 1) {
    checksum += *data_16;
    data_16++;
    left -= 2;
  }
  if(left) {
    checksum += *(uint8_t *)data_16;
  }

  return checksum;
}

/* function: ip_checksum_fold
 * folds a 32-bit partial checksum into 16 bits
 * temp_sum - sum from ip_checksum_add
 * returns: the folded checksum in network byte order
 */
uint16_t ip_checksum_fold(uint32_t temp_sum) {
  while(temp_sum > 0xffff)
    temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);

  return temp_sum;
}

/* function: ip_checksum_finish
 * folds and closes the checksum
 * temp_sum - sum from ip_checksum_add
 * returns: a header checksum value in network byte order
 */
uint16_t ip_checksum_finish(uint32_t temp_sum) {
  return ~ip_checksum_fold(temp_sum);
}

/* function: ip_checksum
 * combined ip_checksum_add and ip_checksum_finish
 * data - data to checksum
 * len  - length of data
 */
uint16_t ip_checksum(const void *data, int len) {
  uint32_t temp_sum;

  temp_sum = ip_checksum_add(0,data,len);
  return ip_checksum_finish(temp_sum);
}

/* function: ipv6_pseudo_header_checksum
 * calculate the pseudo header checksum for use in tcp/udp/icmp headers
 * ip6      - the ipv6 header
 * len      - the transport length (transport header + payload)
 * protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments
 */
uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr *ip6, uint16_t len, uint8_t protocol) {
  uint32_t checksum_len, checksum_next;
  checksum_len = htonl((uint32_t) len);
  checksum_next = htonl(protocol);

  uint32_t current = 0;
  current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr));
  current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr));
  current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len));
  current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next));

  return current;
}

/* function: ipv4_pseudo_header_checksum
 * calculate the pseudo header checksum for use in tcp/udp headers
 * ip      - the ipv4 header
 * len     - the transport length (transport header + payload)
 */
uint32_t ipv4_pseudo_header_checksum(const struct iphdr *ip, uint16_t len) {
  uint16_t temp_protocol, temp_length;

  temp_protocol = htons(ip->protocol);
  temp_length = htons(len);

  uint32_t current = 0;
  current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
  current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t));
  current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t));
  current = ip_checksum_add(current, &temp_length, sizeof(uint16_t));

  return current;
}

/* function: ip_checksum_adjust
 * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums
 * checksum    - the header checksum in the original packet in network byte order
 * old_hdr_sum - the pseudo-header checksum of the original packet
 * new_hdr_sum - the pseudo-header checksum of the translated packet
 * returns: the new header checksum in network byte order
 */
uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) {
  // Algorithm suggested in RFC 1624.
  // http://tools.ietf.org/html/rfc1624#section-3
  checksum = ~checksum;
  uint16_t folded_sum = ip_checksum_fold(checksum + new_hdr_sum);
  uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
  if (folded_sum > folded_old) {
    return ~(folded_sum - folded_old);
  } else {
    return ~(folded_sum - folded_old - 1);  // end-around borrow
  }
}