checksum.go 4.7 KB

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