Files
sing-box-extended-mirror/experimental/clashapi/connections.go
T

180 lines
5.0 KiB
Go
Raw Normal View History

2022-07-19 22:16:49 +08:00
package clashapi
import (
"bytes"
"context"
2022-07-19 22:16:49 +08:00
"net/http"
"strconv"
"time"
"github.com/sagernet/sing-box/adapter"
2026-06-11 21:11:10 +08:00
"github.com/sagernet/sing-box/common/trafficcontrol"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
2023-11-09 16:59:44 +08:00
"github.com/sagernet/ws"
"github.com/sagernet/ws/wsutil"
2022-07-19 22:16:49 +08:00
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
2024-06-11 21:16:33 +08:00
"github.com/gofrs/uuid/v5"
2022-07-19 22:16:49 +08:00
)
2026-06-11 21:11:10 +08:00
func connectionRouter(ctx context.Context, network adapter.NetworkManager, trafficManager *trafficcontrol.Manager) http.Handler {
2022-07-19 22:16:49 +08:00
r := chi.NewRouter()
r.Get("/", getConnections(ctx, trafficManager))
2026-08-10 23:10:24 +08:00
r.Delete("/", closeAllConnections(network, trafficManager))
2022-07-19 22:16:49 +08:00
r.Delete("/{id}", closeConnection(trafficManager))
return r
}
2026-06-11 21:11:10 +08:00
func connectionsSnapshot(trafficManager *trafficcontrol.Manager) render.M {
uplinkTotal, downlinkTotal := trafficManager.Total()
connections := common.Filter(trafficManager.Connections(), func(metadata *trafficcontrol.TrackerMetadata) bool {
return metadata.OutboundType != C.TypeDNS
})
return render.M{
"downloadTotal": downlinkTotal,
"uploadTotal": uplinkTotal,
"connections": common.Map(connections, func(metadata *trafficcontrol.TrackerMetadata) connectionObject {
return connectionObject(*metadata)
}),
"memory": inuseMemory(),
}
}
type connectionObject trafficcontrol.TrackerMetadata
func (c connectionObject) MarshalJSON() ([]byte, error) {
var inbound string
if c.Metadata.Inbound != "" {
inbound = c.Metadata.InboundType + "/" + c.Metadata.Inbound
} else {
inbound = c.Metadata.InboundType
}
var domain string
if c.Metadata.Domain != "" {
domain = c.Metadata.Domain
} else {
domain = c.Metadata.Destination.Fqdn
}
var processPath string
if c.Metadata.ProcessInfo != nil {
if c.Metadata.ProcessInfo.ProcessPath != "" {
processPath = c.Metadata.ProcessInfo.ProcessPath
} else if len(c.Metadata.ProcessInfo.AndroidPackageNames) > 0 {
processPath = c.Metadata.ProcessInfo.AndroidPackageNames[0]
}
if processPath == "" {
if c.Metadata.ProcessInfo.UserId != -1 {
processPath = F.ToString(c.Metadata.ProcessInfo.UserId)
}
} else if c.Metadata.ProcessInfo.UserName != "" {
processPath = F.ToString(processPath, " (", c.Metadata.ProcessInfo.UserName, ")")
} else if c.Metadata.ProcessInfo.UserId != -1 {
processPath = F.ToString(processPath, " (", c.Metadata.ProcessInfo.UserId, ")")
}
}
var rule string
if c.Rule != nil {
rule = F.ToString(c.Rule, " => ", c.Rule.Action())
} else {
rule = "final"
}
return json.Marshal(map[string]any{
"id": c.ID,
"metadata": map[string]any{
"network": c.Metadata.Network,
"type": inbound,
"sourceIP": c.Metadata.Source.Addr,
"destinationIP": c.Metadata.Destination.Addr,
"sourcePort": F.ToString(c.Metadata.Source.Port),
"destinationPort": F.ToString(c.Metadata.Destination.Port),
"host": domain,
"dnsMode": "normal",
"processPath": processPath,
},
"upload": c.Upload.Load(),
"download": c.Download.Load(),
"start": c.CreatedAt,
"chains": c.Chain,
"rule": rule,
"rulePayload": "",
})
}
func getConnections(ctx context.Context, trafficManager *trafficcontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
2022-07-19 22:16:49 +08:00
return func(w http.ResponseWriter, r *http.Request) {
2023-11-09 16:59:44 +08:00
if r.Header.Get("Upgrade") != "websocket" {
2026-06-11 21:11:10 +08:00
render.JSON(w, r, connectionsSnapshot(trafficManager))
2022-07-19 22:16:49 +08:00
return
}
2023-11-09 16:59:44 +08:00
conn, _, _, err := ws.UpgradeHTTP(r, w)
2022-07-19 22:16:49 +08:00
if err != nil {
return
}
defer conn.Close()
2022-07-19 22:16:49 +08:00
intervalStr := r.URL.Query().Get("interval")
interval := 1000
if intervalStr != "" {
t, err := strconv.Atoi(intervalStr)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
interval = t
}
buf := &bytes.Buffer{}
sendSnapshot := func() error {
buf.Reset()
2026-06-11 21:11:10 +08:00
encodeErr := json.NewEncoder(buf).Encode(connectionsSnapshot(trafficManager))
if encodeErr != nil {
return encodeErr
2022-07-19 22:16:49 +08:00
}
2023-11-09 16:59:44 +08:00
return wsutil.WriteServerText(conn, buf.Bytes())
2022-07-19 22:16:49 +08:00
}
if err = sendSnapshot(); err != nil {
return
}
tick := time.NewTicker(time.Millisecond * time.Duration(interval))
defer tick.Stop()
for {
select {
case <-ctx.Done():
return
case <-tick.C:
}
2022-07-19 22:16:49 +08:00
if err = sendSnapshot(); err != nil {
break
}
}
}
}
2026-06-11 21:11:10 +08:00
func closeConnection(trafficManager *trafficcontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
2022-07-19 22:16:49 +08:00
return func(w http.ResponseWriter, r *http.Request) {
2024-06-11 21:16:33 +08:00
id := uuid.FromStringOrNil(chi.URLParam(r, "id"))
2026-06-11 21:11:10 +08:00
targetConnection := trafficManager.Connection(id)
if targetConnection != nil {
targetConnection.Close()
2022-07-19 22:16:49 +08:00
}
render.NoContent(w, r)
}
}
2026-06-11 21:11:10 +08:00
func closeAllConnections(network adapter.NetworkManager, trafficManager *trafficcontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
2022-07-19 22:16:49 +08:00
return func(w http.ResponseWriter, r *http.Request) {
2026-06-11 21:11:10 +08:00
trafficManager.CloseAllConnections()
2026-08-10 23:10:24 +08:00
network.ResetNetwork()
2022-07-19 22:16:49 +08:00
render.NoContent(w, r)
}
}