1 /* 2 * IP checksumming functions. 3 * (c) 2008 Gerd Hoffmann <kraxel@redhat.com> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; under version 2 or later of the License. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "qemu/osdep.h" 19 #include "qemu-common.h" 20 #include "net/checksum.h" 21 #include "net/eth.h" 22 23 uint32_t net_checksum_add_cont(int len, uint8_t *buf, int seq) 24 { 25 uint32_t sum = 0; 26 int i; 27 28 for (i = seq; i < seq + len; i++) { 29 if (i & 1) { 30 sum += (uint32_t)buf[i - seq]; 31 } else { 32 sum += (uint32_t)buf[i - seq] << 8; 33 } 34 } 35 return sum; 36 } 37 38 uint16_t net_checksum_finish(uint32_t sum) 39 { 40 while (sum>>16) 41 sum = (sum & 0xFFFF)+(sum >> 16); 42 return ~sum; 43 } 44 45 uint16_t net_checksum_tcpudp(uint16_t length, uint16_t proto, 46 uint8_t *addrs, uint8_t *buf) 47 { 48 uint32_t sum = 0; 49 50 sum += net_checksum_add(length, buf); // payload 51 sum += net_checksum_add(8, addrs); // src + dst address 52 sum += proto + length; // protocol & length 53 return net_checksum_finish(sum); 54 } 55 56 void net_checksum_calculate(uint8_t *data, int length) 57 { 58 int mac_hdr_len, ip_len; 59 struct ip_header *ip; 60 61 /* 62 * Note: We cannot assume "data" is aligned, so the all code uses 63 * some macros that take care of possible unaligned access for 64 * struct members (just in case). 65 */ 66 67 /* Ensure we have at least an Eth header */ 68 if (length < sizeof(struct eth_header)) { 69 return; 70 } 71 72 /* Handle the optionnal VLAN headers */ 73 switch (lduw_be_p(&PKT_GET_ETH_HDR(data)->h_proto)) { 74 case ETH_P_VLAN: 75 mac_hdr_len = sizeof(struct eth_header) + 76 sizeof(struct vlan_header); 77 break; 78 case ETH_P_DVLAN: 79 if (lduw_be_p(&PKT_GET_VLAN_HDR(data)->h_proto) == ETH_P_VLAN) { 80 mac_hdr_len = sizeof(struct eth_header) + 81 2 * sizeof(struct vlan_header); 82 } else { 83 mac_hdr_len = sizeof(struct eth_header) + 84 sizeof(struct vlan_header); 85 } 86 break; 87 default: 88 mac_hdr_len = sizeof(struct eth_header); 89 break; 90 } 91 92 length -= mac_hdr_len; 93 94 /* Now check we have an IP header (with an optionnal VLAN header) */ 95 if (length < sizeof(struct ip_header)) { 96 return; 97 } 98 99 ip = (struct ip_header *)(data + mac_hdr_len); 100 101 if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) { 102 return; /* not IPv4 */ 103 } 104 105 ip_len = lduw_be_p(&ip->ip_len); 106 107 /* Last, check that we have enough data for the all IP frame */ 108 if (length < ip_len) { 109 return; 110 } 111 112 ip_len -= IP_HDR_GET_LEN(ip); 113 114 switch (ip->ip_p) { 115 case IP_PROTO_TCP: 116 { 117 uint16_t csum; 118 tcp_header *tcp = (tcp_header *)(ip + 1); 119 120 if (ip_len < sizeof(tcp_header)) { 121 return; 122 } 123 124 /* Set csum to 0 */ 125 stw_he_p(&tcp->th_sum, 0); 126 127 csum = net_checksum_tcpudp(ip_len, ip->ip_p, 128 (uint8_t *)&ip->ip_src, 129 (uint8_t *)tcp); 130 131 /* Store computed csum */ 132 stw_be_p(&tcp->th_sum, csum); 133 134 break; 135 } 136 case IP_PROTO_UDP: 137 { 138 uint16_t csum; 139 udp_header *udp = (udp_header *)(ip + 1); 140 141 if (ip_len < sizeof(udp_header)) { 142 return; 143 } 144 145 /* Set csum to 0 */ 146 stw_he_p(&udp->uh_sum, 0); 147 148 csum = net_checksum_tcpudp(ip_len, ip->ip_p, 149 (uint8_t *)&ip->ip_src, 150 (uint8_t *)udp); 151 152 /* Store computed csum */ 153 stw_be_p(&udp->uh_sum, csum); 154 155 break; 156 } 157 default: 158 /* Can't handle any other protocol */ 159 break; 160 } 161 } 162 163 uint32_t 164 net_checksum_add_iov(const struct iovec *iov, const unsigned int iov_cnt, 165 uint32_t iov_off, uint32_t size, uint32_t csum_offset) 166 { 167 size_t iovec_off, buf_off; 168 unsigned int i; 169 uint32_t res = 0; 170 171 iovec_off = 0; 172 buf_off = 0; 173 for (i = 0; i < iov_cnt && size; i++) { 174 if (iov_off < (iovec_off + iov[i].iov_len)) { 175 size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off , size); 176 void *chunk_buf = iov[i].iov_base + (iov_off - iovec_off); 177 178 res += net_checksum_add_cont(len, chunk_buf, csum_offset); 179 csum_offset += len; 180 181 buf_off += len; 182 iov_off += len; 183 size -= len; 184 } 185 iovec_off += iov[i].iov_len; 186 } 187 return res; 188 } 189