mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +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/ipnstate from tailscale.com/client/tailscale+
|
||||
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/netcheck from tailscale.com/cmd/tailscale/cli
|
||||
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/mem from tailscale.com/ipn/store
|
||||
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/netaddr from tailscale.com/ipn+
|
||||
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.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
|
||||
return
|
||||
case ipproto.TSMP:
|
||||
// Inter-tailscale messages.
|
||||
q.dataofs = q.subofs
|
||||
return
|
||||
case ipproto.Fragment:
|
||||
// An IPProto value of 0xff (our Fragment constant for internal use)
|
||||
// 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.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
|
||||
return
|
||||
case ipproto.TSMP:
|
||||
// Inter-tailscale messages.
|
||||
q.dataofs = q.subofs
|
||||
return
|
||||
case ipproto.Fragment:
|
||||
// An IPProto value of 0xff (our Fragment constant for internal use)
|
||||
// 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 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
|
||||
// arrives. If the packet is to be handled internally this returns true,
|
||||
// false otherwise.
|
||||
@ -916,29 +913,6 @@ func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
|
||||
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
|
||||
// with the given contents was sent to the network.
|
||||
// It does not block, but takes ownership of the packet.
|
||||
|
@ -54,17 +54,6 @@ const (
|
||||
GRE Proto = 0x2f
|
||||
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
|
||||
// don't have the sub-protocol header (and therefore can't
|
||||
// figure out what the sub-protocol is).
|
||||
@ -93,8 +82,6 @@ func (p Proto) String() string {
|
||||
return "TCP"
|
||||
case SCTP:
|
||||
return "SCTP"
|
||||
case TSMP:
|
||||
return "TSMP"
|
||||
case GRE:
|
||||
return "GRE"
|
||||
case DCCP:
|
||||
@ -144,7 +131,6 @@ var (
|
||||
"ipv6-icmp": ICMPv6,
|
||||
"sctp": SCTP,
|
||||
"tcp": TCP,
|
||||
"tsmp": TSMP,
|
||||
"udp": UDP,
|
||||
}
|
||||
)
|
||||
|
@ -503,8 +503,6 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
|
||||
if f.matches4.match(q, f.srcIPHasCap) {
|
||||
return Accept, "ok"
|
||||
}
|
||||
case ipproto.TSMP:
|
||||
return Accept, "tsmp ok"
|
||||
default:
|
||||
if f.matches4.matchProtoAndIPsOnlyIfAllPorts(q) {
|
||||
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) {
|
||||
return Accept, "ok"
|
||||
}
|
||||
case ipproto.TSMP:
|
||||
return Accept, "tsmp ok"
|
||||
default:
|
||||
if f.matches6.matchProtoAndIPsOnlyIfAllPorts(q) {
|
||||
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 (
|
||||
"bufio"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -22,7 +21,6 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/flowtrack"
|
||||
"tailscale.com/net/ipset"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/packet"
|
||||
@ -123,11 +121,7 @@ type userspaceEngine struct {
|
||||
statusCallback StatusCallback
|
||||
peerSequence []key.NodePublic
|
||||
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
|
||||
// echo responses. The map key is a random uint32 that is the little endian
|
||||
// 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
|
||||
|
||||
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.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 {
|
||||
idSeq := p.EchoIDSeq()
|
||||
@ -1111,14 +1085,17 @@ func (e *userspaceEngine) Ping(ip netip.Addr, pingType tailcfg.PingType, size in
|
||||
cb(res)
|
||||
return
|
||||
}
|
||||
if pingType == "TSMP" {
|
||||
res.Err = "TSMP ping not supported"
|
||||
cb(res)
|
||||
return
|
||||
}
|
||||
peer := pip.Node
|
||||
|
||||
e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key().ShortString(), peer.ComputedName())
|
||||
switch pingType {
|
||||
case "disco":
|
||||
e.magicConn.Ping(peer, res, size, cb)
|
||||
case "TSMP":
|
||||
e.sendTSMPPing(ip, peer, res, cb)
|
||||
case "ICMP":
|
||||
e.sendICMPEchoRequest(ip, peer, res, cb)
|
||||
}
|
||||
@ -1192,66 +1169,6 @@ func (e *userspaceEngine) sendICMPEchoRequest(destIP netip.Addr, peer tailcfg.No
|
||||
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()) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
Loading…
x
Reference in New Issue
Block a user