diff options
author | Russ Cox <rsc@golang.org> | 2009-05-13 18:03:41 -0700 |
---|---|---|
committer | Russ Cox <rsc@golang.org> | 2009-05-13 18:03:41 -0700 |
commit | 12bab4cf81276e12324517e0943c26414aa3dcd0 (patch) | |
tree | 37103bd4ffc93204aed44061fcabd4f01da42461 /src | |
parent | a3a5f024752e2f98a5c2396773175988259b1f0f (diff) | |
download | golang-12bab4cf81276e12324517e0943c26414aa3dcd0.tar.gz |
Unix domain socket support, Linux and Darwin.
R=r
DELTA=534 (353 added, 99 deleted, 82 changed)
OCL=28783
CL=28783
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/net/net.go | 324 | ||||
-rw-r--r-- | src/lib/net/net_darwin.go | 30 | ||||
-rw-r--r-- | src/lib/net/net_linux.go | 44 | ||||
-rw-r--r-- | src/lib/net/server_test.go (renamed from src/lib/net/tcpserver_test.go) | 8 | ||||
-rw-r--r-- | src/lib/os/error.go | 2 | ||||
-rw-r--r-- | src/lib/syscall/socket_darwin.go | 5 | ||||
-rw-r--r-- | src/lib/syscall/socket_linux.go | 5 |
7 files changed, 336 insertions, 82 deletions
diff --git a/src/lib/net/net.go b/src/lib/net/net.go index cd2828592..d1000bfea 100644 --- a/src/lib/net/net.go +++ b/src/lib/net/net.go @@ -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 */ diff --git a/src/lib/net/net_darwin.go b/src/lib/net/net_darwin.go index 2419f6c6d..50e19d614 100644 --- a/src/lib/net/net_darwin.go +++ b/src/lib/net/net_darwin.go @@ -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; +} diff --git a/src/lib/net/net_linux.go b/src/lib/net/net_linux.go index 9b55f67e6..90d6c245b 100644 --- a/src/lib/net/net_linux.go +++ b/src/lib/net/net_linux.go @@ -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; +} diff --git a/src/lib/net/tcpserver_test.go b/src/lib/net/server_test.go index 62b67b6fa..586b55365 100644 --- a/src/lib/net/tcpserver_test.go +++ b/src/lib/net/server_test.go @@ -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"); + } +} diff --git a/src/lib/os/error.go b/src/lib/os/error.go index d196abfc6..53f58c9ae 100644 --- a/src/lib/os/error.go +++ b/src/lib/os/error.go @@ -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); ) diff --git a/src/lib/syscall/socket_darwin.go b/src/lib/syscall/socket_darwin.go index ba640e956..59ca50310 100644 --- a/src/lib/syscall/socket_darwin.go +++ b/src/lib/syscall/socket_darwin.go @@ -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 diff --git a/src/lib/syscall/socket_linux.go b/src/lib/syscall/socket_linux.go index 8dc75dd3a..5fb3c9a9c 100644 --- a/src/lib/syscall/socket_linux.go +++ b/src/lib/syscall/socket_linux.go @@ -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: |