github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/experimental/clashapi/trafficontrol/tracker.go (about)

     1  package trafficontrol
     2  
     3  import (
     4  	"encoding/json"
     5  	"net"
     6  	"net/netip"
     7  	"time"
     8  
     9  	"github.com/inazumav/sing-box/adapter"
    10  	"github.com/sagernet/sing/common"
    11  	"github.com/sagernet/sing/common/atomic"
    12  	"github.com/sagernet/sing/common/bufio"
    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  		next = router.DefaultOutbound(N.NetworkTCP).Tag()
    98  	} else {
    99  		next = rule.Outbound()
   100  	}
   101  	for {
   102  		chain = append(chain, next)
   103  		detour, loaded := router.Outbound(next)
   104  		if !loaded {
   105  			break
   106  		}
   107  		group, isGroup := detour.(adapter.OutboundGroup)
   108  		if !isGroup {
   109  			break
   110  		}
   111  		next = group.Now()
   112  	}
   113  
   114  	upload := new(atomic.Int64)
   115  	download := new(atomic.Int64)
   116  
   117  	t := &tcpTracker{
   118  		ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
   119  			upload.Add(n)
   120  			manager.PushUploaded(n)
   121  		}}, []N.CountFunc{func(n int64) {
   122  			download.Add(n)
   123  			manager.PushDownloaded(n)
   124  		}}),
   125  		manager: manager,
   126  		trackerInfo: &trackerInfo{
   127  			UUID:          uuid,
   128  			Start:         time.Now(),
   129  			Metadata:      metadata,
   130  			Chain:         common.Reverse(chain),
   131  			Rule:          "",
   132  			UploadTotal:   upload,
   133  			DownloadTotal: download,
   134  		},
   135  	}
   136  
   137  	if rule != nil {
   138  		t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
   139  	} else {
   140  		t.trackerInfo.Rule = "final"
   141  	}
   142  
   143  	manager.Join(t)
   144  	return t
   145  }
   146  
   147  type udpTracker struct {
   148  	N.PacketConn `json:"-"`
   149  	*trackerInfo
   150  	manager *Manager
   151  }
   152  
   153  func (ut *udpTracker) ID() string {
   154  	return ut.UUID.String()
   155  }
   156  
   157  func (ut *udpTracker) Close() error {
   158  	ut.manager.Leave(ut)
   159  	return ut.PacketConn.Close()
   160  }
   161  
   162  func (ut *udpTracker) Leave() {
   163  	ut.manager.Leave(ut)
   164  }
   165  
   166  func (ut *udpTracker) Upstream() any {
   167  	return ut.PacketConn
   168  }
   169  
   170  func (ut *udpTracker) ReaderReplaceable() bool {
   171  	return true
   172  }
   173  
   174  func (ut *udpTracker) WriterReplaceable() bool {
   175  	return true
   176  }
   177  
   178  func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker {
   179  	uuid, _ := uuid.NewV4()
   180  
   181  	var chain []string
   182  	var next string
   183  	if rule == nil {
   184  		next = router.DefaultOutbound(N.NetworkUDP).Tag()
   185  	} else {
   186  		next = rule.Outbound()
   187  	}
   188  	for {
   189  		chain = append(chain, next)
   190  		detour, loaded := router.Outbound(next)
   191  		if !loaded {
   192  			break
   193  		}
   194  		group, isGroup := detour.(adapter.OutboundGroup)
   195  		if !isGroup {
   196  			break
   197  		}
   198  		next = group.Now()
   199  	}
   200  
   201  	upload := new(atomic.Int64)
   202  	download := new(atomic.Int64)
   203  
   204  	ut := &udpTracker{
   205  		PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
   206  			upload.Add(n)
   207  			manager.PushUploaded(n)
   208  		}}, []N.CountFunc{func(n int64) {
   209  			download.Add(n)
   210  			manager.PushDownloaded(n)
   211  		}}),
   212  		manager: manager,
   213  		trackerInfo: &trackerInfo{
   214  			UUID:          uuid,
   215  			Start:         time.Now(),
   216  			Metadata:      metadata,
   217  			Chain:         common.Reverse(chain),
   218  			Rule:          "",
   219  			UploadTotal:   upload,
   220  			DownloadTotal: download,
   221  		},
   222  	}
   223  
   224  	if rule != nil {
   225  		ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
   226  	} else {
   227  		ut.trackerInfo.Rule = "final"
   228  	}
   229  
   230  	manager.Join(ut)
   231  	return ut
   232  }