github.com/sagernet/sing-box@v1.9.0-rc.20/experimental/clashapi/trafficontrol/tracker.go (about)

     1  package trafficontrol
     2  
     3  import (
     4  	"net"
     5  	"net/netip"
     6  	"time"
     7  
     8  	"github.com/sagernet/sing-box/adapter"
     9  	"github.com/sagernet/sing/common"
    10  	"github.com/sagernet/sing/common/atomic"
    11  	"github.com/sagernet/sing/common/bufio"
    12  	"github.com/sagernet/sing/common/json"
    13  	N "github.com/sagernet/sing/common/network"
    14  
    15  	"github.com/gofrs/uuid/v5"
    16  )
    17  
    18  type Metadata struct {
    19  	NetWork     string     `json:"network"`
    20  	Type        string     `json:"type"`
    21  	SrcIP       netip.Addr `json:"sourceIP"`
    22  	DstIP       netip.Addr `json:"destinationIP"`
    23  	SrcPort     string     `json:"sourcePort"`
    24  	DstPort     string     `json:"destinationPort"`
    25  	Host        string     `json:"host"`
    26  	DNSMode     string     `json:"dnsMode"`
    27  	ProcessPath string     `json:"processPath"`
    28  }
    29  
    30  type tracker interface {
    31  	ID() string
    32  	Close() error
    33  	Leave()
    34  }
    35  
    36  type trackerInfo struct {
    37  	UUID          uuid.UUID     `json:"id"`
    38  	Metadata      Metadata      `json:"metadata"`
    39  	UploadTotal   *atomic.Int64 `json:"upload"`
    40  	DownloadTotal *atomic.Int64 `json:"download"`
    41  	Start         time.Time     `json:"start"`
    42  	Chain         []string      `json:"chains"`
    43  	Rule          string        `json:"rule"`
    44  	RulePayload   string        `json:"rulePayload"`
    45  }
    46  
    47  func (t trackerInfo) MarshalJSON() ([]byte, error) {
    48  	return json.Marshal(map[string]any{
    49  		"id":          t.UUID.String(),
    50  		"metadata":    t.Metadata,
    51  		"upload":      t.UploadTotal.Load(),
    52  		"download":    t.DownloadTotal.Load(),
    53  		"start":       t.Start,
    54  		"chains":      t.Chain,
    55  		"rule":        t.Rule,
    56  		"rulePayload": t.RulePayload,
    57  	})
    58  }
    59  
    60  type tcpTracker struct {
    61  	N.ExtendedConn `json:"-"`
    62  	*trackerInfo
    63  	manager *Manager
    64  }
    65  
    66  func (tt *tcpTracker) ID() string {
    67  	return tt.UUID.String()
    68  }
    69  
    70  func (tt *tcpTracker) Close() error {
    71  	tt.manager.Leave(tt)
    72  	return tt.ExtendedConn.Close()
    73  }
    74  
    75  func (tt *tcpTracker) Leave() {
    76  	tt.manager.Leave(tt)
    77  }
    78  
    79  func (tt *tcpTracker) Upstream() any {
    80  	return tt.ExtendedConn
    81  }
    82  
    83  func (tt *tcpTracker) ReaderReplaceable() bool {
    84  	return true
    85  }
    86  
    87  func (tt *tcpTracker) WriterReplaceable() bool {
    88  	return true
    89  }
    90  
    91  func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker {
    92  	uuid, _ := uuid.NewV4()
    93  
    94  	var chain []string
    95  	var next string
    96  	if rule == nil {
    97  		if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
    98  			next = defaultOutbound.Tag()
    99  		}
   100  	} else {
   101  		next = rule.Outbound()
   102  	}
   103  	for {
   104  		chain = append(chain, next)
   105  		detour, loaded := router.Outbound(next)
   106  		if !loaded {
   107  			break
   108  		}
   109  		group, isGroup := detour.(adapter.OutboundGroup)
   110  		if !isGroup {
   111  			break
   112  		}
   113  		next = group.Now()
   114  	}
   115  
   116  	upload := new(atomic.Int64)
   117  	download := new(atomic.Int64)
   118  
   119  	t := &tcpTracker{
   120  		ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
   121  			upload.Add(n)
   122  			manager.PushUploaded(n)
   123  		}}, []N.CountFunc{func(n int64) {
   124  			download.Add(n)
   125  			manager.PushDownloaded(n)
   126  		}}),
   127  		manager: manager,
   128  		trackerInfo: &trackerInfo{
   129  			UUID:          uuid,
   130  			Start:         time.Now(),
   131  			Metadata:      metadata,
   132  			Chain:         common.Reverse(chain),
   133  			Rule:          "",
   134  			UploadTotal:   upload,
   135  			DownloadTotal: download,
   136  		},
   137  	}
   138  
   139  	if rule != nil {
   140  		t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
   141  	} else {
   142  		t.trackerInfo.Rule = "final"
   143  	}
   144  
   145  	manager.Join(t)
   146  	return t
   147  }
   148  
   149  type udpTracker struct {
   150  	N.PacketConn `json:"-"`
   151  	*trackerInfo
   152  	manager *Manager
   153  }
   154  
   155  func (ut *udpTracker) ID() string {
   156  	return ut.UUID.String()
   157  }
   158  
   159  func (ut *udpTracker) Close() error {
   160  	ut.manager.Leave(ut)
   161  	return ut.PacketConn.Close()
   162  }
   163  
   164  func (ut *udpTracker) Leave() {
   165  	ut.manager.Leave(ut)
   166  }
   167  
   168  func (ut *udpTracker) Upstream() any {
   169  	return ut.PacketConn
   170  }
   171  
   172  func (ut *udpTracker) ReaderReplaceable() bool {
   173  	return true
   174  }
   175  
   176  func (ut *udpTracker) WriterReplaceable() bool {
   177  	return true
   178  }
   179  
   180  func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker {
   181  	uuid, _ := uuid.NewV4()
   182  
   183  	var chain []string
   184  	var next string
   185  	if rule == nil {
   186  		if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
   187  			next = defaultOutbound.Tag()
   188  		}
   189  	} else {
   190  		next = rule.Outbound()
   191  	}
   192  	for {
   193  		chain = append(chain, next)
   194  		detour, loaded := router.Outbound(next)
   195  		if !loaded {
   196  			break
   197  		}
   198  		group, isGroup := detour.(adapter.OutboundGroup)
   199  		if !isGroup {
   200  			break
   201  		}
   202  		next = group.Now()
   203  	}
   204  
   205  	upload := new(atomic.Int64)
   206  	download := new(atomic.Int64)
   207  
   208  	ut := &udpTracker{
   209  		PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
   210  			upload.Add(n)
   211  			manager.PushUploaded(n)
   212  		}}, []N.CountFunc{func(n int64) {
   213  			download.Add(n)
   214  			manager.PushDownloaded(n)
   215  		}}),
   216  		manager: manager,
   217  		trackerInfo: &trackerInfo{
   218  			UUID:          uuid,
   219  			Start:         time.Now(),
   220  			Metadata:      metadata,
   221  			Chain:         common.Reverse(chain),
   222  			Rule:          "",
   223  			UploadTotal:   upload,
   224  			DownloadTotal: download,
   225  		},
   226  	}
   227  
   228  	if rule != nil {
   229  		ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
   230  	} else {
   231  		ut.trackerInfo.Rule = "final"
   232  	}
   233  
   234  	manager.Join(ut)
   235  	return ut
   236  }