[client] Fix ios route notify ordering (#6454)
* [client] fix iOS route-update reordering that black-holed IPv6 on exit-node disable On iOS the route notifier delivered each prefix update from its own fire-and-forget goroutine (notify -> `go func`), so Go provided no ordering guarantee between consecutive updates. It also read currentPrefixes inside that goroutine without holding the lock, racing the next OnNewPrefixes write. On exit-node disable the core removes the default routes as two separate prefix updates (0.0.0.0/0, then the synthesized ::/0). When the two goroutines were reordered, the stale snapshot still containing ::/0 was delivered last and clobbered the correct default-free one. iOS then kept the ::/0 default route on the tunnel with no exit node to carry it, black-holing all IPv6 traffic while IPv4 recovered correctly. Fix: deliver updates through a single worker goroutine fed by a buffered channel, preserving production order, and snapshot the joined prefix string under the mutex so it can't race a concurrent update. Buffered so producers (which run under the route manager lock) don't block on the listener callback. * [client] close iOS notifier delivery goroutine on Stop, unbounded queue The delivery goroutine was never stopped, leaking on every engine restart. Add Notifier.Close, called from the route manager Stop after routing cleanup. Replace the buffered update channel with a cond-driven linked-list queue so route-update producers (running under the route manager lock) never block when the listener callback is slow.
This commit is contained in:
@@ -333,6 +333,8 @@ func (m *DefaultManager) Stop(stateManager *statemanager.Manager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.notifier.Close()
|
||||||
|
|
||||||
m.mux.Lock()
|
m.mux.Lock()
|
||||||
defer m.mux.Unlock()
|
defer m.mux.Unlock()
|
||||||
m.clientRoutes = nil
|
m.clientRoutes = nil
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
type Notifier struct {
|
type Notifier struct {
|
||||||
initialRoutes []*route.Route
|
initialRoutes []*route.Route
|
||||||
currentRoutes []*route.Route
|
currentRoutes []*route.Route
|
||||||
fakeIPRoutes []*route.Route
|
fakeIPRoutes []*route.Route
|
||||||
|
|
||||||
listener listener.NetworkChangeListener
|
listener listener.NetworkChangeListener
|
||||||
listenerMux sync.Mutex
|
listenerMux sync.Mutex
|
||||||
@@ -119,3 +119,7 @@ func (n *Notifier) GetInitialRouteRanges() []string {
|
|||||||
sort.Strings(initialStrings)
|
sort.Strings(initialStrings)
|
||||||
return initialStrings
|
return initialStrings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Close() {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package notifier
|
package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/list"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -14,19 +15,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Notifier struct {
|
type Notifier struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
cond *sync.Cond
|
||||||
currentPrefixes []string
|
currentPrefixes []string
|
||||||
|
listener listener.NetworkChangeListener
|
||||||
listener listener.NetworkChangeListener
|
queue *list.List
|
||||||
listenerMux sync.Mutex
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotifier() *Notifier {
|
func NewNotifier() *Notifier {
|
||||||
return &Notifier{}
|
n := &Notifier{
|
||||||
|
queue: list.New(),
|
||||||
|
}
|
||||||
|
n.cond = sync.NewCond(&n.mu)
|
||||||
|
go n.deliverLoop()
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
|
func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
|
||||||
n.listenerMux.Lock()
|
n.mu.Lock()
|
||||||
defer n.listenerMux.Unlock()
|
defer n.mu.Unlock()
|
||||||
n.listener = listener
|
n.listener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,32 +51,52 @@ func (n *Notifier) OnNewRoutes(route.HAMap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
|
func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
|
||||||
newNets := make([]string, 0)
|
newNets := make([]string, 0, len(prefixes))
|
||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
newNets = append(newNets, prefix.String())
|
newNets = append(newNets, prefix.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(newNets)
|
sort.Strings(newNets)
|
||||||
|
|
||||||
|
n.mu.Lock()
|
||||||
if slices.Equal(n.currentPrefixes, newNets) {
|
if slices.Equal(n.currentPrefixes, newNets) {
|
||||||
|
n.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n.currentPrefixes = newNets
|
n.currentPrefixes = newNets
|
||||||
n.notify()
|
routes := strings.Join(n.currentPrefixes, ",")
|
||||||
|
n.queue.PushBack(routes)
|
||||||
|
n.cond.Signal()
|
||||||
|
n.mu.Unlock()
|
||||||
}
|
}
|
||||||
func (n *Notifier) notify() {
|
|
||||||
n.listenerMux.Lock()
|
|
||||||
defer n.listenerMux.Unlock()
|
|
||||||
if n.listener == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go func(l listener.NetworkChangeListener) {
|
func (n *Notifier) Close() {
|
||||||
l.OnNetworkChanged(strings.Join(n.currentPrefixes, ","))
|
n.mu.Lock()
|
||||||
}(n.listener)
|
n.closed = true
|
||||||
|
n.cond.Signal()
|
||||||
|
n.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) GetInitialRouteRanges() []string {
|
func (n *Notifier) GetInitialRouteRanges() []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) deliverLoop() {
|
||||||
|
for {
|
||||||
|
n.mu.Lock()
|
||||||
|
for n.queue.Len() == 0 && !n.closed {
|
||||||
|
n.cond.Wait()
|
||||||
|
}
|
||||||
|
if n.closed && n.queue.Len() == 0 {
|
||||||
|
n.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
routes := n.queue.Remove(n.queue.Front()).(string)
|
||||||
|
l := n.listener
|
||||||
|
n.mu.Unlock()
|
||||||
|
|
||||||
|
if l != nil {
|
||||||
|
l.OnNetworkChanged(routes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,3 +38,7 @@ func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
|
|||||||
func (n *Notifier) GetInitialRouteRanges() []string {
|
func (n *Notifier) GetInitialRouteRanges() []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Close() {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user