summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2009-05-13 18:03:41 -0700
committerRuss Cox <rsc@golang.org>2009-05-13 18:03:41 -0700
commit12bab4cf81276e12324517e0943c26414aa3dcd0 (patch)
tree37103bd4ffc93204aed44061fcabd4f01da42461 /src
parenta3a5f024752e2f98a5c2396773175988259b1f0f (diff)
downloadgolang-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.go324
-rw-r--r--src/lib/net/net_darwin.go30
-rw-r--r--src/lib/net/net_linux.go44
-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.go2
-rw-r--r--src/lib/syscall/socket_darwin.go5
-rw-r--r--src/lib/syscall/socket_linux.go5
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: