Skip to content

Commit 50e760f

Browse files
committed
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 <[email protected]>
1 parent d8bd24b commit 50e760f

14 files changed

+464
-0
lines changed

internal/socket/error_unix.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
6+
7+
package socket
8+
9+
import "syscall"
10+
11+
var (
12+
errEAGAIN error = syscall.EAGAIN
13+
errEINVAL error = syscall.EINVAL
14+
errENOENT error = syscall.ENOENT
15+
)
16+
17+
// errnoErr returns common boxed Errno values, to prevent allocations
18+
// at runtime.
19+
func errnoErr(errno syscall.Errno) error {
20+
switch errno {
21+
case 0:
22+
return nil
23+
case syscall.EAGAIN:
24+
return errEAGAIN
25+
case syscall.EINVAL:
26+
return errEINVAL
27+
case syscall.ENOENT:
28+
return errENOENT
29+
}
30+
return errno
31+
}

internal/socket/error_windows.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package socket
6+
7+
import "syscall"
8+
9+
var (
10+
errERROR_IO_PENDING error = syscall.ERROR_IO_PENDING
11+
errEINVAL error = syscall.EINVAL
12+
)
13+
14+
// errnoErr returns common boxed Errno values, to prevent allocations
15+
// at runtime.
16+
func errnoErr(errno syscall.Errno) error {
17+
switch errno {
18+
case 0:
19+
return nil
20+
case syscall.ERROR_IO_PENDING:
21+
return errERROR_IO_PENDING
22+
case syscall.EINVAL:
23+
return errEINVAL
24+
}
25+
return errno
26+
}

internal/socket/rawconn.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build go1.9
6+
7+
package socket
8+
9+
import (
10+
"errors"
11+
"net"
12+
"os"
13+
"syscall"
14+
)
15+
16+
// A Conn represents a raw connection.
17+
type Conn struct {
18+
network string
19+
c syscall.RawConn
20+
}
21+
22+
// NewConn returns a new raw connection.
23+
func NewConn(c net.Conn) (*Conn, error) {
24+
var err error
25+
var cc Conn
26+
switch c := c.(type) {
27+
case *net.TCPConn:
28+
cc.network = "tcp"
29+
cc.c, err = c.SyscallConn()
30+
case *net.UDPConn:
31+
cc.network = "udp"
32+
cc.c, err = c.SyscallConn()
33+
case *net.IPConn:
34+
cc.network = "ip"
35+
cc.c, err = c.SyscallConn()
36+
default:
37+
return nil, errors.New("unknown connection type")
38+
}
39+
if err != nil {
40+
return nil, err
41+
}
42+
return &cc, nil
43+
}
44+
45+
func (o *Option) get(c *Conn, b []byte) (int, error) {
46+
var operr error
47+
var n int
48+
fn := func(s uintptr) {
49+
n, operr = getsockopt(s, o.Level, o.Name, b)
50+
}
51+
if err := c.c.Control(fn); err != nil {
52+
return 0, err
53+
}
54+
return n, os.NewSyscallError("getsockopt", operr)
55+
}
56+
57+
func (o *Option) set(c *Conn, b []byte) error {
58+
var operr error
59+
fn := func(s uintptr) {
60+
operr = setsockopt(s, o.Level, o.Name, b)
61+
}
62+
if err := c.c.Control(fn); err != nil {
63+
return err
64+
}
65+
return os.NewSyscallError("setsockopt", operr)
66+
}

internal/socket/reflect.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build !go1.9
6+
7+
package socket
8+
9+
import (
10+
"net"
11+
"os"
12+
13+
"golang.org/x/net/internal/netreflect"
14+
)
15+
16+
// A Conn represents a raw connection.
17+
type Conn struct {
18+
c net.Conn
19+
}
20+
21+
// NewConn returns a new raw connection.
22+
func NewConn(c net.Conn) (*Conn, error) {
23+
return &Conn{c: c}, nil
24+
}
25+
26+
func (o *Option) get(c *Conn, b []byte) (int, error) {
27+
s, err := netreflect.SocketOf(c.c)
28+
if err != nil {
29+
return 0, err
30+
}
31+
n, err := getsockopt(s, o.Level, o.Name, b)
32+
return n, os.NewSyscallError("getsockopt", err)
33+
}
34+
35+
func (o *Option) set(c *Conn, b []byte) error {
36+
s, err := netreflect.SocketOf(c.c)
37+
if err != nil {
38+
return err
39+
}
40+
return os.NewSyscallError("setsockopt", setsockopt(s, o.Level, o.Name, b))
41+
}

