]> Cypherpunks repositories - gostls13.git/commitdiff
Unix domain socket support, Linux and Darwin.
authorRuss Cox <rsc@golang.org>
Thu, 14 May 2009 01:03:41 +0000 (18:03 -0700)
committerRuss Cox <rsc@golang.org>
Thu, 14 May 2009 01:03:41 +0000 (18:03 -0700)
R=r
DELTA=534  (353 added, 99 deleted, 82 changed)
OCL=28783
CL=28783

src/lib/net/net.go
src/lib/net/net_darwin.go
src/lib/net/net_linux.go
src/lib/net/server_test.go [moved from src/lib/net/tcpserver_test.go with 88% similarity]
src/lib/os/error.go
src/lib/syscall/socket_darwin.go
src/lib/syscall/socket_linux.go

index cd2828592ef2ea2dd924983d34ea7f5522eb8d4d..d1000bfead98df3b953cddbd906932536e3ecfd3 100644 (file)
@@ -20,6 +20,83 @@ var (
 )
 
 
+// Conn is a generic network connection.
+type Conn interface {
+       // Read blocks until data is ready from the connection
+       // and then reads into b.  It returns the number
+       // of bytes read, or 0 if the connection has been closed.
+       Read(b []byte) (n int, err os.Error);
+
+       // Write writes the data in b to the connection.
+       Write(b []byte) (n int, err os.Error);
+
+       // Close closes the connection.
+       Close() os.Error;
+
+       // For packet-based protocols such as UDP,
+       // ReadFrom reads the next packet from the network,
+       // returning the number of bytes read and the remote
+       // address that sent them.
+       ReadFrom(b []byte) (n int, addr string, err os.Error);
+
+       // For packet-based protocols such as UDP,
+       // WriteTo writes the byte buffer b to the network
+       // as a single payload, sending it to the target address.
+       WriteTo(addr string, b []byte) (n int, err os.Error);
+
+       // SetReadBuffer sets the size of the operating system's
+       // receive buffer associated with the connection.
+       SetReadBuffer(bytes int) os.Error;
+
+       // SetReadBuffer sets the size of the operating system's
+       // transmit buffer associated with the connection.
+       SetWriteBuffer(bytes int) os.Error;
+
+       // SetTimeout sets the read and write deadlines associated
+       // with the connection.
+       SetTimeout(nsec int64) os.Error;
+
+       // SetReadTimeout sets the time (in nanoseconds) that
+       // Read will wait for data before returning os.EAGAIN.
+       // Setting nsec == 0 (the default) disables the deadline.
+       SetReadTimeout(nsec int64) os.Error;
+
+       // SetWriteTimeout sets the time (in nanoseconds) that
+       // Write will wait to send its data before returning os.EAGAIN.
+       // Setting nsec == 0 (the default) disables the deadline.
+       // Even if write times out, it may return n > 0, indicating that
+       // some of the data was successfully written.
+       SetWriteTimeout(nsec int64) os.Error;
+
+       // SetLinger sets the behavior of Close() on a connection
+       // which still has data waiting to be sent or to be acknowledged.
+       //
+       // If sec < 0 (the default), Close returns immediately and
+       // the operating system finishes sending the data in the background.
+       //
+       // If sec == 0, Close returns immediately and the operating system
+       // discards any unsent or unacknowledged data.
+       //
+       // If sec > 0, Close blocks for at most sec seconds waiting for
+       // data to be sent and acknowledged.
+       SetLinger(sec int) os.Error;
+
+       // SetReuseAddr sets whether it is okay to reuse addresses
+       // from recent connections that were not properly closed.
+       SetReuseAddr(reuseaddr bool) os.Error;
+
+       // SetDontRoute sets whether outgoing messages should
+       // bypass the system routing tables.
+       SetDontRoute(dontroute bool) os.Error;
+
+       // SetKeepAlive sets whether the operating system should send
+       // keepalive messages on the connection.
+       SetKeepAlive(keepalive bool) os.Error;
+
+       // BindToDevice binds a connection to a particular network device.
+       BindToDevice(dev string) os.Error;
+}
+
 // Should we try to use the IPv4 socket interface if we're
 // only dealing with IPv4 sockets?  As long as the host system
 // understands IPv6, it's okay to pass IPv4 addresses to the IPv6
