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  }