internal/socket/socket.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package socket provides a portable interface for socket system
6+
// calls.
7+
package socket // import "golang.org/x/net/internal/socket"
8+
9+
import "errors"
10+
11+
// An Option represents a sticky socket option.
12+
type Option struct {
13+
Level int // level
14+
Name int // name; must be equal or greater than 1
15+
Len int // length of value in bytes; must be equal or greater than 1
16+
}
17+
18+
// Get reads a value for the option from the kernel.
19+
// It returns the number of bytes written into b.
20+
func (o *Option) Get(c *Conn, b []byte) (int, error) {
21+
if o.Name < 1 || o.Len < 1 {
22+
return 0, errors.New("invalid option")
23+
}
24+
if len(b) < o.Len {
25+
return 0, errors.New("short buffer")
26+
}
27+
return o.get(c, b)
28+
}
29+
30+
// GetInt returns an integer value for the option.
31+
//
32+
// The Len field of Option must be either 1 or 4.
33+
func (o *Option) GetInt(c *Conn) (int, error) {
34+
if o.Len != 1 && o.Len != 4 {
35+
return 0, errors.New("invalid option")
36+
}
37+
var b []byte
38+
var bb [4]byte
39+
if o.Len == 1 {
40+
b = bb[:1]
41+
} else {
42+
b = bb[:4]
43+
}
44+
n, err := o.get(c, b)
45+
if err != nil {
46+
return 0, err
47+
}
48+
if n != o.Len {
49+
return 0, errors.New("invalid option length")
50+
}
51+
if o.Len == 1 {
52+
return int(b[0]), nil
53+
}
54+
return int(NativeEndian.Uint32(b[:4])), nil
55+
}
56+
57+
// Set writes the option and value to the kernel.
58+
func (o *Option) Set(c *Conn, b []byte) error {
59+
if o.Name < 1 || o.Len < 1 {
60+
return errors.New("invalid option")
61+
}
62+
if len(b) < o.Len {
63+
return errors.New("short buffer")
64+
}
65+
return o.set(c, b)
66+
}
67+
68+
// SetInt writes the option and value to the kernel.
69+
//
70+
// The Len field of Option must be either 1 or 4.
71+
func (o *Option) SetInt(c *Conn, v int) error {
72+
if o.Len != 1 && o.Len != 4 {
73+
return errors.New("invalid option")
74+
}
75+
var b []byte
76+
if o.Len == 1 {
77+
b = []byte{byte(v)}
78+
} else {
79+
var bb [4]byte
80+
NativeEndian.PutUint32(bb[:o.Len], uint32(v))
81+
b = bb[:4]
82+
}
83+
return o.set(c, b)
84+
}

internal/socket/socket_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build darwin dragonfly freebsd linux netbsd openbsd solaris windows
6+
7+
package socket_test
8+
9+
import (
10+
"net"
11+
"runtime"
12+
"syscall"
13+
"testing"
14+
15+
"golang.org/x/net/internal/nettest"
16+
"golang.org/x/net/internal/socket"
17+
)
18+
19+
func TestSocket(t *testing.T) {
20+
t.Run("Option", func(t *testing.T) {
21+
testSocketOption(t, &socket.Option{Level: syscall.SOL_SOCKET, Name: syscall.SO_RCVBUF, Len: 4})
22+
})
23+
}
24+
25+
func testSocketOption(t *testing.T, so *socket.Option) {
26+
c, err := nettest.NewLocalPacketListener("udp")
27+
if err != nil {
28+
t.Skipf("not supported on %s/%s: %v", runtime.GOOS, runtime.GOARCH, err)
29+
}
30+
defer c.Close()
31+
cc, err := socket.NewConn(c.(net.Conn))
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
const N = 2048
36+
if err := so.SetInt(cc, N); err != nil {
37+
t.Fatal(err)
38+
}
39+
n, err := so.GetInt(cc)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
if n < N {
44+
t.Fatalf("got %d; want greater than or equal to %d", n, N)
45+
}
46+
}

internal/socket/sys.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package socket
6+
7+
import (
8+
"encoding/binary"
9+
"unsafe"
10+
)
11+
12+
// NativeEndian is the machine native endian implementation of
13+
// ByteOrder.
14+
var NativeEndian binary.ByteOrder
15+
16+
func init() {
17+
i := uint32(1)
18+
b := (*[4]byte)(unsafe.Pointer(&i))
19+
if b[0] == 1 {
20+
NativeEndian = binary.LittleEndian
21+
} else {
22+
NativeEndian = binary.BigEndian
23+
}
24+
}

internal/socket/sys_linux_386.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package socket
6+
7+
import (
8+
"syscall"
9+
"unsafe"
10+
)
11+
12+
const (
13+
sysSETSOCKOPT = 0xe
14+
sysGETSOCKOPT = 0xf
15+
)
16+
17+
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) (uintptr, syscall.Errno)
18+
func rawsocketcall(call, a0, a1, a2, a3, a4, a5 uintptr) (uintptr, syscall.Errno)
19+
20+
func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
21+
l := uint32(len(b))
22+
_, errno := socketcall(sysGETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&l)), 0)
23+
return int(l), errnoErr(errno)
24+
}
25+
26+
func setsockopt(s uintptr, level, name int, b []byte) error {
27+
_, errno := socketcall(sysSETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0)
28+
return errnoErr(errno)
29+
}

internal/socket/sys_linux_386.s

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2014 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
#include "textflag.h"
6+
7+
TEXT ·socketcall(SB),NOSPLIT,$0-36
8+
JMP syscall·socketcall(SB)
9+
10+
TEXT ·rawsocketcall(SB),NOSPLIT,$0-36
11+
JMP syscall·rawsocketcall(SB)

0 commit comments

Comments
 (0)