checksum.go raw

   1  // Copyright 2018 The gVisor Authors.
   2  //
   3  // Licensed under the Apache License, Version 2.0 (the "License");
   4  // you may not use this file except in compliance with the License.
   5  // You may obtain a copy of the License at
   6  //
   7  //     http://www.apache.org/licenses/LICENSE-2.0
   8  //
   9  // Unless required by applicable law or agreed to in writing, software
  10  // distributed under the License is distributed on an "AS IS" BASIS,
  11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12  // See the License for the specific language governing permissions and
  13  // limitations under the License.
  14  
  15  // Package header provides the implementation of the encoding and decoding of
  16  // network protocol headers.
  17  package header
  18  
  19  import (
  20  	"encoding/binary"
  21  	"fmt"
  22  
  23  	"gvisor.dev/gvisor/pkg/tcpip"
  24  	"gvisor.dev/gvisor/pkg/tcpip/checksum"
  25  )
  26  
  27  // PseudoHeaderChecksum calculates the pseudo-header checksum for the given
  28  // destination protocol and network address. Pseudo-headers are needed by
  29  // transport layers when calculating their own checksum.
  30  func PseudoHeaderChecksum(protocol tcpip.TransportProtocolNumber, srcAddr tcpip.Address, dstAddr tcpip.Address, totalLen uint16) uint16 {
  31  	xsum := checksum.Checksum(srcAddr.AsSlice(), 0)
  32  	xsum = checksum.Checksum(dstAddr.AsSlice(), xsum)
  33  
  34  	// Add the length portion of the checksum to the pseudo-checksum.
  35  	var tmp [2]byte
  36  	binary.BigEndian.PutUint16(tmp[:], totalLen)
  37  	xsum = checksum.Checksum(tmp[:], xsum)
  38  
  39  	return checksum.Checksum([]byte{0, uint8(protocol)}, xsum)
  40  }
  41  
  42  // checksumUpdate2ByteAlignedUint16 updates a uint16 value in a calculated
  43  // checksum.
  44  //
  45  // The value MUST begin at a 2-byte boundary in the original buffer.
  46  func checksumUpdate2ByteAlignedUint16(xsum, old, new uint16) uint16 {
  47  	// As per RFC 1071 page 4,
  48  	//	(4)  Incremental Update
  49  	//
  50  	//        ...
  51  	//
  52  	//        To update the checksum, simply add the differences of the
  53  	//        sixteen bit integers that have been changed.  To see why this
  54  	//        works, observe that every 16-bit integer has an additive inverse
  55  	//        and that addition is associative.  From this it follows that
  56  	//        given the original value m, the new value m', and the old
  57  	//        checksum C, the new checksum C' is:
  58  	//
  59  	//                C' = C + (-m) + m' = C + (m' - m)
  60  	if old == new {
  61  		return xsum
  62  	}
  63  	return checksum.Combine(xsum, checksum.Combine(new, ^old))
  64  }
  65  
  66  // checksumUpdate2ByteAlignedAddress updates an address in a calculated
  67  // checksum.
  68  //
  69  // The addresses must have the same length and must contain an even number
  70  // of bytes. The address MUST begin at a 2-byte boundary in the original buffer.
  71  func checksumUpdate2ByteAlignedAddress(xsum uint16, old, new tcpip.Address) uint16 {
  72  	const uint16Bytes = 2
  73  
  74  	if old.BitLen() != new.BitLen() {
  75  		panic(fmt.Sprintf("buffer lengths are different; old = %d, new = %d", old.BitLen()/8, new.BitLen()/8))
  76  	}
  77  
  78  	if oldBytes := old.BitLen() % 16; oldBytes != 0 {
  79  		panic(fmt.Sprintf("buffer has an odd number of bytes; got = %d", oldBytes))
  80  	}
  81  
  82  	oldAddr := old.AsSlice()
  83  	newAddr := new.AsSlice()
  84  
  85  	// As per RFC 1071 page 4,
  86  	//	(4)  Incremental Update
  87  	//
  88  	//        ...
  89  	//
  90  	//        To update the checksum, simply add the differences of the
  91  	//        sixteen bit integers that have been changed.  To see why this
  92  	//        works, observe that every 16-bit integer has an additive inverse
  93  	//        and that addition is associative.  From this it follows that
  94  	//        given the original value m, the new value m', and the old
  95  	//        checksum C, the new checksum C' is:
  96  	//
  97  	//                C' = C + (-m) + m' = C + (m' - m)
  98  	for len(oldAddr) != 0 {
  99  		// Convert the 2 byte sequences to uint16 values then apply the increment
 100  		// update.
 101  		xsum = checksumUpdate2ByteAlignedUint16(xsum, (uint16(oldAddr[0])<<8)+uint16(oldAddr[1]), (uint16(newAddr[0])<<8)+uint16(newAddr[1]))
 102  		oldAddr = oldAddr[uint16Bytes:]
 103  		newAddr = newAddr[uint16Bytes:]
 104  	}
 105  
 106  	return xsum
 107  }
 108