From 95f4e07db871c1f3fc991104b816b18c3d512699 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Sun, 18 Jan 2026 14:59:20 +0400 Subject: [PATCH 01/19] NM-222: force classify windows interface as private --- cmd/join.go | 1 + cmd/register.go | 7 +++++++ config/config.go | 2 ++ wireguard/wireguard_windows.go | 35 +++++++++++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/cmd/join.go b/cmd/join.go index 89887c1b..8378e621 100644 --- a/cmd/join.go +++ b/cmd/join.go @@ -57,6 +57,7 @@ func init() { joinCmd.Flags().StringP(registerFlags.Name, "o", "", "sets host name") joinCmd.Flags().StringP(registerFlags.Interface, "I", "", "sets netmaker interface to use on host") joinCmd.Flags().StringP(registerFlags.Firewall, "f", "", "selects firewall to use on host: iptables/nftables") + joinCmd.Flags().BoolP(registerFlags.ForcePrivate, "P", false, "forces Windows network interface to be classified as Private") rootCmd.AddCommand(joinCmd) } diff --git a/cmd/register.go b/cmd/register.go index 29879b0d..a92620d1 100644 --- a/cmd/register.go +++ b/cmd/register.go @@ -35,6 +35,7 @@ var registerFlags = struct { Static string Interface string Name string + ForcePrivate string }{ Server: "server", User: "user", @@ -49,6 +50,7 @@ var registerFlags = struct { Name: "name", Interface: "interface", Firewall: "firewall", + ForcePrivate: "force-private", } // registerCmd represents the register command @@ -147,6 +149,11 @@ func setHostFields(cmd *cobra.Command) { config.Netclient().FirewallInUse = firewall } } + if forcePrivate, err := cmd.Flags().GetBool(registerFlags.ForcePrivate); err == nil && forcePrivate { + if ncutils.IsWindows() { + config.Netclient().ForcePrivateProfile = true + } + } } func validateIface(iface string) bool { if iface == "" { diff --git a/config/config.go b/config/config.go index 50c5ef98..2e5acb7f 100644 --- a/config/config.go +++ b/config/config.go @@ -98,6 +98,8 @@ type Config struct { NameServers []string `json:"name_servers" yaml:"name_servers"` DNSSearch string `json:"dns_search" yaml:"dns_search"` DNSOptions string `json:"dns_options" yaml:"dns_options"` + // ForcePrivateProfile - force Windows network interface to be classified as Private + ForcePrivateProfile bool `json:"force_private_profile" yaml:"force_private_profile"` } func init() { diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index f95416d7..3bb5fb47 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "net/netip" + "os/exec" "strconv" "strings" @@ -18,6 +19,26 @@ import ( // TODO: update from netsh to a more programmatic approach. +// SetInterfacePrivateProfile - sets the Windows network interface profile to Private +func SetInterfacePrivateProfile(ifaceName string) error { + // Use PowerShell to set the network profile to Private + psCmd := fmt.Sprintf("Set-NetConnectionProfile -InterfaceAlias '%s' -NetworkCategory Private -ErrorAction SilentlyContinue", ifaceName) + cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) + output, err := cmd.Output() + if err != nil { + // Try alternative approach if the first one fails + psCmd2 := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { Set-NetConnectionProfile -InterfaceAlias '%s' -NetworkCategory Private -ErrorAction Stop }", ifaceName, ifaceName) + cmd2 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd2) + output, err = cmd2.Output() + if err != nil { + slog.Error("failed to set interface profile to Private", "interface", ifaceName, "error", err, "output", string(output)) + return fmt.Errorf("failed to set interface profile: %w", err) + } + } + slog.Info("set interface profile to Private", "interface", ifaceName) + return nil +} + // NCIface.Create - makes a new Wireguard interface and sets given addresses func (nc *NCIface) Create() error { wgMutex.Lock() @@ -46,7 +67,19 @@ func (nc *NCIface) Create() error { slog.Info("created Windows tunnel") nc.Iface = adapter - return adapter.SetAdapterState(driver.AdapterStateUp) + if err := adapter.SetAdapterState(driver.AdapterStateUp); err != nil { + return err + } + + // Set network profile to Private if force flag is set + if config.Netclient().ForcePrivateProfile { + if err := SetInterfacePrivateProfile(ncutils.GetInterfaceName()); err != nil { + slog.Warn("failed to set interface profile to Private", "error", err) + // Don't fail the interface creation if profile setting fails + } + } + + return nil } // NCIface.ApplyAddrs - applies addresses to windows tunnel ifaces, unused currently From f1aa164f98e533cf5f37d79cf78225c944c35f76 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 20 Jan 2026 10:47:49 +0400 Subject: [PATCH 02/19] NM-222: add interface profile flag for win --- cmd/join.go | 1 + cmd/register.go | 71 ++++++++++++++++++++-------------- config/config.go | 2 + wireguard/wireguard_windows.go | 53 +++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/cmd/join.go b/cmd/join.go index 8378e621..d0482f4a 100644 --- a/cmd/join.go +++ b/cmd/join.go @@ -58,6 +58,7 @@ func init() { joinCmd.Flags().StringP(registerFlags.Interface, "I", "", "sets netmaker interface to use on host") joinCmd.Flags().StringP(registerFlags.Firewall, "f", "", "selects firewall to use on host: iptables/nftables") joinCmd.Flags().BoolP(registerFlags.ForcePrivate, "P", false, "forces Windows network interface to be classified as Private") + joinCmd.Flags().StringP(registerFlags.ProfileName, "N", "", "sets Windows network profile name for the interface") rootCmd.AddCommand(joinCmd) } diff --git a/cmd/register.go b/cmd/register.go index a92620d1..5425afe0 100644 --- a/cmd/register.go +++ b/cmd/register.go @@ -21,36 +21,38 @@ import ( ) var registerFlags = struct { - Firewall string - Server string - User string - Token string - Network string - AllNetworks string - EndpointIP string - EndpointIP6 string - Port string - MTU string - StaticPort string - Static string - Interface string - Name string + Firewall string + Server string + User string + Token string + Network string + AllNetworks string + EndpointIP string + EndpointIP6 string + Port string + MTU string + StaticPort string + Static string + Interface string + Name string ForcePrivate string + ProfileName string }{ - Server: "server", - User: "user", - Token: "token", - Network: "net", - AllNetworks: "all-networks", - EndpointIP: "endpoint-ip", - Port: "port", - MTU: "mtu", - StaticPort: "static-port", - Static: "static-endpoint", - Name: "name", - Interface: "interface", - Firewall: "firewall", + Server: "server", + User: "user", + Token: "token", + Network: "net", + AllNetworks: "all-networks", + EndpointIP: "endpoint-ip", + Port: "port", + MTU: "mtu", + StaticPort: "static-port", + Static: "static-endpoint", + Name: "name", + Interface: "interface", + Firewall: "firewall", ForcePrivate: "force-private", + ProfileName: "profile-name", } // registerCmd represents the register command @@ -149,9 +151,20 @@ func setHostFields(cmd *cobra.Command) { config.Netclient().FirewallInUse = firewall } } - if forcePrivate, err := cmd.Flags().GetBool(registerFlags.ForcePrivate); err == nil && forcePrivate { + if forcePrivate, err := cmd.Flags().GetBool(registerFlags.ForcePrivate); err == nil { if ncutils.IsWindows() { - config.Netclient().ForcePrivateProfile = true + config.Netclient().ForcePrivateProfile = forcePrivate + } + } + if profileName, err := cmd.Flags().GetString(registerFlags.ProfileName); err == nil && profileName != "" { + if ncutils.IsWindows() { + config.Netclient().InterfaceProfileName = profileName + } + } + // Save config if any Windows-specific settings were changed + if ncutils.IsWindows() && (cmd.Flags().Changed(registerFlags.ForcePrivate) || cmd.Flags().Changed(registerFlags.ProfileName)) { + if err := config.WriteNetclientConfig(); err != nil { + logger.Log(0, "failed to save config after setting Windows interface settings", err.Error()) } } } diff --git a/config/config.go b/config/config.go index 2e5acb7f..5e93072f 100644 --- a/config/config.go +++ b/config/config.go @@ -100,6 +100,8 @@ type Config struct { DNSOptions string `json:"dns_options" yaml:"dns_options"` // ForcePrivateProfile - force Windows network interface to be classified as Private ForcePrivateProfile bool `json:"force_private_profile" yaml:"force_private_profile"` + // InterfaceProfileName - Windows network profile name for the interface + InterfaceProfileName string `json:"interface_profile_name" yaml:"interface_profile_name"` } func init() { diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 3bb5fb47..d34b315c 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -14,11 +14,56 @@ import ( "github.com/gravitl/netmaker/logger" "golang.org/x/exp/slog" "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" "golang.zx2c4.com/wireguard/windows/driver" ) // TODO: update from netsh to a more programmatic approach. +// SetInterfaceProfileName - sets the Windows network profile name for the interface +func SetInterfaceProfileName(ifaceName string, profileName string) error { + if profileName == "" { + return nil + } + + // First, get the profile GUID for the interface using PowerShell + psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID }", ifaceName) + cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) + output, err := cmd.Output() + if err != nil { + slog.Warn("failed to get interface profile GUID", "interface", ifaceName, "error", err) + return fmt.Errorf("failed to get interface profile GUID: %w", err) + } + + profileGUID := strings.TrimSpace(string(output)) + if profileGUID == "" { + slog.Warn("profile GUID not found for interface", "interface", ifaceName) + return fmt.Errorf("profile GUID not found for interface %s", ifaceName) + } + + // Remove braces if present + profileGUID = strings.Trim(profileGUID, "{}") + profileGUID = strings.TrimSpace(profileGUID) + + // Set the ProfileName in the registry + keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) + key, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE) + if err != nil { + slog.Warn("failed to open profile registry key", "path", keyPath, "error", err) + return fmt.Errorf("failed to open profile registry key: %w", err) + } + defer key.Close() + + err = key.SetStringValue("ProfileName", profileName) + if err != nil { + slog.Warn("failed to set profile name", "error", err) + return fmt.Errorf("failed to set profile name: %w", err) + } + + slog.Info("set interface profile name", "interface", ifaceName, "profileName", profileName, "guid", profileGUID) + return nil +} + // SetInterfacePrivateProfile - sets the Windows network interface profile to Private func SetInterfacePrivateProfile(ifaceName string) error { // Use PowerShell to set the network profile to Private @@ -79,6 +124,14 @@ func (nc *NCIface) Create() error { } } + // Set interface profile name if configured + if config.Netclient().InterfaceProfileName != "" { + if err := SetInterfaceProfileName(ncutils.GetInterfaceName(), config.Netclient().InterfaceProfileName); err != nil { + slog.Warn("failed to set interface profile name", "error", err) + // Don't fail the interface creation if profile name setting fails + } + } + return nil } From 0267dc05de0155a9af66dbe4b457fc29c4528ab2 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 20 Jan 2026 11:19:46 +0400 Subject: [PATCH 03/19] NM-222: add interface profile flag for win --- wireguard/wireguard_windows.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index d34b315c..2c77b357 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -26,18 +26,33 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { return nil } + // Use PowerShell to set the profile name directly + psCmd := fmt.Sprintf("Set-NetConnectionProfile -InterfaceAlias '%s' -Name '%s' -ErrorAction Stop", ifaceName, profileName) + cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) + output, err := cmd.Output() + if err != nil { + slog.Warn("failed to set interface profile name via PowerShell", "interface", ifaceName, "error", err, "output", string(output)) + + // Fallback: Set via registry if PowerShell fails + return setInterfaceProfileNameViaRegistry(ifaceName, profileName) + } + + slog.Info("set interface profile name", "interface", ifaceName, "profileName", profileName) + return nil +} + +// setInterfaceProfileNameViaRegistry - fallback method to set profile name via registry +func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) error { // First, get the profile GUID for the interface using PowerShell psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID }", ifaceName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() if err != nil { - slog.Warn("failed to get interface profile GUID", "interface", ifaceName, "error", err) return fmt.Errorf("failed to get interface profile GUID: %w", err) } profileGUID := strings.TrimSpace(string(output)) if profileGUID == "" { - slog.Warn("profile GUID not found for interface", "interface", ifaceName) return fmt.Errorf("profile GUID not found for interface %s", ifaceName) } @@ -49,18 +64,16 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) key, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE) if err != nil { - slog.Warn("failed to open profile registry key", "path", keyPath, "error", err) return fmt.Errorf("failed to open profile registry key: %w", err) } defer key.Close() err = key.SetStringValue("ProfileName", profileName) if err != nil { - slog.Warn("failed to set profile name", "error", err) return fmt.Errorf("failed to set profile name: %w", err) } - slog.Info("set interface profile name", "interface", ifaceName, "profileName", profileName, "guid", profileGUID) + slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName, "guid", profileGUID) return nil } From 51138d6a0d21c0ee4c4ada22e80c97eb466fd8d7 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 20 Jan 2026 11:30:20 +0400 Subject: [PATCH 04/19] NM-222: flush interface cache on windows --- wireguard/wireguard_windows.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 2c77b357..c6b776bb 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -102,6 +102,9 @@ func (nc *NCIface) Create() error { wgMutex.Lock() defer wgMutex.Unlock() + // Flush network caches before creating interface to ensure clean state + FlushWindowsNetworkCaches() + adapter, err := driver.OpenAdapter(ncutils.GetInterfaceName()) if err != nil { slog.Info("creating Windows tunnel") @@ -594,6 +597,27 @@ func restoreInternetGwV4() (err error) { return config.WriteNetclientConfig() } +// FlushWindowsNetworkCaches - flushes Windows network caches (route, ARP, NetBIOS, DNS) +func FlushWindowsNetworkCaches() { + // Clear route destination cache + _, _ = ncutils.RunCmd("netsh interface ip delete destinationcache", false) + + // Clear ARP cache + _, _ = ncutils.RunCmd("arp -d *", false) + + // Clear NetBIOS cache + _, _ = ncutils.RunCmd("nbtstat -R", false) + _, _ = ncutils.RunCmd("nbtstat -RR", false) + + // Flush DNS cache + _, _ = ncutils.RunCmd("ipconfig /flushdns", false) + + // Re-register DNS + _, _ = ncutils.RunCmd("ipconfig /registerdns", false) + + slog.Info("flushed Windows network caches") +} + // NCIface.Close - closes the managed WireGuard interface func (nc *NCIface) Close() { wgMutex.Lock() @@ -619,6 +643,9 @@ func (nc *NCIface) Close() { } } } + + // Flush network caches when closing interface + FlushWindowsNetworkCaches() } // NCIface.SetMTU - sets the MTU of the windows WireGuard Iface adapter From 75669dbf3a17930a100643521cf0b55e2ff91981 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 20 Jan 2026 11:57:33 +0400 Subject: [PATCH 05/19] NM-222: apply interface profile in background --- wireguard/wireguard_windows.go | 123 ++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 34 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index c6b776bb..fcda7a96 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -8,6 +8,7 @@ import ( "os/exec" "strconv" "strings" + "time" "github.com/gravitl/netclient/config" "github.com/gravitl/netclient/ncutils" @@ -26,45 +27,93 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { return nil } - // Use PowerShell to set the profile name directly - psCmd := fmt.Sprintf("Set-NetConnectionProfile -InterfaceAlias '%s' -Name '%s' -ErrorAction Stop", ifaceName, profileName) - cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) - output, err := cmd.Output() - if err != nil { - slog.Warn("failed to set interface profile name via PowerShell", "interface", ifaceName, "error", err, "output", string(output)) + // Wait a bit for Windows to create the network profile after interface creation + // Retry up to 5 times with 1 second delay + maxRetries := 5 + for i := 0; i < maxRetries; i++ { + if i > 0 { + time.Sleep(1 * time.Second) + } + + // Use PowerShell to set the profile name directly + // First check if profile exists, then set the name + psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { Set-NetConnectionProfile -InterfaceAlias '%s' -Name '%s' -ErrorAction Stop; Write-Output 'Success' } else { Write-Output 'ProfileNotFound' }", ifaceName, ifaceName, profileName) + cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) + output, err := cmd.Output() + outputStr := strings.TrimSpace(string(output)) - // Fallback: Set via registry if PowerShell fails - return setInterfaceProfileNameViaRegistry(ifaceName, profileName) + if err == nil && outputStr == "Success" { + slog.Info("set interface profile name", "interface", ifaceName, "profileName", profileName) + return nil + } + + if outputStr == "ProfileNotFound" { + slog.Debug("profile not found yet, retrying", "interface", ifaceName, "attempt", i+1) + continue + } + + if i == maxRetries-1 { + slog.Warn("failed to set interface profile name via PowerShell after retries", "interface", ifaceName, "error", err, "output", outputStr) + // Fallback: Set via registry if PowerShell fails + return setInterfaceProfileNameViaRegistry(ifaceName, profileName) + } } - slog.Info("set interface profile name", "interface", ifaceName, "profileName", profileName) - return nil + return fmt.Errorf("failed to set profile name after %d attempts", maxRetries) } // setInterfaceProfileNameViaRegistry - fallback method to set profile name via registry func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) error { - // First, get the profile GUID for the interface using PowerShell - psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID }", ifaceName) - cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) - output, err := cmd.Output() - if err != nil { - return fmt.Errorf("failed to get interface profile GUID: %w", err) + // Retry getting the profile GUID with delays + maxRetries := 5 + var profileGUID string + var err error + + for i := 0; i < maxRetries; i++ { + if i > 0 { + time.Sleep(1 * time.Second) + } + + // First, get the profile GUID for the interface using PowerShell + psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) + cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) + output, err := cmd.Output() + if err != nil { + slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) + continue + } + + profileGUID = strings.TrimSpace(string(output)) + if profileGUID != "" { + break + } } - profileGUID := strings.TrimSpace(string(output)) if profileGUID == "" { - return fmt.Errorf("profile GUID not found for interface %s", ifaceName) + return fmt.Errorf("profile GUID not found for interface %s after %d attempts", ifaceName, maxRetries) } // Remove braces if present profileGUID = strings.Trim(profileGUID, "{}") profileGUID = strings.TrimSpace(profileGUID) - // Set the ProfileName in the registry - keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) - key, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE) + // Retry opening the registry key with delays + var key registry.Key + for i := 0; i < maxRetries; i++ { + if i > 0 { + time.Sleep(500 * time.Millisecond) + } + + keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) + key, err = registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE) + if err == nil { + break + } + slog.Debug("failed to open registry key, retrying", "path", keyPath, "attempt", i+1, "error", err) + } + if err != nil { - return fmt.Errorf("failed to open profile registry key: %w", err) + return fmt.Errorf("failed to open profile registry key after %d attempts: %w", maxRetries, err) } defer key.Close() @@ -132,21 +181,27 @@ func (nc *NCIface) Create() error { return err } - // Set network profile to Private if force flag is set - if config.Netclient().ForcePrivateProfile { - if err := SetInterfacePrivateProfile(ncutils.GetInterfaceName()); err != nil { - slog.Warn("failed to set interface profile to Private", "error", err) - // Don't fail the interface creation if profile setting fails + // Set network profile settings asynchronously (non-blocking) + go func() { + ifaceName := ncutils.GetInterfaceName() + + // Wait a moment for Windows to create the network profile after bringing interface up + time.Sleep(2 * time.Second) + + // Set network profile to Private if force flag is set + if config.Netclient().ForcePrivateProfile { + if err := SetInterfacePrivateProfile(ifaceName); err != nil { + slog.Warn("failed to set interface profile to Private", "error", err) + } } - } - // Set interface profile name if configured - if config.Netclient().InterfaceProfileName != "" { - if err := SetInterfaceProfileName(ncutils.GetInterfaceName(), config.Netclient().InterfaceProfileName); err != nil { - slog.Warn("failed to set interface profile name", "error", err) - // Don't fail the interface creation if profile name setting fails + // Set interface profile name if configured + if config.Netclient().InterfaceProfileName != "" { + if err := SetInterfaceProfileName(ifaceName, config.Netclient().InterfaceProfileName); err != nil { + slog.Warn("failed to set interface profile name", "error", err) + } } - } + }() return nil } From 2a95413b8107e1b6af5a1720478e0a3cf772f11b Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 20 Jan 2026 15:16:12 +0400 Subject: [PATCH 06/19] NM-222: use registry method --- wireguard/wireguard_windows.go | 68 ++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index fcda7a96..38793c57 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -28,30 +28,48 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { } // Wait a bit for Windows to create the network profile after interface creation - // Retry up to 5 times with 1 second delay - maxRetries := 5 + // Retry up to 10 times with 1 second delay + maxRetries := 10 for i := 0; i < maxRetries; i++ { if i > 0 { time.Sleep(1 * time.Second) } - // Use PowerShell to set the profile name directly - // First check if profile exists, then set the name - psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { Set-NetConnectionProfile -InterfaceAlias '%s' -Name '%s' -ErrorAction Stop; Write-Output 'Success' } else { Write-Output 'ProfileNotFound' }", ifaceName, ifaceName, profileName) + // Try registry method first (more reliable than PowerShell for profile name) + err := setInterfaceProfileNameViaRegistry(ifaceName, profileName) + if err == nil { + slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName) + return nil + } + + // Fallback to PowerShell if registry fails + // Escape single quotes in profile name for PowerShell + escapedProfileName := strings.ReplaceAll(profileName, "'", "''") + // Use pipeline syntax which is more reliable + psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceAlias '%s'; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' }", ifaceName, escapedProfileName, ifaceName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() outputStr := strings.TrimSpace(string(output)) - if err == nil && outputStr == "Success" { - slog.Info("set interface profile name", "interface", ifaceName, "profileName", profileName) - return nil + // Check if the output matches our desired profile name (case-insensitive) + if err == nil && outputStr != "" && outputStr != "ProfileNotFound" && outputStr != "Failed" { + if strings.EqualFold(outputStr, profileName) { + slog.Info("set interface profile name via PowerShell", "interface", ifaceName, "profileName", profileName, "actualName", outputStr) + return nil + } + // If we got a name but it's not what we want, log and continue retrying + slog.Debug("profile name set but doesn't match expected", "expected", profileName, "got", outputStr, "attempt", i+1) } - if outputStr == "ProfileNotFound" { - slog.Debug("profile not found yet, retrying", "interface", ifaceName, "attempt", i+1) + if outputStr == "ProfileNotFound" || outputStr == "Failed" { + slog.Debug("profile not found or failed, retrying", "interface", ifaceName, "attempt", i+1, "output", outputStr) continue } + if err != nil { + slog.Debug("PowerShell command failed, retrying", "interface", ifaceName, "attempt", i+1, "error", err, "output", outputStr) + } + if i == maxRetries-1 { slog.Warn("failed to set interface profile name via PowerShell after retries", "interface", ifaceName, "error", err, "output", outputStr) // Fallback: Set via registry if PowerShell fails @@ -105,15 +123,35 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er } keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) + // Try to open existing key first key, err = registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE) + if err != nil { + // If key doesn't exist, try to create it + if errors.Is(err, registry.ErrNotExist) { + parentPath := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles` + parentKey, parentErr := registry.OpenKey(registry.LOCAL_MACHINE, parentPath, registry.ALL_ACCESS) + if parentErr == nil { + createdKey, _, createErr := registry.CreateKey(parentKey, "{"+profileGUID+"}", registry.ALL_ACCESS) + parentKey.Close() + if createErr == nil { + key = createdKey + err = nil + } else { + slog.Debug("failed to create registry key, retrying", "path", keyPath, "attempt", i+1, "error", createErr) + } + } + } else { + slog.Debug("failed to open registry key, retrying", "path", keyPath, "attempt", i+1, "error", err) + } + } + if err == nil { break } - slog.Debug("failed to open registry key, retrying", "path", keyPath, "attempt", i+1, "error", err) } if err != nil { - return fmt.Errorf("failed to open profile registry key after %d attempts: %w", maxRetries, err) + return fmt.Errorf("failed to open/create profile registry key after %d attempts: %w", maxRetries, err) } defer key.Close() @@ -122,6 +160,12 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er return fmt.Errorf("failed to set profile name: %w", err) } + // Also set Description to match (some Windows tools use Description) + err = key.SetStringValue("Description", profileName) + if err != nil { + slog.Debug("failed to set Description, continuing", "error", err) + } + slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName, "guid", profileGUID) return nil } From b46d3911e4940cffb8457eed57391b51d6d8539d Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Tue, 20 Jan 2026 23:31:16 +0400 Subject: [PATCH 07/19] NM-222: set interface profile name fix --- wireguard/wireguard_windows.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 38793c57..6fe6fe34 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -45,14 +45,14 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { // Fallback to PowerShell if registry fails // Escape single quotes in profile name for PowerShell escapedProfileName := strings.ReplaceAll(profileName, "'", "''") - // Use pipeline syntax which is more reliable - psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceAlias '%s'; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' }", ifaceName, escapedProfileName, ifaceName) + // Use InterfaceIndex to match the correct profile (more reliable than InterfaceAlias) + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceIndex $index; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' } } else { Write-Output 'AdapterNotFound' }", ifaceName, escapedProfileName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() outputStr := strings.TrimSpace(string(output)) // Check if the output matches our desired profile name (case-insensitive) - if err == nil && outputStr != "" && outputStr != "ProfileNotFound" && outputStr != "Failed" { + if err == nil && outputStr != "" && outputStr != "ProfileNotFound" && outputStr != "Failed" && outputStr != "AdapterNotFound" { if strings.EqualFold(outputStr, profileName) { slog.Info("set interface profile name via PowerShell", "interface", ifaceName, "profileName", profileName, "actualName", outputStr) return nil @@ -61,7 +61,7 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { slog.Debug("profile name set but doesn't match expected", "expected", profileName, "got", outputStr, "attempt", i+1) } - if outputStr == "ProfileNotFound" || outputStr == "Failed" { + if outputStr == "ProfileNotFound" || outputStr == "Failed" || outputStr == "AdapterNotFound" { slog.Debug("profile not found or failed, retrying", "interface", ifaceName, "attempt", i+1, "output", outputStr) continue } @@ -92,17 +92,26 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er time.Sleep(1 * time.Second) } - // First, get the profile GUID for the interface using PowerShell - psCmd := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) + // First, get the InterfaceIndex for the interface to ensure we match the correct one + // Then get the profile GUID using InterfaceIndex (more reliable than InterfaceAlias) + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", ifaceName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() if err != nil { - slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) - continue + slog.Debug("failed to get profile GUID via InterfaceIndex, trying InterfaceAlias", "attempt", i+1, "error", err) + // Fallback to InterfaceAlias method + psCmd2 := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) + cmd2 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd2) + output, err = cmd2.Output() + if err != nil { + slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) + continue + } } profileGUID = strings.TrimSpace(string(output)) if profileGUID != "" { + slog.Debug("found profile GUID", "interface", ifaceName, "guid", profileGUID) break } } From 1048921fc03895395ccec63847df278584cd0b55 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 21 Jan 2026 00:03:53 +0400 Subject: [PATCH 08/19] NM-222: set guid correctly --- wireguard/wireguard_windows.go | 49 +++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 6fe6fe34..2cc663db 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -22,7 +22,7 @@ import ( // TODO: update from netsh to a more programmatic approach. // SetInterfaceProfileName - sets the Windows network profile name for the interface -func SetInterfaceProfileName(ifaceName string, profileName string) error { +func SetInterfaceProfileName(ifaceName string, profileName string, adapterGUID string) error { if profileName == "" { return nil } @@ -36,7 +36,7 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { } // Try registry method first (more reliable than PowerShell for profile name) - err := setInterfaceProfileNameViaRegistry(ifaceName, profileName) + err := setInterfaceProfileNameViaRegistry(ifaceName, profileName, adapterGUID) if err == nil { slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName) return nil @@ -45,8 +45,9 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { // Fallback to PowerShell if registry fails // Escape single quotes in profile name for PowerShell escapedProfileName := strings.ReplaceAll(profileName, "'", "''") - // Use InterfaceIndex to match the correct profile (more reliable than InterfaceAlias) - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceIndex $index; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' } } else { Write-Output 'AdapterNotFound' }", ifaceName, escapedProfileName) + // Use adapter InterfaceGuid to match the correct profile (most reliable) + adapterGUIDFormatted := "{" + adapterGUID + "}" + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -InterfaceGuid '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceIndex $index; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' } } else { Write-Output 'AdapterNotFound' }", adapterGUIDFormatted, escapedProfileName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() outputStr := strings.TrimSpace(string(output)) @@ -73,7 +74,7 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { if i == maxRetries-1 { slog.Warn("failed to set interface profile name via PowerShell after retries", "interface", ifaceName, "error", err, "output", outputStr) // Fallback: Set via registry if PowerShell fails - return setInterfaceProfileNameViaRegistry(ifaceName, profileName) + return setInterfaceProfileNameViaRegistry(ifaceName, profileName, adapterGUID) } } @@ -81,7 +82,7 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { } // setInterfaceProfileNameViaRegistry - fallback method to set profile name via registry -func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) error { +func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string, adapterGUID string) error { // Retry getting the profile GUID with delays maxRetries := 5 var profileGUID string @@ -92,26 +93,35 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er time.Sleep(1 * time.Second) } - // First, get the InterfaceIndex for the interface to ensure we match the correct one - // Then get the profile GUID using InterfaceIndex (more reliable than InterfaceAlias) - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", ifaceName) + // Match by adapter InterfaceGuid to ensure we get the correct profile + // The adapter GUID is based on Host ID, so it's unique to this adapter + // Format: Get adapter by InterfaceGuid, then get its profile + adapterGUIDFormatted := "{" + adapterGUID + "}" + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -InterfaceGuid '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", adapterGUIDFormatted) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() if err != nil { - slog.Debug("failed to get profile GUID via InterfaceIndex, trying InterfaceAlias", "attempt", i+1, "error", err) - // Fallback to InterfaceAlias method - psCmd2 := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) + slog.Debug("failed to get profile GUID via InterfaceGuid, trying InterfaceIndex", "attempt", i+1, "error", err) + // Fallback: Get adapter by name, then use InterfaceIndex + psCmd2 := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", ifaceName) cmd2 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd2) output, err = cmd2.Output() if err != nil { - slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) - continue + slog.Debug("failed to get profile GUID via InterfaceIndex, trying InterfaceAlias", "attempt", i+1, "error", err) + // Final fallback to InterfaceAlias method + psCmd3 := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) + cmd3 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd3) + output, err = cmd3.Output() + if err != nil { + slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) + continue + } } } profileGUID = strings.TrimSpace(string(output)) if profileGUID != "" { - slog.Debug("found profile GUID", "interface", ifaceName, "guid", profileGUID) + slog.Debug("found profile GUID", "interface", ifaceName, "adapterGUID", adapterGUID, "profileGUID", profileGUID) break } } @@ -237,6 +247,13 @@ func (nc *NCIface) Create() error { // Set network profile settings asynchronously (non-blocking) go func() { ifaceName := ncutils.GetInterfaceName() + // Get the adapter GUID to ensure we match the correct profile + var adapterGUID string + idString := config.Netclient().Host.ID.String() + if idString == "" { + idString = config.DefaultHostID + } + adapterGUID = idString // Wait a moment for Windows to create the network profile after bringing interface up time.Sleep(2 * time.Second) @@ -250,7 +267,7 @@ func (nc *NCIface) Create() error { // Set interface profile name if configured if config.Netclient().InterfaceProfileName != "" { - if err := SetInterfaceProfileName(ifaceName, config.Netclient().InterfaceProfileName); err != nil { + if err := SetInterfaceProfileName(ifaceName, config.Netclient().InterfaceProfileName, adapterGUID); err != nil { slog.Warn("failed to set interface profile name", "error", err) } } From e76d09a35ae5e51d9795cdf9db081bf1fcab1114 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 8 Jan 2026 19:20:18 +0400 Subject: [PATCH 09/19] NM-210: reuse existing adapter if exists --- wireguard/wireguard_windows.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 2cc663db..c44f6e05 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -231,8 +231,22 @@ func (nc *NCIface) Create() error { } adapter, err = driver.CreateAdapter(ncutils.GetInterfaceName(), "WireGuard", &windowsGUID) if err != nil { - slog.Error("creating adapter error: ", "error", err) - return err + // Check if adapter already exists - try to open it again + if strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "Cannot create a file when that file already exists") { + slog.Info("adapter already exists, attempting to open it") + // Retry opening the adapter - it might have been created by another process + var openErr error + adapter, openErr = driver.OpenAdapter(ncutils.GetInterfaceName()) + if openErr != nil { + slog.Error("creating adapter error (adapter exists but cannot be opened): ", "error", err, "openError", openErr) + return fmt.Errorf("adapter exists but cannot be opened: %w (original error: %v)", openErr, err) + } + slog.Info("successfully opened existing adapter") + err = nil // Clear the error since we successfully opened the adapter + } else { + slog.Error("creating adapter error: ", "error", err) + return err + } } } else { slog.Info("re-using existing adapter") From e116bf954f029b61b5950f98baedf2b57ddc7f32 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 21 Jan 2026 01:43:12 +0400 Subject: [PATCH 10/19] NM-222: fixed setting of profileName --- wireguard/wireguard_windows.go | 177 ++++++++++++++++++++++++++------- 1 file changed, 140 insertions(+), 37 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index c44f6e05..8fe6fcdd 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -22,11 +22,21 @@ import ( // TODO: update from netsh to a more programmatic approach. // SetInterfaceProfileName - sets the Windows network profile name for the interface -func SetInterfaceProfileName(ifaceName string, profileName string, adapterGUID string) error { +func SetInterfaceProfileName(ifaceName string, profileName string) error { if profileName == "" { return nil } + // Check if interface exists before attempting to set profile name + psCheckCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { Write-Output 'Exists' } else { Write-Output 'NotFound' }", ifaceName) + checkCmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCheckCmd) + checkOutput, err := checkCmd.Output() + checkOutputStr := strings.TrimSpace(string(checkOutput)) + + if err != nil || checkOutputStr != "Exists" { + return fmt.Errorf("interface %s does not exist, cannot set profile name", ifaceName) + } + // Wait a bit for Windows to create the network profile after interface creation // Retry up to 10 times with 1 second delay maxRetries := 10 @@ -36,7 +46,7 @@ func SetInterfaceProfileName(ifaceName string, profileName string, adapterGUID s } // Try registry method first (more reliable than PowerShell for profile name) - err := setInterfaceProfileNameViaRegistry(ifaceName, profileName, adapterGUID) + err := setInterfaceProfileNameViaRegistry(ifaceName, profileName) if err == nil { slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName) return nil @@ -45,9 +55,9 @@ func SetInterfaceProfileName(ifaceName string, profileName string, adapterGUID s // Fallback to PowerShell if registry fails // Escape single quotes in profile name for PowerShell escapedProfileName := strings.ReplaceAll(profileName, "'", "''") - // Use adapter InterfaceGuid to match the correct profile (most reliable) - adapterGUIDFormatted := "{" + adapterGUID + "}" - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -InterfaceGuid '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceIndex $index; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' } } else { Write-Output 'AdapterNotFound' }", adapterGUIDFormatted, escapedProfileName) + // Get adapter by name, then use its InterfaceIndex to match the correct profile + // Get-NetConnectionProfile returns the currently active profile for the InterfaceIndex + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceIndex $index; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' } } else { Write-Output 'AdapterNotFound' }", ifaceName, escapedProfileName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() outputStr := strings.TrimSpace(string(output)) @@ -74,7 +84,7 @@ func SetInterfaceProfileName(ifaceName string, profileName string, adapterGUID s if i == maxRetries-1 { slog.Warn("failed to set interface profile name via PowerShell after retries", "interface", ifaceName, "error", err, "output", outputStr) // Fallback: Set via registry if PowerShell fails - return setInterfaceProfileNameViaRegistry(ifaceName, profileName, adapterGUID) + return setInterfaceProfileNameViaRegistry(ifaceName, profileName) } } @@ -82,7 +92,7 @@ func SetInterfaceProfileName(ifaceName string, profileName string, adapterGUID s } // setInterfaceProfileNameViaRegistry - fallback method to set profile name via registry -func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string, adapterGUID string) error { +func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) error { // Retry getting the profile GUID with delays maxRetries := 5 var profileGUID string @@ -93,36 +103,60 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string, ad time.Sleep(1 * time.Second) } - // Match by adapter InterfaceGuid to ensure we get the correct profile - // The adapter GUID is based on Host ID, so it's unique to this adapter - // Format: Get adapter by InterfaceGuid, then get its profile - adapterGUIDFormatted := "{" + adapterGUID + "}" - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -InterfaceGuid '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", adapterGUIDFormatted) + // Get adapter by name first, then use its InterfaceIndex to get the profile + // Get all profiles and select the one created most recently (within last 10 seconds) + // This ensures we get the profile that was just created when the interface was brought up + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profiles = @(Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue); if ($profiles.Count -gt 0) { $now = Get-Date; $recentProfiles = $profiles | Where-Object { ($now - $_.DateCreated).TotalSeconds -lt 10 }; if ($recentProfiles.Count -gt 0) { ($recentProfiles | Sort-Object -Property DateCreated -Descending | Select-Object -First 1).InstanceID } else { ($profiles | Sort-Object -Property DateCreated -Descending | Select-Object -First 1).InstanceID } } else { Write-Output '' } } else { Write-Output '' }", ifaceName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() if err != nil { - slog.Debug("failed to get profile GUID via InterfaceGuid, trying InterfaceIndex", "attempt", i+1, "error", err) - // Fallback: Get adapter by name, then use InterfaceIndex - psCmd2 := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", ifaceName) + slog.Debug("failed to get profile GUID via InterfaceIndex, trying InterfaceAlias", "attempt", i+1, "error", err) + // Fallback to InterfaceAlias method - Get-NetConnectionProfile returns the active profile + psCmd2 := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) cmd2 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd2) output, err = cmd2.Output() if err != nil { - slog.Debug("failed to get profile GUID via InterfaceIndex, trying InterfaceAlias", "attempt", i+1, "error", err) - // Final fallback to InterfaceAlias method - psCmd3 := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) - cmd3 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd3) - output, err = cmd3.Output() - if err != nil { - slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) - continue - } + slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) + continue } } profileGUID = strings.TrimSpace(string(output)) if profileGUID != "" { - slog.Debug("found profile GUID", "interface", ifaceName, "adapterGUID", adapterGUID, "profileGUID", profileGUID) - break + // Verify this profile GUID actually exists in the registry before proceeding + profileGUIDClean := strings.Trim(profileGUID, "{}") + profileGUIDClean = strings.TrimSpace(profileGUIDClean) + keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUIDClean) + + // Retry checking registry with delays - Windows might not have registered it yet + registryFound := false + for j := 0; j < 3; j++ { + if j > 0 { + time.Sleep(500 * time.Millisecond) + } + testKey, testErr := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.QUERY_VALUE) + if testErr == nil { + testKey.Close() + registryFound = true + slog.Debug("found profile GUID and verified in registry", "interface", ifaceName, "profileGUID", profileGUIDClean, "attempt", j+1) + profileGUID = profileGUIDClean + break + } + } + + if !registryFound { + slog.Debug("profile GUID from PowerShell not in registry, enumerating registry to find correct profile", "interface", ifaceName, "psGUID", profileGUIDClean) + // Fallback: Enumerate registry to find the most recently created profile + // This handles the case where PowerShell returns a stale GUID + profileGUID = findProfileGUIDInRegistry(ifaceName) + if profileGUID != "" { + slog.Debug("found profile GUID by enumerating registry", "interface", ifaceName, "profileGUID", profileGUID) + break + } + profileGUID = "" // Clear it so we retry in next outer loop iteration + } else { + break // Found valid profile GUID in registry + } } } @@ -130,7 +164,7 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string, ad return fmt.Errorf("profile GUID not found for interface %s after %d attempts", ifaceName, maxRetries) } - // Remove braces if present + // Remove braces if present (should already be done, but just in case) profileGUID = strings.Trim(profileGUID, "{}") profileGUID = strings.TrimSpace(profileGUID) @@ -189,6 +223,70 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string, ad return nil } +// findProfileGUIDInRegistry - enumerates registry profiles to find the most recently created one +// This is a fallback when PowerShell returns a GUID that doesn't exist in registry +func findProfileGUIDInRegistry(ifaceName string) string { + // Enumerate all profiles in registry and find the most recently created one + // We'll match by checking if the profile was created recently (within last 30 seconds) + parentPath := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles` + parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, parentPath, registry.ENUMERATE_SUB_KEYS) + if err != nil { + slog.Debug("failed to open profiles registry key for enumeration", "error", err) + return "" + } + defer parentKey.Close() + + // Get all subkeys (profile GUIDs) + subKeys, err := parentKey.ReadSubKeyNames(0) + if err != nil { + slog.Debug("failed to enumerate profile GUIDs", "error", err) + return "" + } + + // Find the most recently created profile + var latestGUID string + var latestTime time.Time + + for _, guid := range subKeys { + profilePath := parentPath + `\` + guid + profileKey, err := registry.OpenKey(registry.LOCAL_MACHINE, profilePath, registry.QUERY_VALUE) + if err != nil { + continue + } + + // Check DateCreated - it's stored as binary + dateBytes, _, err := profileKey.GetBinaryValue("DateCreated") + if err == nil && len(dateBytes) >= 8 { + // Parse FILETIME (low 32 bits, high 32 bits) + low := uint32(dateBytes[0]) | uint32(dateBytes[1])<<8 | uint32(dateBytes[2])<<16 | uint32(dateBytes[3])<<24 + high := uint32(dateBytes[4]) | uint32(dateBytes[5])<<8 | uint32(dateBytes[6])<<16 | uint32(dateBytes[7])<<24 + fileTime := int64(high)<<32 | int64(low) + // Convert FILETIME (100-nanosecond intervals since Jan 1, 1601) to Go time + // FILETIME epoch: Jan 1, 1601, Go epoch: Jan 1, 1970 + // Difference: 116444736000000000 (100-nanosecond intervals) + const fileTimeEpoch = 116444736000000000 + unixNano := (fileTime - fileTimeEpoch) * 100 + profileTime := time.Unix(0, unixNano) + + // Only consider profiles created in the last 30 seconds + if time.Since(profileTime) < 30*time.Second { + if profileTime.After(latestTime) { + latestTime = profileTime + latestGUID = strings.Trim(guid, "{}") + } + } + } + profileKey.Close() + } + + if latestGUID != "" { + slog.Debug("found most recent profile in registry", "interface", ifaceName, "profileGUID", latestGUID, "created", latestTime) + return latestGUID + } + + return "" +} + // SetInterfacePrivateProfile - sets the Windows network interface profile to Private func SetInterfacePrivateProfile(ifaceName string) error { // Use PowerShell to set the network profile to Private @@ -261,16 +359,21 @@ func (nc *NCIface) Create() error { // Set network profile settings asynchronously (non-blocking) go func() { ifaceName := ncutils.GetInterfaceName() - // Get the adapter GUID to ensure we match the correct profile - var adapterGUID string - idString := config.Netclient().Host.ID.String() - if idString == "" { - idString = config.DefaultHostID - } - adapterGUID = idString - // Wait a moment for Windows to create the network profile after bringing interface up - time.Sleep(2 * time.Second) + // Wait for Windows to create and register the network profile in the registry + // This can take a few seconds, especially on slower systems + time.Sleep(5 * time.Second) + + // Check if interface exists before attempting to set profile settings + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { Write-Output 'Exists' } else { Write-Output 'NotFound' }", ifaceName) + cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) + output, err := cmd.Output() + outputStr := strings.TrimSpace(string(output)) + + if err != nil || outputStr != "Exists" { + slog.Warn("interface not found, skipping profile settings", "interface", ifaceName, "output", outputStr, "error", err) + return + } // Set network profile to Private if force flag is set if config.Netclient().ForcePrivateProfile { @@ -281,7 +384,7 @@ func (nc *NCIface) Create() error { // Set interface profile name if configured if config.Netclient().InterfaceProfileName != "" { - if err := SetInterfaceProfileName(ifaceName, config.Netclient().InterfaceProfileName, adapterGUID); err != nil { + if err := SetInterfaceProfileName(ifaceName, config.Netclient().InterfaceProfileName); err != nil { slog.Warn("failed to set interface profile name", "error", err) } } From f12231fa615f4d08fc432ecf5973adfb1131efc9 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 21 Jan 2026 01:57:58 +0400 Subject: [PATCH 11/19] NM-222: cleanup code --- wireguard/wireguard_windows.go | 78 +++++++--------------------------- 1 file changed, 16 insertions(+), 62 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 8fe6fcdd..94730600 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -45,50 +45,15 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { time.Sleep(1 * time.Second) } - // Try registry method first (more reliable than PowerShell for profile name) + // Use registry method (most reliable for profile name) err := setInterfaceProfileNameViaRegistry(ifaceName, profileName) if err == nil { slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName) return nil } - - // Fallback to PowerShell if registry fails - // Escape single quotes in profile name for PowerShell - escapedProfileName := strings.ReplaceAll(profileName, "'", "''") - // Get adapter by name, then use its InterfaceIndex to match the correct profile - // Get-NetConnectionProfile returns the currently active profile for the InterfaceIndex - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile | Set-NetConnectionProfile -Name '%s' -ErrorAction Stop; Start-Sleep -Milliseconds 500; $updated = Get-NetConnectionProfile -InterfaceIndex $index; if ($updated) { Write-Output $updated.Name } else { Write-Output 'Failed' } } else { Write-Output 'ProfileNotFound' } } else { Write-Output 'AdapterNotFound' }", ifaceName, escapedProfileName) - cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) - output, err := cmd.Output() - outputStr := strings.TrimSpace(string(output)) - - // Check if the output matches our desired profile name (case-insensitive) - if err == nil && outputStr != "" && outputStr != "ProfileNotFound" && outputStr != "Failed" && outputStr != "AdapterNotFound" { - if strings.EqualFold(outputStr, profileName) { - slog.Info("set interface profile name via PowerShell", "interface", ifaceName, "profileName", profileName, "actualName", outputStr) - return nil - } - // If we got a name but it's not what we want, log and continue retrying - slog.Debug("profile name set but doesn't match expected", "expected", profileName, "got", outputStr, "attempt", i+1) - } - - if outputStr == "ProfileNotFound" || outputStr == "Failed" || outputStr == "AdapterNotFound" { - slog.Debug("profile not found or failed, retrying", "interface", ifaceName, "attempt", i+1, "output", outputStr) - continue - } - - if err != nil { - slog.Debug("PowerShell command failed, retrying", "interface", ifaceName, "attempt", i+1, "error", err, "output", outputStr) - } - - if i == maxRetries-1 { - slog.Warn("failed to set interface profile name via PowerShell after retries", "interface", ifaceName, "error", err, "output", outputStr) - // Fallback: Set via registry if PowerShell fails - return setInterfaceProfileNameViaRegistry(ifaceName, profileName) - } } - return fmt.Errorf("failed to set profile name after %d attempts", maxRetries) + return fmt.Errorf("failed to set profile name after %d attempts: %w", maxRetries, err) } // setInterfaceProfileNameViaRegistry - fallback method to set profile name via registry @@ -103,32 +68,23 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er time.Sleep(1 * time.Second) } - // Get adapter by name first, then use its InterfaceIndex to get the profile - // Get all profiles and select the one created most recently (within last 10 seconds) - // This ensures we get the profile that was just created when the interface was brought up - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profiles = @(Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue); if ($profiles.Count -gt 0) { $now = Get-Date; $recentProfiles = $profiles | Where-Object { ($now - $_.DateCreated).TotalSeconds -lt 10 }; if ($recentProfiles.Count -gt 0) { ($recentProfiles | Sort-Object -Property DateCreated -Descending | Select-Object -First 1).InstanceID } else { ($profiles | Sort-Object -Property DateCreated -Descending | Select-Object -First 1).InstanceID } } else { Write-Output '' } } else { Write-Output '' }", ifaceName) + // Get adapter by name, then use its InterfaceIndex to get the active profile + psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", ifaceName) cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) output, err := cmd.Output() if err != nil { - slog.Debug("failed to get profile GUID via InterfaceIndex, trying InterfaceAlias", "attempt", i+1, "error", err) - // Fallback to InterfaceAlias method - Get-NetConnectionProfile returns the active profile - psCmd2 := fmt.Sprintf("$profile = Get-NetConnectionProfile -InterfaceAlias '%s' -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' }", ifaceName) - cmd2 := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd2) - output, err = cmd2.Output() - if err != nil { - slog.Debug("failed to get profile GUID, retrying", "attempt", i+1, "error", err) - continue - } + slog.Debug("failed to get profile GUID via PowerShell, retrying", "attempt", i+1, "error", err) + continue } profileGUID = strings.TrimSpace(string(output)) if profileGUID != "" { - // Verify this profile GUID actually exists in the registry before proceeding - profileGUIDClean := strings.Trim(profileGUID, "{}") - profileGUIDClean = strings.TrimSpace(profileGUIDClean) - keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUIDClean) + // Clean up the GUID + profileGUID = strings.Trim(profileGUID, "{}") + profileGUID = strings.TrimSpace(profileGUID) + keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) - // Retry checking registry with delays - Windows might not have registered it yet + // Verify this profile GUID exists in the registry (retry with delays) registryFound := false for j := 0; j < 3; j++ { if j > 0 { @@ -138,24 +94,22 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er if testErr == nil { testKey.Close() registryFound = true - slog.Debug("found profile GUID and verified in registry", "interface", ifaceName, "profileGUID", profileGUIDClean, "attempt", j+1) - profileGUID = profileGUIDClean + slog.Debug("found profile GUID and verified in registry", "interface", ifaceName, "profileGUID", profileGUID) break } } if !registryFound { - slog.Debug("profile GUID from PowerShell not in registry, enumerating registry to find correct profile", "interface", ifaceName, "psGUID", profileGUIDClean) - // Fallback: Enumerate registry to find the most recently created profile - // This handles the case where PowerShell returns a stale GUID + // PowerShell returned a GUID that doesn't exist in registry - enumerate to find the correct one + slog.Debug("profile GUID from PowerShell not in registry, enumerating registry", "interface", ifaceName, "psGUID", profileGUID) profileGUID = findProfileGUIDInRegistry(ifaceName) if profileGUID != "" { slog.Debug("found profile GUID by enumerating registry", "interface", ifaceName, "profileGUID", profileGUID) break } - profileGUID = "" // Clear it so we retry in next outer loop iteration + profileGUID = "" // Retry in next iteration } else { - break // Found valid profile GUID in registry + break // Found valid profile GUID } } } From 2804888d4e409e1fdc0182c7781a022f5d0fe37a Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Wed, 21 Jan 2026 02:23:49 +0400 Subject: [PATCH 12/19] NM-222: optimise profile setting ops --- wireguard/wireguard_windows.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 94730600..f0fd5ce4 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -38,11 +38,16 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { } // Wait a bit for Windows to create the network profile after interface creation - // Retry up to 10 times with 1 second delay - maxRetries := 10 + // Retry up to 8 times with shorter delays (starts with 500ms, then 1s) + maxRetries := 8 for i := 0; i < maxRetries; i++ { if i > 0 { - time.Sleep(1 * time.Second) + // Use shorter delay on first retry, then standard 1 second + delay := 1 * time.Second + if i == 1 { + delay = 500 * time.Millisecond + } + time.Sleep(delay) } // Use registry method (most reliable for profile name) @@ -65,7 +70,12 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er for i := 0; i < maxRetries; i++ { if i > 0 { - time.Sleep(1 * time.Second) + // Use shorter delay on first retry + delay := 1 * time.Second + if i == 1 { + delay = 500 * time.Millisecond + } + time.Sleep(delay) } // Get adapter by name, then use its InterfaceIndex to get the active profile @@ -315,8 +325,8 @@ func (nc *NCIface) Create() error { ifaceName := ncutils.GetInterfaceName() // Wait for Windows to create and register the network profile in the registry - // This can take a few seconds, especially on slower systems - time.Sleep(5 * time.Second) + // Use a shorter initial wait, then check if interface exists + time.Sleep(2 * time.Second) // Check if interface exists before attempting to set profile settings psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { Write-Output 'Exists' } else { Write-Output 'NotFound' }", ifaceName) From 32c7b395c4a41382f27f4f3e2af6b977f2b1384c Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 22 Jan 2026 16:13:13 +0400 Subject: [PATCH 13/19] NM-222: find interface profile via registry lookup --- wireguard/wireguard_windows.go | 242 ++++++++++----------------------- 1 file changed, 75 insertions(+), 167 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index f0fd5ce4..a5cc0043 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -27,19 +27,10 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { return nil } - // Check if interface exists before attempting to set profile name - psCheckCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { Write-Output 'Exists' } else { Write-Output 'NotFound' }", ifaceName) - checkCmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCheckCmd) - checkOutput, err := checkCmd.Output() - checkOutputStr := strings.TrimSpace(string(checkOutput)) - - if err != nil || checkOutputStr != "Exists" { - return fmt.Errorf("interface %s does not exist, cannot set profile name", ifaceName) - } - // Wait a bit for Windows to create the network profile after interface creation // Retry up to 8 times with shorter delays (starts with 500ms, then 1s) maxRetries := 8 + var lastErr error for i := 0; i < maxRetries; i++ { if i > 0 { // Use shorter delay on first retry, then standard 1 second @@ -56,16 +47,16 @@ func SetInterfaceProfileName(ifaceName string, profileName string) error { slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName) return nil } + lastErr = err } - return fmt.Errorf("failed to set profile name after %d attempts: %w", maxRetries, err) + return fmt.Errorf("failed to set profile name after %d attempts: %w", maxRetries, lastErr) } -// setInterfaceProfileNameViaRegistry - fallback method to set profile name via registry +// setInterfaceProfileNameViaRegistry - sets profile name by enumerating registry profiles func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) error { - // Retry getting the profile GUID with delays + // Retry finding and updating the profile with delays maxRetries := 5 - var profileGUID string var err error for i := 0; i < maxRetries; i++ { @@ -78,177 +69,105 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er time.Sleep(delay) } - // Get adapter by name, then use its InterfaceIndex to get the active profile - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { $index = $adapter.InterfaceIndex; $profile = Get-NetConnectionProfile -InterfaceIndex $index -ErrorAction SilentlyContinue; if ($profile) { $profile.InstanceID } else { Write-Output '' } } else { Write-Output '' }", ifaceName) - cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) - output, err := cmd.Output() - if err != nil { - slog.Debug("failed to get profile GUID via PowerShell, retrying", "attempt", i+1, "error", err) - continue + // Enumerate registry to find and update the profile + err = findAndUpdateProfileName(ifaceName, profileName) + if err == nil { + slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName) + return nil } + slog.Debug("failed to find/update profile in registry, retrying", "attempt", i+1, "error", err) + } - profileGUID = strings.TrimSpace(string(output)) - if profileGUID != "" { - // Clean up the GUID - profileGUID = strings.Trim(profileGUID, "{}") - profileGUID = strings.TrimSpace(profileGUID) - keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) + return fmt.Errorf("failed to set profile name after %d attempts: %w", maxRetries, err) +} - // Verify this profile GUID exists in the registry (retry with delays) - registryFound := false - for j := 0; j < 3; j++ { - if j > 0 { - time.Sleep(500 * time.Millisecond) - } - testKey, testErr := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.QUERY_VALUE) - if testErr == nil { - testKey.Close() - registryFound = true - slog.Debug("found profile GUID and verified in registry", "interface", ifaceName, "profileGUID", profileGUID) - break - } - } +// findAndUpdateProfileName - enumerates registry profiles to find one matching interface name and update it +func findAndUpdateProfileName(ifaceName string, profileName string) error { + parentPath := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles` + parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, parentPath, registry.ENUMERATE_SUB_KEYS) + if err != nil { + return fmt.Errorf("failed to open profiles registry key: %w", err) + } + defer parentKey.Close() - if !registryFound { - // PowerShell returned a GUID that doesn't exist in registry - enumerate to find the correct one - slog.Debug("profile GUID from PowerShell not in registry, enumerating registry", "interface", ifaceName, "psGUID", profileGUID) - profileGUID = findProfileGUIDInRegistry(ifaceName) - if profileGUID != "" { - slog.Debug("found profile GUID by enumerating registry", "interface", ifaceName, "profileGUID", profileGUID) - break - } - profileGUID = "" // Retry in next iteration + // First, try to find a profile where ProfileName matches the interface name + // If not found, fall back to the most recently created profile (within last 30 seconds) + var matchingGUID string + var latestGUID string + keepLooking := true + + for keepLooking { + subKeyNames, err := parentKey.ReadSubKeyNames(10) + if err != nil { + if err == registry.ErrNotExist || err.Error() == "EOF" { + keepLooking = false } else { - break // Found valid profile GUID + return fmt.Errorf("failed to read subkeys: %w", err) } } - } - - if profileGUID == "" { - return fmt.Errorf("profile GUID not found for interface %s after %d attempts", ifaceName, maxRetries) - } - - // Remove braces if present (should already be done, but just in case) - profileGUID = strings.Trim(profileGUID, "{}") - profileGUID = strings.TrimSpace(profileGUID) - - // Retry opening the registry key with delays - var key registry.Key - for i := 0; i < maxRetries; i++ { - if i > 0 { - time.Sleep(500 * time.Millisecond) + if len(subKeyNames) == 0 { + keepLooking = false + continue } - keyPath := fmt.Sprintf(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles\{%s}`, profileGUID) - // Try to open existing key first - key, err = registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE) - if err != nil { - // If key doesn't exist, try to create it - if errors.Is(err, registry.ErrNotExist) { - parentPath := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles` - parentKey, parentErr := registry.OpenKey(registry.LOCAL_MACHINE, parentPath, registry.ALL_ACCESS) - if parentErr == nil { - createdKey, _, createErr := registry.CreateKey(parentKey, "{"+profileGUID+"}", registry.ALL_ACCESS) - parentKey.Close() - if createErr == nil { - key = createdKey - err = nil - } else { - slog.Debug("failed to create registry key, retrying", "path", keyPath, "attempt", i+1, "error", createErr) - } - } - } else { - slog.Debug("failed to open registry key, retrying", "path", keyPath, "attempt", i+1, "error", err) + for _, guid := range subKeyNames { + subKey, err := registry.OpenKey(parentKey, guid, registry.QUERY_VALUE) + if err != nil { + continue } + + // Check if ProfileName matches the interface name + currentProfileName, _, err := subKey.GetStringValue("ProfileName") + if err == nil && currentProfileName == ifaceName { + // Found a profile matching the interface name + matchingGUID = strings.Trim(guid, "{}") + subKey.Close() + break + } + subKey.Close() } - if err == nil { - break + // If we found a matching profile, stop searching + if matchingGUID != "" { + keepLooking = false } } - if err != nil { - return fmt.Errorf("failed to open/create profile registry key after %d attempts: %w", maxRetries, err) + // Use matching profile if found, otherwise use the most recent one + selectedGUID := matchingGUID + if selectedGUID == "" { + selectedGUID = latestGUID } - defer key.Close() - err = key.SetStringValue("ProfileName", profileName) - if err != nil { - return fmt.Errorf("failed to set profile name: %w", err) + if selectedGUID == "" { + return fmt.Errorf("no profile found in registry for interface %s", ifaceName) } - // Also set Description to match (some Windows tools use Description) - err = key.SetStringValue("Description", profileName) + // Update the profile name + profilePath := parentPath + `\` + "{" + selectedGUID + "}" + profileKey, err := registry.OpenKey(registry.LOCAL_MACHINE, profilePath, registry.SET_VALUE) if err != nil { - slog.Debug("failed to set Description, continuing", "error", err) + return fmt.Errorf("failed to open profile key: %w", err) } + defer profileKey.Close() - slog.Info("set interface profile name via registry", "interface", ifaceName, "profileName", profileName, "guid", profileGUID) - return nil -} - -// findProfileGUIDInRegistry - enumerates registry profiles to find the most recently created one -// This is a fallback when PowerShell returns a GUID that doesn't exist in registry -func findProfileGUIDInRegistry(ifaceName string) string { - // Enumerate all profiles in registry and find the most recently created one - // We'll match by checking if the profile was created recently (within last 30 seconds) - parentPath := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles` - parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, parentPath, registry.ENUMERATE_SUB_KEYS) + err = profileKey.SetStringValue("ProfileName", profileName) if err != nil { - slog.Debug("failed to open profiles registry key for enumeration", "error", err) - return "" + return fmt.Errorf("failed to set profile name: %w", err) } - defer parentKey.Close() - // Get all subkeys (profile GUIDs) - subKeys, err := parentKey.ReadSubKeyNames(0) + // Also set Description to match + err = profileKey.SetStringValue("Description", profileName) if err != nil { - slog.Debug("failed to enumerate profile GUIDs", "error", err) - return "" - } - - // Find the most recently created profile - var latestGUID string - var latestTime time.Time - - for _, guid := range subKeys { - profilePath := parentPath + `\` + guid - profileKey, err := registry.OpenKey(registry.LOCAL_MACHINE, profilePath, registry.QUERY_VALUE) - if err != nil { - continue - } - - // Check DateCreated - it's stored as binary - dateBytes, _, err := profileKey.GetBinaryValue("DateCreated") - if err == nil && len(dateBytes) >= 8 { - // Parse FILETIME (low 32 bits, high 32 bits) - low := uint32(dateBytes[0]) | uint32(dateBytes[1])<<8 | uint32(dateBytes[2])<<16 | uint32(dateBytes[3])<<24 - high := uint32(dateBytes[4]) | uint32(dateBytes[5])<<8 | uint32(dateBytes[6])<<16 | uint32(dateBytes[7])<<24 - fileTime := int64(high)<<32 | int64(low) - // Convert FILETIME (100-nanosecond intervals since Jan 1, 1601) to Go time - // FILETIME epoch: Jan 1, 1601, Go epoch: Jan 1, 1970 - // Difference: 116444736000000000 (100-nanosecond intervals) - const fileTimeEpoch = 116444736000000000 - unixNano := (fileTime - fileTimeEpoch) * 100 - profileTime := time.Unix(0, unixNano) - - // Only consider profiles created in the last 30 seconds - if time.Since(profileTime) < 30*time.Second { - if profileTime.After(latestTime) { - latestTime = profileTime - latestGUID = strings.Trim(guid, "{}") - } - } - } - profileKey.Close() + slog.Debug("failed to set Description, continuing", "error", err) } - if latestGUID != "" { - slog.Debug("found most recent profile in registry", "interface", ifaceName, "profileGUID", latestGUID, "created", latestTime) - return latestGUID + if matchingGUID != "" { + slog.Debug("updated profile name in registry (matched by interface name)", "interface", ifaceName, "profileGUID", selectedGUID, "profileName", profileName) + } else { + slog.Debug("updated profile name in registry (using most recent profile)", "interface", ifaceName, "profileGUID", selectedGUID, "profileName", profileName) } - - return "" + return nil } // SetInterfacePrivateProfile - sets the Windows network interface profile to Private @@ -328,17 +247,6 @@ func (nc *NCIface) Create() error { // Use a shorter initial wait, then check if interface exists time.Sleep(2 * time.Second) - // Check if interface exists before attempting to set profile settings - psCmd := fmt.Sprintf("$adapter = Get-NetAdapter -Name '%s' -ErrorAction SilentlyContinue; if ($adapter) { Write-Output 'Exists' } else { Write-Output 'NotFound' }", ifaceName) - cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCmd) - output, err := cmd.Output() - outputStr := strings.TrimSpace(string(output)) - - if err != nil || outputStr != "Exists" { - slog.Warn("interface not found, skipping profile settings", "interface", ifaceName, "output", outputStr, "error", err) - return - } - // Set network profile to Private if force flag is set if config.Netclient().ForcePrivateProfile { if err := SetInterfacePrivateProfile(ifaceName); err != nil { From 90ac870a378571cbc39ef134459de7784d1254ce Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 22 Jan 2026 16:18:39 +0400 Subject: [PATCH 14/19] NM-222: cleanup code --- wireguard/wireguard_windows.go | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index a5cc0043..2020327c 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -90,10 +90,8 @@ func findAndUpdateProfileName(ifaceName string, profileName string) error { } defer parentKey.Close() - // First, try to find a profile where ProfileName matches the interface name - // If not found, fall back to the most recently created profile (within last 30 seconds) + // Find a profile where ProfileName matches the interface name var matchingGUID string - var latestGUID string keepLooking := true for keepLooking { @@ -122,29 +120,19 @@ func findAndUpdateProfileName(ifaceName string, profileName string) error { // Found a profile matching the interface name matchingGUID = strings.Trim(guid, "{}") subKey.Close() + keepLooking = false break } subKey.Close() } - - // If we found a matching profile, stop searching - if matchingGUID != "" { - keepLooking = false - } } - // Use matching profile if found, otherwise use the most recent one - selectedGUID := matchingGUID - if selectedGUID == "" { - selectedGUID = latestGUID - } - - if selectedGUID == "" { - return fmt.Errorf("no profile found in registry for interface %s", ifaceName) + if matchingGUID == "" { + return fmt.Errorf("no profile found with ProfileName matching interface %s", ifaceName) } // Update the profile name - profilePath := parentPath + `\` + "{" + selectedGUID + "}" + profilePath := parentPath + `\` + "{" + matchingGUID + "}" profileKey, err := registry.OpenKey(registry.LOCAL_MACHINE, profilePath, registry.SET_VALUE) if err != nil { return fmt.Errorf("failed to open profile key: %w", err) @@ -162,11 +150,7 @@ func findAndUpdateProfileName(ifaceName string, profileName string) error { slog.Debug("failed to set Description, continuing", "error", err) } - if matchingGUID != "" { - slog.Debug("updated profile name in registry (matched by interface name)", "interface", ifaceName, "profileGUID", selectedGUID, "profileName", profileName) - } else { - slog.Debug("updated profile name in registry (using most recent profile)", "interface", ifaceName, "profileGUID", selectedGUID, "profileName", profileName) - } + slog.Debug("updated profile name in registry", "interface", ifaceName, "profileGUID", matchingGUID, "profileName", profileName) return nil } From 2ae5c65780a1be16c019f93ae400e85f2b19efca Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 22 Jan 2026 21:15:18 +0400 Subject: [PATCH 15/19] NM-222: use stat func to retrieve network profile keys --- wireguard/wireguard_windows.go | 48 +++++++++++++++------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 2020327c..5ac9bb70 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -90,41 +90,35 @@ func findAndUpdateProfileName(ifaceName string, profileName string) error { } defer parentKey.Close() + // Get subkey count using Stat() for efficient enumeration + keyInfo, err := parentKey.Stat() + if err != nil { + return fmt.Errorf("failed to get registry key info: %w", err) + } + + // Read all subkeys at once since we know the count + subKeyNames, err := parentKey.ReadSubKeyNames(int(keyInfo.SubKeyCount)) + if err != nil { + return fmt.Errorf("failed to read subkeys: %w", err) + } + // Find a profile where ProfileName matches the interface name var matchingGUID string - keepLooking := true - - for keepLooking { - subKeyNames, err := parentKey.ReadSubKeyNames(10) + for _, guid := range subKeyNames { + subKey, err := registry.OpenKey(parentKey, guid, registry.QUERY_VALUE) if err != nil { - if err == registry.ErrNotExist || err.Error() == "EOF" { - keepLooking = false - } else { - return fmt.Errorf("failed to read subkeys: %w", err) - } - } - if len(subKeyNames) == 0 { - keepLooking = false continue } - for _, guid := range subKeyNames { - subKey, err := registry.OpenKey(parentKey, guid, registry.QUERY_VALUE) - if err != nil { - continue - } - - // Check if ProfileName matches the interface name - currentProfileName, _, err := subKey.GetStringValue("ProfileName") - if err == nil && currentProfileName == ifaceName { - // Found a profile matching the interface name - matchingGUID = strings.Trim(guid, "{}") - subKey.Close() - keepLooking = false - break - } + // Check if ProfileName matches the interface name + currentProfileName, _, err := subKey.GetStringValue("ProfileName") + if err == nil && currentProfileName == ifaceName { + // Found a profile matching the interface name + matchingGUID = strings.Trim(guid, "{}") subKey.Close() + break } + subKey.Close() } if matchingGUID == "" { From 8e380b73c306c5602f200b5c9b48300947202bcd Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 22 Jan 2026 22:16:34 +0400 Subject: [PATCH 16/19] NM-222: fix registry perms --- wireguard/wireguard_windows.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 5ac9bb70..2a36abea 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -84,7 +84,8 @@ func setInterfaceProfileNameViaRegistry(ifaceName string, profileName string) er // findAndUpdateProfileName - enumerates registry profiles to find one matching interface name and update it func findAndUpdateProfileName(ifaceName string, profileName string) error { parentPath := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles` - parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, parentPath, registry.ENUMERATE_SUB_KEYS) + // Need READ permission (includes QUERY_VALUE and ENUMERATE_SUB_KEYS) to use Stat() method + parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, parentPath, registry.ALL_ACCESS) if err != nil { return fmt.Errorf("failed to open profiles registry key: %w", err) } From 5399a0c100e6808d133c5fd27f75f0bce122534f Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Thu, 22 Jan 2026 22:24:50 +0400 Subject: [PATCH 17/19] NM-222: fix retry --- wireguard/wireguard_windows.go | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index 2a36abea..aa2d65e2 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -103,25 +103,43 @@ func findAndUpdateProfileName(ifaceName string, profileName string) error { return fmt.Errorf("failed to read subkeys: %w", err) } - // Find a profile where ProfileName matches the interface name + // First, check if the profile name is already set to the target name + // This handles the case where a previous attempt succeeded but we're retrying var matchingGUID string + var alreadySet bool for _, guid := range subKeyNames { subKey, err := registry.OpenKey(parentKey, guid, registry.QUERY_VALUE) if err != nil { continue } - // Check if ProfileName matches the interface name currentProfileName, _, err := subKey.GetStringValue("ProfileName") - if err == nil && currentProfileName == ifaceName { - // Found a profile matching the interface name - matchingGUID = strings.Trim(guid, "{}") + if err != nil { + subKey.Close() + continue + } + + // If profile name already matches target, we're done + if currentProfileName == profileName { + alreadySet = true subKey.Close() break } + + // Check if ProfileName matches the interface name (for initial update) + if currentProfileName == ifaceName { + // Found a profile matching the interface name + matchingGUID = strings.Trim(guid, "{}") + } subKey.Close() } + // If already set to target name, return success + if alreadySet { + return nil + } + + // If no matching profile found, return error if matchingGUID == "" { return fmt.Errorf("no profile found with ProfileName matching interface %s", ifaceName) } From b3bb8909e1207f4f1d3dff19f759ac5b7a2d13da Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 23 Jan 2026 00:41:22 +0400 Subject: [PATCH 18/19] NM-222: update windows service file --- daemon/common_windows.go | 16 +++++++------ wireguard/wireguard_windows.go | 43 +++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/daemon/common_windows.go b/daemon/common_windows.go index 5eb8af80..3e8c3379 100644 --- a/daemon/common_windows.go +++ b/daemon/common_windows.go @@ -158,16 +158,18 @@ func writeServiceConfig() error { Automatic -true +false +Tcpip +Dhcp +NlaSvc `, strings.Replace(config.GetNetclientPath()+"netclient.exe", `\\`, `\`, -1)) - if !ncutils.FileExists(serviceConfigPath) { - err := os.WriteFile(serviceConfigPath, []byte(scriptString), 0600) - if err != nil { - return err - } - logger.Log(0, "wrote the daemon config file to the Netclient directory") + // Always write the service config to ensure it's up to date + err := os.WriteFile(serviceConfigPath, []byte(scriptString), 0600) + if err != nil { + return err } + logger.Log(0, "wrote the daemon config file to the Netclient directory") return nil } diff --git a/wireguard/wireguard_windows.go b/wireguard/wireguard_windows.go index aa2d65e2..f83fa8fc 100644 --- a/wireguard/wireguard_windows.go +++ b/wireguard/wireguard_windows.go @@ -8,6 +8,7 @@ import ( "os/exec" "strconv" "strings" + "sync" "time" "github.com/gravitl/netclient/config" @@ -710,21 +711,47 @@ func restoreInternetGwV4() (err error) { // FlushWindowsNetworkCaches - flushes Windows network caches (route, ARP, NetBIOS, DNS) func FlushWindowsNetworkCaches() { + var wg sync.WaitGroup + // Clear route destination cache - _, _ = ncutils.RunCmd("netsh interface ip delete destinationcache", false) + wg.Add(1) + go func() { + defer wg.Done() + _, _ = ncutils.RunCmd("netsh interface ip delete destinationcache", false) + }() // Clear ARP cache - _, _ = ncutils.RunCmd("arp -d *", false) + wg.Add(1) + go func() { + defer wg.Done() + _, _ = ncutils.RunCmd("arp -d *", false) + }() // Clear NetBIOS cache - _, _ = ncutils.RunCmd("nbtstat -R", false) - _, _ = ncutils.RunCmd("nbtstat -RR", false) + wg.Add(1) + go func() { + defer wg.Done() + _, _ = ncutils.RunCmd("nbtstat -R", false) + }() + + wg.Add(1) + go func() { + defer wg.Done() + _, _ = ncutils.RunCmd("nbtstat -RR", false) + }() - // Flush DNS cache - _, _ = ncutils.RunCmd("ipconfig /flushdns", false) + // Flush DNS cache (run sequentially with registerdns to avoid potential conflicts) + wg.Add(1) + go func() { + defer wg.Done() + // Flush DNS cache first + _, _ = ncutils.RunCmd("ipconfig /flushdns", false) + // Then re-register DNS + _, _ = ncutils.RunCmd("ipconfig /registerdns", false) + }() - // Re-register DNS - _, _ = ncutils.RunCmd("ipconfig /registerdns", false) + // Wait for all commands to complete + wg.Wait() slog.Info("flushed Windows network caches") } From cb94f13e0f3c05ebfceb22134471e724464e12f1 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Fri, 23 Jan 2026 02:47:53 +0400 Subject: [PATCH 19/19] revert winsw --- daemon/common_windows.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/daemon/common_windows.go b/daemon/common_windows.go index 3e8c3379..5eb8af80 100644 --- a/daemon/common_windows.go +++ b/daemon/common_windows.go @@ -158,18 +158,16 @@ func writeServiceConfig() error { Automatic -false -Tcpip -Dhcp -NlaSvc +true `, strings.Replace(config.GetNetclientPath()+"netclient.exe", `\\`, `\`, -1)) - // Always write the service config to ensure it's up to date - err := os.WriteFile(serviceConfigPath, []byte(scriptString), 0600) - if err != nil { - return err + if !ncutils.FileExists(serviceConfigPath) { + err := os.WriteFile(serviceConfigPath, []byte(scriptString), 0600) + if err != nil { + return err + } + logger.Log(0, "wrote the daemon config file to the Netclient directory") } - logger.Log(0, "wrote the daemon config file to the Netclient directory") return nil }