// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause //go:build !ts_omit_netlog && !ts_omit_logtail package netlog import ( "encoding/binary" "math/rand/v2" "net/netip" "sync" "testing" "testing/synctest" "time" jsonv2 "github.com/go-json-experiment/json" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "tailscale.com/tailcfg" "tailscale.com/types/bools" "tailscale.com/types/ipproto" "tailscale.com/types/netlogtype" "tailscale.com/types/netmap" "tailscale.com/wgengine/router" ) func TestEmbedNodeInfo(t *testing.T) { // Initialize the logger with a particular view of the netmap. var logger Logger logger.ReconfigNetworkMap(&netmap.NetworkMap{ SelfNode: (&tailcfg.Node{ StableID: "n123456CNTL", ID: 123456, Name: "test.tail123456.ts.net", Addresses: []netip.Prefix{prefix("100.1.2.3")}, Tags: []string{"tag:foo", "tag:bar"}, }).View(), Peers: []tailcfg.NodeView{ (&tailcfg.Node{ StableID: "n123457CNTL", ID: 123457, Name: "peer1.tail123456.ts.net", Addresses: []netip.Prefix{prefix("100.1.2.4")}, Tags: []string{"tag:peer"}, }).View(), (&tailcfg.Node{ StableID: "n123458CNTL", ID: 123458, Name: "peer2.tail123456.ts.net", Addresses: []netip.Prefix{prefix("100.1.2.5")}, User: 54321, }).View(), }, UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{ 54321: (&tailcfg.UserProfile{ID: 54321, LoginName: "peer@example.com"}).View(), }, }) logger.ReconfigRoutes(&router.Config{ SubnetRoutes: []netip.Prefix{ prefix("172.16.1.1/16"), prefix("192.168.1.1/24"), }, }) // Update the counters for a few connections. var group sync.WaitGroup defer group.Wait() conns := []struct { virt bool proto ipproto.Proto src, dst netip.AddrPort txP, txB, rxP, rxB int }{ {true, 0x6, addrPort("100.1.2.3:80"), addrPort("100.1.2.4:1812"), 88, 278, 34, 887}, {true, 0x6, addrPort("100.1.2.3:443"), addrPort("100.1.2.5:1742"), 96, 635, 23, 790}, {true, 0x6, addrPort("100.1.2.3:443"), addrPort("100.1.2.6:1175"), 48, 94, 86, 618}, // unknown peer (in Tailscale IP space, but not a known peer) {true, 0x6, addrPort("100.1.2.3:80"), addrPort("192.168.1.241:713"), 43, 154, 66, 883}, {true, 0x6, addrPort("100.1.2.3:80"), addrPort("192.168.2.241:713"), 43, 154, 66, 883}, // not in the subnet, must be exit traffic {true, 0x6, addrPort("100.1.2.3:80"), addrPort("172.16.5.18:713"), 7, 243, 40, 59}, {true, 0x6, addrPort("100.1.2.3:80"), addrPort("172.20.5.18:713"), 61, 753, 42, 492}, // not in the subnet, must be exit traffic {true, 0x6, addrPort("192.168.1.241:713"), addrPort("100.1.2.3:80"), 43, 154, 66, 883}, {true, 0x6, addrPort("192.168.2.241:713"), addrPort("100.1.2.3:80"), 43, 154, 66, 883}, // not in the subnet, must be exit traffic {true, 0x6, addrPort("172.16.5.18:713"), addrPort("100.1.2.3:80"), 7, 243, 40, 59}, {true, 0x6, addrPort("172.20.5.18:713"), addrPort("100.1.2.3:80"), 61, 753, 42, 492}, // not in the subnet, must be exit traffic {true, 0x6, addrPort("14.255.192.128:39230"), addrPort("243.42.106.193:48206"), 81, 791, 79, 316}, // unknown connection {false, 0x6, addrPort("100.1.2.4:0"), addrPort("35.92.180.165:9743"), 63, 136, 61, 409}, // physical traffic with peer1 {false, 0x6, addrPort("100.1.2.5:0"), addrPort("131.19.35.17:9743"), 88, 452, 2, 716}, // physical traffic with peer2 } for range 10 { for _, conn := range conns { update := bools.IfElse(conn.virt, logger.updateVirtConn, logger.updatePhysConn) group.Go(func() { update(conn.proto, conn.src, conn.dst, conn.txP, conn.txB, false) }) group.Go(func() { update(conn.proto, conn.src, conn.dst, conn.rxP, conn.rxB, true) }) } } group.Wait() // Verify that the counters match. got := logger.record.toMessage(false, false) got.Start = time.Time{} // avoid flakiness want := netlogtype.Message{ NodeID: "n123456CNTL", SrcNode: netlogtype.Node{ NodeID: "n123456CNTL", Name: "test.tail123456.ts.net", Addresses: []netip.Addr{addr("100.1.2.3")}, Tags: []string{"tag:bar", "tag:foo"}, }, DstNodes: []netlogtype.Node{{ NodeID: "n123457CNTL", Name: "peer1.tail123456.ts.net", Addresses: []netip.Addr{addr("100.1.2.4")}, Tags: []string{"tag:peer"}, }, { NodeID: "n123458CNTL", Name: "peer2.tail123456.ts.net", Addresses: []netip.Addr{addr("100.1.2.5")}, User: "peer@example.com", }}, VirtualTraffic: []netlogtype.ConnectionCounts{ {Connection: conn(0x6, "100.1.2.3:80", "100.1.2.4:1812"), Counts: counts(880, 2780, 340, 8870)}, {Connection: conn(0x6, "100.1.2.3:443", "100.1.2.5:1742"), Counts: counts(960, 6350, 230, 7900)}, }, SubnetTraffic: []netlogtype.ConnectionCounts{ {Connection: conn(0x6, "100.1.2.3:80", "172.16.5.18:713"), Counts: counts(70, 2430, 400, 590)}, {Connection: conn(0x6, "100.1.2.3:80", "192.168.1.241:713"), Counts: counts(430, 1540, 660, 8830)}, {Connection: conn(0x6, "172.16.5.18:713", "100.1.2.3:80"), Counts: counts(70, 2430, 400, 590)}, {Connection: conn(0x6, "192.168.1.241:713", "100.1.2.3:80"), Counts: counts(430, 1540, 660, 8830)}, }, ExitTraffic: []netlogtype.ConnectionCounts{ {Connection: conn(0x6, "14.255.192.128:39230", "243.42.106.193:48206"), Counts: counts(810, 7910, 790, 3160)}, {Connection: conn(0x6, "100.1.2.3:80", "172.20.5.18:713"), Counts: counts(610, 7530, 420, 4920)}, {Connection: conn(0x6, "100.1.2.3:80", "192.168.2.241:713"), Counts: counts(430, 1540, 660, 8830)}, {Connection: conn(0x6, "100.1.2.3:443", "100.1.2.6:1175"), Counts: counts(480, 940, 860, 6180)}, {Connection: conn(0x6, "172.20.5.18:713", "100.1.2.3:80"), Counts: counts(610, 7530, 420, 4920)}, {Connection: conn(0x6, "192.168.2.241:713", "100.1.2.3:80"), Counts: counts(430, 1540, 660, 8830)}, }, PhysicalTraffic: []netlogtype.ConnectionCounts{ {Connection: conn(0x6, "100.1.2.4:0", "35.92.180.165:9743"), Counts: counts(630, 1360, 610, 4090)}, {Connection: conn(0x6, "100.1.2.5:0", "131.19.35.17:9743"), Counts: counts(880, 4520, 20, 7160)}, }, } if d := cmp.Diff(got, want, cmpopts.EquateComparable(netip.Addr{}, netip.AddrPort{})); d != "" { t.Errorf("Message (-got +want):\n%s", d) } } func TestUpdateRace(t *testing.T) { var logger Logger logger.recordsChan = make(chan record, 1) go func(recordsChan chan record) { for range recordsChan { } }(logger.recordsChan) var group sync.WaitGroup defer group.Wait() for i := range 1000 { group.Go(func() { src, dst := randAddrPort(), randAddrPort() for j := range 1000 { if i%2 == 0 { logger.updateVirtConn(0x1, src, dst, rand.IntN(10), rand.IntN(1000), j%2 == 0) } else { logger.updatePhysConn(0x1, src, dst, rand.IntN(10), rand.IntN(1000), j%2 == 0) } } }) group.Go(func() { for range 1000 { logger.ReconfigNetworkMap(new(netmap.NetworkMap)) } }) group.Go(func() { for range 1000 { logger.ReconfigRoutes(new(router.Config)) } }) } group.Wait() logger.mu.Lock() close(logger.recordsChan) logger.mu.Unlock() } func randAddrPort() netip.AddrPort { var b [4]uint8 binary.LittleEndian.PutUint32(b[:], rand.Uint32()) return netip.AddrPortFrom(netip.AddrFrom4(b), uint16(rand.Uint32())) } func TestAutoFlushMaxConns(t *testing.T) { var logger Logger logger.recordsChan = make(chan record, 1) for i := 0; len(logger.recordsChan) == 0; i++ { logger.updateVirtConn(0, netip.AddrPortFrom(netip.Addr{}, uint16(i)), netip.AddrPort{}, 1, 1, false) } b, _ := jsonv2.Marshal(logger.recordsChan) if len(b) > maxLogSize { t.Errorf("len(Message) = %v, want <= %d", len(b), maxLogSize) } } func TestAutoFlushTimeout(t *testing.T) { var logger Logger logger.recordsChan = make(chan record, 1) synctest.Test(t, func(t *testing.T) { logger.updateVirtConn(0, netip.AddrPort{}, netip.AddrPort{}, 1, 1, false) time.Sleep(pollPeriod) }) rec := <-logger.recordsChan if d := rec.end.Sub(rec.start); d != pollPeriod { t.Errorf("window = %v, want %v", d, pollPeriod) } if len(rec.virtConns) != 1 { t.Errorf("len(virtConns) = %d, want 1", len(rec.virtConns)) } } func BenchmarkUpdateSameConn(b *testing.B) { var logger Logger b.ReportAllocs() for range b.N { logger.updateVirtConn(0, netip.AddrPort{}, netip.AddrPort{}, 1, 1, false) } } func BenchmarkUpdateNewConns(b *testing.B) { var logger Logger b.ReportAllocs() for i := range b.N { logger.updateVirtConn(0, netip.AddrPortFrom(netip.Addr{}, uint16(i)), netip.AddrPort{}, 1, 1, false) } }