checksum.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. // do not attempt to set checksums for fragments
  44. if binary.BigEndian.Uint16(packet[6:8])&0x3fff != 0 {
  45. return
  46. }
  47. transport, ok := transportExtent(packet[ihl:end], packet[9])
  48. if !ok {
  49. return
  50. }
  51. csum := ipv4PseudoheaderChecksum(packet[12:16], packet[16:20], uint32(packet[9]), uint32(len(transport)))
  52. writeTransportChecksum(transport, packet[9], csum)
  53. }
  54. func setTransportChecksum6(packet []byte) {
  55. if len(packet) < ipv6.HeaderLen {
  56. return
  57. }
  58. end := ipv6.HeaderLen + int(binary.BigEndian.Uint16(packet[4:6]))
  59. if end > len(packet) {
  60. return
  61. }
  62. // we must find the transport header in order to write a checksum
  63. proto, offset, _, anyFragment, err := IPv6FindUpperProtocol(packet[:end])
  64. if err != nil || anyFragment || offset >= end {
  65. return
  66. }
  67. transport, ok := transportExtent(packet[offset:end], proto)
  68. if !ok {
  69. return
  70. }
  71. csum := ipv6PseudoheaderChecksum(packet[8:24], packet[24:40], uint32(proto), uint32(len(transport)))
  72. writeTransportChecksum(transport, proto, csum)
  73. }
  74. // transportExtent narrows a segment to the length its own header declares. UDP
  75. // carries a Length field, and RFC 768 and RFC 8200 section 8.1 both make that
  76. // field, not the IP payload extent, the length the pseudo-header counts and the
  77. // checksum covers; a datagram padded out to a link's minimum frame is the usual
  78. // way the two differ. TCP has no such field, so its segment runs to the end of
  79. // the IP payload. A Length that overruns the bytes IP delivered describes a
  80. // datagram that is not there.
  81. func transportExtent(transport []byte, proto uint8) ([]byte, bool) {
  82. if proto != IPProtocolUDP {
  83. return transport, true
  84. }
  85. if len(transport) < udpHeaderLen {
  86. return nil, false
  87. }
  88. ulen := int(binary.BigEndian.Uint16(transport[4:6]))
  89. if ulen < udpHeaderLen || ulen > len(transport) {
  90. return nil, false
  91. }
  92. return transport[:ulen], true
  93. }
  94. // writeTransportChecksum stores the checksum of transport, taken over the
  95. // pseudo-header sum csum, in the header's checksum field. A UDP checksum that
  96. // computes to zero goes on the wire as 0xffff: zero means no checksum was
  97. // computed (RFC 768), and over IPv6 the checksum is mandatory (RFC 8200 section 8.1).
  98. func writeTransportChecksum(transport []byte, proto uint8, csum uint32) {
  99. var at, minLen int
  100. switch proto {
  101. case IPProtocolTCP:
  102. at, minLen = 16, 20
  103. case IPProtocolUDP:
  104. at, minLen = 6, udpHeaderLen
  105. default:
  106. return
  107. }
  108. if len(transport) < minLen {
  109. return
  110. }
  111. transport[at], transport[at+1] = 0, 0
  112. sum := ^checksum.Checksum(transport, fold(csum))
  113. if sum == 0 && proto == IPProtocolUDP {
  114. sum = 0xffff
  115. }
  116. binary.BigEndian.PutUint16(transport[at:], sum)
  117. }
  118. func fold(csum uint32) uint16 {
  119. for csum > 0xffff {
  120. csum = (csum >> 16) + (csum & 0xffff)
  121. }
  122. return uint16(csum)
  123. }