go.nanomsg.org/mangos/v3@v3.4.3-0.20240217232803-46464076f1f5/transport/ipc/ipc_unix.go (about) 1 // +build !windows,!plan9,!js 2 3 // Copyright 2021 The Mangos Authors 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use file except in compliance with the License. 7 // You may obtain a copy of the license at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 // Package ipc implements the IPC transport on top of UNIX domain sockets. 18 // To enable it simply import it. 19 package ipc 20 21 import ( 22 "errors" 23 "net" 24 "os" 25 "sync" 26 "syscall" 27 "time" 28 29 "go.nanomsg.org/mangos/v3" 30 "go.nanomsg.org/mangos/v3/transport" 31 ) 32 33 const ( 34 // Transport implements IPC. 35 Transport = ipcTran(0) 36 ) 37 38 func init() { 39 transport.RegisterTransport(Transport) 40 } 41 42 type dialer struct { 43 addr *net.UnixAddr 44 proto transport.ProtocolInfo 45 hs transport.Handshaker 46 maxRcvSize int 47 lock sync.Mutex 48 } 49 50 // Dial implements the Dialer Dial method 51 func (d *dialer) Dial() (transport.Pipe, error) { 52 53 conn, err := net.DialUnix("unix", nil, d.addr) 54 if err != nil { 55 return nil, err 56 } 57 p := transport.NewConnPipeIPC(conn, d.proto) 58 d.lock.Lock() 59 p.SetOption(mangos.OptionMaxRecvSize, d.maxRcvSize) 60 getPeer(conn, p) 61 d.lock.Unlock() 62 d.hs.Start(p) 63 return d.hs.Wait() 64 } 65 66 // SetOption implements Dialer SetOption method. 67 func (d *dialer) SetOption(n string, v interface{}) error { 68 d.lock.Lock() 69 defer d.lock.Unlock() 70 71 switch n { 72 case mangos.OptionMaxRecvSize: 73 if b, ok := v.(int); ok { 74 d.maxRcvSize = b 75 return nil 76 } 77 return mangos.ErrBadValue 78 } 79 return mangos.ErrBadOption 80 } 81 82 // GetOption implements Dialer GetOption method. 83 func (d *dialer) GetOption(n string) (interface{}, error) { 84 d.lock.Lock() 85 defer d.lock.Unlock() 86 87 switch n { 88 case mangos.OptionMaxRecvSize: 89 return d.maxRcvSize, nil 90 } 91 return nil, mangos.ErrBadOption 92 } 93 94 type listener struct { 95 addr *net.UnixAddr 96 proto transport.ProtocolInfo 97 listener *net.UnixListener 98 hs transport.Handshaker 99 closeQ chan struct{} 100 closed bool 101 maxRcvSize int 102 owner int 103 group int 104 chown bool 105 mode uint32 106 chmod bool 107 once sync.Once 108 lock sync.Mutex 109 } 110 111 // Listen implements the PipeListener Listen method. 112 func (l *listener) Listen() error { 113 l.lock.Lock() 114 defer l.lock.Unlock() 115 116 select { 117 case <-l.closeQ: 118 return mangos.ErrClosed 119 default: 120 } 121 listener, err := net.ListenUnix("unix", l.addr) 122 123 if err != nil && (isSyscallError(err, syscall.EADDRINUSE) || isSyscallError(err, syscall.EEXIST)) { 124 l.removeStaleIPC() 125 listener, err = net.ListenUnix("unix", l.addr) 126 if isSyscallError(err, syscall.EADDRINUSE) || isSyscallError(err, syscall.EEXIST) { 127 err = mangos.ErrAddrInUse 128 } 129 } 130 if err != nil { 131 return err 132 } 133 134 // Best effort to update permissions -- note that this can fail for various reasons, 135 // and it is unadvisable to rely too strongly upon it. (It isn't portable at least.) 136 if l.chown { 137 _ = os.Chown(l.addr.String(), l.owner, l.group) 138 } 139 if l.chmod { 140 _ = os.Chmod(l.addr.String(), os.FileMode(l.mode)) 141 } 142 143 l.listener = listener 144 go func() { 145 for { 146 conn, err := l.listener.AcceptUnix() 147 if err != nil { 148 select { 149 case <-l.closeQ: 150 return 151 default: 152 continue 153 } 154 } 155 p := transport.NewConnPipeIPC(conn, l.proto) 156 l.lock.Lock() 157 p.SetOption(mangos.OptionMaxRecvSize, l.maxRcvSize) 158 getPeer(conn, p) 159 l.lock.Unlock() 160 l.hs.Start(p) 161 } 162 }() 163 return nil 164 } 165 166 func (l *listener) Address() string { 167 return "ipc://" + l.addr.String() 168 } 169 170 // Accept implements the PipeListener Accept method. 171 func (l *listener) Accept() (transport.Pipe, error) { 172 l.lock.Lock() 173 if l.listener == nil { 174 l.lock.Unlock() 175 return nil, mangos.ErrClosed 176 } 177 l.lock.Unlock() 178 return l.hs.Wait() 179 } 180 181 // Close implements the PipeListener Close method. 182 func (l *listener) Close() error { 183 l.once.Do(func() { 184 l.lock.Lock() 185 l.closed = true 186 if l.listener != nil { 187 _ = l.listener.Close() 188 } 189 l.hs.Close() 190 close(l.closeQ) 191 l.lock.Unlock() 192 }) 193 return nil 194 } 195 196 // SetOption implements a stub PipeListener SetOption method. 197 func (l *listener) SetOption(n string, v interface{}) error { 198 l.lock.Lock() 199 defer l.lock.Unlock() 200 201 switch n { 202 case mangos.OptionMaxRecvSize: 203 if b, ok := v.(int); ok { 204 l.maxRcvSize = b 205 return nil 206 } 207 return mangos.ErrBadValue 208 case OptionIpcSocketPermissions: 209 if b, ok := v.(uint32); ok && b&uint32(os.ModePerm) == v { 210 l.mode = b 211 l.chmod = true 212 return nil 213 } 214 if b, ok := v.(os.FileMode); ok && b&os.ModePerm == v { 215 l.mode = uint32(b) 216 l.chmod = true 217 return nil 218 } 219 return mangos.ErrBadValue 220 case OptionIpcSocketOwner: 221 if b, ok := v.(int); ok { 222 l.owner = b 223 l.chown = true 224 return nil 225 } 226 return mangos.ErrBadValue 227 case OptionIpcSocketGroup: 228 if b, ok := v.(int); ok { 229 l.group = b 230 l.chown = true 231 return nil 232 } 233 return mangos.ErrBadValue 234 } 235 236 return mangos.ErrBadOption 237 } 238 239 // GetOption implements a stub PipeListener GetOption method. 240 func (l *listener) GetOption(n string) (interface{}, error) { 241 l.lock.Lock() 242 defer l.lock.Unlock() 243 244 switch n { 245 case mangos.OptionMaxRecvSize: 246 return l.maxRcvSize, nil 247 } 248 return nil, mangos.ErrBadOption 249 } 250 251 func (l *listener) removeStaleIPC() { 252 addr := l.addr.String() 253 // if it's not a socket, then leave it alone! 254 if st, err := os.Stat(addr); err != nil || st.Mode()&os.ModeType != os.ModeSocket { 255 return 256 } 257 conn, err := net.DialTimeout("unix", l.addr.String(), 100*time.Millisecond) 258 if err != nil && isSyscallError(err, syscall.ECONNREFUSED) { 259 _ = os.Remove(l.addr.String()) 260 return 261 } 262 if err == nil { 263 _ = conn.Close() 264 } 265 } 266 267 type ipcTran int 268 269 // Scheme implements the Transport Scheme method. 270 func (ipcTran) Scheme() string { 271 return "ipc" 272 } 273 274 // NewDialer implements the Transport NewDialer method. 275 func (t ipcTran) NewDialer(addr string, sock mangos.Socket) (transport.Dialer, error) { 276 var err error 277 d := &dialer{ 278 proto: sock.Info(), 279 hs: transport.NewConnHandshaker(), 280 } 281 282 if addr, err = transport.StripScheme(t, addr); err != nil { 283 return nil, err 284 } 285 286 // ignoring the errors, because this cannot fail on POSIX systems; 287 // the only error conditions are if the network is not "unix" 288 d.addr, _ = net.ResolveUnixAddr("unix", addr) 289 return d, nil 290 } 291 292 // NewListener implements the Transport NewListener method. 293 func (t ipcTran) NewListener(addr string, sock mangos.Socket) (transport.Listener, error) { 294 var err error 295 l := &listener{ 296 proto: sock.Info(), 297 closeQ: make(chan struct{}), 298 hs: transport.NewConnHandshaker(), 299 owner: os.Geteuid(), 300 group: os.Getegid(), 301 } 302 303 if addr, err = transport.StripScheme(t, addr); err != nil { 304 return nil, err 305 } 306 307 // ignoring the errors, as it cannot fail. 308 l.addr, _ = net.ResolveUnixAddr("unix", addr) 309 return l, nil 310 } 311 312 func isSyscallError(err error, code syscall.Errno) bool { 313 314 var errno syscall.Errno 315 if errors.As(err, &errno) { 316 return errno == code 317 } 318 return false 319 }