github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/socket/sockets_unix.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package socket 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "net" 11 "os" 12 "time" 13 14 "golang.org/x/sys/unix" 15 "google.golang.org/grpc" 16 "google.golang.org/grpc/credentials/insecure" 17 18 "github.com/telepresenceio/telepresence/v2/pkg/proc" 19 ) 20 21 // userDaemonPath is the path used when communicating to the user daemon process. 22 func userDaemonPath(ctx context.Context) string { 23 return "/tmp/telepresence-connector.socket" 24 } 25 26 // rootDaemonPath is the path used when communicating to the root daemon process. 27 func rootDaemonPath(ctx context.Context) string { 28 return "/var/run/telepresence-daemon.socket" 29 } 30 31 func dial(ctx context.Context, socketName string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { 32 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) // FIXME(lukeshu): Make this configurable 33 defer cancel() 34 for firstTry := true; ; firstTry = false { 35 conn, err := grpc.DialContext(ctx, "unix:"+socketName, append([]grpc.DialOption{ 36 grpc.WithTransportCredentials(insecure.NewCredentials()), 37 grpc.WithNoProxy(), 38 grpc.WithBlock(), 39 grpc.FailOnNonTempDialError(true), 40 }, opts...)...) 41 if err == nil { 42 return conn, nil 43 } 44 45 if firstTry && errors.Is(err, unix.ECONNREFUSED) { 46 // Socket exists but doesn't accept connections. This usually means that the process 47 // terminated ungracefully. To remedy this, we make an attempt to remove the socket 48 // and dial again. 49 if rmErr := os.Remove(socketName); rmErr != nil { 50 err = fmt.Errorf("%w (socket rm failed with %v)", err, rmErr) 51 } else { 52 continue 53 } 54 } 55 56 if err == context.DeadlineExceeded { 57 // grpc.DialContext doesn't wrap context.DeadlineExceeded with any useful 58 // information at all. Fix that. 59 err = &net.OpError{ 60 Op: "dial", 61 Net: "unix", 62 Addr: &net.UnixAddr{ 63 Name: socketName, 64 Net: "unix", 65 }, 66 Err: fmt.Errorf("socket exists but is not responding: %w", err), 67 } 68 } 69 // Add some Telepresence-specific commentary on what specific common errors mean. 70 switch { 71 case errors.Is(err, context.DeadlineExceeded): 72 err = fmt.Errorf("%w; this usually means that the process has locked up", err) 73 case errors.Is(err, unix.ECONNREFUSED): 74 err = fmt.Errorf("%w; this usually means that the process has terminated ungracefully", err) 75 case errors.Is(err, os.ErrNotExist): 76 err = fmt.Errorf("%w; this usually means that the process is not running", err) 77 } 78 return nil, err 79 } 80 } 81 82 func listen(_ context.Context, processName, socketName string) (net.Listener, error) { 83 if proc.IsAdmin() { 84 origUmask := unix.Umask(0) 85 defer unix.Umask(origUmask) 86 } 87 listener, err := net.Listen("unix", socketName) 88 if err != nil { 89 if errors.Is(err, unix.EADDRINUSE) { 90 err = fmt.Errorf("socket %q exists so the %s is either already running or terminated ungracefully", socketName, processName) 91 } 92 return nil, err 93 } 94 // Don't have dhttp.ServerConfig.Serve unlink the socket; defer unlinking the socket 95 // until the process exits. 96 listener.(*net.UnixListener).SetUnlinkOnClose(false) 97 return listener, nil 98 } 99 100 // exists returns true if a socket is found at the given path. 101 func exists(path string) (bool, error) { 102 s, err := os.Stat(path) 103 if err != nil { 104 if os.IsNotExist(err) { 105 err = nil 106 } 107 return false, err 108 } 109 if s.Mode()&os.ModeSocket == 0 { 110 return false, fmt.Errorf("%q is not a socket", path) 111 } 112 return true, nil 113 }