mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-31 00:03:47 +00:00
lanscaping: remove TSMP
-rwxr-xr-x@ 1 bradfitz staff 9752834 Jan 12 16:59 /Users/bradfitz/bin/tailscaled.min -rwxr-xr-x@ 1 bradfitz staff 9633944 Jan 12 16:59 /Users/bradfitz/bin/tailscaled.minlinux Change-Id: I12db5d0f2b90aae55709eed4751cc342d59b43cd Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
31a2724245
commit
bc78993f8d
@ -51,7 +51,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/ipn from tailscale.com/client/tailscale+
|
tailscale.com/ipn from tailscale.com/client/tailscale+
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
||||||
tailscale.com/licenses from tailscale.com/client/web+
|
tailscale.com/licenses from tailscale.com/client/web+
|
||||||
tailscale.com/net/flowtrack from tailscale.com/net/packet
|
|
||||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
||||||
|
@ -44,7 +44,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/ipn/store from tailscale.com/cmd/tailscaled
|
tailscale.com/ipn/store from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/ipn/store/mem from tailscale.com/ipn/store
|
tailscale.com/ipn/store/mem from tailscale.com/ipn/store
|
||||||
tailscale.com/logtail/backoff from tailscale.com/control/controlclient+
|
tailscale.com/logtail/backoff from tailscale.com/control/controlclient+
|
||||||
tailscale.com/net/flowtrack from tailscale.com/net/packet+
|
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter
|
||||||
tailscale.com/net/ipset from tailscale.com/ipn/ipnlocal+
|
tailscale.com/net/ipset from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
|
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
|
||||||
|
@ -218,10 +218,6 @@ func (q *Parsed) decode4(b []byte) {
|
|||||||
q.Src = withPort(q.Src, binary.BigEndian.Uint16(sub[0:2]))
|
q.Src = withPort(q.Src, binary.BigEndian.Uint16(sub[0:2]))
|
||||||
q.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
|
q.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
|
||||||
return
|
return
|
||||||
case ipproto.TSMP:
|
|
||||||
// Inter-tailscale messages.
|
|
||||||
q.dataofs = q.subofs
|
|
||||||
return
|
|
||||||
case ipproto.Fragment:
|
case ipproto.Fragment:
|
||||||
// An IPProto value of 0xff (our Fragment constant for internal use)
|
// An IPProto value of 0xff (our Fragment constant for internal use)
|
||||||
// should never actually be used in the wild; if we see it,
|
// should never actually be used in the wild; if we see it,
|
||||||
@ -321,10 +317,6 @@ func (q *Parsed) decode6(b []byte) {
|
|||||||
q.Src = withPort(q.Src, binary.BigEndian.Uint16(sub[0:2]))
|
q.Src = withPort(q.Src, binary.BigEndian.Uint16(sub[0:2]))
|
||||||
q.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
|
q.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
|
||||||
return
|
return
|
||||||
case ipproto.TSMP:
|
|
||||||
// Inter-tailscale messages.
|
|
||||||
q.dataofs = q.subofs
|
|
||||||
return
|
|
||||||
case ipproto.Fragment:
|
case ipproto.Fragment:
|
||||||
// An IPProto value of 0xff (our Fragment constant for internal use)
|
// An IPProto value of 0xff (our Fragment constant for internal use)
|
||||||
// should never actually be used in the wild; if we see it,
|
// should never actually be used in the wild; if we see it,
|
||||||
|
@ -1,264 +0,0 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
// TSMP is our ICMP-like "Tailscale Message Protocol" for signaling
|
|
||||||
// Tailscale-specific messages between nodes. It uses IP protocol 99
|
|
||||||
// (reserved for "any private encryption scheme") within
|
|
||||||
// WireGuard's normal encryption between peers and never hits the host
|
|
||||||
// network stack.
|
|
||||||
|
|
||||||
package packet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"tailscale.com/net/flowtrack"
|
|
||||||
"tailscale.com/types/ipproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TailscaleRejectedHeader is a TSMP message that says that one
|
|
||||||
// Tailscale node has rejected the connection from another. Unlike a
|
|
||||||
// TCP RST, this includes a reason.
|
|
||||||
//
|
|
||||||
// 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)
|
|
||||||
// - srcPort big endian uint16
|
|
||||||
// - dstPort big endian uint16
|
|
||||||
// - [optional] byte of flag bits:
|
|
||||||
// lowest bit (0x1): MaybeBroken
|
|
||||||
//
|
|
||||||
// In the future it might also accept 16 byte IP flow src/dst IPs
|
|
||||||
// after the header, if they're different than the IP-level ones.
|
|
||||||
type TailscaleRejectedHeader struct {
|
|
||||||
IPSrc netip.Addr // IPv4 or IPv6 header's src IP
|
|
||||||
IPDst netip.Addr // IPv4 or IPv6 header's dst IP
|
|
||||||
Src netip.AddrPort // rejected flow's src
|
|
||||||
Dst netip.AddrPort // rejected flow's dst
|
|
||||||
Proto ipproto.Proto // proto that was rejected (TCP or UDP)
|
|
||||||
Reason TailscaleRejectReason // why the connection was rejected
|
|
||||||
|
|
||||||
// MaybeBroken is whether the rejection is non-terminal (the
|
|
||||||
// client should not fail immediately). This is sent by a
|
|
||||||
// target when it's not sure whether it's totally broken, but
|
|
||||||
// it might be. For example, the target tailscaled might think
|
|
||||||
// its host firewall or IP forwarding aren't configured
|
|
||||||
// properly, but tailscaled might be wrong (not having enough
|
|
||||||
// visibility into what the OS is doing). When true, the
|
|
||||||
// message is simply an FYI as a potential reason to use for
|
|
||||||
// later when the pendopen connection tracking timer expires.
|
|
||||||
MaybeBroken bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const rejectFlagBitMaybeBroken = 0x1
|
|
||||||
|
|
||||||
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
|
|
||||||
return flowtrack.MakeTuple(rh.Proto, rh.Src, rh.Dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rh TailscaleRejectedHeader) String() string {
|
|
||||||
return fmt.Sprintf("TSMP-reject-flow{%s %s > %s}: %s", rh.Proto, rh.Src, rh.Dst, rh.Reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TSMPType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// TSMPTypeRejectedConn is the type byte for a TailscaleRejectedHeader.
|
|
||||||
TSMPTypeRejectedConn TSMPType = '!'
|
|
||||||
|
|
||||||
// TSMPTypePing is the type byte for a TailscalePingRequest.
|
|
||||||
TSMPTypePing TSMPType = 'p'
|
|
||||||
|
|
||||||
// TSMPTypePong is the type byte for a TailscalePongResponse.
|
|
||||||
TSMPTypePong TSMPType = 'o'
|
|
||||||
)
|
|
||||||
|
|
||||||
type TailscaleRejectReason byte
|
|
||||||
|
|
||||||
// IsZero reports whether r is the zero value, representing no rejection.
|
|
||||||
func (r TailscaleRejectReason) IsZero() bool { return r == TailscaleRejectReasonNone }
|
|
||||||
|
|
||||||
const (
|
|
||||||
// TailscaleRejectReasonNone is the TailscaleRejectReason zero value.
|
|
||||||
TailscaleRejectReasonNone TailscaleRejectReason = 0
|
|
||||||
|
|
||||||
// RejectedDueToACLs means that the host rejected the connection due to ACLs.
|
|
||||||
RejectedDueToACLs TailscaleRejectReason = 'A'
|
|
||||||
|
|
||||||
// RejectedDueToShieldsUp means that the host rejected the connection due to shields being up.
|
|
||||||
RejectedDueToShieldsUp TailscaleRejectReason = 'S'
|
|
||||||
|
|
||||||
// RejectedDueToIPForwarding means that the relay node's IP
|
|
||||||
// forwarding is disabled.
|
|
||||||
RejectedDueToIPForwarding TailscaleRejectReason = 'F'
|
|
||||||
|
|
||||||
// RejectedDueToHostFirewall means that the target host's
|
|
||||||
// firewall is blocking the traffic.
|
|
||||||
RejectedDueToHostFirewall TailscaleRejectReason = 'W'
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r TailscaleRejectReason) String() string {
|
|
||||||
switch r {
|
|
||||||
case RejectedDueToACLs:
|
|
||||||
return "acl"
|
|
||||||
case RejectedDueToShieldsUp:
|
|
||||||
return "shields"
|
|
||||||
case RejectedDueToIPForwarding:
|
|
||||||
return "host-ip-forwarding-unavailable"
|
|
||||||
case RejectedDueToHostFirewall:
|
|
||||||
return "host-firewall"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("0x%02x", byte(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h TailscaleRejectedHeader) hasFlags() bool {
|
|
||||||
return h.MaybeBroken // the only one currently
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h TailscaleRejectedHeader) Len() int {
|
|
||||||
v := 1 + // TSMPType byte
|
|
||||||
1 + // IPProto byte
|
|
||||||
1 + // TailscaleRejectReason byte
|
|
||||||
2*2 // 2 uint16 ports
|
|
||||||
if h.IPSrc.Is4() {
|
|
||||||
v += ip4HeaderLength
|
|
||||||
} else if h.IPSrc.Is6() {
|
|
||||||
v += ip6HeaderLength
|
|
||||||
}
|
|
||||||
if h.hasFlags() {
|
|
||||||
v++
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
|
|
||||||
if len(buf) < h.Len() {
|
|
||||||
return errSmallBuffer
|
|
||||||
}
|
|
||||||
if len(buf) > maxPacketLength {
|
|
||||||
return errLargePacket
|
|
||||||
}
|
|
||||||
if h.Src.Addr().Is4() {
|
|
||||||
iph := IP4Header{
|
|
||||||
IPProto: ipproto.TSMP,
|
|
||||||
Src: h.IPSrc,
|
|
||||||
Dst: h.IPDst,
|
|
||||||
}
|
|
||||||
iph.Marshal(buf)
|
|
||||||
buf = buf[ip4HeaderLength:]
|
|
||||||
} else if h.Src.Addr().Is6() {
|
|
||||||
iph := IP6Header{
|
|
||||||
IPProto: ipproto.TSMP,
|
|
||||||
Src: h.IPSrc,
|
|
||||||
Dst: h.IPDst,
|
|
||||||
}
|
|
||||||
iph.Marshal(buf)
|
|
||||||
buf = buf[ip6HeaderLength:]
|
|
||||||
} else {
|
|
||||||
return errors.New("bogus src IP")
|
|
||||||
}
|
|
||||||
buf[0] = byte(TSMPTypeRejectedConn)
|
|
||||||
buf[1] = byte(h.Proto)
|
|
||||||
buf[2] = byte(h.Reason)
|
|
||||||
binary.BigEndian.PutUint16(buf[3:5], h.Src.Port())
|
|
||||||
binary.BigEndian.PutUint16(buf[5:7], h.Dst.Port())
|
|
||||||
|
|
||||||
if h.hasFlags() {
|
|
||||||
var flags byte
|
|
||||||
if h.MaybeBroken {
|
|
||||||
flags |= rejectFlagBitMaybeBroken
|
|
||||||
}
|
|
||||||
buf[7] = flags
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsTailscaleRejectedHeader parses pp as an incoming rejection
|
|
||||||
// connection TSMP message.
|
|
||||||
//
|
|
||||||
// ok reports whether pp was a valid TSMP rejection packet.
|
|
||||||
func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok bool) {
|
|
||||||
p := pp.Payload()
|
|
||||||
if len(p) < 7 || p[0] != byte(TSMPTypeRejectedConn) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h = TailscaleRejectedHeader{
|
|
||||||
Proto: ipproto.Proto(p[1]),
|
|
||||||
Reason: TailscaleRejectReason(p[2]),
|
|
||||||
IPSrc: pp.Src.Addr(),
|
|
||||||
IPDst: pp.Dst.Addr(),
|
|
||||||
Src: netip.AddrPortFrom(pp.Dst.Addr(), binary.BigEndian.Uint16(p[3:5])),
|
|
||||||
Dst: netip.AddrPortFrom(pp.Src.Addr(), binary.BigEndian.Uint16(p[5:7])),
|
|
||||||
}
|
|
||||||
if len(p) > 7 {
|
|
||||||
flags := p[7]
|
|
||||||
h.MaybeBroken = (flags & rejectFlagBitMaybeBroken) != 0
|
|
||||||
}
|
|
||||||
return h, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TSMPPingRequest is a TSMP message that's like an ICMP ping request.
|
|
||||||
//
|
|
||||||
// On the wire, after the IP header, it's currently 9 bytes:
|
|
||||||
// - 'p' (TSMPTypePing)
|
|
||||||
// - 8 opaque ping bytes to copy back in the response
|
|
||||||
type TSMPPingRequest struct {
|
|
||||||
Data [8]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *Parsed) AsTSMPPing() (h TSMPPingRequest, ok bool) {
|
|
||||||
if pp.IPProto != ipproto.TSMP {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p := pp.Payload()
|
|
||||||
if len(p) < 9 || p[0] != byte(TSMPTypePing) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
copy(h.Data[:], p[1:])
|
|
||||||
return h, true
|
|
||||||
}
|
|
||||||
|
|
||||||
type TSMPPongReply struct {
|
|
||||||
IPHeader Header
|
|
||||||
Data [8]byte
|
|
||||||
PeerAPIPort uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsTSMPPong returns pp as a TSMPPongReply and whether it is one.
|
|
||||||
// The pong.IPHeader field is not populated.
|
|
||||||
func (pp *Parsed) AsTSMPPong() (pong TSMPPongReply, ok bool) {
|
|
||||||
if pp.IPProto != ipproto.TSMP {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p := pp.Payload()
|
|
||||||
if len(p) < 9 || p[0] != byte(TSMPTypePong) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
copy(pong.Data[:], p[1:])
|
|
||||||
if len(p) >= 11 {
|
|
||||||
pong.PeerAPIPort = binary.BigEndian.Uint16(p[9:])
|
|
||||||
}
|
|
||||||
return pong, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h TSMPPongReply) Len() int {
|
|
||||||
return h.IPHeader.Len() + 11
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h TSMPPongReply) Marshal(buf []byte) error {
|
|
||||||
if len(buf) < h.Len() {
|
|
||||||
return errSmallBuffer
|
|
||||||
}
|
|
||||||
if err := h.IPHeader.Marshal(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf = buf[h.IPHeader.Len():]
|
|
||||||
buf[0] = byte(TSMPTypePong)
|
|
||||||
copy(buf[1:], h.Data[:])
|
|
||||||
binary.BigEndian.PutUint16(buf[9:11], h.PeerAPIPort)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -164,9 +164,6 @@ type Wrapper struct {
|
|||||||
// PostFilterPacketOutboundToWireGuard is the outbound filter function that runs after the main filter.
|
// PostFilterPacketOutboundToWireGuard is the outbound filter function that runs after the main filter.
|
||||||
PostFilterPacketOutboundToWireGuard FilterFunc
|
PostFilterPacketOutboundToWireGuard FilterFunc
|
||||||
|
|
||||||
// OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives.
|
|
||||||
OnTSMPPongReceived func(packet.TSMPPongReply)
|
|
||||||
|
|
||||||
// OnICMPEchoResponseReceived, if non-nil, is called whenever a ICMP echo response
|
// OnICMPEchoResponseReceived, if non-nil, is called whenever a ICMP echo response
|
||||||
// arrives. If the packet is to be handled internally this returns true,
|
// arrives. If the packet is to be handled internally this returns true,
|
||||||
// false otherwise.
|
// false otherwise.
|
||||||
@ -916,29 +913,6 @@ func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) {
|
|
||||||
pong := packet.TSMPPongReply{
|
|
||||||
Data: req.Data,
|
|
||||||
}
|
|
||||||
if t.PeerAPIPort != nil {
|
|
||||||
pong.PeerAPIPort, _ = t.PeerAPIPort(pp.Dst.Addr())
|
|
||||||
}
|
|
||||||
switch pp.IPVersion {
|
|
||||||
case 4:
|
|
||||||
h4 := pp.IP4Header()
|
|
||||||
h4.ToResponse()
|
|
||||||
pong.IPHeader = h4
|
|
||||||
case 6:
|
|
||||||
h6 := pp.IP6Header()
|
|
||||||
h6.ToResponse()
|
|
||||||
pong.IPHeader = h6
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.InjectOutbound(packet.Generate(pong, nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// InjectOutbound makes the Wrapper device behave as if a packet
|
// InjectOutbound makes the Wrapper device behave as if a packet
|
||||||
// with the given contents was sent to the network.
|
// with the given contents was sent to the network.
|
||||||
// It does not block, but takes ownership of the packet.
|
// It does not block, but takes ownership of the packet.
|
||||||
|
@ -54,17 +54,6 @@ const (
|
|||||||
GRE Proto = 0x2f
|
GRE Proto = 0x2f
|
||||||
SCTP Proto = 0x84
|
SCTP Proto = 0x84
|
||||||
|
|
||||||
// TSMP is the Tailscale Message Protocol (our ICMP-ish
|
|
||||||
// thing), an IP protocol used only between Tailscale nodes
|
|
||||||
// (still encrypted by WireGuard) that communicates why things
|
|
||||||
// failed, etc.
|
|
||||||
//
|
|
||||||
// Proto number 99 is reserved for "any private encryption
|
|
||||||
// scheme". We never accept these from the host OS stack nor
|
|
||||||
// send them to the host network stack. It's only used between
|
|
||||||
// nodes.
|
|
||||||
TSMP Proto = 99
|
|
||||||
|
|
||||||
// Fragment represents any non-first IP fragment, for which we
|
// Fragment represents any non-first IP fragment, for which we
|
||||||
// don't have the sub-protocol header (and therefore can't
|
// don't have the sub-protocol header (and therefore can't
|
||||||
// figure out what the sub-protocol is).
|
// figure out what the sub-protocol is).
|
||||||
@ -93,8 +82,6 @@ func (p Proto) String() string {
|
|||||||
return "TCP"
|
return "TCP"
|
||||||
case SCTP:
|
case SCTP:
|
||||||
return "SCTP"
|
return "SCTP"
|
||||||
case TSMP:
|
|
||||||
return "TSMP"
|
|
||||||
case GRE:
|
case GRE:
|
||||||
return "GRE"
|
return "GRE"
|
||||||
case DCCP:
|
case DCCP:
|
||||||
@ -144,7 +131,6 @@ var (
|
|||||||
"ipv6-icmp": ICMPv6,
|
"ipv6-icmp": ICMPv6,
|
||||||
"sctp": SCTP,
|
"sctp": SCTP,
|
||||||
"tcp": TCP,
|
"tcp": TCP,
|
||||||
"tsmp": TSMP,
|
|
||||||
"udp": UDP,
|
"udp": UDP,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -503,8 +503,6 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
|
|||||||
if f.matches4.match(q, f.srcIPHasCap) {
|
if f.matches4.match(q, f.srcIPHasCap) {
|
||||||
return Accept, "ok"
|
return Accept, "ok"
|
||||||
}
|
}
|
||||||
case ipproto.TSMP:
|
|
||||||
return Accept, "tsmp ok"
|
|
||||||
default:
|
default:
|
||||||
if f.matches4.matchProtoAndIPsOnlyIfAllPorts(q) {
|
if f.matches4.matchProtoAndIPsOnlyIfAllPorts(q) {
|
||||||
return Accept, "other-portless ok"
|
return Accept, "other-portless ok"
|
||||||
@ -563,8 +561,6 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
|
|||||||
if f.matches6.match(q, f.srcIPHasCap) {
|
if f.matches6.match(q, f.srcIPHasCap) {
|
||||||
return Accept, "ok"
|
return Accept, "ok"
|
||||||
}
|
}
|
||||||
case ipproto.TSMP:
|
|
||||||
return Accept, "tsmp ok"
|
|
||||||
default:
|
default:
|
||||||
if f.matches6.matchProtoAndIPsOnlyIfAllPorts(q) {
|
if f.matches6.matchProtoAndIPsOnlyIfAllPorts(q) {
|
||||||
return Accept, "other-portless ok"
|
return Accept, "other-portless ok"
|
||||||
|
@ -1,228 +0,0 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
package wgengine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tailscale.com/net/flowtrack"
|
|
||||||
"tailscale.com/net/packet"
|
|
||||||
"tailscale.com/net/tstun"
|
|
||||||
"tailscale.com/types/ipproto"
|
|
||||||
"tailscale.com/util/mak"
|
|
||||||
"tailscale.com/wgengine/filter"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tcpTimeoutBeforeDebug = 5 * time.Second
|
|
||||||
|
|
||||||
type pendingOpenFlow struct {
|
|
||||||
timer *time.Timer // until giving up on the flow
|
|
||||||
|
|
||||||
// guarded by userspaceEngine.mu:
|
|
||||||
|
|
||||||
// problem is non-zero if we got a MaybeBroken (non-terminal)
|
|
||||||
// TSMP "reject" header.
|
|
||||||
problem packet.TailscaleRejectReason
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) {
|
|
||||||
e.mu.Lock()
|
|
||||||
defer e.mu.Unlock()
|
|
||||||
of, ok := e.pendOpen[f]
|
|
||||||
if !ok {
|
|
||||||
// Not a tracked flow (likely already removed)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
of.timer.Stop()
|
|
||||||
delete(e.pendOpen, f)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *userspaceEngine) noteFlowProblemFromPeer(f flowtrack.Tuple, problem packet.TailscaleRejectReason) {
|
|
||||||
e.mu.Lock()
|
|
||||||
defer e.mu.Unlock()
|
|
||||||
of, ok := e.pendOpen[f]
|
|
||||||
if !ok {
|
|
||||||
// Not a tracked flow (likely already removed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
of.problem = problem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.Wrapper) (res filter.Response) {
|
|
||||||
res = filter.Accept // always
|
|
||||||
|
|
||||||
if pp.IPProto == ipproto.TSMP {
|
|
||||||
res = filter.DropSilently
|
|
||||||
rh, ok := pp.AsTailscaleRejectedHeader()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if rh.MaybeBroken {
|
|
||||||
e.noteFlowProblemFromPeer(rh.Flow(), rh.Reason)
|
|
||||||
} else if f := rh.Flow(); e.removeFlow(f) {
|
|
||||||
e.logf("open-conn-track: flow %v %v > %v rejected due to %v", rh.Proto, rh.Src, rh.Dst, rh.Reason)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if pp.IPVersion == 0 ||
|
|
||||||
pp.IPProto != ipproto.TCP ||
|
|
||||||
pp.TCPFlags&(packet.TCPSyn|packet.TCPRst) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Either a SYN or a RST came back. Remove it in either case.
|
|
||||||
|
|
||||||
f := flowtrack.MakeTuple(pp.IPProto, pp.Dst, pp.Src) // src/dst reversed
|
|
||||||
removed := e.removeFlow(f)
|
|
||||||
if removed && pp.TCPFlags&packet.TCPRst != 0 {
|
|
||||||
e.logf("open-conn-track: flow TCP %v got RST by peer", f)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// isOSNetworkProbe reports whether the target is likely a network
|
|
||||||
// connectivity probe target from e.g. iOS or Ubuntu network-manager.
|
|
||||||
//
|
|
||||||
// iOS likes to probe Apple IPs on all interfaces to check for connectivity.
|
|
||||||
// Don't start timers tracking those. They won't succeed anyway. Avoids log
|
|
||||||
// spam like:
|
|
||||||
func (e *userspaceEngine) isOSNetworkProbe(dst netip.AddrPort) bool {
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.Wrapper) (res filter.Response) {
|
|
||||||
res = filter.Accept // always
|
|
||||||
|
|
||||||
if pp.IPVersion == 0 ||
|
|
||||||
pp.IPProto != ipproto.TCP ||
|
|
||||||
pp.TCPFlags&packet.TCPAck != 0 ||
|
|
||||||
pp.TCPFlags&packet.TCPSyn == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.isOSNetworkProbe(pp.Dst) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
flow := flowtrack.MakeTuple(pp.IPProto, pp.Src, pp.Dst)
|
|
||||||
|
|
||||||
e.mu.Lock()
|
|
||||||
defer e.mu.Unlock()
|
|
||||||
if _, dup := e.pendOpen[flow]; dup {
|
|
||||||
// Duplicates are expected when the OS retransmits. Ignore.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
timer := time.AfterFunc(tcpTimeoutBeforeDebug, func() {
|
|
||||||
e.onOpenTimeout(flow)
|
|
||||||
})
|
|
||||||
mak.Set(&e.pendOpen, flow, &pendingOpenFlow{timer: timer})
|
|
||||||
|
|
||||||
return filter.Accept
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
|
|
||||||
e.mu.Lock()
|
|
||||||
of, ok := e.pendOpen[flow]
|
|
||||||
if !ok {
|
|
||||||
// Not a tracked flow, or already handled & deleted.
|
|
||||||
e.mu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete(e.pendOpen, flow)
|
|
||||||
problem := of.problem
|
|
||||||
e.mu.Unlock()
|
|
||||||
|
|
||||||
if !problem.IsZero() {
|
|
||||||
e.logf("open-conn-track: timeout opening %v; peer reported problem: %v", flow, problem)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diagnose why it might've timed out.
|
|
||||||
pip, ok := e.PeerForIP(flow.DstAddr())
|
|
||||||
if !ok {
|
|
||||||
e.logf("open-conn-track: timeout opening %v; no associated peer node", flow)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n := pip.Node
|
|
||||||
if !n.IsWireGuardOnly() {
|
|
||||||
if n.DiscoKey().IsZero() {
|
|
||||||
e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key().ShortString())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if n.DERP() == "" {
|
|
||||||
e.logf("open-conn-track: timeout opening %v; peer node %v not connected to any DERP relay", flow, n.Key().ShortString())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ps, found := e.getPeerStatusLite(n.Key())
|
|
||||||
if !found {
|
|
||||||
onlyZeroRoute := true // whether peerForIP returned n only because its /0 route matched
|
|
||||||
for _, r := range n.AllowedIPs().All() {
|
|
||||||
if r.Bits() != 0 && r.Contains(flow.DstAddr()) {
|
|
||||||
onlyZeroRoute = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if onlyZeroRoute {
|
|
||||||
// This node was returned by peerForIP because
|
|
||||||
// its exit node /0 route(s) matched, but this
|
|
||||||
// might not be the exit node that's currently
|
|
||||||
// selected. Rather than log misleading
|
|
||||||
// errors, just don't log at all for now.
|
|
||||||
// TODO(bradfitz): update this code to be
|
|
||||||
// exit-node-aware and make peerForIP return
|
|
||||||
// the node of the currently selected exit
|
|
||||||
// node.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.logf("open-conn-track: timeout opening %v; target node %v in netmap but unknown to WireGuard", flow, n.Key().ShortString())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bradfitz): figure out what PeerStatus.LastHandshake
|
|
||||||
// is. It appears to be the last time we sent a handshake,
|
|
||||||
// which isn't what I expected. I thought it was when a
|
|
||||||
// handshake completed, which is what I want.
|
|
||||||
_ = ps.LastHandshake
|
|
||||||
|
|
||||||
online := "?"
|
|
||||||
if n.IsWireGuardOnly() {
|
|
||||||
online = "wg"
|
|
||||||
} else {
|
|
||||||
if v := n.Online(); v != nil {
|
|
||||||
if *v {
|
|
||||||
online = "yes"
|
|
||||||
} else {
|
|
||||||
online = "no"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n.LastSeen() != nil && online != "yes" {
|
|
||||||
online += fmt.Sprintf(", lastseen=%v", durFmt(*n.LastSeen()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.logf("open-conn-track: timeout opening %v to node %v; online=%v, lastRecv=%v",
|
|
||||||
flow, n.Key().ShortString(),
|
|
||||||
online,
|
|
||||||
e.magicConn.LastRecvActivityOfNodeKey(n.Key()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func durFmt(t time.Time) string {
|
|
||||||
if t.IsZero() {
|
|
||||||
return "never"
|
|
||||||
}
|
|
||||||
d := time.Since(t).Round(time.Second)
|
|
||||||
if d < 10*time.Minute {
|
|
||||||
// node.LastSeen times are rounded very coarsely, and
|
|
||||||
// we compare times from different clocks (server vs
|
|
||||||
// local), so negative is common when close. Format as
|
|
||||||
// "recent" if negative or actually recent.
|
|
||||||
return "recent"
|
|
||||||
}
|
|
||||||
return d.String()
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ package wgengine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
crand "crypto/rand"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -22,7 +21,6 @@ import (
|
|||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/flowtrack"
|
|
||||||
"tailscale.com/net/ipset"
|
"tailscale.com/net/ipset"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
@ -123,11 +121,7 @@ type userspaceEngine struct {
|
|||||||
statusCallback StatusCallback
|
statusCallback StatusCallback
|
||||||
peerSequence []key.NodePublic
|
peerSequence []key.NodePublic
|
||||||
endpoints []tailcfg.Endpoint
|
endpoints []tailcfg.Endpoint
|
||||||
pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
|
|
||||||
|
|
||||||
// pongCallback is the map of response handlers waiting for disco or TSMP
|
|
||||||
// pong callbacks. The map key is a random slice of bytes.
|
|
||||||
pongCallback map[[8]byte]func(packet.TSMPPongReply)
|
|
||||||
// icmpEchoResponseCallback is the map of response handlers waiting for ICMP
|
// icmpEchoResponseCallback is the map of response handlers waiting for ICMP
|
||||||
// echo responses. The map key is a random uint32 that is the little endian
|
// echo responses. The map key is a random uint32 that is the little endian
|
||||||
// value of the ICMP identifier and sequence number concatenated.
|
// value of the ICMP identifier and sequence number concatenated.
|
||||||
@ -366,27 +360,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
|||||||
}
|
}
|
||||||
e.tundev.PreFilterPacketOutboundToWireGuardEngineIntercept = e.handleLocalPackets
|
e.tundev.PreFilterPacketOutboundToWireGuardEngineIntercept = e.handleLocalPackets
|
||||||
|
|
||||||
if envknob.BoolDefaultTrue("TS_DEBUG_CONNECT_FAILURES") {
|
|
||||||
if e.tundev.PreFilterPacketInboundFromWireGuard != nil {
|
|
||||||
return nil, errors.New("unexpected PreFilterIn already set")
|
|
||||||
}
|
|
||||||
e.tundev.PreFilterPacketInboundFromWireGuard = e.trackOpenPreFilterIn
|
|
||||||
if e.tundev.PostFilterPacketOutboundToWireGuard != nil {
|
|
||||||
return nil, errors.New("unexpected PostFilterOut already set")
|
|
||||||
}
|
|
||||||
e.tundev.PostFilterPacketOutboundToWireGuard = e.trackOpenPostFilterOut
|
|
||||||
}
|
|
||||||
|
|
||||||
e.wgLogger = wglog.NewLogger(logf)
|
e.wgLogger = wglog.NewLogger(logf)
|
||||||
e.tundev.OnTSMPPongReceived = func(pong packet.TSMPPongReply) {
|
|
||||||
e.mu.Lock()
|
|
||||||
defer e.mu.Unlock()
|
|
||||||
cb := e.pongCallback[pong.Data]
|
|
||||||
e.logf("wgengine: got TSMP pong %02x, peerAPIPort=%v; cb=%v", pong.Data, pong.PeerAPIPort, cb != nil)
|
|
||||||
if cb != nil {
|
|
||||||
go cb(pong)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.tundev.OnICMPEchoResponseReceived = func(p *packet.Parsed) bool {
|
e.tundev.OnICMPEchoResponseReceived = func(p *packet.Parsed) bool {
|
||||||
idSeq := p.EchoIDSeq()
|
idSeq := p.EchoIDSeq()
|
||||||
@ -1111,14 +1085,17 @@ func (e *userspaceEngine) Ping(ip netip.Addr, pingType tailcfg.PingType, size in
|
|||||||
cb(res)
|
cb(res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if pingType == "TSMP" {
|
||||||
|
res.Err = "TSMP ping not supported"
|
||||||
|
cb(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
peer := pip.Node
|
peer := pip.Node
|
||||||
|
|
||||||
e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key().ShortString(), peer.ComputedName())
|
e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key().ShortString(), peer.ComputedName())
|
||||||
switch pingType {
|
switch pingType {
|
||||||
case "disco":
|
case "disco":
|
||||||
e.magicConn.Ping(peer, res, size, cb)
|
e.magicConn.Ping(peer, res, size, cb)
|
||||||
case "TSMP":
|
|
||||||
e.sendTSMPPing(ip, peer, res, cb)
|
|
||||||
case "ICMP":
|
case "ICMP":
|
||||||
e.sendICMPEchoRequest(ip, peer, res, cb)
|
e.sendICMPEchoRequest(ip, peer, res, cb)
|
||||||
}
|
}
|
||||||
@ -1192,66 +1169,6 @@ func (e *userspaceEngine) sendICMPEchoRequest(destIP netip.Addr, peer tailcfg.No
|
|||||||
e.tundev.InjectOutbound(icmpPing)
|
e.tundev.InjectOutbound(icmpPing)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *userspaceEngine) sendTSMPPing(ip netip.Addr, peer tailcfg.NodeView, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
|
|
||||||
srcIP, err := e.mySelfIPMatchingFamily(ip)
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err.Error()
|
|
||||||
cb(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var iph packet.Header
|
|
||||||
if srcIP.Is4() {
|
|
||||||
iph = packet.IP4Header{
|
|
||||||
IPProto: ipproto.TSMP,
|
|
||||||
Src: srcIP,
|
|
||||||
Dst: ip,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
iph = packet.IP6Header{
|
|
||||||
IPProto: ipproto.TSMP,
|
|
||||||
Src: srcIP,
|
|
||||||
Dst: ip,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var data [8]byte
|
|
||||||
crand.Read(data[:])
|
|
||||||
|
|
||||||
expireTimer := time.AfterFunc(10*time.Second, func() {
|
|
||||||
e.setTSMPPongCallback(data, nil)
|
|
||||||
})
|
|
||||||
t0 := time.Now()
|
|
||||||
e.setTSMPPongCallback(data, func(pong packet.TSMPPongReply) {
|
|
||||||
expireTimer.Stop()
|
|
||||||
d := time.Since(t0)
|
|
||||||
res.LatencySeconds = d.Seconds()
|
|
||||||
res.NodeIP = ip.String()
|
|
||||||
res.NodeName = peer.ComputedName()
|
|
||||||
res.PeerAPIPort = pong.PeerAPIPort
|
|
||||||
cb(res)
|
|
||||||
})
|
|
||||||
|
|
||||||
var tsmpPayload [9]byte
|
|
||||||
tsmpPayload[0] = byte(packet.TSMPTypePing)
|
|
||||||
copy(tsmpPayload[1:], data[:])
|
|
||||||
|
|
||||||
tsmpPing := packet.Generate(iph, tsmpPayload[:])
|
|
||||||
e.tundev.InjectOutbound(tsmpPing)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func(packet.TSMPPongReply)) {
|
|
||||||
e.mu.Lock()
|
|
||||||
defer e.mu.Unlock()
|
|
||||||
if e.pongCallback == nil {
|
|
||||||
e.pongCallback = map[[8]byte]func(packet.TSMPPongReply){}
|
|
||||||
}
|
|
||||||
if cb == nil {
|
|
||||||
delete(e.pongCallback, data)
|
|
||||||
} else {
|
|
||||||
e.pongCallback[data] = cb
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *userspaceEngine) setICMPEchoResponseCallback(idSeq uint32, cb func()) {
|
func (e *userspaceEngine) setICMPEchoResponseCallback(idSeq uint32, cb func()) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
defer e.mu.Unlock()
|
defer e.mu.Unlock()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user