diff options
author | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2011-12-30 16:23:18 +0100 |
---|---|---|
committer | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2011-12-30 16:25:54 +0100 |
commit | 4362d1a0c20b8490ffc3115ac4bd93183a670af6 (patch) | |
tree | e827216ca7636b25b8a951b33e39cabeb2bc7b71 /xen | |
parent | 58aaf6e7f80c13338b8bc1603d59a56300aa8ab3 (diff) |
Cope with dom0s which do not respect feature-no-csum-offload
Event with feature-no-csum-offload=1, some dom0s (such as Debian Squeeze's
2.6.32) send packets with a blank checksum. Recompute them before givin the
packet to pfinet.
* xen/net.c (recompute_checksum): New function.
(hyp_net_intr): Call recompute_checksum when the checksum is blank but the
data is validated.
Diffstat (limited to 'xen')
-rw-r--r-- | xen/net.c | 74 |
1 files changed, 74 insertions, 0 deletions
@@ -117,6 +117,61 @@ static void enqueue_rx_buf(struct net_data *nd, int number) { req->gref = nd->rx_buf_gnt[number] = gref; } +static int recompute_checksum(void *data, int len) { + unsigned16_t *header16 = data; + unsigned8_t *header8 = data; + unsigned length, i; + unsigned32_t checksum = 0; + + /* IPv4 header length */ + length = (header8[0] & 0xf) * 4; + if (length < 20) + /* Too small for an IP header16 */ + return -1; + if (length > len) + /* Does not fit in the ethernet frame */ + return -1; + + /* Compute IP header checksum */ + header16[5] = 0; + for (i = 0; i < length/2; i++) + checksum += ntohs(header16[i]); + + while (checksum >> 16) + checksum = (checksum & 0xffff) + (checksum >> 16); + + header16[5] = htons(~checksum); + + if (header8[9] == 6) { + /* Need to fix TCP checksum as well */ + unsigned16_t *tcp_header16 = header16 + length/2; + unsigned8_t *tcp_header8 = header8 + length; + unsigned tcp_length = ntohs(header16[1]) - length; + + /* Pseudo IP header */ + checksum = ntohs(header16[6]) + ntohs(header16[7]) + + ntohs(header16[8]) + ntohs(header16[9]) + + header8[9] + tcp_length; + + tcp_header16[8] = 0; + for (i = 0; i < tcp_length / 2; i++) + checksum += ntohs(tcp_header16[i]); + if (tcp_length & 1) + checksum += tcp_header8[tcp_length-1] << 8; + + while (checksum >> 16) + checksum = (checksum & 0xffff) + (checksum >> 16); + + tcp_header16[8] = htons(~checksum); + } else if (header8[9] == 17) { + /* Drop any bogus checksum */ + unsigned16_t *udp_header16 = header16 + length/2; + udp_header16[3] = 0; + } + + return 0; +} + static void hyp_net_intr(int unit) { ipc_kmsg_t kmsg; struct ether_header *eh; @@ -172,6 +227,25 @@ static void hyp_net_intr(int unit) { data = nd->rx_buf[number] + rx_rsp->offset; len = rx_rsp->status; + if (rx_rsp->flags & NETRXF_csum_blank) { + struct ether_header *ether = data; + + if (!(rx_rsp->flags & NETRXF_data_validated)) { + printf("packet with no checksum and not validated, dropping it\n"); + goto drop_kmsg; + } + + /* TODO: rather tell pfinet to ignore checksums */ + + if (ntohs(ether->ether_type) != 0x0800) { + printf("packet with no checksum and not IPv4, dropping it\n"); + goto drop_kmsg; + } + + if (recompute_checksum(data + sizeof(*ether), len - sizeof(*ether))) + goto drop_kmsg; + } + eh = (void*) (net_kmsg(kmsg)->header); ph = (void*) (net_kmsg(kmsg)->packet); memcpy(eh, data, sizeof (struct ether_header)); |