github.com/apernet/sing-tun@v0.2.6-0.20240323130332-b9f6511036ad/internal/winipcfg/route_change_handler.go (about) 1 /* SPDX-License-Identifier: MIT 2 * 3 * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. 4 */ 5 6 package winipcfg 7 8 import ( 9 "sync" 10 11 "golang.org/x/sys/windows" 12 ) 13 14 // RouteChangeCallback structure allows route change callback handling. 15 type RouteChangeCallback struct { 16 cb func(notificationType MibNotificationType, route *MibIPforwardRow2) 17 wait sync.WaitGroup 18 } 19 20 var ( 21 routeChangeAddRemoveMutex = sync.Mutex{} 22 routeChangeMutex = sync.Mutex{} 23 routeChangeCallbacks = make(map[*RouteChangeCallback]bool) 24 routeChangeHandle = windows.Handle(0) 25 ) 26 27 // RegisterRouteChangeCallback registers a new RouteChangeCallback. If this particular callback is already 28 // registered, the function will silently return. Returned RouteChangeCallback.Unregister method should be used 29 // to unregister. 30 func RegisterRouteChangeCallback(callback func(notificationType MibNotificationType, route *MibIPforwardRow2)) (*RouteChangeCallback, error) { 31 s := &RouteChangeCallback{cb: callback} 32 33 routeChangeAddRemoveMutex.Lock() 34 defer routeChangeAddRemoveMutex.Unlock() 35 36 routeChangeMutex.Lock() 37 defer routeChangeMutex.Unlock() 38 39 routeChangeCallbacks[s] = true 40 41 if routeChangeHandle == 0 { 42 err := notifyRouteChange2(windows.AF_UNSPEC, windows.NewCallback(routeChanged), 0, false, &routeChangeHandle) 43 if err != nil { 44 delete(routeChangeCallbacks, s) 45 routeChangeHandle = 0 46 return nil, err 47 } 48 } 49 50 return s, nil 51 } 52 53 // Unregister unregisters the callback. 54 func (callback *RouteChangeCallback) Unregister() error { 55 routeChangeAddRemoveMutex.Lock() 56 defer routeChangeAddRemoveMutex.Unlock() 57 58 routeChangeMutex.Lock() 59 delete(routeChangeCallbacks, callback) 60 removeIt := len(routeChangeCallbacks) == 0 && routeChangeHandle != 0 61 routeChangeMutex.Unlock() 62 63 callback.wait.Wait() 64 65 if removeIt { 66 err := cancelMibChangeNotify2(routeChangeHandle) 67 if err != nil { 68 return err 69 } 70 routeChangeHandle = 0 71 } 72 73 return nil 74 } 75 76 func routeChanged(callerContext uintptr, row *MibIPforwardRow2, notificationType MibNotificationType) uintptr { 77 rowCopy := *row 78 routeChangeMutex.Lock() 79 for cb := range routeChangeCallbacks { 80 cb.wait.Add(1) 81 go func(cb *RouteChangeCallback) { 82 cb.cb(notificationType, &rowCopy) 83 cb.wait.Done() 84 }(cb) 85 } 86 routeChangeMutex.Unlock() 87 return 0 88 }