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 }