mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-10-20 19:12:30 +00:00
Close idle connections of unreferenced outbounds and DNS servers
This commit is contained in:
parent
d2cf9747d7
commit
fd9a076d25
40 changed files with 851 additions and 81 deletions
|
|
@ -50,3 +50,11 @@ type OutboundManager interface {
|
|||
Remove(tag string) error
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
|
||||
}
|
||||
|
||||
type IdleConnectionCloser interface {
|
||||
CloseIdleConnections()
|
||||
}
|
||||
|
||||
type Referrer interface {
|
||||
References() []string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,9 +151,9 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
|||
if common.Contains(oTree, problemOutboundTag) {
|
||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
||||
}
|
||||
m.access.Lock()
|
||||
problemOutbound := m.outboundByTag[problemOutboundTag]
|
||||
m.access.Unlock()
|
||||
problemOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {
|
||||
return it.Tag() == problemOutboundTag
|
||||
})
|
||||
if problemOutbound == nil {
|
||||
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]")
|
||||
}
|
||||
|
|
|
|||
1
box.go
1
box.go
|
|
@ -257,6 +257,7 @@ func New(options Options) (*Box, error) {
|
|||
service.MustRegisterPtr(ctx, clashMode)
|
||||
internalServices = append(internalServices, clashMode)
|
||||
}
|
||||
internalServices = append(internalServices, route.NewReferenceManager(ctx, logFactory.NewLogger("reference"), options.Options))
|
||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||
var timeService *tls.TimeServiceWrapper
|
||||
if ntpOptions.Enabled {
|
||||
|
|
|
|||
|
|
@ -40,10 +40,12 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string,
|
|||
return nil, err
|
||||
}
|
||||
return &ManagedTransport{
|
||||
dialer: rawDialer,
|
||||
headers: headers,
|
||||
host: host,
|
||||
tag: tag,
|
||||
dialer: rawDialer,
|
||||
headers: headers,
|
||||
host: host,
|
||||
tag: tag,
|
||||
detour: options.Detour,
|
||||
defaultOutbound: options.DefaultOutbound,
|
||||
factory: func() (innerTransport, error) {
|
||||
return newAppleTransport(ctx, logger, rawDialer, options)
|
||||
},
|
||||
|
|
@ -69,11 +71,13 @@ func NewTransport(ctx context.Context, logger logger.ContextLogger, tag string,
|
|||
return nil, err
|
||||
}
|
||||
managedTransport := &ManagedTransport{
|
||||
cheapRebuild: cheapRebuild,
|
||||
dialer: rawDialer,
|
||||
headers: headers,
|
||||
host: host,
|
||||
tag: tag,
|
||||
cheapRebuild: cheapRebuild,
|
||||
dialer: rawDialer,
|
||||
headers: headers,
|
||||
host: host,
|
||||
tag: tag,
|
||||
detour: options.Detour,
|
||||
defaultOutbound: options.DefaultOutbound,
|
||||
factory: func() (innerTransport, error) {
|
||||
return newTransport(rawDialer, baseTLSConfig, options)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@ type ManagedTransport struct {
|
|||
factory func() (innerTransport, error)
|
||||
cheapRebuild bool
|
||||
|
||||
dialer N.Dialer
|
||||
headers http.Header
|
||||
host string
|
||||
tag string
|
||||
dialer N.Dialer
|
||||
headers http.Header
|
||||
host string
|
||||
tag string
|
||||
detour string
|
||||
defaultOutbound bool
|
||||
}
|
||||
|
||||
type transportEpoch struct {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ import (
|
|||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var (
|
||||
_ adapter.HTTPClientManager = (*Manager)(nil)
|
||||
_ adapter.LifecycleService = (*Manager)(nil)
|
||||
_ adapter.Referrer = (*Manager)(nil)
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
|
|
@ -177,3 +179,27 @@ func (m *Manager) Close() error {
|
|||
m.sharedTransports = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) References() []string {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
var references []string
|
||||
var needDefault bool
|
||||
for _, transport := range m.managedTransports {
|
||||
if transport.detour != "" {
|
||||
references = append(references, transport.detour)
|
||||
} else if transport.defaultOutbound {
|
||||
needDefault = true
|
||||
}
|
||||
}
|
||||
if needDefault {
|
||||
outboundManager := service.FromContext[adapter.OutboundManager](m.ctx)
|
||||
if outboundManager != nil {
|
||||
defaultOutbound := outboundManager.Default()
|
||||
if defaultOutbound != nil {
|
||||
references = append(references, defaultOutbound.Tag())
|
||||
}
|
||||
}
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ type TransportAdapter struct {
|
|||
transportType string
|
||||
transportTag string
|
||||
dependencies []string
|
||||
references []string
|
||||
}
|
||||
|
||||
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
|
||||
|
|
@ -23,10 +24,15 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
|||
if localOptions.DomainResolver != nil && localOptions.DomainResolver.Server != "" {
|
||||
dependencies = append(dependencies, localOptions.DomainResolver.Server)
|
||||
}
|
||||
var references []string
|
||||
if localOptions.Detour != "" {
|
||||
references = []string{localOptions.Detour}
|
||||
}
|
||||
return TransportAdapter{
|
||||
transportType: transportType,
|
||||
transportTag: transportTag,
|
||||
dependencies: dependencies,
|
||||
references: references,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,10 +41,15 @@ func NewTransportAdapterWithRemoteOptions(transportType string, transportTag str
|
|||
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
||||
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
||||
}
|
||||
var references []string
|
||||
if remoteOptions.Detour != "" {
|
||||
references = []string{remoteOptions.Detour}
|
||||
}
|
||||
return TransportAdapter{
|
||||
transportType: transportType,
|
||||
transportTag: transportTag,
|
||||
dependencies: dependencies,
|
||||
references: references,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,3 +64,7 @@ func (a *TransportAdapter) Tag() string {
|
|||
func (a *TransportAdapter) Dependencies() []string {
|
||||
return a.dependencies
|
||||
}
|
||||
|
||||
func (a *TransportAdapter) References() []string {
|
||||
return a.references
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func prepareWorkingDirectory() error {
|
|||
return err
|
||||
}
|
||||
libbox.PromoteOOMDraft()
|
||||
libbox.PromotePowerReportDraft()
|
||||
libbox.DiscardPowerReportDraft()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ func (d *Daemon) configureWorkingDirectoryLocked(directory string) error {
|
|||
return err
|
||||
}
|
||||
libbox.PromoteOOMDraft()
|
||||
libbox.PromotePowerReportDraft()
|
||||
libbox.DiscardPowerReportDraft()
|
||||
d.runtimeWorkingDirectory = directory
|
||||
return nil
|
||||
}
|
||||
|
|
@ -262,7 +262,7 @@ func (d *Daemon) stopServiceLocked(ownerUserID string) error {
|
|||
}
|
||||
}
|
||||
d.powerManager.Close()
|
||||
libbox.PromotePowerReportDraft()
|
||||
libbox.DiscardPowerReportDraft()
|
||||
directory := userWorkingDirectory(ownerUserID)
|
||||
crashReportError := tagUnownedReports(filepath.Join(directory, crashReportsDirectoryName), ownerUserID)
|
||||
if crashReportError != nil {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,6 @@ func PowerReportOptions(startedService *daemon.StartedService) powerreport.Optio
|
|||
}
|
||||
}
|
||||
|
||||
func PromotePowerReportDraft() {
|
||||
powerreport.PromoteDraft(sWorkingPath)
|
||||
func DiscardPowerReportDraft() {
|
||||
powerreport.DiscardDraft(sWorkingPath)
|
||||
}
|
||||
|
|
|
|||
6
go.mod
6
go.mod
|
|
@ -47,14 +47,14 @@ require (
|
|||
github.com/sagernet/quic-go v0.61.0-sing-box-mod.7
|
||||
github.com/sagernet/sing v0.9.6-0.20260927091426-5f9aad7def20
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3
|
||||
github.com/sagernet/sing-mux v0.3.8
|
||||
github.com/sagernet/sing-mux v0.3.9-0.20260927144857-0257b84c582a
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260810065514-53aa8058f8df
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260729104525-103eb5fe5eb6
|
||||
github.com/sagernet/sing-quic v0.7.1-0.20260924092235-6a3a24d65b99
|
||||
github.com/sagernet/sing-quic v0.7.2-0.20261002084117-75c3ac4fa12b
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1
|
||||
github.com/sagernet/sing-snell v0.0.0-20260829071736-20f2eaec77c3
|
||||
github.com/sagernet/sing-snell v0.0.0-20260904135315-bc5a12ac736f
|
||||
github.com/sagernet/sing-tun v0.9.6-0.20260924001923-ddaa4ca25e3b
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260817040617-28bd42667eca
|
||||
github.com/sagernet/sing-vmess v0.2.8
|
||||
|
|
|
|||
12
go.sum
12
go.sum
|
|
@ -322,22 +322,22 @@ github.com/sagernet/sing v0.9.6-0.20260927091426-5f9aad7def20 h1:FRiFCD9vYplVTPr
|
|||
github.com/sagernet/sing v0.9.6-0.20260927091426-5f9aad7def20/go.mod h1:K3Owt3xPhHugvlnlPPxZJ/exXdaJfEPOTNorGk4AXjo=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6++yIa8XlDhxPkpR4p+7RUHVY2KTP9CPIGnWmOlO8=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg=
|
||||
github.com/sagernet/sing-mux v0.3.8 h1:mM0TLUkr36aEhgUHi9Qh60qcAbD2fZcmnJ30937xxrY=
|
||||
github.com/sagernet/sing-mux v0.3.8/go.mod h1:jricpwCOndDsTEM1TM3piJvhL/2Mm7OhLkm+JadJJ1Q=
|
||||
github.com/sagernet/sing-mux v0.3.9-0.20260927144857-0257b84c582a h1:VrlU4QE8Bh4kEeLgvvWLBtyTiuXqc6/5vf3B+xQCTRE=
|
||||
github.com/sagernet/sing-mux v0.3.9-0.20260927144857-0257b84c582a/go.mod h1:jricpwCOndDsTEM1TM3piJvhL/2Mm7OhLkm+JadJJ1Q=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260810065514-53aa8058f8df h1:ogyC7KPTWueN7kXFhKSdHeS5/t/zBjQgAOpWjqnrMos=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260810065514-53aa8058f8df/go.mod h1:4AKZLVcvY3r54UaK2Gbnm7aN8pOwdLz+y4EP0QFZ5Eg=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260729104525-103eb5fe5eb6 h1:ZAvhkor0prJ8KEX9EmTEH+gJ1WlA3ffjS8GZT6KKq9c=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260729104525-103eb5fe5eb6/go.mod h1:PWX7WygD8jpwfqfaGNySXpJYTn0SOwjBI1BKHHC2+Bw=
|
||||
github.com/sagernet/sing-quic v0.7.1-0.20260924092235-6a3a24d65b99 h1:zxN7hz90xUa03Nbrnp6nutArlrQaVHOOvbTYVGx4/PI=
|
||||
github.com/sagernet/sing-quic v0.7.1-0.20260924092235-6a3a24d65b99/go.mod h1:rJFeVxbEej2iuyeCJ3drKW+p5rOgtLIW3XhZ6ty7Fig=
|
||||
github.com/sagernet/sing-quic v0.7.2-0.20261002084117-75c3ac4fa12b h1:2Jx/URJAPt32MEbLmAR5nzH87tO6NQZLRfRFvFqBWuk=
|
||||
github.com/sagernet/sing-quic v0.7.2-0.20261002084117-75c3ac4fa12b/go.mod h1:rJFeVxbEej2iuyeCJ3drKW+p5rOgtLIW3XhZ6ty7Fig=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkGCKNXhbaM=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260829071736-20f2eaec77c3 h1:wjEw0lmCSC+oqh7GD4rbMB/dk7+hZ2VopUj4xM5/Jrc=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260829071736-20f2eaec77c3/go.mod h1:et8Lws4f5QbOrY65DmjevHGup3mijJkhswkto6cwciM=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260904135315-bc5a12ac736f h1:tbTCI+iis5sTmua7a6Q7vebcSRURok/LGgnEafnQAsc=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260904135315-bc5a12ac736f/go.mod h1:et8Lws4f5QbOrY65DmjevHGup3mijJkhswkto6cwciM=
|
||||
github.com/sagernet/sing-tun v0.9.6-0.20260924001923-ddaa4ca25e3b h1:tjQUA4fQi0OWDMMYfRigzvNlBTYyP/01e+P9BT/RZ4g=
|
||||
github.com/sagernet/sing-tun v0.9.6-0.20260924001923-ddaa4ca25e3b/go.mod h1:3EgPst7agntRO7D6GOsiZ1l9FoqdLeuWmKT5TnWkmf0=
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260817040617-28bd42667eca h1:5wA+IE0Fq1CGVLOgSpm0gKKZ03HzugcJ2JyhmDqeX6A=
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-cloudflared"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
|
@ -79,14 +80,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
}
|
||||
|
||||
return &Inbound{
|
||||
Adapter: inbound.NewAdapter(C.TypeCloudflared, tag),
|
||||
service: service,
|
||||
Adapter: inbound.NewAdapter(C.TypeCloudflared, tag),
|
||||
service: service,
|
||||
references: common.FilterNotDefault([]string{options.ControlDialer.Detour, options.TunnelDialer.Detour}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
service *cloudflared.Service
|
||||
service *cloudflared.Service
|
||||
references []string
|
||||
}
|
||||
|
||||
func (i *Inbound) Start(stage adapter.StartStage) error {
|
||||
|
|
@ -255,6 +258,10 @@ func (h *icmpRouterHandler) RouteICMPFlow(source netip.Addr, destination netip.A
|
|||
}
|
||||
}
|
||||
|
||||
func (i *Inbound) References() []string {
|
||||
return i.references
|
||||
}
|
||||
|
||||
var (
|
||||
_ N.HandshakeSuccess = (*routedConn)(nil)
|
||||
_ N.ReaderWithUpstream = (*routedConn)(nil)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ func RegisterSelector(registry *outbound.Registry) {
|
|||
|
||||
var (
|
||||
_ adapter.OutboundGroup = (*Selector)(nil)
|
||||
_ adapter.Referrer = (*Selector)(nil)
|
||||
_ adapter.ConnectionHandler = (*Selector)(nil)
|
||||
_ adapter.PacketConnectionHandler = (*Selector)(nil)
|
||||
)
|
||||
|
|
@ -120,6 +121,10 @@ func (s *Selector) All() []string {
|
|||
return s.tags
|
||||
}
|
||||
|
||||
func (s *Selector) References() []string {
|
||||
return []string{s.Now()}
|
||||
}
|
||||
|
||||
func (s *Selector) SelectOutbound(tag string) bool {
|
||||
detour, loaded := s.outbounds[tag]
|
||||
if !loaded {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ func RegisterURLTest(registry *outbound.Registry) {
|
|||
var (
|
||||
_ adapter.OutboundGroup = (*URLTest)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*URLTest)(nil)
|
||||
_ adapter.Referrer = (*URLTest)(nil)
|
||||
)
|
||||
|
||||
type URLTest struct {
|
||||
|
|
@ -111,6 +112,21 @@ func (s *URLTest) All() []string {
|
|||
return s.tags
|
||||
}
|
||||
|
||||
func (s *URLTest) References() []string {
|
||||
group := s.group
|
||||
if group == nil {
|
||||
return nil
|
||||
}
|
||||
var references []string
|
||||
if group.selectedOutboundTCP != nil {
|
||||
references = append(references, group.selectedOutboundTCP.Tag())
|
||||
}
|
||||
if group.selectedOutboundUDP != nil && group.selectedOutboundUDP != group.selectedOutboundTCP {
|
||||
references = append(references, group.selectedOutboundUDP.Tag())
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
func (s *URLTest) URLTest(ctx context.Context) (map[string]uint16, error) {
|
||||
return s.group.URLTest(ctx)
|
||||
}
|
||||
|
|
@ -488,20 +504,28 @@ func (b *urlTestBatch) test(outbounds []adapter.Outbound, link string, interval
|
|||
func (g *URLTestGroup) performUpdateCheck() {
|
||||
g.updateAccess.Lock()
|
||||
defer g.updateAccess.Unlock()
|
||||
var updated bool
|
||||
var (
|
||||
updated bool
|
||||
selected bool
|
||||
)
|
||||
if outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) {
|
||||
if g.selectedOutboundTCP != nil {
|
||||
updated = true
|
||||
}
|
||||
g.selectedOutboundTCP = outbound
|
||||
selected = true
|
||||
}
|
||||
if outbound, exists := g.Select(N.NetworkUDP); outbound != nil && (g.selectedOutboundUDP == nil || (exists && outbound != g.selectedOutboundUDP)) {
|
||||
if g.selectedOutboundUDP != nil {
|
||||
updated = true
|
||||
}
|
||||
g.selectedOutboundUDP = outbound
|
||||
selected = true
|
||||
}
|
||||
if updated {
|
||||
g.interruptGroup.Interrupt(g.interruptExternalConnections)
|
||||
}
|
||||
if selected {
|
||||
g.history.NotifyUpdated()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
var (
|
||||
_ adapter.Outbound = (*tuic.Outbound)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
|
|
@ -118,6 +119,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
h.client.CloseWithError(E.New("network changed"))
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
h.client.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return h.client.CloseWithError(os.ErrClosed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
var (
|
||||
_ adapter.Outbound = (*tuic.Outbound)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
|
|
@ -210,6 +211,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
h.client.CloseWithError(E.New("network changed"))
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
h.client.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return h.client.CloseWithError(os.ErrClosed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound)
|
||||
}
|
||||
|
||||
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
var (
|
||||
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
|
|
@ -136,6 +140,12 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return common.Close(common.PtrOrNil(h.multiplexDialer))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@ func RegisterInbound(registry *inbound.Registry) {
|
|||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
router adapter.Router
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
service *shadowtls.Service
|
||||
router adapter.Router
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
service *shadowtls.Service
|
||||
references []string
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (adapter.Inbound, error) {
|
||||
|
|
@ -48,6 +49,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
|
||||
if options.HandshakeForServerName != nil {
|
||||
for _, entry := range options.HandshakeForServerName.Entries() {
|
||||
if entry.Value.Detour != "" {
|
||||
inbound.references = append(inbound.references, entry.Value.Detour)
|
||||
}
|
||||
handshakeDialer, err := dialer.New(ctx, entry.Value.DialerOptions, entry.Value.ServerIsDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -67,6 +71,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.Handshake.Detour != "" {
|
||||
inbound.references = append(inbound.references, options.Handshake.Detour)
|
||||
}
|
||||
service, err := shadowtls.NewService(shadowtls.ServiceConfig{
|
||||
Version: options.Version,
|
||||
Password: options.Password,
|
||||
|
|
@ -139,3 +146,7 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou
|
|||
}
|
||||
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (h *Inbound) References() []string {
|
||||
return h.references
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,12 +32,16 @@ type Outbound struct {
|
|||
serverAddr M.Socksaddr
|
||||
}
|
||||
|
||||
var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
var (
|
||||
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type snellClient interface {
|
||||
snellprotocol.Method
|
||||
DialContext(ctx context.Context, destination M.Socksaddr) (net.Conn, error)
|
||||
Reset()
|
||||
CloseIdleConnections()
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
|
@ -143,6 +147,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
h.client.Reset()
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
h.client.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return h.client.Close()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
outbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound)
|
||||
}
|
||||
|
||||
var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
var (
|
||||
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
|
|
@ -50,6 +53,8 @@ type Outbound struct {
|
|||
clientAccess sync.Mutex
|
||||
clientConn net.Conn
|
||||
client *ssh.Client
|
||||
streams int
|
||||
draining bool
|
||||
}
|
||||
|
||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) {
|
||||
|
|
@ -127,14 +132,12 @@ func randomVersion() string {
|
|||
}
|
||||
|
||||
func (s *Outbound) connect(ctx context.Context) (client *ssh.Client, err error) {
|
||||
if s.client != nil {
|
||||
return s.client, nil
|
||||
}
|
||||
|
||||
s.clientAccess.Lock()
|
||||
defer s.clientAccess.Unlock()
|
||||
|
||||
if s.client != nil {
|
||||
s.streams++
|
||||
s.draining = false
|
||||
return s.client, nil
|
||||
}
|
||||
|
||||
|
|
@ -193,13 +196,17 @@ func (s *Outbound) connect(ctx context.Context) (client *ssh.Client, err error)
|
|||
|
||||
s.clientConn = conn
|
||||
s.client = client
|
||||
s.streams = 1
|
||||
s.draining = false
|
||||
|
||||
go func() {
|
||||
client.Wait()
|
||||
conn.Close()
|
||||
s.clientAccess.Lock()
|
||||
s.client = nil
|
||||
s.clientConn = nil
|
||||
if s.client == client {
|
||||
s.client = nil
|
||||
s.clientConn = nil
|
||||
}
|
||||
s.clientAccess.Unlock()
|
||||
}()
|
||||
|
||||
|
|
@ -210,6 +217,42 @@ func (s *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
common.Close(s.clientConn)
|
||||
}
|
||||
|
||||
func (s *Outbound) CloseIdleConnections() {
|
||||
s.clientAccess.Lock()
|
||||
if s.client == nil {
|
||||
s.clientAccess.Unlock()
|
||||
return
|
||||
}
|
||||
s.draining = true
|
||||
if s.streams > 0 {
|
||||
s.clientAccess.Unlock()
|
||||
return
|
||||
}
|
||||
clientConn := s.clientConn
|
||||
s.client = nil
|
||||
s.clientConn = nil
|
||||
s.clientAccess.Unlock()
|
||||
common.Close(clientConn)
|
||||
}
|
||||
|
||||
func (s *Outbound) releaseStream(client *ssh.Client) {
|
||||
s.clientAccess.Lock()
|
||||
if s.client != client {
|
||||
s.clientAccess.Unlock()
|
||||
return
|
||||
}
|
||||
s.streams--
|
||||
if !s.draining || s.streams > 0 {
|
||||
s.clientAccess.Unlock()
|
||||
return
|
||||
}
|
||||
clientConn := s.clientConn
|
||||
s.client = nil
|
||||
s.clientConn = nil
|
||||
s.clientAccess.Unlock()
|
||||
common.Close(clientConn)
|
||||
}
|
||||
|
||||
func (s *Outbound) Close() error {
|
||||
return common.Close(s.clientConn)
|
||||
}
|
||||
|
|
@ -221,9 +264,10 @@ func (s *Outbound) DialContext(ctx context.Context, network string, destination
|
|||
}
|
||||
conn, err := client.Dial(network, destination.String())
|
||||
if err != nil {
|
||||
s.releaseStream(client)
|
||||
return nil, err
|
||||
}
|
||||
return &chanConnWrapper{Conn: conn}, nil
|
||||
return &chanConnWrapper{Conn: conn, onClose: func() { s.releaseStream(client) }}, nil
|
||||
}
|
||||
|
||||
func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
|
|
@ -232,6 +276,14 @@ func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||
|
||||
type chanConnWrapper struct {
|
||||
net.Conn
|
||||
closeOnce sync.Once
|
||||
onClose func()
|
||||
}
|
||||
|
||||
func (c *chanConnWrapper) Close() error {
|
||||
err := c.Conn.Close()
|
||||
c.closeOnce.Do(c.onClose)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *chanConnWrapper) SetDeadline(t time.Time) error {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ import (
|
|||
var (
|
||||
_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*Endpoint)(nil)
|
||||
_ adapter.Referrer = (*Endpoint)(nil)
|
||||
_ dialer.PacketDialerWithDestination = (*Endpoint)(nil)
|
||||
_ tun.Port = (*Endpoint)(nil)
|
||||
)
|
||||
|
|
@ -89,6 +90,7 @@ type Endpoint struct {
|
|||
dnsRouter adapter.DNSRouter
|
||||
network adapter.NetworkManager
|
||||
platformInterface adapter.PlatformInterface
|
||||
detour string
|
||||
server *tsnet.Server
|
||||
stack *stack.Stack
|
||||
icmpForwarder *tun.ICMPForwarder
|
||||
|
|
@ -200,6 +202,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||
queryOptions: dialerQueryOptions,
|
||||
network: service.FromContext[adapter.NetworkManager](ctx),
|
||||
platformInterface: platformInterface,
|
||||
detour: options.Detour,
|
||||
server: &tsnet.Server{
|
||||
Dir: stateDirectory,
|
||||
Hostname: hostname,
|
||||
|
|
@ -251,6 +254,13 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (t *Endpoint) References() []string {
|
||||
if t.detour == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{t.detour}
|
||||
}
|
||||
|
||||
func (t *Endpoint) Start(stage adapter.StartStage) error {
|
||||
switch stage {
|
||||
case adapter.StartStateInitialize:
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ type Inbound struct {
|
|||
fallbackAddr M.Socksaddr
|
||||
fallbackAddrTLSNextProto map[string]M.Socksaddr
|
||||
transport adapter.V2RayServerTransport
|
||||
references []string
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (adapter.Inbound, error) {
|
||||
|
|
@ -61,6 +62,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
return nil, err
|
||||
}
|
||||
inbound.tlsConfig = tlsConfig
|
||||
if options.TLS.Reality != nil && options.TLS.Reality.Enabled && options.TLS.Reality.Handshake.Detour != "" {
|
||||
inbound.references = []string{options.TLS.Reality.Handshake.Detour}
|
||||
}
|
||||
}
|
||||
var fallbackHandler N.TCPConnectionHandlerEx
|
||||
if options.Fallback != nil && options.Fallback.Server != "" || len(options.FallbackForALPN) > 0 {
|
||||
|
|
@ -260,3 +264,7 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
|
|||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
(*Inbound)(h).NewConnection(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (h *Inbound) References() []string {
|
||||
return h.references
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound)
|
||||
}
|
||||
|
||||
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
var (
|
||||
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
|
|
@ -122,6 +126,16 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
transportCloser, isTransportCloser := h.transport.(adapter.IdleConnectionCloser)
|
||||
if isTransportCloser {
|
||||
transportCloser.CloseIdleConnections()
|
||||
}
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound)
|
||||
}
|
||||
|
||||
var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
var (
|
||||
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
|
|
@ -145,6 +148,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
_ = h.client.CloseWithError(E.New("network changed"))
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
h.client.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return h.client.CloseWithError(os.ErrClosed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,14 +35,15 @@ var _ adapter.TCPInjectableInbound = (*Inbound)(nil)
|
|||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
ctx context.Context
|
||||
router adapter.ConnectionRouterEx
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
users []option.VLESSUser
|
||||
service *vless.Service[int]
|
||||
tlsConfig tls.ServerConfig
|
||||
transport adapter.V2RayServerTransport
|
||||
ctx context.Context
|
||||
router adapter.ConnectionRouterEx
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
users []option.VLESSUser
|
||||
service *vless.Service[int]
|
||||
tlsConfig tls.ServerConfig
|
||||
transport adapter.V2RayServerTransport
|
||||
references []string
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (adapter.Inbound, error) {
|
||||
|
|
@ -81,6 +82,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.TLS.Reality != nil && options.TLS.Reality.Enabled && options.TLS.Reality.Handshake.Detour != "" {
|
||||
inbound.references = []string{options.TLS.Reality.Handshake.Detour}
|
||||
}
|
||||
}
|
||||
if options.Transport != nil {
|
||||
inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound))
|
||||
|
|
@ -220,3 +224,7 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
|
|||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
(*Inbound)(h).NewConnection(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (h *Inbound) References() []string {
|
||||
return h.references
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,11 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound)
|
||||
}
|
||||
|
||||
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
var (
|
||||
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
|
|
@ -142,6 +146,16 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
transportCloser, isTransportCloser := h.transport.(adapter.IdleConnectionCloser)
|
||||
if isTransportCloser {
|
||||
transportCloser.CloseIdleConnections()
|
||||
}
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,11 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||
outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound)
|
||||
}
|
||||
|
||||
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
var (
|
||||
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
|
||||
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
|
||||
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
|
|
@ -120,6 +124,16 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) CloseIdleConnections() {
|
||||
transportCloser, isTransportCloser := h.transport.(adapter.IdleConnectionCloser)
|
||||
if isTransportCloser {
|
||||
transportCloser.CloseIdleConnections()
|
||||
}
|
||||
if h.multiplexDialer != nil {
|
||||
h.multiplexDialer.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
|
||||
}
|
||||
|
|
|
|||
235
route/reference.go
Normal file
235
route/reference.go
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/experimental/clashmode"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/observable"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var _ adapter.LifecycleService = (*ReferenceManager)(nil)
|
||||
|
||||
type ReferenceManager struct {
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
rules []option.Rule
|
||||
dnsRules []option.DNSRule
|
||||
staticOutbounds []string
|
||||
staticTransports []string
|
||||
subscriber *observable.Subscriber[struct{}]
|
||||
unreferencedOutbounds map[string]bool
|
||||
unreferencedTransports map[string]bool
|
||||
}
|
||||
|
||||
func NewReferenceManager(ctx context.Context, logger log.ContextLogger, options option.Options) *ReferenceManager {
|
||||
var (
|
||||
staticOutbounds []string
|
||||
staticTransports []string
|
||||
)
|
||||
if options.NTP != nil && options.NTP.Enabled && options.NTP.Detour != "" {
|
||||
staticOutbounds = append(staticOutbounds, options.NTP.Detour)
|
||||
}
|
||||
for _, outboundOptions := range options.Outbounds {
|
||||
staticTransports = appendDomainResolver(staticTransports, outboundOptions.Options)
|
||||
}
|
||||
for _, endpointOptions := range options.Endpoints {
|
||||
staticTransports = appendDomainResolver(staticTransports, endpointOptions.Options)
|
||||
}
|
||||
var (
|
||||
rules []option.Rule
|
||||
dnsRules []option.DNSRule
|
||||
)
|
||||
if options.Route != nil {
|
||||
rules = options.Route.Rules
|
||||
}
|
||||
if options.DNS != nil {
|
||||
dnsRules = options.DNS.Rules
|
||||
}
|
||||
return &ReferenceManager{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
rules: rules,
|
||||
dnsRules: dnsRules,
|
||||
staticOutbounds: staticOutbounds,
|
||||
staticTransports: staticTransports,
|
||||
}
|
||||
}
|
||||
|
||||
func appendDomainResolver(transports []string, rawOptions any) []string {
|
||||
dialerOptionsWrapper, isDialerOptionsWrapper := rawOptions.(option.DialerOptionsWrapper)
|
||||
if !isDialerOptionsWrapper {
|
||||
return transports
|
||||
}
|
||||
dialerOptions := dialerOptionsWrapper.TakeDialerOptions()
|
||||
if dialerOptions.DomainResolver == nil || dialerOptions.DomainResolver.Server == "" {
|
||||
return transports
|
||||
}
|
||||
return append(transports, dialerOptions.DomainResolver.Server)
|
||||
}
|
||||
|
||||
func (m *ReferenceManager) Name() string {
|
||||
return "reference manager"
|
||||
}
|
||||
|
||||
func (m *ReferenceManager) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStarted {
|
||||
return nil
|
||||
}
|
||||
m.subscriber = observable.NewSubscriber[struct{}](1)
|
||||
history := service.PtrFromContext[urltest.HistoryStorage](m.ctx)
|
||||
if history != nil {
|
||||
history.AddUpdateHook(m.subscriber)
|
||||
}
|
||||
clashMode := service.PtrFromContext[clashmode.Manager](m.ctx)
|
||||
if clashMode != nil {
|
||||
clashMode.AddUpdateHook(m.subscriber)
|
||||
}
|
||||
m.update()
|
||||
go m.loop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ReferenceManager) Close() error {
|
||||
if m.subscriber != nil {
|
||||
m.subscriber.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ReferenceManager) loop() {
|
||||
subscription, done := m.subscriber.Subscription()
|
||||
for {
|
||||
select {
|
||||
case <-subscription:
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
m.update()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ReferenceManager) update() {
|
||||
var mode string
|
||||
clashMode := service.PtrFromContext[clashmode.Manager](m.ctx)
|
||||
if clashMode != nil {
|
||||
mode = clashMode.Mode()
|
||||
}
|
||||
outboundManager := service.FromContext[adapter.OutboundManager](m.ctx)
|
||||
endpointManager := service.FromContext[adapter.EndpointManager](m.ctx)
|
||||
inboundManager := service.FromContext[adapter.InboundManager](m.ctx)
|
||||
serviceManager := service.FromContext[adapter.ServiceManager](m.ctx)
|
||||
transportManager := service.FromContext[adapter.DNSTransportManager](m.ctx)
|
||||
networkManager := service.FromContext[adapter.NetworkManager](m.ctx)
|
||||
httpClientManager := service.FromContext[adapter.HTTPClientManager](m.ctx)
|
||||
|
||||
transportQueue := slices.Clone(m.staticTransports)
|
||||
outboundQueue := slices.Clone(m.staticOutbounds)
|
||||
if !collectDNSRuleReferences(m.dnsRules, mode, &transportQueue) {
|
||||
defaultTransport := transportManager.Default()
|
||||
if defaultTransport != nil {
|
||||
transportQueue = append(transportQueue, defaultTransport.Tag())
|
||||
}
|
||||
}
|
||||
transportQueue = append(transportQueue, networkManager.DefaultOptions().DomainResolver)
|
||||
if !collectRuleReferences(m.rules, mode, &outboundQueue, &transportQueue) {
|
||||
defaultOutbound := outboundManager.Default()
|
||||
if defaultOutbound != nil {
|
||||
outboundQueue = append(outboundQueue, defaultOutbound.Tag())
|
||||
}
|
||||
}
|
||||
for _, endpoint := range endpointManager.Endpoints() {
|
||||
outboundQueue = append(outboundQueue, endpoint.Tag())
|
||||
}
|
||||
for _, inbound := range inboundManager.Inbounds() {
|
||||
referrer, isReferrer := inbound.(adapter.Referrer)
|
||||
if isReferrer {
|
||||
outboundQueue = append(outboundQueue, referrer.References()...)
|
||||
}
|
||||
}
|
||||
for _, boxService := range serviceManager.Services() {
|
||||
referrer, isReferrer := boxService.(adapter.Referrer)
|
||||
if isReferrer {
|
||||
outboundQueue = append(outboundQueue, referrer.References()...)
|
||||
}
|
||||
}
|
||||
referrer, isReferrer := httpClientManager.(adapter.Referrer)
|
||||
if isReferrer {
|
||||
outboundQueue = append(outboundQueue, referrer.References()...)
|
||||
}
|
||||
|
||||
referencedTransports := make(map[string]bool)
|
||||
for len(transportQueue) > 0 {
|
||||
tag := transportQueue[0]
|
||||
transportQueue = transportQueue[1:]
|
||||
if tag == "" || referencedTransports[tag] {
|
||||
continue
|
||||
}
|
||||
transport, loaded := transportManager.Transport(tag)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
referencedTransports[tag] = true
|
||||
transportQueue = append(transportQueue, transport.Dependencies()...)
|
||||
transportReferrer, isTransportReferrer := transport.(adapter.Referrer)
|
||||
if isTransportReferrer {
|
||||
outboundQueue = append(outboundQueue, transportReferrer.References()...)
|
||||
}
|
||||
}
|
||||
referencedOutbounds := make(map[string]bool)
|
||||
for len(outboundQueue) > 0 {
|
||||
tag := outboundQueue[0]
|
||||
outboundQueue = outboundQueue[1:]
|
||||
if tag == "" || referencedOutbounds[tag] {
|
||||
continue
|
||||
}
|
||||
outbound, loaded := outboundManager.Outbound(tag)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
referencedOutbounds[tag] = true
|
||||
outboundReferrer, isOutboundReferrer := outbound.(adapter.Referrer)
|
||||
if isOutboundReferrer {
|
||||
outboundQueue = append(outboundQueue, outboundReferrer.References()...)
|
||||
} else {
|
||||
outboundQueue = append(outboundQueue, outbound.Dependencies()...)
|
||||
}
|
||||
}
|
||||
|
||||
unreferencedOutbounds := make(map[string]bool)
|
||||
for _, outbound := range outboundManager.Outbounds() {
|
||||
tag := outbound.Tag()
|
||||
if referencedOutbounds[tag] {
|
||||
continue
|
||||
}
|
||||
unreferencedOutbounds[tag] = true
|
||||
closer, isCloser := outbound.(adapter.IdleConnectionCloser)
|
||||
if !isCloser {
|
||||
continue
|
||||
}
|
||||
if !m.unreferencedOutbounds[tag] {
|
||||
m.logger.Debug("outbound/", outbound.Type(), "[", tag, "] is unreferenced, closing idle connections")
|
||||
}
|
||||
closer.CloseIdleConnections()
|
||||
}
|
||||
m.unreferencedOutbounds = unreferencedOutbounds
|
||||
|
||||
unreferencedTransports := make(map[string]bool)
|
||||
for _, transport := range transportManager.Transports() {
|
||||
tag := transport.Tag()
|
||||
if referencedTransports[tag] {
|
||||
continue
|
||||
}
|
||||
unreferencedTransports[tag] = true
|
||||
if m.unreferencedTransports != nil && !m.unreferencedTransports[tag] {
|
||||
m.logger.Debug("dns/", transport.Type(), "[", tag, "] is unreferenced, resetting")
|
||||
transport.Reset()
|
||||
}
|
||||
}
|
||||
m.unreferencedTransports = unreferencedTransports
|
||||
}
|
||||
168
route/reference_rule.go
Normal file
168
route/reference_rule.go
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
package route
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type staticMatch uint8
|
||||
|
||||
const (
|
||||
staticMatchUnknown staticMatch = iota
|
||||
staticMatchNever
|
||||
staticMatchAlways
|
||||
)
|
||||
|
||||
func (m staticMatch) invert() staticMatch {
|
||||
switch m {
|
||||
case staticMatchNever:
|
||||
return staticMatchAlways
|
||||
case staticMatchAlways:
|
||||
return staticMatchNever
|
||||
default:
|
||||
return staticMatchUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateClashMode(clashMode string, mode string, invert bool, otherConditions bool) staticMatch {
|
||||
match := staticMatchUnknown
|
||||
if clashMode != "" && !strings.EqualFold(clashMode, mode) {
|
||||
match = staticMatchNever
|
||||
} else if !otherConditions {
|
||||
match = staticMatchAlways
|
||||
}
|
||||
if invert {
|
||||
match = match.invert()
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
func evaluateLogical(matches []staticMatch, logicalMode string, invert bool) staticMatch {
|
||||
var match staticMatch
|
||||
if logicalMode == C.LogicalTypeAnd {
|
||||
match = staticMatchAlways
|
||||
} else {
|
||||
match = staticMatchNever
|
||||
}
|
||||
for _, ruleMatch := range matches {
|
||||
if logicalMode == C.LogicalTypeAnd && ruleMatch == staticMatchNever {
|
||||
match = staticMatchNever
|
||||
break
|
||||
}
|
||||
if logicalMode == C.LogicalTypeOr && ruleMatch == staticMatchAlways {
|
||||
match = staticMatchAlways
|
||||
break
|
||||
}
|
||||
if ruleMatch == staticMatchUnknown {
|
||||
match = staticMatchUnknown
|
||||
}
|
||||
}
|
||||
if invert {
|
||||
match = match.invert()
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
func evaluateRule(rule option.Rule, mode string) staticMatch {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
conditions := rule.DefaultOptions.RawDefaultRule
|
||||
conditions.ClashMode = ""
|
||||
conditions.Invert = false
|
||||
return evaluateClashMode(rule.DefaultOptions.ClashMode, mode, rule.DefaultOptions.Invert, !reflect.DeepEqual(conditions, option.RawDefaultRule{}))
|
||||
case C.RuleTypeLogical:
|
||||
matches := make([]staticMatch, 0, len(rule.LogicalOptions.Rules))
|
||||
for _, subRule := range rule.LogicalOptions.Rules {
|
||||
matches = append(matches, evaluateRule(subRule, mode))
|
||||
}
|
||||
return evaluateLogical(matches, rule.LogicalOptions.Mode, rule.LogicalOptions.Invert)
|
||||
default:
|
||||
return staticMatchUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateDNSRule(rule option.DNSRule, mode string) staticMatch {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
conditions := rule.DefaultOptions.RawDefaultDNSRule
|
||||
conditions.ClashMode = ""
|
||||
conditions.Invert = false
|
||||
return evaluateClashMode(rule.DefaultOptions.ClashMode, mode, rule.DefaultOptions.Invert, !reflect.DeepEqual(conditions, option.RawDefaultDNSRule{}))
|
||||
case C.RuleTypeLogical:
|
||||
matches := make([]staticMatch, 0, len(rule.LogicalOptions.Rules))
|
||||
for _, subRule := range rule.LogicalOptions.Rules {
|
||||
matches = append(matches, evaluateDNSRule(subRule, mode))
|
||||
}
|
||||
return evaluateLogical(matches, rule.LogicalOptions.Mode, rule.LogicalOptions.Invert)
|
||||
default:
|
||||
return staticMatchUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func collectRuleReferences(rules []option.Rule, mode string, outbounds *[]string, transports *[]string) (shadowed bool) {
|
||||
for _, rule := range rules {
|
||||
match := evaluateRule(rule, mode)
|
||||
if match == staticMatchNever {
|
||||
continue
|
||||
}
|
||||
var action option.RuleAction
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
action = rule.DefaultOptions.RuleAction
|
||||
case C.RuleTypeLogical:
|
||||
action = rule.LogicalOptions.RuleAction
|
||||
}
|
||||
var final bool
|
||||
switch action.Action {
|
||||
case C.RuleActionTypeRoute:
|
||||
*outbounds = append(*outbounds, action.RouteOptions.Outbound)
|
||||
final = true
|
||||
case C.RuleActionTypeBypass:
|
||||
if action.BypassOptions.Outbound != "" {
|
||||
*outbounds = append(*outbounds, action.BypassOptions.Outbound)
|
||||
final = true
|
||||
}
|
||||
case C.RuleActionTypeDirect, C.RuleActionTypeReject, C.RuleActionTypeHijackDNS:
|
||||
final = true
|
||||
case C.RuleActionTypeResolve:
|
||||
*transports = append(*transports, action.ResolveOptions.Server)
|
||||
}
|
||||
if final && match == staticMatchAlways {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func collectDNSRuleReferences(rules []option.DNSRule, mode string, transports *[]string) (shadowed bool) {
|
||||
for _, rule := range rules {
|
||||
match := evaluateDNSRule(rule, mode)
|
||||
if match == staticMatchNever {
|
||||
continue
|
||||
}
|
||||
var action option.DNSRuleAction
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
action = rule.DefaultOptions.DNSRuleAction
|
||||
case C.RuleTypeLogical:
|
||||
action = rule.LogicalOptions.DNSRuleAction
|
||||
}
|
||||
var final bool
|
||||
switch action.Action {
|
||||
case C.RuleActionTypeRoute:
|
||||
*transports = append(*transports, action.RouteOptions.Server)
|
||||
final = true
|
||||
case C.RuleActionTypeEvaluate:
|
||||
*transports = append(*transports, action.EvaluateOptions.Server)
|
||||
case C.RuleActionTypeRespond, C.RuleActionTypeReject, C.RuleActionTypePredefined:
|
||||
final = true
|
||||
}
|
||||
if final && match == staticMatchAlways {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -114,6 +114,7 @@ type Service struct {
|
|||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
credentialPath string
|
||||
detour string
|
||||
credentials *oauthCredentials
|
||||
users []option.CCMUser
|
||||
httpClient *http.Client
|
||||
|
|
@ -168,6 +169,7 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
|
|||
|
||||
service := &Service{
|
||||
Adapter: boxService.NewAdapter(C.TypeCCM, tag),
|
||||
detour: options.Detour,
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
credentialPath: options.CredentialPath,
|
||||
|
|
@ -594,3 +596,10 @@ func (s *Service) Close() error {
|
|||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) References() []string {
|
||||
if s.detour == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{s.detour}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -512,3 +512,9 @@ func (d *Service) loopSTUNPacket(packetConn *net.UDPConn) {
|
|||
packetConn.WriteMsgUDPAddrPort(stun.Response(txid, addrPort), oob[:oobN], addrPort)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Service) References() []string {
|
||||
return common.FilterNotDefault(common.Map(d.meshWith, func(it *option.DERPMeshOptions) string {
|
||||
return it.Detour
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ type Service struct {
|
|||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
credentialPath string
|
||||
detour string
|
||||
credentials *oauthCredentials
|
||||
users []option.OCMUser
|
||||
dialer N.Dialer
|
||||
|
|
@ -187,6 +188,7 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
|
|||
|
||||
service := &Service{
|
||||
Adapter: boxService.NewAdapter(C.TypeOCM, tag),
|
||||
detour: options.Detour,
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
credentialPath: options.CredentialPath,
|
||||
|
|
@ -706,3 +708,10 @@ func (s *Service) startWebSocketShutdown() []*webSocketSession {
|
|||
}
|
||||
return webSocketSessions
|
||||
}
|
||||
|
||||
func (s *Service) References() []string {
|
||||
if s.detour == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{s.detour}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,12 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
func PromoteDraft(basePath string) {
|
||||
promoteDirectory(filepath.Join(basePath, DraftDirectoryName), filepath.Join(basePath, ReportsDirectoryName))
|
||||
func DiscardDraft(basePath string) {
|
||||
os.RemoveAll(filepath.Join(basePath, DraftDirectoryName))
|
||||
}
|
||||
|
||||
func finalizeDraft(draftPath string) {
|
||||
promoteDirectory(draftPath, filepath.Join(filepath.Dir(draftPath), ReportsDirectoryName))
|
||||
}
|
||||
|
||||
func promoteDirectory(draftPath string, reportsPath string) {
|
||||
reportsPath := filepath.Join(filepath.Dir(draftPath), ReportsDirectoryName)
|
||||
info, err := os.Stat(draftPath)
|
||||
if err != nil || !info.IsDir() {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ func (r *Recorder) Start() error {
|
|||
if r.started {
|
||||
return nil
|
||||
}
|
||||
PromoteDraft(filepath.Dir(r.draftPath))
|
||||
DiscardDraft(filepath.Dir(r.draftPath))
|
||||
err := os.MkdirAll(r.draftPath, 0o777)
|
||||
if err != nil {
|
||||
return E.Cause(err, "create power report draft directory")
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ type ClientService struct {
|
|||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
inner *usbip.ClientService
|
||||
detour string
|
||||
}
|
||||
|
||||
func NewClientService(ctx context.Context, logger log.ContextLogger, tag string, options option.USBIPClientServiceOptions) (adapter.Service, error) {
|
||||
|
|
@ -47,6 +48,7 @@ func NewClientService(ctx context.Context, logger log.ContextLogger, tag string,
|
|||
ctx: ctx,
|
||||
logger: logger,
|
||||
inner: inner,
|
||||
detour: options.Detour,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -60,3 +62,10 @@ func (s *ClientService) Start(stage adapter.StartStage) error {
|
|||
func (s *ClientService) Close() error {
|
||||
return s.inner.Close()
|
||||
}
|
||||
|
||||
func (s *ClientService) References() []string {
|
||||
if s.detour == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{s.detour}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) CloseIdleConnections() {
|
||||
c.transport.CloseIdleConnections()
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
v2rayhttp.ResetTransport(c.transport)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -170,6 +170,10 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) CloseIdleConnections() {
|
||||
CloseIdleConnections(c.transport)
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.transport.Store(ResetTransport(c.transport.Load()))
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -28,10 +28,54 @@ type Client struct {
|
|||
tlsConfig tls.Config
|
||||
quicConfig *quic.Config
|
||||
connAccess sync.Mutex
|
||||
conn common.TypedValue[*quic.Conn]
|
||||
conn common.TypedValue[*clientConnection]
|
||||
rawConn net.Conn
|
||||
}
|
||||
|
||||
type clientConnection struct {
|
||||
*quic.Conn
|
||||
access sync.Mutex
|
||||
streams int
|
||||
draining bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (c *clientConnection) active() bool {
|
||||
c.access.Lock()
|
||||
closed := c.closed
|
||||
c.access.Unlock()
|
||||
return !closed && !common.Done(c.Context())
|
||||
}
|
||||
|
||||
func (c *clientConnection) acquireStream() bool {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
c.streams++
|
||||
c.draining = false
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *clientConnection) releaseStream() {
|
||||
c.access.Lock()
|
||||
c.streams--
|
||||
closed := c.draining && c.streams == 0 && c.markClosedLocked()
|
||||
c.access.Unlock()
|
||||
if closed {
|
||||
c.CloseWithError(0, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientConnection) markClosedLocked() bool {
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
c.closed = true
|
||||
return true
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
|
||||
quicConfig := &quic.Config{
|
||||
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
|
||||
|
|
@ -48,15 +92,15 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) offer() (*quic.Conn, error) {
|
||||
func (c *Client) offer() (*clientConnection, error) {
|
||||
conn := c.conn.Load()
|
||||
if conn != nil && !common.Done(conn.Context()) {
|
||||
if conn != nil && conn.active() {
|
||||
return conn, nil
|
||||
}
|
||||
c.connAccess.Lock()
|
||||
defer c.connAccess.Unlock()
|
||||
conn = c.conn.Load()
|
||||
if conn != nil && !common.Done(conn.Context()) {
|
||||
if conn != nil && conn.active() {
|
||||
return conn, nil
|
||||
}
|
||||
conn, err := c.offerNew()
|
||||
|
|
@ -66,7 +110,7 @@ func (c *Client) offer() (*quic.Conn, error) {
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) offerNew() (*quic.Conn, error) {
|
||||
func (c *Client) offerNew() (*clientConnection, error) {
|
||||
udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -82,21 +126,42 @@ func (c *Client) offerNew() (*quic.Conn, error) {
|
|||
<-quicConn.Context().Done()
|
||||
udpConn.Close()
|
||||
}()
|
||||
c.conn.Store(quicConn)
|
||||
conn := &clientConnection{Conn: quicConn}
|
||||
c.conn.Store(conn)
|
||||
c.rawConn = udpConn
|
||||
return quicConn, nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
conn, err := c.offer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for {
|
||||
conn, err := c.offer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !conn.acquireStream() {
|
||||
continue
|
||||
}
|
||||
stream, err := conn.OpenStream()
|
||||
if err != nil {
|
||||
conn.releaseStream()
|
||||
return nil, err
|
||||
}
|
||||
return &StreamWrapper{Conn: conn.Conn, Stream: stream, onClose: conn.releaseStream}, nil
|
||||
}
|
||||
stream, err := conn.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *Client) CloseIdleConnections() {
|
||||
conn := c.conn.Load()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
conn.access.Lock()
|
||||
conn.draining = true
|
||||
closed := conn.streams == 0 && conn.markClosedLocked()
|
||||
conn.access.Unlock()
|
||||
if closed {
|
||||
conn.CloseWithError(0, "")
|
||||
}
|
||||
return &StreamWrapper{Conn: conn, Stream: stream}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package v2rayquic
|
|||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/quic-go"
|
||||
|
|
@ -11,6 +12,8 @@ import (
|
|||
type StreamWrapper struct {
|
||||
Conn *quic.Conn
|
||||
*quic.Stream
|
||||
closeOnce sync.Once
|
||||
onClose func()
|
||||
}
|
||||
|
||||
func (s *StreamWrapper) Read(p []byte) (n int, err error) {
|
||||
|
|
@ -41,5 +44,8 @@ func (s *StreamWrapper) Close() error {
|
|||
// quic-go's Stream.Close does not unblock a Write blocked on flow control,
|
||||
// but a past write deadline does; buffered data and the FIN are unaffected.
|
||||
s.Stream.SetWriteDeadline(time.Now())
|
||||
if s.onClose != nil {
|
||||
s.closeOnce.Do(s.onClose)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue