qemu/net/checksum.c
<<
>>
Prefs
   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
  23uint32_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
  38uint16_t net_checksum_finish(uint32_t sum)
  39{
  40    while (sum>>16)
  41        sum = (sum & 0xFFFF)+(sum >> 16);
  42    return ~sum;
  43}
  44
  45uint16_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
  56void 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
 163uint32_t
 164net_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