Merge d98115dd9adcda306395f61a31bda62e83f672f8 into 05c4f94982abc10d149335dca4326ca199125b0a

This commit is contained in:
Juvenn Woo 2024-08-27 11:56:48 +00:00 committed by GitHub
commit ead40d7fba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 336 additions and 48 deletions

View File

@ -13,7 +13,7 @@ RUN set -ex \
&& export COMMIT=$(git rev-parse --short HEAD) \ && export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \ && export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \ && go build -v -trimpath -tags \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \ "with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api,with_metric_api" \
-o /go/bin/sing-box \ -o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
./cmd/sing-box ./cmd/sing-box

View File

@ -1,6 +1,6 @@
NAME = sing-box NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD) COMMIT = $(shell git rev-parse --short HEAD)
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_metric_api,with_quic,with_utls
TAGS_GO121 = with_ech TAGS_GO121 = with_ech
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121) TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
@ -208,4 +208,4 @@ clean:
update: update:
git fetch git fetch
git reset FETCH_HEAD --hard git reset FETCH_HEAD --hard
git clean -fdx git clean -fdx

View File

@ -118,10 +118,20 @@ func OutboundTag(detour Outbound) string {
type V2RayServer interface { type V2RayServer interface {
Service Service
StatsService() V2RayStatsService StatsService() PacketTracking
} }
type V2RayStatsService interface { type MetricService interface {
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn Service
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn PacketTracking
} }
type PacketTracking interface {
WithConnCounters(inbound, outbound, user string) ConnAdapter[net.Conn]
WithPacketConnCounters(inbound, outbound, user string) ConnAdapter[N.PacketConn]
}
// ConnAdapter
// Transform a connection to another connection. The T should be either of
// net.Conn or N.PacketConn.
type ConnAdapter[T any] func(T) T

12
box.go
View File

@ -61,6 +61,7 @@ func New(options Options) (*Box, error) {
var needCacheFile bool var needCacheFile bool
var needClashAPI bool var needClashAPI bool
var needV2RayAPI bool var needV2RayAPI bool
var needMetricAPI bool
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil { if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
needCacheFile = true needCacheFile = true
} }
@ -70,6 +71,9 @@ func New(options Options) (*Box, error) {
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true needV2RayAPI = true
} }
if experimentalOptions.MetricAPI.Enabled() {
needMetricAPI = true
}
var defaultLogWriter io.Writer var defaultLogWriter io.Writer
if options.PlatformInterface != nil { if options.PlatformInterface != nil {
defaultLogWriter = io.Discard defaultLogWriter = io.Discard
@ -183,6 +187,14 @@ func New(options Options) (*Box, error) {
router.SetV2RayServer(v2rayServer) router.SetV2RayServer(v2rayServer)
preServices2["v2ray api"] = v2rayServer preServices2["v2ray api"] = v2rayServer
} }
if needMetricAPI {
metricServer, err := experimental.NewMetricServer(logFactory.NewLogger("metric-api"), common.PtrValueOrDefault(experimentalOptions.MetricAPI))
if err != nil {
return nil, E.Cause(err, "create metric api server")
}
router.SetMetricServer(metricServer)
preServices2["metric api"] = metricServer
}
return &Box{ return &Box{
router: router, router: router,
inbounds: inbounds, inbounds: inbounds,

View File

@ -54,7 +54,7 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=") sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api", "with_metric_api")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack") iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
debugTags = append(debugTags, "debug") debugTags = append(debugTags, "debug")
} }

View File

