| /* |
| Copyright The containerd Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package util |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "net" |
| "net/url" |
| "strings" |
| "syscall" |
| "time" |
| |
| "github.com/Microsoft/go-winio" |
| ) |
| |
| const ( |
| tcpProtocol = "tcp" |
| npipeProtocol = "npipe" |
| ) |
| |
| // CreateListener creates a listener on the specified endpoint. |
| func CreateListener(endpoint string) (net.Listener, error) { |
| protocol, addr, err := parseEndpoint(endpoint) |
| if err != nil { |
| return nil, err |
| } |
| |
| switch protocol { |
| case tcpProtocol: |
| return net.Listen(tcpProtocol, addr) |
| |
| case npipeProtocol: |
| return winio.ListenPipe(addr, nil) |
| |
| default: |
| return nil, fmt.Errorf("only support tcp and npipe endpoint") |
| } |
| } |
| |
| // GetAddressAndDialer returns the address parsed from the given endpoint and a context dialer. |
| func GetAddressAndDialer(endpoint string) (string, func(ctx context.Context, addr string) (net.Conn, error), error) { |
| protocol, addr, err := parseEndpoint(endpoint) |
| if err != nil { |
| return "", nil, err |
| } |
| |
| // Use passthrough as the scheme so it allows us to use our custom dialer: |
| // |
| // "grpc.Dial uses "passthrough" as the default name resolver for backward compatibility while grpc.NewClient |
| // uses "dns" as its default name resolver. This subtle difference is important to legacy systems that also |
| // specified a custom dialer and expected it to receive the target string directly." |
| // https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md#the-wrong-way-grpcdial |
| addr = fmt.Sprintf("passthrough:///%s", addr) |
| if protocol == tcpProtocol { |
| return addr, tcpDial, nil |
| } |
| |
| if protocol == npipeProtocol { |
| return addr, npipeDial, nil |
| } |
| |
| return "", nil, errors.New("only support tcp and npipe endpoint") |
| } |
| |
| func tcpDial(ctx context.Context, addr string) (net.Conn, error) { |
| return (&net.Dialer{}).DialContext(ctx, tcpProtocol, addr) |
| } |
| |
| func npipeDial(ctx context.Context, addr string) (net.Conn, error) { |
| return winio.DialPipeContext(ctx, addr) |
| } |
| |
| func parseEndpoint(endpoint string) (string, string, error) { |
| // url.Parse doesn't recognize \, so replace with / first. |
| endpoint = strings.ReplaceAll(endpoint, "\\", "/") |
| u, err := url.Parse(endpoint) |
| if err != nil { |
| return "", "", err |
| } |
| |
| switch u.Scheme { |
| case "tcp": |
| return "tcp", u.Host, nil |
| case "npipe": |
| if strings.HasPrefix(u.Path, "//./pipe") { |
| return "npipe", u.Path, nil |
| } |
| |
| // fallback host if not provided. |
| host := u.Host |
| if host == "" { |
| host = "." |
| } |
| return "npipe", fmt.Sprintf("//%s%s", host, u.Path), nil |
| case "": |
| return "", "", fmt.Errorf("using %q as endpoint is deprecated, please consider using full url format", endpoint) |
| } |
| return u.Scheme, "", fmt.Errorf("protocol %q not supported", u.Scheme) |
| } |
| |
| var tickCount = syscall.NewLazyDLL("kernel32.dll").NewProc("GetTickCount64") |
| |
| // GetBootTime returns the time at which the machine was started, truncated to the nearest second |
| func GetBootTime() (time.Time, error) { |
| currentTime := time.Now() |
| output, _, err := tickCount.Call() |
| if errno, ok := err.(syscall.Errno); !ok || errno != 0 { |
| return time.Time{}, err |
| } |
| return currentTime.Add(-time.Duration(output) * time.Millisecond).Truncate(time.Second), nil |
| } |
| |
| // IsUnixDomainSocket returns whether a given file is a AF_UNIX socket file |
| func IsUnixDomainSocket(filePath string) (bool, error) { |
| // Due to the absence of golang support for os.ModeSocket in Windows (https://github.com/golang/go/issues/33357) |
| // we need to dial the file and check if we receive an error to determine if a file is Unix Domain Socket file. |
| |
| // Note that querrying for the Reparse Points (https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points) |
| // for the file (using FSCTL_GET_REPARSE_POINT) and checking for reparse tag: reparseTagSocket |
| // does NOT work in 1809 if the socket file is created within a bind mounted directory by a container |
| // and the FSCTL is issued in the host by the kubelet. |
| |
| c, err := net.Dial("unix", filePath) |
| if err == nil { |
| c.Close() |
| return true, nil |
| } |
| return false, nil |
| } |
| |
| // NormalizePath converts FS paths returned by certain go frameworks (like fsnotify) |
| // to native Windows paths that can be passed to Windows specific code |
| func NormalizePath(path string) string { |
| path = strings.ReplaceAll(path, "/", "\\") |
| if strings.HasPrefix(path, "\\") { |
| path = "c:" + path |
| } |
| return path |
| } |