internal/socket: new package
This is a counterpart of https://go-review.googlesource.com/37039.
This change introduces a package that provides a portable interface
for the manipulation of sockets using either syscall.Conn and
syscall.RawConn interfaces or the internal/netreflect package
appropriately.
The package ensures that a package using this works with all supported
versions of the Go standard library.
Updates golang/go#19051.
Change-Id: Ib72ea369e6839e77fed6e35b9aedc364e73c51cb
Reviewed-on: https://go-review.googlesource.com/37035
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/internal/socket/error_unix.go b/internal/socket/error_unix.go
new file mode 100644
index 0000000..93dff91
--- /dev/null
+++ b/internal/socket/error_unix.go
@@ -0,0 +1,31 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package socket
+
+import "syscall"
+
+var (
+ errEAGAIN error = syscall.EAGAIN
+ errEINVAL error = syscall.EINVAL
+ errENOENT error = syscall.ENOENT
+)
+
+// errnoErr returns common boxed Errno values, to prevent allocations
+// at runtime.
+func errnoErr(errno syscall.Errno) error {
+ switch errno {
+ case 0:
+ return nil
+ case syscall.EAGAIN:
+ return errEAGAIN
+ case syscall.EINVAL:
+ return errEINVAL
+ case syscall.ENOENT:
+ return errENOENT
+ }
+ return errno
+}
diff --git a/internal/socket/error_windows.go b/internal/socket/error_windows.go
new file mode 100644
index 0000000..6a6379a
--- /dev/null
+++ b/internal/socket/error_windows.go
@@ -0,0 +1,26 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package socket
+
+import "syscall"
+
+var (
+ errERROR_IO_PENDING error = syscall.ERROR_IO_PENDING
+ errEINVAL error = syscall.EINVAL
+)
+
+// errnoErr returns common boxed Errno values, to prevent allocations
+// at runtime.
+func errnoErr(errno syscall.Errno) error {
+ switch errno {
+ case 0:
+ return nil
+ case syscall.ERROR_IO_PENDING:
+ return errERROR_IO_PENDING
+ case syscall.EINVAL:
+ return errEINVAL
+ }
+ return errno
+}
diff --git a/internal/socket/rawconn.go b/internal/socket/rawconn.go
new file mode 100644
index 0000000..d6871d5
--- /dev/null
+++ b/internal/socket/rawconn.go
@@ -0,0 +1,66 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.9
+
+package socket
+
+import (
+ "errors"
+ "net"
+ "os"
+ "syscall"
+)
+
+// A Conn represents a raw connection.
+type Conn struct {
+ network string
+ c syscall.RawConn
+}
+
+// NewConn returns a new raw connection.
+func NewConn(c net.Conn) (*Conn, error) {
+ var err error
+ var cc Conn
+ switch c := c.(type) {
+ case *net.TCPConn:
+ cc.network = "tcp"
+ cc.c, err = c.SyscallConn()
+ case *net.UDPConn:
+ cc.network = "udp"
+ cc.c, err = c.SyscallConn()
+ case *net.IPConn:
+ cc.network = "ip"
+ cc.c, err = c.SyscallConn()
+ default:
+ return nil, errors.New("unknown connection type")
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &cc, nil
+}
+
+func (o *Option) get(c *Conn, b []byte) (int, error) {
+ var operr error
+ var n int
+ fn := func(s uintptr) {
+ n, operr = getsockopt(s, o.Level, o.Name, b)
+ }
+ if err := c.c.Control(fn); err != nil {
+ return 0, err
+ }
+ return n, os.NewSyscallError("getsockopt", operr)
+}
+
+func (o *Option) set(c *Conn, b []byte) error {
+ var operr error
+ fn := func(s uintptr) {
+ operr = setsockopt(s, o.Level, o.Name, b)
+ }
+ if err := c.c.Control(fn); err != nil {
+ return err
+ }
+ return os.NewSyscallError("setsockopt", operr)
+}
diff --git a/internal/socket/reflect.go b/internal/socket/reflect.go
new file mode 100644
index 0000000..2368eec
--- /dev/null
+++ b/internal/socket/reflect.go
@@ -0,0 +1,41 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.9
+
+package socket
+
+import (
+ "net"
+ "os"
+
+ "golang.org/x/net/internal/netreflect"
+)
+
+// A Conn represents a raw connection.
+type Conn struct {
+ c net.Conn
+}
+
+// NewConn returns a new raw connection.
+func NewConn(c net.Conn) (*Conn, error) {
+ return &Conn{c: c}, nil
+}
+
+func (o *Option) get(c *Conn, b []byte) (int, error) {
+ s, err := netreflect.SocketOf(c.c)
+ if err != nil {
+ return 0, err
+ }
+ n, err := getsockopt(s, o.Level, o.Name, b)
+ return n, os.NewSyscallError("getsockopt", err)
+}
+
+func (o *Option) set(c *Conn, b []byte) error {
+ s, err := netreflect.SocketOf(c.c)
+ if err != nil {
+ return err
+ }
+ return os.NewSyscallError("setsockopt", setsockopt(s, o.Level, o.Name, b))
+}
diff --git a/internal/socket/socket.go b/internal/socket/socket.go
new file mode 100644
index 0000000..366b8da
--- /dev/null
+++ b/internal/socket/socket.go
@@ -0,0 +1,84 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package socket provides a portable interface for socket system
+// calls.
+package socket // import "golang.org/x/net/internal/socket"
+
+import "errors"
+
+// An Option represents a sticky socket option.
+type Option struct {
+ Level int // level
+ Name int // name; must be equal or greater than 1
+ Len int // length of value in bytes; must be equal or greater than 1
+}
+
+// Get reads a value for the option from the kernel.
+// It returns the number of bytes written into b.
+func (o *Option) Get(c *Conn, b []byte) (int, error) {
+ if o.Name < 1 || o.Len < 1 {
+ return 0, errors.New("invalid option")
+ }
+ if len(b) < o.Len {
+ return 0, errors.New("short buffer")
+ }
+ return o.get(c, b)
+}
+
+// GetInt returns an integer value for the option.
+//
+// The Len field of Option must be either 1 or 4.
+func (o *Option) GetInt(c *Conn) (int, error) {
+ if o.Len != 1 && o.Len != 4 {
+ return 0, errors.New("invalid option")
+ }
+ var b []byte
+ var bb [4]byte
+ if o.Len == 1 {
+ b = bb[:1]
+ } else {
+ b = bb[:4]
+ }
+ n, err := o.get(c, b)
+ if err != nil {
+ return 0, err
+ }
+ if n != o.Len {
+ return 0, errors.New("invalid option length")
+ }
+ if o.Len == 1 {
+ return int(b[0]), nil
+ }
+ return int(NativeEndian.Uint32(b[:4])), nil
+}
+
+// Set writes the option and value to the kernel.
+func (o *Option) Set(c *Conn, b []byte) error {
+ if o.Name < 1 || o.Len < 1 {
+ return errors.New("invalid option")
+ }
+ if len(b) < o.Len {
+ return errors.New("short buffer")
+ }
+ return o.set(c, b)
+}
+
+// SetInt writes the option and value to the kernel.
+//
+// The Len field of Option must be either 1 or 4.
+func (o *Option) SetInt(c *Conn, v int) error {
+ if o.Len != 1 && o.Len != 4 {
+ return errors.New("invalid option")
+ }
+ var b []byte
+ if o.Len == 1 {
+ b = []byte{byte(v)}
+ } else {
+ var bb [4]byte
+ NativeEndian.PutUint32(bb[:o.Len], uint32(v))
+ b = bb[:4]
+ }
+ return o.set(c, b)
+}
diff --git a/internal/socket/socket_test.go b/internal/socket/socket_test.go
new file mode 100644
index 0000000..bf3751b
--- /dev/null
+++ b/internal/socket/socket_test.go
@@ -0,0 +1,46 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
+
+package socket_test
+
+import (
+ "net"
+ "runtime"
+ "syscall"
+ "testing"
+
+ "golang.org/x/net/internal/nettest"
+ "golang.org/x/net/internal/socket"
+)
+
+func TestSocket(t *testing.T) {
+ t.Run("Option", func(t *testing.T) {
+ testSocketOption(t, &socket.Option{Level: syscall.SOL_SOCKET, Name: syscall.SO_RCVBUF, Len: 4})
+ })
+}
+
+func testSocketOption(t *testing.T, so *socket.Option) {
+ c, err := nettest.NewLocalPacketListener("udp")
+ if err != nil {
+ t.Skipf("not supported on %s/%s: %v", runtime.GOOS, runtime.GOARCH, err)
+ }
+ defer c.Close()
+ cc, err := socket.NewConn(c.(net.Conn))
+ if err != nil {
+ t.Fatal(err)
+ }
+ const N = 2048
+ if err := so.SetInt(cc, N); err != nil {
+ t.Fatal(err)
+ }
+ n, err := so.GetInt(cc)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n < N {
+ t.Fatalf("got %d; want greater than or equal to %d", n, N)
+ }
+}
diff --git a/internal/socket/sys.go b/internal/socket/sys.go
new file mode 100644
index 0000000..51702b8
--- /dev/null
+++ b/internal/socket/sys.go
@@ -0,0 +1,24 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package socket
+
+import (
+ "encoding/binary"
+ "unsafe"
+)
+
+// NativeEndian is the machine native endian implementation of
+// ByteOrder.
+var NativeEndian binary.ByteOrder
+
+func init() {
+ i := uint32(1)
+ b := (*[4]byte)(unsafe.Pointer(&i))
+ if b[0] == 1 {
+ NativeEndian = binary.LittleEndian
+ } else {
+ NativeEndian = binary.BigEndian
+ }
+}
diff --git a/internal/socket/sys_linux_386.go b/internal/socket/sys_linux_386.go
new file mode 100644
index 0000000..0a9ec1a
--- /dev/null
+++ b/internal/socket/sys_linux_386.go
@@ -0,0 +1,29 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package socket
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const (
+ sysSETSOCKOPT = 0xe
+ sysGETSOCKOPT = 0xf
+)
+
+func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) (uintptr, syscall.Errno)
+func rawsocketcall(call, a0, a1, a2, a3, a4, a5 uintptr) (uintptr, syscall.Errno)
+
+func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
+ l := uint32(len(b))
+ _, errno := socketcall(sysGETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&l)), 0)
+ return int(l), errnoErr(errno)
+}
+
+func setsockopt(s uintptr, level, name int, b []byte) error {
+ _, errno := socketcall(sysSETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0)
+ return errnoErr(errno)
+}
diff --git a/internal/socket/sys_linux_386.s b/internal/socket/sys_linux_386.s
new file mode 100644
index 0000000..93e7d75
--- /dev/null
+++ b/internal/socket/sys_linux_386.s
@@ -0,0 +1,11 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "textflag.h"
+
+TEXT ·socketcall(SB),NOSPLIT,$0-36
+ JMP syscall·socketcall(SB)
+
+TEXT ·rawsocketcall(SB),NOSPLIT,$0-36
+ JMP syscall·rawsocketcall(SB)
diff --git a/internal/socket/sys_solaris.go b/internal/socket/sys_solaris.go
new file mode 100644
index 0000000..707adaa
--- /dev/null
+++ b/internal/socket/sys_solaris.go
@@ -0,0 +1,35 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package socket
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+//go:cgo_import_dynamic libc___xnet_getsockopt __xnet_getsockopt "libsocket.so"
+//go:cgo_import_dynamic libc_setsockopt setsockopt "libsocket.so"
+
+//go:linkname procGetsockopt libc___xnet_getsockopt
+//go:linkname procSetsockopt libc_setsockopt
+
+var (
+ procGetsockopt uintptr
+ procSetsockopt uintptr
+)
+
+func sysvicall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (uintptr, uintptr, syscall.Errno)
+func rawSysvicall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (uintptr, uintptr, syscall.Errno)
+
+func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
+ l := uint32(len(b))
+ _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procGetsockopt)), 5, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&l)), 0)
+ return int(l), errnoErr(errno)
+}
+
+func setsockopt(s uintptr, level, name int, b []byte) error {
+ _, _, errno := sysvicall6(uintptr(unsafe.Pointer(&procSetsockopt)), 5, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0)
+ return errnoErr(errno)
+}
diff --git a/internal/socket/sys_solaris_amd64.s b/internal/socket/sys_solaris_amd64.s
new file mode 100644
index 0000000..a18ac5e
--- /dev/null
+++ b/internal/socket/sys_solaris_amd64.s
@@ -0,0 +1,11 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "textflag.h"
+
+TEXT ·sysvicall6(SB),NOSPLIT,$0-88
+ JMP syscall·sysvicall6(SB)
+
+TEXT ·rawSysvicall6(SB),NOSPLIT,$0-88
+ JMP syscall·rawSysvicall6(SB)
diff --git a/internal/socket/sys_stub.go b/internal/socket/sys_stub.go
new file mode 100644
index 0000000..c3a8e13
--- /dev/null
+++ b/internal/socket/sys_stub.go
@@ -0,0 +1,17 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build nacl plan9
+
+package socket
+
+import "errors"
+
+func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
+ return 0, errors.New("not implemented")
+}
+
+func setsockopt(s uintptr, level, name int, b []byte) error {
+ return errors.New("not implemented")
+}
diff --git a/internal/socket/sys_unix.go b/internal/socket/sys_unix.go
new file mode 100644
index 0000000..c94595e
--- /dev/null
+++ b/internal/socket/sys_unix.go
@@ -0,0 +1,23 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux,!386 netbsd openbsd
+
+package socket
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
+ l := uint32(len(b))
+ _, _, errno := syscall.Syscall6(syscall.SYS_GETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&l)), 0)
+ return int(l), errnoErr(errno)
+}
+
+func setsockopt(s uintptr, level, name int, b []byte) error {
+ _, _, errno := syscall.Syscall6(syscall.SYS_SETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0)
+ return errnoErr(errno)
+}
diff --git a/internal/socket/sys_windows.go b/internal/socket/sys_windows.go
new file mode 100644
index 0000000..bd3bce6
--- /dev/null
+++ b/internal/socket/sys_windows.go
@@ -0,0 +1,20 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package socket
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
+ l := uint32(len(b))
+ err := syscall.Getsockopt(syscall.Handle(s), int32(level), int32(name), (*byte)(unsafe.Pointer(&b[0])), (*int32)(unsafe.Pointer(&l)))
+ return int(l), err
+}
+
+func setsockopt(s uintptr, level, name int, b []byte) error {
+ return syscall.Setsockopt(syscall.Handle(s), int32(level), int32(name), (*byte)(unsafe.Pointer(&b[0])), int32(len(b)))
+}