Skip to content

Commit 9b7f36a

Browse files
committed
feat: close conns
1 parent 9a8944d commit 9b7f36a

11 files changed

Lines changed: 95 additions & 49 deletions

File tree

adapter/outboundgroup/fallback.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,22 @@ func (f *Fallback) ForceSet(name string) {
150150
f.selected = name
151151
}
152152

153+
func (f *Fallback) CloseRelatedConns() {
154+
f.closeRelatedConns()
155+
}
156+
153157
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
154158
return &Fallback{
155159
GroupBase: NewGroupBase(GroupBaseOption{
156-
Name: option.Name,
157-
Type: C.Fallback,
158-
Filter: option.Filter,
159-
ExcludeFilter: option.ExcludeFilter,
160-
ExcludeType: option.ExcludeType,
161-
TestTimeout: option.TestTimeout,
162-
MaxFailedTimes: option.MaxFailedTimes,
163-
Providers: providers,
160+
Name: option.Name,
161+
Type: C.Fallback,
162+
Filter: option.Filter,
163+
ExcludeFilter: option.ExcludeFilter,
164+
ExcludeType: option.ExcludeType,
165+
TestTimeout: option.TestTimeout,
166+
MaxFailedTimes: option.MaxFailedTimes,
167+
CloseOnSelected: option.CloseOnSelected,
168+
Providers: providers,
164169
}),
165170
disableUDP: option.DisableUDP,
166171
testUrl: option.URL,

adapter/outboundgroup/groupbase.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
types "github.com/metacubex/mihomo/constant/provider"
1717
"github.com/metacubex/mihomo/log"
1818
"github.com/metacubex/mihomo/tunnel"
19+
"github.com/metacubex/mihomo/tunnel/statistic"
20+
"github.com/samber/lo"
1921

2022
"github.com/dlclark/regexp2"
2123
"golang.org/x/exp/slices"
@@ -33,6 +35,7 @@ type GroupBase struct {
3335
failedTesting atomic.Bool
3436
TestTimeout int
3537
maxFailedTimes int
38+
closeOnSelected bool
3639

3740
// for GetProxies
3841
getProxiesMutex sync.Mutex
@@ -41,14 +44,15 @@ type GroupBase struct {
4144
}
4245

4346
type GroupBaseOption struct {
44-
Name string
45-
Type C.AdapterType
46-
Filter string
47-
ExcludeFilter string
48-
ExcludeType string
49-
TestTimeout int
50-
MaxFailedTimes int
51-
Providers []provider.ProxyProvider
47+
Name string
48+
Type C.AdapterType
49+
Filter string
50+
ExcludeFilter string
51+
ExcludeType string
52+
TestTimeout int
53+
MaxFailedTimes int
54+
CloseOnSelected bool
55+
Providers []provider.ProxyProvider
5256
}
5357

5458
func NewGroupBase(opt GroupBaseOption) *GroupBase {
@@ -82,6 +86,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
8286
failedTesting: atomic.NewBool(false),
8387
TestTimeout: opt.TestTimeout,
8488
maxFailedTimes: opt.MaxFailedTimes,
89+
closeOnSelected: opt.CloseOnSelected,
8590
}
8691

8792
if gb.TestTimeout == 0 {
@@ -306,3 +311,15 @@ func (gb *GroupBase) onDialSuccess() {
306311
gb.failedTimes = 0
307312
}
308313
}
314+
315+
func (gb *GroupBase) closeRelatedConns() {
316+
if !gb.closeOnSelected {
317+
return
318+
}
319+
statistic.DefaultManager.Range(func(tracker statistic.Tracker) bool {
320+
if lo.Contains(tracker.Chains(), gb.Name()) {
321+
_ = tracker.Close()
322+
}
323+
return true
324+
})
325+
}

adapter/outboundgroup/parser.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type GroupCommonOption struct {
4343
IncludeAllProviders bool `group:"include-all-providers,omitempty"`
4444
Hidden bool `group:"hidden,omitempty"`
4545
Icon string `group:"icon,omitempty"`
46+
CloseOnSelected bool `group:"close-on-selected,omitempty"`
4647

4748
// removed configs, only for error logging
4849
Interface string `group:"interface-name,omitempty"`

adapter/outboundgroup/selector.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ func (s *Selector) Set(name string) error {
8888
return errors.New("proxy not exist")
8989
}
9090

91+
func (s *Selector) CloseRelatedConns() {
92+
s.closeRelatedConns()
93+
}
94+
9195
func (s *Selector) ForceSet(name string) {
9296
s.selected = name
9397
}
@@ -111,14 +115,15 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy {
111115
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
112116
return &Selector{
113117
GroupBase: NewGroupBase(GroupBaseOption{
114-
Name: option.Name,
115-
Type: C.Selector,
116-
Filter: option.Filter,
117-
ExcludeFilter: option.ExcludeFilter,
118-
ExcludeType: option.ExcludeType,
119-
TestTimeout: option.TestTimeout,
120-
MaxFailedTimes: option.MaxFailedTimes,
121-
Providers: providers,
118+
Name: option.Name,
119+
Type: C.Selector,
120+
Filter: option.Filter,
121+
ExcludeFilter: option.ExcludeFilter,
122+
ExcludeType: option.ExcludeType,
123+
TestTimeout: option.TestTimeout,
124+
MaxFailedTimes: option.MaxFailedTimes,
125+
CloseOnSelected: option.CloseOnSelected,
126+
Providers: providers,
122127
}),
123128
selected: "COMPATIBLE",
124129
disableUDP: option.DisableUDP,

adapter/outboundgroup/urltest.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ func (u *URLTest) Set(name string) error {
5454
return nil
5555
}
5656

57+
func (u *URLTest) CloseRelatedConns() {
58+
u.closeRelatedConns()
59+
}
60+
5761
func (u *URLTest) ForceSet(name string) {
5862
u.selected = name
5963
u.fastSingle.Reset()
@@ -205,14 +209,15 @@ func parseURLTestOption(config map[string]any) []urlTestOption {
205209
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
206210
urlTest := &URLTest{
207211
GroupBase: NewGroupBase(GroupBaseOption{
208-
Name: option.Name,
209-
Type: C.URLTest,
210-
Filter: option.Filter,
211-
ExcludeFilter: option.ExcludeFilter,
212-
ExcludeType: option.ExcludeType,
213-
TestTimeout: option.TestTimeout,
214-
MaxFailedTimes: option.MaxFailedTimes,
215-
Providers: providers,
212+
Name: option.Name,
213+
Type: C.URLTest,
214+
Filter: option.Filter,
215+
ExcludeFilter: option.ExcludeFilter,
216+
ExcludeType: option.ExcludeType,
217+
TestTimeout: option.TestTimeout,
218+
MaxFailedTimes: option.MaxFailedTimes,
219+
CloseOnSelected: option.CloseOnSelected,
220+
Providers: providers,
216221
}),
217222
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
218223
disableUDP: option.DisableUDP,

adapter/outboundgroup/util.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package outboundgroup
33
type SelectAble interface {
44
Set(string) error
55
ForceSet(name string)
6+
CloseRelatedConns()
67
}
78

8-
var _ SelectAble = (*Fallback)(nil)
9-
var _ SelectAble = (*URLTest)(nil)
10-
var _ SelectAble = (*Selector)(nil)
9+
var (
10+
_ SelectAble = (*Fallback)(nil)
11+
_ SelectAble = (*URLTest)(nil)
12+
_ SelectAble = (*Selector)(nil)
13+
)

config/config.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type General struct {
6565
KeepAliveIdle int `json:"keep-alive-idle"`
6666
KeepAliveInterval int `json:"keep-alive-interval"`
6767
DisableKeepAlive bool `json:"disable-keep-alive"`
68+
CloseOnSelected bool `json:"close-on-selected"`
6869
}
6970

7071
// Inbound config
@@ -270,7 +271,7 @@ type RawTun struct {
270271
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
271272
GSO bool `yaml:"gso" json:"gso,omitempty"`
272273
GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"`
273-
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
274+
// Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
274275
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
275276
IPRoute2TableIndex int `yaml:"iproute2-table-index" json:"iproute2-table-index,omitempty"`
276277
IPRoute2RuleIndex int `yaml:"iproute2-rule-index" json:"iproute2-rule-index,omitempty"`
@@ -427,6 +428,7 @@ type RawConfig struct {
427428
KeepAliveIdle int `yaml:"keep-alive-idle" json:"keep-alive-idle"`
428429
KeepAliveInterval int `yaml:"keep-alive-interval" json:"keep-alive-interval"`
429430
DisableKeepAlive bool `yaml:"disable-keep-alive" json:"disable-keep-alive"`
431+
CloseOnSelected bool `yaml:"close-on-selected" json:"close-on-selected"`
430432

431433
ProxyProvider map[string]map[string]any `yaml:"proxy-providers" json:"proxy-providers"`
432434
RuleProvider map[string]map[string]any `yaml:"rule-providers" json:"rule-providers"`
@@ -595,7 +597,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
595597

596598
func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
597599
config := &Config{}
598-
log.Infoln("Start initial configuration in progress") //Segment finished in xxm
600+
log.Infoln("Start initial configuration in progress") // Segment finished in xxm
599601
startTime := time.Now()
600602

601603
general, err := parseGeneral(rawCfg)
@@ -721,7 +723,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
721723
}
722724

723725
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
724-
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
726+
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) // Segment finished in xxm
725727

726728
return config, nil
727729
}
@@ -772,6 +774,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
772774
KeepAliveIdle: cfg.KeepAliveIdle,
773775
KeepAliveInterval: cfg.KeepAliveInterval,
774776
DisableKeepAlive: cfg.DisableKeepAlive,
777+
CloseOnSelected: cfg.CloseOnSelected,
775778
}, nil
776779
}
777780

@@ -916,8 +919,16 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
916919
slices.Sort(AllProxies)
917920
slices.Sort(AllProviders)
918921

922+
globalCloseOnSelected := cfg.CloseOnSelected
923+
919924
// parse proxy group
920925
for idx, mapping := range groupsConfig {
926+
if groupCloseOnSelected, ok := mapping["close-on-selected"].(bool); ok {
927+
mapping["close-on-selected"] = groupCloseOnSelected
928+
} else {
929+
mapping["close-on-selected"] = globalCloseOnSelected
930+
}
931+
921932
group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders)
922933
if err != nil {
923934
return nil, nil, fmt.Errorf("proxy group[%d]: %w", idx, err)

docs/config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,10 @@ proxies: # socks5
10621062
type: direct
10631063
interface-name: en1
10641064
routing-mark: 6667
1065+
1066+
# 可选择类型的代理组(select/urltest/fallback)在通过 api 选择节点后是否关闭之前的连接,默认为 false
1067+
# close-on-selected: true
1068+
10651069
proxy-groups:
10661070
# 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic
10671071
# wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项
@@ -1077,6 +1081,7 @@ proxy-groups:
10771081
# url-test 将按照 url 测试结果使用延迟最低节点
10781082
- name: "auto"
10791083
type: url-test
1084+
# close-on-selected: false
10801085
proxies:
10811086
- ss1
10821087
- ss2

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ require (
4646
github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20
4747
github.com/sagernet/cors v1.2.1
4848
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
49-
github.com/samber/lo v1.51.0
49+
github.com/samber/lo v1.52.0
5050
github.com/sirupsen/logrus v1.9.3
5151
github.com/stretchr/testify v1.11.1
5252
github.com/vmihailenco/msgpack/v5 v5.4.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
175175
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
176176
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
177177
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
178-
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
179-
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
178+
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
179+
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
180180
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
181181
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
182182
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=

0 commit comments

Comments
 (0)