github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/pkg/tcpip/header/checksum.go (about)

     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  	"github.com/sagernet/gvisor/pkg/tcpip"
    24  	"github.com/sagernet/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  }