network_category_windows.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. //go:build !e2e_testing
  2. package overlay
  3. import (
  4. "errors"
  5. "fmt"
  6. "log/slog"
  7. "runtime"
  8. "strings"
  9. "syscall"
  10. "time"
  11. "unsafe"
  12. "golang.org/x/sys/windows"
  13. )
  14. // networkCategory mirrors NLM_NETWORK_CATEGORY from netlistmgr.h.
  15. type networkCategory int32
  16. const (
  17. networkCategoryPublic networkCategory = 0
  18. networkCategoryPrivate networkCategory = 1
  19. networkCategoryDomainAuthenticated networkCategory = 2
  20. )
  21. func (c networkCategory) String() string {
  22. switch c {
  23. case networkCategoryPublic:
  24. return "public"
  25. case networkCategoryPrivate:
  26. return "private"
  27. case networkCategoryDomainAuthenticated:
  28. return "domain"
  29. }
  30. return fmt.Sprintf("unknown(%d)", c)
  31. }
  32. // parseNetworkCategory accepts the user-supplied tun.network_category. A
  33. // second return of false means "leave the category alone".
  34. func parseNetworkCategory(s string) (networkCategory, bool, error) {
  35. switch strings.ToLower(strings.TrimSpace(s)) {
  36. case "", "unset":
  37. return 0, false, nil
  38. case "public":
  39. return networkCategoryPublic, true, nil
  40. case "private":
  41. return networkCategoryPrivate, true, nil
  42. case "domain", "domainauthenticated":
  43. return networkCategoryDomainAuthenticated, true, nil
  44. }
  45. return 0, false, fmt.Errorf("unknown tun.network_category %q (expected public, private, domain, or unset)", s)
  46. }
  47. // CLSID_NetworkListManager {DCB00C01-570F-4A9B-8D69-199FDBA5723B}
  48. var clsidNetworkListManager = windows.GUID{
  49. Data1: 0xDCB00C01, Data2: 0x570F, Data3: 0x4A9B,
  50. Data4: [8]byte{0x8D, 0x69, 0x19, 0x9F, 0xDB, 0xA5, 0x72, 0x3B},
  51. }
  52. // IID_INetworkListManager {DCB00000-570F-4A9B-8D69-199FDBA5723B}
  53. var iidINetworkListManager = windows.GUID{
  54. Data1: 0xDCB00000, Data2: 0x570F, Data3: 0x4A9B,
  55. Data4: [8]byte{0x8D, 0x69, 0x19, 0x9F, 0xDB, 0xA5, 0x72, 0x3B},
  56. }
  57. // x/sys/windows doesn't expose CoCreateInstance, so we bind it ourselves.
  58. var procCoCreateInstance = windows.NewLazySystemDLL("ole32.dll").NewProc("CoCreateInstance")
  59. const clsCtxAll = windows.CLSCTX_INPROC_SERVER | windows.CLSCTX_INPROC_HANDLER |
  60. windows.CLSCTX_LOCAL_SERVER | windows.CLSCTX_REMOTE_SERVER
  61. const (
  62. hrSFALSE = 0x00000001
  63. hrRPCEChangedMode = 0x80010106
  64. )
  65. type hresult uint32
  66. func (h hresult) failed() bool { return int32(h) < 0 }
  67. func (h hresult) String() string {
  68. return fmt.Sprintf("HRESULT 0x%08x", uint32(h))
  69. }
  70. var errAdapterNotFound = errors.New("adapter not present in network connections enumeration")
  71. // Vtable layouts. Slot order must match the declaration order in netlistmgr.h.
  72. // All NLM interfaces here derive from IDispatch, which derives from IUnknown.
  73. type iUnknownVtbl struct {
  74. QueryInterface uintptr
  75. AddRef uintptr
  76. Release uintptr
  77. }
  78. type iDispatchVtbl struct {
  79. iUnknownVtbl
  80. GetTypeInfoCount uintptr
  81. GetTypeInfo uintptr
  82. GetIDsOfNames uintptr
  83. Invoke uintptr
  84. }
  85. type iNetworkListManagerVtbl struct {
  86. iDispatchVtbl
  87. GetNetworks uintptr
  88. GetNetwork uintptr
  89. GetNetworkConnections uintptr
  90. GetNetworkConnection uintptr
  91. IsConnectedToInternet uintptr
  92. IsConnected uintptr
  93. GetConnectivity uintptr
  94. }
  95. type iNetworkListManager struct{ Vtbl *iNetworkListManagerVtbl }
  96. func (n *iNetworkListManager) Release() {
  97. syscall.SyscallN(n.Vtbl.Release, uintptr(unsafe.Pointer(n)))
  98. }
  99. func (n *iNetworkListManager) GetNetworkConnections() (*iEnumNetworkConnections, error) {
  100. var enum *iEnumNetworkConnections
  101. r1, _, _ := syscall.SyscallN(n.Vtbl.GetNetworkConnections,
  102. uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(&enum)),
  103. )
  104. if hr := hresult(r1); hr.failed() {
  105. return nil, fmt.Errorf("INetworkListManager.GetNetworkConnections: %s", hr)
  106. }
  107. return enum, nil
  108. }
  109. type iEnumNetworkConnectionsVtbl struct {
  110. iDispatchVtbl
  111. NewEnum uintptr
  112. Next uintptr
  113. Skip uintptr
  114. Reset uintptr
  115. Clone uintptr
  116. }
  117. type iEnumNetworkConnections struct{ Vtbl *iEnumNetworkConnectionsVtbl }
  118. func (e *iEnumNetworkConnections) Release() {
  119. syscall.SyscallN(e.Vtbl.Release, uintptr(unsafe.Pointer(e)))
  120. }
  121. // Next returns the next connection, or (nil, nil) at the end of the enumeration.
  122. func (e *iEnumNetworkConnections) Next() (*iNetworkConnection, error) {
  123. var conn *iNetworkConnection
  124. var fetched uint32
  125. r1, _, _ := syscall.SyscallN(e.Vtbl.Next,
  126. uintptr(unsafe.Pointer(e)), 1,
  127. uintptr(unsafe.Pointer(&conn)), uintptr(unsafe.Pointer(&fetched)),
  128. )
  129. if hr := hresult(r1); hr.failed() {
  130. return nil, fmt.Errorf("IEnumNetworkConnections.Next: %s", hr)
  131. }
  132. if fetched == 0 {
  133. return nil, nil
  134. }
  135. return conn, nil
  136. }
  137. type iNetworkConnectionVtbl struct {
  138. iDispatchVtbl
  139. GetNetwork uintptr
  140. IsConnectedToInternet uintptr
  141. IsConnected uintptr
  142. GetConnectivity uintptr
  143. GetConnectionId uintptr
  144. GetAdapterId uintptr
  145. GetDomainType uintptr
  146. }
  147. type iNetworkConnection struct{ Vtbl *iNetworkConnectionVtbl }
  148. func (c *iNetworkConnection) Release() {
  149. syscall.SyscallN(c.Vtbl.Release, uintptr(unsafe.Pointer(c)))
  150. }
  151. func (c *iNetworkConnection) GetAdapterId() (windows.GUID, error) {
  152. var g windows.GUID
  153. r1, _, _ := syscall.SyscallN(c.Vtbl.GetAdapterId,
  154. uintptr(unsafe.Pointer(c)), uintptr(unsafe.Pointer(&g)),
  155. )
  156. if hr := hresult(r1); hr.failed() {
  157. return windows.GUID{}, fmt.Errorf("INetworkConnection.GetAdapterId: %s", hr)
  158. }
  159. return g, nil
  160. }
  161. func (c *iNetworkConnection) GetNetwork() (*iNetwork, error) {
  162. var net *iNetwork
  163. r1, _, _ := syscall.SyscallN(c.Vtbl.GetNetwork,
  164. uintptr(unsafe.Pointer(c)), uintptr(unsafe.Pointer(&net)),
  165. )
  166. if hr := hresult(r1); hr.failed() {
  167. return nil, fmt.Errorf("INetworkConnection.GetNetwork: %s", hr)
  168. }
  169. return net, nil
  170. }
  171. type iNetworkVtbl struct {
  172. iDispatchVtbl
  173. GetName uintptr
  174. SetName uintptr
  175. GetDescription uintptr
  176. SetDescription uintptr
  177. GetNetworkId uintptr
  178. GetDomainType uintptr
  179. GetNetworkConnections uintptr
  180. GetTimeCreatedAndConnected uintptr
  181. IsConnectedToInternet uintptr
  182. IsConnected uintptr
  183. GetConnectivity uintptr
  184. GetCategory uintptr
  185. SetCategory uintptr
  186. }
  187. type iNetwork struct{ Vtbl *iNetworkVtbl }
  188. func (n *iNetwork) Release() {
  189. syscall.SyscallN(n.Vtbl.Release, uintptr(unsafe.Pointer(n)))
  190. }
  191. func (n *iNetwork) GetCategory() (networkCategory, error) {
  192. var c networkCategory
  193. r1, _, _ := syscall.SyscallN(n.Vtbl.GetCategory,
  194. uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(&c)),
  195. )
  196. if hr := hresult(r1); hr.failed() {
  197. return 0, fmt.Errorf("INetwork.GetCategory: %s", hr)
  198. }
  199. return c, nil
  200. }
  201. func (n *iNetwork) SetCategory(c networkCategory) error {
  202. r1, _, _ := syscall.SyscallN(n.Vtbl.SetCategory,
  203. uintptr(unsafe.Pointer(n)), uintptr(int32(c)),
  204. )
  205. if hr := hresult(r1); hr.failed() {
  206. return fmt.Errorf("INetwork.SetCategory: %s", hr)
  207. }
  208. return nil
  209. }
  210. // coInit initializes COM for the current OS thread. The returned function must
  211. // be deferred to balance a successful init. RPC_E_CHANGED_MODE means COM is
  212. // already initialized in a different mode on this thread, which is still fine
  213. // for our calls but we must not Uninitialize in that case.
  214. func coInit() (func(), error) {
  215. err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED)
  216. if err == nil {
  217. return windows.CoUninitialize, nil
  218. }
  219. if e, ok := err.(syscall.Errno); ok {
  220. switch uint32(e) {
  221. case hrSFALSE:
  222. return windows.CoUninitialize, nil
  223. case hrRPCEChangedMode:
  224. return func() {}, nil
  225. }
  226. }
  227. return nil, fmt.Errorf("CoInitializeEx: %w", err)
  228. }
  229. func createNetworkListManager() (*iNetworkListManager, error) {
  230. var nlm *iNetworkListManager
  231. r1, _, _ := procCoCreateInstance.Call(
  232. uintptr(unsafe.Pointer(&clsidNetworkListManager)),
  233. 0,
  234. uintptr(clsCtxAll),
  235. uintptr(unsafe.Pointer(&iidINetworkListManager)),
  236. uintptr(unsafe.Pointer(&nlm)),
  237. )
  238. if hr := hresult(r1); hr.failed() {
  239. return nil, fmt.Errorf("CoCreateInstance(NetworkListManager): %s", hr)
  240. }
  241. return nlm, nil
  242. }
  243. // setNetworkCategory locates the network connection bound to adapterGUID and
  244. // sets the category of its parent network. Returns errAdapterNotFound if the
  245. // adapter is not yet visible in the NLM enumeration.
  246. func setNetworkCategory(adapterGUID windows.GUID, cat networkCategory) error {
  247. deinit, err := coInit()
  248. if err != nil {
  249. return err
  250. }
  251. defer deinit()
  252. nlm, err := createNetworkListManager()
  253. if err != nil {
  254. return err
  255. }
  256. defer nlm.Release()
  257. enum, err := nlm.GetNetworkConnections()
  258. if err != nil {
  259. return err
  260. }
  261. defer enum.Release()
  262. for {
  263. conn, err := enum.Next()
  264. if err != nil {
  265. return err
  266. }
  267. if conn == nil {
  268. return errAdapterNotFound
  269. }
  270. guid, err := conn.GetAdapterId()
  271. if err != nil || guid != adapterGUID {
  272. conn.Release()
  273. continue
  274. }
  275. net, err := conn.GetNetwork()
  276. conn.Release()
  277. if err != nil {
  278. return err
  279. }
  280. err = net.SetCategory(cat)
  281. net.Release()
  282. return err
  283. }
  284. }
  285. // applyNetworkCategory polls until the wintun adapter shows up in the NLM
  286. // enumeration, then sets the category. Intended to run in its own goroutine.
  287. func applyNetworkCategory(l *slog.Logger, adapterGUID windows.GUID, cat networkCategory) {
  288. // COM Init/Uninit must be paired on the same OS thread.
  289. runtime.LockOSThread()
  290. defer runtime.UnlockOSThread()
  291. const (
  292. attempts = 30
  293. interval = 500 * time.Millisecond
  294. )
  295. for i := 0; i < attempts; i++ {
  296. err := setNetworkCategory(adapterGUID, cat)
  297. if err == nil {
  298. l.Info("Set Windows network category", "category", cat.String())
  299. return
  300. }
  301. if !errors.Is(err, errAdapterNotFound) {
  302. l.Warn("Failed to set Windows network category", "error", err, "category", cat.String())
  303. return
  304. }
  305. time.Sleep(interval)
  306. }
  307. l.Warn("Gave up waiting for adapter to appear in NLM enumeration; network category not set",
  308. "category", cat.String(),
  309. "waited", time.Duration(attempts)*interval,
  310. )
  311. }