Skip to content

Commit

Permalink
internal/socket: new package
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
cixtor committed May 24, 2017
1 parent d8bd24b commit 50e760f
Show file tree
Hide file tree
Showing 14 changed files with 464 additions and 0 deletions.
31 changes: 31 additions & 0 deletions internal/socket/error_unix.go
Original file line number Diff line number Diff line change
@@ -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
}
26 changes: 26 additions & 0 deletions internal/socket/error_windows.go
Original file line number Diff line number Diff line change
@@ -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
}
66 changes: 66 additions & 0 deletions internal/socket/rawconn.go
Original file line number Diff line number Diff line change
@@ -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)
}
41 changes: 41 additions & 0 deletions internal/socket/reflect.go
Original file line number Diff line number Diff line change
@@ -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))
}
84 changes: 84 additions & 0 deletions internal/socket/socket.go
Original file line number Diff line number Diff line change
@@ -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)
}
46 changes: 46 additions & 0 deletions internal/socket/socket_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
24 changes: 24 additions & 0 deletions internal/socket/sys.go
Original file line number Diff line number Diff line change
@@ -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
}
}
29 changes: 29 additions & 0 deletions internal/socket/sys_linux_386.go
Original file line number Diff line number Diff line change
@@ -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)
}
11 changes: 11 additions & 0 deletions internal/socket/sys_linux_386.s
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit 50e760f

Please sign in to comment.