| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 | package libboximport (	"bufio"	"net"	"slices"	"strings"	"time"	"github.com/sagernet/sing-box/experimental/clashapi"	"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"	"github.com/sagernet/sing/common/binary"	E "github.com/sagernet/sing/common/exceptions"	M "github.com/sagernet/sing/common/metadata"	"github.com/sagernet/sing/common/varbin"	"github.com/gofrs/uuid/v5")func (c *CommandClient) handleConnectionsConn(conn net.Conn) {	defer conn.Close()	reader := bufio.NewReader(conn)	var (		rawConnections []Connection		connections    Connections	)	for {		rawConnections = nil		err := varbin.Read(reader, binary.BigEndian, &rawConnections)		if err != nil {			c.handler.Disconnected(err.Error())			return		}		connections.input = rawConnections		c.handler.WriteConnections(&connections)	}}func (s *CommandServer) handleConnectionsConn(conn net.Conn) error {	var interval int64	err := binary.Read(conn, binary.BigEndian, &interval)	if err != nil {		return E.Cause(err, "read interval")	}	ticker := time.NewTicker(time.Duration(interval))	defer ticker.Stop()	ctx := connKeepAlive(conn)	var trafficManager *trafficontrol.Manager	for {		service := s.service		if service != nil {			trafficManager = service.clashServer.(*clashapi.Server).TrafficManager()			break		}		select {		case <-ctx.Done():			return ctx.Err()		case <-ticker.C:		}	}	var (		connections    = make(map[uuid.UUID]*Connection)		outConnections []Connection	)	writer := bufio.NewWriter(conn)	for {		outConnections = outConnections[:0]		for _, connection := range trafficManager.Connections() {			outConnections = append(outConnections, newConnection(connections, connection, false))		}		for _, connection := range trafficManager.ClosedConnections() {			outConnections = append(outConnections, newConnection(connections, connection, true))		}		err = varbin.Write(writer, binary.BigEndian, outConnections)		if err != nil {			return err		}		err = writer.Flush()		if err != nil {			return err		}		select {		case <-ctx.Done():			return ctx.Err()		case <-ticker.C:		}	}}const (	ConnectionStateAll = iota	ConnectionStateActive	ConnectionStateClosed)type Connections struct {	input    []Connection	filtered []Connection}func (c *Connections) FilterState(state int32) {	c.filtered = c.filtered[:0]	switch state {	case ConnectionStateAll:		c.filtered = append(c.filtered, c.input...)	case ConnectionStateActive:		for _, connection := range c.input {			if connection.ClosedAt == 0 {				c.filtered = append(c.filtered, connection)			}		}	case ConnectionStateClosed:		for _, connection := range c.input {			if connection.ClosedAt != 0 {				c.filtered = append(c.filtered, connection)			}		}	}}func (c *Connections) SortByDate() {	slices.SortStableFunc(c.filtered, func(x, y Connection) int {		if x.CreatedAt < y.CreatedAt {			return 1		} else if x.CreatedAt > y.CreatedAt {			return -1		} else {			return strings.Compare(y.ID, x.ID)		}	})}func (c *Connections) SortByTraffic() {	slices.SortStableFunc(c.filtered, func(x, y Connection) int {		xTraffic := x.Uplink + x.Downlink		yTraffic := y.Uplink + y.Downlink		if xTraffic < yTraffic {			return 1		} else if xTraffic > yTraffic {			return -1		} else {			return strings.Compare(y.ID, x.ID)		}	})}func (c *Connections) SortByTrafficTotal() {	slices.SortStableFunc(c.filtered, func(x, y Connection) int {		xTraffic := x.UplinkTotal + x.DownlinkTotal		yTraffic := y.UplinkTotal + y.DownlinkTotal		if xTraffic < yTraffic {			return 1		} else if xTraffic > yTraffic {			return -1		} else {			return strings.Compare(y.ID, x.ID)		}	})}func (c *Connections) Iterator() ConnectionIterator {	return newPtrIterator(c.filtered)}type Connection struct {	ID            string	Inbound       string	InboundType   string	IPVersion     int32	Network       string	Source        string	Destination   string	Domain        string	Protocol      string	User          string	FromOutbound  string	CreatedAt     int64	ClosedAt      int64	Uplink        int64	Downlink      int64	UplinkTotal   int64	DownlinkTotal int64	Rule          string	Outbound      string	OutboundType  string	ChainList     []string}func (c *Connection) Chain() StringIterator {	return newIterator(c.ChainList)}func (c *Connection) DisplayDestination() string {	destination := M.ParseSocksaddr(c.Destination)	if destination.IsIP() && c.Domain != "" {		destination = M.Socksaddr{			Fqdn: c.Domain,			Port: destination.Port,		}		return destination.String()	}	return c.Destination}type ConnectionIterator interface {	Next() *Connection	HasNext() bool}func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) Connection {	if oldConnection, loaded := connections[metadata.ID]; loaded {		if isClosed {			if oldConnection.ClosedAt == 0 {				oldConnection.Uplink = 0				oldConnection.Downlink = 0				oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()			}			return *oldConnection		}		lastUplink := oldConnection.UplinkTotal		lastDownlink := oldConnection.DownlinkTotal		uplinkTotal := metadata.Upload.Load()		downlinkTotal := metadata.Download.Load()		oldConnection.Uplink = uplinkTotal - lastUplink		oldConnection.Downlink = downlinkTotal - lastDownlink		oldConnection.UplinkTotal = uplinkTotal		oldConnection.DownlinkTotal = downlinkTotal		return *oldConnection	}	var rule string	if metadata.Rule != nil {		rule = metadata.Rule.String()	}	uplinkTotal := metadata.Upload.Load()	downlinkTotal := metadata.Download.Load()	uplink := uplinkTotal	downlink := downlinkTotal	var closedAt int64	if !metadata.ClosedAt.IsZero() {		closedAt = metadata.ClosedAt.UnixMilli()		uplink = 0		downlink = 0	}	connection := Connection{		ID:            metadata.ID.String(),		Inbound:       metadata.Metadata.Inbound,		InboundType:   metadata.Metadata.InboundType,		IPVersion:     int32(metadata.Metadata.IPVersion),		Network:       metadata.Metadata.Network,		Source:        metadata.Metadata.Source.String(),		Destination:   metadata.Metadata.Destination.String(),		Domain:        metadata.Metadata.Domain,		Protocol:      metadata.Metadata.Protocol,		User:          metadata.Metadata.User,		FromOutbound:  metadata.Metadata.Outbound,		CreatedAt:     metadata.CreatedAt.UnixMilli(),		ClosedAt:      closedAt,		Uplink:        uplink,		Downlink:      downlink,		UplinkTotal:   uplinkTotal,		DownlinkTotal: downlinkTotal,		Rule:          rule,		Outbound:      metadata.Outbound,		OutboundType:  metadata.OutboundType,		ChainList:     metadata.Chain,	}	connections[metadata.ID] = &connection	return connection}
 |