@ -52,6 +52,7 @@ import (
"tailscale.com/tstime/mono"
"tailscale.com/tstime/mono"
"tailscale.com/types/key"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/logger"
"tailscale.com/types/netlogtype"
"tailscale.com/types/netmap"
"tailscale.com/types/netmap"
"tailscale.com/types/nettype"
"tailscale.com/types/nettype"
"tailscale.com/util/clientmetric"
"tailscale.com/util/clientmetric"
@ -337,6 +338,21 @@ type Conn struct {
// port is the preferred port from opts.Port; 0 means auto.
// port is the preferred port from opts.Port; 0 means auto.
port atomic . Uint32
port atomic . Uint32
// stats maintains per-connection counters.
// See SetStatisticsEnabled and ExtractStatistics for details.
stats struct {
enabled atomic . Bool
// TODO(joetsai): A per-Conn map of connections is easiest to implement.
// Since every packet occurs within the context of an endpoint,
// we could track the counts within the endpoint itself,
// and then merge the results when ExtractStatistics is called.
// That would avoid a map lookup for every packet.
mu sync . Mutex
m map [ netlogtype . Connection ] netlogtype . Counts
}
// ============================================================
// ============================================================
// mu guards all following fields; see userspaceEngine lock
// mu guards all following fields; see userspaceEngine lock
// ordering rules against the engine. For derphttp, mu must
// ordering rules against the engine. For derphttp, mu must
@ -1750,6 +1766,9 @@ func (c *Conn) receiveIP(b []byte, ipp netip.AddrPort, cache *ippEndpointCache,
ep = de
ep = de
}
}
ep . noteRecvActivity ( )
ep . noteRecvActivity ( )
if c . stats . enabled . Load ( ) {
c . updateStats ( ep . nodeAddr , ipp , netlogtype . Counts { RxPackets : 1 , RxBytes : uint64 ( len ( b ) ) } )
}
return ep , true
return ep , true
}
}
@ -1805,6 +1824,9 @@ func (c *Conn) processDERPReadResult(dm derpReadResult, b []byte) (n int, ep *en
}
}
ep . noteRecvActivity ( )
ep . noteRecvActivity ( )
if c . stats . enabled . Load ( ) {
c . updateStats ( ep . nodeAddr , ipp , netlogtype . Counts { RxPackets : 1 , RxBytes : uint64 ( dm . n ) } )
}
return n , ep
return n , ep
}
}
@ -2406,6 +2428,9 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
endpointState : map [ netip . AddrPort ] * endpointState { } ,
endpointState : map [ netip . AddrPort ] * endpointState { } ,
heartbeatDisabled : heartbeatDisabled ,
heartbeatDisabled : heartbeatDisabled ,
}
}
if len ( n . Addresses ) > 0 {
ep . nodeAddr = n . Addresses [ 0 ] . Addr ( )
}
if ! n . DiscoKey . IsZero ( ) {
if ! n . DiscoKey . IsZero ( ) {
ep . discoKey = n . DiscoKey
ep . discoKey = n . DiscoKey
ep . discoShort = n . DiscoKey . ShortString ( )
ep . discoShort = n . DiscoKey . ShortString ( )
@ -3298,6 +3323,39 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
} )
} )
}
}
// updateStats updates the statistics counters with the src, dst, and cnts.
// It is the caller's responsibility to check whether logging is enabled.
func ( c * Conn ) updateStats ( src netip . Addr , dst netip . AddrPort , cnts netlogtype . Counts ) {
conn := netlogtype . Connection { Src : netip . AddrPortFrom ( src , 0 ) , Dst : dst }
c . stats . mu . Lock ( )
defer c . stats . mu . Unlock ( )
mak . Set ( & c . stats . m , conn , c . stats . m [ conn ] . Add ( cnts ) )
}
// SetStatisticsEnabled enables per-connection packet counters.
// Disabling statistics gathering does not reset the counters.
// ExtractStatistics must be called to reset the counters and
// be periodically called while enabled to avoid unbounded memory use.
func ( c * Conn ) SetStatisticsEnabled ( enable bool ) {
c . stats . enabled . Store ( enable )
}
// ExtractStatistics extracts and resets the counters for all active connections.
// It must be called periodically otherwise the memory used is unbounded.
//
// The source is always a peer's tailscale IP address,
// while the destination is the peer's physical IP address and port.
// As a special case, packets routed through DERP use a destination address
// of 127.3.3.40 with the port being the DERP region.
// This node's tailscale IP address never appears in the returned map.
func ( c * Conn ) ExtractStatistics ( ) map [ netlogtype . Connection ] netlogtype . Counts {
c . stats . mu . Lock ( )
defer c . stats . mu . Unlock ( )
m := c . stats . m
c . stats . m = nil
return m
}
func ippDebugString ( ua netip . AddrPort ) string {
func ippDebugString ( ua netip . AddrPort ) string {
if ua . Addr ( ) == derpMagicIPAddr {
if ua . Addr ( ) == derpMagicIPAddr {
return fmt . Sprintf ( "derp-%d" , ua . Port ( ) )
return fmt . Sprintf ( "derp-%d" , ua . Port ( ) )
@ -3332,6 +3390,7 @@ type endpoint struct {
publicKey key . NodePublic // peer public key (for WireGuard + DERP)
publicKey key . NodePublic // peer public key (for WireGuard + DERP)
publicKeyHex string // cached output of publicKey.UntypedHexString
publicKeyHex string // cached output of publicKey.UntypedHexString
fakeWGAddr netip . AddrPort // the UDP address we tell wireguard-go we're using
fakeWGAddr netip . AddrPort // the UDP address we tell wireguard-go we're using
nodeAddr netip . Addr // the node's first tailscale address (only used for logging)
// mu protects all following fields.
// mu protects all following fields.
mu sync . Mutex // Lock ordering: Conn.mu, then endpoint.mu
mu sync . Mutex // Lock ordering: Conn.mu, then endpoint.mu
@ -3676,11 +3735,19 @@ func (de *endpoint) send(b []byte) error {
var err error
var err error
if udpAddr . IsValid ( ) {
if udpAddr . IsValid ( ) {
_ , err = de . c . sendAddr ( udpAddr , de . publicKey , b )
_ , err = de . c . sendAddr ( udpAddr , de . publicKey , b )
if err == nil && de . c . stats . enabled . Load ( ) {
de . c . updateStats ( de . nodeAddr , udpAddr , netlogtype . Counts { TxPackets : 1 , TxBytes : uint64 ( len ( b ) ) } )
}
}
}
if derpAddr . IsValid ( ) {
if derpAddr . IsValid ( ) {
if ok , _ := de . c . sendAddr ( derpAddr , de . publicKey , b ) ; ok && err != nil {
if ok , _ := de . c . sendAddr ( derpAddr , de . publicKey , b ) ; ok {
// UDP failed but DERP worked, so good enough:
if de . c . stats . enabled . Load ( ) {
return nil
de . c . updateStats ( de . nodeAddr , derpAddr , netlogtype . Counts { TxPackets : 1 , TxBytes : uint64 ( len ( b ) ) } )
}
if err != nil {
// UDP failed but DERP worked, so good enough:
return nil
}
}
}
}
}
return err
return err