blob: fd17a9309a66a6f140480c6adec975d74f394b26 [file] [log] [blame]
Ryan Brownfed691e2014-09-15 13:09:40 -07001// Package unixfd provides provides support for Dialing and Listening
2// on already connected file descriptors (like those returned by socketpair).
3package unixfd
4
5import (
6 "errors"
7 "fmt"
8 "io"
9 "net"
10 "os"
11 "strconv"
12 "sync"
13 "syscall"
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -080014 "time"
Ryan Brown50b473a2014-09-23 14:23:00 -070015 "unsafe"
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -080016
Jiri Simsa764efb72014-12-25 20:57:03 -080017 "v.io/core/veyron2/ipc/stream"
Ryan Brownfed691e2014-09-15 13:09:40 -070018)
19
20const Network string = "unixfd"
21
22// singleConnListener implements net.Listener for an already-connected socket.
23// This is different from net.FileListener, which calls syscall.Listen
24// on an unconnected socket.
25type singleConnListener struct {
26 c chan net.Conn
27 addr net.Addr
28 sync.Mutex
29}
30
31func (l *singleConnListener) getChan() chan net.Conn {
32 l.Lock()
33 defer l.Unlock()
34 return l.c
35}
36
37func (l *singleConnListener) Accept() (net.Conn, error) {
38 c := l.getChan()
39 if c == nil {
40 return nil, errors.New("listener closed")
41 }
42 if conn, ok := <-c; ok {
43 return conn, nil
44 }
45 return nil, io.EOF
46}
47
48func (l *singleConnListener) Close() error {
49 l.Lock()
50 defer l.Unlock()
51 lc := l.c
52 if lc == nil {
53 return errors.New("listener already closed")
54 }
55 close(l.c)
56 l.c = nil
57 // If the socket was never Accept'ed we need to close it.
58 if c, ok := <-lc; ok {
59 return c.Close()
60 }
61 return nil
62}
63
64func (l *singleConnListener) Addr() net.Addr {
65 return l.addr
66}
67
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -080068func unixFDConn(protocol, address string, timeout time.Duration) (net.Conn, error) {
69 // TODO(cnicolaou): have this respect the timeout. Possibly have a helper
70 // function that can be generally used for this, but in practice, I think
71 // it'll be cleaner to use the underlying protocol's deadline support of it
72 // has it.
Ryan Brownfed691e2014-09-15 13:09:40 -070073 fd, err := strconv.ParseInt(address, 10, 32)
74 if err != nil {
75 return nil, err
76 }
77 file := os.NewFile(uintptr(fd), "tmp")
78 conn, err := net.FileConn(file)
79 // 'file' is not used after this point, but we keep it open
80 // so that 'address' remains valid.
81 if err != nil {
82 file.Close()
83 return nil, err
84 }
85 // We wrap 'conn' so we can customize the address, and also
86 // to close 'file'.
Matt Rosencrantz0610a232014-12-04 10:26:39 -080087 return &fdConn{addr: addr(address), sock: file, Conn: conn}, nil
Ryan Brownfed691e2014-09-15 13:09:40 -070088}
89
90type fdConn struct {
91 addr net.Addr
92 sock *os.File
93 net.Conn
Matt Rosencrantz0610a232014-12-04 10:26:39 -080094
95 mu sync.Mutex
96 closed bool
Ryan Brownfed691e2014-09-15 13:09:40 -070097}
98
99func (c *fdConn) Close() (err error) {
Matt Rosencrantz0610a232014-12-04 10:26:39 -0800100 c.mu.Lock()
101 defer c.mu.Unlock()
102
103 if c.closed {
104 return nil
105 }
106
107 c.closed = true
Ryan Brownfed691e2014-09-15 13:09:40 -0700108 defer c.sock.Close()
109 return c.Conn.Close()
110}
111
112func (c *fdConn) LocalAddr() net.Addr {
113 return c.addr
114}
115
116func (c *fdConn) RemoteAddr() net.Addr {
117 return c.addr
118}
119
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -0800120func unixFDListen(protocol, address string) (net.Listener, error) {
121 conn, err := unixFDConn(protocol, address, 0)
Ryan Brownfed691e2014-09-15 13:09:40 -0700122 if err != nil {
123 return nil, err
124 }
125 c := make(chan net.Conn, 1)
126 c <- conn
127 return &singleConnListener{c, conn.LocalAddr(), sync.Mutex{}}, nil
128}
129
130type addr string
131
132func (a addr) Network() string { return Network }
133func (a addr) String() string { return string(a) }
134
135// Addr returns a net.Addr for the unixfd network for the given file descriptor.
136func Addr(fd uintptr) net.Addr {
137 return addr(fmt.Sprintf("%d", fd))
138}
139
Ryan Brown50b473a2014-09-23 14:23:00 -0700140type fileDescriptor struct {
141 fd chan int
142 name string
143}
144
145func newFd(fd int, name string) *fileDescriptor {
146 ch := make(chan int, 1)
147 ch <- fd
148 close(ch)
149 d := &fileDescriptor{ch, name}
150 return d
151}
152
153func (f *fileDescriptor) releaseAddr() net.Addr {
154 if fd, ok := <-f.fd; ok {
155 return Addr(uintptr(fd))
156 }
157 return nil
158}
159
160func (f *fileDescriptor) releaseFile() *os.File {
161 if fd, ok := <-f.fd; ok {
162 return os.NewFile(uintptr(fd), f.name)
163 }
164 return nil
165}
166
167// maybeClose closes the file descriptor, if it hasn't been released.
168func (f *fileDescriptor) maybeClose() {
169 if file := f.releaseFile(); file != nil {
170 file.Close()
171 }
172}
173
174// Socketpair returns a pair of connected sockets for communicating with a child process.
175func Socketpair() (*net.UnixConn, *os.File, error) {
Bogdan Capritabb37c542015-01-22 10:21:57 -0800176 lfd, rfd, err := socketpair()
Ryan Brown50b473a2014-09-23 14:23:00 -0700177 if err != nil {
178 return nil, nil, err
179 }
180 defer rfd.maybeClose()
181 file := lfd.releaseFile()
182 // FileConn dups the fd, so we still want to close the original one.
183 defer file.Close()
184 conn, err := net.FileConn(file)
185 if err != nil {
186 return nil, nil, err
187 }
188 return conn.(*net.UnixConn), rfd.releaseFile(), nil
189}
190
Bogdan Capritabb37c542015-01-22 10:21:57 -0800191func socketpair() (local, remote *fileDescriptor, err error) {
Ryan Brown50b473a2014-09-23 14:23:00 -0700192 syscall.ForkLock.RLock()
Ryan Brownfed691e2014-09-15 13:09:40 -0700193 fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
Ryan Brown50b473a2014-09-23 14:23:00 -0700194 if err == nil {
195 syscall.CloseOnExec(fds[0])
Bogdan Capritabb37c542015-01-22 10:21:57 -0800196 syscall.CloseOnExec(fds[1])
Ryan Brown50b473a2014-09-23 14:23:00 -0700197 }
198 syscall.ForkLock.RUnlock()
199 if err != nil {
200 return nil, nil, err
201 }
202 return newFd(fds[0], "local"), newFd(fds[1], "remote"), nil
203}
204
205// SendConnection creates a new connected socket and sends
206// one end over 'conn', along with 'data'. It returns the address for
207// the local end of the socketpair.
Ryan Brown81789442014-10-30 13:23:53 -0700208// Note that the returned address is an open file descriptor,
Ryan Brown50b473a2014-09-23 14:23:00 -0700209// which you must close if you do not Dial or Listen to the address.
Bogdan Capritabb37c542015-01-22 10:21:57 -0800210func SendConnection(conn *net.UnixConn, data []byte) (addr net.Addr, err error) {
Ryan Brown50b473a2014-09-23 14:23:00 -0700211 if len(data) < 1 {
212 return nil, errors.New("cannot send a socket without data.")
213 }
Bogdan Capritabb37c542015-01-22 10:21:57 -0800214 remote, local, err := socketpair()
Ryan Brownfed691e2014-09-15 13:09:40 -0700215 if err != nil {
216 return nil, err
217 }
Ryan Brown50b473a2014-09-23 14:23:00 -0700218 defer local.maybeClose()
219 rfile := remote.releaseFile()
Ryan Brown50b473a2014-09-23 14:23:00 -0700220
221 rights := syscall.UnixRights(int(rfile.Fd()))
222 n, oobn, err := conn.WriteMsgUnix(data, rights, nil)
223 if err != nil {
Ryan Brownfac212e2014-11-13 12:23:47 -0800224 rfile.Close()
Ryan Brown50b473a2014-09-23 14:23:00 -0700225 return nil, err
226 } else if n != len(data) || oobn != len(rights) {
Ryan Brownfac212e2014-11-13 12:23:47 -0800227 rfile.Close()
Ryan Brown50b473a2014-09-23 14:23:00 -0700228 return nil, fmt.Errorf("expected to send %d, %d bytes, sent %d, %d", len(data), len(rights), n, oobn)
229 }
Ryan Brownfac212e2014-11-13 12:23:47 -0800230 // Wait for the other side to acknowledge.
231 // This is to work around a race on OS X where it appears we can close
232 // the file descriptor before it gets transfered over the socket.
233 f := local.releaseFile()
234 fd, err := syscall.Dup(int(f.Fd()))
235 if err != nil {
236 f.Close()
237 rfile.Close()
238 return nil, err
239 }
240 newConn, err := net.FileConn(f)
241 f.Close()
242 if err != nil {
243 rfile.Close()
244 return nil, err
245 }
246 newConn.Read(make([]byte, 1))
247 newConn.Close()
248 rfile.Close()
249
250 return Addr(uintptr(fd)), nil
Ryan Brown50b473a2014-09-23 14:23:00 -0700251}
252
253const cmsgDataLength = int(unsafe.Sizeof(int(1)))
254
255// ReadConnection reads a connection and additional data sent on 'conn' via a call to SendConnection.
256// 'buf' must be large enough to hold the data.
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800257// The returned function must be called when you are ready for the other side
258// to start sending data, but before writing anything to the connection.
259// If there is an error you must still call the function before closing the connection.
260func ReadConnection(conn *net.UnixConn, buf []byte) (net.Addr, int, func(), error) {
Ryan Brown50b473a2014-09-23 14:23:00 -0700261 oob := make([]byte, syscall.CmsgLen(cmsgDataLength))
262 n, oobn, _, _, err := conn.ReadMsgUnix(buf, oob)
263 if err != nil {
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800264 return nil, n, nil, err
Ryan Brown50b473a2014-09-23 14:23:00 -0700265 }
266 if oobn > len(oob) {
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800267 return nil, n, nil, fmt.Errorf("received too large oob data (%d, max %d)", oobn, len(oob))
Ryan Brown50b473a2014-09-23 14:23:00 -0700268 }
269 scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
270 if err != nil {
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800271 return nil, n, nil, err
Ryan Brown50b473a2014-09-23 14:23:00 -0700272 }
273 fd := -1
Ryan Brownfac212e2014-11-13 12:23:47 -0800274 // Loop through any file descriptors we are sent, and close
275 // all extras.
Ryan Brown50b473a2014-09-23 14:23:00 -0700276 for _, scm := range scms {
277 fds, err := syscall.ParseUnixRights(&scm)
278 if err != nil {
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800279 return nil, n, nil, err
Ryan Brown50b473a2014-09-23 14:23:00 -0700280 }
281 for _, f := range fds {
Ryan Brownfac212e2014-11-13 12:23:47 -0800282 if fd == -1 {
283 fd = f
284 } else if f != -1 {
285 syscall.Close(f)
Ryan Brown50b473a2014-09-23 14:23:00 -0700286 }
Ryan Brown50b473a2014-09-23 14:23:00 -0700287 }
288 }
289 if fd == -1 {
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800290 return nil, n, nil, nil
Ryan Brown50b473a2014-09-23 14:23:00 -0700291 }
Ryan Brownfac212e2014-11-13 12:23:47 -0800292 result := Addr(uintptr(fd))
293 fd, err = syscall.Dup(fd)
294 if err != nil {
295 CloseUnixAddr(result)
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800296 return nil, n, nil, err
Ryan Brownfac212e2014-11-13 12:23:47 -0800297 }
298 file := os.NewFile(uintptr(fd), "newconn")
299 newconn, err := net.FileConn(file)
300 file.Close()
301 if err != nil {
302 CloseUnixAddr(result)
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800303 return nil, n, nil, err
Ryan Brownfac212e2014-11-13 12:23:47 -0800304 }
Ryan Brown53a8e2b2015-01-07 10:50:04 -0800305 return result, n, func() {
306 newconn.Write(make([]byte, 1))
307 newconn.Close()
308 }, nil
Ryan Brownfed691e2014-09-15 13:09:40 -0700309}
310
Ryan Brown81789442014-10-30 13:23:53 -0700311func CloseUnixAddr(addr net.Addr) error {
312 if addr.Network() != Network {
313 return errors.New("invalid network")
314 }
315 fd, err := strconv.ParseInt(addr.String(), 10, 32)
316 if err != nil {
317 return err
318 }
319 return syscall.Close(int(fd))
320}
321
Ryan Brownfed691e2014-09-15 13:09:40 -0700322func init() {
323 stream.RegisterProtocol(Network, unixFDConn, unixFDListen)
324}