|
@@ -2,97 +2,135 @@ package trafficontrol
|
|
|
|
|
|
import (
|
|
|
"net"
|
|
|
- "net/netip"
|
|
|
"time"
|
|
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
|
"github.com/sagernet/sing/common"
|
|
|
"github.com/sagernet/sing/common/atomic"
|
|
|
"github.com/sagernet/sing/common/bufio"
|
|
|
+ F "github.com/sagernet/sing/common/format"
|
|
|
"github.com/sagernet/sing/common/json"
|
|
|
N "github.com/sagernet/sing/common/network"
|
|
|
|
|
|
"github.com/gofrs/uuid/v5"
|
|
|
)
|
|
|
|
|
|
-type Metadata struct {
|
|
|
- NetWork string `json:"network"`
|
|
|
- Type string `json:"type"`
|
|
|
- SrcIP netip.Addr `json:"sourceIP"`
|
|
|
- DstIP netip.Addr `json:"destinationIP"`
|
|
|
- SrcPort string `json:"sourcePort"`
|
|
|
- DstPort string `json:"destinationPort"`
|
|
|
- Host string `json:"host"`
|
|
|
- DNSMode string `json:"dnsMode"`
|
|
|
- ProcessPath string `json:"processPath"`
|
|
|
+type TrackerMetadata struct {
|
|
|
+ ID uuid.UUID
|
|
|
+ Metadata adapter.InboundContext
|
|
|
+ CreatedAt time.Time
|
|
|
+ ClosedAt time.Time
|
|
|
+ Upload *atomic.Int64
|
|
|
+ Download *atomic.Int64
|
|
|
+ Chain []string
|
|
|
+ Rule adapter.Rule
|
|
|
+ Outbound string
|
|
|
+ OutboundType string
|
|
|
}
|
|
|
|
|
|
-type tracker interface {
|
|
|
- ID() string
|
|
|
- Close() error
|
|
|
- Leave()
|
|
|
-}
|
|
|
-
|
|
|
-type trackerInfo struct {
|
|
|
- UUID uuid.UUID `json:"id"`
|
|
|
- Metadata Metadata `json:"metadata"`
|
|
|
- UploadTotal *atomic.Int64 `json:"upload"`
|
|
|
- DownloadTotal *atomic.Int64 `json:"download"`
|
|
|
- Start time.Time `json:"start"`
|
|
|
- Chain []string `json:"chains"`
|
|
|
- Rule string `json:"rule"`
|
|
|
- RulePayload string `json:"rulePayload"`
|
|
|
-}
|
|
|
-
|
|
|
-func (t trackerInfo) MarshalJSON() ([]byte, error) {
|
|
|
+func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
|
|
+ var inbound string
|
|
|
+ if t.Metadata.Inbound != "" {
|
|
|
+ inbound = t.Metadata.InboundType + "/" + t.Metadata.Inbound
|
|
|
+ } else {
|
|
|
+ inbound = t.Metadata.InboundType
|
|
|
+ }
|
|
|
+ var domain string
|
|
|
+ if t.Metadata.Domain != "" {
|
|
|
+ domain = t.Metadata.Domain
|
|
|
+ } else {
|
|
|
+ domain = t.Metadata.Destination.Fqdn
|
|
|
+ }
|
|
|
+ var processPath string
|
|
|
+ if t.Metadata.ProcessInfo != nil {
|
|
|
+ if t.Metadata.ProcessInfo.ProcessPath != "" {
|
|
|
+ processPath = t.Metadata.ProcessInfo.ProcessPath
|
|
|
+ } else if t.Metadata.ProcessInfo.PackageName != "" {
|
|
|
+ processPath = t.Metadata.ProcessInfo.PackageName
|
|
|
+ }
|
|
|
+ if processPath == "" {
|
|
|
+ if t.Metadata.ProcessInfo.UserId != -1 {
|
|
|
+ processPath = F.ToString(t.Metadata.ProcessInfo.UserId)
|
|
|
+ }
|
|
|
+ } else if t.Metadata.ProcessInfo.User != "" {
|
|
|
+ processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.User, ")")
|
|
|
+ } else if t.Metadata.ProcessInfo.UserId != -1 {
|
|
|
+ processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var rule string
|
|
|
+ if t.Rule != nil {
|
|
|
+ rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
|
|
|
+ } else {
|
|
|
+ rule = "final"
|
|
|
+ }
|
|
|
return json.Marshal(map[string]any{
|
|
|
- "id": t.UUID.String(),
|
|
|
- "metadata": t.Metadata,
|
|
|
- "upload": t.UploadTotal.Load(),
|
|
|
- "download": t.DownloadTotal.Load(),
|
|
|
- "start": t.Start,
|
|
|
+ "id": t.ID,
|
|
|
+ "metadata": map[string]any{
|
|
|
+ "network": t.Metadata.Network,
|
|
|
+ "type": inbound,
|
|
|
+ "sourceIP": t.Metadata.Source.Addr,
|
|
|
+ "destinationIP": t.Metadata.Destination.Addr,
|
|
|
+ "sourcePort": F.ToString(t.Metadata.Source.Port),
|
|
|
+ "destinationPort": F.ToString(t.Metadata.Destination.Port),
|
|
|
+ "host": domain,
|
|
|
+ "dnsMode": "normal",
|
|
|
+ "processPath": processPath,
|
|
|
+ },
|
|
|
+ "upload": t.Upload.Load(),
|
|
|
+ "download": t.Download.Load(),
|
|
|
+ "start": t.CreatedAt,
|
|
|
"chains": t.Chain,
|
|
|
- "rule": t.Rule,
|
|
|
- "rulePayload": t.RulePayload,
|
|
|
+ "rule": rule,
|
|
|
+ "rulePayload": "",
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-type tcpTracker struct {
|
|
|
- N.ExtendedConn `json:"-"`
|
|
|
- *trackerInfo
|
|
|
- manager *Manager
|
|
|
+type Tracker interface {
|
|
|
+ adapter.Tracker
|
|
|
+ Metadata() TrackerMetadata
|
|
|
+ Close() error
|
|
|
+}
|
|
|
+
|
|
|
+type TCPConn struct {
|
|
|
+ N.ExtendedConn
|
|
|
+ metadata TrackerMetadata
|
|
|
+ manager *Manager
|
|
|
}
|
|
|
|
|
|
-func (tt *tcpTracker) ID() string {
|
|
|
- return tt.UUID.String()
|
|
|
+func (tt *TCPConn) Metadata() TrackerMetadata {
|
|
|
+ return tt.metadata
|
|
|
}
|
|
|
|
|
|
-func (tt *tcpTracker) Close() error {
|
|
|
+func (tt *TCPConn) Close() error {
|
|
|
tt.manager.Leave(tt)
|
|
|
return tt.ExtendedConn.Close()
|
|
|
}
|
|
|
|
|
|
-func (tt *tcpTracker) Leave() {
|
|
|
+func (tt *TCPConn) Leave() {
|
|
|
tt.manager.Leave(tt)
|
|
|
}
|
|
|
|
|
|
-func (tt *tcpTracker) Upstream() any {
|
|
|
+func (tt *TCPConn) Upstream() any {
|
|
|
return tt.ExtendedConn
|
|
|
}
|
|
|
|
|
|
-func (tt *tcpTracker) ReaderReplaceable() bool {
|
|
|
+func (tt *TCPConn) ReaderReplaceable() bool {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
-func (tt *tcpTracker) WriterReplaceable() bool {
|
|
|
+func (tt *TCPConn) WriterReplaceable() bool {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
-func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker {
|
|
|
- uuid, _ := uuid.NewV4()
|
|
|
-
|
|
|
- var chain []string
|
|
|
- var next string
|
|
|
+func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn {
|
|
|
+ id, _ := uuid.NewV4()
|
|
|
+ var (
|
|
|
+ chain []string
|
|
|
+ next string
|
|
|
+ outbound string
|
|
|
+ outboundType string
|
|
|
+ )
|
|
|
if rule == nil {
|
|
|
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
|
|
next = defaultOutbound.Tag()
|
|
@@ -106,17 +144,17 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
|
|
if !loaded {
|
|
|
break
|
|
|
}
|
|
|
+ outbound = detour.Tag()
|
|
|
+ outboundType = detour.Type()
|
|
|
group, isGroup := detour.(adapter.OutboundGroup)
|
|
|
if !isGroup {
|
|
|
break
|
|
|
}
|
|
|
next = group.Now()
|
|
|
}
|
|
|
-
|
|
|
upload := new(atomic.Int64)
|
|
|
download := new(atomic.Int64)
|
|
|
-
|
|
|
- t := &tcpTracker{
|
|
|
+ tracker := &TCPConn{
|
|
|
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
|
|
upload.Add(n)
|
|
|
manager.PushUploaded(n)
|
|
@@ -124,64 +162,62 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
|
|
download.Add(n)
|
|
|
manager.PushDownloaded(n)
|
|
|
}}),
|
|
|
- manager: manager,
|
|
|
- trackerInfo: &trackerInfo{
|
|
|
- UUID: uuid,
|
|
|
- Start: time.Now(),
|
|
|
- Metadata: metadata,
|
|
|
- Chain: common.Reverse(chain),
|
|
|
- Rule: "",
|
|
|
- UploadTotal: upload,
|
|
|
- DownloadTotal: download,
|
|
|
+ metadata: TrackerMetadata{
|
|
|
+ ID: id,
|
|
|
+ Metadata: metadata,
|
|
|
+ CreatedAt: time.Now(),
|
|
|
+ Upload: upload,
|
|
|
+ Download: download,
|
|
|
+ Chain: common.Reverse(chain),
|
|
|
+ Rule: rule,
|
|
|
+ Outbound: outbound,
|
|
|
+ OutboundType: outboundType,
|
|
|
},
|
|
|
+ manager: manager,
|
|
|
}
|
|
|
-
|
|
|
- if rule != nil {
|
|
|
- t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
|
|
- } else {
|
|
|
- t.trackerInfo.Rule = "final"
|
|
|
- }
|
|
|
-
|
|
|
- manager.Join(t)
|
|
|
- return t
|
|
|
+ manager.Join(tracker)
|
|
|
+ return tracker
|
|
|
}
|
|
|
|
|
|
-type udpTracker struct {
|
|
|
+type UDPConn struct {
|
|
|
N.PacketConn `json:"-"`
|
|
|
- *trackerInfo
|
|
|
- manager *Manager
|
|
|
+ metadata TrackerMetadata
|
|
|
+ manager *Manager
|
|
|
}
|
|
|
|
|
|
-func (ut *udpTracker) ID() string {
|
|
|
- return ut.UUID.String()
|
|
|
+func (ut *UDPConn) Metadata() TrackerMetadata {
|
|
|
+ return ut.metadata
|
|
|
}
|
|
|
|
|
|
-func (ut *udpTracker) Close() error {
|
|
|
+func (ut *UDPConn) Close() error {
|
|
|
ut.manager.Leave(ut)
|
|
|
return ut.PacketConn.Close()
|
|
|
}
|
|
|
|
|
|
-func (ut *udpTracker) Leave() {
|
|
|
+func (ut *UDPConn) Leave() {
|
|
|
ut.manager.Leave(ut)
|
|
|
}
|
|
|
|
|
|
-func (ut *udpTracker) Upstream() any {
|
|
|
+func (ut *UDPConn) Upstream() any {
|
|
|
return ut.PacketConn
|
|
|
}
|
|
|
|
|
|
-func (ut *udpTracker) ReaderReplaceable() bool {
|
|
|
+func (ut *UDPConn) ReaderReplaceable() bool {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
-func (ut *udpTracker) WriterReplaceable() bool {
|
|
|
+func (ut *UDPConn) WriterReplaceable() bool {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
-func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker {
|
|
|
- uuid, _ := uuid.NewV4()
|
|
|
-
|
|
|
- var chain []string
|
|
|
- var next string
|
|
|
+func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn {
|
|
|
+ id, _ := uuid.NewV4()
|
|
|
+ var (
|
|
|
+ chain []string
|
|
|
+ next string
|
|
|
+ outbound string
|
|
|
+ outboundType string
|
|
|
+ )
|
|
|
if rule == nil {
|
|
|
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
|
|
next = defaultOutbound.Tag()
|
|
@@ -195,17 +231,17 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
|
|
if !loaded {
|
|
|
break
|
|
|
}
|
|
|
+ outbound = detour.Tag()
|
|
|
+ outboundType = detour.Type()
|
|
|
group, isGroup := detour.(adapter.OutboundGroup)
|
|
|
if !isGroup {
|
|
|
break
|
|
|
}
|
|
|
next = group.Now()
|
|
|
}
|
|
|
-
|
|
|
upload := new(atomic.Int64)
|
|
|
download := new(atomic.Int64)
|
|
|
-
|
|
|
- ut := &udpTracker{
|
|
|
+ trackerConn := &UDPConn{
|
|
|
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
|
|
|
upload.Add(n)
|
|
|
manager.PushUploaded(n)
|
|
@@ -213,24 +249,19 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
|
|
download.Add(n)
|
|
|
manager.PushDownloaded(n)
|
|
|
}}),
|
|
|
- manager: manager,
|
|
|
- trackerInfo: &trackerInfo{
|
|
|
- UUID: uuid,
|
|
|
- Start: time.Now(),
|
|
|
- Metadata: metadata,
|
|
|
- Chain: common.Reverse(chain),
|
|
|
- Rule: "",
|
|
|
- UploadTotal: upload,
|
|
|
- DownloadTotal: download,
|
|
|
+ metadata: TrackerMetadata{
|
|
|
+ ID: id,
|
|
|
+ Metadata: metadata,
|
|
|
+ CreatedAt: time.Now(),
|
|
|
+ Upload: upload,
|
|
|
+ Download: download,
|
|
|
+ Chain: common.Reverse(chain),
|
|
|
+ Rule: rule,
|
|
|
+ Outbound: outbound,
|
|
|
+ OutboundType: outboundType,
|
|
|
},
|
|
|
+ manager: manager,
|
|
|
}
|
|
|
-
|
|
|
- if rule != nil {
|
|
|
- ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
|
|
- } else {
|
|
|
- ut.trackerInfo.Rule = "final"
|
|
|
- }
|
|
|
-
|
|
|
- manager.Join(ut)
|
|
|
- return ut
|
|
|
+ manager.Join(trackerConn)
|
|
|
+ return trackerConn
|
|
|
}
|