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 }