| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- package iputil
- import (
- "encoding/binary"
- "github.com/slackhq/nebula/overlay/checksum"
- "golang.org/x/net/ipv4"
- "golang.org/x/net/ipv6"
- )
- const udpHeaderLen = 8
- // SetTransportChecksum recomputes the TCP or UDP checksum of an IPv4 or IPv6
- // packet in place.
- //
- // A kernel that offloads checksums to the NIC hands a packet to a tun with the
- // transport checksum unfinished: only the pseudo-header sum is in the field and
- // the rest is left for hardware that a tun does not have. A packet written
- // straight back to that tun is dropped on re-entry unless the checksum is
- // completed first. ICMP is left alone; it arrived complete on the kernels this
- // was measured against.
- //
- // So is any packet whose transport header cannot be located: fragments, unknown
- // extension headers and truncated packets. An IPv6 fragment header is declined
- // even when it carries the whole datagram (RFC 6946 atomic fragment), because
- // the walk reports only that a fragment header was present.
- func SetTransportChecksum(packet []byte) {
- if len(packet) < 1 {
- return
- }
- switch int(packet[0] >> 4) {
- case ipv4.Version:
- setTransportChecksum4(packet)
- case ipv6.Version:
- setTransportChecksum6(packet)
- }
- }
- func setTransportChecksum4(packet []byte) {
- if len(packet) < ipv4.HeaderLen {
- return
- }
- ihl := int(packet[0]&0x0f) << 2
- end := int(binary.BigEndian.Uint16(packet[2:4]))
- if ihl < ipv4.HeaderLen || end < ihl || end > len(packet) {
- return
- }
- // The checksum covers the whole datagram, which a fragment (MF set or a
- // non-zero offset) does not carry.
- if binary.BigEndian.Uint16(packet[6:8])&0x3fff != 0 {
- return
- }
- transport, ok := transportExtent(packet[ihl:end], packet[9])
- if !ok {
- return
- }
- csum := ipv4PseudoheaderChecksum(packet[12:16], packet[16:20], uint32(packet[9]), uint32(len(transport)))
- writeTransportChecksum(transport, packet[9], csum)
- }
- func setTransportChecksum6(packet []byte) {
- if len(packet) < ipv6.HeaderLen {
- return
- }
- end := ipv6.HeaderLen + int(binary.BigEndian.Uint16(packet[4:6]))
- if end > len(packet) {
- return
- }
- // The checksum covers the whole datagram, which a fragment does not carry.
- // An unknown extension header hides where the transport header starts. A
- // chain longer than the walk's budget ends it early, at an offset that was
- // never checked against the packet.
- proto, offset, _, anyFragment, err := IPv6FindUpperProtocol(packet[:end])
- if err != nil || anyFragment || offset >= end {
- return
- }
- transport, ok := transportExtent(packet[offset:end], proto)
- if !ok {
- return
- }
- csum := ipv6PseudoheaderChecksum(packet[8:24], packet[24:40], uint32(proto), uint32(len(transport)))
- writeTransportChecksum(transport, proto, csum)
- }
- // transportExtent narrows a segment to the length its own header declares. UDP
- // carries a Length field, and RFC 768 and RFC 8200 section 8.1 both make that
- // field, not the IP payload extent, the length the pseudo-header counts and the
- // checksum covers; a datagram padded out to a link's minimum frame is the usual
- // way the two differ. TCP has no such field, so its segment runs to the end of
- // the IP payload. A Length that overruns the bytes IP delivered describes a
- // datagram that is not there.
- func transportExtent(transport []byte, proto uint8) ([]byte, bool) {
- if proto != IPProtocolUDP {
- return transport, true
- }
- if len(transport) < udpHeaderLen {
- return nil, false
- }
- ulen := int(binary.BigEndian.Uint16(transport[4:6]))
- if ulen < udpHeaderLen || ulen > len(transport) {
- return nil, false
- }
- return transport[:ulen], true
- }
- // writeTransportChecksum stores the checksum of transport, taken over the
- // pseudo-header sum csum, in the header's checksum field. A UDP checksum that
- // computes to zero goes on the wire as 0xffff: zero means no checksum was
- // computed (RFC 768), and over IPv6 the checksum is mandatory (RFC 8200
- // section 8.1).
- func writeTransportChecksum(transport []byte, proto uint8, csum uint32) {
- var at, minLen int
- switch proto {
- case IPProtocolTCP:
- at, minLen = 16, 20
- case IPProtocolUDP:
- at, minLen = 6, udpHeaderLen
- default:
- return
- }
- if len(transport) < minLen {
- return
- }
- transport[at], transport[at+1] = 0, 0
- sum := ^checksum.Checksum(transport, fold(csum))
- if sum == 0 && proto == IPProtocolUDP {
- sum = 0xffff
- }
- binary.BigEndian.PutUint16(transport[at:], sum)
- }
- // fold reduces a pseudo-header sum to the 16 bit seed Checksum takes. Carrying
- // the high half back into the low half is what keeps the reduction lossless, so
- // the seed sums exactly as the wider value would; 0xffff is its fixed point.
- // Every term of that sum comes from a 16 bit field, so it stays far below the
- // width at which the accumulator would wrap.
- func fold(csum uint32) uint16 {
- for csum > 0xffff {
- csum = (csum >> 16) + (csum & 0xffff)
- }
- return uint16(csum)
- }
|