Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_debug && !ts_omit_connreject
package wgengine
import ( "net/netip" "testing"
"tailscale.com/net/connreject" "tailscale.com/net/packet" "tailscale.com/types/ipproto" "tailscale.com/wgengine/filter")
// TestInboundTSMPRecordsRejection asserts that a parsed inbound TSMP reject// packet is delivered as an outgoing-direction [connreject.Event] to the// installed callback when trackOpenPreFilterIn processes it.func TestInboundTSMPRecordsRejection(t *testing.T) { t.Parallel() var events []connreject.Event e := &userspaceEngine{} e.SetConnRejectCallback(func(evt connreject.Event) { events = append(events, evt) })
rh := packet.TailscaleRejectedHeader{ IPSrc: netip.MustParseAddr("100.0.0.2"), IPDst: netip.MustParseAddr("100.0.0.1"), Src: netip.MustParseAddrPort("100.0.0.1:12345"), Dst: netip.MustParseAddrPort("100.0.0.2:443"), Proto: ipproto.TCP, Reason: packet.RejectedDueToACLs, } raw := packet.Generate(rh, nil) var pp packet.Parsed pp.Decode(raw)
_ = e.trackOpenPreFilterIn(&pp, nil)
if len(events) != 1 { t.Fatalf("got %d events, want 1", len(events)) } got := events[0] if got.Direction != connreject.Outgoing { t.Errorf("Direction = %v, want Outgoing", got.Direction) } if got.Source != connreject.SourceTSMPRecv { t.Errorf("Source = %v, want tsmp_recv", got.Source) } if got.Reason != connreject.ReasonACL { t.Errorf("Reason = %q, want %q", got.Reason, connreject.ReasonACL) } if got.Proto != ipproto.TCP { t.Errorf("Proto = %v, want TCP", got.Proto) } if got.Dst.Addr() != netip.MustParseAddr("100.0.0.2") { t.Errorf("Dst.Addr = %v, want 100.0.0.2", got.Dst.Addr()) }}
// TestInboundTSMPAppConnectorTransitIPRecordsAndContinues verifies that an// unmapped app-connector transit IP is recorded with its specific reason while// the TSMP packet remains accepted for tstun's mapping-recovery handler.func TestInboundTSMPAppConnectorTransitIPRecordsAndContinues(t *testing.T) { t.Parallel() var events []connreject.Event e := &userspaceEngine{} e.SetConnRejectCallback(func(evt connreject.Event) { events = append(events, evt) })
rh := packet.TailscaleRejectedHeader{ IPSrc: netip.MustParseAddr("100.0.0.2"), IPDst: netip.MustParseAddr("100.0.0.1"), Src: netip.MustParseAddrPort("100.0.0.1:12345"), Dst: netip.MustParseAddrPort("100.0.0.2:443"), Proto: ipproto.TCP, Reason: packet.RejectedDueToUnknownAppConnectorTransitIP, } raw := packet.Generate(rh, nil) var pp packet.Parsed pp.Decode(raw)
if got := e.trackOpenPreFilterIn(&pp, nil); got != filter.Accept { t.Fatalf("filter response = %v, want Accept so tstun can handle mapping recovery", got) } if len(events) != 1 { t.Fatalf("got %d events, want 1", len(events)) } if got := events[0].Reason; got != connreject.ReasonAppConnectorTransitIPUnknown { t.Errorf("Reason = %q, want %q", got, connreject.ReasonAppConnectorTransitIPUnknown) } if got := events[0].Source; got != connreject.SourceTSMPRecv { t.Errorf("Source = %q, want %q", got, connreject.SourceTSMPRecv) }}
// TestInboundTSMPNonTerminalRecordsMaybeBroken asserts that a MaybeBroken// TSMP reject is published with MaybeBroken=true.func TestInboundTSMPNonTerminalRecordsMaybeBroken(t *testing.T) { t.Parallel() var events []connreject.Event e := &userspaceEngine{} e.SetConnRejectCallback(func(evt connreject.Event) { events = append(events, evt) })
rh := packet.TailscaleRejectedHeader{ IPSrc: netip.MustParseAddr("100.0.0.2"), IPDst: netip.MustParseAddr("100.0.0.1"), Src: netip.MustParseAddrPort("100.0.0.1:12345"), Dst: netip.MustParseAddrPort("100.0.0.2:443"), Proto: ipproto.TCP, Reason: packet.RejectedDueToHostFirewall, MaybeBroken: true, } raw := packet.Generate(rh, nil) var pp packet.Parsed pp.Decode(raw)
_ = e.trackOpenPreFilterIn(&pp, nil)
if len(events) != 1 { t.Fatalf("got %d events, want 1", len(events)) } got := events[0] if !got.MaybeBroken { t.Error("MaybeBroken = false, want true") } if got.Reason != connreject.ReasonHostFirewall { t.Errorf("Reason = %q, want %q", got.Reason, connreject.ReasonHostFirewall) }}
// TestSetConnRejectCallbackUninstall verifies that SetConnRejectCallback(nil)// safely clears the previously installed callback and that// notifyConnRejectTSMPRecv is a safe no-op afterward.func TestSetConnRejectCallbackUninstall(t *testing.T) { t.Parallel() var events []connreject.Event e := &userspaceEngine{} e.SetConnRejectCallback(func(evt connreject.Event) { events = append(events, evt) })
rh := packet.TailscaleRejectedHeader{ IPSrc: netip.MustParseAddr("100.0.0.2"), IPDst: netip.MustParseAddr("100.0.0.1"), Src: netip.MustParseAddrPort("100.0.0.1:12345"), Dst: netip.MustParseAddrPort("100.0.0.2:443"), Proto: ipproto.TCP, Reason: packet.RejectedDueToACLs, } raw := packet.Generate(rh, nil) var pp packet.Parsed pp.Decode(raw)
// First emit while installed: should deliver. _ = e.trackOpenPreFilterIn(&pp, nil) if len(events) != 1 { t.Fatalf("after install: got %d events, want 1", len(events)) }
// Uninstall and emit again: must not panic and must not deliver. e.SetConnRejectCallback(nil) _ = e.trackOpenPreFilterIn(&pp, nil) if len(events) != 1 { t.Errorf("after uninstall: got %d events, want still 1", len(events)) }
// Reinstall and emit a third time: should deliver again. e.SetConnRejectCallback(func(evt connreject.Event) { events = append(events, evt) }) _ = e.trackOpenPreFilterIn(&pp, nil) if len(events) != 2 { t.Errorf("after reinstall: got %d events, want 2", len(events)) }}
func TestClassifyOpenTimeout(t *testing.T) { t.Parallel() tests := []struct { name string in openTimeoutDiag wantReason connreject.Reason wantSource connreject.Source }{ { name: "only-zero-route-suppresses-recording", in: openTimeoutDiag{onlyZeroRoute: true}, wantReason: "", wantSource: connreject.SourceUnknown, }, { name: "only-zero-route-suppresses-even-with-problem", in: openTimeoutDiag{onlyZeroRoute: true, problem: packet.RejectedDueToACLs}, wantReason: "", wantSource: connreject.SourceUnknown, }, { name: "problem-supersedes-diagnosis", in: openTimeoutDiag{problem: packet.RejectedDueToACLs, noPeer: true}, wantReason: connreject.ReasonACL, wantSource: connreject.SourceTSMPRecv, }, { name: "problem-host-firewall", in: openTimeoutDiag{problem: packet.RejectedDueToHostFirewall}, wantReason: connreject.ReasonHostFirewall, wantSource: connreject.SourceTSMPRecv, }, { name: "noPeer", in: openTimeoutDiag{noPeer: true}, wantReason: connreject.ReasonNoPeer, wantSource: connreject.SourcePendOpenTimeout, }, { name: "peerUnreachable", in: openTimeoutDiag{peerUnreachable: true}, wantReason: connreject.ReasonPeerUnreachable, wantSource: connreject.SourcePendOpenTimeout, }, { name: "plain-timeout", in: openTimeoutDiag{}, wantReason: connreject.ReasonTimeout, wantSource: connreject.SourcePendOpenTimeout, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { gotReason, gotSource := classifyOpenTimeout(tc.in) if gotReason != tc.wantReason { t.Errorf("reason = %q, want %q", gotReason, tc.wantReason) } if gotSource != tc.wantSource { t.Errorf("source = %q, want %q", gotSource, tc.wantSource) } }) }}
func TestRejectReasonToReason(t *testing.T) { t.Parallel() tests := []struct { in packet.TailscaleRejectReason want connreject.Reason }{ {packet.RejectedDueToACLs, connreject.ReasonACL}, {packet.RejectedDueToShieldsUp, connreject.ReasonShields}, {packet.RejectedDueToIPForwarding, connreject.ReasonHostIPForwarding}, {packet.RejectedDueToHostFirewall, connreject.ReasonHostFirewall}, {packet.RejectedDueToUnknownAppConnectorTransitIP, connreject.ReasonAppConnectorTransitIPUnknown}, {packet.TailscaleRejectReasonNone, connreject.ReasonUnknown}, } for _, tc := range tests { if got := rejectReasonToReason(tc.in); got != tc.want { t.Errorf("rejectReasonToReason(%v) = %q, want %q", tc.in, got, tc.want) } }}