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  }