1 //go:build !windows 2 3 /* 4 Package sockets is a simple unix domain socket wrapper. 5 6 # Usage 7 8 For example: 9 10 import( 11 "fmt" 12 "net" 13 "os" 14 "github.com/docker/go-connections/sockets" 15 ) 16 17 func main() { 18 l, err := sockets.NewUnixSocketWithOpts("/path/to/sockets", 19 sockets.WithChown(0,0),sockets.WithChmod(0660)) 20 if err != nil { 21 panic(err) 22 } 23 echoStr := "hello" 24 25 go func() { 26 for { 27 conn, err := l.Accept() 28 if err != nil { 29 return 30 } 31 conn.Write([]byte(echoStr)) 32 conn.Close() 33 } 34 }() 35 36 conn, err := net.Dial("unix", path) 37 if err != nil { 38 t.Fatal(err) 39 } 40 41 buf := make([]byte, 5) 42 if _, err := conn.Read(buf); err != nil { 43 panic(err) 44 } else if string(buf) != echoStr { 45 panic(fmt.Errorf("msg may lost")) 46 } 47 } 48 */ 49 package sockets 50 51 import ( 52 "net" 53 "os" 54 "syscall" 55 ) 56 57 // SockOption sets up socket file's creating option 58 type SockOption func(string) error 59 60 // WithChown modifies the socket file's uid and gid 61 func WithChown(uid, gid int) SockOption { 62 return func(path string) error { 63 if err := os.Chown(path, uid, gid); err != nil { 64 return err 65 } 66 return nil 67 } 68 } 69 70 // WithChmod modifies socket file's access mode. 71 func WithChmod(mask os.FileMode) SockOption { 72 return func(path string) error { 73 if err := os.Chmod(path, mask); err != nil { 74 return err 75 } 76 return nil 77 } 78 } 79 80 // NewUnixSocketWithOpts creates a unix socket with the specified options. 81 // By default, socket permissions are 0000 (i.e.: no access for anyone); pass 82 // WithChmod() and WithChown() to set the desired ownership and permissions. 83 // 84 // This function temporarily changes the system's "umask" to 0777 to work around 85 // a race condition between creating the socket and setting its permissions. While 86 // this should only be for a short duration, it may affect other processes that 87 // create files/directories during that period. 88 func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error) { 89 if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { 90 return nil, err 91 } 92 93 // net.Listen does not allow for permissions to be set. As a result, when 94 // specifying custom permissions ("WithChmod()"), there is a short time 95 // between creating the socket and applying the permissions, during which 96 // the socket permissions are Less restrictive than desired. 97 // 98 // To work around this limitation of net.Listen(), we temporarily set the 99 // umask to 0777, which forces the socket to be created with 000 permissions 100 // (i.e.: no access for anyone). After that, WithChmod() must be used to set 101 // the desired permissions. 102 // 103 // We don't use "defer" here, to reset the umask to its original value as soon 104 // as possible. Ideally we'd be able to detect if WithChmod() was passed as 105 // an option, and skip changing umask if default permissions are used. 106 origUmask := syscall.Umask(0o777) 107 l, err := net.Listen("unix", path) 108 syscall.Umask(origUmask) 109 if err != nil { 110 return nil, err 111 } 112 113 for _, op := range opts { 114 if err := op(path); err != nil { 115 _ = l.Close() 116 return nil, err 117 } 118 } 119 120 return l, nil 121 } 122 123 // NewUnixSocket creates a unix socket with the specified path and group. 124 func NewUnixSocket(path string, gid int) (net.Listener, error) { 125 return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660)) 126 } 127