@ -2,7 +2,7 @@
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
:material-plus: [cache_file](#cache_file) :material-plus: [cache_file](#cache_file)
:material-alert-decagram: [clash_api](#clash_api) :material-alert-decagram: [clash_api](#clash_api)
### Structure ### Structure
@ -23,4 +23,5 @@
|--------------|----------------------------| |--------------|----------------------------|
| `cache_file` | [Cache File](./cache-file/) | | `cache_file` | [Cache File](./cache-file/) |
| `clash_api` | [Clash API](./clash-api/) | | `clash_api` | [Clash API](./clash-api/) |
| `v2ray_api` | [V2Ray API](./v2ray-api/) | | `metric_api` | [Metric API](./metric-api/) |
| `v2ray_api` | [V2Ray API](./v2ray-api/) |

View File

@ -2,7 +2,7 @@
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-plus: [cache_file](#cache_file) :material-plus: [cache_file](#cache_file)
:material-alert-decagram: [clash_api](#clash_api) :material-alert-decagram: [clash_api](#clash_api)
### 结构 ### 结构
@ -12,6 +12,7 @@
"experimental": { "experimental": {
"cache_file": {}, "cache_file": {},
"clash_api": {}, "clash_api": {},
"metrics": {},
"v2ray_api": {} "v2ray_api": {}
} }
} }
@ -19,8 +20,9 @@
### 字段 ### 字段
| 键 | 格式 | | 键 | 格式 |
|--------------|--------------------------| |--------------|------------------------------|
| `cache_file` | [缓存文件](./cache-file/) | | `cache_file` | [缓存文件](./cache-file/) |
| `clash_api` | [Clash API](./clash-api/) | | `clash_api` | [Clash API](./clash-api/) |
| `v2ray_api` | [V2Ray API](./v2ray-api/) | | `metric_api` | [Metric API](./metric-api/) |
| `v2ray_api` | [V2Ray API](./v2ray-api/) |

View File

@ -0,0 +1,21 @@
### Structure
```json
{
"metrics": {
"listen": ":8080",
"path": "/metrics"
}
}
```
### Fields
#### listen
Prometheus metrics API listening address, disabled if empty.
#### path
Prometheus scrape path, `/metrics` will be used if empty.

View File

@ -0,0 +1,21 @@
### Structure
```json
{
"metrics": {
"listen": ":8080",
"path": "/metrics"
}
}
```
### Fields
#### listen
Prometheus 指标监听地址,如果为空则禁用。
#### path
HTTP 路径,如果为空则使用 `/metrics`。本路径可用于 Prometheus exporter 进行抓取。

View File

@ -65,6 +65,7 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | | `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | | `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | | `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
| `with_metric_api` | :material-check: | Build with Prometheus metric API support, see [Experimental](/configuration/experimental#metric-api-fields). |
| `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | | `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | | `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
| `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | | `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |

View File

@ -65,6 +65,7 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). | | `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls/). |
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). | | `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/). |
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). | | `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
| `with_metric_api` | :material-check: | Build with Prometheus metric API support, see [Experimental](/configuration/experimental#metric-api-fields). |
| `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). | | `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). | | `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
| `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). | | `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/). |

24
experimental/metricapi.go Normal file
View File

@ -0,0 +1,24 @@
package experimental
import (
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type MetricServerConstructor = func(logger log.Logger, options option.MetricOptions) (adapter.MetricService, error)
var metricServerConstructor MetricServerConstructor
func RegisterMetricServerConstructor(constructor MetricServerConstructor) {
metricServerConstructor = constructor
}
func NewMetricServer(logger log.Logger, options option.MetricOptions) (adapter.MetricService, error) {
if metricServerConstructor == nil {
return nil, os.ErrInvalid
}
return metricServerConstructor(logger, options)
}

View File

@ -0,0 +1,77 @@
package metrics
import (
"errors"
"net"
"net/http"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/go-chi/chi/v5"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var _ adapter.MetricService = (*metricServer)(nil)
func init() {
experimental.RegisterMetricServerConstructor(NewServer)
}
type metricServer struct {
http *http.Server
logger log.Logger
opts option.MetricOptions
packetCountersInbound *prometheus.CounterVec
packetCountersOutbound *prometheus.CounterVec
}
func NewServer(logger log.Logger, opts option.MetricOptions) (adapter.MetricService, error) {
r := chi.NewRouter()
_server := &http.Server{
Addr: opts.Listen,
Handler: r,
}
if opts.Path == "" {
opts.Path = "/metrics"
}
r.Get(opts.Path, promhttp.Handler().ServeHTTP)
server := &metricServer{
http: _server,
logger: logger,
opts: opts,
}
err := server.registerMetrics()
return server, err
}
func (s *metricServer) Start() error {
if !s.opts.Enabled() {
return nil
}
listener, err := net.Listen("tcp", s.opts.Listen)
if err != nil {
return err
}
s.logger.Info("metrics api listening at ", s.http.Addr, s.opts.Path)
go func() {
err := s.http.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
s.logger.Error("metrics api serve error: ", err)
}
}()
return nil
}
func (s *metricServer) Close() error {
if !s.opts.Enabled() {
return nil
}
return common.Close(common.PtrOrNil(s.http))
}

View File

@ -0,0 +1,52 @@
package metrics
import (
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/prometheus/client_golang/prometheus"
)
func (s *metricServer) registerMetrics() error {
s.packetCountersInbound = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "inbound_packet_bytes",
Help: "Total bytes of inbound packets",
}, []string{"inbound", "user"})
s.packetCountersOutbound = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "outbound_packet_bytes",
Help: "Total bytes of outbound packets",
}, []string{"outbound", "user"})
var err error
err = prometheus.Register(s.packetCountersInbound)
err = prometheus.Register(s.packetCountersOutbound)
return err
}
func (s *metricServer) WithConnCounters(inbound, outbound, user string) adapter.ConnAdapter[net.Conn] {
incRead, incWrite := s.getPacketCounters(inbound, outbound, user)
return func(conn net.Conn) net.Conn {
return bufio.NewCounterConn(conn, []N.CountFunc{incRead}, []N.CountFunc{incWrite})
}
}
func (s *metricServer) WithPacketConnCounters(inbound, outbound, user string) adapter.ConnAdapter[N.PacketConn] {
incRead, incWrite := s.getPacketCounters(inbound, outbound, user)
return func(conn N.PacketConn) N.PacketConn {
return bufio.NewCounterPacketConn(conn, []N.CountFunc{incRead}, []N.CountFunc{incWrite})
}
}
func (s *metricServer) getPacketCounters(inbound, outbound, user string) (
readCounters N.CountFunc,
writeCounters N.CountFunc,
) {
return func(n int64) {
s.packetCountersInbound.WithLabelValues(inbound, user).Add(float64(n))
}, func(n int64) {
s.packetCountersOutbound.WithLabelValues(outbound, user).Add(float64(n))
}
}

