// Copyright 2017 Microsoft. All rights reserved. // MIT License package platform import ( "bytes" "context" "fmt" "net" "os" "os/exec" "strings" "syscall" "time" "github.com/Azure/azure-container-networking/log" "github.com/Azure/azure-container-networking/platform/windows/adapter" "github.com/Azure/azure-container-networking/platform/windows/adapter/mellanox" "github.com/pkg/errors" "go.uber.org/zap" "golang.org/x/sys/windows" ) const ( // CNMRuntimePath is the path where CNM state files are stored. CNMRuntimePath = "" // CNIRuntimePath is the path where CNI state files are stored. CNIRuntimePath = "" // CNILockPath is the path where CNI lock files are stored. CNILockPath = "" // CNIStateFilePath is the path to the CNI state file CNIStateFilePath = "C:\\k\\azure-vnet.json" // CNIIpamStatePath is the name of IPAM state file CNIIpamStatePath = "C:\\k\\azure-vnet-ipam.json" // CNIBinaryPath is the path to the CNI binary CNIBinaryPath = "C:\\k\\azurecni\\bin\\azure-vnet.exe" // CNI runtime path on a Kubernetes cluster K8SCNIRuntimePath = "C:\\k\\azurecni\\bin" // Network configuration file path on a Kubernetes cluster K8SNetConfigPath = "C:\\k\\azurecni\\netconf" // CNSRuntimePath is the path where CNS state files are stored. CNSRuntimePath = "" // NPMRuntimePath is the path where NPM state files are stored. NPMRuntimePath = "" // DNCRuntimePath is the path where DNC state files are stored. DNCRuntimePath = "" // SDNRemoteArpMacAddress is the registry key for the remote arp mac address. // This is set for multitenancy to get arp response from within VM // for vlan tagged arp requests SDNRemoteArpMacAddress = "12-34-56-78-9a-bc" // Command to get SDNRemoteArpMacAddress registry key GetSdnRemoteArpMacAddressCommand = "(Get-ItemProperty " + "-Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State -Name SDNRemoteArpMacAddress).SDNRemoteArpMacAddress" // Command to set SDNRemoteArpMacAddress registry key SetSdnRemoteArpMacAddressCommand = "Set-ItemProperty " + "-Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State -Name SDNRemoteArpMacAddress -Value \"12-34-56-78-9a-bc\"" // Command to check if system has hns state path or not CheckIfHNSStatePathExistsCommand = "Test-Path " + "-Path HKLM:\\SYSTEM\\CurrentControlSet\\Services\\hns\\State" // Command to fetch netadapter and pnp id GetMacAddressVFPPnpIDMapping = "Get-NetAdapter | Select-Object MacAddress, PnpDeviceID| Format-Table -HideTableHeaders" // Command to restart HNS service RestartHnsServiceCommand = "Restart-Service -Name hns" // Interval between successive checks for mellanox adapter's PriorityVLANTag value defaultMellanoxMonitorInterval = 30 * time.Second // Value for reg key: PriorityVLANTag for adapter // reg key value for PriorityVLANTag = 3 --> Packet priority and VLAN enabled // for more details goto https://learn.microsoft.com/en-us/windows-hardware/drivers/network/standardized-inf-keywords-for-ndis-qos desiredVLANTagForMellanox = 3 // Powershell command timeout ExecTimeout = 10 * time.Second ) // Flag to check if sdnRemoteArpMacAddress registry key is set var sdnRemoteArpMacAddressSet = false // GetOSInfo returns OS version information. func GetOSInfo() string { return "windows" } func GetProcessSupport() error { p := NewExecClient(nil) cmd := fmt.Sprintf("Get-Process -Id %v", os.Getpid()) _, err := p.ExecutePowershellCommand(cmd) return err } var tickCount = syscall.NewLazyDLL("kernel32.dll").NewProc("GetTickCount64") // GetLastRebootTime returns the last time the system rebooted. func (p *execClient) GetLastRebootTime() (time.Time, error) { currentTime := time.Now() output, _, err := tickCount.Call() if errno, ok := err.(syscall.Errno); !ok || errno != 0 { if p.logger != nil { p.logger.Error("Failed to call GetTickCount64", zap.Error(err)) } else { log.Printf("Failed to call GetTickCount64, err: %v", err) } return time.Time{}.UTC(), err } rebootTime := currentTime.Add(-time.Duration(output) * time.Millisecond).Truncate(time.Second) if p.logger != nil { p.logger.Info("Formatted Boot", zap.String("time", rebootTime.Format(time.RFC3339))) } else { log.Printf("Formatted Boot time: %s", rebootTime.Format(time.RFC3339)) } return rebootTime.UTC(), nil } // Deprecated: ExecuteRawCommand is deprecated, it is recommended to use ExecuteCommand when possible func (p *execClient) ExecuteRawCommand(command string) (string, error) { if p.logger != nil { p.logger.Info("[Azure-Utils]", zap.String("ExecuteRawCommand", command)) } else { log.Printf("[Azure-Utils] ExecuteRawCommand: %q", command) } var stderr, stdout bytes.Buffer cmd := exec.Command("cmd", "/c", command) cmd.Stderr = &stderr cmd.Stdout = &stdout if err := cmd.Run(); err != nil { return "", errors.Wrapf(err, "ExecuteRawCommand failed. stdout: %q, stderr: %q", stdout.String(), stderr.String()) } return stdout.String(), nil } // ExecuteCommand passes its parameters to an exec.CommandContext, runs the command, and returns its output, or an error if the command fails or times out func (p *execClient) ExecuteCommand(ctx context.Context, command string, args ...string) (string, error) { if p.logger != nil { p.logger.Info("[Azure-Utils]", zap.String("ExecuteCommand", command), zap.Strings("args", args)) } else { log.Printf("[Azure-Utils] ExecuteCommand: %q %v", command, args) } var stderr, stdout bytes.Buffer // Create a new context and add a timeout to it derivedCtx, cancel := context.WithTimeout(ctx, p.Timeout) defer cancel() // The cancel should be deferred so resources are cleaned up cmd := exec.CommandContext(derivedCtx, command, args...) cmd.Stderr = &stderr cmd.Stdout = &stdout if err := cmd.Run(); err != nil { return "", errors.Wrapf(err, "ExecuteCommand failed. stdout: %q, stderr: %q", stdout.String(), stderr.String()) } return stdout.String(), nil } func SetOutboundSNAT(subnet string) error { return nil } // ClearNetworkConfiguration clears the azure-vnet.json contents. // This will be called only when reboot is detected - This is windows specific func (p *execClient) ClearNetworkConfiguration() (bool, error) { jsonStore := CNIRuntimePath + "azure-vnet.json" p.logger.Info("Deleting the json", zap.String("store", jsonStore)) cmd := exec.Command("cmd", "/c", "del", jsonStore) if err := cmd.Run(); err != nil { p.logger.Info("Error deleting the json", zap.String("store", jsonStore)) return true, err } return true, nil } func (p *execClient) KillProcessByName(processName string) error { cmd := fmt.Sprintf("taskkill /IM %v /F", processName) _, err := p.ExecuteRawCommand(cmd) return err // nolint } // ExecutePowershellCommand executes powershell command // Deprecated: ExecutePowershellCommand is deprecated, it is recommended to use ExecuteCommand when possible func (p *execClient) ExecutePowershellCommand(command string) (string, error) { ps, err := exec.LookPath("powershell.exe") if err != nil { return "", fmt.Errorf("Failed to find powershell executable") } if p.logger != nil { p.logger.Info("[Azure-Utils]", zap.String("command", command)) } else { log.Printf("[Azure-Utils] %s", command) } cmd := exec.Command(ps, command) var stdout bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err = cmd.Run() if err != nil { return "", fmt.Errorf("%s:%s", err.Error(), stderr.String()) } return strings.TrimSpace(stdout.String()), nil } // ExecutePowershellCommandWithContext executes powershell command wth context // Deprecated: ExecutePowershellCommandWithContext is deprecated, it is recommended to use ExecuteCommand when possible func (p *execClient) ExecutePowershellCommandWithContext(ctx context.Context, command string) (string, error) { ps, err := exec.LookPath("powershell.exe") if err != nil { return "", errors.New("failed to find powershell executable") } if p.logger != nil { p.logger.Info("[Azure-Utils]", zap.String("command", command)) } else { log.Printf("[Azure-Utils] %s", command) } cmd := exec.CommandContext(ctx, ps, command) var stdout bytes.Buffer var stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err = cmd.Run() if err != nil { ErrPowershellExecution := errors.New("failed to execute powershell command") return "", fmt.Errorf("%w:%s", ErrPowershellExecution, stderr.String()) } return strings.TrimSpace(stdout.String()), nil } // SetSdnRemoteArpMacAddress sets the regkey for SDNRemoteArpMacAddress needed for multitenancy if hns is enabled func SetSdnRemoteArpMacAddress(execClient ExecClient) error { exists, err := execClient.ExecutePowershellCommand(CheckIfHNSStatePathExistsCommand) if err != nil { errMsg := fmt.Sprintf("Failed to check the existent of hns state path due to error %s", err.Error()) log.Printf(errMsg) return errors.Errorf(errMsg) } if strings.EqualFold(exists, "false") { log.Printf("hns state path does not exist, skip setting SdnRemoteArpMacAddress") return nil } if sdnRemoteArpMacAddressSet == false { result, err := execClient.ExecutePowershellCommand(GetSdnRemoteArpMacAddressCommand) if err != nil { return err } // Set the reg key if not already set or has incorrect value if result != SDNRemoteArpMacAddress { if _, err = execClient.ExecutePowershellCommand(SetSdnRemoteArpMacAddressCommand); err != nil { log.Printf("Failed to set SDNRemoteArpMacAddress due to error %s", err.Error()) return err } log.Printf("[Azure CNS] SDNRemoteArpMacAddress regKey set successfully. Restarting hns service.") if _, err := execClient.ExecutePowershellCommand(RestartHnsServiceCommand); err != nil { log.Printf("Failed to Restart HNS Service due to error %s", err.Error()) return err } } sdnRemoteArpMacAddressSet = true } return nil } func HasMellanoxAdapter() bool { m := &mellanox.Mellanox{} return hasNetworkAdapter(m) } func hasNetworkAdapter(na adapter.NetworkAdapter) bool { adapterName, err := na.GetAdapterName() if err != nil { log.Errorf("Error while getting network adapter name: %v", err) return false } log.Printf("Name of the network adapter : %v", adapterName) return true } // Regularly monitors the Mellanox PriorityVLANGTag registry value and sets it to desired value if needed func MonitorAndSetMellanoxRegKeyPriorityVLANTag(ctx context.Context, intervalSecs int) { m := &mellanox.Mellanox{} interval := defaultMellanoxMonitorInterval if intervalSecs > 0 { interval = time.Duration(intervalSecs) * time.Second } err := updatePriorityVLANTagIfRequired(m, desiredVLANTagForMellanox) if err != nil { log.Errorf("Error while monitoring mellanox, continuing: %v", err) } ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): log.Printf("context cancelled, stopping Mellanox Monitoring: %v", ctx.Err()) return case <-ticker.C: err := updatePriorityVLANTagIfRequired(m, desiredVLANTagForMellanox) if err != nil { log.Errorf("Error while monitoring mellanox, continuing: %v", err) } } } } // Updates the priority VLAN Tag of mellanox adapter if not already set to the desired value func updatePriorityVLANTagIfRequired(na adapter.NetworkAdapter, desiredValue int) error { currentVal, err := na.GetPriorityVLANTag() if err != nil { return fmt.Errorf("error while getting Priority VLAN Tag value: %w", err) } if currentVal == desiredValue { log.Printf("Adapter's PriorityVLANTag is already set to %v, skipping reset", desiredValue) return nil } err = na.SetPriorityVLANTag(desiredValue) if err != nil { return fmt.Errorf("error while setting Priority VLAN Tag value: %w", err) } return nil } func GetOSDetails() (map[string]string, error) { return nil, nil } func GetProcessNameByID(pidstr string) (string, error) { pidstr = strings.Trim(pidstr, "\r\n") cmd := fmt.Sprintf("Get-Process -Id %s|Format-List", pidstr) p := NewExecClient(nil) out, err := p.ExecutePowershellCommand(cmd) if err != nil { log.Printf("Process is not running. Output:%v, Error %v", out, err) return "", err } if len(out) <= 0 { log.Printf("Output length is 0") return "", fmt.Errorf("get-process output length is 0") } lines := strings.Split(out, "\n") for _, line := range lines { if strings.Contains(line, "Name") { pName := strings.Split(line, ":") if len(pName) > 1 { return strings.TrimSpace(pName[1]), nil } } } return "", fmt.Errorf("Process not found") } func PrintDependencyPackageDetails() { } // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw func ReplaceFile(source, destination string) error { src, err := syscall.UTF16PtrFromString(source) if err != nil { return err } dest, err := syscall.UTF16PtrFromString(destination) if err != nil { return err } return windows.MoveFileEx(src, dest, windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH) } /* Output: 6C-A1-00-50-E4-2D PCI\VEN_8086&DEV_2723&SUBSYS_00808086&REV_1A\4&328243d9&0&00E0 80-6D-97-1E-CF-4E USB\VID_17EF&PID_A359\3010019E3 */ func FetchMacAddressPnpIDMapping(ctx context.Context, execClient ExecClient) (map[string]string, error) { ctx, cancel := context.WithTimeout(ctx, ExecTimeout) defer cancel() // The cancel should be deferred so resources are cleaned up output, err := execClient.ExecutePowershellCommandWithContext(ctx, GetMacAddressVFPPnpIDMapping) if err != nil { return nil, errors.Wrap(err, "failed to fetch VF mapping") } result := make(map[string]string) if output != "" { // Split the output based on new line characters lines := strings.Split(output, "\n") for _, line := range lines { line = strings.TrimSpace(line) // Split based on " " to fetch the macaddress and pci id parts := strings.Split(line, " ") // Changing the format of macaddress from xx-xx-xx-xx to xx:xx:xx:xx formattedMacaddress, err := net.ParseMAC(parts[0]) if err != nil { return nil, errors.Wrap(err, "failed to fetch MACAddressPnpIDMapping") } key := formattedMacaddress.String() value := parts[1] result[key] = value } } return result, nil }