pax_global_header00006660000000000000000000000064141567126150014522gustar00rootroot0000000000000052 comment=f10f20d90b126972de78cb59707ca32e07cbabac socket-0.1.1/000077500000000000000000000000001415671261500130115ustar00rootroot00000000000000socket-0.1.1/.github/000077500000000000000000000000001415671261500143515ustar00rootroot00000000000000socket-0.1.1/.github/workflows/000077500000000000000000000000001415671261500164065ustar00rootroot00000000000000socket-0.1.1/.github/workflows/static-analysis.yml000066400000000000000000000013621415671261500222430ustar00rootroot00000000000000name: Static Analysis on: push: branches: - main pull_request: branches: - '*' jobs: build: strategy: matrix: go-version: [1.16] runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Download staticcheck run: go get honnef.co/go/tools/cmd/staticcheck - name: Print staticcheck version run: go run honnef.co/go/tools/cmd/staticcheck -version - name: Run staticcheck run: go run honnef.co/go/tools/cmd/staticcheck -- ./... - name: Run go vet run: go vet ./... socket-0.1.1/.github/workflows/test.yml000066400000000000000000000010321415671261500201040ustar00rootroot00000000000000name: Test on: push: branches: - main pull_request: branches: - '*' jobs: build: strategy: fail-fast: false matrix: go-version: [1.16] os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - name: Set up Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Run tests run: go test -race ./... socket-0.1.1/CHANGELOG.md000066400000000000000000000005211415671261500146200ustar00rootroot00000000000000# CHANGELOG ## v0.1.1 - [New API]: `socket.Conn` now has `CloseRead`, `CloseWrite`, and `Shutdown` methods. - [Improvement]: internal rework to more robustly handle various errors. ## v0.1.0 - Initial unstable release. Most functionality has been developed and ported from package [`netlink`](https://github.com/mdlayher/netlink). socket-0.1.1/LICENSE.md000066400000000000000000000020561415671261500144200ustar00rootroot00000000000000# MIT License Copyright (C) 2021 Matt Layher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. socket-0.1.1/README.md000066400000000000000000000020441415671261500142700ustar00rootroot00000000000000# socket [![Test Status](https://github.com/mdlayher/socket/workflows/Test/badge.svg)](https://github.com/mdlayher/socket/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/socket.svg)](https://pkg.go.dev/github.com/mdlayher/socket) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/socket)](https://goreportcard.com/report/github.com/mdlayher/socket) Package `socket` provides a low-level network connection type which integrates with Go's runtime network poller to provide asynchronous I/O and deadline support. MIT Licensed. This package focuses on UNIX-like operating systems which make use of BSD sockets system call APIs. It is meant to be used as a foundation for the creation of operating system-specific socket packages, for socket families such as Linux's `AF_NETLINK`, `AF_PACKET`, or `AF_VSOCK`. This package should not be used directly in end user applications. Any use of package socket should be guarded by build tags, as one would also use when importing the `syscall` or `golang.org/x/sys` packages. socket-0.1.1/accept.go000066400000000000000000000007601415671261500146020ustar00rootroot00000000000000//go:build !dragonfly && !freebsd && !illumos && !linux // +build !dragonfly,!freebsd,!illumos,!linux package socket import ( "fmt" "runtime" "golang.org/x/sys/unix" ) const sysAccept = "accept" // accept wraps accept(2). func accept(fd, flags int) (int, unix.Sockaddr, error) { if flags != 0 { // These operating systems have no support for flags to accept(2). return 0, nil, fmt.Errorf("socket: Conn.Accept flags are ineffective on %s", runtime.GOOS) } return unix.Accept(fd) } socket-0.1.1/accept4.go000066400000000000000000000004501415671261500146620ustar00rootroot00000000000000//go:build dragonfly || freebsd || illumos || linux // +build dragonfly freebsd illumos linux package socket import ( "golang.org/x/sys/unix" ) const sysAccept = "accept4" // accept wraps accept4(2). func accept(fd, flags int) (int, unix.Sockaddr, error) { return unix.Accept4(fd, flags) } socket-0.1.1/conn.go000066400000000000000000000410631415671261500143010ustar00rootroot00000000000000package socket import ( "os" "sync/atomic" "syscall" "time" "golang.org/x/sys/unix" ) // A Conn is a low-level network connection which integrates with Go's runtime // network poller to provide asynchronous I/O and deadline support. type Conn struct { // Indicates whether or not Conn.Close has been called. Must be accessed // atomically. Atomics definitions must come first in the Conn struct. closed uint32 // A unique name for the Conn which is also associated with derived file // descriptors such as those created by accept(2). name string // Provides access to the underlying file registered with the runtime // network poller, and arbitrary raw I/O calls. fd *os.File rc syscall.RawConn } // A Config contains options for a Conn. type Config struct { // NetNS specifies the Linux network namespace the Conn will operate in. // This option is unsupported on other operating systems. // // If set (non-zero), Conn will enter the specified network namespace and an // error will occur in Socket if the operation fails. // // If not set (zero), a best-effort attempt will be made to enter the // network namespace of the calling thread: this means that any changes made // to the calling thread's network namespace will also be reflected in Conn. // If this operation fails (due to lack of permissions or because network // namespaces are disabled by kernel configuration), Socket will not return // an error, and the Conn will operate in the default network namespace of // the process. This enables non-privileged use of Conn in applications // which do not require elevated privileges. // // Entering a network namespace is a privileged operation (root or // CAP_SYS_ADMIN are required), and most applications should leave this set // to 0. NetNS int } // High-level methods which provide convenience over raw system calls. // Close closes the underlying file descriptor for the Conn, which also causes // all in-flight I/O operations to immediately unblock and return errors. Any // subsequent uses of Conn will result in EBADF. func (c *Conn) Close() error { // The caller has expressed an intent to close the socket, so immediately // increment s.closed to force further calls to result in EBADF before also // closing the file descriptor to unblock any outstanding operations. // // Because other operations simply check for s.closed != 0, we will permit // double Close, which would increment s.closed beyond 1. if atomic.AddUint32(&c.closed, 1) != 1 { // Multiple Close calls. return nil } return os.NewSyscallError("close", c.fd.Close()) } // CloseRead shuts down the reading side of the Conn. Most callers should just // use Close. func (c *Conn) CloseRead() error { return c.Shutdown(unix.SHUT_RD) } // CloseWrite shuts down the writing side of the Conn. Most callers should just // use Close. func (c *Conn) CloseWrite() error { return c.Shutdown(unix.SHUT_WR) } // Read implements io.Reader by reading directly from the underlying file // descriptor. func (c *Conn) Read(b []byte) (int, error) { return c.fd.Read(b) } // Write implements io.Writer by writing directly to the underlying file // descriptor. func (c *Conn) Write(b []byte) (int, error) { return c.fd.Write(b) } // SetDeadline sets both the read and write deadlines associated with the Conn. func (c *Conn) SetDeadline(t time.Time) error { return c.fd.SetDeadline(t) } // SetReadDeadline sets the read deadline associated with the Conn. func (c *Conn) SetReadDeadline(t time.Time) error { return c.fd.SetReadDeadline(t) } // SetWriteDeadline sets the write deadline associated with the Conn. func (c *Conn) SetWriteDeadline(t time.Time) error { return c.fd.SetWriteDeadline(t) } // ReadBuffer gets the size of the operating system's receive buffer associated // with the Conn. func (c *Conn) ReadBuffer() (int, error) { return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF) } // WriteBuffer gets the size of the operating system's transmit buffer // associated with the Conn. func (c *Conn) WriteBuffer() (int, error) { return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF) } // SetReadBuffer sets the size of the operating system's receive buffer // associated with the Conn. // // When called with elevated privileges on Linux, the SO_RCVBUFFORCE option will // be used to override operating system limits. Otherwise SO_RCVBUF is used // (which obeys operating system limits). func (c *Conn) SetReadBuffer(bytes int) error { return c.setReadBuffer(bytes) } // SetWriteBuffer sets the size of the operating system's transmit buffer // associated with the Conn. // // When called with elevated privileges on Linux, the SO_SNDBUFFORCE option will // be used to override operating system limits. Otherwise SO_SNDBUF is used // (which obeys operating system limits). func (c *Conn) SetWriteBuffer(bytes int) error { return c.setWriteBuffer(bytes) } // SyscallConn returns a raw network connection. This implements the // syscall.Conn interface. // // SyscallConn is intended for advanced use cases, such as getting and setting // arbitrary socket options using the socket's file descriptor. If possible, // those operations should be performed using methods on Conn instead. // // Once invoked, it is the caller's responsibility to ensure that operations // performed using Conn and the syscall.RawConn do not conflict with each other. func (c *Conn) SyscallConn() (syscall.RawConn, error) { if atomic.LoadUint32(&c.closed) != 0 { return nil, os.NewSyscallError("syscallconn", unix.EBADF) } // TODO(mdlayher): mutex or similar to enforce syscall.RawConn contract of // FD remaining valid for duration of calls? return c.rc, nil } // Socket wraps the socket(2) system call to produce a Conn. domain, typ, and // proto are passed directly to socket(2), and name should be a unique name for // the socket type such as "netlink" or "vsock". // // The cfg parameter specifies optional configuration for the Conn. If nil, no // additional configuration will be applied. // // If the operating system supports SOCK_CLOEXEC and SOCK_NONBLOCK, they are // automatically applied to typ to mirror the standard library's socket flag // behaviors. func Socket(domain, typ, proto int, name string, cfg *Config) (*Conn, error) { if cfg == nil { cfg = &Config{} } if cfg.NetNS == 0 { // Non-Linux or no network namespace. return socket(domain, typ, proto, name) } // Linux only: create Conn in the specified network namespace. return withNetNS(cfg.NetNS, func() (*Conn, error) { return socket(domain, typ, proto, name) }) } // socket is the internal, cross-platform entry point for socket(2). func socket(domain, typ, proto int, name string) (*Conn, error) { var ( fd int err error ) for { fd, err = unix.Socket(domain, typ|socketFlags, proto) switch { case err == nil: // Some OSes already set CLOEXEC with typ. if !flagCLOEXEC { unix.CloseOnExec(fd) } // No error, prepare the Conn. return newConn(fd, name) case !ready(err): // System call interrupted or not ready, try again. continue case err == unix.EINVAL, err == unix.EPROTONOSUPPORT: // On Linux, SOCK_NONBLOCK and SOCK_CLOEXEC were introduced in // 2.6.27. On FreeBSD, both flags were introduced in FreeBSD 10. // EINVAL and EPROTONOSUPPORT check for earlier versions of these // OSes respectively. // // Mirror what the standard library does when creating file // descriptors: avoid racing a fork/exec with the creation of new // file descriptors, so that child processes do not inherit socket // file descriptors unexpectedly. // // For a more thorough explanation, see similar work in the Go tree: // func sysSocket in net/sock_cloexec.go, as well as the detailed // comment in syscall/exec_unix.go. syscall.ForkLock.RLock() fd, err = unix.Socket(domain, typ, proto) if err != nil { syscall.ForkLock.RUnlock() return nil, os.NewSyscallError("socket", err) } unix.CloseOnExec(fd) syscall.ForkLock.RUnlock() return newConn(fd, name) default: // Unhandled error. return nil, os.NewSyscallError("socket", err) } } } // TODO(mdlayher): consider exporting newConn as New? // newConn wraps an existing file descriptor to create a Conn. name should be a // unique name for the socket type such as "netlink" or "vsock". func newConn(fd int, name string) (*Conn, error) { // All Conn I/O is nonblocking for integration with Go's runtime network // poller. Depending on the OS this might already be set but it can't hurt // to set it again. if err := unix.SetNonblock(fd, true); err != nil { return nil, os.NewSyscallError("setnonblock", err) } // os.NewFile registers the non-blocking file descriptor with the runtime // poller, which is then used for most subsequent operations except those // that require raw I/O via SyscallConn. // // See also: https://golang.org/pkg/os/#NewFile f := os.NewFile(uintptr(fd), name) rc, err := f.SyscallConn() if err != nil { return nil, err } return &Conn{ name: name, fd: f, rc: rc, }, nil } // Low-level methods which provide raw system call access. // Accept wraps accept(2) or accept4(2) depending on the operating system, but // returns a Conn for the accepted connection rather than a raw file descriptor. // // If the operating system supports accept4(2) (which allows flags), // SOCK_CLOEXEC and SOCK_NONBLOCK are automatically applied to flags to mirror // the standard library's socket flag behaviors. // // If the operating system only supports accept(2) (which does not allow flags) // and flags is not zero, an error will be returned. func (c *Conn) Accept(flags int) (*Conn, unix.Sockaddr, error) { var ( nfd int sa unix.Sockaddr err error ) doErr := c.read(sysAccept, func(fd int) error { // Either accept(2) or accept4(2) depending on the OS. nfd, sa, err = accept(fd, flags|socketFlags) return err }) if doErr != nil { return nil, nil, doErr } if err != nil { // sysAccept is either "accept" or "accept4" depending on the OS. return nil, nil, os.NewSyscallError(sysAccept, err) } // Successfully accepted a connection, wrap it in a Conn for use by the // caller. ac, err := newConn(nfd, c.name) if err != nil { return nil, nil, err } return ac, sa, nil } // Bind wraps bind(2). func (c *Conn) Bind(sa unix.Sockaddr) error { const op = "bind" var err error doErr := c.control(op, func(fd int) error { err = unix.Bind(fd, sa) return err }) if doErr != nil { return doErr } return os.NewSyscallError(op, err) } // Connect wraps connect(2). func (c *Conn) Connect(sa unix.Sockaddr) error { const op = "connect" var err error doErr := c.write(op, func(fd int) error { err = unix.Connect(fd, sa) return err }) if doErr != nil { return doErr } if err == unix.EISCONN { // EISCONN is reported if the socket is already established and should // not be treated as an error. // - Darwin reports this for at least TCP sockets // - Linux reports this for at least AF_VSOCK sockets return nil } return os.NewSyscallError(op, err) } // Getsockname wraps getsockname(2). func (c *Conn) Getsockname() (unix.Sockaddr, error) { const op = "getsockname" var ( sa unix.Sockaddr err error ) doErr := c.control(op, func(fd int) error { sa, err = unix.Getsockname(fd) return err }) if doErr != nil { return nil, doErr } return sa, os.NewSyscallError(op, err) } // GetsockoptInt wraps getsockopt(2) for integer values. func (c *Conn) GetsockoptInt(level, opt int) (int, error) { const op = "getsockopt" var ( value int err error ) doErr := c.control(op, func(fd int) error { value, err = unix.GetsockoptInt(fd, level, opt) return err }) if doErr != nil { return 0, doErr } return value, os.NewSyscallError(op, err) } // Listen wraps listen(2). func (c *Conn) Listen(n int) error { const op = "listen" var err error doErr := c.control(op, func(fd int) error { err = unix.Listen(fd, n) return err }) if doErr != nil { return doErr } return os.NewSyscallError(op, err) } // Recvmsg wraps recvmsg(2). func (c *Conn) Recvmsg(p, oob []byte, flags int) (int, int, int, unix.Sockaddr, error) { const op = "recvmsg" var ( n, oobn, recvflags int from unix.Sockaddr err error ) doErr := c.read(op, func(fd int) error { n, oobn, recvflags, from, err = unix.Recvmsg(fd, p, oob, flags) return err }) if doErr != nil { return 0, 0, 0, nil, doErr } return n, oobn, recvflags, from, os.NewSyscallError(op, err) } // Recvfrom wraps recvfrom(2) func (c *Conn) Recvfrom(p []byte, flags int) (int, unix.Sockaddr, error) { const op = "recvfrom" var ( n int addr unix.Sockaddr err error ) doErr := c.read(op, func(fd int) error { n, addr, err = unix.Recvfrom(fd, p, flags) return err }) if doErr != nil { return 0, nil, doErr } return n, addr, os.NewSyscallError(op, err) } // Sendmsg wraps sendmsg(2). func (c *Conn) Sendmsg(p, oob []byte, to unix.Sockaddr, flags int) error { const op = "sendmsg" var err error doErr := c.write(op, func(fd int) error { err = unix.Sendmsg(fd, p, oob, to, flags) return err }) if doErr != nil { return doErr } return os.NewSyscallError(op, err) } // Sendto wraps sendto(2). func (c *Conn) Sendto(b []byte, to unix.Sockaddr, flags int) error { const op = "sendto" var err error doErr := c.write(op, func(fd int) error { err = unix.Sendto(fd, b, flags, to) return err }) if doErr != nil { return doErr } return os.NewSyscallError(op, err) } // SetsockoptInt wraps setsockopt(2) for integer values. func (c *Conn) SetsockoptInt(level, opt, value int) error { const op = "setsockopt" var err error doErr := c.control(op, func(fd int) error { err = unix.SetsockoptInt(fd, level, opt, value) return err }) if doErr != nil { return doErr } return os.NewSyscallError(op, err) } // Shutdown wraps shutdown(2). func (c *Conn) Shutdown(how int) error { const op = "shutdown" var err error doErr := c.control(op, func(fd int) error { err = unix.Shutdown(fd, how) return err }) if doErr != nil { return doErr } return os.NewSyscallError(op, err) } // Conn low-level read/write/control functions. These functions mirror the // syscall.RawConn APIs but the input closures return errors rather than // booleans. Any syscalls invoked within f should return their error to allow // the Conn to check for readiness with the runtime network poller, or to retry // operations which may have been interrupted by EINTR or similar. // // Note that errors from the input closure functions are not propagated to the // error return values of read/write/control, and the caller is still // responsible for error handling. // read executes f, a read function, against the associated file descriptor. // op is used to create an *os.SyscallError if the file descriptor is closed. func (c *Conn) read(op string, f func(fd int) error) error { if atomic.LoadUint32(&c.closed) != 0 { return os.NewSyscallError(op, unix.EBADF) } return c.rc.Read(func(fd uintptr) bool { return ready(f(int(fd))) }) } // write executes f, a write function, against the associated file descriptor. // op is used to create an *os.SyscallError if the file descriptor is closed. func (c *Conn) write(op string, f func(fd int) error) error { if atomic.LoadUint32(&c.closed) != 0 { return os.NewSyscallError(op, unix.EBADF) } return c.rc.Write(func(fd uintptr) bool { return ready(f(int(fd))) }) } // control executes f, a control function, against the associated file // descriptor. op is used to create an *os.SyscallError if the file descriptor // is closed. func (c *Conn) control(op string, f func(fd int) error) error { if atomic.LoadUint32(&c.closed) != 0 { return os.NewSyscallError(op, unix.EBADF) } return c.rc.Control(func(fd uintptr) { // Repeatedly attempt the syscall(s) invoked by f until completion is // indicated by the return value of ready. for { if ready(f(int(fd))) { return } } }) } // ready indicates readiness based on the value of err. func ready(err error) bool { // When a socket is in non-blocking mode, we might see a variety of errors: // - EAGAIN: most common case for a socket read not being ready // - EALREADY: reported on connect after EINPROGRESS for AF_VSOCK at least // - EINPROGRESS: reported by some sockets when first calling connect // - EINTR: system call interrupted, more frequently occurs in Go 1.14+ // because goroutines can be asynchronously preempted // // Return false to let the poller wait for readiness. See the source code // for internal/poll.FD.RawRead for more details. switch err { case unix.EAGAIN, unix.EALREADY, unix.EINPROGRESS, unix.EINTR: // Not ready. return false default: // Ready regardless of whether there was an error or no error. return true } } socket-0.1.1/conn_linux.go000066400000000000000000000040451415671261500155170ustar00rootroot00000000000000//go:build linux // +build linux package socket import ( "os" "unsafe" "golang.org/x/net/bpf" "golang.org/x/sys/unix" ) // SetBPF attaches an assembled BPF program to a Conn. func (c *Conn) SetBPF(filter []bpf.RawInstruction) error { // We can't point to the first instruction in the array if no instructions // are present. if len(filter) == 0 { return os.NewSyscallError("setsockopt", unix.EINVAL) } prog := unix.SockFprog{ Len: uint16(len(filter)), Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])), } return c.SetsockoptSockFprog(unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, &prog) } // RemoveBPF removes a BPF filter from a Conn. func (c *Conn) RemoveBPF() error { // 0 argument is ignored. return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0) } // SetsockoptSockFprog wraps setsockopt(2) for unix.SockFprog values. func (c *Conn) SetsockoptSockFprog(level, opt int, fprog *unix.SockFprog) error { const op = "setsockopt" var err error doErr := c.control(op, func(fd int) error { err = unix.SetsockoptSockFprog(fd, level, opt, fprog) return err }) if doErr != nil { return doErr } return os.NewSyscallError(op, err) } // GetSockoptTpacketStats wraps getsockopt(2) for getting TpacketStats func (c *Conn) GetSockoptTpacketStats(level, name int) (*unix.TpacketStats, error) { const op = "getsockopt" var ( stats *unix.TpacketStats err error ) doErr := c.control(op, func(fd int) error { stats, err = unix.GetsockoptTpacketStats(fd, level, name) return err }) if doErr != nil { return stats, doErr } return stats, os.NewSyscallError(op, err) } // GetSockoptTpacketStatsV3 wraps getsockopt(2) for getting TpacketStatsV3 func (c *Conn) GetSockoptTpacketStatsV3(level, name int) (*unix.TpacketStatsV3, error) { const op = "getsockopt" var ( stats *unix.TpacketStatsV3 err error ) doErr := c.control(op, func(fd int) error { stats, err = unix.GetsockoptTpacketStatsV3(fd, level, name) return err }) if doErr != nil { return stats, doErr } return stats, os.NewSyscallError(op, err) } socket-0.1.1/conn_linux_test.go000066400000000000000000000063351415671261500165620ustar00rootroot00000000000000//go:build linux // +build linux package socket_test import ( "errors" "fmt" "net" "os" "runtime" "testing" "github.com/google/go-cmp/cmp" "github.com/mdlayher/socket" "github.com/mdlayher/socket/internal/sockettest" "golang.org/x/sync/errgroup" "golang.org/x/sys/unix" ) func TestLinuxConnBuffers(t *testing.T) { // This test isn't necessarily Linux-specific but it's easiest to verify on // Linux because we can rely on the kernel's documented buffer size // manipulation behavior. c, err := socket.Socket(unix.AF_INET, unix.SOCK_STREAM, 0, "tcpv4", nil) if err != nil { t.Fatalf("failed to open socket: %v", err) } defer c.Close() const ( set = 8192 // Per socket(7): // // "The kernel doubles this value (to allow space for // book‐keeping overhead) when it is set using setsockopt(2), // and this doubled value is returned by getsockopt(2)."" want = set * 2 ) if err := c.SetReadBuffer(set); err != nil { t.Fatalf("failed to set read buffer size: %v", err) } if err := c.SetWriteBuffer(set); err != nil { t.Fatalf("failed to set write buffer size: %v", err) } // Now that we've set the buffers, we can check the size by asking the // kernel using SyscallConn and getsockopt. rcv, err := c.ReadBuffer() if err != nil { t.Fatalf("failed to get read buffer size: %v", err) } snd, err := c.WriteBuffer() if err != nil { t.Fatalf("failed to get write buffer size: %v", err) } if diff := cmp.Diff(want, rcv); diff != "" { t.Fatalf("unexpected read buffer size (-want +got):\n%s", diff) } if diff := cmp.Diff(want, snd); diff != "" { t.Fatalf("unexpected write buffer size (-want +got):\n%s", diff) } } func TestLinuxNetworkNamespaces(t *testing.T) { l, err := sockettest.Listen(0, nil) if err != nil { t.Fatalf("failed to create listener: %v", err) } defer l.Close() addrC := make(chan net.Addr, 1) var eg errgroup.Group eg.Go(func() error { // We are poisoning this thread by creating a new anonymous network // namespace. Do not unlock the OS thread so that the runtime will kill // this thread when the goroutine exits. runtime.LockOSThread() if err := unix.Unshare(unix.CLONE_NEWNET); err != nil { // Explicit wrap to check for permission denied. return fmt.Errorf("failed to unshare network namespace: %w", err) } ns, err := socket.ThreadNetNS() if err != nil { return fmt.Errorf("failed to get listener thread's network namespace: %v", err) } // This OS thread has been moved to a different network namespace and // thus we should also be able to start a listener on the same port. l, err := sockettest.Listen( l.Addr().(*net.TCPAddr).Port, &socket.Config{NetNS: ns.FD()}, ) if err != nil { return fmt.Errorf("failed to create listener in network namespace: %v", err) } defer l.Close() addrC <- l.Addr() return nil }) if err := eg.Wait(); err != nil { if errors.Is(err, os.ErrPermission) { t.Skipf("skipping, permission denied: %v", err) } t.Fatalf("failed to run listener thread: %v", err) } select { case addr := <-addrC: if diff := cmp.Diff(l.Addr(), addr); diff != "" { t.Fatalf("unexpected network address (-want +got):\n%s", diff) } default: t.Fatal("listener thread did not return its local address") } } socket-0.1.1/conn_test.go000066400000000000000000000100251415671261500153320ustar00rootroot00000000000000package socket_test import ( "bytes" "io" "io/ioutil" "net" "runtime" "sync" "testing" "time" "github.com/mdlayher/socket/internal/sockettest" "golang.org/x/net/nettest" ) func TestConn(t *testing.T) { nettest.TestConn(t, makePipe) // Our own extensions to TestConn. t.Run("CloseReadWrite", func(t *testing.T) { timeoutWrapper(t, makePipe, testCloseReadWrite) }) } // Use our TCP net.Listener and net.Conn implementations backed by *socket.Conn // and run compliance tests with nettest.TestConn. // // This nettest.MakePipe function is adapted from nettest's own tests: // https://github.com/golang/net/blob/master/nettest/conntest_test.go // // 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. func makePipe() (c1, c2 net.Conn, stop func(), err error) { ln, err := sockettest.Listen(0, nil) if err != nil { return nil, nil, nil, err } // Start a connection between two endpoints. var err1, err2 error done := make(chan bool) go func() { c2, err2 = ln.Accept() close(done) }() c1, err1 = sockettest.Dial(ln.Addr(), nil) <-done stop = func() { if err1 == nil { c1.Close() } if err2 == nil { c2.Close() } ln.Close() } switch { case err1 != nil: stop() return nil, nil, nil, err1 case err2 != nil: stop() return nil, nil, nil, err2 default: return c1, c2, stop, nil } } // Copied from x/net/nettest, pending acceptance of: // https://go-review.googlesource.com/c/net/+/372815 type connTester func(t *testing.T, c1, c2 net.Conn) func timeoutWrapper(t *testing.T, mp nettest.MakePipe, f connTester) { t.Helper() c1, c2, stop, err := mp() if err != nil { t.Fatalf("unable to make pipe: %v", err) } var once sync.Once defer once.Do(func() { stop() }) timer := time.AfterFunc(time.Minute, func() { once.Do(func() { t.Error("test timed out; terminating pipe") stop() }) }) defer timer.Stop() f(t, c1, c2) } // testCloseReadWrite tests that net.Conns which also implement the optional // CloseRead and CloseWrite methods can be half-closed correctly. func testCloseReadWrite(t *testing.T, c1, c2 net.Conn) { // TODO(mdlayher): investigate why Mac/Windows errors are so different. if runtime.GOOS != "linux" { t.Skip("skipping, not supported on non-Linux platforms") } type closerConn interface { net.Conn CloseRead() error CloseWrite() error } cc1, ok1 := c1.(closerConn) cc2, ok2 := c2.(closerConn) if !ok1 || !ok2 { // Both c1 and c2 must implement closerConn to proceed. return } var wg sync.WaitGroup wg.Add(2) defer wg.Wait() go func() { defer wg.Done() // Writing succeeds at first but should result in a permanent "broken // pipe" error after closing the write side of the net.Conn. b := make([]byte, 64) if err := chunkedCopy(cc1, bytes.NewReader(b)); err != nil { t.Errorf("unexpected initial cc1.Write error: %v", err) } if err := cc1.CloseWrite(); err != nil { t.Errorf("unexpected cc1.CloseWrite error: %v", err) } _, err := cc1.Write(b) if nerr, ok := err.(net.Error); !ok || nerr.Temporary() { t.Errorf("unexpected final cc1.Write error: %v", err) } }() go func() { defer wg.Done() // Reading succeeds at first but should result in an EOF error after // closing the read side of the net.Conn. if err := chunkedCopy(ioutil.Discard, cc2); err != nil { t.Errorf("unexpected initial cc2.Read error: %v", err) } if err := cc2.CloseRead(); err != nil { t.Errorf("unexpected cc2.CloseRead error: %v", err) } if _, err := cc2.Read(make([]byte, 64)); err != io.EOF { t.Errorf("unexpected final cc2.Read error: %v", err) } }() } // chunkedCopy copies from r to w in fixed-width chunks to avoid // causing a Write that exceeds the maximum packet size for packet-based // connections like "unixpacket". // We assume that the maximum packet size is at least 1024. func chunkedCopy(w io.Writer, r io.Reader) error { b := make([]byte, 1024) _, err := io.CopyBuffer(struct{ io.Writer }{w}, struct{ io.Reader }{r}, b) return err } socket-0.1.1/doc.go000066400000000000000000000012621415671261500141060ustar00rootroot00000000000000// Package socket provides a low-level network connection type which integrates // with Go's runtime network poller to provide asynchronous I/O and deadline // support. // // This package focuses on UNIX-like operating systems which make use of BSD // sockets system call APIs. It is meant to be used as a foundation for the // creation of operating system-specific socket packages, for socket families // such as Linux's AF_NETLINK, AF_PACKET, or AF_VSOCK. This package should not // be used directly in end user applications. // // Any use of package socket should be guarded by build tags, as one would also // use when importing the syscall or golang.org/x/sys packages. package socket socket-0.1.1/export_linux_test.go000066400000000000000000000005071415671261500171410ustar00rootroot00000000000000//go:build linux // +build linux package socket // A NetNS is an exported wrapper for netNS for tests. type NetNS struct{ *netNS } // ThreadNetNS is an exported wrapper for threadNetNS for tests. func ThreadNetNS() (*NetNS, error) { ns, err := threadNetNS() if err != nil { return nil, err } return &NetNS{ns}, nil } socket-0.1.1/go.mod000066400000000000000000000003711415671261500141200ustar00rootroot00000000000000module github.com/mdlayher/socket go 1.17 require ( github.com/google/go-cmp v0.5.6 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 ) socket-0.1.1/go.sum000066400000000000000000000027541415671261500141540ustar00rootroot00000000000000github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= socket-0.1.1/internal/000077500000000000000000000000001415671261500146255ustar00rootroot00000000000000socket-0.1.1/internal/sockettest/000077500000000000000000000000001415671261500170155ustar00rootroot00000000000000socket-0.1.1/internal/sockettest/sockettest.go000066400000000000000000000075411415671261500215430ustar00rootroot00000000000000// Package sockettest implements net.Listener and net.Conn types based on // *socket.Conn for use in the package's tests. package sockettest import ( "fmt" "io" "net" "time" "github.com/mdlayher/socket" "golang.org/x/sys/unix" ) type listener struct { addr *net.TCPAddr c *socket.Conn } // Listen creates an IPv6 TCP net.Listener backed by a *socket.Conn on the // specified port with optional configuration. func Listen(port int, cfg *socket.Config) (net.Listener, error) { c, err := socket.Socket(unix.AF_INET6, unix.SOCK_STREAM, 0, "tcpv6-server", cfg) if err != nil { return nil, fmt.Errorf("failed to open socket: %v", err) } // Be sure to close the Conn if any of the system calls fail before we // return the Conn to the caller. if err := c.Bind(&unix.SockaddrInet6{Port: port}); err != nil { _ = c.Close() return nil, fmt.Errorf("failed to bind: %v", err) } if err := c.Listen(unix.SOMAXCONN); err != nil { _ = c.Close() return nil, fmt.Errorf("failed to listen: %v", err) } sa, err := c.Getsockname() if err != nil { _ = c.Close() return nil, fmt.Errorf("failed to getsockname: %v", err) } return &listener{ addr: newTCPAddr(sa), c: c, }, nil } func (l *listener) Addr() net.Addr { return l.addr } func (l *listener) Close() error { return l.c.Close() } func (l *listener) Accept() (net.Conn, error) { // SOCK_CLOEXEC and SOCK_NONBLOCK set automatically by Accept when possible. c, rsa, err := l.c.Accept(0) if err != nil { return nil, err } lsa, err := c.Getsockname() if err != nil { // Don't leak the Conn if the system call fails. _ = c.Close() return nil, err } return &conn{ local: newTCPAddr(lsa), remote: newTCPAddr(rsa), c: c, }, nil } type conn struct { local, remote *net.TCPAddr c *socket.Conn } // Dial creates an IPv6 TCP net.Conn backed by a *socket.Conn with optional // configuration. func Dial(addr net.Addr, cfg *socket.Config) (net.Conn, error) { ta, ok := addr.(*net.TCPAddr) if !ok { return nil, fmt.Errorf("expected *net.TCPAddr, but got: %T", addr) } c, err := socket.Socket(unix.AF_INET6, unix.SOCK_STREAM, 0, "tcpv6-client", cfg) if err != nil { return nil, fmt.Errorf("failed to open socket: %v", err) } var sa unix.SockaddrInet6 copy(sa.Addr[:], ta.IP) sa.Port = ta.Port // Be sure to close the Conn if any of the system calls fail before we // return the Conn to the caller. if err := c.Connect(&sa); err != nil { _ = c.Close() return nil, fmt.Errorf("failed to connect: %v", err) } lsa, err := c.Getsockname() if err != nil { _ = c.Close() return nil, err } return &conn{ local: newTCPAddr(lsa), remote: ta, c: c, }, nil } func (c *conn) Close() error { return c.c.Close() } func (c *conn) CloseRead() error { return c.c.CloseRead() } func (c *conn) CloseWrite() error { return c.c.CloseWrite() } func (c *conn) LocalAddr() net.Addr { return c.local } func (c *conn) RemoteAddr() net.Addr { return c.remote } func (c *conn) SetDeadline(t time.Time) error { return c.c.SetDeadline(t) } func (c *conn) SetReadDeadline(t time.Time) error { return c.c.SetReadDeadline(t) } func (c *conn) SetWriteDeadline(t time.Time) error { return c.c.SetWriteDeadline(t) } func (c *conn) Read(b []byte) (int, error) { n, err := c.c.Read(b) return n, opError("read", err) } func (c *conn) Write(b []byte) (int, error) { n, err := c.c.Write(b) return n, opError("write", err) } func opError(op string, err error) error { // This is still a bit simplistic but sufficient for nettest.TestConn. switch err { case nil: return nil case io.EOF: return io.EOF default: return &net.OpError{Op: op, Err: err} } } func newTCPAddr(sa unix.Sockaddr) *net.TCPAddr { sa6 := sa.(*unix.SockaddrInet6) return &net.TCPAddr{ IP: sa6.Addr[:], Port: sa6.Port, } } socket-0.1.1/netns_linux.go000066400000000000000000000076751415671261500157250ustar00rootroot00000000000000//go:build linux // +build linux package socket import ( "errors" "fmt" "os" "runtime" "golang.org/x/sync/errgroup" "golang.org/x/sys/unix" ) // errNetNSDisabled is returned when network namespaces are unavailable on // a given system. var errNetNSDisabled = errors.New("socket: Linux network namespaces are not enabled on this system") // withNetNS invokes fn within the context of the network namespace specified by // fd, while also managing the logic required to safely do so by manipulating // thread-local state. func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) { var ( eg errgroup.Group conn *Conn ) eg.Go(func() error { // Retrieve and store the calling OS thread's network namespace so the // thread can be reassigned to it after creating a socket in another network // namespace. runtime.LockOSThread() ns, err := threadNetNS() if err != nil { // No thread-local manipulation, unlock. runtime.UnlockOSThread() return err } defer ns.Close() // Beyond this point, the thread's network namespace is poisoned. Do not // unlock the OS thread until all network namespace manipulation completes // to avoid returning to the caller with altered thread-local state. // Assign the current OS thread the goroutine is locked to to the given // network namespace. if err := ns.Set(fd); err != nil { return err } // Attempt Conn creation and unconditionally restore the original namespace. c, err := fn() if nerr := ns.Restore(); nerr != nil { // Failed to restore original namespace. Return an error and allow the // runtime to terminate the thread. if err == nil { _ = c.Close() } return nerr } // No more thread-local state manipulation; return the new Conn. runtime.UnlockOSThread() conn = c return nil }) if err := eg.Wait(); err != nil { return nil, err } return conn, nil } // A netNS is a handle that can manipulate network namespaces. // // Operations performed on a netNS must use runtime.LockOSThread before // manipulating any network namespaces. type netNS struct { // The handle to a network namespace. f *os.File // Indicates if network namespaces are disabled on this system, and thus // operations should become a no-op or return errors. disabled bool } // threadNetNS constructs a netNS using the network namespace of the calling // thread. If the namespace is not the default namespace, runtime.LockOSThread // should be invoked first. func threadNetNS() (*netNS, error) { return fileNetNS(fmt.Sprintf("/proc/self/task/%d/ns/net", unix.Gettid())) } // fileNetNS opens file and creates a netNS. fileNetNS should only be called // directly in tests. func fileNetNS(file string) (*netNS, error) { f, err := os.Open(file) switch { case err == nil: return &netNS{f: f}, nil case os.IsNotExist(err): // Network namespaces are not enabled on this system. Use this signal // to return errors elsewhere if the caller explicitly asks for a // network namespace to be set. return &netNS{disabled: true}, nil default: return nil, err } } // Close releases the handle to a network namespace. func (n *netNS) Close() error { return n.do(func() error { return n.f.Close() }) } // FD returns a file descriptor which represents the network namespace. func (n *netNS) FD() int { if n.disabled { // No reasonable file descriptor value in this case, so specify a // non-existent one. return -1 } return int(n.f.Fd()) } // Restore restores the original network namespace for the calling thread. func (n *netNS) Restore() error { return n.do(func() error { return n.Set(n.FD()) }) } // Set sets a new network namespace for the current thread using fd. func (n *netNS) Set(fd int) error { return n.do(func() error { return os.NewSyscallError("setns", unix.Setns(fd, unix.CLONE_NEWNET)) }) } // do runs fn if network namespaces are enabled on this system. func (n *netNS) do(fn func() error) error { if n.disabled { return errNetNSDisabled } return fn() } socket-0.1.1/netns_others.go000066400000000000000000000004601415671261500160530ustar00rootroot00000000000000//go:build !linux // +build !linux package socket import ( "fmt" "runtime" ) // withNetNS returns an error on non-Linux systems. func withNetNS(_ int, _ func() (*Conn, error)) (*Conn, error) { return nil, fmt.Errorf("socket: Linux network namespace support is not available on %s", runtime.GOOS) } socket-0.1.1/setbuffer_linux.go000066400000000000000000000012131415671261500165410ustar00rootroot00000000000000//go:build linux // +build linux package socket import "golang.org/x/sys/unix" // setReadBuffer wraps the SO_RCVBUF{,FORCE} setsockopt(2) options. func (c *Conn) setReadBuffer(bytes int) error { err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes) if err != nil { err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes) } return err } // setWriteBuffer wraps the SO_SNDBUF{,FORCE} setsockopt(2) options. func (c *Conn) setWriteBuffer(bytes int) error { err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes) if err != nil { err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes) } return err } socket-0.1.1/setbuffer_others.go000066400000000000000000000006611415671261500167140ustar00rootroot00000000000000//go:build !linux // +build !linux package socket import "golang.org/x/sys/unix" // setReadBuffer wraps the SO_RCVBUF setsockopt(2) option. func (c *Conn) setReadBuffer(bytes int) error { return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes) } // setWriteBuffer wraps the SO_SNDBUF setsockopt(2) option. func (c *Conn) setWriteBuffer(bytes int) error { return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes) } socket-0.1.1/typ_cloexec_nonblock.go000066400000000000000000000003631415671261500175450ustar00rootroot00000000000000//go:build !darwin // +build !darwin package socket import "golang.org/x/sys/unix" const ( // These operating systems support CLOEXEC and NONBLOCK socket options. flagCLOEXEC = true socketFlags = unix.SOCK_CLOEXEC | unix.SOCK_NONBLOCK ) socket-0.1.1/typ_none.go000066400000000000000000000002701415671261500151720ustar00rootroot00000000000000//go:build darwin // +build darwin package socket const ( // These operating systems do not support CLOEXEC and NONBLOCK socket // options. flagCLOEXEC = false socketFlags = 0 )