View File

@ -70,6 +70,6 @@ func (s *Server) Close() error {
) )
} }
func (s *Server) StatsService() adapter.V2RayStatsService { func (s *Server) StatsService() adapter.PacketTracking {
return s.statsService return s.statsService
} }

View File

@ -22,8 +22,8 @@ func init() {
} }
var ( var (
_ adapter.V2RayStatsService = (*StatsService)(nil) _ adapter.PacketTracking = (*StatsService)(nil)
_ StatsServiceServer = (*StatsService)(nil) _ StatsServiceServer = (*StatsService)(nil)
) )
type StatsService struct { type StatsService struct {
@ -60,14 +60,14 @@ func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService {
} }
} }
func (s *StatsService) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn { func (s *StatsService) getPacketCounters(inbound, outbound, user string) (incRead N.CountFunc, inWrite N.CountFunc) {
var readCounter []*atomic.Int64 var readCounter []*atomic.Int64
var writeCounter []*atomic.Int64 var writeCounter []*atomic.Int64
countInbound := inbound != "" && s.inbounds[inbound] countInbound := inbound != "" && s.inbounds[inbound]
countOutbound := outbound != "" && s.outbounds[outbound] countOutbound := outbound != "" && s.outbounds[outbound]
countUser := user != "" && s.users[user] countUser := user != "" && s.users[user]
if !countInbound && !countOutbound && !countUser { if !countInbound && !countOutbound && !countUser {
return conn return func(n int64) {}, func(n int64) {}
} }
s.access.Lock() s.access.Lock()
if countInbound { if countInbound {
@ -83,33 +83,29 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink")) writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
} }
s.access.Unlock() s.access.Unlock()
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter) return func(n int64) {
for _, c := range readCounter {
c.Add(n)
}
}, func(n int64) {
for _, c := range writeCounter {
c.Add(n)
}
}
} }
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn { func (s *StatsService) WithConnCounters(inbound, outbound, user string) adapter.ConnAdapter[net.Conn] {
var readCounter []*atomic.Int64 rd, wr := s.getPacketCounters(inbound, outbound, user)
var writeCounter []*atomic.Int64 return func(conn net.Conn) net.Conn {
countInbound := inbound != "" && s.inbounds[inbound] return bufio.NewCounterConn(conn, []N.CountFunc{rd}, []N.CountFunc{wr})
countOutbound := outbound != "" && s.outbounds[outbound]
countUser := user != "" && s.users[user]
if !countInbound && !countOutbound && !countUser {
return conn
} }
s.access.Lock() }
if countInbound {
readCounter = append(readCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink")) func (s *StatsService) WithPacketConnCounters(inbound, outbound, user string) adapter.ConnAdapter[N.PacketConn] {
writeCounter = append(writeCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink")) rd, wr := s.getPacketCounters(inbound, outbound, user)
return func(conn N.PacketConn) N.PacketConn {
return bufio.NewCounterPacketConn(conn, []N.CountFunc{rd}, []N.CountFunc{wr})
} }
if countOutbound {
readCounter = append(readCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink"))
writeCounter = append(writeCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink"))
}
if countUser {
readCounter = append(readCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>uplink"))
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
}
s.access.Unlock()
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
} }
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {

13
go.mod
View File

@ -50,7 +50,7 @@ require (
golang.org/x/sys v0.22.0 golang.org/x/sys v0.22.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.63.2 google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.34.2
howett.net/plist v1.0.1 howett.net/plist v1.0.1
) )
@ -59,6 +59,8 @@ require (
require ( require (
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect github.com/gaukas/godicttls v0.0.4 // indirect
@ -72,15 +74,20 @@ require (
github.com/hashicorp/yamux v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.20.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
@ -96,7 +103,7 @@ require (
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.23.0 // indirect golang.org/x/tools v0.23.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect
) )

20
go.sum
View File

@ -4,8 +4,12 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -53,9 +57,12 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -78,6 +85,8 @@ github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
@ -91,6 +100,14 @@ github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
@ -213,9 +230,12 @@ google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

5
include/metricapi.go Normal file
View File

@ -0,0 +1,5 @@
//go:build with_metric_api
package include
import _ "github.com/sagernet/sing-box/experimental/metrics"

17
include/metricapi_stub.go Normal file
View File

@ -0,0 +1,17 @@
//go:build !with_metric_api
package include
import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func init() {
experimental.RegisterMetricServerConstructor(func(logger log.Logger, options option.MetricOptions) (adapter.MetricService, error) {
return nil, E.New(`metric api is not included in this build, rebuild with -tags with_metric_api`)
})
}

View File

@ -5,6 +5,7 @@ type ExperimentalOptions struct {
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"` V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
Debug *DebugOptions `json:"debug,omitempty"` Debug *DebugOptions `json:"debug,omitempty"`
MetricAPI *MetricOptions `json:"metrics,omitempty"`
} }
type CacheFileOptions struct { type CacheFileOptions struct {
@ -50,3 +51,12 @@ type V2RayStatsServiceOptions struct {
Outbounds []string `json:"outbounds,omitempty"` Outbounds []string `json:"outbounds,omitempty"`
Users []string `json:"users,omitempty"` Users []string `json:"users,omitempty"`
} }
type MetricOptions struct {
Listen string `json:"listen,omitempty"`
Path string `json:"path,omitempty"`
}
func (m *MetricOptions) Enabled() bool {
return m != nil && m.Listen != ""
}

View File

@ -93,6 +93,7 @@ type Router struct {
pauseManager pause.Manager pauseManager pause.Manager
clashServer adapter.ClashServer clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer v2rayServer adapter.V2RayServer
metricServer adapter.MetricService
platformInterface platform.Interface platformInterface platform.Interface
needWIFIState bool needWIFIState bool
needPackageManager bool needPackageManager bool
@ -921,9 +922,12 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
} }
if r.v2rayServer != nil { if r.v2rayServer != nil {
if statsService := r.v2rayServer.StatsService(); statsService != nil { if statsService := r.v2rayServer.StatsService(); statsService != nil {
conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) conn = statsService.WithConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
} }
} }
if r.metricServer != nil {
conn = r.metricServer.WithConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
}
return detour.NewConnection(ctx, conn, metadata) return detour.NewConnection(ctx, conn, metadata)
} }
@ -1097,9 +1101,12 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
} }
if r.v2rayServer != nil { if r.v2rayServer != nil {
if statsService := r.v2rayServer.StatsService(); statsService != nil { if statsService := r.v2rayServer.StatsService(); statsService != nil {
conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), metadata.User, conn) conn = statsService.WithPacketConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
} }
} }
if r.metricServer != nil {
conn = r.metricServer.WithPacketConnCounters(metadata.Inbound, detour.Tag(), metadata.User)(conn)
}
if metadata.FakeIP { if metadata.FakeIP {
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination) conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, metadata.Destination)
} }
@ -1265,6 +1272,10 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
r.v2rayServer = server r.v2rayServer = server
} }
func (r *Router) SetMetricServer(server adapter.MetricService) {
r.metricServer = server
}
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) { func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
} }