123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- /*
- * IP checksumming functions.
- * (c) 2008 Gerd Hoffmann <kraxel@redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; under version 2 or later of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
- */
- #include "qemu/osdep.h"
- #include "net/checksum.h"
- #include "net/eth.h"
- uint32_t net_checksum_add_cont(int len, uint8_t *buf, int seq)
- {
- uint32_t sum1 = 0, sum2 = 0;
- int i;
- for (i = 0; i < len - 1; i += 2) {
- sum1 += (uint32_t)buf[i];
- sum2 += (uint32_t)buf[i + 1];
- }
- if (i < len) {
- sum1 += (uint32_t)buf[i];
- }
- if (seq & 1) {
- return sum1 + (sum2 << 8);
- } else {
- return sum2 + (sum1 << 8);
- }
- }
- uint16_t net_checksum_finish(uint32_t sum)
- {
- while (sum>>16)
- sum = (sum & 0xFFFF)+(sum >> 16);
- return ~sum;
- }
- uint16_t net_checksum_tcpudp(uint16_t length, uint16_t proto,
- uint8_t *addrs, uint8_t *buf)
- {
- uint32_t sum = 0;
- sum += net_checksum_add(length, buf); // payload
- sum += net_checksum_add(8, addrs); // src + dst address
- sum += proto + length; // protocol & length
- return net_checksum_finish(sum);
- }
- void net_checksum_calculate(void *data, int length, int csum_flag)
- {
- int mac_hdr_len, ip_len;
- struct ip_header *ip;
- uint16_t csum;
- /*
- * Note: We cannot assume "data" is aligned, so the all code uses
- * some macros that take care of possible unaligned access for
- * struct members (just in case).
- */
- /* Ensure we have at least an Eth header */
- if (length < sizeof(struct eth_header)) {
- return;
- }
- /* Handle the optional VLAN headers */
- switch (lduw_be_p(&PKT_GET_ETH_HDR(data)->h_proto)) {
- case ETH_P_VLAN:
- mac_hdr_len = sizeof(struct eth_header) +
- sizeof(struct vlan_header);
- break;
- case ETH_P_DVLAN:
- if (lduw_be_p(&PKT_GET_VLAN_HDR(data)->h_proto) == ETH_P_VLAN) {
- mac_hdr_len = sizeof(struct eth_header) +
- 2 * sizeof(struct vlan_header);
- } else {
- mac_hdr_len = sizeof(struct eth_header) +
- sizeof(struct vlan_header);
- }
- break;
- default:
- mac_hdr_len = sizeof(struct eth_header);
- break;
- }
- length -= mac_hdr_len;
- /* Now check we have an IP header (with an optional VLAN header) */
- if (length < sizeof(struct ip_header)) {
- return;
- }
- ip = (struct ip_header *)((uint8_t *)data + mac_hdr_len);
- if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
- return; /* not IPv4 */
- }
- /* Calculate IP checksum */
- if (csum_flag & CSUM_IP) {
- stw_he_p(&ip->ip_sum, 0);
- csum = net_raw_checksum((uint8_t *)ip, IP_HDR_GET_LEN(ip));
- stw_be_p(&ip->ip_sum, csum);
- }
- if (IP4_IS_FRAGMENT(ip)) {
- return; /* a fragmented IP packet */
- }
- ip_len = lduw_be_p(&ip->ip_len);
- /* Last, check that we have enough data for the all IP frame */
- if (length < ip_len) {
- return;
- }
- ip_len -= IP_HDR_GET_LEN(ip);
- switch (ip->ip_p) {
- case IP_PROTO_TCP:
- {
- if (!(csum_flag & CSUM_TCP)) {
- return;
- }
- tcp_header *tcp = (tcp_header *)(ip + 1);
- if (ip_len < sizeof(tcp_header)) {
- return;
- }
- /* Set csum to 0 */
- stw_he_p(&tcp->th_sum, 0);
- csum = net_checksum_tcpudp(ip_len, ip->ip_p,
- (uint8_t *)&ip->ip_src,
- (uint8_t *)tcp);
- /* Store computed csum */
- stw_be_p(&tcp->th_sum, csum);
- break;
- }
- case IP_PROTO_UDP:
- {
- if (!(csum_flag & CSUM_UDP)) {
- return;
- }
- udp_header *udp = (udp_header *)(ip + 1);
- if (ip_len < sizeof(udp_header)) {
- return;
- }
- /* Set csum to 0 */
- stw_he_p(&udp->uh_sum, 0);
- csum = net_checksum_tcpudp(ip_len, ip->ip_p,
- (uint8_t *)&ip->ip_src,
- (uint8_t *)udp);
- /* Store computed csum */
- stw_be_p(&udp->uh_sum, csum);
- break;
- }
- default:
- /* Can't handle any other protocol */
- break;
- }
- }
- 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)
- {
- size_t iovec_off;
- unsigned int i;
- uint32_t res = 0;
- iovec_off = 0;
- for (i = 0; i < iov_cnt && size; i++) {
- if (iov_off < (iovec_off + iov[i].iov_len)) {
- size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off , size);
- void *chunk_buf = iov[i].iov_base + (iov_off - iovec_off);
- res += net_checksum_add_cont(len, chunk_buf, csum_offset);
- csum_offset += len;
- iov_off += len;
- size -= len;
- }
- iovec_off += iov[i].iov_len;
- }
- return res;
- }
|