platform: Prepare connections list

This commit is contained in:
世界
2024-06-11 21:16:33 +08:00
parent e02f945e44
commit 8983e1c2a8
18 changed files with 651 additions and 254 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/gofrs/uuid/v5"
)
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
@@ -76,10 +77,10 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW
func closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
id := uuid.FromStringOrNil(chi.URLParam(r, "id"))
snapshot := trafficManager.Snapshot()
for _, c := range snapshot.Connections {
if id == c.ID() {
if id == c.Metadata().ID {
c.Close()
break
}

View File

@@ -19,7 +19,6 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
@@ -218,58 +217,15 @@ func (s *Server) TrafficManager() *trafficontrol.Manager {
}
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
return tracker, tracker
}
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
return tracker, tracker
}
func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
var inbound string
if metadata.Inbound != "" {
inbound = metadata.InboundType + "/" + metadata.Inbound
} else {
inbound = metadata.InboundType
}
var domain string
if metadata.Domain != "" {
domain = metadata.Domain
} else {
domain = metadata.Destination.Fqdn
}
var processPath string
if metadata.ProcessInfo != nil {
if metadata.ProcessInfo.ProcessPath != "" {
processPath = metadata.ProcessInfo.ProcessPath
} else if metadata.ProcessInfo.PackageName != "" {
processPath = metadata.ProcessInfo.PackageName
}
if processPath == "" {
if metadata.ProcessInfo.UserId != -1 {
processPath = F.ToString(metadata.ProcessInfo.UserId)
}
} else if metadata.ProcessInfo.User != "" {
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.User, ")")
} else if metadata.ProcessInfo.UserId != -1 {
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.UserId, ")")
}
}
return trafficontrol.Metadata{
NetWork: metadata.Network,
Type: inbound,
SrcIP: metadata.Source.Addr,
DstIP: metadata.Destination.Addr,
SrcPort: F.ToString(metadata.Source.Port),
DstPort: F.ToString(metadata.Destination.Port),
Host: domain,
DNSMode: "normal",
ProcessPath: processPath,
}
}
func authentication(serverSecret string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {

View File

@@ -2,10 +2,17 @@ package trafficontrol
import (
"runtime"
"sync"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/x/list"
"github.com/gofrs/uuid/v5"
)
type Manager struct {
@@ -16,9 +23,11 @@ type Manager struct {
uploadTotal atomic.Int64
downloadTotal atomic.Int64
connections compatible.Map[string, tracker]
ticker *time.Ticker
done chan struct{}
connections compatible.Map[uuid.UUID, Tracker]
closedConnectionsAccess sync.Mutex
closedConnections list.List[TrackerMetadata]
ticker *time.Ticker
done chan struct{}
// process *process.Process
memory uint64
}
@@ -33,12 +42,22 @@ func NewManager() *Manager {
return manager
}
func (m *Manager) Join(c tracker) {
m.connections.Store(c.ID(), c)
func (m *Manager) Join(c Tracker) {
m.connections.Store(c.Metadata().ID, c)
}
func (m *Manager) Leave(c tracker) {
m.connections.Delete(c.ID())
func (m *Manager) Leave(c Tracker) {
metadata := c.Metadata()
_, loaded := m.connections.LoadAndDelete(metadata.ID)
if loaded {
metadata.ClosedAt = time.Now()
m.closedConnectionsAccess.Lock()
defer m.closedConnectionsAccess.Unlock()
if m.closedConnections.Len() >= 1000 {
m.closedConnections.PopFront()
}
m.closedConnections.PushBack(metadata)
}
}
func (m *Manager) PushUploaded(size int64) {
@@ -59,14 +78,39 @@ func (m *Manager) Total() (up int64, down int64) {
return m.uploadTotal.Load(), m.downloadTotal.Load()
}
func (m *Manager) Connections() int {
func (m *Manager) ConnectionsLen() int {
return m.connections.Len()
}
func (m *Manager) Connections() []TrackerMetadata {
var connections []TrackerMetadata
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
connections = append(connections, value.Metadata())
return true
})
return connections
}
func (m *Manager) ClosedConnections() []TrackerMetadata {
m.closedConnectionsAccess.Lock()
defer m.closedConnectionsAccess.Unlock()
return m.closedConnections.Array()
}
func (m *Manager) Connection(id uuid.UUID) Tracker {
connection, loaded := m.connections.Load(id)
if !loaded {
return nil
}
return connection
}
func (m *Manager) Snapshot() *Snapshot {
var connections []tracker
m.connections.Range(func(_ string, value tracker) bool {
connections = append(connections, value)
var connections []Tracker
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
if value.Metadata().OutboundType != C.TypeDNS {
connections = append(connections, value)
}
return true
})
@@ -75,10 +119,10 @@ func (m *Manager) Snapshot() *Snapshot {
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
return &Snapshot{
UploadTotal: m.uploadTotal.Load(),
DownloadTotal: m.downloadTotal.Load(),
Connections: connections,
Memory: m.memory,
Upload: m.uploadTotal.Load(),
Download: m.downloadTotal.Load(),
Connections: connections,
Memory: m.memory,
}
}
@@ -114,8 +158,17 @@ func (m *Manager) Close() error {
}
type Snapshot struct {
DownloadTotal int64 `json:"downloadTotal"`
UploadTotal int64 `json:"uploadTotal"`
Connections []tracker `json:"connections"`
Memory uint64 `json:"memory"`
Download int64
Upload int64
Connections []Tracker
Memory uint64
}
func (s *Snapshot) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"downloadTotal": s.Download,
"uploadTotal": s.Upload,
"connections": common.Map(s.Connections, func(t Tracker) TrackerMetadata { return t.Metadata() }),
"memory": s.Memory,
})
}

View File

@@ -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
}
func (tt *tcpTracker) ID() string {
return tt.UUID.String()
type TCPConn struct {
N.ExtendedConn
metadata TrackerMetadata
manager *Manager
}
func (tt *tcpTracker) Close() error {
func (tt *TCPConn) Metadata() TrackerMetadata {
return tt.metadata
}
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
}