checksum.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package iputil
  2. import (
  3. "encoding/binary"
  4. "github.com/google/gopacket/layers"
  5. "golang.org/x/net/ipv4"
  6. "golang.org/x/net/ipv6"
  7. "gvisor.dev/gvisor/pkg/tcpip/checksum"
  8. )
  9. const udpHeaderLen = 8
  10. // SetTransportChecksum recomputes the TCP or UDP checksum of an IPv4 or IPv6
  11. // packet in place.
  12. //
  13. // A kernel that offloads checksums to the NIC hands a packet to a tun with the
  14. // transport checksum unfinished: only the pseudo-header sum is in the field and
  15. // the rest is left for hardware that a tun does not have. A packet written
  16. // straight back to that tun is dropped on re-entry unless the checksum is
  17. // completed first. ICMP is left alone; it arrived complete on the kernels this
  18. // was measured against.
  19. //
  20. // So is any packet whose transport header cannot be located: fragments, unknown
  21. // extension headers and truncated packets. An IPv6 fragment header is declined
  22. // even when it carries the whole datagram (RFC 6946 atomic fragment), because
  23. // the walk reports only that a fragment header was present.
  24. func SetTransportChecksum(packet []byte) {
  25. if len(packet) < 1 {
  26. return
  27. }
  28. switch int(packet[0] >> 4) {
  29. case ipv4.Version:
  30. setTransportChecksum4(packet)
  31. case ipv6.Version:
  32. setTransportChecksum6(packet)
  33. }
  34. }
  35. func setTransportChecksum4(packet []byte) {
  36. if len(packet) < ipv4.HeaderLen {
  37. return
  38. }
  39. ihl := int(packet[0]&0x0f) << 2
  40. end := int(binary.BigEndian.Uint16(packet[2:4]))
  41. if ihl < ipv4.HeaderLen || end < ihl || end > len(packet) {
  42. return
  43. }
  44. // The checksum covers the whole datagram, which a fragment (MF set or a
  45. // non-zero offset) does not carry.
  46. if binary.BigEndian.Uint16(packet[6:8])&0x3fff != 0 {
  47. return
  48. }
  49. transport, ok := transportExtent(packet[ihl:end], packet[9])
  50. if !ok {
  51. return
  52. }
  53. csum := ipv4PseudoheaderChecksum(packet[12:16], packet[16:20], uint32(packet[9]), uint32(len(transport)))
  54. writeTransportChecksum(transport, layers.IPProtocol(packet[9]), csum)
  55. }
  56. func setTransportChecksum6(packet []byte) {
  57. if len(packet) < ipv6.HeaderLen {
  58. return
  59. }
  60. end := ipv6.HeaderLen + int(binary.BigEndian.Uint16(packet[4:6]))
  61. if end > len(packet) {
  62. return
  63. }
  64. // The checksum covers the whole datagram, which a fragment does not carry.
  65. // An unknown extension header hides where the transport header starts. A
  66. // chain longer than the walk's budget ends it early, at an offset that was
  67. // never checked against the packet.
  68. proto, offset, _, anyFragment, err := IPv6FindUpperProtocol(packet[:end])
  69. if err != nil || anyFragment || offset >= end {
  70. return
  71. }
  72. transport, ok := transportExtent(packet[offset:end], proto)
  73. if !ok {
  74. return
  75. }
  76. csum := ipv6PseudoheaderChecksum(packet[8:24], packet[24:40], uint32(proto), uint32(len(transport)))
  77. writeTransportChecksum(transport, layers.IPProtocol(proto), csum)
  78. }
  79. // transportExtent narrows a segment to the length its own header declares. UDP
  80. // carries a Length field, and RFC 768 and RFC 8200 section 8.1 both make that
  81. // field, not the IP payload extent, the length the pseudo-header counts and the
  82. // checksum covers; a datagram padded out to a link's minimum frame is the usual
  83. // way the two differ. TCP has no such field, so its segment runs to the end of
  84. // the IP payload. A Length that overruns the bytes IP delivered describes a
  85. // datagram that is not there.
  86. func transportExtent(transport []byte, proto uint8) ([]byte, bool) {
  87. if layers.IPProtocol(proto) != layers.IPProtocolUDP {
  88. return transport, true
  89. }
  90. if len(transport) < udpHeaderLen {
  91. return nil, false
  92. }
  93. ulen := int(binary.BigEndian.Uint16(transport[4:6]))
  94. if ulen < udpHeaderLen || ulen > len(transport) {
  95. return nil, false
  96. }
  97. return transport[:ulen], true
  98. }
  99. // writeTransportChecksum stores the checksum of transport, taken over the
  100. // pseudo-header sum csum, in the header's checksum field. A UDP checksum that
  101. // computes to zero goes on the wire as 0xffff: zero means no checksum was
  102. // computed (RFC 768), and over IPv6 the checksum is mandatory (RFC 8200
  103. // section 8.1).
  104. func writeTransportChecksum(transport []byte, proto layers.IPProtocol, csum uint32) {
  105. var at, minLen int
  106. switch proto {
  107. case layers.IPProtocolTCP:
  108. at, minLen = 16, 20
  109. case layers.IPProtocolUDP:
  110. at, minLen = 6, udpHeaderLen
  111. default:
  112. return
  113. }
  114. if len(transport) < minLen {
  115. return
  116. }
  117. transport[at], transport[at+1] = 0, 0
  118. sum := ^checksum.Checksum(transport, fold(csum))
  119. if sum == 0 && proto == layers.IPProtocolUDP {
  120. sum = 0xffff
  121. }
  122. binary.BigEndian.PutUint16(transport[at:], sum)
  123. }
  124. // fold reduces a pseudo-header sum to the 16 bit seed Checksum takes. Carrying
  125. // the high half back into the low half is what keeps the reduction lossless, so
  126. // the seed sums exactly as the wider value would; 0xffff is its fixed point.
  127. // Every term of that sum comes from a 16 bit field, so it stays far below the
  128. // width at which the accumulator would wrap.
  129. func fold(csum uint32) uint16 {
  130. for csum > 0xffff {
  131. csum = (csum >> 16) + (csum & 0xffff)
  132. }
  133. return uint16(csum)
  134. }