xref: /qemu/net/checksum.c (revision 0a19d879)
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 
net_checksum_add_cont(int len,uint8_t * buf,int seq)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 
net_checksum_finish(uint32_t sum)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 
net_checksum_tcpudp(uint16_t length,uint16_t proto,uint8_t * addrs,uint8_t * buf)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 
net_checksum_calculate(uint8_t * data,int length,int csum_flag)60f5746335SBin Meng void net_checksum_calculate(uint8_t *data, int length, int csum_flag)
617200ac3cSMark McLoughlin {
62ade6bad1SJean-Christophe Dubois     int mac_hdr_len, ip_len;
6350dbce65SJean-Christophe Dubois     struct ip_header *ip;
64d97f1159SGuishan Qin     uint16_t csum;
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 
770a19d879SMichael Tokarev     /* Handle the optional 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 
990a19d879SMichael Tokarev     /* Now check we have an IP header (with an optional 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 
110d97f1159SGuishan Qin     /* Calculate IP checksum */
111f5746335SBin Meng     if (csum_flag & CSUM_IP) {
112d97f1159SGuishan Qin         stw_he_p(&ip->ip_sum, 0);
113d97f1159SGuishan Qin         csum = net_raw_checksum((uint8_t *)ip, IP_HDR_GET_LEN(ip));
114d97f1159SGuishan Qin         stw_be_p(&ip->ip_sum, csum);
115f5746335SBin Meng     }
116d97f1159SGuishan Qin 
1170dcf0c0aSMarkus Carlstedt     if (IP4_IS_FRAGMENT(ip)) {
1180dcf0c0aSMarkus Carlstedt         return; /* a fragmented IP packet */
1190dcf0c0aSMarkus Carlstedt     }
1200dcf0c0aSMarkus Carlstedt 
12150dbce65SJean-Christophe Dubois     ip_len = lduw_be_p(&ip->ip_len);
12250dbce65SJean-Christophe Dubois 
12350dbce65SJean-Christophe Dubois     /* Last, check that we have enough data for the all IP frame */
12450dbce65SJean-Christophe Dubois     if (length < ip_len) {
12550dbce65SJean-Christophe Dubois         return;
12650dbce65SJean-Christophe Dubois     }
12750dbce65SJean-Christophe Dubois 
12850dbce65SJean-Christophe Dubois     ip_len -= IP_HDR_GET_LEN(ip);
12950dbce65SJean-Christophe Dubois 
13050dbce65SJean-Christophe Dubois     switch (ip->ip_p) {
13150dbce65SJean-Christophe Dubois     case IP_PROTO_TCP:
13250dbce65SJean-Christophe Dubois     {
133f5746335SBin Meng         if (!(csum_flag & CSUM_TCP)) {
134f5746335SBin Meng             return;
135f5746335SBin Meng         }
136f5746335SBin Meng 
13750dbce65SJean-Christophe Dubois         tcp_header *tcp = (tcp_header *)(ip + 1);
13850dbce65SJean-Christophe Dubois 
13950dbce65SJean-Christophe Dubois         if (ip_len < sizeof(tcp_header)) {
14050dbce65SJean-Christophe Dubois             return;
14150dbce65SJean-Christophe Dubois         }
14250dbce65SJean-Christophe Dubois 
14350dbce65SJean-Christophe Dubois         /* Set csum to 0 */
14450dbce65SJean-Christophe Dubois         stw_he_p(&tcp->th_sum, 0);
14550dbce65SJean-Christophe Dubois 
14650dbce65SJean-Christophe Dubois         csum = net_checksum_tcpudp(ip_len, ip->ip_p,
14750dbce65SJean-Christophe Dubois                                    (uint8_t *)&ip->ip_src,
14850dbce65SJean-Christophe Dubois                                    (uint8_t *)tcp);
14950dbce65SJean-Christophe Dubois 
15050dbce65SJean-Christophe Dubois         /* Store computed csum */
15150dbce65SJean-Christophe Dubois         stw_be_p(&tcp->th_sum, csum);
15250dbce65SJean-Christophe Dubois 
1537200ac3cSMark McLoughlin         break;
15450dbce65SJean-Christophe Dubois     }
15550dbce65SJean-Christophe Dubois     case IP_PROTO_UDP:
15650dbce65SJean-Christophe Dubois     {
157f5746335SBin Meng         if (!(csum_flag & CSUM_UDP)) {
158f5746335SBin Meng             return;
159f5746335SBin Meng         }
160f5746335SBin Meng 
16150dbce65SJean-Christophe Dubois         udp_header *udp = (udp_header *)(ip + 1);
16250dbce65SJean-Christophe Dubois 
16350dbce65SJean-Christophe Dubois         if (ip_len < sizeof(udp_header)) {
16450dbce65SJean-Christophe Dubois             return;
16550dbce65SJean-Christophe Dubois         }
16650dbce65SJean-Christophe Dubois 
16750dbce65SJean-Christophe Dubois         /* Set csum to 0 */
16850dbce65SJean-Christophe Dubois         stw_he_p(&udp->uh_sum, 0);
16950dbce65SJean-Christophe Dubois 
17050dbce65SJean-Christophe Dubois         csum = net_checksum_tcpudp(ip_len, ip->ip_p,
17150dbce65SJean-Christophe Dubois                                    (uint8_t *)&ip->ip_src,
17250dbce65SJean-Christophe Dubois                                    (uint8_t *)udp);
17350dbce65SJean-Christophe Dubois 
17450dbce65SJean-Christophe Dubois         /* Store computed csum */
17550dbce65SJean-Christophe Dubois         stw_be_p(&udp->uh_sum, csum);
17650dbce65SJean-Christophe Dubois 
1777200ac3cSMark McLoughlin         break;
17850dbce65SJean-Christophe Dubois     }
1797200ac3cSMark McLoughlin     default:
18050dbce65SJean-Christophe Dubois         /* Can't handle any other protocol */
18150dbce65SJean-Christophe Dubois         break;
1827200ac3cSMark McLoughlin     }
1837200ac3cSMark McLoughlin }
18484026301SDmitry Fleytman 
18584026301SDmitry Fleytman uint32_t
net_checksum_add_iov(const struct iovec * iov,const unsigned int iov_cnt,uint32_t iov_off,uint32_t size,uint32_t csum_offset)18684026301SDmitry Fleytman net_checksum_add_iov(const struct iovec *iov, const unsigned int iov_cnt,
187eb700029SDmitry Fleytman                      uint32_t iov_off, uint32_t size, uint32_t csum_offset)
18884026301SDmitry Fleytman {
189fe4bd917SRichard Henderson     size_t iovec_off;
19084026301SDmitry Fleytman     unsigned int i;
19184026301SDmitry Fleytman     uint32_t res = 0;
19284026301SDmitry Fleytman 
19384026301SDmitry Fleytman     iovec_off = 0;
19484026301SDmitry Fleytman     for (i = 0; i < iov_cnt && size; i++) {
19584026301SDmitry Fleytman         if (iov_off < (iovec_off + iov[i].iov_len)) {
19684026301SDmitry Fleytman             size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off , size);
19784026301SDmitry Fleytman             void *chunk_buf = iov[i].iov_base + (iov_off - iovec_off);
19884026301SDmitry Fleytman 
199eb700029SDmitry Fleytman             res += net_checksum_add_cont(len, chunk_buf, csum_offset);
200eb700029SDmitry Fleytman             csum_offset += len;
20184026301SDmitry Fleytman 
20284026301SDmitry Fleytman             iov_off += len;
20384026301SDmitry Fleytman             size -= len;
20484026301SDmitry Fleytman         }
20584026301SDmitry Fleytman         iovec_off += iov[i].iov_len;
20684026301SDmitry Fleytman     }
20784026301SDmitry Fleytman     return res;
20884026301SDmitry Fleytman }
209