xref: /openbmc/qemu/net/checksum.c (revision 0dcf0c0aeefd2bc1023c9fe7ab0f1b6bc993c360)
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"
197200ac3cSMark McLoughlin #include "net/checksum.h"
2050dbce65SJean-Christophe Dubois #include "net/eth.h"
217200ac3cSMark McLoughlin 
225acf5ea4SDmitry Fleytman uint32_t net_checksum_add_cont(int len, uint8_t *buf, int seq)
237200ac3cSMark McLoughlin {
24d5aa3e6eSLadi Prosek     uint32_t sum1 = 0, sum2 = 0;
257200ac3cSMark McLoughlin     int i;
267200ac3cSMark McLoughlin 
27d5aa3e6eSLadi Prosek     for (i = 0; i < len - 1; i += 2) {
28d5aa3e6eSLadi Prosek         sum1 += (uint32_t)buf[i];
29d5aa3e6eSLadi Prosek         sum2 += (uint32_t)buf[i + 1];
30d5aa3e6eSLadi Prosek     }
31d5aa3e6eSLadi Prosek     if (i < len) {
32d5aa3e6eSLadi Prosek         sum1 += (uint32_t)buf[i];
33d5aa3e6eSLadi Prosek     }
34d5aa3e6eSLadi Prosek 
35d5aa3e6eSLadi Prosek     if (seq & 1) {
36d5aa3e6eSLadi Prosek         return sum1 + (sum2 << 8);
375acf5ea4SDmitry Fleytman     } else {
38d5aa3e6eSLadi Prosek         return sum2 + (sum1 << 8);
395acf5ea4SDmitry Fleytman     }
407200ac3cSMark McLoughlin }
417200ac3cSMark McLoughlin 
427200ac3cSMark McLoughlin uint16_t net_checksum_finish(uint32_t sum)
437200ac3cSMark McLoughlin {
447200ac3cSMark McLoughlin     while (sum>>16)
457200ac3cSMark McLoughlin         sum = (sum & 0xFFFF)+(sum >> 16);
467200ac3cSMark McLoughlin     return ~sum;
477200ac3cSMark McLoughlin }
487200ac3cSMark McLoughlin 
497200ac3cSMark McLoughlin uint16_t net_checksum_tcpudp(uint16_t length, uint16_t proto,
507200ac3cSMark McLoughlin                              uint8_t *addrs, uint8_t *buf)
517200ac3cSMark McLoughlin {
527200ac3cSMark McLoughlin     uint32_t sum = 0;
537200ac3cSMark McLoughlin 
547200ac3cSMark McLoughlin     sum += net_checksum_add(length, buf);         // payload
557200ac3cSMark McLoughlin     sum += net_checksum_add(8, addrs);            // src + dst address
567200ac3cSMark McLoughlin     sum += proto + length;                        // protocol & length
577200ac3cSMark McLoughlin     return net_checksum_finish(sum);
587200ac3cSMark McLoughlin }
597200ac3cSMark McLoughlin 
607200ac3cSMark McLoughlin void net_checksum_calculate(uint8_t *data, int length)
617200ac3cSMark McLoughlin {
62ade6bad1SJean-Christophe Dubois     int mac_hdr_len, ip_len;
6350dbce65SJean-Christophe Dubois     struct ip_header *ip;
6450dbce65SJean-Christophe Dubois 
6550dbce65SJean-Christophe Dubois     /*
6650dbce65SJean-Christophe Dubois      * Note: We cannot assume "data" is aligned, so the all code uses
6750dbce65SJean-Christophe Dubois      * some macros that take care of possible unaligned access for
6850dbce65SJean-Christophe Dubois      * struct members (just in case).
6950dbce65SJean-Christophe Dubois      */
707200ac3cSMark McLoughlin 
71ade6bad1SJean-Christophe Dubois     /* Ensure we have at least an Eth header */
72ade6bad1SJean-Christophe Dubois     if (length < sizeof(struct eth_header)) {
73362786f1SPrasad J Pandit         return;
74362786f1SPrasad J Pandit     }
75362786f1SPrasad J Pandit 
76ade6bad1SJean-Christophe Dubois     /* Handle the optionnal VLAN headers */
77ade6bad1SJean-Christophe Dubois     switch (lduw_be_p(&PKT_GET_ETH_HDR(data)->h_proto)) {
78ade6bad1SJean-Christophe Dubois     case ETH_P_VLAN:
79ade6bad1SJean-Christophe Dubois         mac_hdr_len = sizeof(struct eth_header) +
80ade6bad1SJean-Christophe Dubois                      sizeof(struct vlan_header);
81ade6bad1SJean-Christophe Dubois         break;
82ade6bad1SJean-Christophe Dubois     case ETH_P_DVLAN:
83ade6bad1SJean-Christophe Dubois         if (lduw_be_p(&PKT_GET_VLAN_HDR(data)->h_proto) == ETH_P_VLAN) {
84ade6bad1SJean-Christophe Dubois             mac_hdr_len = sizeof(struct eth_header) +
85ade6bad1SJean-Christophe Dubois                          2 * sizeof(struct vlan_header);
86ade6bad1SJean-Christophe Dubois         } else {
87ade6bad1SJean-Christophe Dubois             mac_hdr_len = sizeof(struct eth_header) +
88ade6bad1SJean-Christophe Dubois                          sizeof(struct vlan_header);
89ade6bad1SJean-Christophe Dubois         }
90ade6bad1SJean-Christophe Dubois         break;
91ade6bad1SJean-Christophe Dubois     default:
92ade6bad1SJean-Christophe Dubois         mac_hdr_len = sizeof(struct eth_header);
93ade6bad1SJean-Christophe Dubois         break;
94ade6bad1SJean-Christophe Dubois     }
95ade6bad1SJean-Christophe Dubois 
96ade6bad1SJean-Christophe Dubois     length -= mac_hdr_len;
97ade6bad1SJean-Christophe Dubois 
98ade6bad1SJean-Christophe Dubois     /* Now check we have an IP header (with an optionnal VLAN header) */
99ade6bad1SJean-Christophe Dubois     if (length < sizeof(struct ip_header)) {
100ade6bad1SJean-Christophe Dubois         return;
101ade6bad1SJean-Christophe Dubois     }
102ade6bad1SJean-Christophe Dubois 
103ade6bad1SJean-Christophe Dubois     ip = (struct ip_header *)(data + mac_hdr_len);
10450dbce65SJean-Christophe Dubois 
10550dbce65SJean-Christophe Dubois     if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
1067200ac3cSMark McLoughlin         return; /* not IPv4 */
10750dbce65SJean-Christophe Dubois     }
1087200ac3cSMark McLoughlin 
109*0dcf0c0aSMarkus Carlstedt     if (IP4_IS_FRAGMENT(ip)) {
110*0dcf0c0aSMarkus Carlstedt         return; /* a fragmented IP packet */
111*0dcf0c0aSMarkus Carlstedt     }
112*0dcf0c0aSMarkus Carlstedt 
11350dbce65SJean-Christophe Dubois     ip_len = lduw_be_p(&ip->ip_len);
11450dbce65SJean-Christophe Dubois 
11550dbce65SJean-Christophe Dubois     /* Last, check that we have enough data for the all IP frame */
11650dbce65SJean-Christophe Dubois     if (length < ip_len) {
11750dbce65SJean-Christophe Dubois         return;
11850dbce65SJean-Christophe Dubois     }
11950dbce65SJean-Christophe Dubois 
12050dbce65SJean-Christophe Dubois     ip_len -= IP_HDR_GET_LEN(ip);
12150dbce65SJean-Christophe Dubois 
12250dbce65SJean-Christophe Dubois     switch (ip->ip_p) {
12350dbce65SJean-Christophe Dubois     case IP_PROTO_TCP:
12450dbce65SJean-Christophe Dubois     {
12550dbce65SJean-Christophe Dubois         uint16_t csum;
12650dbce65SJean-Christophe Dubois         tcp_header *tcp = (tcp_header *)(ip + 1);
12750dbce65SJean-Christophe Dubois 
12850dbce65SJean-Christophe Dubois         if (ip_len < sizeof(tcp_header)) {
12950dbce65SJean-Christophe Dubois             return;
13050dbce65SJean-Christophe Dubois         }
13150dbce65SJean-Christophe Dubois 
13250dbce65SJean-Christophe Dubois         /* Set csum to 0 */
13350dbce65SJean-Christophe Dubois         stw_he_p(&tcp->th_sum, 0);
13450dbce65SJean-Christophe Dubois 
13550dbce65SJean-Christophe Dubois         csum = net_checksum_tcpudp(ip_len, ip->ip_p,
13650dbce65SJean-Christophe Dubois                                    (uint8_t *)&ip->ip_src,
13750dbce65SJean-Christophe Dubois                                    (uint8_t *)tcp);
13850dbce65SJean-Christophe Dubois 
13950dbce65SJean-Christophe Dubois         /* Store computed csum */
14050dbce65SJean-Christophe Dubois         stw_be_p(&tcp->th_sum, csum);
14150dbce65SJean-Christophe Dubois 
1427200ac3cSMark McLoughlin         break;
14350dbce65SJean-Christophe Dubois     }
14450dbce65SJean-Christophe Dubois     case IP_PROTO_UDP:
14550dbce65SJean-Christophe Dubois     {
14650dbce65SJean-Christophe Dubois         uint16_t csum;
14750dbce65SJean-Christophe Dubois         udp_header *udp = (udp_header *)(ip + 1);
14850dbce65SJean-Christophe Dubois 
14950dbce65SJean-Christophe Dubois         if (ip_len < sizeof(udp_header)) {
15050dbce65SJean-Christophe Dubois             return;
15150dbce65SJean-Christophe Dubois         }
15250dbce65SJean-Christophe Dubois 
15350dbce65SJean-Christophe Dubois         /* Set csum to 0 */
15450dbce65SJean-Christophe Dubois         stw_he_p(&udp->uh_sum, 0);
15550dbce65SJean-Christophe Dubois 
15650dbce65SJean-Christophe Dubois         csum = net_checksum_tcpudp(ip_len, ip->ip_p,
15750dbce65SJean-Christophe Dubois                                    (uint8_t *)&ip->ip_src,
15850dbce65SJean-Christophe Dubois                                    (uint8_t *)udp);
15950dbce65SJean-Christophe Dubois 
16050dbce65SJean-Christophe Dubois         /* Store computed csum */
16150dbce65SJean-Christophe Dubois         stw_be_p(&udp->uh_sum, csum);
16250dbce65SJean-Christophe Dubois 
1637200ac3cSMark McLoughlin         break;
16450dbce65SJean-Christophe Dubois     }
1657200ac3cSMark McLoughlin     default:
16650dbce65SJean-Christophe Dubois         /* Can't handle any other protocol */
16750dbce65SJean-Christophe Dubois         break;
1687200ac3cSMark McLoughlin     }
1697200ac3cSMark McLoughlin }
17084026301SDmitry Fleytman 
17184026301SDmitry Fleytman uint32_t
17284026301SDmitry Fleytman net_checksum_add_iov(const struct iovec *iov, const unsigned int iov_cnt,
173eb700029SDmitry Fleytman                      uint32_t iov_off, uint32_t size, uint32_t csum_offset)
17484026301SDmitry Fleytman {
17584026301SDmitry Fleytman     size_t iovec_off, buf_off;
17684026301SDmitry Fleytman     unsigned int i;
17784026301SDmitry Fleytman     uint32_t res = 0;
17884026301SDmitry Fleytman 
17984026301SDmitry Fleytman     iovec_off = 0;
18084026301SDmitry Fleytman     buf_off = 0;
18184026301SDmitry Fleytman     for (i = 0; i < iov_cnt && size; i++) {
18284026301SDmitry Fleytman         if (iov_off < (iovec_off + iov[i].iov_len)) {
18384026301SDmitry Fleytman             size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off , size);
18484026301SDmitry Fleytman             void *chunk_buf = iov[i].iov_base + (iov_off - iovec_off);
18584026301SDmitry Fleytman 
186eb700029SDmitry Fleytman             res += net_checksum_add_cont(len, chunk_buf, csum_offset);
187eb700029SDmitry Fleytman             csum_offset += len;
18884026301SDmitry Fleytman 
18984026301SDmitry Fleytman             buf_off += len;
19084026301SDmitry Fleytman             iov_off += len;
19184026301SDmitry Fleytman             size -= len;
19284026301SDmitry Fleytman         }
19384026301SDmitry Fleytman         iovec_off += iov[i].iov_len;
19484026301SDmitry Fleytman     }
19584026301SDmitry Fleytman     return res;
19684026301SDmitry Fleytman }
197