types/netlogtype: new package for network logging types (#6092)

The netlog.Message type is useful to depend on from other packages,
but doing so would transitively cause gvisor and other large packages
to be linked in.

Avoid this problem by moving all network logging types to a single package.

We also update staticcheck to take in:

	003d277bcf

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
pull/5925/head
Joe Tsai 2 years ago committed by GitHub
parent a44687e71f
commit c21a3c4733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -39,10 +39,8 @@ import (
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"tailscale.com/net/flowtrack" "tailscale.com/types/netlogtype"
"tailscale.com/net/tunstats"
"tailscale.com/util/must" "tailscale.com/util/must"
"tailscale.com/wgengine/netlog"
) )
var ( var (
@ -62,7 +60,7 @@ func main() {
Logtail struct { Logtail struct {
ID string `json:"id"` ID string `json:"id"`
} `json:"logtail"` } `json:"logtail"`
netlog.Message netlogtype.Message
} }
if err := dec.Decode(&msg); err != nil { if err := dec.Decode(&msg); err != nil {
if err == io.EOF { if err == io.EOF {
@ -77,16 +75,16 @@ func main() {
// Construct a table of network traffic per connection. // Construct a table of network traffic per connection.
rows := [][7]string{{3: "Tx[P/s]", 4: "Tx[B/s]", 5: "Rx[P/s]", 6: "Rx[B/s]"}} rows := [][7]string{{3: "Tx[P/s]", 4: "Tx[B/s]", 5: "Rx[P/s]", 6: "Rx[B/s]"}}
duration := msg.End.Sub(msg.Start) duration := msg.End.Sub(msg.Start)
addRows := func(heading string, traffic []netlog.TupleCounts) { addRows := func(heading string, traffic []netlogtype.ConnectionCounts) {
if len(traffic) == 0 { if len(traffic) == 0 {
return return
} }
slices.SortFunc(traffic, func(x, y netlog.TupleCounts) bool { slices.SortFunc(traffic, func(x, y netlogtype.ConnectionCounts) bool {
nx := x.TxPackets + x.TxBytes + x.RxPackets + x.RxBytes nx := x.TxPackets + x.TxBytes + x.RxPackets + x.RxBytes
ny := y.TxPackets + y.TxBytes + y.RxPackets + y.RxBytes ny := y.TxPackets + y.TxBytes + y.RxPackets + y.RxBytes
return nx > ny return nx > ny
}) })
var sum tunstats.Counts var sum netlogtype.Counts
for _, cc := range traffic { for _, cc := range traffic {
sum = sum.Add(cc.Counts) sum = sum.Add(cc.Counts)
} }
@ -97,7 +95,7 @@ func main() {
5: formatSI(float64(sum.RxPackets) / duration.Seconds()), 5: formatSI(float64(sum.RxPackets) / duration.Seconds()),
6: formatIEC(float64(sum.RxBytes) / duration.Seconds()), 6: formatIEC(float64(sum.RxBytes) / duration.Seconds()),
}) })
if len(traffic) == 1 && traffic[0].Tuple == (flowtrack.Tuple{}) { if len(traffic) == 1 && traffic[0].Connection.IsZero() {
return // this is already a summary counts return // this is already a summary counts
} }
formatAddrPort := func(a netip.AddrPort) string { formatAddrPort := func(a netip.AddrPort) string {

@ -240,7 +240,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/tsdial from tailscale.com/control/controlclient+ tailscale.com/net/tsdial from tailscale.com/control/controlclient+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
tailscale.com/net/tstun from tailscale.com/net/dns+ tailscale.com/net/tstun from tailscale.com/net/dns+
tailscale.com/net/tunstats from tailscale.com/net/tstun+ tailscale.com/net/tunstats from tailscale.com/net/tstun
tailscale.com/net/wsconn from tailscale.com/control/controlhttp+ tailscale.com/net/wsconn from tailscale.com/control/controlhttp+
tailscale.com/paths from tailscale.com/ipn/ipnlocal+ tailscale.com/paths from tailscale.com/ipn/ipnlocal+
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal 💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
@ -262,6 +262,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+ tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
tailscale.com/types/key from tailscale.com/control/controlbase+ tailscale.com/types/key from tailscale.com/control/controlbase+
tailscale.com/types/logger from tailscale.com/control/controlclient+ tailscale.com/types/logger from tailscale.com/control/controlclient+
tailscale.com/types/netlogtype from tailscale.com/net/tstun+
tailscale.com/types/netmap from tailscale.com/control/controlclient+ tailscale.com/types/netmap from tailscale.com/control/controlclient+
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock+ tailscale.com/types/nettype from tailscale.com/wgengine/magicsock+
tailscale.com/types/opt from tailscale.com/control/controlclient+ tailscale.com/types/opt from tailscale.com/control/controlclient+

@ -67,7 +67,7 @@ require (
golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0
golang.zx2c4.com/wireguard/windows v0.5.3 golang.zx2c4.com/wireguard/windows v0.5.3
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83 honnef.co/go/tools v0.4.0-0.dev.0.20220517111757-f4a2f64ce238
inet.af/peercred v0.0.0-20210906144145-0893ea02156a inet.af/peercred v0.0.0-20210906144145-0893ea02156a
inet.af/wf v0.0.0-20220728202103-50d96caab2f6 inet.af/wf v0.0.0-20220728202103-50d96caab2f6
nhooyr.io/websocket v1.8.7 nhooyr.io/websocket v1.8.7

@ -1829,8 +1829,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83 h1:lZ9GIYaU+o5+X6ST702I/Ntyq9Y2oIMZ42rBQpem64A= honnef.co/go/tools v0.4.0-0.dev.0.20220517111757-f4a2f64ce238 h1:8Vr1KP9OTjoKQSSeLefzibQgDV4s2ujJElKHqMi7nsA=
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70= honnef.co/go/tools v0.4.0-0.dev.0.20220517111757-f4a2f64ce238/go.mod h1:DCQzo6aCmhYDJH+We7BIU38vNvVkaOKa6s57pewKdvI=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=

@ -22,7 +22,6 @@ import (
"golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
"tailscale.com/disco" "tailscale.com/disco"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet" "tailscale.com/net/packet"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/net/tunstats" "tailscale.com/net/tunstats"
@ -31,6 +30,7 @@ import (
"tailscale.com/types/ipproto" "tailscale.com/types/ipproto"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/netlogtype"
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
) )
@ -853,7 +853,7 @@ func (t *Wrapper) SetStatisticsEnabled(enable bool) {
// ExtractStatistics extracts and resets the counters for all active connections. // ExtractStatistics extracts and resets the counters for all active connections.
// It must be called periodically otherwise the memory used is unbounded. // It must be called periodically otherwise the memory used is unbounded.
func (t *Wrapper) ExtractStatistics() map[flowtrack.Tuple]tunstats.Counts { func (t *Wrapper) ExtractStatistics() map[netlogtype.Connection]netlogtype.Counts {
return t.stats.Extract() return t.stats.Extract()
} }

@ -19,15 +19,14 @@ import (
"go4.org/netipx" "go4.org/netipx"
"golang.zx2c4.com/wireguard/tun/tuntest" "golang.zx2c4.com/wireguard/tun/tuntest"
"tailscale.com/disco" "tailscale.com/disco"
"tailscale.com/net/flowtrack"
"tailscale.com/net/netaddr" "tailscale.com/net/netaddr"
"tailscale.com/net/packet" "tailscale.com/net/packet"
"tailscale.com/net/tunstats"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/tstime/mono" "tailscale.com/tstime/mono"
"tailscale.com/types/ipproto" "tailscale.com/types/ipproto"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/netlogtype"
"tailscale.com/wgengine/filter" "tailscale.com/wgengine/filter"
) )
@ -379,17 +378,17 @@ func TestFilter(t *testing.T) {
} }
got := tun.ExtractStatistics() got := tun.ExtractStatistics()
want := map[flowtrack.Tuple]tunstats.Counts{} want := map[netlogtype.Connection]netlogtype.Counts{}
if !tt.drop { if !tt.drop {
var p packet.Parsed var p packet.Parsed
p.Decode(tt.data) p.Decode(tt.data)
switch tt.dir { switch tt.dir {
case in: case in:
tuple := flowtrack.Tuple{Proto: ipproto.UDP, Src: p.Dst, Dst: p.Src} conn := netlogtype.Connection{Proto: ipproto.UDP, Src: p.Dst, Dst: p.Src}
want[tuple] = tunstats.Counts{RxPackets: 1, RxBytes: uint64(len(tt.data))} want[conn] = netlogtype.Counts{RxPackets: 1, RxBytes: uint64(len(tt.data))}
case out: case out:
tuple := flowtrack.Tuple{Proto: ipproto.UDP, Src: p.Src, Dst: p.Dst} conn := netlogtype.Connection{Proto: ipproto.UDP, Src: p.Src, Dst: p.Dst}
want[tuple] = tunstats.Counts{TxPackets: 1, TxBytes: uint64(len(tt.data))} want[conn] = netlogtype.Counts{TxPackets: 1, TxBytes: uint64(len(tt.data))}
} }
} }
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {

@ -9,8 +9,8 @@ package tunstats
import ( import (
"sync" "sync"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet" "tailscale.com/net/packet"
"tailscale.com/types/netlogtype"
) )
// Statistics maintains counters for every connection. // Statistics maintains counters for every connection.
@ -18,36 +18,19 @@ import (
// The zero value is ready for use. // The zero value is ready for use.
type Statistics struct { type Statistics struct {
mu sync.Mutex mu sync.Mutex
m map[flowtrack.Tuple]Counts m map[netlogtype.Connection]netlogtype.Counts
}
// Counts are statistics about a particular connection.
type Counts struct {
TxPackets uint64 `json:"txPkts,omitempty"`
TxBytes uint64 `json:"txBytes,omitempty"`
RxPackets uint64 `json:"rxPkts,omitempty"`
RxBytes uint64 `json:"rxBytes,omitempty"`
}
// Add adds the counts from both c1 and c2.
func (c1 Counts) Add(c2 Counts) Counts {
c1.TxPackets += c2.TxPackets
c1.TxBytes += c2.TxBytes
c1.RxPackets += c2.RxPackets
c1.RxBytes += c2.RxBytes
return c1
} }
// UpdateTx updates the counters for a transmitted IP packet // UpdateTx updates the counters for a transmitted IP packet
// The source and destination of the packet directly correspond with // The source and destination of the packet directly correspond with
// the source and destination in flowtrack.Tuple. // the source and destination in netlogtype.Connection.
func (s *Statistics) UpdateTx(b []byte) { func (s *Statistics) UpdateTx(b []byte) {
s.update(b, false) s.update(b, false)
} }
// UpdateRx updates the counters for a received IP packet. // UpdateRx updates the counters for a received IP packet.
// The source and destination of the packet are inverted with respect to // The source and destination of the packet are inverted with respect to
// the source and destination in flowtrack.Tuple. // the source and destination in netlogtype.Connection.
func (s *Statistics) UpdateRx(b []byte) { func (s *Statistics) UpdateRx(b []byte) {
s.update(b, true) s.update(b, true)
} }
@ -55,17 +38,17 @@ func (s *Statistics) UpdateRx(b []byte) {
func (s *Statistics) update(b []byte, receive bool) { func (s *Statistics) update(b []byte, receive bool) {
var p packet.Parsed var p packet.Parsed
p.Decode(b) p.Decode(b)
tuple := flowtrack.Tuple{Proto: p.IPProto, Src: p.Src, Dst: p.Dst} conn := netlogtype.Connection{Proto: p.IPProto, Src: p.Src, Dst: p.Dst}
if receive { if receive {
tuple.Src, tuple.Dst = tuple.Dst, tuple.Src conn.Src, conn.Dst = conn.Dst, conn.Src
} }
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.m == nil { if s.m == nil {
s.m = make(map[flowtrack.Tuple]Counts) s.m = make(map[netlogtype.Connection]netlogtype.Counts)
} }
cnts := s.m[tuple] cnts := s.m[conn]
if receive { if receive {
cnts.RxPackets++ cnts.RxPackets++
cnts.RxBytes += uint64(len(b)) cnts.RxBytes += uint64(len(b))
@ -73,15 +56,15 @@ func (s *Statistics) update(b []byte, receive bool) {
cnts.TxPackets++ cnts.TxPackets++
cnts.TxBytes += uint64(len(b)) cnts.TxBytes += uint64(len(b))
} }
s.m[tuple] = cnts s.m[conn] = cnts
} }
// Extract extracts and resets the counters for all active connections. // Extract extracts and resets the counters for all active connections.
// It must be called periodically otherwise the memory used is unbounded. // It must be called periodically otherwise the memory used is unbounded.
func (s *Statistics) Extract() map[flowtrack.Tuple]Counts { func (s *Statistics) Extract() map[netlogtype.Connection]netlogtype.Counts {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
m := s.m m := s.m
s.m = make(map[flowtrack.Tuple]Counts) s.m = make(map[netlogtype.Connection]netlogtype.Counts)
return m return m
} }

@ -15,8 +15,8 @@ import (
"time" "time"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"tailscale.com/net/flowtrack"
"tailscale.com/types/ipproto" "tailscale.com/types/ipproto"
"tailscale.com/types/netlogtype"
) )
func testPacketV4(proto ipproto.Proto, srcAddr, dstAddr [4]byte, srcPort, dstPort, size uint16) (out []byte) { func testPacketV4(proto ipproto.Proto, srcAddr, dstAddr [4]byte, srcPort, dstPort, size uint16) (out []byte) {
@ -48,17 +48,17 @@ func TestConcurrent(t *testing.T) {
c := qt.New(t) c := qt.New(t)
var stats Statistics var stats Statistics
var wants []map[flowtrack.Tuple]Counts var wants []map[netlogtype.Connection]netlogtype.Counts
gots := make([]map[flowtrack.Tuple]Counts, runtime.NumCPU()) gots := make([]map[netlogtype.Connection]netlogtype.Counts, runtime.NumCPU())
var group sync.WaitGroup var group sync.WaitGroup
for i := range gots { for i := range gots {
group.Add(1) group.Add(1)
go func(i int) { go func(i int) {
defer group.Done() defer group.Done()
gots[i] = make(map[flowtrack.Tuple]Counts) gots[i] = make(map[netlogtype.Connection]netlogtype.Counts)
rn := rand.New(rand.NewSource(time.Now().UnixNano())) rn := rand.New(rand.NewSource(time.Now().UnixNano()))
var p []byte var p []byte
var t flowtrack.Tuple var t netlogtype.Connection
for j := 0; j < 1000; j++ { for j := 0; j < 1000; j++ {
delay := rn.Intn(10000) delay := rn.Intn(10000)
if p == nil || rn.Intn(64) == 0 { if p == nil || rn.Intn(64) == 0 {
@ -72,7 +72,7 @@ func TestConcurrent(t *testing.T) {
dstPort := uint16(rand.Intn(16)) dstPort := uint16(rand.Intn(16))
size := uint16(64 + rand.Intn(1024)) size := uint16(64 + rand.Intn(1024))
p = testPacketV4(proto, srcAddr.As4(), dstAddr.As4(), srcPort, dstPort, size) p = testPacketV4(proto, srcAddr.As4(), dstAddr.As4(), srcPort, dstPort, size)
t = flowtrack.Tuple{Proto: proto, Src: netip.AddrPortFrom(srcAddr, srcPort), Dst: netip.AddrPortFrom(dstAddr, dstPort)} t = netlogtype.Connection{Proto: proto, Src: netip.AddrPortFrom(srcAddr, srcPort), Dst: netip.AddrPortFrom(dstAddr, dstPort)}
} }
t2 := t t2 := t
receive := rn.Intn(2) == 0 receive := rn.Intn(2) == 0
@ -102,17 +102,17 @@ func TestConcurrent(t *testing.T) {
group.Wait() group.Wait()
wants = append(wants, stats.Extract()) wants = append(wants, stats.Extract())
got := make(map[flowtrack.Tuple]Counts) got := make(map[netlogtype.Connection]netlogtype.Counts)
want := make(map[flowtrack.Tuple]Counts) want := make(map[netlogtype.Connection]netlogtype.Counts)
mergeMaps(got, gots...) mergeMaps(got, gots...)
mergeMaps(want, wants...) mergeMaps(want, wants...)
c.Assert(got, qt.DeepEquals, want) c.Assert(got, qt.DeepEquals, want)
} }
func mergeMaps(dst map[flowtrack.Tuple]Counts, srcs ...map[flowtrack.Tuple]Counts) { func mergeMaps(dst map[netlogtype.Connection]netlogtype.Counts, srcs ...map[netlogtype.Connection]netlogtype.Counts) {
for _, src := range srcs { for _, src := range srcs {
for tuple, cnts := range src { for conn, cnts := range src {
dst[tuple] = dst[tuple].Add(cnts) dst[conn] = dst[conn].Add(cnts)
} }
} }
} }

@ -0,0 +1,58 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package netlogtype defines types for network logging.
package netlogtype
import (
"net/netip"
"time"
"tailscale.com/types/ipproto"
)
// Message is the log message that captures network traffic.
type Message struct {
Start time.Time `json:"start"` // inclusive
End time.Time `json:"end"` // inclusive
VirtualTraffic []ConnectionCounts `json:"virtualTraffic,omitempty"`
SubnetTraffic []ConnectionCounts `json:"subnetTraffic,omitempty"`
ExitTraffic []ConnectionCounts `json:"exitTraffic,omitempty"`
PhysicalTraffic []ConnectionCounts `json:"physicalTraffic,omitempty"`
}
// ConnectionCounts is a flattened struct of both a connection and counts.
type ConnectionCounts struct {
Connection
Counts
}
// Connection is a 5-tuple of proto, source and destination IP and port.
type Connection struct {
Proto ipproto.Proto `json:"proto,omitzero,omitempty"`
Src netip.AddrPort `json:"src,omitzero"`
Dst netip.AddrPort `json:"dst,omitzero"`
}
func (c Connection) IsZero() bool { return c == Connection{} }
// Counts are statistics about a particular connection.
type Counts struct {
TxPackets uint64 `json:"txPkts,omitzero,omitempty"`
TxBytes uint64 `json:"txBytes,omitzero,omitempty"`
RxPackets uint64 `json:"rxPkts,omitzero,omitempty"`
RxBytes uint64 `json:"rxBytes,omitzero,omitempty"`
}
func (c Counts) IsZero() bool { return c == Counts{} }
// Add adds the counts from both c1 and c2.
func (c1 Counts) Add(c2 Counts) Counts {
c1.TxPackets += c2.TxPackets
c1.TxBytes += c2.TxBytes
c1.RxPackets += c2.RxPackets
c1.RxBytes += c2.RxBytes
return c1
}

@ -20,10 +20,9 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"tailscale.com/logpolicy" "tailscale.com/logpolicy"
"tailscale.com/logtail" "tailscale.com/logtail"
"tailscale.com/net/flowtrack"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/net/tunstats"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/types/netlogtype"
"tailscale.com/wgengine/router" "tailscale.com/wgengine/router"
) )
@ -36,29 +35,13 @@ const pollPeriod = 5 * time.Second
// TODO(joetsai): Make *magicsock.Conn implement this interface. // TODO(joetsai): Make *magicsock.Conn implement this interface.
type Device interface { type Device interface {
SetStatisticsEnabled(bool) SetStatisticsEnabled(bool)
ExtractStatistics() map[flowtrack.Tuple]tunstats.Counts ExtractStatistics() map[netlogtype.Connection]netlogtype.Counts
} }
type noopDevice struct{} type noopDevice struct{}
func (noopDevice) SetStatisticsEnabled(bool) {} func (noopDevice) SetStatisticsEnabled(bool) {}
func (noopDevice) ExtractStatistics() map[flowtrack.Tuple]tunstats.Counts { return nil } func (noopDevice) ExtractStatistics() map[netlogtype.Connection]netlogtype.Counts { return nil }
// Message is the log message that captures network traffic.
type Message struct {
Start time.Time `json:"start"` // inclusive
End time.Time `json:"end"` // inclusive
VirtualTraffic []TupleCounts `json:"virtualTraffic,omitempty"`
SubnetTraffic []TupleCounts `json:"subnetTraffic,omitempty"`
ExitTraffic []TupleCounts `json:"exitTraffic,omitempty"`
PhysicalTraffic []TupleCounts `json:"physicalTraffic,omitempty"`
}
// TupleCounts is a flattened struct of both a connection and counts.
type TupleCounts struct {
flowtrack.Tuple
tunstats.Counts
}
// Logger logs statistics about every connection. // Logger logs statistics about every connection.
// At present, it only logs connections within a tailscale network. // At present, it only logs connections within a tailscale network.
@ -192,8 +175,8 @@ func (nl *Logger) Startup(nodeID, domainID logtail.PrivateID, tun, sock Device)
return nil return nil
} }
func recordStatistics(logger *logtail.Logger, start, end time.Time, tunStats, sockStats map[flowtrack.Tuple]tunstats.Counts, addrs map[netip.Addr]bool, prefixes map[netip.Prefix]bool) { func recordStatistics(logger *logtail.Logger, start, end time.Time, tunStats, sockStats map[netlogtype.Connection]netlogtype.Counts, addrs map[netip.Addr]bool, prefixes map[netip.Prefix]bool) {
m := Message{Start: start.UTC(), End: end.UTC()} m := netlogtype.Message{Start: start.UTC(), End: end.UTC()}
classifyAddr := func(a netip.Addr) (isTailscale, withinRoute bool) { classifyAddr := func(a netip.Addr) (isTailscale, withinRoute bool) {
// NOTE: There could be mis-classifications where an address is treated // NOTE: There could be mis-classifications where an address is treated
@ -214,23 +197,23 @@ func recordStatistics(logger *logtail.Logger, start, end time.Time, tunStats, so
dstIsTailscaleIP, dstWithinSubnet := classifyAddr(conn.Dst.Addr()) dstIsTailscaleIP, dstWithinSubnet := classifyAddr(conn.Dst.Addr())
switch { switch {
case srcIsTailscaleIP && dstIsTailscaleIP: case srcIsTailscaleIP && dstIsTailscaleIP:
m.VirtualTraffic = append(m.VirtualTraffic, TupleCounts{conn, cnts}) m.VirtualTraffic = append(m.VirtualTraffic, netlogtype.ConnectionCounts{Connection: conn, Counts: cnts})
case srcWithinSubnet || dstWithinSubnet: case srcWithinSubnet || dstWithinSubnet:
m.SubnetTraffic = append(m.SubnetTraffic, TupleCounts{conn, cnts}) m.SubnetTraffic = append(m.SubnetTraffic, netlogtype.ConnectionCounts{Connection: conn, Counts: cnts})
default: default:
const anonymize = true const anonymize = true
if anonymize { if anonymize {
if len(m.ExitTraffic) == 0 { if len(m.ExitTraffic) == 0 {
m.ExitTraffic = []TupleCounts{{}} m.ExitTraffic = []netlogtype.ConnectionCounts{{}}
} }
m.ExitTraffic[0].Counts = m.ExitTraffic[0].Counts.Add(cnts) m.ExitTraffic[0].Counts = m.ExitTraffic[0].Counts.Add(cnts)
} else { } else {
m.ExitTraffic = append(m.ExitTraffic, TupleCounts{conn, cnts}) m.ExitTraffic = append(m.ExitTraffic, netlogtype.ConnectionCounts{Connection: conn, Counts: cnts})
} }
} }
} }
for conn, cnts := range sockStats { for conn, cnts := range sockStats {
m.PhysicalTraffic = append(m.PhysicalTraffic, TupleCounts{conn, cnts}) m.PhysicalTraffic = append(m.PhysicalTraffic, netlogtype.ConnectionCounts{Connection: conn, Counts: cnts})
} }
if len(m.VirtualTraffic)+len(m.SubnetTraffic)+len(m.ExitTraffic)+len(m.PhysicalTraffic) > 0 { if len(m.VirtualTraffic)+len(m.SubnetTraffic)+len(m.ExitTraffic)+len(m.PhysicalTraffic) > 0 {

@ -11,9 +11,8 @@ import (
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"tailscale.com/logtail" "tailscale.com/logtail"
"tailscale.com/net/flowtrack"
"tailscale.com/net/tunstats"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/types/netlogtype"
"tailscale.com/util/must" "tailscale.com/util/must"
"tailscale.com/wgengine/router" "tailscale.com/wgengine/router"
) )
@ -42,7 +41,7 @@ func (d *fakeDevice) SetStatisticsEnabled(enable bool) {
} }
} }
func (fakeDevice) ExtractStatistics() map[flowtrack.Tuple]tunstats.Counts { func (fakeDevice) ExtractStatistics() map[netlogtype.Connection]netlogtype.Counts {
// TODO(dsnet): Add a test that verifies that statistics are correctly // TODO(dsnet): Add a test that verifies that statistics are correctly
// extracted from the device and uploaded. Unfortunately, // extracted from the device and uploaded. Unfortunately,
// we can't reliably run this test until we fix http://go/oss/5856. // we can't reliably run this test until we fix http://go/oss/5856.

Loading…
Cancel
Save