network_category_windows.go 9.6 KB

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