@@ -160,9 +237,7 @@ func boolint(b bool) int {
 }
 
 // Generic socket creation.
-func socket(net, laddr, raddr string, f, p, t int64, la, ra *syscall.Sockaddr)
-       (fd *netFD, err os.Error)
-{
+func socket(net, laddr, raddr string, f, p, t int64, la, ra *syscall.Sockaddr) (fd *netFD, err os.Error) {
        // See ../syscall/exec.go for description of ForkLock.
        syscall.ForkLock.RLock();
        s, e := syscall.Socket(f, p, t);
@@ -318,9 +393,7 @@ func (c *connBase) SetLinger(sec int) os.Error {
 
 // Internet sockets (TCP, UDP)
 
-func internetSocket(net, laddr, raddr string, proto int64, mode string)
-       (fd *netFD, err os.Error)
-{
+func internetSocket(net, laddr, raddr string, proto int64, mode string) (fd *netFD, err os.Error) {
        // Parse addresses (unless they are empty).
        var lip, rip IP;
        var lport, rport int;
@@ -388,6 +461,8 @@ func internetSocket(net, laddr, raddr string, proto int64, mode string)
 
 // TCP connections.
 
+// ConnTCP is an implementation of the Conn interface
+// for TCP network connections.
 type ConnTCP struct {
        connBase
 }
@@ -407,6 +482,8 @@ func newConnTCP(fd *netFD, raddr string) *ConnTCP {
        return c
 }
 
+// DialTCP is like Dial but can only connect to TCP networks
+// and returns a ConnTCP structure.
 func DialTCP(net, laddr, raddr string) (c *ConnTCP, err os.Error) {
        if raddr == "" {
                return nil, MissingAddress
@@ -423,6 +500,8 @@ func DialTCP(net, laddr, raddr string) (c *ConnTCP, err os.Error) {
 
 // TODO(rsc): UDP headers mode
 
+// ConnUDP is an implementation of the Conn interface
+// for UDP network connections.
 type ConnUDP struct {
        connBase
 }
@@ -434,6 +513,8 @@ func newConnUDP(fd *netFD, raddr string) *ConnUDP {
        return c
 }
 
+// DialUDP is like Dial but can only connect to UDP networks
+// and returns a ConnUDP structure.
 func DialUDP(net, laddr, raddr string) (c *ConnUDP, err os.Error) {
        if raddr == "" {
                return nil, MissingAddress
@@ -450,81 +531,172 @@ func DialUDP(net, laddr, raddr string) (c *ConnUDP, err os.Error) {
 
 // TODO: raw ethernet connections
 
-// A Conn is a generic network connection.
-type Conn interface {
-       // Read blocks until data is ready from the connection
-       // and then reads into b.  It returns the number
-       // of bytes read, or 0 if the connection has been closed.
-       Read(b []byte) (n int, err os.Error);
 
-       // Write writes the data in b to the connection.
-       Write(b []byte) (n int, err os.Error);
+// Unix domain sockets
 
-       // Close closes the connection.
-       Close() os.Error;
+func unixSocket(net, laddr, raddr string, mode string) (fd *netFD, err os.Error) {
+       var proto int64;
+       switch net {
+       default:
+               return nil, UnknownNetwork;
+       case "unix":
+               proto = syscall.SOCK_STREAM;
+       case "unix-dgram":
+               proto = syscall.SOCK_DGRAM;
+       }
 
-       // For packet-based protocols such as UDP,
-       // ReadFrom reads the next packet from the network,
-       // returning the number of bytes read and the remote
-       // address that sent them.
-       ReadFrom(b []byte) (n int, addr string, err os.Error);
+       var la, ra *syscall.Sockaddr;
+       switch mode {
+       case "dial":
+               if laddr != "" {
+                       return nil, BadAddress;
+               }
+               if raddr == "" {
+                       return nil, MissingAddress;
+               }
+               ra, err = unixToSockaddr(raddr);
+               if err != nil {
+                       return nil, err;
+               }
 
-       // For packet-based protocols such as UDP,
-       // WriteTo writes the byte buffer b to the network
-       // as a single payload, sending it to the target address.
-       WriteTo(addr string, b []byte) (n int, err os.Error);
+       case "listen":
+               if laddr == "" {
+                       return nil, MissingAddress;
+               }
+               la, err = unixToSockaddr(laddr);
+               if err != nil {
+                       return nil, err;
+               }
+               if raddr != "" {
+                       return nil, BadAddress;
+               }
+       }
 
-       // SetReadBuffer sets the size of the operating system's
-       // receive buffer associated with the connection.
-       SetReadBuffer(bytes int) os.Error;
+       fd, err = socket(net, laddr, raddr, syscall.AF_UNIX, proto, 0, la, ra);
+       return fd, err
+}
 
-       // SetReadBuffer sets the size of the operating system's
-       // transmit buffer associated with the connection.
-       SetWriteBuffer(bytes int) os.Error;
+// ConnUnix is an implementation of the Conn interface
+// for connections to Unix domain sockets.
+type ConnUnix struct {
+       connBase
+}
 
-       // SetTimeout sets the read and write deadlines associated
-       // with the connection.
-       SetTimeout(nsec int64) os.Error;
+func newConnUnix(fd *netFD, raddr string) *ConnUnix {
+       c := new(ConnUnix);
+       c.fd = fd;
+       c.raddr = raddr;
+       return c;
+}
 
-       // SetReadTimeout sets the time (in nanoseconds) that
-       // Read will wait for data before returning os.EAGAIN.
-       // Setting nsec == 0 (the default) disables the deadline.
-       SetReadTimeout(nsec int64) os.Error;
+// DialUnix is like Dial but can only connect to Unix domain sockets
+// and returns a ConnUnix structure.  The laddr argument must be
+// the empty string; it is included only to match the signature of
+// the other dial routines.
+func DialUnix(net, laddr, raddr string) (c *ConnUnix, err os.Error) {
+       fd, e := unixSocket(net, laddr, raddr, "dial");
+       if e != nil {
+               return nil, e
+       }
+       return newConnUnix(fd, raddr), nil;
+}
 
-       // SetWriteTimeout sets the time (in nanoseconds) that
-       // Write will wait to send its data before returning os.EAGAIN.
-       // Setting nsec == 0 (the default) disables the deadline.
-       // Even if write times out, it may return n > 0, indicating that
-       // some of the data was successfully written.
-       SetWriteTimeout(nsec int64) os.Error;
+// ListenerUnix is a Unix domain socket listener.
+// Clients should typically use variables of type Listener
+// instead of assuming Unix domain sockets.
+type ListenerUnix struct {
+       fd *netFD;
+       laddr string
+}
 
-       // SetLinger sets the behavior of Close() on a connection
-       // which still has data waiting to be sent or to be acknowledged.
-       //
-       // If sec < 0 (the default), Close returns immediately and
-       // the operating system finishes sending the data in the background.
-       //
-       // If sec == 0, Close returns immediately and the operating system
-       // discards any unsent or unacknowledged data.
-       //
-       // If sec > 0, Close blocks for at most sec seconds waiting for
-       // data to be sent and acknowledged.
-       SetLinger(sec int) os.Error;
+// ListenUnix announces on the Unix domain socket laddr and returns a Unix listener.
+// Net can be either "unix" (stream sockets) or "unix-dgram" (datagram sockets).
+func ListenUnix(net, laddr string) (l *ListenerUnix, err os.Error) {
+       fd, e := unixSocket(net, laddr, "", "listen");
+       if e != nil {
+               // Check for socket ``in use'' but ``refusing connections,''
+               // which means some program created it and exited
+               // without unlinking it from the file system.
+               // Clean up on that program's behalf and try again.
+               // Don't do this for Linux's ``abstract'' sockets, which begin with @.
+               if e != os.EADDRINUSE || laddr[0] == '@' {
+                       return nil, e;
+               }
+               fd1, e1 := unixSocket(net, "", laddr, "dial");
+               if e1 == nil {
+                       fd1.Close();
+               }
+               if e1 != os.ECONNREFUSED {
+                       return nil, e;
+               }
+               syscall.Unlink(laddr);
+               fd1, e1 = unixSocket(net, laddr, "", "listen");
+               if e1 != nil {
+                       return nil, e;
+               }
+               fd = fd1;
+       }
+       r, e1 := syscall.Listen(fd.fd, 8); // listenBacklog());
+       if e1 != 0 {
+               syscall.Close(fd.fd);
+               return nil, os.ErrnoToError(e1);
+       }
+       return &ListenerUnix{fd, laddr}, nil;
+}
 
-       // SetReuseAddr sets whether it is okay to reuse addresses
-       // from recent connections that were not properly closed.
-       SetReuseAddr(reuseaddr bool) os.Error;
+// AcceptUnix accepts the next incoming call and returns the new connection
+// and the remote address.
+func (l *ListenerUnix) AcceptUnix() (c *ConnUnix, raddr string, err os.Error) {
+       if l == nil || l.fd == nil || l.fd.fd < 0 {
+               return nil, "", os.EINVAL
+       }
+       var sa syscall.Sockaddr;
+       fd, e := l.fd.Accept(&sa);
+       if e != nil {
+               return nil, "", e
+       }
+       raddr, err = sockaddrToUnix(&sa);
+       if err != nil {
+               fd.Close();
+               return nil, "", err
+       }
+       return newConnUnix(fd, raddr), raddr, nil
+}
 
-       // SetDontRoute sets whether outgoing messages should
-       // bypass the system routing tables.
-       SetDontRoute(dontroute bool) os.Error;
+// Accept implements the Accept method in the Listener interface;
+// it waits for the next call and returns a generic Conn.
+func (l *ListenerUnix) Accept() (c Conn, raddr string, err os.Error) {
+       // TODO(rsc): 6g bug prevents saying
+       //      c, raddr, err = l.AcceptUnix();
+       //      return;
+       c1, r1, e1 := l.AcceptUnix();
+       return c1, r1, e1;
+}
 
-       // SetKeepAlive sets whether the operating system should send
-       // keepalive messages on the connection.
-       SetKeepAlive(keepalive bool) os.Error;
 
-       // BindToDevice binds a connection to a particular network device.
-       BindToDevice(dev string) os.Error;
+// Close stops listening on the Unix address.
+// Already accepted connections are not closed.
+func (l *ListenerUnix) Close() os.Error {
+       if l == nil || l.fd == nil {
+               return os.EINVAL
+       }
+
+       // The operating system doesn't clean up
+       // the file that announcing created, so
+       // we have to clean it up ourselves.
+       // There's a race here--we can't know for
+       // sure whether someone else has come along
+       // and replaced our socket name already--
+       // but this sequence (remove then close)
+       // is at least compatible with the auto-remove
+       // sequence in ListenUnix.  It's only non-Go
+       // programs that can mess us up.
+       if l.laddr[0] != '@' {
+               syscall.Unlink(l.laddr);
+       }
+       err := l.fd.Close();
+       l.fd = nil;
+       return err;
 }
 
 // Dial connects to the remote address raddr on the network net.
@@ -553,6 +725,9 @@ func Dial(net, laddr, raddr string) (c Conn, err os.Error) {
        case "udp", "udp4", "upd6":
                c, err := DialUDP(net, laddr, raddr);
                return c, err;
+       case "unix", "unix-dgram":
+               c, err := DialUnix(net, laddr, raddr);
+               return c, err;
 /*
        case "ether":
                c, err := DialEther(net, laddr, raddr);
@@ -619,7 +794,7 @@ func (l *ListenerTCP) AcceptTCP() (c *ConnTCP, raddr string, err os.Error) {
        return newConnTCP(fd, raddr), raddr, nil
 }
 
-// Accept implements the accept method in the Listener interface;
+// Accept implements the Accept method in the Listener interface;
 // it waits for the next call and returns a generic Conn.
 func (l *ListenerTCP) Accept() (c Conn, raddr string, err os.Error) {
        c1, r1, e1 := l.AcceptTCP();
@@ -639,15 +814,22 @@ func (l *ListenerTCP) Close() os.Error {
 }
 
 // Listen announces on the local network address laddr.
-// The network string net must be "tcp", "tcp4", or "tcp6".
+// The network string net must be "tcp", "tcp4", "tcp6",
+// "unix", or "unix-dgram".
 func Listen(net, laddr string) (l Listener, err os.Error) {
        switch net {
        case "tcp", "tcp4", "tcp6":
                l, err := ListenTCP(net, laddr);
                if err != nil {
-                       return nil, err
+                       return nil, err;
+               }
+               return l, nil;
+       case "unix", "unix-dgram":
+               l, err := ListenUnix(net, laddr);
+               if err != nil {
+                       return nil, err;
                }
-               return l, nil
+               return l, nil;
 /*
        more here
 */
index 2419f6c6debc1526c8dde23d11abe937032bf5b9..50e19d61427ef7a7ee480b33bf954318a3f426fb 100644 (file)
@@ -43,7 +43,6 @@ func v6ToSockaddr(p IP, port int) (sa1 *syscall.Sockaddr, err os.Error) {
        return (*syscall.Sockaddr)(unsafe.Pointer(sa)), nil
 }
 
-
 func sockaddrToIP(sa1 *syscall.Sockaddr) (p IP, port int, err os.Error) {
        switch sa1.Family {
        case syscall.AF_INET:
@@ -70,3 +69,32 @@ func listenBacklog() int64 {
        return syscall.SOMAXCONN
 }
 
+func unixToSockaddr(name string) (sa1 *syscall.Sockaddr, err os.Error) {
+       sa := new(syscall.SockaddrUnix);
+       n := len(name);
+       if n >= len(sa.Path) || n == 0 {
+               return nil, os.EINVAL;
+       }
+       sa.Len = byte(3 + n);   // 2 for Family, Len; 1 for NUL
+       sa.Family = syscall.AF_UNIX;
+       for i := 0; i < len(name); i++ {
+               sa.Path[i] = name[i];
+       }
+       return (*syscall.Sockaddr)(unsafe.Pointer(sa)), nil;
+}
+
+func sockaddrToUnix(sa1 *syscall.Sockaddr) (string, os.Error) {
+       if sa1.Family != syscall.AF_UNIX || sa1.Len < 3 || sa1.Len > syscall.SizeofSockaddrUnix {
+               return "", os.EINVAL;
+       }
+       sa := (*syscall.SockaddrUnix)(unsafe.Pointer(sa1));
+       n := int(sa.Len) - 3;   // subtract leading Family, Len, terminating NUL
+       for i := 0; i < n; i++ {
+               if sa.Path[i] == 0 {
+                       // found early NUL; assume Len is overestimating
+                       n = i;
+                       break;
+               }
+       }
+       return string(sa.Path[0:n]), nil;
+}
index 9b55f67e6ab5c2ea17c6166a4f6f91f29a63cf55..90d6c245bd379466e9f907e8a7ce58dfda53582b 100644 (file)
@@ -77,3 +77,47 @@ func listenBacklog() int64 {
        return syscall.SOMAXCONN
 }
 
+func unixToSockaddr(name string) (sa1 *syscall.Sockaddr, err os.Error) {
+       sa := new(syscall.SockaddrUnix);
+       n := len(name);
+       if n >= len(sa.Path) || n == 0 {
+               return nil, os.EINVAL;
+       }
+       sa.Family = syscall.AF_UNIX;
+       for i := 0; i < len(name); i++ {
+               sa.Path[i] = name[i];
+       }
+
+       // Special case: @ in first position indicates
+       // an abstract socket, which has no file system
+       // representation and starts with a NUL byte
+       // when talking to the kernel about it.
+       if sa.Path[0] == '@' {
+               sa.Path[0] = 0;
+       }
+
+       return (*syscall.Sockaddr)(unsafe.Pointer(sa)), nil;
+}
+
+func sockaddrToUnix(sa1 *syscall.Sockaddr) (string, os.Error) {
+       if sa1.Family != syscall.AF_UNIX {
+               return "", os.EINVAL;
+       }
+
+       sa := (*syscall.SockaddrUnix)(unsafe.Pointer(sa1));
+
+       // @ special case (see comment in unixToSockaddr).
+       if sa.Path[0] == 0 {
+               // Not friendly to overwrite in place but
+               // okay in an internal function.
+               // The caller doesn't care if we do.
+               sa.Path[0] = '@';
+       }
+
+       // count length of path
+       n := 0;
+       for n < len(sa.Path) && sa.Path[n] != 0 {
+               n++;
+       }
+       return string(sa.Path[0:n]), nil;
+}
similarity index 88%
rename from src/lib/net/tcpserver_test.go
rename to src/lib/net/server_test.go
index 62b67b6fa2ff8e4aab8a1674983e01031e84306d..586b553659921a0d288c83f5d43d13d882c339ef 100644 (file)
@@ -8,6 +8,7 @@ import (
        "io";
        "net";
        "os";
+       "syscall";
        "testing";
 )
 
@@ -83,3 +84,10 @@ func TestTcpServer(t *testing.T) {
        doTest(t, "tcp", "0.0.0.0:9997", "[::ffff:127.0.0.1]:9997");
 }
 
+func TestUnixServer(t *testing.T) {
+       doTest(t, "unix", "/tmp/gotest.net", "/tmp/gotest.net");
+       if syscall.OS == "linux" {
+               // Test abstract unix domain socket, a Linux-ism
+               doTest(t, "unix", "@gotest/net", "@gotest/net");
+       }
+}
index d196abfc6e365552cf18c7d8c90e0764d5f564b2..53f58c9aeea12c991762838bd10a6a90d6f91dc4 100644 (file)
@@ -77,5 +77,7 @@ var (
        EAGAIN Error = Errno(syscall.EAGAIN);
        EDOM Error = Errno(syscall.EDOM);
        ERANGE Error = Errno(syscall.ERANGE);
+       EADDRINUSE Error = Errno(syscall.EADDRINUSE);
+       ECONNREFUSED Error = Errno(syscall.ECONNREFUSED);
 )
 
index ba640e95682f90f0b4e13402cf3f4d2f64216c24..59ca503109ddcefbc3c9a4377f3a189988290f5b 100644 (file)
@@ -16,11 +16,6 @@ import (
 // creation of IPv6 sockets to return EAFNOSUPPORT.
 var SocketDisableIPv6 bool
 
-func SockaddrToSockaddrInet4(s *Sockaddr) *SockaddrInet4;
-func SockaddrToSockaddrInet6(s *Sockaddr) *SockaddrInet6;
-func SockaddrInet4ToSockaddr(s *SockaddrInet4) *Sockaddr;
-func SockaddrInet6ToSockaddr(s *SockaddrInet6) *Sockaddr;
-
 func Socket(domain, proto, typ int64) (ret int64, err int64) {
        if domain == AF_INET6 && SocketDisableIPv6 {
                return -1, EAFNOSUPPORT
index 8dc75dd3a4d2058f1d49dfb6fe61e18500dd9eaa..5fb3c9a9c2d2e425aab3ccc98ae504fc1dc9f4dd 100644 (file)
@@ -16,11 +16,6 @@ import (
 // creation of IPv6 sockets to return EAFNOSUPPORT.
 var SocketDisableIPv6 bool
 
-func SockaddrToSockaddrInet4(s *Sockaddr) *SockaddrInet4;
-func SockaddrToSockaddrInet6(s *Sockaddr) *SockaddrInet6;
-func SockaddrInet4ToSockaddr(s *SockaddrInet4) *Sockaddr;
-func SockaddrInet6ToSockaddr(s *SockaddrInet6) *Sockaddr;
-
 func saLen(s *Sockaddr) int64 {
        switch s.Family {
        case AF_UNIX: