From 6e1407f7f827f930baddb4cc0d67cf622f80d8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 3 Feb 2024 17:45:27 +0800 Subject: [PATCH 1/6] Add address filter support for DNS rules --- docs/configuration/experimental/cache-file.md | 4 ---- docs/configuration/experimental/cache-file.zh.md | 4 ---- 2 files changed, 8 deletions(-) diff --git a/docs/configuration/experimental/cache-file.md b/docs/configuration/experimental/cache-file.md index b30538e5..18c430d9 100644 --- a/docs/configuration/experimental/cache-file.md +++ b/docs/configuration/experimental/cache-file.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "Since sing-box 1.8.0" !!! quote "Changes in sing-box 1.9.0" diff --git a/docs/configuration/experimental/cache-file.zh.md b/docs/configuration/experimental/cache-file.zh.md index 6d86dc84..656d53c4 100644 --- a/docs/configuration/experimental/cache-file.zh.md +++ b/docs/configuration/experimental/cache-file.zh.md @@ -1,7 +1,3 @@ ---- -icon: material/new-box ---- - !!! question "自 sing-box 1.8.0 起" !!! quote "sing-box 1.9.0 中的更改" From 8c893f518023308fdd7934b5f61a918d47ceb421 Mon Sep 17 00:00:00 2001 From: ashly-right Date: Sun, 10 Mar 2024 04:58:00 +0100 Subject: [PATCH 2/6] Implementing select random outbound --- option/group.go | 1 + outbound/urltest.go | 163 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 130 insertions(+), 34 deletions(-) diff --git a/option/group.go b/option/group.go index 72a0f637..8aece458 100644 --- a/option/group.go +++ b/option/group.go @@ -13,4 +13,5 @@ type URLTestOutboundOptions struct { Tolerance uint16 `json:"tolerance,omitempty"` IdleTimeout Duration `json:"idle_timeout,omitempty"` InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` + Randomized bool `json:"randomized,omitempty"` } diff --git a/outbound/urltest.go b/outbound/urltest.go index aa7cff6c..411f0eaf 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -2,6 +2,7 @@ package outbound import ( "context" + "math/rand" "net" "sync" "time" @@ -38,6 +39,7 @@ type URLTest struct { idleTimeout time.Duration group *URLTestGroup interruptExternalConnections bool + randomized bool } func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { @@ -57,6 +59,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo tolerance: options.Tolerance, idleTimeout: time.Duration(options.IdleTimeout), interruptExternalConnections: options.InterruptExistConnections, + randomized: options.Randomized, } if len(outbound.tags) == 0 { return nil, E.New("missing tags") @@ -83,6 +86,7 @@ func (s *URLTest) Start() error { s.tolerance, s.idleTimeout, s.interruptExternalConnections, + s.randomized, ) if err != nil { return err @@ -126,16 +130,20 @@ func (s *URLTest) CheckOutbounds() { func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { s.group.Touch() var outbound adapter.Outbound - switch N.NetworkName(network) { - case N.NetworkTCP: - outbound = s.group.selectedOutboundTCP - case N.NetworkUDP: - outbound = s.group.selectedOutboundUDP - default: - return nil, E.Extend(N.ErrUnknownNetwork, network) - } - if outbound == nil { - outbound, _ = s.group.Select(network) + if s.randomized { + outbound = s.group.selectRandomOutbound(network) + } else { + switch N.NetworkName(network) { + case N.NetworkTCP: + outbound = s.group.selectedOutboundTCP + case N.NetworkUDP: + outbound = s.group.selectedOutboundUDP + default: + return nil, E.Extend(N.ErrUnknownNetwork, network) + } + if outbound == nil { + outbound, _ = s.group.Select(network) + } } if outbound == nil { return nil, E.New("missing supported outbound") @@ -151,9 +159,14 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { s.group.Touch() - outbound := s.group.selectedOutboundUDP - if outbound == nil { - outbound, _ = s.group.Select(N.NetworkUDP) + var outbound adapter.Outbound + if s.randomized { + outbound = s.group.selectRandomOutbound(N.NetworkUDP) // Since ListenPacket is for UDP, we pass "N.NetworkUDP" as the network type + } else { + outbound = s.group.selectedOutboundUDP + if outbound == nil { + outbound, _ = s.group.Select(N.NetworkUDP) + } } if outbound == nil { return nil, E.New("missing supported outbound") @@ -196,9 +209,12 @@ type URLTestGroup struct { pauseManager pause.Manager selectedOutboundTCP adapter.Outbound selectedOutboundUDP adapter.Outbound + randomized bool + bestTCPLatencyOutbounds []adapter.Outbound + bestUDPLatencyOutbounds []adapter.Outbound interruptGroup *interrupt.Group interruptExternalConnections bool - + access sync.Mutex ticker *time.Ticker close chan struct{} @@ -216,6 +232,7 @@ func NewURLTestGroup( tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool, + randomized bool, ) (*URLTestGroup, error) { if interval == 0 { interval = C.DefaultURLTestInterval @@ -250,6 +267,7 @@ func NewURLTestGroup( pauseManager: service.FromContext[pause.Manager](ctx), interruptGroup: interrupt.NewGroup(), interruptExternalConnections: interruptExternalConnections, + randomized: randomized, }, nil } @@ -330,26 +348,29 @@ func (g *URLTestGroup) Select(network string) (adapter.Outbound, bool) { } func (g *URLTestGroup) loopCheck() { - if time.Now().Sub(g.lastActive.Load()) > g.interval { - g.lastActive.Store(time.Now()) - g.CheckOutbounds(false) - } - for { - select { - case <-g.close: - return - case <-g.ticker.C: + if time.Now().Sub(g.lastActive.Load()) > g.interval { + g.lastActive.Store(time.Now()) + g.CheckOutbounds(false) + } + for { + select { + case <-g.close: + return + case <-g.ticker.C: + } + if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout { + g.access.Lock() + g.ticker.Stop() + g.ticker = nil + g.access.Unlock() + return + } + g.pauseManager.WaitActive() + g.CheckOutbounds(false) + if g.randomized { + g.selectBestLatencyOutbounds() } - if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout { - g.access.Lock() - g.ticker.Stop() - g.ticker = nil - g.access.Unlock() - return - } - g.pauseManager.WaitActive() - g.CheckOutbounds(false) - } + } } func (g *URLTestGroup) CheckOutbounds(force bool) { @@ -357,7 +378,15 @@ func (g *URLTestGroup) CheckOutbounds(force bool) { } func (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) { - return g.urlTest(ctx, false) + result, err := g.urlTest(ctx, false) + if err != nil { + return nil, err + } + + if g.randomized { + g.selectBestLatencyOutbounds() + } + return result, nil } func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint16, error) { @@ -423,3 +452,69 @@ func (g *URLTestGroup) performUpdateCheck() { g.interruptGroup.Interrupt(g.interruptExternalConnections) } } + +func (g *URLTestGroup) selectBestLatencyOutbounds() { + var bestTCPLatency uint16 + var bestUDPLatency uint16 + + var bestTCPOutbounds []adapter.Outbound + var bestUDPOutbounds []adapter.Outbound + + for _, detour := range g.outbounds { + history := g.history.LoadURLTestHistory(RealTag(detour)) + if history == nil { + continue + } + + if common.Contains(detour.Network(), N.NetworkTCP) { + if bestTCPLatency == 0 || history.Delay < bestTCPLatency { + bestTCPLatency = history.Delay + } + } else if common.Contains(detour.Network(), N.NetworkUDP) { + if bestUDPLatency == 0 || history.Delay < bestUDPLatency { + bestUDPLatency = history.Delay + } + } + } + + for _, detour := range g.outbounds { + history := g.history.LoadURLTestHistory(RealTag(detour)) + if history == nil { + continue + } + + if common.Contains(detour.Network(), N.NetworkTCP) && history.Delay <= bestTCPLatency+g.tolerance { + bestTCPOutbounds = append(bestTCPOutbounds, detour) + } else if common.Contains(detour.Network(), N.NetworkUDP) && history.Delay <= bestUDPLatency+g.tolerance { + bestUDPOutbounds = append(bestUDPOutbounds, detour) + } + } + + g.bestTCPLatencyOutbounds = bestTCPOutbounds + g.bestUDPLatencyOutbounds = bestUDPOutbounds +} + +// selectRandomOutbound selects an outbound randomly among the outbounds with the best latency +func (g *URLTestGroup) selectRandomOutbound(network string) adapter.Outbound { + var bestOutbounds []adapter.Outbound + + switch network { + case N.NetworkTCP: + bestOutbounds = g.bestTCPLatencyOutbounds + case N.NetworkUDP: + bestOutbounds = g.bestUDPLatencyOutbounds + default: + return nil + } + + if len(bestOutbounds) == 0 { + return nil + } + + randIndex := rand.Intn(len(bestOutbounds)) + g.logger.Debug("Random outbound selection: ", bestOutbounds[randIndex].Tag()) + + return bestOutbounds[randIndex] +} + + From 646703e61e22848b0b637edd3aa76bb38db9d2bf Mon Sep 17 00:00:00 2001 From: ashly-right Date: Sun, 10 Mar 2024 05:11:43 +0100 Subject: [PATCH 3/6] Rename the randomize attribute --- option/group.go | 2 +- outbound/urltest.go | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/option/group.go b/option/group.go index 8aece458..f420cda0 100644 --- a/option/group.go +++ b/option/group.go @@ -13,5 +13,5 @@ type URLTestOutboundOptions struct { Tolerance uint16 `json:"tolerance,omitempty"` IdleTimeout Duration `json:"idle_timeout,omitempty"` InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` - Randomized bool `json:"randomized,omitempty"` + Randomize bool `json:"randomize,omitempty"` } diff --git a/outbound/urltest.go b/outbound/urltest.go index 411f0eaf..0c326eb5 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -39,7 +39,7 @@ type URLTest struct { idleTimeout time.Duration group *URLTestGroup interruptExternalConnections bool - randomized bool + randomize bool } func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { @@ -59,7 +59,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo tolerance: options.Tolerance, idleTimeout: time.Duration(options.IdleTimeout), interruptExternalConnections: options.InterruptExistConnections, - randomized: options.Randomized, + randomize: options.Randomize, } if len(outbound.tags) == 0 { return nil, E.New("missing tags") @@ -86,7 +86,7 @@ func (s *URLTest) Start() error { s.tolerance, s.idleTimeout, s.interruptExternalConnections, - s.randomized, + s.randomize, ) if err != nil { return err @@ -130,7 +130,7 @@ func (s *URLTest) CheckOutbounds() { func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { s.group.Touch() var outbound adapter.Outbound - if s.randomized { + if s.randomize { outbound = s.group.selectRandomOutbound(network) } else { switch N.NetworkName(network) { @@ -160,7 +160,7 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { s.group.Touch() var outbound adapter.Outbound - if s.randomized { + if s.randomize { outbound = s.group.selectRandomOutbound(N.NetworkUDP) // Since ListenPacket is for UDP, we pass "N.NetworkUDP" as the network type } else { outbound = s.group.selectedOutboundUDP @@ -209,7 +209,7 @@ type URLTestGroup struct { pauseManager pause.Manager selectedOutboundTCP adapter.Outbound selectedOutboundUDP adapter.Outbound - randomized bool + randomize bool bestTCPLatencyOutbounds []adapter.Outbound bestUDPLatencyOutbounds []adapter.Outbound interruptGroup *interrupt.Group @@ -232,7 +232,7 @@ func NewURLTestGroup( tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool, - randomized bool, + randomize bool, ) (*URLTestGroup, error) { if interval == 0 { interval = C.DefaultURLTestInterval @@ -267,7 +267,7 @@ func NewURLTestGroup( pauseManager: service.FromContext[pause.Manager](ctx), interruptGroup: interrupt.NewGroup(), interruptExternalConnections: interruptExternalConnections, - randomized: randomized, + randomize: randomize, }, nil } @@ -367,7 +367,7 @@ func (g *URLTestGroup) loopCheck() { } g.pauseManager.WaitActive() g.CheckOutbounds(false) - if g.randomized { + if g.randomize { g.selectBestLatencyOutbounds() } } @@ -383,7 +383,7 @@ func (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) { return nil, err } - if g.randomized { + if g.randomize { g.selectBestLatencyOutbounds() } return result, nil From 91e20525faf8f23f7157c3084f9a033e6b9d514b Mon Sep 17 00:00:00 2001 From: ashly-right Date: Sun, 10 Mar 2024 05:13:14 +0100 Subject: [PATCH 4/6] update the urltest documents - randomize attribute --- docs/configuration/outbound/urltest.md | 9 ++++++++- docs/configuration/outbound/urltest.zh.md | 12 ++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/configuration/outbound/urltest.md b/docs/configuration/outbound/urltest.md index f4b3b0aa..0353e479 100644 --- a/docs/configuration/outbound/urltest.md +++ b/docs/configuration/outbound/urltest.md @@ -14,7 +14,8 @@ "interval": "", "tolerance": 0, "idle_timeout": "", - "interrupt_exist_connections": false + "interrupt_exist_connections": false, + "randomize": false } ``` @@ -47,3 +48,9 @@ The idle timeout. `30m` will be used if empty. Interrupt existing connections when the selected outbound has changed. Only inbound connections are affected by this setting, internal connections will always be interrupted. + +#### randomize + +Outbound would be selected randomly within the best latency in the tolerance range. It's deactivated by default. + +The interrupt_exist_connections will be ignored if the randomize is activated. \ No newline at end of file diff --git a/docs/configuration/outbound/urltest.zh.md b/docs/configuration/outbound/urltest.zh.md index 4372298a..fddb8da2 100644 --- a/docs/configuration/outbound/urltest.zh.md +++ b/docs/configuration/outbound/urltest.zh.md @@ -14,7 +14,8 @@ "interval": "", "tolerance": 50, "idle_timeout": "", - "interrupt_exist_connections": false + "interrupt_exist_connections": false, + "randomize": false } ``` @@ -46,4 +47,11 @@ 当选定的出站发生更改时,中断现有连接。 -仅入站连接受此设置影响,内部连接将始终被中断。 \ No newline at end of file +仅入站连接受此设置影响,内部连接将始终被中断。 + + +#### randomize + +出站将在容忍范围内的最佳延迟内随机选择。 默认情况下它处于禁用状态。 + +如果激活了随机化,则interrupt_exist_connections将被忽略。 \ No newline at end of file From 0ade3706a88100302d0bbf66ae03c7f680dcb1ef Mon Sep 17 00:00:00 2001 From: ashly-right Date: Sun, 10 Mar 2024 19:28:48 +0100 Subject: [PATCH 5/6] Check urlTest outbound for both networks (TCP/UDP) --- outbound/urltest.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/outbound/urltest.go b/outbound/urltest.go index 0c326eb5..4ee80891 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -470,7 +470,8 @@ func (g *URLTestGroup) selectBestLatencyOutbounds() { if bestTCPLatency == 0 || history.Delay < bestTCPLatency { bestTCPLatency = history.Delay } - } else if common.Contains(detour.Network(), N.NetworkUDP) { + } + if common.Contains(detour.Network(), N.NetworkUDP) { if bestUDPLatency == 0 || history.Delay < bestUDPLatency { bestUDPLatency = history.Delay } @@ -485,7 +486,8 @@ func (g *URLTestGroup) selectBestLatencyOutbounds() { if common.Contains(detour.Network(), N.NetworkTCP) && history.Delay <= bestTCPLatency+g.tolerance { bestTCPOutbounds = append(bestTCPOutbounds, detour) - } else if common.Contains(detour.Network(), N.NetworkUDP) && history.Delay <= bestUDPLatency+g.tolerance { + } + if common.Contains(detour.Network(), N.NetworkUDP) && history.Delay <= bestUDPLatency+g.tolerance { bestUDPOutbounds = append(bestUDPOutbounds, detour) } } From 81e7bd5adb304b69f0f4e74effbb9cd77038e054 Mon Sep 17 00:00:00 2001 From: ashly-right Date: Thu, 21 Mar 2024 07:40:02 +0100 Subject: [PATCH 6/6] Fix lint problem --- outbound/urltest.go | 152 ++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 77 deletions(-) diff --git a/outbound/urltest.go b/outbound/urltest.go index 4ee80891..3cac8641 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -209,12 +209,12 @@ type URLTestGroup struct { pauseManager pause.Manager selectedOutboundTCP adapter.Outbound selectedOutboundUDP adapter.Outbound - randomize bool + randomize bool bestTCPLatencyOutbounds []adapter.Outbound bestUDPLatencyOutbounds []adapter.Outbound interruptGroup *interrupt.Group interruptExternalConnections bool - + access sync.Mutex ticker *time.Ticker close chan struct{} @@ -267,7 +267,7 @@ func NewURLTestGroup( pauseManager: service.FromContext[pause.Manager](ctx), interruptGroup: interrupt.NewGroup(), interruptExternalConnections: interruptExternalConnections, - randomize: randomize, + randomize: randomize, }, nil } @@ -348,29 +348,29 @@ func (g *URLTestGroup) Select(network string) (adapter.Outbound, bool) { } func (g *URLTestGroup) loopCheck() { - if time.Now().Sub(g.lastActive.Load()) > g.interval { - g.lastActive.Store(time.Now()) - g.CheckOutbounds(false) - } - for { - select { - case <-g.close: - return - case <-g.ticker.C: - } - if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout { - g.access.Lock() - g.ticker.Stop() - g.ticker = nil - g.access.Unlock() - return - } - g.pauseManager.WaitActive() - g.CheckOutbounds(false) + if time.Now().Sub(g.lastActive.Load()) > g.interval { + g.lastActive.Store(time.Now()) + g.CheckOutbounds(false) + } + for { + select { + case <-g.close: + return + case <-g.ticker.C: + } + if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout { + g.access.Lock() + g.ticker.Stop() + g.ticker = nil + g.access.Unlock() + return + } + g.pauseManager.WaitActive() + g.CheckOutbounds(false) if g.randomize { g.selectBestLatencyOutbounds() } - } + } } func (g *URLTestGroup) CheckOutbounds(force bool) { @@ -379,14 +379,14 @@ func (g *URLTestGroup) CheckOutbounds(force bool) { func (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) { result, err := g.urlTest(ctx, false) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } if g.randomize { g.selectBestLatencyOutbounds() } - return result, nil + return result, nil } func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint16, error) { @@ -454,69 +454,67 @@ func (g *URLTestGroup) performUpdateCheck() { } func (g *URLTestGroup) selectBestLatencyOutbounds() { - var bestTCPLatency uint16 - var bestUDPLatency uint16 + var bestTCPLatency uint16 + var bestUDPLatency uint16 - var bestTCPOutbounds []adapter.Outbound - var bestUDPOutbounds []adapter.Outbound + var bestTCPOutbounds []adapter.Outbound + var bestUDPOutbounds []adapter.Outbound - for _, detour := range g.outbounds { - history := g.history.LoadURLTestHistory(RealTag(detour)) - if history == nil { - continue - } - - if common.Contains(detour.Network(), N.NetworkTCP) { - if bestTCPLatency == 0 || history.Delay < bestTCPLatency { - bestTCPLatency = history.Delay - } - } + for _, detour := range g.outbounds { + history := g.history.LoadURLTestHistory(RealTag(detour)) + if history == nil { + continue + } + + if common.Contains(detour.Network(), N.NetworkTCP) { + if bestTCPLatency == 0 || history.Delay < bestTCPLatency { + bestTCPLatency = history.Delay + } + } if common.Contains(detour.Network(), N.NetworkUDP) { - if bestUDPLatency == 0 || history.Delay < bestUDPLatency { - bestUDPLatency = history.Delay - } - } - } + if bestUDPLatency == 0 || history.Delay < bestUDPLatency { + bestUDPLatency = history.Delay + } + } + } - for _, detour := range g.outbounds { - history := g.history.LoadURLTestHistory(RealTag(detour)) - if history == nil { - continue - } + for _, detour := range g.outbounds { + history := g.history.LoadURLTestHistory(RealTag(detour)) + if history == nil { + continue + } - if common.Contains(detour.Network(), N.NetworkTCP) && history.Delay <= bestTCPLatency+g.tolerance { - bestTCPOutbounds = append(bestTCPOutbounds, detour) - } + if common.Contains(detour.Network(), N.NetworkTCP) && history.Delay <= bestTCPLatency+g.tolerance { + bestTCPOutbounds = append(bestTCPOutbounds, detour) + } if common.Contains(detour.Network(), N.NetworkUDP) && history.Delay <= bestUDPLatency+g.tolerance { - bestUDPOutbounds = append(bestUDPOutbounds, detour) - } - } + bestUDPOutbounds = append(bestUDPOutbounds, detour) + } + } - g.bestTCPLatencyOutbounds = bestTCPOutbounds - g.bestUDPLatencyOutbounds = bestUDPOutbounds + g.bestTCPLatencyOutbounds = bestTCPOutbounds + g.bestUDPLatencyOutbounds = bestUDPOutbounds } // selectRandomOutbound selects an outbound randomly among the outbounds with the best latency func (g *URLTestGroup) selectRandomOutbound(network string) adapter.Outbound { - var bestOutbounds []adapter.Outbound + var bestOutbounds []adapter.Outbound - switch network { - case N.NetworkTCP: - bestOutbounds = g.bestTCPLatencyOutbounds - case N.NetworkUDP: - bestOutbounds = g.bestUDPLatencyOutbounds - default: - return nil - } + switch network { + case N.NetworkTCP: + bestOutbounds = g.bestTCPLatencyOutbounds + case N.NetworkUDP: + bestOutbounds = g.bestUDPLatencyOutbounds + default: + return nil + } - if len(bestOutbounds) == 0 { - return nil - } + if len(bestOutbounds) == 0 { + return nil + } - randIndex := rand.Intn(len(bestOutbounds)) + randIndex := rand.Intn(len(bestOutbounds)) g.logger.Debug("Random outbound selection: ", bestOutbounds[randIndex].Tag()) - - return bestOutbounds[randIndex] + + return bestOutbounds[randIndex] } - -