Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package checksum provides functions for updating checksums in parsed packets.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package packet contains packet parsing and marshaling utilities.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
@@ -45,7 +45,7 @@ type HeaderChecksummer interface {
|
||||
Header
|
||||
|
||||
// WriteCheck writes the correct checksum into buf, which should
|
||||
// be be the already-marshalled header and payload.
|
||||
// be the already-marshalled header and payload.
|
||||
WriteChecksum(buf []byte)
|
||||
}
|
||||
|
||||
|
||||
+63
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
crand "crypto/rand"
|
||||
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// ICMPEchoPayload generates a new random ID/Sequence pair, and returns a uint32
|
||||
@@ -26,3 +27,64 @@ func ICMPEchoPayload(payload []byte) (idSeq uint32, buf []byte) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// icmpDestUnreachableUnusedLen is the number of unused bytes that both ICMPv4
|
||||
// and ICMPv6 "Destination Unreachable" messages have between header and bits
|
||||
// from the invoking packet.
|
||||
const icmpDestUnreachableUnusedLen = 4
|
||||
const minIPv6MTU = 1280 // RFC 2460, section 5
|
||||
|
||||
// GenerateICMPHostUnreachable builds an ICMPv4 or ICMPv6 "Destination
|
||||
// Unreachable" message according to RFC 792 and RFC 4443, section 3.1.
|
||||
// from and to must both be of the same address family; otherwise
|
||||
// GenerateICMPHostUnreachable returns nil.
|
||||
func GenerateICMPHostUnreachable(from, to netip.Addr, invoking *Parsed) []byte {
|
||||
buf := invoking.Buffer()
|
||||
switch {
|
||||
case from.Is4() && to.Is4():
|
||||
// RFC 792
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type | Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | unused |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Internet Header + 64 bits of Original Data Datagram |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
ipHeaderLen := len(buf) - len(invoking.Transport())
|
||||
return Generate(ICMP4Header{
|
||||
IP4Header: IP4Header{Src: from, Dst: to},
|
||||
Type: ICMP4Unreachable,
|
||||
Code: ICMP4HostUnreachable,
|
||||
}, icmpDestUnreachablePayload(buf, ipHeaderLen+8))
|
||||
case from.Is6() && to.Is6():
|
||||
// RFC 4443, section 3.1
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type | Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Unused |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | As much of invoking packet |
|
||||
// + as possible without the ICMPv6 packet +
|
||||
// | exceeding the minimum IPv6 MTU |
|
||||
maxOrig := minIPv6MTU - (ICMP6Header{}).Len() - icmpDestUnreachableUnusedLen
|
||||
return Generate(ICMP6Header{
|
||||
IP6Header: IP6Header{Src: from, Dst: to},
|
||||
Type: ICMP6Unreachable,
|
||||
Code: ICMP6AddressUnreachable,
|
||||
}, icmpDestUnreachablePayload(buf, maxOrig))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// icmpDestUnreachablePayload composes the payload for an ICMP "Destination Unreachable" packet.
|
||||
func icmpDestUnreachablePayload(orig []byte, maxOrig int) []byte {
|
||||
n := min(len(orig), maxOrig)
|
||||
payload := make([]byte, icmpDestUnreachableUnusedLen+n)
|
||||
copy(payload[icmpDestUnreachableUnusedLen:], orig[:n])
|
||||
return payload
|
||||
}
|
||||
+5
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
@@ -49,6 +49,10 @@ type ICMP4Code uint8
|
||||
|
||||
const (
|
||||
ICMP4NoCode ICMP4Code = 0
|
||||
|
||||
// ICMP4HostUnreachable is the code used with ICMP4Unreachable to
|
||||
// indicate that the destination host could not be reached.
|
||||
ICMP4HostUnreachable ICMP4Code = 1
|
||||
)
|
||||
|
||||
// ICMP4Header is an IPv4+ICMPv4 header.
|
||||
|
||||
+6
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
@@ -52,6 +52,11 @@ type ICMP6Code uint8
|
||||
|
||||
const (
|
||||
ICMP6NoCode ICMP6Code = 0
|
||||
|
||||
// ICMP6AddressUnreachable is the code used with ICMP6Unreachable to
|
||||
// indicate that the destination address could not be reached. It is
|
||||
// the IPv6 equivalent of ICMP4HostUnreachable.
|
||||
ICMP6AddressUnreachable ICMP6Code = 3
|
||||
)
|
||||
|
||||
// ICMP6Header is an IPv4+ICMPv4 header.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
+89
-15
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
@@ -17,8 +17,23 @@ import (
|
||||
const unknown = ipproto.Unknown
|
||||
|
||||
// RFC1858: prevent overlapping fragment attacks.
|
||||
//
|
||||
// The bound is sized for IPv4 (max IPv4 header + basic TCP header) but is
|
||||
// intentionally reused by decode6 for IPv6 fragments. It is conservative for
|
||||
// IPv6, whose fragments carry no per-fragment IP header, so it only ever
|
||||
// rejects more later fragments as Unknown, never fewer.
|
||||
const minFragBlks = (60 + 20) / 8 // max IPv4 header + basic TCP header in fragment blocks (8 bytes each)
|
||||
|
||||
// ip6FragHeader is the IANA protocol number for the IPv6 Fragment extension
|
||||
// header ("IPv6-Frag"). It appears as the base header's Next Header value on a
|
||||
// fragmented packet; decode6 steps over it to reach the real sub-protocol.
|
||||
// This is distinct from ipproto.Fragment (0xFF), our internal sentinel for a
|
||||
// non-first fragment whose sub-protocol header is not present.
|
||||
const ip6FragHeader ipproto.Proto = 44
|
||||
|
||||
// ip6FragHeaderLength is the length of the IPv6 Fragment extension header.
|
||||
const ip6FragHeaderLength = 8
|
||||
|
||||
type TCPFlag uint8
|
||||
|
||||
const (
|
||||
@@ -134,12 +149,15 @@ func (q *Parsed) decode4(b []byte) {
|
||||
q.Src = withIP(q.Src, netaddr.IPv4(b[12], b[13], b[14], b[15]))
|
||||
q.Dst = withIP(q.Dst, netaddr.IPv4(b[16], b[17], b[18], b[19]))
|
||||
|
||||
q.subofs = int((b[0] & 0x0F) << 2)
|
||||
if q.subofs > q.length {
|
||||
// subofs is only committed to q after validation, so a rejected
|
||||
// packet can't leave it pointing past the end of the buffer.
|
||||
subofs := int((b[0] & 0x0F) << 2)
|
||||
if subofs > q.length {
|
||||
// next-proto starts beyond end of packet.
|
||||
q.IPProto = unknown
|
||||
return
|
||||
}
|
||||
q.subofs = subofs
|
||||
sub := b[q.subofs:]
|
||||
sub = sub[:len(sub):len(sub)] // help the compiler do bounds check elimination
|
||||
|
||||
@@ -272,19 +290,25 @@ func (q *Parsed) decode6(b []byte) {
|
||||
q.Src = withIP(q.Src, srcIP)
|
||||
q.Dst = withIP(q.Dst, dstIP)
|
||||
|
||||
// We don't support any IPv6 extension headers. Don't try to
|
||||
// be clever. Therefore, the IP subprotocol always starts at
|
||||
// byte 40.
|
||||
// The IP subprotocol normally begins right after the 40-byte IPv6
|
||||
// header. The one extension header we parse is the Fragment header
|
||||
// (Next Header 44): a host source-fragmenting a datagram larger than
|
||||
// the tun MTU emits these, and RFC 8200 section 4.5 requires the
|
||||
// receiver to reassemble them, so we must let them through. For the
|
||||
// first fragment we step over the fragment header and read the real
|
||||
// sub-protocol's ports exactly as decode4 does; later fragments are
|
||||
// marked ipproto.Fragment and passed through by the filter.
|
||||
//
|
||||
// Note that this means we don't support fragmentation in
|
||||
// IPv6. This is fine, because IPv6 strongly mandates that you
|
||||
// should not fragment, which makes fragmentation on the open
|
||||
// internet extremely uncommon.
|
||||
//
|
||||
// This also means we don't support IPSec headers (AH/ESP), or
|
||||
// IPv6 jumbo frames. Those will get marked Unknown and
|
||||
// dropped.
|
||||
// We still don't parse any other extension headers (hop-by-hop,
|
||||
// routing, destination options) or IPSec headers (AH/ESP), nor a
|
||||
// Fragment header that isn't the base header's immediate Next Header.
|
||||
// Those get marked Unknown and dropped.
|
||||
q.subofs = 40
|
||||
if q.IPProto == ip6FragHeader {
|
||||
if !q.decode6Fragment(b) {
|
||||
return
|
||||
}
|
||||
}
|
||||
sub := b[q.subofs:]
|
||||
sub = sub[:len(sub):len(sub)] // help the compiler do bounds check elimination
|
||||
|
||||
@@ -341,6 +365,49 @@ func (q *Parsed) decode6(b []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// decode6Fragment parses the IPv6 Fragment extension header at q.subofs in b
|
||||
// (q.subofs is the 40-byte base header length when called). It reports whether
|
||||
// decode6 should continue into the sub-protocol switch: true only for the
|
||||
// first fragment, where q.subofs and q.IPProto have been advanced to the real
|
||||
// transport header so its ports get parsed like an unfragmented packet. For
|
||||
// later or malformed fragments it sets q.IPProto itself (ipproto.Fragment to
|
||||
// pass through, or unknown to drop) and returns false.
|
||||
func (q *Parsed) decode6Fragment(b []byte) (continueDecode bool) {
|
||||
// The fragment header is 8 bytes: Next Header, Reserved, a 13-bit
|
||||
// Fragment Offset (in 8-byte blocks) plus a More-Fragments flag, and a
|
||||
// 32-bit Identification.
|
||||
if len(b) < q.subofs+ip6FragHeaderLength {
|
||||
q.IPProto = unknown
|
||||
return false
|
||||
}
|
||||
frag := b[q.subofs:]
|
||||
nextHdr := ipproto.Proto(frag[0])
|
||||
fragOfs := binary.BigEndian.Uint16(frag[2:4]) >> 3
|
||||
|
||||
// Step over the fragment header. The real sub-protocol (first fragment)
|
||||
// or the continued payload (later fragments) begins here.
|
||||
q.subofs += ip6FragHeaderLength
|
||||
|
||||
if fragOfs == 0 {
|
||||
// First fragment: decode the real sub-protocol's header so the
|
||||
// filter can match on its ports. The switch in decode6 performs
|
||||
// the per-protocol bounds checks, including rejecting a first
|
||||
// fragment too short to hold the transport header.
|
||||
q.IPProto = nextHdr
|
||||
return true
|
||||
}
|
||||
|
||||
// Later fragment: there's no sub-protocol header to read. Reject offsets
|
||||
// small enough to overlap the transport header (RFC 1858, same guard as
|
||||
// decode4); otherwise pass it through as a fragment.
|
||||
if fragOfs < minFragBlks {
|
||||
q.IPProto = unknown
|
||||
return false
|
||||
}
|
||||
q.IPProto = ipproto.Fragment
|
||||
return false
|
||||
}
|
||||
|
||||
func (q *Parsed) IP4Header() IP4Header {
|
||||
if q.IPVersion != 4 {
|
||||
panic("IP4Header called on non-IPv4 Parsed")
|
||||
@@ -401,7 +468,10 @@ func (q *Parsed) Buffer() []byte {
|
||||
// This is a read-only view; that is, q retains the ownership of the buffer.
|
||||
func (q *Parsed) Payload() []byte {
|
||||
// If the packet is truncated, return nothing instead of crashing.
|
||||
if q.length > len(q.b) || q.dataofs > len(q.b) {
|
||||
// dataofs can also land past length rather than past len(b): the IP
|
||||
// header can declare a total length that stops before the end of the
|
||||
// sub-protocol header, which would invert the slice bounds below.
|
||||
if q.length > len(q.b) || q.dataofs > len(q.b) || q.dataofs > q.length {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -411,6 +481,10 @@ func (q *Parsed) Payload() []byte {
|
||||
// Transport returns the transport header and payload (IP subprotocol, such as TCP or UDP).
|
||||
// This is a read-only view; that is, p retains the ownership of the buffer.
|
||||
func (p *Parsed) Transport() []byte {
|
||||
if p.subofs > len(p.b) {
|
||||
// defensive check, should not survive decode4 or decode6 (first line)
|
||||
return nil
|
||||
}
|
||||
return p.b[p.subofs:]
|
||||
}
|
||||
|
||||
|
||||
+22
-8
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// TSMP is our ICMP-like "Tailscale Message Protocol" for signaling
|
||||
@@ -29,7 +29,7 @@ const minTSMPSize = 7 // the rejected body is 7 bytes
|
||||
// On the wire, after the IP header, it's currently 7 or 8 bytes:
|
||||
// - '!'
|
||||
// - IPProto byte (IANA protocol number: TCP or UDP)
|
||||
// - 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp)
|
||||
// - byte stating rejection reason (see [TailscaleRejectReason] for valid values)
|
||||
// - srcPort big endian uint16
|
||||
// - dstPort big endian uint16
|
||||
// - [optional] byte of flag bits:
|
||||
@@ -63,19 +63,20 @@ func (rh TailscaleRejectedHeader) String() string {
|
||||
return fmt.Sprintf("TSMP-reject-flow{%s %s > %s}: %s", rh.Proto, rh.Src, rh.Dst, rh.Reason)
|
||||
}
|
||||
|
||||
// TSMPType is the type byte of a TSMP message. It follows the IPv4 or IPv6 header.
|
||||
type TSMPType uint8
|
||||
|
||||
const (
|
||||
// TSMPTypeRejectedConn is the type byte for a TailscaleRejectedHeader.
|
||||
// TSMPTypeRejectedConn is the type byte for a [TailscaleRejectedHeader].
|
||||
TSMPTypeRejectedConn TSMPType = '!'
|
||||
|
||||
// TSMPTypePing is the type byte for a TailscalePingRequest.
|
||||
// TSMPTypePing is the type byte for a [TSMPPingRequest].
|
||||
TSMPTypePing TSMPType = 'p'
|
||||
|
||||
// TSMPTypePong is the type byte for a TailscalePongResponse.
|
||||
// TSMPTypePong is the type byte for a [TSMPPongReply].
|
||||
TSMPTypePong TSMPType = 'o'
|
||||
|
||||
// TSPMTypeDiscoAdvertisement is the type byte for sending disco keys
|
||||
// TSMPTypeDiscoAdvertisement is the type byte for a [TSMPDiscoKeyAdvertisement].
|
||||
TSMPTypeDiscoAdvertisement TSMPType = 'a'
|
||||
)
|
||||
|
||||
@@ -101,6 +102,11 @@ const (
|
||||
// RejectedDueToHostFirewall means that the target host's
|
||||
// firewall is blocking the traffic.
|
||||
RejectedDueToHostFirewall TailscaleRejectReason = 'W'
|
||||
|
||||
// RejectedDueToUnknownAppConnectorTransitIP means that the connector host has no real IP
|
||||
// mapping that matches the provided transit IP for this client, so the
|
||||
// connector has no destination to forward the connection to.
|
||||
RejectedDueToUnknownAppConnectorTransitIP TailscaleRejectReason = 'T'
|
||||
)
|
||||
|
||||
func (r TailscaleRejectReason) String() string {
|
||||
@@ -113,6 +119,8 @@ func (r TailscaleRejectReason) String() string {
|
||||
return "host-ip-forwarding-unavailable"
|
||||
case RejectedDueToHostFirewall:
|
||||
return "host-firewall"
|
||||
case RejectedDueToUnknownAppConnectorTransitIP:
|
||||
return "app-connector-transit-ip-unknown"
|
||||
}
|
||||
return fmt.Sprintf("0x%02x", byte(r))
|
||||
}
|
||||
@@ -265,7 +273,9 @@ func (h TSMPPongReply) Marshal(buf []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TSMPDiscoKeyAdvertisement is a TSMP message that's used for distributing Disco Keys.
|
||||
// TSMPDiscoKeyAdvertisement is a TSMP message that's used for distributing
|
||||
// [key.DiscoPublic]. It is sent immediately following eligible WireGuard
|
||||
// session establishment starting at [tailscale.com/tailcfg.CapabilityVersion] 144.
|
||||
//
|
||||
// On the wire, after the IP header, it's currently 33 bytes:
|
||||
// - 'a' (TSMPTypeDiscoAdvertisement)
|
||||
@@ -275,6 +285,10 @@ type TSMPDiscoKeyAdvertisement struct {
|
||||
Key key.DiscoPublic
|
||||
}
|
||||
|
||||
// Marshal marshals ka to a wire representation appropriate for transmission
|
||||
// over WireGuard to a remote client. A marshaled [TSMPDiscoKeyAdvertisement]
|
||||
// must not exceed [github.com/tailscale/wireguard-go/device.MaxPriorityMessageContentSize],
|
||||
// or wireguard-go will reject and discard the payload.
|
||||
func (ka *TSMPDiscoKeyAdvertisement) Marshal() ([]byte, error) {
|
||||
var iph Header
|
||||
if ka.Src.Is4() {
|
||||
@@ -290,7 +304,7 @@ func (ka *TSMPDiscoKeyAdvertisement) Marshal() ([]byte, error) {
|
||||
Dst: ka.Dst,
|
||||
}
|
||||
}
|
||||
payload := make([]byte, 0, 33)
|
||||
payload := make([]byte, 0, 1+key.DiscoPublicRawLen)
|
||||
payload = append(payload, byte(TSMPTypeDiscoAdvertisement))
|
||||
payload = ka.Key.AppendTo(payload)
|
||||
if len(payload) != 33 {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
Reference in new issue
Block a user