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/slices"
"tailscale.com/net/flowtrack"
"tailscale.com/net/tunstats"
"tailscale.com/types/netlogtype"
"tailscale.com/util/must"
"tailscale.com/wgengine/netlog"
)
var (
@ -62,7 +60,7 @@ func main() {
Logtail struct {
ID string `json:"id"`
} `json:"logtail"`
netlog.Message
netlogtype.Message
}
if err := dec.Decode(&msg); err != nil {
if err == io.EOF {
@ -77,16 +75,16 @@ func main() {
// 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]"}}
duration := msg.End.Sub(msg.Start)
addRows := func(heading string, traffic []netlog.TupleCounts) {
addRows := func(heading string, traffic []netlogtype.ConnectionCounts) {
if len(traffic) == 0 {
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
ny := y.TxPackets + y.TxBytes + y.RxPackets + y.RxBytes
return nx > ny
})
var sum tunstats.Counts
var sum netlogtype.Counts
for _, cc := range traffic {
sum = sum.Add(cc.Counts)
}
@ -97,7 +95,7 @@ func main() {
5: formatSI(float64(sum.RxPackets) / 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
}
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/tshttpproxy from tailscale.com/control/controlclient+
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/paths 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/key from tailscale.com/control/controlbase+
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/nettype from tailscale.com/wgengine/magicsock+
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/windows v0.5.3
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/wf v0.0.0-20220728202103-50d96caab2f6
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.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
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.20220404092545-59d7a2877f83/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
honnef.co/go/tools v0.4.0-0.dev.0.20220517111757-f4a2f64ce238 h1:8Vr1KP9OTjoKQSSeLefzibQgDV4s2ujJElKHqMi7nsA=
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 v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=

@ -22,7 +22,6 @@ import (
"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"tailscale.com/disco"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tunstats"
@ -31,6 +30,7 @@ import (
"tailscale.com/types/ipproto"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netlogtype"
"tailscale.com/util/clientmetric"
"tailscale.com/wgengine/filter"
)
@ -853,7 +853,7 @@ func (t *Wrapper) SetStatisticsEnabled(enable bool) {
// ExtractStatistics extracts and resets the counters for all active connections.
// 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()
}

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

@ -9,8 +9,8 @@ package tunstats
import (
"sync"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
"tailscale.com/types/netlogtype"
)
// Statistics maintains counters for every connection.
@ -18,36 +18,19 @@ import (
// The zero value is ready for use.
type Statistics struct {
mu sync.Mutex
m map[flowtrack.Tuple]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
m map[netlogtype.Connection]netlogtype.Counts
}
// UpdateTx updates the counters for a transmitted IP packet
// 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) {
s.update(b, false)
}
// 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 in flowtrack.Tuple.
// the source and destination in netlogtype.Connection.
func (s *Statistics) UpdateRx(b []byte) {
s.update(b, true)
}
@ -55,17 +38,17 @@ func (s *Statistics) UpdateRx(b []byte) {
func (s *Statistics) update(b []byte, receive bool) {
var p packet.Parsed
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 {
tuple.Src, tuple.Dst = tuple.Dst, tuple.Src
conn.Src, conn.Dst = conn.Dst, conn.Src
}
s.mu.Lock()
defer s.mu.Unlock()
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 {
cnts.RxPackets++
cnts.RxBytes += uint64(len(b))
@ -73,15 +56,15 @@ func (s *Statistics) update(b []byte, receive bool) {
cnts.TxPackets++
cnts.TxBytes += uint64(len(b))
}
s.m[tuple] = cnts
s.m[conn] = cnts
}
// Extract extracts and resets the counters for all active connections.
// 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()
defer s.mu.Unlock()
m := s.m
s.m = make(map[flowtrack.Tuple]Counts)
s.m = make(map[netlogtype.Connection]netlogtype.Counts)
return m
}

@ -15,8 +15,8 @@ import (
"time"
qt "github.com/frankban/quicktest"
"tailscale.com/net/flowtrack"
"tailscale.com/types/ipproto"
"tailscale.com/types/netlogtype"
)
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)
var stats Statistics
var wants []map[flowtrack.Tuple]Counts
gots := make([]map[flowtrack.Tuple]Counts, runtime.NumCPU())
var wants []map[netlogtype.Connection]netlogtype.Counts
gots := make([]map[netlogtype.Connection]netlogtype.Counts, runtime.NumCPU())
var group sync.WaitGroup
for i := range gots {
group.Add(1)
go func(i int) {
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()))
var p []byte
var t flowtrack.Tuple
var t netlogtype.Connection
for j := 0; j < 1000; j++ {
delay := rn.Intn(10000)
if p == nil || rn.Intn(64) == 0 {
@ -72,7 +72,7 @@ func TestConcurrent(t *testing.T) {
dstPort := uint16(rand.Intn(16))
size := uint16(64 + rand.Intn(1024))
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
receive := rn.Intn(2) == 0
@ -102,17 +102,17 @@ func TestConcurrent(t *testing.T) {
group.Wait()
wants = append(wants, stats.Extract())
got := make(map[flowtrack.Tuple]Counts)
want := make(map[flowtrack.Tuple]Counts)
got := make(map[netlogtype.Connection]netlogtype.Counts)
want := make(map[netlogtype.Connection]netlogtype.Counts)
mergeMaps(got, gots...)
mergeMaps(want, wants...)
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 tuple, cnts := range src {
dst[tuple] = dst[tuple].Add(cnts)
for conn, cnts := range src {
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"
"tailscale.com/logpolicy"
"tailscale.com/logtail"
"tailscale.com/net/flowtrack"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tunstats"
"tailscale.com/smallzstd"
"tailscale.com/types/netlogtype"
"tailscale.com/wgengine/router"
)
@ -36,29 +35,13 @@ const pollPeriod = 5 * time.Second
// TODO(joetsai): Make *magicsock.Conn implement this interface.
type Device interface {
SetStatisticsEnabled(bool)
ExtractStatistics() map[flowtrack.Tuple]tunstats.Counts
ExtractStatistics() map[netlogtype.Connection]netlogtype.Counts
}
type noopDevice struct{}
func (noopDevice) SetStatisticsEnabled(bool) {}
func (noopDevice) ExtractStatistics() map[flowtrack.Tuple]tunstats.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
}
func (noopDevice) SetStatisticsEnabled(bool) {}
func (noopDevice) ExtractStatistics() map[netlogtype.Connection]netlogtype.Counts { return nil }
// Logger logs statistics about every connection.
// 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
}
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) {
m := Message{Start: start.UTC(), End: end.UTC()}
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 := netlogtype.Message{Start: start.UTC(), End: end.UTC()}
classifyAddr := func(a netip.Addr) (isTailscale, withinRoute bool) {
// 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())
switch {
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:
m.SubnetTraffic = append(m.SubnetTraffic, TupleCounts{conn, cnts})
m.SubnetTraffic = append(m.SubnetTraffic, netlogtype.ConnectionCounts{Connection: conn, Counts: cnts})
default:
const anonymize = true
if anonymize {
if len(m.ExitTraffic) == 0 {
m.ExitTraffic = []TupleCounts{{}}
m.ExitTraffic = []netlogtype.ConnectionCounts{{}}
}
m.ExitTraffic[0].Counts = m.ExitTraffic[0].Counts.Add(cnts)
} 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 {
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 {

@ -11,9 +11,8 @@ import (
qt "github.com/frankban/quicktest"
"tailscale.com/logtail"
"tailscale.com/net/flowtrack"
"tailscale.com/net/tunstats"
"tailscale.com/tstest"
"tailscale.com/types/netlogtype"
"tailscale.com/util/must"
"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
// extracted from the device and uploaded. Unfortunately,
// we can't reliably run this test until we fix http://go/oss/5856.

Loading…
Cancel
Save