k8s.io/kubernetes@v1.29.3/pkg/kubelet/util/util_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 /* 5 Copyright 2017 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package util 21 22 import ( 23 "context" 24 "fmt" 25 "net" 26 "net/url" 27 "path/filepath" 28 "strings" 29 "syscall" 30 "time" 31 32 "github.com/Microsoft/go-winio" 33 ) 34 35 const ( 36 tcpProtocol = "tcp" 37 npipeProtocol = "npipe" 38 ) 39 40 // CreateListener creates a listener on the specified endpoint. 41 func CreateListener(endpoint string) (net.Listener, error) { 42 protocol, addr, err := parseEndpoint(endpoint) 43 if err != nil { 44 return nil, err 45 } 46 47 switch protocol { 48 case tcpProtocol: 49 return net.Listen(tcpProtocol, addr) 50 51 case npipeProtocol: 52 return winio.ListenPipe(addr, nil) 53 54 default: 55 return nil, fmt.Errorf("only support tcp and npipe endpoint") 56 } 57 } 58 59 // GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer. 60 func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) { 61 protocol, addr, err := parseEndpoint(endpoint) 62 if err != nil { 63 return "", nil, err 64 } 65 66 if protocol == tcpProtocol { 67 return addr, tcpDial, nil 68 } 69 70 if protocol == npipeProtocol { 71 return addr, npipeDial, nil 72 } 73 74 return "", nil, fmt.Errorf("only support tcp and npipe endpoint") 75 } 76 77 func tcpDial(ctx context.Context, addr string) (net.Conn, error) { 78 return (&net.Dialer{}).DialContext(ctx, tcpProtocol, addr) 79 } 80 81 func npipeDial(ctx context.Context, addr string) (net.Conn, error) { 82 return winio.DialPipeContext(ctx, addr) 83 } 84 85 func parseEndpoint(endpoint string) (string, string, error) { 86 // url.Parse doesn't recognize \, so replace with / first. 87 endpoint = strings.Replace(endpoint, "\\", "/", -1) 88 u, err := url.Parse(endpoint) 89 if err != nil { 90 return "", "", err 91 } 92 93 if u.Scheme == "tcp" { 94 return "tcp", u.Host, nil 95 } else if u.Scheme == "npipe" { 96 if strings.HasPrefix(u.Path, "//./pipe") { 97 return "npipe", u.Path, nil 98 } 99 100 // fallback host if not provided. 101 host := u.Host 102 if host == "" { 103 host = "." 104 } 105 return "npipe", fmt.Sprintf("//%s%s", host, u.Path), nil 106 } else if u.Scheme == "" { 107 return "", "", fmt.Errorf("Using %q as endpoint is deprecated, please consider using full url format", endpoint) 108 } else { 109 return u.Scheme, "", fmt.Errorf("protocol %q not supported", u.Scheme) 110 } 111 } 112 113 // LocalEndpoint returns the full path to a named pipe at the given endpoint - unlike on unix, we can't use sockets. 114 func LocalEndpoint(path, file string) (string, error) { 115 // extract the podresources config name from the path. We only need this on windows because the preferred layout of pipes, 116 // this is why we have the extra logic in here instead of changing the function signature. Join the file to make sure the 117 // last path component is a file, so the operation chain works.. 118 podResourcesDir := filepath.Base(filepath.Dir(filepath.Join(path, file))) 119 if podResourcesDir == "" { 120 // should not happen because the user can configure a root directory, and we expected a subdirectory inside 121 // the user supplied root directory named like "pod-resources" or so. 122 return "", fmt.Errorf("cannot infer the podresources directory from path %q", path) 123 } 124 // windows pipes are expected to use forward slashes: https://learn.microsoft.com/windows/win32/ipc/pipe-names 125 // so using `url` like we do on unix gives us unclear benefits - see https://github.com/kubernetes/kubernetes/issues/78628 126 // So we just construct the path from scratch. 127 // Format: \\ServerName\pipe\PipeName 128 // Where where ServerName is either the name of a remote computer or a period, to specify the local computer. 129 // We only consider PipeName as regular windows path, while the pipe path components are fixed, hence we use constants. 130 serverPart := `\\.` 131 pipePart := "pipe" 132 pipeName := "kubelet-" + podResourcesDir 133 return npipeProtocol + "://" + filepath.Join(serverPart, pipePart, pipeName), nil 134 } 135 136 var tickCount = syscall.NewLazyDLL("kernel32.dll").NewProc("GetTickCount64") 137 138 // GetBootTime returns the time at which the machine was started, truncated to the nearest second 139 func GetBootTime() (time.Time, error) { 140 currentTime := time.Now() 141 output, _, err := tickCount.Call() 142 if errno, ok := err.(syscall.Errno); !ok || errno != 0 { 143 return time.Time{}, err 144 } 145 return currentTime.Add(-time.Duration(output) * time.Millisecond).Truncate(time.Second), nil 146 } 147 148 // NormalizePath converts FS paths returned by certain go frameworks (like fsnotify) 149 // to native Windows paths that can be passed to Windows specific code 150 func NormalizePath(path string) string { 151 path = strings.ReplaceAll(path, "/", "\\") 152 if strings.HasPrefix(path, "\\") { 153 path = "c:" + path 154 } 155 return path 156 }