/* * Copyright (c) 2014, 2016 VMware, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "precomp.h" #include "Debug.h" #include "Flow.h" #include "Offload.h" #include "PacketParser.h" #ifdef OVS_DBG_MOD #undef OVS_DBG_MOD #endif #define OVS_DBG_MOD OVS_DBG_CHECKSUM #ifndef htons #define htons(_x) (((UINT16)(_x) >> 8) + (((UINT16)(_x) << 8) & 0xff00)) #endif #ifndef swap64 #define swap64(_x) ((((UINT64)(_x) >> 8) & 0x00ff00ff00ff00ff) + \ (((UINT64)(_x) << 8) & 0xff00ff00ff00ff00)) #endif #define fold64(_x) \ _x = ((_x) >> 32) + ((_x) & 0xffffffff); \ _x = (UINT32)(((_x) >> 32) + (_x)); \ _x = ((_x) >> 16) + ((_x) & 0xffff); \ _x = (UINT16)(((_x) >> 16) + (_x)) #define fold32(_x) \ _x = ((_x) >> 16) + ((_x) & 0xffff); \ _x = (UINT16)(((_x) >> 16) + (_x)) /* *---------------------------------------------------------------------------- * CalculateOnesComplement -- * * Given the start address and buffer length, calculate the 1's complement * This routine can be used when multiple buffers are used for a packets. * * PLEASE NOTE, even though the last parameter is UINT64, but the assumption * is it will not overflowed after adding the extra data. * ------------------------------------------------ * * Result: * As name indicate, the final data is not 1's complemnent *---------------------------------------------------------------------------- */ UINT64 CalculateOnesComplement(UINT8 *start, UINT16 totalLength, UINT64 initial, BOOLEAN isEvenStart) { UINT64 sum = 0, val64; UINT64 *src = (UINT64 *)start; while (totalLength > 7) { val64 = *src; sum += val64; if (sum < val64) sum++; src++; totalLength -= 8; } start = (UINT8 *)src; if (totalLength > 3) { UINT32 val = *(UINT32 *)start; sum += val; if (sum < val) sum++; start += 4; totalLength -= 4; } if (totalLength > 1) { UINT16 val = *(UINT16 *)start; sum += val; if (sum < val) sum++; start += 2; totalLength -= 2; } if (totalLength > 0) { UINT8 val = *start; sum += val; if (sum < val) sum++; start += 1; totalLength -= 1; } ASSERT(totalLength == 0); if (!isEvenStart) { sum = _byteswap_uint64(sum); } sum += initial; if (sum < initial) sum++; return sum; } /* *---------------------------------------------------------------------------- * CalculateChecksum -- * * Given the start point, and length, calculate the checksum * as 1's complement of 1's comlement. * * This assume the checksum field is initailized properly. * * Input Parameter: * ptr: point to the data to be checksumed * totalLength: total length of the data * initial: inital value to remit the checksum. Please note this * value should be network byte order value. * * The last parameter may be useful where you don't want to set * checksum field to zero, in that case you can pass ~checksum, * this is equivalent of set checksum field to zero. * * Result: * The result can be assigned to checksum field directly. *---------------------------------------------------------------------------- */ UINT16 CalculateChecksum(UINT8 *ptr, UINT16 totalLength, UINT16 initial) { UINT64 sum = CalculateOnesComplement(ptr, totalLength, initial, TRUE); fold64(sum); return (UINT16)~sum; } /* *---------------------------------------------------------------------------- * CopyAndCalculateOnesComplement -- * * Given the start address and buffer length, calculate the 1's complement * at same time, copt the data from src to dst. * * This routine can be used when multiple buffers are used for a packets. * * PLEASE NOTE, even though the last parameter is UINT64, but the assumption * is it will not overflowed after adding the extra data. * ------------------------------------------------ * * Result: * As name indicate, the final data is not 1's complemnent *---------------------------------------------------------------------------- */ UINT64 CopyAndCalculateOnesComplement(UINT8 *dst, UINT8 *src, UINT16 length, UINT64 initial, BOOLEAN isEvenStart) { UINT64 sum =0, val; UINT64 *src64, *dst64; union { UINT32 val; UINT8 b8[4]; } tmp; src64 = (UINT64 *)src; dst64 = (UINT64 *)dst; while (length > 7) { val = *src64; *dst64 = val; sum += (val >> 32) + (val & 0xffffffff); src64++; dst64++; length -= 8; } if (length > 3) { val = *(UINT32 *)src64; *(UINT32 *)dst64 = (UINT32)val; sum += (UINT32)val; dst64 = (UINT64 *)((UINT8 *)dst64 + 4); src64 = (UINT64 *)((UINT8 *)src64 + 4); length -= 4; } src = (UINT8 *)src64; dst = (UINT8 *)dst64; tmp.val = 0; switch (length) { case 3: dst[2] = src[2]; tmp.b8[2] = src[2]; case 2: dst[1] = src[1]; tmp.b8[1] = src[1]; case 1: dst[0] = src[0]; tmp.b8[0] = src[0]; sum += tmp.val; } sum = (isEvenStart ? sum : swap64(sum)) + initial; return sum; } /* *---------------------------------------------------------------------------- * CopyAndCalculateChecksum -- * * This is similar to CalculateChecksum, except it will also copy data to * destination address. *---------------------------------------------------------------------------- */ UINT16 CopyAndCalculateChecksum(UINT8 *dst, UINT8 *src, UINT16 length, UINT16 initial) { UINT64 sum = CopyAndCalculateOnesComplement(dst, src, length, initial, TRUE); fold64(sum); return (UINT16)~sum; } /* *---------------------------------------------------------------------------- * IPChecksum -- * * Give IP header, calculate the IP checksum. * We assume IP checksum field is initialized properly * * Input Pramater: * ipHdr: IP header start point * length: IP header length (potentially include IP options) * initial: same as CalculateChecksum * * Result: * The result is already 1's complement, so can be assigned * to checksum field directly *---------------------------------------------------------------------------- */ UINT16 IPChecksum(UINT8 *ipHdr, UINT16 length, UINT16 initial) { UINT32 sum = initial; UINT16 *ptr = (UINT16 *)ipHdr; ASSERT((length & 0x3) == 0); while (length > 1) { sum += ptr[0]; ptr++; length -= 2; } fold32(sum); return (UINT16)~sum; } /* *---------------------------------------------------------------------------- * IPPseudoChecksum -- * * Give src and dst IP address, protocol value and total * upper layer length(not include IP header, but include * upller layer protocol header, for example it include * TCP header for TCP checksum), calculate the pseudo * checksum, please note this checksum is just 1's complement * addition. * * Input Parameter: * src: please note it is in network byte order * dst: same as src * protocol: protocol value in IP header * totalLength: total length of upper layer data including * header. * * Result: * * This value should be put in TCP checksum field before * calculating TCP checksum using CalculateChecksum with * initial value of 0. *---------------------------------------------------------------------------- */ UINT16 IPPseudoChecksum(UINT32 *src, UINT32 *dst, UINT8 protocol, UINT16 totalLength) { UINT32 sum = (UINT32)htons(totalLength) + htons(protocol); sum += (*src >> 16) + (*src & 0xffff); sum += (*dst >> 16) + (*dst & 0xffff); fold32(sum); return (UINT16)sum; } /* *---------------------------------------------------------------------------- * IPv6PseudoChecksum -- * * Given IPv6 src and dst address, upper layer protocol and total * upper layer protocol data length including upper layer header * part, calculate the pseudo checksum for upper layer protocol * checksum. * * please note this checksum is just 1's complement addition. * * Input Parameter: * src: src IPv6 address in network byte order * dst: dst IPv6 address. * protocol: upper layer protocol * totalLength: total length of upper layer data. Please note this is * in host byte order. * * Result: * * Place in upper layer checksum field before calculate upper layer * checksum. *---------------------------------------------------------------------------- */ UINT16 IPv6PseudoChecksum(UINT32 *src, UINT32 *dst, UINT8 protocol, UINT16 totalLength) { UINT64 sum = (UINT32)htons(totalLength) + htons(protocol); sum += (UINT64)src[0] + src[1] + src[2] + src[3]; sum += (UINT64)dst[0] + dst[1] + dst[2] + dst[3]; fold64(sum); return (UINT16)sum; } /* *---------------------------------------------------------------------------- * ChecksumUpdate32 -- * * Given old checksum value (as it is in checksum field), * prev value of the relevant field in network byte order * new value of the relevant field in the network byte order * calculate the new checksum. * Please check relevant RFC for reference. * * Input Pramater: * oldSum: old checksum value in checksum field * prev: previous value of relevant 32 bit feld in network * byte order. * new: new value of the relevant 32 bit field in network * byte order. * * Result: * new checksum value to be placed in the checksum field. *---------------------------------------------------------------------------- */ UINT16 ChecksumUpdate32(UINT16 oldSum, UINT32 prev, UINT32 newValue) { UINT32 sum = ~prev; sum = (sum >> 16) + (sum & 0xffff); sum += (newValue >> 16) + (newValue & 0xffff); sum += (UINT16)~oldSum; fold32(sum); return (UINT16)~sum; } /* *---------------------------------------------------------------------------- * ChecksumUpdate16 -- * * Given old checksum value (as it is in checksum field), * prev value of the relevant field in network byte order * new value of the relevant field in the network byte order * calculate the new checksum. * Please check relevant RFC for reference. * * Input Pramater: * oldSum: old checksum value in checksum field * prev: previous value of relevant 32 bit feld in network * byte order. * new: new value of the relevant 32 bit field in network * byte order. * * Result: * new checksum value to be placed in the checksum field. *---------------------------------------------------------------------------- */ UINT16 ChecksumUpdate16(UINT16 oldSum, UINT16 prev, UINT16 newValue) { UINT32 sum = (UINT16)~oldSum; sum += (UINT32)((UINT16)~prev) + newValue; fold32(sum); return (UINT16)~sum; } /* *---------------------------------------------------------------------------- * CalculateChecksumNB -- * * Calculates checksum over a length of bytes contained in an NB. * * nb : NB which contains the packet bytes. * csumDataLen : Length of bytes to be checksummed. * offset : offset to the first bytes of the data stream to be * checksumed. * * Result: * return 0, if there is a failure. *---------------------------------------------------------------------------- */ UINT16 CalculateChecksumNB(const PNET_BUFFER nb, UINT16 csumDataLen, UINT32 offset) { ULONG mdlLen; UINT16 csLen; PUCHAR src; UINT64 csum = 0; PMDL currentMdl; ULONG firstMdlLen; /* Running count of bytes in remainder of the MDLs including current. */ ULONG packetLen; BOOLEAN swapEnd = 1 & csumDataLen; if ((nb == NULL) || (csumDataLen == 0) || (offset >= NET_BUFFER_DATA_LENGTH(nb)) || (offset + csumDataLen > NET_BUFFER_DATA_LENGTH(nb))) { OVS_LOG_ERROR("Invalid parameters - csum length %u, offset %u," "pkt%s len %u", csumDataLen, offset, nb? "":"(null)", nb? NET_BUFFER_DATA_LENGTH(nb) : 0); return 0; } currentMdl = NET_BUFFER_CURRENT_MDL(nb); packetLen = NET_BUFFER_DATA_LENGTH(nb); firstMdlLen = MmGetMdlByteCount(currentMdl) - NET_BUFFER_CURRENT_MDL_OFFSET(nb); firstMdlLen = MIN(firstMdlLen, packetLen); if (offset < firstMdlLen) { src = (PUCHAR)OvsGetMdlWithLowPriority(currentMdl); if (!src) { return 0; } src += (NET_BUFFER_CURRENT_MDL_OFFSET(nb) + offset); mdlLen = firstMdlLen - offset; packetLen -= firstMdlLen; ASSERT((INT)packetLen >= 0); } else { offset -= firstMdlLen; packetLen -= firstMdlLen; ASSERT((INT)packetLen >= 0); currentMdl = NDIS_MDL_LINKAGE(currentMdl); mdlLen = MmGetMdlByteCount(currentMdl); mdlLen = MIN(mdlLen, packetLen); while (offset >= mdlLen) { offset -= mdlLen; packetLen -= mdlLen; ASSERT((INT)packetLen >= 0); currentMdl = NDIS_MDL_LINKAGE(currentMdl); mdlLen = MmGetMdlByteCount(currentMdl); mdlLen = MIN(mdlLen, packetLen); } src = (PUCHAR)OvsGetMdlWithLowPriority(currentMdl); if (!src) { return 0; } src += offset; mdlLen -= offset; } while (csumDataLen && (currentMdl != NULL)) { ASSERT(mdlLen < 65536); csLen = MIN((UINT16) mdlLen, csumDataLen); csum = CalculateOnesComplement(src, csLen, csum, !(1 & csumDataLen)); fold64(csum); csumDataLen -= csLen; currentMdl = NDIS_MDL_LINKAGE(currentMdl); if (csumDataLen && currentMdl) { src = OvsGetMdlWithLowPriority(currentMdl); if (!src) { return 0; } mdlLen = MmGetMdlByteCount(currentMdl); mdlLen = MIN(mdlLen, packetLen); /* packetLen does not include the current MDL from here on. */ packetLen -= mdlLen; ASSERT((INT)packetLen >= 0); } } fold64(csum); ASSERT(csumDataLen == 0); ASSERT((csum & ~0xffff) == 0); csum = (UINT16)~csum; if (swapEnd) { return _byteswap_ushort((UINT16)csum); } return (UINT16)csum; } /* * -------------------------------------------------------------------------- * OvsValidateIPChecksum * -------------------------------------------------------------------------- */ NDIS_STATUS OvsValidateIPChecksum(PNET_BUFFER_LIST curNbl, POVS_PACKET_HDR_INFO hdrInfo) { NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo; uint16_t checksum, hdrChecksum; struct IPHdr ip_storage; const IPHdr *ipHdr; if (!hdrInfo->isIPv4) { /*need check add v6 check logic*/ return NDIS_STATUS_SUCCESS; } /* First check if NIC has indicated checksum failure. */ csumInfo.Value = NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo); if (csumInfo.Receive.IpChecksumFailed) { return NDIS_STATUS_FAILURE; } /* Next, check if the NIC did not validate the RX checksum. */ if (!csumInfo.Receive.IpChecksumSucceeded) { ipHdr = OvsGetIp(curNbl, hdrInfo->l3Offset, &ip_storage); if (ipHdr) { ip_storage = *ipHdr; hdrChecksum = ipHdr->check; ip_storage.check = 0; checksum = IPChecksum((uint8 *)&ip_storage, ipHdr->ihl * 4, 0); if (checksum != hdrChecksum) { return NDIS_STATUS_FAILURE; } } else { /* Invalid network header */ return NDIS_STATUS_FAILURE; } } return NDIS_STATUS_SUCCESS; } /* *---------------------------------------------------------------------------- * OvsValidateUDPChecksum *---------------------------------------------------------------------------- */ NDIS_STATUS OvsValidateUDPChecksum(PNET_BUFFER_LIST curNbl, BOOLEAN udpCsumZero) { NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo; csumInfo.Value = NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo); if (udpCsumZero) { /* Zero is valid checksum. */ csumInfo.Receive.UdpChecksumFailed = 0; NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo) = csumInfo.Value; return NDIS_STATUS_SUCCESS; } /* First check if NIC has indicated UDP checksum failure. */ if (csumInfo.Receive.UdpChecksumFailed) { return NDIS_STATUS_INVALID_PACKET; } return NDIS_STATUS_SUCCESS; } /* *---------------------------------------------------------------------------- * OvsCalculateUDPChecksum * Calculate UDP checksum *---------------------------------------------------------------------------- */ NDIS_STATUS OvsCalculateUDPChecksum(PNET_BUFFER_LIST curNbl, PNET_BUFFER curNb, EthHdr *ethHdr, UDPHdr *udpHdr, UINT32 packetLength, POVS_PACKET_HDR_INFO layers) { NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo; UINT16 checkSum; csumInfo.Value = NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo); /* Next check if UDP checksum has been calculated. */ if (!csumInfo.Receive.UdpChecksumSucceeded) { UINT32 l4Payload = packetLength - layers->l4Offset; checkSum = udpHdr->check; switch (ethHdr->Type) { case ETH_TYPE_IPV4_NBO: { IPHdr *ipHdr = (IPHdr *)((PCHAR)ethHdr + layers->l3Offset); udpHdr->check = 0; udpHdr->check = IPPseudoChecksum((UINT32 *)&ipHdr->saddr, (UINT32 *)&ipHdr->daddr, IPPROTO_UDP, (UINT16)l4Payload); udpHdr->check = CalculateChecksumNB(curNb, (UINT16)l4Payload, layers->l4Offset); break; } case ETH_TYPE_IPV6_NBO: { IPv6Hdr *ipv6Hdr = (IPv6Hdr *)((PCHAR)ethHdr + layers->l3Offset); udpHdr->check = 0; udpHdr->check = IPv6PseudoChecksum((UINT32 *)&ipv6Hdr->saddr, (UINT32 *)&ipv6Hdr->daddr, IPPROTO_UDP, (UINT16)l4Payload); udpHdr->check = CalculateChecksumNB(curNb, (UINT16)l4Payload, layers->l4Offset); break; } default: OVS_LOG_ERROR("Invalid eth type: %d\n", ethHdr->Type); ASSERT(!"Invalid eth type"); } if (checkSum != udpHdr->check) { OVS_LOG_ERROR("UDP checksum incorrect, expected %u, got %u", udpHdr->check, checkSum); return NDIS_STATUS_INVALID_PACKET; } } csumInfo.Receive.UdpChecksumSucceeded = 1; NET_BUFFER_LIST_INFO(curNbl, TcpIpChecksumNetBufferListInfo) = csumInfo.Value; return NDIS_STATUS_SUCCESS; } /* * OvsApplySWChecksumOnNB -- * * This function calculates and sets the required software offloads given by * csumInfo for a given NBL(nbl). If the given net buffer list 'nbl' * has multiple net buffers, we assume that they are part of the same * connection with the same offsets defined in 'layers'. * */ NDIS_STATUS OvsApplySWChecksumOnNB(POVS_PACKET_HDR_INFO layers, PNET_BUFFER_LIST nbl, PNDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO csumInfo) { PNET_BUFFER curNb; PMDL curMdl; PUINT8 bufferStart; UINT32 packetLength = 0; ASSERT(nbl != NULL); for (curNb = NET_BUFFER_LIST_FIRST_NB(nbl); curNb != NULL; curNb = curNb->Next) { packetLength = NET_BUFFER_DATA_LENGTH(curNb); curMdl = NET_BUFFER_CURRENT_MDL(curNb); bufferStart = (PUINT8)OvsGetMdlWithLowPriority(curMdl); if (!bufferStart) { return NDIS_STATUS_RESOURCES; } bufferStart += NET_BUFFER_CURRENT_MDL_OFFSET(curNb); if (layers->isIPv4) { IPHdr *ip = (IPHdr *)(bufferStart + layers->l3Offset); if (csumInfo->Transmit.IpHeaderChecksum) { ip->check = 0; ip->check = IPChecksum((UINT8 *)ip, 4 * ip->ihl, 0); } if (layers->isTcp && csumInfo->Transmit.TcpChecksum) { UINT16 csumLength = (UINT16)(packetLength - layers->l4Offset); TCPHdr *tcp = (TCPHdr *)(bufferStart + layers->l4Offset); tcp->check = IPPseudoChecksum(&ip->saddr, &ip->daddr, IPPROTO_TCP, csumLength); tcp->check = CalculateChecksumNB(curNb, csumLength, (UINT32)(layers->l4Offset)); } else if (layers->isUdp && csumInfo->Transmit.UdpChecksum) { UINT16 csumLength = (UINT16)(packetLength - layers->l4Offset); UDPHdr *udp = (UDPHdr *)((PCHAR)ip + sizeof *ip); udp->check = IPPseudoChecksum(&ip->saddr, &ip->daddr, IPPROTO_UDP, csumLength); udp->check = CalculateChecksumNB(curNb, csumLength, (UINT32)(layers->l4Offset)); } } else if (layers->isIPv6) { IPv6Hdr *ip = (IPv6Hdr *)(bufferStart + layers->l3Offset); if (layers->isTcp && csumInfo->Transmit.TcpChecksum) { UINT16 csumLength = (UINT16)(packetLength - layers->l4Offset); TCPHdr *tcp = (TCPHdr *)(bufferStart + layers->l4Offset); tcp->check = IPv6PseudoChecksum((UINT32 *) &ip->saddr, (UINT32 *) &ip->daddr, IPPROTO_TCP, csumLength); tcp->check = CalculateChecksumNB(curNb, csumLength, (UINT32)(layers->l4Offset)); } else if (layers->isUdp && csumInfo->Transmit.UdpChecksum) { UINT16 csumLength = (UINT16)(packetLength - layers->l4Offset); UDPHdr *udp = (UDPHdr *)((PCHAR)ip + sizeof *ip); udp->check = IPv6PseudoChecksum((UINT32 *) &ip->saddr, (UINT32 *) &ip->daddr, IPPROTO_UDP, csumLength); udp->check = CalculateChecksumNB(curNb, csumLength, (UINT32)(layers->l4Offset)); } } } return NDIS_STATUS_SUCCESS; } /* * OVSGetTcpMSS -- * * This function returns the maximum segment size of the given NBL. It takes * into consideration both LSO v1 and v2. */ ULONG OVSGetTcpMSS(PNET_BUFFER_LIST nbl) { NDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO lsoInfo; ASSERT(nbl != NULL); lsoInfo.Value = NET_BUFFER_LIST_INFO(nbl, TcpLargeSendNetBufferListInfo); switch (lsoInfo.Transmit.Type) { case NDIS_TCP_LARGE_SEND_OFFLOAD_V1_TYPE: return lsoInfo.LsoV1Transmit.MSS; break; case NDIS_TCP_LARGE_SEND_OFFLOAD_V2_TYPE: return lsoInfo.LsoV2Transmit.MSS; break; default: OVS_LOG_ERROR("Unknown LSO transmit type:%d", lsoInfo.Transmit.Type); return 0; } }