xref: /openbmc/qemu/net/checksum.c (revision d5aa3e6e0cd6259003790769c448d4fbb5b5c810)
17200ac3cSMark McLoughlin /*
27200ac3cSMark McLoughlin  *  IP checksumming functions.
37200ac3cSMark McLoughlin  *  (c) 2008 Gerd Hoffmann <kraxel@redhat.com>
47200ac3cSMark McLoughlin  *
57200ac3cSMark McLoughlin  *  This program is free software; you can redistribute it and/or modify
67200ac3cSMark McLoughlin  *  it under the terms of the GNU General Public License as published by
74c32fe66SStefan Weil  *  the Free Software Foundation; under version 2 or later of the License.
87200ac3cSMark McLoughlin  *
97200ac3cSMark McLoughlin  *  This program is distributed in the hope that it will be useful,
107200ac3cSMark McLoughlin  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
117200ac3cSMark McLoughlin  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
127200ac3cSMark McLoughlin  *  GNU General Public License for more details.
137200ac3cSMark McLoughlin  *
147200ac3cSMark McLoughlin  *  You should have received a copy of the GNU General Public License
157200ac3cSMark McLoughlin  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
167200ac3cSMark McLoughlin  */
177200ac3cSMark McLoughlin 
182744d920SPeter Maydell #include "qemu/osdep.h"
1984026301SDmitry Fleytman #include "qemu-common.h"
207200ac3cSMark McLoughlin #include "net/checksum.h"
2150dbce65SJean-Christophe Dubois #include "net/eth.h"
227200ac3cSMark McLoughlin 
235acf5ea4SDmitry Fleytman uint32_t net_checksum_add_cont(int len, uint8_t *buf, int seq)
247200ac3cSMark McLoughlin {
25*d5aa3e6eSLadi Prosek     uint32_t sum1 = 0, sum2 = 0;
267200ac3cSMark McLoughlin     int i;
277200ac3cSMark McLoughlin 
28*d5aa3e6eSLadi Prosek     for (i = 0; i < len - 1; i += 2) {
29*d5aa3e6eSLadi Prosek         sum1 += (uint32_t)buf[i];
30*d5aa3e6eSLadi Prosek         sum2 += (uint32_t)buf[i + 1];
31*d5aa3e6eSLadi Prosek     }
32*d5aa3e6eSLadi Prosek     if (i < len) {
33*d5aa3e6eSLadi Prosek         sum1 += (uint32_t)buf[i];
34*d5aa3e6eSLadi Prosek     }
35*d5aa3e6eSLadi Prosek 
36*d5aa3e6eSLadi Prosek     if (seq & 1) {
37*d5aa3e6eSLadi Prosek         return sum1 + (sum2 << 8);
385acf5ea4SDmitry Fleytman     } else {
39*d5aa3e6eSLadi Prosek         return sum2 + (sum1 << 8);
405acf5ea4SDmitry Fleytman     }
417200ac3cSMark McLoughlin }
427200ac3cSMark McLoughlin 
437200ac3cSMark McLoughlin uint16_t net_checksum_finish(uint32_t sum)
447200ac3cSMark McLoughlin {
457200ac3cSMark McLoughlin     while (sum>>16)
467200ac3cSMark McLoughlin 	sum = (sum & 0xFFFF)+(sum >> 16);
477200ac3cSMark McLoughlin     return ~sum;
487200ac3cSMark McLoughlin }
497200ac3cSMark McLoughlin 
507200ac3cSMark McLoughlin uint16_t net_checksum_tcpudp(uint16_t length, uint16_t proto,
517200ac3cSMark McLoughlin                              uint8_t *addrs, uint8_t *buf)
527200ac3cSMark McLoughlin {
537200ac3cSMark McLoughlin     uint32_t sum = 0;
547200ac3cSMark McLoughlin 
557200ac3cSMark McLoughlin     sum += net_checksum_add(length, buf);         // payload
567200ac3cSMark McLoughlin     sum += net_checksum_add(8, addrs);            // src + dst address
577200ac3cSMark McLoughlin     sum += proto + length;                        // protocol & length
587200ac3cSMark McLoughlin     return net_checksum_finish(sum);
597200ac3cSMark McLoughlin }
607200ac3cSMark McLoughlin 
617200ac3cSMark McLoughlin void net_checksum_calculate(uint8_t *data, int length)
627200ac3cSMark McLoughlin {
63ade6bad1SJean-Christophe Dubois     int mac_hdr_len, ip_len;
6450dbce65SJean-Christophe Dubois     struct ip_header *ip;
6550dbce65SJean-Christophe Dubois 
6650dbce65SJean-Christophe Dubois     /*
6750dbce65SJean-Christophe Dubois      * Note: We cannot assume "data" is aligned, so the all code uses
6850dbce65SJean-Christophe Dubois      * some macros that take care of possible unaligned access for
6950dbce65SJean-Christophe Dubois      * struct members (just in case).
7050dbce65SJean-Christophe Dubois      */
717200ac3cSMark McLoughlin 
72ade6bad1SJean-Christophe Dubois     /* Ensure we have at least an Eth header */
73ade6bad1SJean-Christophe Dubois     if (length < sizeof(struct eth_header)) {
74362786f1SPrasad J Pandit         return;
75362786f1SPrasad J Pandit     }
76362786f1SPrasad J Pandit 
77ade6bad1SJean-Christophe Dubois     /* Handle the optionnal VLAN headers */
78ade6bad1SJean-Christophe Dubois     switch (lduw_be_p(&PKT_GET_ETH_HDR(data)->h_proto)) {
79ade6bad1SJean-Christophe Dubois     case ETH_P_VLAN:
80ade6bad1SJean-Christophe Dubois         mac_hdr_len = sizeof(struct eth_header) +
81ade6bad1SJean-Christophe Dubois                      sizeof(struct vlan_header);
82ade6bad1SJean-Christophe Dubois         break;
83ade6bad1SJean-Christophe Dubois     case ETH_P_DVLAN:
84ade6bad1SJean-Christophe Dubois         if (lduw_be_p(&PKT_GET_VLAN_HDR(data)->h_proto) == ETH_P_VLAN) {
85ade6bad1SJean-Christophe Dubois             mac_hdr_len = sizeof(struct eth_header) +
86ade6bad1SJean-Christophe Dubois                          2 * sizeof(struct vlan_header);
87ade6bad1SJean-Christophe Dubois         } else {
88ade6bad1SJean-Christophe Dubois             mac_hdr_len = sizeof(struct eth_header) +
89ade6bad1SJean-Christophe Dubois                          sizeof(struct vlan_header);
90ade6bad1SJean-Christophe Dubois         }
91ade6bad1SJean-Christophe Dubois         break;
92ade6bad1SJean-Christophe Dubois     default:
93ade6bad1SJean-Christophe Dubois         mac_hdr_len = sizeof(struct eth_header);
94ade6bad1SJean-Christophe Dubois         break;
95ade6bad1SJean-Christophe Dubois     }
96ade6bad1SJean-Christophe Dubois 
97ade6bad1SJean-Christophe Dubois     length -= mac_hdr_len;
98ade6bad1SJean-Christophe Dubois 
99ade6bad1SJean-Christophe Dubois     /* Now check we have an IP header (with an optionnal VLAN header) */
100ade6bad1SJean-Christophe Dubois     if (length < sizeof(struct ip_header)) {
101ade6bad1SJean-Christophe Dubois         return;
102ade6bad1SJean-Christophe Dubois     }
103ade6bad1SJean-Christophe Dubois 
104ade6bad1SJean-Christophe Dubois     ip = (struct ip_header *)(data + mac_hdr_len);
10550dbce65SJean-Christophe Dubois 
10650dbce65SJean-Christophe Dubois     if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
1077200ac3cSMark McLoughlin         return; /* not IPv4 */
10850dbce65SJean-Christophe Dubois     }
1097200ac3cSMark McLoughlin 
11050dbce65SJean-Christophe Dubois     ip_len = lduw_be_p(&ip->ip_len);
11150dbce65SJean-Christophe Dubois 
11250dbce65SJean-Christophe Dubois     /* Last, check that we have enough data for the all IP frame */
11350dbce65SJean-Christophe Dubois     if (length < ip_len) {
11450dbce65SJean-Christophe Dubois         return;
11550dbce65SJean-Christophe Dubois     }
11650dbce65SJean-Christophe Dubois 
11750dbce65SJean-Christophe Dubois     ip_len -= IP_HDR_GET_LEN(ip);
11850dbce65SJean-Christophe Dubois 
11950dbce65SJean-Christophe Dubois     switch (ip->ip_p) {
12050dbce65SJean-Christophe Dubois     case IP_PROTO_TCP:
12150dbce65SJean-Christophe Dubois     {
12250dbce65SJean-Christophe Dubois         uint16_t csum;
12350dbce65SJean-Christophe Dubois         tcp_header *tcp = (tcp_header *)(ip + 1);
12450dbce65SJean-Christophe Dubois 
12550dbce65SJean-Christophe Dubois         if (ip_len < sizeof(tcp_header)) {
12650dbce65SJean-Christophe Dubois             return;
12750dbce65SJean-Christophe Dubois         }
12850dbce65SJean-Christophe Dubois 
12950dbce65SJean-Christophe Dubois         /* Set csum to 0 */
13050dbce65SJean-Christophe Dubois         stw_he_p(&tcp->th_sum, 0);
13150dbce65SJean-Christophe Dubois 
13250dbce65SJean-Christophe Dubois         csum = net_checksum_tcpudp(ip_len, ip->ip_p,
13350dbce65SJean-Christophe Dubois                                    (uint8_t *)&ip->ip_src,
13450dbce65SJean-Christophe Dubois                                    (uint8_t *)tcp);
13550dbce65SJean-Christophe Dubois 
13650dbce65SJean-Christophe Dubois         /* Store computed csum */
13750dbce65SJean-Christophe Dubois         stw_be_p(&tcp->th_sum, csum);
13850dbce65SJean-Christophe Dubois 
1397200ac3cSMark McLoughlin         break;
14050dbce65SJean-Christophe Dubois     }
14150dbce65SJean-Christophe Dubois     case IP_PROTO_UDP:
14250dbce65SJean-Christophe Dubois     {
14350dbce65SJean-Christophe Dubois         uint16_t csum;
14450dbce65SJean-Christophe Dubois         udp_header *udp = (udp_header *)(ip + 1);
14550dbce65SJean-Christophe Dubois 
14650dbce65SJean-Christophe Dubois         if (ip_len < sizeof(udp_header)) {
14750dbce65SJean-Christophe Dubois             return;
14850dbce65SJean-Christophe Dubois         }
14950dbce65SJean-Christophe Dubois 
15050dbce65SJean-Christophe Dubois         /* Set csum to 0 */
15150dbce65SJean-Christophe Dubois         stw_he_p(&udp->uh_sum, 0);
15250dbce65SJean-Christophe Dubois 
15350dbce65SJean-Christophe Dubois         csum = net_checksum_tcpudp(ip_len, ip->ip_p,
15450dbce65SJean-Christophe Dubois                                    (uint8_t *)&ip->ip_src,
15550dbce65SJean-Christophe Dubois                                    (uint8_t *)udp);
15650dbce65SJean-Christophe Dubois 
15750dbce65SJean-Christophe Dubois         /* Store computed csum */
15850dbce65SJean-Christophe Dubois         stw_be_p(&udp->uh_sum, csum);
15950dbce65SJean-Christophe Dubois 
1607200ac3cSMark McLoughlin         break;
16150dbce65SJean-Christophe Dubois     }
1627200ac3cSMark McLoughlin     default:
16350dbce65SJean-Christophe Dubois         /* Can't handle any other protocol */
16450dbce65SJean-Christophe Dubois         break;
1657200ac3cSMark McLoughlin     }
1667200ac3cSMark McLoughlin }
16784026301SDmitry Fleytman 
16884026301SDmitry Fleytman uint32_t
16984026301SDmitry Fleytman net_checksum_add_iov(const struct iovec *iov, const unsigned int iov_cnt,
170eb700029SDmitry Fleytman                      uint32_t iov_off, uint32_t size, uint32_t csum_offset)
17184026301SDmitry Fleytman {
17284026301SDmitry Fleytman     size_t iovec_off, buf_off;
17384026301SDmitry Fleytman     unsigned int i;
17484026301SDmitry Fleytman     uint32_t res = 0;
17584026301SDmitry Fleytman 
17684026301SDmitry Fleytman     iovec_off = 0;
17784026301SDmitry Fleytman     buf_off = 0;
17884026301SDmitry Fleytman     for (i = 0; i < iov_cnt && size; i++) {
17984026301SDmitry Fleytman         if (iov_off < (iovec_off + iov[i].iov_len)) {
18084026301SDmitry Fleytman             size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off , size);
18184026301SDmitry Fleytman             void *chunk_buf = iov[i].iov_base + (iov_off - iovec_off);
18284026301SDmitry Fleytman 
183eb700029SDmitry Fleytman             res += net_checksum_add_cont(len, chunk_buf, csum_offset);
184eb700029SDmitry Fleytman             csum_offset += len;
18584026301SDmitry Fleytman 
18684026301SDmitry Fleytman             buf_off += len;
18784026301SDmitry Fleytman             iov_off += len;
18884026301SDmitry Fleytman             size -= len;
18984026301SDmitry Fleytman         }
19084026301SDmitry Fleytman         iovec_off += iov[i].iov_len;
19184026301SDmitry Fleytman     }
19284026301SDmitry Fleytman     return res;
19384026301SDmitry Fleytman }
194