100 lines
2.2 KiB
Go
100 lines
2.2 KiB
Go
package xboard
|
|
|
|
import (
|
|
"net"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
"github.com/sagernet/sing-box/service/ssmapi"
|
|
N "github.com/sagernet/sing/common/network"
|
|
)
|
|
|
|
const aliveIPRetention = 5 * time.Minute
|
|
|
|
var _ adapter.SSMTracker = (*xboardTracker)(nil)
|
|
|
|
type xboardTracker struct {
|
|
service *Service
|
|
traffic *ssmapi.TrafficManager
|
|
}
|
|
|
|
func newXboardTracker(service *Service) *xboardTracker {
|
|
return &xboardTracker{
|
|
service: service,
|
|
traffic: ssmapi.NewTrafficManager(),
|
|
}
|
|
}
|
|
|
|
func (t *xboardTracker) TrafficManager() *ssmapi.TrafficManager {
|
|
return t.traffic
|
|
}
|
|
|
|
func (t *xboardTracker) TrackConnection(conn net.Conn, metadata adapter.InboundContext) net.Conn {
|
|
t.service.recordAliveIP(metadata)
|
|
return t.traffic.TrackConnection(conn, metadata)
|
|
}
|
|
|
|
func (t *xboardTracker) TrackPacketConnection(conn N.PacketConn, metadata adapter.InboundContext) N.PacketConn {
|
|
t.service.recordAliveIP(metadata)
|
|
return t.traffic.TrackPacketConnection(conn, metadata)
|
|
}
|
|
|
|
func (s *Service) recordAliveIP(metadata adapter.InboundContext) {
|
|
if metadata.User == "" || !metadata.Source.IsValid() || !metadata.Source.Addr.IsValid() {
|
|
return
|
|
}
|
|
|
|
clientIP := metadata.Source.Addr.Unmap().String()
|
|
if clientIP == "" {
|
|
return
|
|
}
|
|
|
|
s.access.Lock()
|
|
defer s.access.Unlock()
|
|
|
|
ipSet, exists := s.aliveUsers[metadata.User]
|
|
if !exists {
|
|
ipSet = make(map[string]time.Time)
|
|
s.aliveUsers[metadata.User] = ipSet
|
|
}
|
|
ipSet[clientIP] = time.Now()
|
|
}
|
|
|
|
func (s *Service) buildAlivePayload() map[string][]string {
|
|
now := time.Now()
|
|
|
|
s.access.Lock()
|
|
defer s.access.Unlock()
|
|
|
|
payload := make(map[string][]string)
|
|
for userName, ipSet := range s.aliveUsers {
|
|
userMeta, exists := s.localUsers[userName]
|
|
if !exists || userMeta.ID == 0 {
|
|
delete(s.aliveUsers, userName)
|
|
continue
|
|
}
|
|
|
|
activeIPs := make([]string, 0, len(ipSet))
|
|
for ip, lastSeen := range ipSet {
|
|
if now.Sub(lastSeen) > aliveIPRetention {
|
|
delete(ipSet, ip)
|
|
continue
|
|
}
|
|
activeIPs = append(activeIPs, ip)
|
|
}
|
|
if len(ipSet) == 0 {
|
|
delete(s.aliveUsers, userName)
|
|
}
|
|
if len(activeIPs) == 0 {
|
|
continue
|
|
}
|
|
|
|
sort.Strings(activeIPs)
|
|
payload[strconv.Itoa(userMeta.ID)] = activeIPs
|
|
}
|
|
|
|
return payload
|
|
}
|