Merge remote-tracking branch 'upstream/dev-next' into dev-next
This commit is contained in:
23
.fpm_pacman
Normal file
23
.fpm_pacman
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-s dir
|
||||||
|
--name sing-box
|
||||||
|
--category net
|
||||||
|
--license GPL-3.0-or-later
|
||||||
|
--description "The universal proxy platform."
|
||||||
|
--url "https://sing-box.sagernet.org/"
|
||||||
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
|
--config-files etc/sing-box/config.json
|
||||||
|
--after-install release/config/sing-box.postinst
|
||||||
|
|
||||||
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||||
|
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||||
|
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||||
|
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
||||||
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
|||||||
abd78bb191a815236485ad929716845ffb41465a
|
17c7ef18afa63b205e835c6270277b29382eb8e3
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -373,7 +373,7 @@ jobs:
|
|||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
cp .fpm_systemd .fpm
|
cp .fpm_pacman .fpm
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||||
|
|||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -3,4 +3,4 @@
|
|||||||
url = https://github.com/SagerNet/sing-box-for-apple.git
|
url = https://github.com/SagerNet/sing-box-for-apple.git
|
||||||
[submodule "clients/android"]
|
[submodule "clients/android"]
|
||||||
path = clients/android
|
path = clients/android
|
||||||
url = https://git-gitea-1:3000/icybear/sing-box-for-android.git
|
url = https://github.com/SagerNet/sing-box-for-android.git
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -249,8 +249,8 @@ lib_apple_new:
|
|||||||
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
|
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.11
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import (
|
|||||||
|
|
||||||
type ConnectionManager interface {
|
type ConnectionManager interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
|
Count() int
|
||||||
|
CloseAll()
|
||||||
|
TrackConn(conn net.Conn) net.Conn
|
||||||
|
TrackPacketConn(conn net.PacketConn) net.PacketConn
|
||||||
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
}
|
}
|
||||||
|
|||||||
5
box.go
5
box.go
@@ -125,7 +125,10 @@ func New(options Options) (*Box, error) {
|
|||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var needCacheFile bool
|
var needCacheFile bool
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
||||||
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
||||||
// memcTags = append(memcTags, "with_tailscale")
|
// memcTags = append(memcTags, "with_tailscale")
|
||||||
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
element *list.Element[io.Closer]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(conn net.Conn) (net.Conn, error) {
|
|
||||||
connAccess.Lock()
|
|
||||||
element := openConnection.PushBack(conn)
|
|
||||||
connAccess.Unlock()
|
|
||||||
if KillerEnabled {
|
|
||||||
err := KillerCheck()
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Conn{
|
|
||||||
Conn: conn,
|
|
||||||
element: element,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
if c.element.Value != nil {
|
|
||||||
connAccess.Lock()
|
|
||||||
if c.element.Value != nil {
|
|
||||||
openConnection.Remove(c.element)
|
|
||||||
c.element.Value = nil
|
|
||||||
}
|
|
||||||
connAccess.Unlock()
|
|
||||||
}
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
runtimeDebug "runtime/debug"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/memory"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
KillerEnabled bool
|
|
||||||
MemoryLimit uint64
|
|
||||||
killerLastCheck time.Time
|
|
||||||
)
|
|
||||||
|
|
||||||
func KillerCheck() error {
|
|
||||||
if !KillerEnabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nowTime := time.Now()
|
|
||||||
if nowTime.Sub(killerLastCheck) < 3*time.Second {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
killerLastCheck = nowTime
|
|
||||||
if memory.Total() > MemoryLimit {
|
|
||||||
Close()
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
runtimeDebug.FreeOSMemory()
|
|
||||||
}()
|
|
||||||
return E.New("out of memory")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
element *list.Element[io.Closer]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
|
||||||
connAccess.Lock()
|
|
||||||
element := openConnection.PushBack(conn)
|
|
||||||
connAccess.Unlock()
|
|
||||||
if KillerEnabled {
|
|
||||||
err := KillerCheck()
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &PacketConn{
|
|
||||||
PacketConn: conn,
|
|
||||||
element: element,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Close() error {
|
|
||||||
if c.element.Value != nil {
|
|
||||||
connAccess.Lock()
|
|
||||||
if c.element.Value != nil {
|
|
||||||
openConnection.Remove(c.element)
|
|
||||||
c.element.Value = nil
|
|
||||||
}
|
|
||||||
connAccess.Unlock()
|
|
||||||
}
|
|
||||||
return c.PacketConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Upstream() any {
|
|
||||||
return bufio.NewPacketConn(c.PacketConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
connAccess sync.RWMutex
|
|
||||||
openConnection list.List[io.Closer]
|
|
||||||
)
|
|
||||||
|
|
||||||
func Count() int {
|
|
||||||
if !Enabled {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return openConnection.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func List() []io.Closer {
|
|
||||||
if !Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
connAccess.RLock()
|
|
||||||
defer connAccess.RUnlock()
|
|
||||||
connList := make([]io.Closer, 0, openConnection.Len())
|
|
||||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
|
||||||
connList = append(connList, element.Value)
|
|
||||||
}
|
|
||||||
return connList
|
|
||||||
}
|
|
||||||
|
|
||||||
func Close() {
|
|
||||||
if !Enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
connAccess.Lock()
|
|
||||||
defer connAccess.Unlock()
|
|
||||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
|
||||||
common.Close(element.Value)
|
|
||||||
element.Value = nil
|
|
||||||
}
|
|
||||||
openConnection.Init()
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build !with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = false
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = true
|
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/common/listener"
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -37,6 +36,7 @@ type DefaultDialer struct {
|
|||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
netns string
|
netns string
|
||||||
|
connectionManager adapter.ConnectionManager
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy *C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
defaultNetworkStrategy bool
|
defaultNetworkStrategy bool
|
||||||
@@ -47,6 +47,7 @@ type DefaultDialer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
|
connectionManager := service.FromContext[adapter.ConnectionManager](ctx)
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if defaultOptions.BindInterface != "" {
|
if defaultOptions.BindInterface != "" && !disableDefaultBind {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
@@ -157,8 +158,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if keepInterval == 0 {
|
if keepInterval == 0 {
|
||||||
keepInterval = C.TCPKeepAliveInterval
|
keepInterval = C.TCPKeepAliveInterval
|
||||||
}
|
}
|
||||||
dialer.KeepAlive = keepIdle
|
dialer.KeepAliveConfig = net.KeepAliveConfig{
|
||||||
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval))
|
Enable: true,
|
||||||
|
Idle: keepIdle,
|
||||||
|
Interval: keepInterval,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
@@ -206,6 +210,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr4: udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
netns: options.NetNs,
|
netns: options.NetNs,
|
||||||
|
connectionManager: connectionManager,
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
networkStrategy: networkStrategy,
|
||||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
@@ -238,7 +243,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
return nil, E.New("domain not resolved")
|
return nil, E.New("domain not resolved")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
return d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
@@ -303,12 +308,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
if !fastFallback && !isPrimary {
|
if !fastFallback && !isPrimary {
|
||||||
d.networkLastFallback.Store(time.Now())
|
d.networkLastFallback.Store(time.Now())
|
||||||
}
|
}
|
||||||
return trackConn(conn, nil)
|
return d.trackConn(conn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
||||||
if destination.IsIPv6() {
|
if destination.IsIPv6() {
|
||||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
@@ -360,23 +365,23 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return trackPacketConn(packetConn, nil)
|
return d.trackPacketConn(packetConn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) WireGuardControl() control.Func {
|
func (d *DefaultDialer) WireGuardControl() control.Func {
|
||||||
return d.udpListener.Control
|
return d.udpListener.Control
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
if !conntrack.Enabled || err != nil {
|
if d.connectionManager == nil || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return conntrack.NewConn(conn)
|
return d.connectionManager.TrackConn(conn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||||
if !conntrack.Enabled || err != nil {
|
if d.connectionManager == nil || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return conntrack.NewPacketConn(conn)
|
return d.connectionManager.TrackPacketConn(conn), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const (
|
|||||||
TypeSSMAPI = "ssm-api"
|
TypeSSMAPI = "ssm-api"
|
||||||
TypeCCM = "ccm"
|
TypeCCM = "ccm"
|
||||||
TypeOCM = "ocm"
|
TypeOCM = "ocm"
|
||||||
|
TypeOOMKiller = "oom-killer"
|
||||||
TypeMySQL = "mysql"
|
TypeMySQL = "mysql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,6 +87,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
case TypeAnyTLS:
|
case TypeAnyTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
|
case TypeTailscale:
|
||||||
|
return "Tailscale"
|
||||||
case TypeMySQL:
|
case TypeMySQL:
|
||||||
return "MySQL"
|
return "MySQL"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import (
|
|||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -21,6 +23,7 @@ type Instance struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
instance *box.Box
|
instance *box.Box
|
||||||
|
connectionManager adapter.ConnectionManager
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
cacheFile adapter.CacheFile
|
cacheFile adapter.CacheFile
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
@@ -84,6 +87,15 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if s.oomKiller && C.IsIos {
|
||||||
|
if !common.Any(options.Services, func(it option.Service) bool {
|
||||||
|
return it.Type == C.TypeOOMKiller
|
||||||
|
}) {
|
||||||
|
options.Services = append(options.Services, option.Service{
|
||||||
|
Type: C.TypeOOMKiller,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||||
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||||
i := &Instance{
|
i := &Instance{
|
||||||
@@ -101,6 +113,7 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
i.instance = boxInstance
|
i.instance = boxInstance
|
||||||
|
i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx)
|
||||||
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
||||||
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||||
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
@@ -36,6 +35,7 @@ type StartedService struct {
|
|||||||
handler PlatformHandler
|
handler PlatformHandler
|
||||||
debug bool
|
debug bool
|
||||||
logMaxLines int
|
logMaxLines int
|
||||||
|
oomKiller bool
|
||||||
// workingDirectory string
|
// workingDirectory string
|
||||||
// tempDirectory string
|
// tempDirectory string
|
||||||
// userID int
|
// userID int
|
||||||
@@ -67,6 +67,7 @@ type ServiceOptions struct {
|
|||||||
Handler PlatformHandler
|
Handler PlatformHandler
|
||||||
Debug bool
|
Debug bool
|
||||||
LogMaxLines int
|
LogMaxLines int
|
||||||
|
OOMKiller bool
|
||||||
// WorkingDirectory string
|
// WorkingDirectory string
|
||||||
// TempDirectory string
|
// TempDirectory string
|
||||||
// UserID int
|
// UserID int
|
||||||
@@ -81,6 +82,7 @@ func NewStartedService(options ServiceOptions) *StartedService {
|
|||||||
handler: options.Handler,
|
handler: options.Handler,
|
||||||
debug: options.Debug,
|
debug: options.Debug,
|
||||||
logMaxLines: options.LogMaxLines,
|
logMaxLines: options.LogMaxLines,
|
||||||
|
oomKiller: options.OOMKiller,
|
||||||
// workingDirectory: options.WorkingDirectory,
|
// workingDirectory: options.WorkingDirectory,
|
||||||
// tempDirectory: options.TempDirectory,
|
// tempDirectory: options.TempDirectory,
|
||||||
// userID: options.UserID,
|
// userID: options.UserID,
|
||||||
@@ -407,12 +409,14 @@ func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server
|
|||||||
|
|
||||||
func (s *StartedService) readStatus() *Status {
|
func (s *StartedService) readStatus() *Status {
|
||||||
var status Status
|
var status Status
|
||||||
status.Memory = memory.Inuse()
|
status.Memory = memory.Total()
|
||||||
status.Goroutines = int32(runtime.NumGoroutine())
|
status.Goroutines = int32(runtime.NumGoroutine())
|
||||||
status.ConnectionsOut = int32(conntrack.Count())
|
|
||||||
s.serviceAccess.RLock()
|
s.serviceAccess.RLock()
|
||||||
nowService := s.instance
|
nowService := s.instance
|
||||||
s.serviceAccess.RUnlock()
|
s.serviceAccess.RUnlock()
|
||||||
|
if nowService != nil && nowService.connectionManager != nil {
|
||||||
|
status.ConnectionsOut = int32(nowService.connectionManager.Count())
|
||||||
|
}
|
||||||
if nowService != nil {
|
if nowService != nil {
|
||||||
if clashServer := nowService.clashServer; clashServer != nil {
|
if clashServer := nowService.clashServer; clashServer != nil {
|
||||||
status.TrafficAvailable = true
|
status.TrafficAvailable = true
|
||||||
@@ -993,7 +997,12 @@ func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
conntrack.Close()
|
s.serviceAccess.RLock()
|
||||||
|
nowService := s.instance
|
||||||
|
s.serviceAccess.RUnlock()
|
||||||
|
if nowService != nil && nowService.connectionManager != nil {
|
||||||
|
nowService.connectionManager.CloseAll()
|
||||||
|
}
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
debug.go
8
debug.go
@@ -3,11 +3,11 @@ package box
|
|||||||
import (
|
import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
func applyDebugOptions(options option.DebugOptions) error {
|
||||||
applyDebugListenOption(options)
|
applyDebugListenOption(options)
|
||||||
if options.GCPercent != nil {
|
if options.GCPercent != nil {
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
@@ -26,9 +26,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
}
|
}
|
||||||
if options.MemoryLimit.Value() != 0 {
|
if options.MemoryLimit.Value() != 0 {
|
||||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||||
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
return E.New("legacy oom_killer in debug options is removed, use oom-killer service instead")
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.13.0-rc.7
|
#### 1.13.0
|
||||||
|
|
||||||
* Add advertise tags support for Tailscale endpoint
|
|
||||||
|
|
||||||
Important changes since 1.12:
|
Important changes since 1.12:
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ Important changes since 1.12:
|
|||||||
* Improve `local` DNS server **12**
|
* Improve `local` DNS server **12**
|
||||||
* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for listen and dial fields **13**
|
* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for listen and dial fields **13**
|
||||||
* Add `bind_address_no_port` option for dial fields **14**
|
* Add `bind_address_no_port` option for dial fields **14**
|
||||||
* Add system interface and relay server options for Tailscale endpoint **15**
|
* Add system interface, relay server and advertise tags options for Tailscale endpoint **15**
|
||||||
* Add Claude Code Multiplexer service **16**
|
* Add Claude Code Multiplexer service **16**
|
||||||
* Add OpenAI Codex Multiplexer service **17**
|
* Add OpenAI Codex Multiplexer service **17**
|
||||||
* Apple/Android: Refactor GUI
|
* Apple/Android: Refactor GUI
|
||||||
@@ -170,6 +168,10 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu
|
|||||||
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
|
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
|
||||||
use NaiveProxy instead for TLS fingerprint resistance.
|
use NaiveProxy instead for TLS fingerprint resistance.
|
||||||
|
|
||||||
|
#### 1.12.23
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.13.0-rc.5
|
#### 1.13.0-rc.5
|
||||||
|
|
||||||
* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound
|
* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ func NewCommandServer(handler CommandServerHandler, platformInterface PlatformIn
|
|||||||
Handler: (*platformHandler)(server),
|
Handler: (*platformHandler)(server),
|
||||||
Debug: sDebug,
|
Debug: sDebug,
|
||||||
LogMaxLines: sLogMaxLines,
|
LogMaxLines: sLogMaxLines,
|
||||||
|
OOMKiller: memoryLimitEnabled,
|
||||||
// WorkingDirectory: sWorkingPath,
|
// WorkingDirectory: sWorkingPath,
|
||||||
// TempDirectory: sTempPath,
|
// TempDirectory: sTempPath,
|
||||||
// UserID: sUserID,
|
// UserID: sUserID,
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"with_utls",
|
"with_utls",
|
||||||
"with_naive_outbound",
|
"with_naive_outbound",
|
||||||
"with_clash_api",
|
"with_clash_api",
|
||||||
"with_conntrack",
|
|
||||||
"badlinkname",
|
"badlinkname",
|
||||||
"tfogo_checklinkname0",
|
"tfogo_checklinkname0",
|
||||||
"with_tailscale",
|
"with_tailscale",
|
||||||
@@ -59,7 +58,6 @@
|
|||||||
"with_wireguard",
|
"with_wireguard",
|
||||||
"with_utls",
|
"with_utls",
|
||||||
"with_clash_api",
|
"with_clash_api",
|
||||||
"with_conntrack",
|
|
||||||
"badlinkname",
|
"badlinkname",
|
||||||
"tfogo_checklinkname0",
|
"tfogo_checklinkname0",
|
||||||
"with_tailscale",
|
"with_tailscale",
|
||||||
@@ -90,7 +88,6 @@
|
|||||||
"with_utls",
|
"with_utls",
|
||||||
"with_naive_outbound",
|
"with_naive_outbound",
|
||||||
"with_clash_api",
|
"with_clash_api",
|
||||||
"with_conntrack",
|
|
||||||
"badlinkname",
|
"badlinkname",
|
||||||
"tfogo_checklinkname0",
|
"tfogo_checklinkname0",
|
||||||
"with_dhcp",
|
"with_dhcp",
|
||||||
@@ -134,7 +131,6 @@
|
|||||||
"with_naive_outbound",
|
"with_naive_outbound",
|
||||||
"with_purego",
|
"with_purego",
|
||||||
"with_clash_api",
|
"with_clash_api",
|
||||||
"with_conntrack",
|
|
||||||
"badlinkname",
|
"badlinkname",
|
||||||
"tfogo_checklinkname0",
|
"tfogo_checklinkname0",
|
||||||
"with_tailscale",
|
"with_tailscale",
|
||||||
|
|||||||
@@ -4,20 +4,23 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var memoryLimitEnabled bool
|
||||||
|
|
||||||
func SetMemoryLimit(enabled bool) {
|
func SetMemoryLimit(enabled bool) {
|
||||||
const memoryLimit = 45 * 1024 * 1024
|
memoryLimitEnabled = enabled
|
||||||
const memoryLimitGo = memoryLimit / 1.5
|
const memoryLimitGo = 45 * 1024 * 1024
|
||||||
if enabled {
|
if enabled {
|
||||||
runtimeDebug.SetGCPercent(10)
|
runtimeDebug.SetGCPercent(10)
|
||||||
|
if C.IsIos {
|
||||||
runtimeDebug.SetMemoryLimit(memoryLimitGo)
|
runtimeDebug.SetMemoryLimit(memoryLimitGo)
|
||||||
conntrack.KillerEnabled = true
|
}
|
||||||
conntrack.MemoryLimit = memoryLimit
|
|
||||||
} else {
|
} else {
|
||||||
runtimeDebug.SetGCPercent(100)
|
runtimeDebug.SetGCPercent(100)
|
||||||
|
if C.IsIos {
|
||||||
runtimeDebug.SetMemoryLimit(math.MaxInt64)
|
runtimeDebug.SetMemoryLimit(math.MaxInt64)
|
||||||
conntrack.KillerEnabled = false
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
go.mod
70
go.mod
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/caddyserver/certmagic v0.25.0
|
github.com/caddyserver/certmagic v0.25.0
|
||||||
github.com/coder/websocket v1.8.14
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/database64128/tfo-go/v2 v2.3.1
|
github.com/database64128/tfo-go/v2 v2.3.2
|
||||||
github.com/go-chi/chi/v5 v5.2.3
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/go-mysql-org/go-mysql v1.13.0
|
github.com/go-mysql-org/go-mysql v1.13.0
|
||||||
@@ -28,13 +28,13 @@ require (
|
|||||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8
|
github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6
|
||||||
github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8
|
github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.11
|
github.com/sagernet/gomobile v0.1.12
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||||
github.com/sagernet/sing v0.8.0-beta.16
|
github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07
|
||||||
github.com/sagernet/sing-mux v0.3.4
|
github.com/sagernet/sing-mux v0.3.4
|
||||||
github.com/sagernet/sing-quic v0.6.0-beta.13
|
github.com/sagernet/sing-quic v0.6.0-beta.13
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
@@ -55,7 +55,7 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
|
||||||
golang.org/x/mod v0.31.0
|
golang.org/x/mod v0.31.0
|
||||||
golang.org/x/net v0.48.0
|
golang.org/x/net v0.48.0
|
||||||
golang.org/x/sys v0.39.0
|
golang.org/x/sys v0.41.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||||
google.golang.org/grpc v1.77.0
|
google.golang.org/grpc v1.77.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
@@ -110,35 +110,35 @@ require (
|
|||||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/safchain/ethtool v0.3.0 // indirect
|
github.com/safchain/ethtool v0.3.0 // indirect
|
||||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe // indirect
|
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
|
|||||||
199
go.sum
199
go.sum
@@ -12,7 +12,6 @@ github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAf
|
|||||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
||||||
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
||||||
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
|
||||||
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
|
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
|
||||||
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
||||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||||
@@ -30,10 +29,9 @@ github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
|||||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||||
github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM=
|
github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM=
|
||||||
github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc=
|
github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc=
|
||||||
github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw=
|
github.com/database64128/tfo-go/v2 v2.3.2 h1:UhZMKiMq3swZGUiETkLBDzQnZBPSAeBMClpJGlnJ5Fw=
|
||||||
github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU=
|
github.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||||
@@ -64,18 +62,12 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-mysql-org/go-mysql v1.13.0 h1:Hlsa5x1bX/wBFtMbdIOmb6YzyaVNBWnwrb8gSIEPMDc=
|
|
||||||
github.com/go-mysql-org/go-mysql v1.13.0/go.mod h1:FQxw17uRbFvMZFK+dPtIPufbU46nBdrGaxOw0ac9MFs=
|
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
||||||
github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk=
|
github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk=
|
||||||
github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||||
@@ -112,13 +104,6 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
|
|||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE=
|
github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE=
|
||||||
github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ=
|
github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ=
|
||||||
github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8=
|
github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8=
|
||||||
@@ -149,16 +134,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5
|
|||||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
|
||||||
github.com/pingcap/errors v0.11.5-0.20250318082626-8f80e5cb09ec h1:3EiGmeJWoNixU+EwllIn26x6s4njiWRXewdx2zlYa84=
|
|
||||||
github.com/pingcap/errors v0.11.5-0.20250318082626-8f80e5cb09ec/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
|
|
||||||
github.com/pingcap/log v1.1.1-0.20241212030209-7e3ff8601a2a h1:WIhmJBlNGmnCWH6TLMdZfNEDaiU8cFpZe3iaqDbQ0M8=
|
|
||||||
github.com/pingcap/log v1.1.1-0.20241212030209-7e3ff8601a2a/go.mod h1:ORfBOFp1eteu2odzsyaxI+b8TzJwgjwyQcGhI+9SfEA=
|
|
||||||
github.com/pingcap/tidb/pkg/parser v0.0.0-20250421232622-526b2c79173d h1:3Ej6eTuLZp25p3aH/EXdReRHY12hjZYs3RrGp7iLdag=
|
|
||||||
github.com/pingcap/tidb/pkg/parser v0.0.0-20250421232622-526b2c79173d/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE=
|
|
||||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@@ -166,8 +143,6 @@ github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyf
|
|||||||
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||||
@@ -177,72 +152,72 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
|
|||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||||
github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8 h1:XcZiLUXnYE74RvqVdsyxgIInBuFaZbABx2Hom5U6uuk=
|
github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6 h1:Ato+guxmEL4uezcYV1UUUDpAv9HlcJQ7BZt2zpnzjuw=
|
||||||
github.com/sagernet/cronet-go v0.0.0-20260221042137-abd78bb191a8/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
github.com/sagernet/cronet-go v0.0.0-20260227112944-17c7ef18afa6/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
||||||
github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8 h1:uaUy9opPmPYD+viUeUnBzT+lw5b19j6pC/iKew7u13I=
|
github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6 h1:0ldSjcR5Gt/o+otTvUAmJ28FCLab9lnlpEhxRCMQpRA=
|
||||||
github.com/sagernet/cronet-go/all v0.0.0-20260221042137-abd78bb191a8/go.mod h1:Gn1d0D8adjp7mlgSv+/pVLJsG+engIMBp/R4+1MOhlk=
|
github.com/sagernet/cronet-go/all v0.0.0-20260227112944-17c7ef18afa6/go.mod h1:xVwYoNCyv9tF7W1RJlUdDbT4bn5tyqtyTe1P1ZY2VP8=
|
||||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe h1:iKIZJsvD+D3sdAzAeeOodJBxnFL9OVs1LTq3xnmQ6wQ=
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d h1:tudlBYdQHIWctKIdf7pceBOFIUIISK6yFivwsxhxDk0=
|
||||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260227112350-bf468eec914d/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
||||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:/YhWKKVb3uQ5JmBQwFEOKg8QK2w0Ky6dxEb/UHrhQww=
|
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d h1:F5EsQlIknj0HlExBFR4EXW69dYj0MpK1HCpKhL/weEs=
|
||||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
|
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
|
||||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe h1:h+XF746wRtYKavUeS8//Vro6s9f0F6+pI8VQFLMLg6E=
|
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d h1:9SQ6I2Y2radd6RyWEfV+9s1Q9Kth54B6gBHuJWNzQas=
|
||||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
|
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260227112350-bf468eec914d/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
|
||||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:yMs96D9ErwAG8gEHV6zaQ5cp9ZPNBHExxJ5+u8cZ644=
|
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d h1:+XoeknBi6+s6epDAS3BkEsp5zGqEJsT9L8JEcaq+0nE=
|
||||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
|
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
|
||||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:HUJtGjXcB+70W+YfeLgue6X1u69XLN0Ar56Ipg3gtvY=
|
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d h1:poqfhHJAg+7BtABn4cue7V4y8Kb2eZ1Cy0j+bhDangw=
|
||||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
|
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
|
||||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:ivo7JwVqDTMf/qVfpKYdwcIc+NzKGyMJ/WLj/TTNYXg=
|
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d h1:nH6rtfqWbui9zQPjd18cpvZncGvn21UcVLtmeUoQKXs=
|
||||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
|
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
|
||||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:HdWJLwa/Ie3jsueJ0O2mZd4V/NP1UJ6bamdcNHWsYEo=
|
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d h1:HtnjWZzSQBaP29XJ5NoIps1TVZ7DUC7R0NH7IyhJ5Ag=
|
||||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
|
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
|
||||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:A9PWi2xCI+TCr9ALr+BO76WCCk1JnRyjeEH0/+rdyRc=
|
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d h1:E2DWx0Agrj8Fi745S+otYW+W0rL2I8+Z2rZCFqGYPvQ=
|
||||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
|
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
|
||||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:aMOUWbGjkPBFqObA+uAJOfVuBkHfvz2sibNgOJqjuBs=
|
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d h1:j7f/rBwPlO1RpFQeM35QVHymVXGVo6d8WTz4i4SjcPo=
|
||||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
|
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
|
||||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe h1:CzE+sJ2iOvJwOuZhpiDV5VlQrBaNJAZhDCafly+TH9c=
|
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d h1:hz8kkcHGMe7QBTpbqkaw89ZFsfX+UN5F5RIDrroDkx8=
|
||||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
|
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260227112350-bf468eec914d/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
|
||||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe h1:2grC2CeyUiYVgqG7BGKpJvjFzYv0wL64QMoBqOHVZsI=
|
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d h1:TNFaO19ySEyqG79j5+dYb+w4ivusrTXanWuogmC4VM0=
|
||||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
|
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
|
||||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:+N9/LauocInR5kxXU+L5bQe1bndCZUC+6L0FaozWZNI=
|
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d h1:Ewc/wR3yu/hOwG/p49nI9TwYmYv3Llm5DA6fSb1w8hY=
|
||||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
|
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
|
||||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe h1:mRJcjGtKG/eaPL4sZ4Ij+e7aLdg1AEXNI1PgRnxI6H8=
|
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d h1:PJ24NkPNpMrLGNRdb6moEqJo8gfhYcIRZmQD8jPPCJk=
|
||||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
|
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe h1:UkWiTAxUAjTtsu7e52cvMrmbShz+ahTdGkhF9mEIIZU=
|
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d h1:IaUghNA8cOmmwvzUPKPsfhiG0KmpWpE0mFZl85T5/Bw=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
|
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260227112350-bf468eec914d/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:giJVex0bwZy+DwmPwfZ+NZmafBRTsaZ+QUaD2Fkacxs=
|
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d h1:whbeDcr9dDWPr45Is9QV6OHAncrBWLJtPuo4uyEJFBg=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
|
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe h1:XkjAQkciY78eSMF/9VdaWRWb+OfPvoIxVKx5gHGfSIg=
|
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d h1:ecHgaGMvikNYjsfULekdXjL/cQJXCS38yvHaKVMWtXc=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
|
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe h1:8uDfbPXAL0MWqGI8bm6YJghRmGvK08z4jEIGoODKqTI=
|
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d h1:no7Cb54+vv1bQ39zFp+JIHKO8Tu3sTwqz8SoOAuV/Ek=
|
||||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
|
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
|
||||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe h1:Ucs4htbATTdG7YGHCyQ4vMYRhltVvsapZ95THRNssr4=
|
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d h1:DqBSbam9KAzBgDInOoNy4K0baSJyxGWESxrDewU5aSs=
|
||||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
|
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
|
||||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe h1:oeQjTH4lveV4M7/hqOJFfwQ9UfWvkFZEXTc00R2acuk=
|
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d h1:fOR5i+hRyjG8ZzPSG6URkoTKr5qYOJfxZ58zd8HBteM=
|
||||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
|
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
|
||||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe h1:NWABhpSuXcN61hF0CUqwliJXxEbmHidoAHxtB61V3GA=
|
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d h1:hEQGQI+PfUzYBVas4NWw8WiEUsATco6vwv+t4qTtgtw=
|
||||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
|
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260227112350-bf468eec914d/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
|
||||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe h1:+0VrQdlGR/zLjPzinXFqFR2sdzF2BXoXu7f8xaMuwtg=
|
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d h1:AzzJ5AtZlwTbU5QOSixZdPLTjzWKCun3AobQChKy0W8=
|
||||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
|
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260227112350-bf468eec914d/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
|
||||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe h1:DYW55QJOZBI4Znjhc0IiusF+IMg4R2dHPX0KnZC6gSo=
|
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d h1:9Tp7s/WX4DZLx4ues8G38G2OV7eQbeuU2COEZEbGcF0=
|
||||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
|
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
|
||||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe h1:xbbZtyXOxYJMplsyv371ddQb7QrEnyXIIGdUK/3WNTE=
|
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d h1:T9EVZKTyZHOamwevomUZnJ6TQNc09I/BwK+L5HJCJj8=
|
||||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
|
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
|
||||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe h1:YSH2lVT+Sn29lQQbwhDpxZvGjVSg80SUfW4JQ8vM3aA=
|
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d h1:FZmThI7xScJRPERFiA4L2l9KCwA0oi8/lEOajIKEtUQ=
|
||||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
|
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260227112350-bf468eec914d/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
|
||||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:gQ1veofYJr8Z1hBVM2PIrn4+EMKvwh+zWpYBr+mxgQ8=
|
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d h1:BCC/b8bL0dD9Q4ghgKABV/EsMe0J8GE/l7hcRdPkUXQ=
|
||||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
|
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
|
||||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:e2TMlbEottRCDfTWxUSw4Jl5dK8IInV02XIvLKVjLbM=
|
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d h1:3l463BXnC/X42ow2zqHm9Y/K4GM6aRsKUIZBcFxr2+Q=
|
||||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
|
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
|
||||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe h1:Cgh+DP/Ns1djisz+LFxA1nEhyF6EEU5ZdVxNTkiX2BI=
|
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d h1:+XHEZ/z5NgPfjOAzOwfbQzR+42qaDNB0nv+fAOcd6Pc=
|
||||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
|
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260227112350-bf468eec914d/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
|
||||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe h1:VCtjRmkI1IkKdWQ3Jh7j/ze5fhBQJZo1JR70cVKLaKw=
|
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d h1:sYWbP+qCt9Rhb1yGaIRY7HVLtaQZmrHWR0obc5+Q1qc=
|
||||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
|
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
|
||||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe h1:SKePXZMEPUY5zA1VFBPbPOxZsfb/wkMNZAvjPO7hL+I=
|
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d h1:r6eOVlAfmcUMD5nfz+mPd/aORevUKhcvxA1z1GdPnG8=
|
||||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260221041448-e52d68fd87fe/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
|
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260227112350-bf468eec914d/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
|
||||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||||
github.com/sagernet/gomobile v0.1.11 h1:niMQAspvuThup5eRZQpsGcbM76zAvnsGr7RUIpnQMDQ=
|
github.com/sagernet/gomobile v0.1.12 h1:XwzjZaclFF96deLqwAgK8gU3w0M2A8qxgDmhV+A0wjg=
|
||||||
github.com/sagernet/gomobile v0.1.11/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
github.com/sagernet/gomobile v0.1.12/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o=
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4=
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
@@ -251,8 +226,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
|
|||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||||
github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA=
|
github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07 h1:LQqb+xtR5uqF6bePmJQ3sAToF/kMCjxSnz17HnboXA8=
|
||||||
github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.8.0-beta.16.0.20260227013657-e419e9875a07/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
|
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
|
||||||
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||||
github.com/sagernet/sing-quic v0.6.0-beta.13 h1:umDr6GC5fVbOIoTvqV4544wY61zEN+ObQwVGNP8sX1M=
|
github.com/sagernet/sing-quic v0.6.0-beta.13 h1:umDr6GC5fVbOIoTvqV4544wY61zEN+ObQwVGNP8sX1M=
|
||||||
@@ -275,17 +250,11 @@ github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:
|
|||||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
|
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
|
||||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
@@ -340,19 +309,10 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
|
||||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
|
||||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
|
||||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||||
@@ -362,7 +322,6 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4
|
|||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
@@ -370,34 +329,28 @@ golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1i
|
|||||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
@@ -405,12 +358,8 @@ golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
|||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -428,18 +377,12 @@ google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
|||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
|
||||||
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.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
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.0-20210107192922-496545a6307b/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=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||||
|
|||||||
10
include/oom_killer.go
Normal file
10
include/oom_killer.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/service/oomkiller"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerOOMKillerService(registry *service.Registry) {
|
||||||
|
oomkiller.RegisterService(registry)
|
||||||
|
}
|
||||||
@@ -140,6 +140,7 @@ func ServiceRegistry() *service.Registry {
|
|||||||
registerDERPService(registry)
|
registerDERPService(registry)
|
||||||
registerCCMService(registry)
|
registerCCMService(registry)
|
||||||
registerOCMService(registry)
|
registerOCMService(registry)
|
||||||
|
registerOOMKillerService(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package option
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
|
"github.com/sagernet/sing/common/byteformats"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,8 +31,10 @@ type NaiveOutboundOptions struct {
|
|||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
InsecureConcurrency int `json:"insecure_concurrency,omitempty"`
|
InsecureConcurrency int `json:"insecure_concurrency,omitempty"`
|
||||||
ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"`
|
ExtraHeaders badoption.HTTPHeader `json:"extra_headers,omitempty"`
|
||||||
|
ReceiveWindow *byteformats.MemoryBytes `json:"stream_receive_window,omitempty"`
|
||||||
UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
UDPOverTCP *UDPOverTCPOptions `json:"udp_over_tcp,omitempty"`
|
||||||
QUIC bool `json:"quic,omitempty"`
|
QUIC bool `json:"quic,omitempty"`
|
||||||
QUICCongestionControl string `json:"quic_congestion_control,omitempty"`
|
QUICCongestionControl string `json:"quic_congestion_control,omitempty"`
|
||||||
|
QUICSessionReceiveWindow *byteformats.MemoryBytes `json:"quic_session_receive_window,omitempty"`
|
||||||
OutboundTLSOptionsContainer
|
OutboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
|||||||
14
option/oom_killer.go
Normal file
14
option/oom_killer.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/byteformats"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OOMKillerServiceOptions struct {
|
||||||
|
MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"`
|
||||||
|
SafetyMargin *byteformats.MemoryBytes `json:"safety_margin,omitempty"`
|
||||||
|
MinInterval badoption.Duration `json:"min_interval,omitempty"`
|
||||||
|
MaxInterval badoption.Duration `json:"max_interval,omitempty"`
|
||||||
|
ChecksBeforeLimit int `json:"checks_before_limit,omitempty"`
|
||||||
|
}
|
||||||
@@ -254,6 +254,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
return h.uotClient.ListenPacket(ctx, destination)
|
return h.uotClient.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) InterfaceUpdated() {
|
||||||
|
h.client.Engine().CloseAllConnections()
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Outbound) Close() error {
|
func (h *Outbound) Close() error {
|
||||||
return h.client.Close()
|
return h.client.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
Ephemeral: options.Ephemeral,
|
Ephemeral: options.Ephemeral,
|
||||||
AuthKey: options.AuthKey,
|
AuthKey: options.AuthKey,
|
||||||
ControlURL: options.ControlURL,
|
ControlURL: options.ControlURL,
|
||||||
|
AdvertiseTags: options.AdvertiseTags,
|
||||||
Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger},
|
Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger},
|
||||||
LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) {
|
LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) {
|
||||||
return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions())
|
return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions())
|
||||||
@@ -363,12 +364,10 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
|||||||
Prefs: ipn.Prefs{
|
Prefs: ipn.Prefs{
|
||||||
RouteAll: t.acceptRoutes,
|
RouteAll: t.acceptRoutes,
|
||||||
AdvertiseRoutes: t.advertiseRoutes,
|
AdvertiseRoutes: t.advertiseRoutes,
|
||||||
AdvertiseTags: t.advertiseTags,
|
|
||||||
},
|
},
|
||||||
RouteAllSet: true,
|
RouteAllSet: true,
|
||||||
ExitNodeIPSet: true,
|
ExitNodeIPSet: true,
|
||||||
AdvertiseRoutesSet: true,
|
AdvertiseRoutesSet: true,
|
||||||
AdvertiseTagsSet: true,
|
|
||||||
RelayServerPortSet: true,
|
RelayServerPortSet: true,
|
||||||
RelayServerStaticEndpointsSet: true,
|
RelayServerStaticEndpointsSet: true,
|
||||||
}
|
}
|
||||||
|
|||||||
120
route/conn.go
120
route/conn.go
@@ -44,16 +44,52 @@ func (m *ConnectionManager) Start(stage adapter.StartStage) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ConnectionManager) Close() error {
|
func (m *ConnectionManager) Count() int {
|
||||||
m.access.Lock()
|
return m.connections.Len()
|
||||||
defer m.access.Unlock()
|
|
||||||
for element := m.connections.Front(); element != nil; element = element.Next() {
|
|
||||||
common.Close(element.Value)
|
|
||||||
}
|
}
|
||||||
m.connections.Init()
|
|
||||||
|
func (m *ConnectionManager) CloseAll() {
|
||||||
|
m.access.Lock()
|
||||||
|
var closers []io.Closer
|
||||||
|
for element := m.connections.Front(); element != nil; {
|
||||||
|
nextElement := element.Next()
|
||||||
|
closers = append(closers, element.Value)
|
||||||
|
m.connections.Remove(element)
|
||||||
|
element = nextElement
|
||||||
|
}
|
||||||
|
m.access.Unlock()
|
||||||
|
for _, closer := range closers {
|
||||||
|
common.Close(closer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ConnectionManager) Close() error {
|
||||||
|
m.CloseAll()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ConnectionManager) TrackConn(conn net.Conn) net.Conn {
|
||||||
|
m.access.Lock()
|
||||||
|
element := m.connections.PushBack(conn)
|
||||||
|
m.access.Unlock()
|
||||||
|
return &trackedConn{
|
||||||
|
Conn: conn,
|
||||||
|
manager: m,
|
||||||
|
element: element,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ConnectionManager) TrackPacketConn(conn net.PacketConn) net.PacketConn {
|
||||||
|
m.access.Lock()
|
||||||
|
element := m.connections.PushBack(conn)
|
||||||
|
m.access.Unlock()
|
||||||
|
return &trackedPacketConn{
|
||||||
|
PacketConn: conn,
|
||||||
|
manager: m,
|
||||||
|
element: element,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var (
|
var (
|
||||||
@@ -92,14 +128,6 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co
|
|||||||
if metadata.TLSFragment || metadata.TLSRecordFragment {
|
if metadata.TLSFragment || metadata.TLSRecordFragment {
|
||||||
remoteConn = tf.NewConn(remoteConn, ctx, metadata.TLSFragment, metadata.TLSRecordFragment, metadata.TLSFragmentFallbackDelay)
|
remoteConn = tf.NewConn(remoteConn, ctx, metadata.TLSFragment, metadata.TLSRecordFragment, metadata.TLSFragmentFallbackDelay)
|
||||||
}
|
}
|
||||||
m.access.Lock()
|
|
||||||
element := m.connections.PushBack(conn)
|
|
||||||
m.access.Unlock()
|
|
||||||
onClose = N.AppendClose(onClose, func(it error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.connections.Remove(element)
|
|
||||||
})
|
|
||||||
var done atomic.Bool
|
var done atomic.Bool
|
||||||
if m.kickWriteHandshake(ctx, conn, remoteConn, false, &done, onClose) {
|
if m.kickWriteHandshake(ctx, conn, remoteConn, false, &done, onClose) {
|
||||||
return
|
return
|
||||||
@@ -216,14 +244,6 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial
|
|||||||
ctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout)
|
ctx, conn = canceler.NewPacketConn(ctx, conn, udpTimeout)
|
||||||
}
|
}
|
||||||
destination := bufio.NewPacketConn(remotePacketConn)
|
destination := bufio.NewPacketConn(remotePacketConn)
|
||||||
m.access.Lock()
|
|
||||||
element := m.connections.PushBack(conn)
|
|
||||||
m.access.Unlock()
|
|
||||||
onClose = N.AppendClose(onClose, func(it error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.connections.Remove(element)
|
|
||||||
})
|
|
||||||
var done atomic.Bool
|
var done atomic.Bool
|
||||||
go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose)
|
go m.packetConnectionCopy(ctx, conn, destination, false, &done, onClose)
|
||||||
go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose)
|
go m.packetConnectionCopy(ctx, destination, conn, true, &done, onClose)
|
||||||
@@ -242,7 +262,9 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn,
|
|||||||
destination.Close()
|
destination.Close()
|
||||||
}
|
}
|
||||||
if done.Swap(true) {
|
if done.Swap(true) {
|
||||||
|
if onClose != nil {
|
||||||
onClose(err)
|
onClose(err)
|
||||||
|
}
|
||||||
common.Close(source, destination)
|
common.Close(source, destination)
|
||||||
}
|
}
|
||||||
if !direction {
|
if !direction {
|
||||||
@@ -303,8 +325,10 @@ func (m *ConnectionManager) kickWriteHandshake(ctx context.Context, source net.C
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !done.Swap(true) {
|
if !done.Swap(true) {
|
||||||
|
if onClose != nil {
|
||||||
onClose(err)
|
onClose(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
common.Close(source, destination)
|
common.Close(source, destination)
|
||||||
if !direction {
|
if !direction {
|
||||||
m.logger.ErrorContext(ctx, "connection upload handshake: ", err)
|
m.logger.ErrorContext(ctx, "connection upload handshake: ", err)
|
||||||
@@ -334,7 +358,59 @@ func (m *ConnectionManager) packetConnectionCopy(ctx context.Context, source N.P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !done.Swap(true) {
|
if !done.Swap(true) {
|
||||||
|
if onClose != nil {
|
||||||
onClose(err)
|
onClose(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
common.Close(source, destination)
|
common.Close(source, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type trackedConn struct {
|
||||||
|
net.Conn
|
||||||
|
manager *ConnectionManager
|
||||||
|
element *list.Element[io.Closer]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedConn) Close() error {
|
||||||
|
c.manager.access.Lock()
|
||||||
|
c.manager.connections.Remove(c.element)
|
||||||
|
c.manager.access.Unlock()
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedConn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedConn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedConn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type trackedPacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
manager *ConnectionManager
|
||||||
|
element *list.Element[io.Closer]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedPacketConn) Close() error {
|
||||||
|
c.manager.access.Lock()
|
||||||
|
c.manager.connections.Remove(c.element)
|
||||||
|
c.manager.access.Unlock()
|
||||||
|
return c.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedPacketConn) Upstream() any {
|
||||||
|
return bufio.NewPacketConn(c.PacketConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedPacketConn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackedPacketConn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/common/settings"
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -48,6 +47,7 @@ type NetworkManager struct {
|
|||||||
powerListener winpowrprof.EventListener
|
powerListener winpowrprof.EventListener
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
platformInterface adapter.PlatformInterface
|
platformInterface adapter.PlatformInterface
|
||||||
|
connectionManager adapter.ConnectionManager
|
||||||
endpoint adapter.EndpointManager
|
endpoint adapter.EndpointManager
|
||||||
inbound adapter.InboundManager
|
inbound adapter.InboundManager
|
||||||
outbound adapter.OutboundManager
|
outbound adapter.OutboundManager
|
||||||
@@ -90,6 +90,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options
|
|||||||
},
|
},
|
||||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||||
|
connectionManager: service.FromContext[adapter.ConnectionManager](ctx),
|
||||||
endpoint: service.FromContext[adapter.EndpointManager](ctx),
|
endpoint: service.FromContext[adapter.EndpointManager](ctx),
|
||||||
inbound: service.FromContext[adapter.InboundManager](ctx),
|
inbound: service.FromContext[adapter.InboundManager](ctx),
|
||||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
@@ -450,7 +451,9 @@ func (r *NetworkManager) UpdateWIFIState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *NetworkManager) ResetNetwork() {
|
func (r *NetworkManager) ResetNetwork() {
|
||||||
conntrack.Close()
|
if r.connectionManager != nil {
|
||||||
|
r.connectionManager.CloseAll()
|
||||||
|
}
|
||||||
|
|
||||||
for _, endpoint := range r.endpoint.Endpoints() {
|
for _, endpoint := range r.endpoint.Endpoints() {
|
||||||
listener, isListener := endpoint.(adapter.InterfaceUpdateListener)
|
listener, isListener := endpoint.(adapter.InterfaceUpdateListener)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -80,7 +79,6 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
injectable.NewConnectionEx(ctx, conn, metadata, onClose)
|
injectable.NewConnectionEx(ctx, conn, metadata, onClose)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conntrack.KillerCheck()
|
|
||||||
metadata.Network = N.NetworkTCP
|
metadata.Network = N.NetworkTCP
|
||||||
switch metadata.Destination.Fqdn {
|
switch metadata.Destination.Fqdn {
|
||||||
case mux.Destination.Fqdn:
|
case mux.Destination.Fqdn:
|
||||||
@@ -216,8 +214,6 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
injectable.NewPacketConnectionEx(ctx, conn, metadata, onClose)
|
injectable.NewPacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conntrack.KillerCheck()
|
|
||||||
|
|
||||||
// TODO: move to UoT
|
// TODO: move to UoT
|
||||||
metadata.Network = N.NetworkUDP
|
metadata.Network = N.NetworkUDP
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -79,6 +80,35 @@ func isHopByHopHeader(header string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
weeklyWindowSeconds = 604800
|
||||||
|
weeklyWindowMinutes = weeklyWindowSeconds / 60
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseInt64Header(headers http.Header, headerName string) (int64, bool) {
|
||||||
|
headerValue := strings.TrimSpace(headers.Get(headerName))
|
||||||
|
if headerValue == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
parsedValue, parseError := strconv.ParseInt(headerValue, 10, 64)
|
||||||
|
if parseError != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return parsedValue, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint {
|
||||||
|
resetAtUnix, hasResetAt := parseInt64Header(headers, "anthropic-ratelimit-unified-7d-reset")
|
||||||
|
if !hasResetAt || resetAtUnix <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WeeklyCycleHint{
|
||||||
|
WindowMinutes: weeklyWindowMinutes,
|
||||||
|
ResetAt: time.Unix(resetAtUnix, 0).UTC(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
boxService.Adapter
|
boxService.Adapter
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -392,6 +422,7 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, requestModel string, anthropicBetaHeader string, messagesCount int, username string) {
|
func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, requestModel string, anthropicBetaHeader string, messagesCount int, username string) {
|
||||||
|
weeklyCycleHint := extractWeeklyCycleHint(response.Header)
|
||||||
mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
|
mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
|
||||||
isStreaming := err == nil && mediaType == "text/event-stream"
|
isStreaming := err == nil && mediaType == "text/event-stream"
|
||||||
|
|
||||||
@@ -417,7 +448,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
|
|||||||
if usage.InputTokens > 0 || usage.OutputTokens > 0 {
|
if usage.InputTokens > 0 || usage.OutputTokens > 0 {
|
||||||
if responseModel != "" {
|
if responseModel != "" {
|
||||||
contextWindow := detectContextWindow(anthropicBetaHeader, usage.InputTokens)
|
contextWindow := detectContextWindow(anthropicBetaHeader, usage.InputTokens)
|
||||||
s.usageTracker.AddUsage(
|
s.usageTracker.AddUsageWithCycleHint(
|
||||||
responseModel,
|
responseModel,
|
||||||
contextWindow,
|
contextWindow,
|
||||||
messagesCount,
|
messagesCount,
|
||||||
@@ -428,6 +459,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
|
|||||||
usage.CacheCreation.Ephemeral5mInputTokens,
|
usage.CacheCreation.Ephemeral5mInputTokens,
|
||||||
usage.CacheCreation.Ephemeral1hInputTokens,
|
usage.CacheCreation.Ephemeral1hInputTokens,
|
||||||
username,
|
username,
|
||||||
|
time.Now(),
|
||||||
|
weeklyCycleHint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,7 +548,7 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
|
|||||||
if accumulatedUsage.InputTokens > 0 || accumulatedUsage.OutputTokens > 0 {
|
if accumulatedUsage.InputTokens > 0 || accumulatedUsage.OutputTokens > 0 {
|
||||||
if responseModel != "" {
|
if responseModel != "" {
|
||||||
contextWindow := detectContextWindow(anthropicBetaHeader, accumulatedUsage.InputTokens)
|
contextWindow := detectContextWindow(anthropicBetaHeader, accumulatedUsage.InputTokens)
|
||||||
s.usageTracker.AddUsage(
|
s.usageTracker.AddUsageWithCycleHint(
|
||||||
responseModel,
|
responseModel,
|
||||||
contextWindow,
|
contextWindow,
|
||||||
messagesCount,
|
messagesCount,
|
||||||
@@ -526,6 +559,8 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
|
|||||||
accumulatedUsage.CacheCreation.Ephemeral5mInputTokens,
|
accumulatedUsage.CacheCreation.Ephemeral5mInputTokens,
|
||||||
accumulatedUsage.CacheCreation.Ephemeral1hInputTokens,
|
accumulatedUsage.CacheCreation.Ephemeral1hInputTokens,
|
||||||
username,
|
username,
|
||||||
|
time.Now(),
|
||||||
|
weeklyCycleHint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ccm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -26,6 +27,7 @@ type UsageStats struct {
|
|||||||
type CostCombination struct {
|
type CostCombination struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
ContextWindow int `json:"context_window"`
|
ContextWindow int `json:"context_window"`
|
||||||
|
WeekStartUnix int64 `json:"week_start_unix,omitempty"`
|
||||||
Total UsageStats `json:"total"`
|
Total UsageStats `json:"total"`
|
||||||
ByUser map[string]UsageStats `json:"by_user"`
|
ByUser map[string]UsageStats `json:"by_user"`
|
||||||
}
|
}
|
||||||
@@ -57,6 +59,7 @@ type UsageStatsJSON struct {
|
|||||||
type CostCombinationJSON struct {
|
type CostCombinationJSON struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
ContextWindow int `json:"context_window"`
|
ContextWindow int `json:"context_window"`
|
||||||
|
WeekStartUnix int64 `json:"week_start_unix,omitempty"`
|
||||||
Total UsageStatsJSON `json:"total"`
|
Total UsageStatsJSON `json:"total"`
|
||||||
ByUser map[string]UsageStatsJSON `json:"by_user"`
|
ByUser map[string]UsageStatsJSON `json:"by_user"`
|
||||||
}
|
}
|
||||||
@@ -64,6 +67,7 @@ type CostCombinationJSON struct {
|
|||||||
type CostsSummaryJSON struct {
|
type CostsSummaryJSON struct {
|
||||||
TotalUSD float64 `json:"total_usd"`
|
TotalUSD float64 `json:"total_usd"`
|
||||||
ByUser map[string]float64 `json:"by_user"`
|
ByUser map[string]float64 `json:"by_user"`
|
||||||
|
ByWeek map[string]float64 `json:"by_week,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AggregatedUsageJSON struct {
|
type AggregatedUsageJSON struct {
|
||||||
@@ -72,6 +76,11 @@ type AggregatedUsageJSON struct {
|
|||||||
Combinations []CostCombinationJSON `json:"combinations"`
|
Combinations []CostCombinationJSON `json:"combinations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WeeklyCycleHint struct {
|
||||||
|
WindowMinutes int64
|
||||||
|
ResetAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type ModelPricing struct {
|
type ModelPricing struct {
|
||||||
InputPrice float64
|
InputPrice float64
|
||||||
OutputPrice float64
|
OutputPrice float64
|
||||||
@@ -312,8 +321,7 @@ func calculateCost(stats UsageStats, model string, contextWindow int) float64 {
|
|||||||
|
|
||||||
cacheCreationCost := 0.0
|
cacheCreationCost := 0.0
|
||||||
if stats.CacheCreation5MinuteInputTokens > 0 || stats.CacheCreation1HourInputTokens > 0 {
|
if stats.CacheCreation5MinuteInputTokens > 0 || stats.CacheCreation1HourInputTokens > 0 {
|
||||||
cacheCreationCost =
|
cacheCreationCost = float64(stats.CacheCreation5MinuteInputTokens)*pricing.CacheWritePrice5Minute +
|
||||||
float64(stats.CacheCreation5MinuteInputTokens)*pricing.CacheWritePrice5Minute +
|
|
||||||
float64(stats.CacheCreation1HourInputTokens)*pricing.CacheWritePrice1Hour
|
float64(stats.CacheCreation1HourInputTokens)*pricing.CacheWritePrice1Hour
|
||||||
} else {
|
} else {
|
||||||
// Backward compatibility for usage files generated before TTL split tracking.
|
// Backward compatibility for usage files generated before TTL split tracking.
|
||||||
@@ -328,46 +336,108 @@ func calculateCost(stats UsageStats, model string, contextWindow int) float64 {
|
|||||||
return math.Round(cost*100) / 100
|
return math.Round(cost*100) / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
|
func roundCost(cost float64) float64 {
|
||||||
u.mutex.Lock()
|
return math.Round(cost*100) / 100
|
||||||
defer u.mutex.Unlock()
|
|
||||||
|
|
||||||
result := &AggregatedUsageJSON{
|
|
||||||
LastUpdated: u.LastUpdated,
|
|
||||||
Combinations: make([]CostCombinationJSON, len(u.Combinations)),
|
|
||||||
Costs: CostsSummaryJSON{
|
|
||||||
TotalUSD: 0,
|
|
||||||
ByUser: make(map[string]float64),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, combo := range u.Combinations {
|
func normalizeCombinations(combinations []CostCombination) {
|
||||||
totalCost := calculateCost(combo.Total, combo.Model, combo.ContextWindow)
|
for index := range combinations {
|
||||||
|
if combinations[index].ByUser == nil {
|
||||||
|
combinations[index].ByUser = make(map[string]UsageStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.Costs.TotalUSD += totalCost
|
func addUsageToCombinations(
|
||||||
|
combinations *[]CostCombination,
|
||||||
|
model string,
|
||||||
|
contextWindow int,
|
||||||
|
weekStartUnix int64,
|
||||||
|
messagesCount int,
|
||||||
|
inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,
|
||||||
|
user string,
|
||||||
|
) {
|
||||||
|
var matchedCombination *CostCombination
|
||||||
|
for index := range *combinations {
|
||||||
|
combination := &(*combinations)[index]
|
||||||
|
if combination.Model == model && combination.ContextWindow == contextWindow && combination.WeekStartUnix == weekStartUnix {
|
||||||
|
matchedCombination = combination
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
comboJSON := CostCombinationJSON{
|
if matchedCombination == nil {
|
||||||
Model: combo.Model,
|
newCombination := CostCombination{
|
||||||
ContextWindow: combo.ContextWindow,
|
Model: model,
|
||||||
|
ContextWindow: contextWindow,
|
||||||
|
WeekStartUnix: weekStartUnix,
|
||||||
|
Total: UsageStats{},
|
||||||
|
ByUser: make(map[string]UsageStats),
|
||||||
|
}
|
||||||
|
*combinations = append(*combinations, newCombination)
|
||||||
|
matchedCombination = &(*combinations)[len(*combinations)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if cacheCreationTokens == 0 {
|
||||||
|
cacheCreationTokens = cacheCreation5MinuteTokens + cacheCreation1HourTokens
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedCombination.Total.RequestCount++
|
||||||
|
matchedCombination.Total.MessagesCount += messagesCount
|
||||||
|
matchedCombination.Total.InputTokens += inputTokens
|
||||||
|
matchedCombination.Total.OutputTokens += outputTokens
|
||||||
|
matchedCombination.Total.CacheReadInputTokens += cacheReadTokens
|
||||||
|
matchedCombination.Total.CacheCreationInputTokens += cacheCreationTokens
|
||||||
|
matchedCombination.Total.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens
|
||||||
|
matchedCombination.Total.CacheCreation1HourInputTokens += cacheCreation1HourTokens
|
||||||
|
|
||||||
|
if user != "" {
|
||||||
|
userStats := matchedCombination.ByUser[user]
|
||||||
|
userStats.RequestCount++
|
||||||
|
userStats.MessagesCount += messagesCount
|
||||||
|
userStats.InputTokens += inputTokens
|
||||||
|
userStats.OutputTokens += outputTokens
|
||||||
|
userStats.CacheReadInputTokens += cacheReadTokens
|
||||||
|
userStats.CacheCreationInputTokens += cacheCreationTokens
|
||||||
|
userStats.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens
|
||||||
|
userStats.CacheCreation1HourInputTokens += cacheCreation1HourTokens
|
||||||
|
matchedCombination.ByUser[user] = userStats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) {
|
||||||
|
result := make([]CostCombinationJSON, len(combinations))
|
||||||
|
var totalCost float64
|
||||||
|
|
||||||
|
for index, combination := range combinations {
|
||||||
|
combinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ContextWindow)
|
||||||
|
totalCost += combinationTotalCost
|
||||||
|
|
||||||
|
combinationJSON := CostCombinationJSON{
|
||||||
|
Model: combination.Model,
|
||||||
|
ContextWindow: combination.ContextWindow,
|
||||||
|
WeekStartUnix: combination.WeekStartUnix,
|
||||||
Total: UsageStatsJSON{
|
Total: UsageStatsJSON{
|
||||||
RequestCount: combo.Total.RequestCount,
|
RequestCount: combination.Total.RequestCount,
|
||||||
MessagesCount: combo.Total.MessagesCount,
|
MessagesCount: combination.Total.MessagesCount,
|
||||||
InputTokens: combo.Total.InputTokens,
|
InputTokens: combination.Total.InputTokens,
|
||||||
OutputTokens: combo.Total.OutputTokens,
|
OutputTokens: combination.Total.OutputTokens,
|
||||||
CacheReadInputTokens: combo.Total.CacheReadInputTokens,
|
CacheReadInputTokens: combination.Total.CacheReadInputTokens,
|
||||||
CacheCreationInputTokens: combo.Total.CacheCreationInputTokens,
|
CacheCreationInputTokens: combination.Total.CacheCreationInputTokens,
|
||||||
CacheCreation5MinuteInputTokens: combo.Total.CacheCreation5MinuteInputTokens,
|
CacheCreation5MinuteInputTokens: combination.Total.CacheCreation5MinuteInputTokens,
|
||||||
CacheCreation1HourInputTokens: combo.Total.CacheCreation1HourInputTokens,
|
CacheCreation1HourInputTokens: combination.Total.CacheCreation1HourInputTokens,
|
||||||
CostUSD: totalCost,
|
CostUSD: combinationTotalCost,
|
||||||
},
|
},
|
||||||
ByUser: make(map[string]UsageStatsJSON),
|
ByUser: make(map[string]UsageStatsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
for user, userStats := range combo.ByUser {
|
for user, userStats := range combination.ByUser {
|
||||||
userCost := calculateCost(userStats, combo.Model, combo.ContextWindow)
|
userCost := calculateCost(userStats, combination.Model, combination.ContextWindow)
|
||||||
result.Costs.ByUser[user] += userCost
|
if aggregateUserCosts != nil {
|
||||||
|
aggregateUserCosts[user] += userCost
|
||||||
|
}
|
||||||
|
|
||||||
comboJSON.ByUser[user] = UsageStatsJSON{
|
combinationJSON.ByUser[user] = UsageStatsJSON{
|
||||||
RequestCount: userStats.RequestCount,
|
RequestCount: userStats.RequestCount,
|
||||||
MessagesCount: userStats.MessagesCount,
|
MessagesCount: userStats.MessagesCount,
|
||||||
InputTokens: userStats.InputTokens,
|
InputTokens: userStats.InputTokens,
|
||||||
@@ -380,12 +450,80 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Combinations[i] = comboJSON
|
result[index] = combinationJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, roundCost(totalCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatUTCOffsetLabel(timestamp time.Time) string {
|
||||||
|
_, offsetSeconds := timestamp.Zone()
|
||||||
|
sign := "+"
|
||||||
|
if offsetSeconds < 0 {
|
||||||
|
sign = "-"
|
||||||
|
offsetSeconds = -offsetSeconds
|
||||||
|
}
|
||||||
|
offsetHours := offsetSeconds / 3600
|
||||||
|
offsetMinutes := (offsetSeconds % 3600) / 60
|
||||||
|
if offsetMinutes == 0 {
|
||||||
|
return fmt.Sprintf("UTC%s%d", sign, offsetHours)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("UTC%s%d:%02d", sign, offsetHours, offsetMinutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatWeekStartKey(cycleStartAt time.Time) string {
|
||||||
|
localCycleStart := cycleStartAt.In(time.Local)
|
||||||
|
return fmt.Sprintf("%s %s", localCycleStart.Format("2006-01-02 15:04:05"), formatUTCOffsetLabel(localCycleStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildByWeekCost(combinations []CostCombination) map[string]float64 {
|
||||||
|
byWeek := make(map[string]float64)
|
||||||
|
for _, combination := range combinations {
|
||||||
|
if combination.WeekStartUnix <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()
|
||||||
|
weekKey := formatWeekStartKey(weekStartAt)
|
||||||
|
byWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ContextWindow)
|
||||||
|
}
|
||||||
|
for weekKey, weekCost := range byWeek {
|
||||||
|
byWeek[weekKey] = roundCost(weekCost)
|
||||||
|
}
|
||||||
|
return byWeek
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 {
|
||||||
|
if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
windowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute
|
||||||
|
return cycleHint.ResetAt.UTC().Add(-windowDuration).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
|
||||||
|
u.mutex.Lock()
|
||||||
|
defer u.mutex.Unlock()
|
||||||
|
|
||||||
|
result := &AggregatedUsageJSON{
|
||||||
|
LastUpdated: u.LastUpdated,
|
||||||
|
Costs: CostsSummaryJSON{
|
||||||
|
TotalUSD: 0,
|
||||||
|
ByUser: make(map[string]float64),
|
||||||
|
ByWeek: make(map[string]float64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser)
|
||||||
|
result.Combinations = globalCombinationsJSON
|
||||||
|
result.Costs.TotalUSD = totalCost
|
||||||
|
result.Costs.ByWeek = buildByWeekCost(u.Combinations)
|
||||||
|
|
||||||
|
if len(result.Costs.ByWeek) == 0 {
|
||||||
|
result.Costs.ByWeek = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100
|
|
||||||
for user, cost := range result.Costs.ByUser {
|
for user, cost := range result.Costs.ByUser {
|
||||||
result.Costs.ByUser[user] = math.Round(cost*100) / 100
|
result.Costs.ByUser[user] = roundCost(cost)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -395,6 +533,9 @@ func (u *AggregatedUsage) Load() error {
|
|||||||
u.mutex.Lock()
|
u.mutex.Lock()
|
||||||
defer u.mutex.Unlock()
|
defer u.mutex.Unlock()
|
||||||
|
|
||||||
|
u.LastUpdated = time.Time{}
|
||||||
|
u.Combinations = nil
|
||||||
|
|
||||||
data, err := os.ReadFile(u.filePath)
|
data, err := os.ReadFile(u.filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -415,12 +556,7 @@ func (u *AggregatedUsage) Load() error {
|
|||||||
|
|
||||||
u.LastUpdated = temp.LastUpdated
|
u.LastUpdated = temp.LastUpdated
|
||||||
u.Combinations = temp.Combinations
|
u.Combinations = temp.Combinations
|
||||||
|
normalizeCombinations(u.Combinations)
|
||||||
for i := range u.Combinations {
|
|
||||||
if u.Combinations[i].ByUser == nil {
|
|
||||||
u.Combinations[i].ByUser = make(map[string]UsageStats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -454,6 +590,18 @@ func (u *AggregatedUsage) AddUsage(
|
|||||||
messagesCount int,
|
messagesCount int,
|
||||||
inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,
|
inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,
|
||||||
user string,
|
user string,
|
||||||
|
) error {
|
||||||
|
return u.AddUsageWithCycleHint(model, contextWindow, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user, time.Now(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AggregatedUsage) AddUsageWithCycleHint(
|
||||||
|
model string,
|
||||||
|
contextWindow int,
|
||||||
|
messagesCount int,
|
||||||
|
inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens int64,
|
||||||
|
user string,
|
||||||
|
observedAt time.Time,
|
||||||
|
cycleHint *WeeklyCycleHint,
|
||||||
) error {
|
) error {
|
||||||
if model == "" {
|
if model == "" {
|
||||||
return E.New("model cannot be empty")
|
return E.New("model cannot be empty")
|
||||||
@@ -461,59 +609,17 @@ func (u *AggregatedUsage) AddUsage(
|
|||||||
if contextWindow <= 0 {
|
if contextWindow <= 0 {
|
||||||
return E.New("contextWindow must be positive")
|
return E.New("contextWindow must be positive")
|
||||||
}
|
}
|
||||||
|
if observedAt.IsZero() {
|
||||||
|
observedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
u.mutex.Lock()
|
u.mutex.Lock()
|
||||||
defer u.mutex.Unlock()
|
defer u.mutex.Unlock()
|
||||||
|
|
||||||
u.LastUpdated = time.Now()
|
u.LastUpdated = observedAt
|
||||||
|
weekStartUnix := deriveWeekStartUnix(cycleHint)
|
||||||
|
|
||||||
// Find or create combination
|
addUsageToCombinations(&u.Combinations, model, contextWindow, weekStartUnix, messagesCount, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, cacheCreation5MinuteTokens, cacheCreation1HourTokens, user)
|
||||||
var combo *CostCombination
|
|
||||||
for i := range u.Combinations {
|
|
||||||
if u.Combinations[i].Model == model && u.Combinations[i].ContextWindow == contextWindow {
|
|
||||||
combo = &u.Combinations[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if combo == nil {
|
|
||||||
newCombo := CostCombination{
|
|
||||||
Model: model,
|
|
||||||
ContextWindow: contextWindow,
|
|
||||||
Total: UsageStats{},
|
|
||||||
ByUser: make(map[string]UsageStats),
|
|
||||||
}
|
|
||||||
u.Combinations = append(u.Combinations, newCombo)
|
|
||||||
combo = &u.Combinations[len(u.Combinations)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if cacheCreationTokens == 0 {
|
|
||||||
cacheCreationTokens = cacheCreation5MinuteTokens + cacheCreation1HourTokens
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update total stats
|
|
||||||
combo.Total.RequestCount++
|
|
||||||
combo.Total.MessagesCount += messagesCount
|
|
||||||
combo.Total.InputTokens += inputTokens
|
|
||||||
combo.Total.OutputTokens += outputTokens
|
|
||||||
combo.Total.CacheReadInputTokens += cacheReadTokens
|
|
||||||
combo.Total.CacheCreationInputTokens += cacheCreationTokens
|
|
||||||
combo.Total.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens
|
|
||||||
combo.Total.CacheCreation1HourInputTokens += cacheCreation1HourTokens
|
|
||||||
|
|
||||||
// Update per-user stats if user is specified
|
|
||||||
if user != "" {
|
|
||||||
userStats := combo.ByUser[user]
|
|
||||||
userStats.RequestCount++
|
|
||||||
userStats.MessagesCount += messagesCount
|
|
||||||
userStats.InputTokens += inputTokens
|
|
||||||
userStats.OutputTokens += outputTokens
|
|
||||||
userStats.CacheReadInputTokens += cacheReadTokens
|
|
||||||
userStats.CacheCreationInputTokens += cacheCreationTokens
|
|
||||||
userStats.CacheCreation5MinuteInputTokens += cacheCreation5MinuteTokens
|
|
||||||
userStats.CacheCreation1HourInputTokens += cacheCreation1HourTokens
|
|
||||||
combo.ByUser[user] = userStats
|
|
||||||
}
|
|
||||||
|
|
||||||
go u.scheduleSave()
|
go u.scheduleSave()
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -71,6 +72,57 @@ func isHopByHopHeader(header string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeRateLimitIdentifier(limitIdentifier string) string {
|
||||||
|
trimmedIdentifier := strings.TrimSpace(strings.ToLower(limitIdentifier))
|
||||||
|
if trimmedIdentifier == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.ReplaceAll(trimmedIdentifier, "_", "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt64Header(headers http.Header, headerName string) (int64, bool) {
|
||||||
|
headerValue := strings.TrimSpace(headers.Get(headerName))
|
||||||
|
if headerValue == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
parsedValue, parseError := strconv.ParseInt(headerValue, 10, 64)
|
||||||
|
if parseError != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return parsedValue, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func weeklyCycleHintForLimit(headers http.Header, limitIdentifier string) *WeeklyCycleHint {
|
||||||
|
normalizedLimitIdentifier := normalizeRateLimitIdentifier(limitIdentifier)
|
||||||
|
if normalizedLimitIdentifier == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
windowHeader := "x-" + normalizedLimitIdentifier + "-secondary-window-minutes"
|
||||||
|
resetHeader := "x-" + normalizedLimitIdentifier + "-secondary-reset-at"
|
||||||
|
|
||||||
|
windowMinutes, hasWindowMinutes := parseInt64Header(headers, windowHeader)
|
||||||
|
resetAtUnix, hasResetAt := parseInt64Header(headers, resetHeader)
|
||||||
|
if !hasWindowMinutes || !hasResetAt || windowMinutes <= 0 || resetAtUnix <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WeeklyCycleHint{
|
||||||
|
WindowMinutes: windowMinutes,
|
||||||
|
ResetAt: time.Unix(resetAtUnix, 0).UTC(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint {
|
||||||
|
activeLimitIdentifier := normalizeRateLimitIdentifier(headers.Get("x-codex-active-limit"))
|
||||||
|
if activeLimitIdentifier != "" {
|
||||||
|
if activeHint := weeklyCycleHintForLimit(headers, activeLimitIdentifier); activeHint != nil {
|
||||||
|
return activeHint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return weeklyCycleHintForLimit(headers, "codex")
|
||||||
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
boxService.Adapter
|
boxService.Adapter
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -404,6 +456,7 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) {
|
func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) {
|
||||||
isChatCompletions := path == "/v1/chat/completions"
|
isChatCompletions := path == "/v1/chat/completions"
|
||||||
|
weeklyCycleHint := extractWeeklyCycleHint(response.Header)
|
||||||
mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
|
mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
|
||||||
isStreaming := err == nil && mediaType == "text/event-stream"
|
isStreaming := err == nil && mediaType == "text/event-stream"
|
||||||
if !isStreaming && !isChatCompletions && response.Header.Get("Content-Type") == "" {
|
if !isStreaming && !isChatCompletions && response.Header.Get("Content-Type") == "" {
|
||||||
@@ -444,7 +497,16 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
|
|||||||
responseModel = requestModel
|
responseModel = requestModel
|
||||||
}
|
}
|
||||||
if responseModel != "" {
|
if responseModel != "" {
|
||||||
s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, serviceTier, username)
|
s.usageTracker.AddUsageWithCycleHint(
|
||||||
|
responseModel,
|
||||||
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
|
cachedTokens,
|
||||||
|
serviceTier,
|
||||||
|
username,
|
||||||
|
time.Now(),
|
||||||
|
weeklyCycleHint,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +606,16 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons
|
|||||||
|
|
||||||
if inputTokens > 0 || outputTokens > 0 {
|
if inputTokens > 0 || outputTokens > 0 {
|
||||||
if responseModel != "" {
|
if responseModel != "" {
|
||||||
s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, serviceTier, username)
|
s.usageTracker.AddUsageWithCycleHint(
|
||||||
|
responseModel,
|
||||||
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
|
cachedTokens,
|
||||||
|
serviceTier,
|
||||||
|
username,
|
||||||
|
time.Now(),
|
||||||
|
weeklyCycleHint,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ocm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -45,6 +46,7 @@ func (u *UsageStats) UnmarshalJSON(data []byte) error {
|
|||||||
type CostCombination struct {
|
type CostCombination struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
ServiceTier string `json:"service_tier,omitempty"`
|
ServiceTier string `json:"service_tier,omitempty"`
|
||||||
|
WeekStartUnix int64 `json:"week_start_unix,omitempty"`
|
||||||
Total UsageStats `json:"total"`
|
Total UsageStats `json:"total"`
|
||||||
ByUser map[string]UsageStats `json:"by_user"`
|
ByUser map[string]UsageStats `json:"by_user"`
|
||||||
}
|
}
|
||||||
@@ -72,6 +74,7 @@ type UsageStatsJSON struct {
|
|||||||
type CostCombinationJSON struct {
|
type CostCombinationJSON struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
ServiceTier string `json:"service_tier,omitempty"`
|
ServiceTier string `json:"service_tier,omitempty"`
|
||||||
|
WeekStartUnix int64 `json:"week_start_unix,omitempty"`
|
||||||
Total UsageStatsJSON `json:"total"`
|
Total UsageStatsJSON `json:"total"`
|
||||||
ByUser map[string]UsageStatsJSON `json:"by_user"`
|
ByUser map[string]UsageStatsJSON `json:"by_user"`
|
||||||
}
|
}
|
||||||
@@ -79,6 +82,7 @@ type CostCombinationJSON struct {
|
|||||||
type CostsSummaryJSON struct {
|
type CostsSummaryJSON struct {
|
||||||
TotalUSD float64 `json:"total_usd"`
|
TotalUSD float64 `json:"total_usd"`
|
||||||
ByUser map[string]float64 `json:"by_user"`
|
ByUser map[string]float64 `json:"by_user"`
|
||||||
|
ByWeek map[string]float64 `json:"by_week,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AggregatedUsageJSON struct {
|
type AggregatedUsageJSON struct {
|
||||||
@@ -87,6 +91,11 @@ type AggregatedUsageJSON struct {
|
|||||||
Combinations []CostCombinationJSON `json:"combinations"`
|
Combinations []CostCombinationJSON `json:"combinations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WeeklyCycleHint struct {
|
||||||
|
WindowMinutes int64
|
||||||
|
ResetAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type ModelPricing struct {
|
type ModelPricing struct {
|
||||||
InputPrice float64
|
InputPrice float64
|
||||||
OutputPrice float64
|
OutputPrice float64
|
||||||
@@ -372,6 +381,10 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardModelFamilies = []modelFamily{
|
standardModelFamilies = []modelFamily{
|
||||||
|
{
|
||||||
|
pattern: regexp.MustCompile(`^gpt-5\.3-codex(?:$|-)`),
|
||||||
|
pricing: gpt52CodexPricing,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`),
|
pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`),
|
||||||
pricing: gpt52CodexPricing,
|
pricing: gpt52CodexPricing,
|
||||||
@@ -388,6 +401,10 @@ var (
|
|||||||
pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`),
|
pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`),
|
||||||
pricing: gpt51CodexPricing,
|
pricing: gpt51CodexPricing,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`),
|
||||||
|
pricing: gpt51CodexMiniPricing,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),
|
pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),
|
||||||
pricing: gpt51CodexPricing,
|
pricing: gpt51CodexPricing,
|
||||||
@@ -538,6 +555,10 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
priorityModelFamilies = []modelFamily{
|
priorityModelFamilies = []modelFamily{
|
||||||
|
{
|
||||||
|
pattern: regexp.MustCompile(`^gpt-5\.3-codex(?:$|-)`),
|
||||||
|
pricing: gpt52CodexPriorityPricing,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`),
|
pattern: regexp.MustCompile(`^gpt-5\.2-codex(?:$|-)`),
|
||||||
pricing: gpt52CodexPriorityPricing,
|
pricing: gpt52CodexPriorityPricing,
|
||||||
@@ -550,6 +571,10 @@ var (
|
|||||||
pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`),
|
pattern: regexp.MustCompile(`^gpt-5\.1-codex(?:$|-)`),
|
||||||
pricing: gpt51CodexPriorityPricing,
|
pricing: gpt51CodexPriorityPricing,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: regexp.MustCompile(`^gpt-5-codex-mini(?:$|-)`),
|
||||||
|
pricing: gpt5MiniPriorityPricing,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),
|
pattern: regexp.MustCompile(`^gpt-5-codex(?:$|-)`),
|
||||||
pricing: gpt51CodexPriorityPricing,
|
pricing: gpt51CodexPriorityPricing,
|
||||||
@@ -677,7 +702,7 @@ func normalizeGPT5Model(model string) string {
|
|||||||
case strings.Contains(model, "-codex-max"):
|
case strings.Contains(model, "-codex-max"):
|
||||||
return "gpt-5.1-codex-max"
|
return "gpt-5.1-codex-max"
|
||||||
case strings.Contains(model, "-codex"):
|
case strings.Contains(model, "-codex"):
|
||||||
return "gpt-5.2-codex"
|
return "gpt-5.3-codex"
|
||||||
case strings.Contains(model, "-chat-latest"):
|
case strings.Contains(model, "-chat-latest"):
|
||||||
return "gpt-5.2-chat-latest"
|
return "gpt-5.2-chat-latest"
|
||||||
case strings.Contains(model, "-pro"):
|
case strings.Contains(model, "-pro"):
|
||||||
@@ -706,42 +731,89 @@ func calculateCost(stats UsageStats, model string, serviceTier string) float64 {
|
|||||||
return math.Round(cost*100) / 100
|
return math.Round(cost*100) / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
|
func roundCost(cost float64) float64 {
|
||||||
u.mutex.Lock()
|
return math.Round(cost*100) / 100
|
||||||
defer u.mutex.Unlock()
|
|
||||||
|
|
||||||
result := &AggregatedUsageJSON{
|
|
||||||
LastUpdated: u.LastUpdated,
|
|
||||||
Combinations: make([]CostCombinationJSON, len(u.Combinations)),
|
|
||||||
Costs: CostsSummaryJSON{
|
|
||||||
TotalUSD: 0,
|
|
||||||
ByUser: make(map[string]float64),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, combo := range u.Combinations {
|
func normalizeCombinations(combinations []CostCombination) {
|
||||||
totalCost := calculateCost(combo.Total, combo.Model, combo.ServiceTier)
|
for index := range combinations {
|
||||||
|
combinations[index].ServiceTier = normalizeServiceTier(combinations[index].ServiceTier)
|
||||||
|
if combinations[index].ByUser == nil {
|
||||||
|
combinations[index].ByUser = make(map[string]UsageStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.Costs.TotalUSD += totalCost
|
func addUsageToCombinations(combinations *[]CostCombination, model string, serviceTier string, weekStartUnix int64, user string, inputTokens, outputTokens, cachedTokens int64) {
|
||||||
|
var matchedCombination *CostCombination
|
||||||
|
for index := range *combinations {
|
||||||
|
combination := &(*combinations)[index]
|
||||||
|
combinationServiceTier := normalizeServiceTier(combination.ServiceTier)
|
||||||
|
if combination.ServiceTier != combinationServiceTier {
|
||||||
|
combination.ServiceTier = combinationServiceTier
|
||||||
|
}
|
||||||
|
if combination.Model == model && combinationServiceTier == serviceTier && combination.WeekStartUnix == weekStartUnix {
|
||||||
|
matchedCombination = combination
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
comboJSON := CostCombinationJSON{
|
if matchedCombination == nil {
|
||||||
Model: combo.Model,
|
newCombination := CostCombination{
|
||||||
ServiceTier: combo.ServiceTier,
|
Model: model,
|
||||||
|
ServiceTier: serviceTier,
|
||||||
|
WeekStartUnix: weekStartUnix,
|
||||||
|
Total: UsageStats{},
|
||||||
|
ByUser: make(map[string]UsageStats),
|
||||||
|
}
|
||||||
|
*combinations = append(*combinations, newCombination)
|
||||||
|
matchedCombination = &(*combinations)[len(*combinations)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedCombination.Total.RequestCount++
|
||||||
|
matchedCombination.Total.InputTokens += inputTokens
|
||||||
|
matchedCombination.Total.OutputTokens += outputTokens
|
||||||
|
matchedCombination.Total.CachedTokens += cachedTokens
|
||||||
|
|
||||||
|
if user != "" {
|
||||||
|
userStats := matchedCombination.ByUser[user]
|
||||||
|
userStats.RequestCount++
|
||||||
|
userStats.InputTokens += inputTokens
|
||||||
|
userStats.OutputTokens += outputTokens
|
||||||
|
userStats.CachedTokens += cachedTokens
|
||||||
|
matchedCombination.ByUser[user] = userStats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCombinationJSON(combinations []CostCombination, aggregateUserCosts map[string]float64) ([]CostCombinationJSON, float64) {
|
||||||
|
result := make([]CostCombinationJSON, len(combinations))
|
||||||
|
var totalCost float64
|
||||||
|
|
||||||
|
for index, combination := range combinations {
|
||||||
|
combinationTotalCost := calculateCost(combination.Total, combination.Model, combination.ServiceTier)
|
||||||
|
totalCost += combinationTotalCost
|
||||||
|
|
||||||
|
combinationJSON := CostCombinationJSON{
|
||||||
|
Model: combination.Model,
|
||||||
|
ServiceTier: combination.ServiceTier,
|
||||||
|
WeekStartUnix: combination.WeekStartUnix,
|
||||||
Total: UsageStatsJSON{
|
Total: UsageStatsJSON{
|
||||||
RequestCount: combo.Total.RequestCount,
|
RequestCount: combination.Total.RequestCount,
|
||||||
InputTokens: combo.Total.InputTokens,
|
InputTokens: combination.Total.InputTokens,
|
||||||
OutputTokens: combo.Total.OutputTokens,
|
OutputTokens: combination.Total.OutputTokens,
|
||||||
CachedTokens: combo.Total.CachedTokens,
|
CachedTokens: combination.Total.CachedTokens,
|
||||||
CostUSD: totalCost,
|
CostUSD: combinationTotalCost,
|
||||||
},
|
},
|
||||||
ByUser: make(map[string]UsageStatsJSON),
|
ByUser: make(map[string]UsageStatsJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
for user, userStats := range combo.ByUser {
|
for user, userStats := range combination.ByUser {
|
||||||
userCost := calculateCost(userStats, combo.Model, combo.ServiceTier)
|
userCost := calculateCost(userStats, combination.Model, combination.ServiceTier)
|
||||||
result.Costs.ByUser[user] += userCost
|
if aggregateUserCosts != nil {
|
||||||
|
aggregateUserCosts[user] += userCost
|
||||||
|
}
|
||||||
|
|
||||||
comboJSON.ByUser[user] = UsageStatsJSON{
|
combinationJSON.ByUser[user] = UsageStatsJSON{
|
||||||
RequestCount: userStats.RequestCount,
|
RequestCount: userStats.RequestCount,
|
||||||
InputTokens: userStats.InputTokens,
|
InputTokens: userStats.InputTokens,
|
||||||
OutputTokens: userStats.OutputTokens,
|
OutputTokens: userStats.OutputTokens,
|
||||||
@@ -750,12 +822,80 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Combinations[i] = comboJSON
|
result[index] = combinationJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, roundCost(totalCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatUTCOffsetLabel(timestamp time.Time) string {
|
||||||
|
_, offsetSeconds := timestamp.Zone()
|
||||||
|
sign := "+"
|
||||||
|
if offsetSeconds < 0 {
|
||||||
|
sign = "-"
|
||||||
|
offsetSeconds = -offsetSeconds
|
||||||
|
}
|
||||||
|
offsetHours := offsetSeconds / 3600
|
||||||
|
offsetMinutes := (offsetSeconds % 3600) / 60
|
||||||
|
if offsetMinutes == 0 {
|
||||||
|
return fmt.Sprintf("UTC%s%d", sign, offsetHours)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("UTC%s%d:%02d", sign, offsetHours, offsetMinutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatWeekStartKey(cycleStartAt time.Time) string {
|
||||||
|
localCycleStart := cycleStartAt.In(time.Local)
|
||||||
|
return fmt.Sprintf("%s %s", localCycleStart.Format("2006-01-02 15:04:05"), formatUTCOffsetLabel(localCycleStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildByWeekCost(combinations []CostCombination) map[string]float64 {
|
||||||
|
byWeek := make(map[string]float64)
|
||||||
|
for _, combination := range combinations {
|
||||||
|
if combination.WeekStartUnix <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()
|
||||||
|
weekKey := formatWeekStartKey(weekStartAt)
|
||||||
|
byWeek[weekKey] += calculateCost(combination.Total, combination.Model, combination.ServiceTier)
|
||||||
|
}
|
||||||
|
for weekKey, weekCost := range byWeek {
|
||||||
|
byWeek[weekKey] = roundCost(weekCost)
|
||||||
|
}
|
||||||
|
return byWeek
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 {
|
||||||
|
if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
windowDuration := time.Duration(cycleHint.WindowMinutes) * time.Minute
|
||||||
|
return cycleHint.ResetAt.UTC().Add(-windowDuration).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
|
||||||
|
u.mutex.Lock()
|
||||||
|
defer u.mutex.Unlock()
|
||||||
|
|
||||||
|
result := &AggregatedUsageJSON{
|
||||||
|
LastUpdated: u.LastUpdated,
|
||||||
|
Costs: CostsSummaryJSON{
|
||||||
|
TotalUSD: 0,
|
||||||
|
ByUser: make(map[string]float64),
|
||||||
|
ByWeek: make(map[string]float64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCombinationsJSON, totalCost := buildCombinationJSON(u.Combinations, result.Costs.ByUser)
|
||||||
|
result.Combinations = globalCombinationsJSON
|
||||||
|
result.Costs.TotalUSD = totalCost
|
||||||
|
result.Costs.ByWeek = buildByWeekCost(u.Combinations)
|
||||||
|
|
||||||
|
if len(result.Costs.ByWeek) == 0 {
|
||||||
|
result.Costs.ByWeek = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100
|
|
||||||
for user, cost := range result.Costs.ByUser {
|
for user, cost := range result.Costs.ByUser {
|
||||||
result.Costs.ByUser[user] = math.Round(cost*100) / 100
|
result.Costs.ByUser[user] = roundCost(cost)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -765,6 +905,9 @@ func (u *AggregatedUsage) Load() error {
|
|||||||
u.mutex.Lock()
|
u.mutex.Lock()
|
||||||
defer u.mutex.Unlock()
|
defer u.mutex.Unlock()
|
||||||
|
|
||||||
|
u.LastUpdated = time.Time{}
|
||||||
|
u.Combinations = nil
|
||||||
|
|
||||||
data, err := os.ReadFile(u.filePath)
|
data, err := os.ReadFile(u.filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -785,13 +928,7 @@ func (u *AggregatedUsage) Load() error {
|
|||||||
|
|
||||||
u.LastUpdated = temp.LastUpdated
|
u.LastUpdated = temp.LastUpdated
|
||||||
u.Combinations = temp.Combinations
|
u.Combinations = temp.Combinations
|
||||||
|
normalizeCombinations(u.Combinations)
|
||||||
for i := range u.Combinations {
|
|
||||||
u.Combinations[i].ServiceTier = normalizeServiceTier(u.Combinations[i].ServiceTier)
|
|
||||||
if u.Combinations[i].ByUser == nil {
|
|
||||||
u.Combinations[i].ByUser = make(map[string]UsageStats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -820,53 +957,26 @@ func (u *AggregatedUsage) Save() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string) error {
|
func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string) error {
|
||||||
|
return u.AddUsageWithCycleHint(model, inputTokens, outputTokens, cachedTokens, serviceTier, user, time.Now(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AggregatedUsage) AddUsageWithCycleHint(model string, inputTokens, outputTokens, cachedTokens int64, serviceTier string, user string, observedAt time.Time, cycleHint *WeeklyCycleHint) error {
|
||||||
if model == "" {
|
if model == "" {
|
||||||
return E.New("model cannot be empty")
|
return E.New("model cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizedServiceTier := normalizeServiceTier(serviceTier)
|
normalizedServiceTier := normalizeServiceTier(serviceTier)
|
||||||
|
if observedAt.IsZero() {
|
||||||
|
observedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
u.mutex.Lock()
|
u.mutex.Lock()
|
||||||
defer u.mutex.Unlock()
|
defer u.mutex.Unlock()
|
||||||
|
|
||||||
u.LastUpdated = time.Now()
|
u.LastUpdated = observedAt
|
||||||
|
weekStartUnix := deriveWeekStartUnix(cycleHint)
|
||||||
|
|
||||||
var combo *CostCombination
|
addUsageToCombinations(&u.Combinations, model, normalizedServiceTier, weekStartUnix, user, inputTokens, outputTokens, cachedTokens)
|
||||||
for i := range u.Combinations {
|
|
||||||
comboServiceTier := normalizeServiceTier(u.Combinations[i].ServiceTier)
|
|
||||||
if u.Combinations[i].ServiceTier != comboServiceTier {
|
|
||||||
u.Combinations[i].ServiceTier = comboServiceTier
|
|
||||||
}
|
|
||||||
if u.Combinations[i].Model == model && comboServiceTier == normalizedServiceTier {
|
|
||||||
combo = &u.Combinations[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if combo == nil {
|
|
||||||
newCombo := CostCombination{
|
|
||||||
Model: model,
|
|
||||||
ServiceTier: normalizedServiceTier,
|
|
||||||
Total: UsageStats{},
|
|
||||||
ByUser: make(map[string]UsageStats),
|
|
||||||
}
|
|
||||||
u.Combinations = append(u.Combinations, newCombo)
|
|
||||||
combo = &u.Combinations[len(u.Combinations)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
combo.Total.RequestCount++
|
|
||||||
combo.Total.InputTokens += inputTokens
|
|
||||||
combo.Total.OutputTokens += outputTokens
|
|
||||||
combo.Total.CachedTokens += cachedTokens
|
|
||||||
|
|
||||||
if user != "" {
|
|
||||||
userStats := combo.ByUser[user]
|
|
||||||
userStats.RequestCount++
|
|
||||||
userStats.InputTokens += inputTokens
|
|
||||||
userStats.OutputTokens += outputTokens
|
|
||||||
userStats.CachedTokens += cachedTokens
|
|
||||||
combo.ByUser[user] = userStats
|
|
||||||
}
|
|
||||||
|
|
||||||
go u.scheduleSave()
|
go u.scheduleSave()
|
||||||
|
|
||||||
|
|||||||
51
service/oomkiller/config.go
Normal file
51
service/oomkiller/config.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package oomkiller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildTimerConfig(options option.OOMKillerServiceOptions, memoryLimit uint64, useAvailable bool) (timerConfig, error) {
|
||||||
|
safetyMargin := uint64(defaultSafetyMargin)
|
||||||
|
if options.SafetyMargin != nil && options.SafetyMargin.Value() > 0 {
|
||||||
|
safetyMargin = options.SafetyMargin.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
minInterval := defaultMinInterval
|
||||||
|
if options.MinInterval != 0 {
|
||||||
|
minInterval = time.Duration(options.MinInterval.Build())
|
||||||
|
if minInterval <= 0 {
|
||||||
|
return timerConfig{}, E.New("min_interval must be greater than 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxInterval := defaultMaxInterval
|
||||||
|
if options.MaxInterval != 0 {
|
||||||
|
maxInterval = time.Duration(options.MaxInterval.Build())
|
||||||
|
if maxInterval <= 0 {
|
||||||
|
return timerConfig{}, E.New("max_interval must be greater than 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxInterval < minInterval {
|
||||||
|
return timerConfig{}, E.New("max_interval must be greater than or equal to min_interval")
|
||||||
|
}
|
||||||
|
|
||||||
|
checksBeforeLimit := defaultChecksBeforeLimit
|
||||||
|
if options.ChecksBeforeLimit != 0 {
|
||||||
|
checksBeforeLimit = options.ChecksBeforeLimit
|
||||||
|
if checksBeforeLimit <= 0 {
|
||||||
|
return timerConfig{}, E.New("checks_before_limit must be greater than 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return timerConfig{
|
||||||
|
memoryLimit: memoryLimit,
|
||||||
|
safetyMargin: safetyMargin,
|
||||||
|
minInterval: minInterval,
|
||||||
|
maxInterval: maxInterval,
|
||||||
|
checksBeforeLimit: checksBeforeLimit,
|
||||||
|
useAvailable: useAvailable,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
192
service/oomkiller/service.go
Normal file
192
service/oomkiller/service.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
//go:build darwin && cgo
|
||||||
|
|
||||||
|
package oomkiller
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <dispatch/dispatch.h>
|
||||||
|
|
||||||
|
static dispatch_source_t memoryPressureSource;
|
||||||
|
|
||||||
|
extern void goMemoryPressureCallback(unsigned long status);
|
||||||
|
|
||||||
|
static void startMemoryPressureMonitor() {
|
||||||
|
memoryPressureSource = dispatch_source_create(
|
||||||
|
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
|
||||||
|
0,
|
||||||
|
DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL,
|
||||||
|
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
|
||||||
|
);
|
||||||
|
dispatch_source_set_event_handler(memoryPressureSource, ^{
|
||||||
|
unsigned long status = dispatch_source_get_data(memoryPressureSource);
|
||||||
|
goMemoryPressureCallback(status);
|
||||||
|
});
|
||||||
|
dispatch_activate(memoryPressureSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stopMemoryPressureMonitor() {
|
||||||
|
if (memoryPressureSource) {
|
||||||
|
dispatch_source_cancel(memoryPressureSource);
|
||||||
|
memoryPressureSource = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
runtimeDebug "runtime/debug"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
boxConstant "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/memory"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *boxService.Registry) {
|
||||||
|
boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalAccess sync.Mutex
|
||||||
|
globalServices []*Service
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
boxService.Adapter
|
||||||
|
logger log.ContextLogger
|
||||||
|
router adapter.Router
|
||||||
|
memoryLimit uint64
|
||||||
|
hasTimerMode bool
|
||||||
|
useAvailable bool
|
||||||
|
timerConfig timerConfig
|
||||||
|
adaptiveTimer *adaptiveTimer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) {
|
||||||
|
s := &Service{
|
||||||
|
Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),
|
||||||
|
logger: logger,
|
||||||
|
router: service.FromContext[adapter.Router](ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.MemoryLimit != nil {
|
||||||
|
s.memoryLimit = options.MemoryLimit.Value()
|
||||||
|
if s.memoryLimit > 0 {
|
||||||
|
s.hasTimerMode = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.timerConfig = config
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.hasTimerMode {
|
||||||
|
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
|
||||||
|
if s.memoryLimit > 0 {
|
||||||
|
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||||
|
} else {
|
||||||
|
s.logger.Info("started memory monitor with available memory detection")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.logger.Info("started memory pressure monitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
globalAccess.Lock()
|
||||||
|
isFirst := len(globalServices) == 0
|
||||||
|
globalServices = append(globalServices, s)
|
||||||
|
globalAccess.Unlock()
|
||||||
|
|
||||||
|
if isFirst {
|
||||||
|
C.startMemoryPressureMonitor()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
if s.adaptiveTimer != nil {
|
||||||
|
s.adaptiveTimer.stop()
|
||||||
|
}
|
||||||
|
globalAccess.Lock()
|
||||||
|
for i, svc := range globalServices {
|
||||||
|
if svc == s {
|
||||||
|
globalServices = append(globalServices[:i], globalServices[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isLast := len(globalServices) == 0
|
||||||
|
globalAccess.Unlock()
|
||||||
|
if isLast {
|
||||||
|
C.stopMemoryPressureMonitor()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goMemoryPressureCallback
|
||||||
|
func goMemoryPressureCallback(status C.ulong) {
|
||||||
|
globalAccess.Lock()
|
||||||
|
services := make([]*Service, len(globalServices))
|
||||||
|
copy(services, globalServices)
|
||||||
|
globalAccess.Unlock()
|
||||||
|
if len(services) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
criticalFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_CRITICAL)
|
||||||
|
warnFlag := C.ulong(C.DISPATCH_MEMORYPRESSURE_WARN)
|
||||||
|
isCritical := status&criticalFlag != 0
|
||||||
|
isWarning := status&warnFlag != 0
|
||||||
|
var level string
|
||||||
|
switch {
|
||||||
|
case isCritical:
|
||||||
|
level = "critical"
|
||||||
|
case isWarning:
|
||||||
|
level = "warning"
|
||||||
|
default:
|
||||||
|
level = "normal"
|
||||||
|
}
|
||||||
|
var freeOSMemory bool
|
||||||
|
for _, s := range services {
|
||||||
|
usage := memory.Total()
|
||||||
|
if s.hasTimerMode {
|
||||||
|
if isCritical {
|
||||||
|
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||||
|
if s.adaptiveTimer != nil {
|
||||||
|
s.adaptiveTimer.startNow()
|
||||||
|
}
|
||||||
|
} else if isWarning {
|
||||||
|
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||||
|
} else {
|
||||||
|
s.logger.Debug("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||||
|
if s.adaptiveTimer != nil {
|
||||||
|
s.adaptiveTimer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if isCritical {
|
||||||
|
s.logger.Error("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB, resetting network")
|
||||||
|
s.router.ResetNetwork()
|
||||||
|
freeOSMemory = true
|
||||||
|
} else if isWarning {
|
||||||
|
s.logger.Warn("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||||
|
} else {
|
||||||
|
s.logger.Debug("memory pressure: ", level, ", usage: ", usage/(1024*1024), " MiB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if freeOSMemory {
|
||||||
|
runtimeDebug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
}
|
||||||
81
service/oomkiller/service_stub.go
Normal file
81
service/oomkiller/service_stub.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//go:build !darwin || !cgo
|
||||||
|
|
||||||
|
package oomkiller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
boxConstant "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/memory"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *boxService.Registry) {
|
||||||
|
boxService.Register[option.OOMKillerServiceOptions](registry, boxConstant.TypeOOMKiller, NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
boxService.Adapter
|
||||||
|
logger log.ContextLogger
|
||||||
|
router adapter.Router
|
||||||
|
adaptiveTimer *adaptiveTimer
|
||||||
|
timerConfig timerConfig
|
||||||
|
hasTimerMode bool
|
||||||
|
useAvailable bool
|
||||||
|
memoryLimit uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OOMKillerServiceOptions) (adapter.Service, error) {
|
||||||
|
s := &Service{
|
||||||
|
Adapter: boxService.NewAdapter(boxConstant.TypeOOMKiller, tag),
|
||||||
|
logger: logger,
|
||||||
|
router: service.FromContext[adapter.Router](ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.MemoryLimit != nil {
|
||||||
|
s.memoryLimit = options.MemoryLimit.Value()
|
||||||
|
}
|
||||||
|
if s.memoryLimit > 0 {
|
||||||
|
s.hasTimerMode = true
|
||||||
|
} else if memory.AvailableSupported() {
|
||||||
|
s.useAvailable = true
|
||||||
|
s.hasTimerMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := buildTimerConfig(options, s.memoryLimit, s.useAvailable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.timerConfig = config
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !s.hasTimerMode {
|
||||||
|
return E.New("memory pressure monitoring is not available on this platform without memory_limit")
|
||||||
|
}
|
||||||
|
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
|
||||||
|
s.adaptiveTimer.start(0)
|
||||||
|
if s.useAvailable {
|
||||||
|
s.logger.Info("started memory monitor with available memory detection")
|
||||||
|
} else {
|
||||||
|
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
if s.adaptiveTimer != nil {
|
||||||
|
s.adaptiveTimer.stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
158
service/oomkiller/service_timer.go
Normal file
158
service/oomkiller/service_timer.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package oomkiller
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtimeDebug "runtime/debug"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultChecksBeforeLimit = 4
|
||||||
|
defaultMinInterval = 500 * time.Millisecond
|
||||||
|
defaultMaxInterval = 10 * time.Second
|
||||||
|
defaultSafetyMargin = 5 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
type adaptiveTimer struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
router adapter.Router
|
||||||
|
memoryLimit uint64
|
||||||
|
safetyMargin uint64
|
||||||
|
minInterval time.Duration
|
||||||
|
maxInterval time.Duration
|
||||||
|
checksBeforeLimit int
|
||||||
|
useAvailable bool
|
||||||
|
|
||||||
|
access sync.Mutex
|
||||||
|
timer *time.Timer
|
||||||
|
previousUsage uint64
|
||||||
|
lastInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type timerConfig struct {
|
||||||
|
memoryLimit uint64
|
||||||
|
safetyMargin uint64
|
||||||
|
minInterval time.Duration
|
||||||
|
maxInterval time.Duration
|
||||||
|
checksBeforeLimit int
|
||||||
|
useAvailable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAdaptiveTimer(logger log.ContextLogger, router adapter.Router, config timerConfig) *adaptiveTimer {
|
||||||
|
return &adaptiveTimer{
|
||||||
|
logger: logger,
|
||||||
|
router: router,
|
||||||
|
memoryLimit: config.memoryLimit,
|
||||||
|
safetyMargin: config.safetyMargin,
|
||||||
|
minInterval: config.minInterval,
|
||||||
|
maxInterval: config.maxInterval,
|
||||||
|
checksBeforeLimit: config.checksBeforeLimit,
|
||||||
|
useAvailable: config.useAvailable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *adaptiveTimer) start(_ uint64) {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
t.startLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *adaptiveTimer) startNow() {
|
||||||
|
t.access.Lock()
|
||||||
|
t.startLocked()
|
||||||
|
t.access.Unlock()
|
||||||
|
t.poll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *adaptiveTimer) startLocked() {
|
||||||
|
if t.timer != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.previousUsage = memory.Total()
|
||||||
|
t.lastInterval = t.minInterval
|
||||||
|
t.timer = time.AfterFunc(t.minInterval, t.poll)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *adaptiveTimer) stop() {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
t.stopLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *adaptiveTimer) stopLocked() {
|
||||||
|
if t.timer != nil {
|
||||||
|
t.timer.Stop()
|
||||||
|
t.timer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *adaptiveTimer) running() bool {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
return t.timer != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *adaptiveTimer) poll() {
|
||||||
|
t.access.Lock()
|
||||||
|
defer t.access.Unlock()
|
||||||
|
if t.timer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
usage := memory.Total()
|
||||||
|
delta := int64(usage) - int64(t.previousUsage)
|
||||||
|
t.previousUsage = usage
|
||||||
|
|
||||||
|
var remaining uint64
|
||||||
|
var triggered bool
|
||||||
|
|
||||||
|
if t.memoryLimit > 0 {
|
||||||
|
if usage >= t.memoryLimit {
|
||||||
|
remaining = 0
|
||||||
|
triggered = true
|
||||||
|
} else {
|
||||||
|
remaining = t.memoryLimit - usage
|
||||||
|
}
|
||||||
|
} else if t.useAvailable {
|
||||||
|
available := memory.Available()
|
||||||
|
if available <= t.safetyMargin {
|
||||||
|
remaining = 0
|
||||||
|
triggered = true
|
||||||
|
} else {
|
||||||
|
remaining = available - t.safetyMargin
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remaining = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if triggered {
|
||||||
|
t.logger.Error("memory threshold reached, usage: ", usage/(1024*1024), " MiB, resetting network")
|
||||||
|
t.router.ResetNetwork()
|
||||||
|
runtimeDebug.FreeOSMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
var interval time.Duration
|
||||||
|
if triggered {
|
||||||
|
interval = t.maxInterval
|
||||||
|
} else if delta <= 0 {
|
||||||
|
interval = t.maxInterval
|
||||||
|
} else if t.checksBeforeLimit <= 0 {
|
||||||
|
interval = t.maxInterval
|
||||||
|
} else {
|
||||||
|
timeToLimit := time.Duration(float64(remaining) / float64(delta) * float64(t.lastInterval))
|
||||||
|
interval = timeToLimit / time.Duration(t.checksBeforeLimit)
|
||||||
|
if interval < t.minInterval {
|
||||||
|
interval = t.minInterval
|
||||||
|
}
|
||||||
|
if interval > t.maxInterval {
|
||||||
|
interval = t.maxInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.lastInterval = interval
|
||||||
|
t.timer.Reset(interval)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user