This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
version: "2"
linters:
enable:
- misspell
- modernize
- revive
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
formatters:
exclusions:
generated: lax
+40
View File
@@ -1,5 +1,45 @@
# CHANGELOG
## v0.7.0
**This is the first release of package socket that only supports Go 1.26+.**
- [New API] [PR](https://github.com/mdlayher/socket/pull/18):
add `Conn.SendmsgBuffers` to wrap `sendmsg(2)` with scatter-gather I/O
support for multiple input buffers.
- [New API]: add `Conn.SetsockoptBytes` and `Conn.GetsockoptBytes` for
binary-safe byte-slice socket options on Linux.
- [Bug Fix]: context cancelation is now honored immediately for blocking
operations when the context also carries a deadline; previously cancelation
was not noticed until the deadline expired.
- [Improvement]: bump build to Go 1.26.0. Package socket now supports Go 1.26
and Go 1.27, mirroring Go's own release policy.
## v0.6.1
- [Improvement]: update dependencies.
## v0.6.0
**This is the first release of package socket that only supports Go 1.25+.**
- [Bug Fix] [PR](https://github.com/mdlayher/socket/pull/16):
`socket.Socket` with `Config.NetNS` set now returns the error from socket
creation inside the target network namespace, rather than silently discarding
it and returning a nil `Conn`.
- [Improvement]: bump build to Go 1.25.0.
- [Improvement]: enable `golangci-lint` in CI.
## v0.5.2
- [Improvement]: Bump build to Go 1.23.0. Note this is required for the latest
Go extended library versions.
## v0.5.1
- [Improvement]: revert `go.mod` to Go 1.20 to [resolve an issue around Go
module version upgrades](https://github.com/mdlayher/socket/issues/13).
## v0.5.0
**This is the first release of package socket that only supports Go 1.21+.
+1 -1
View File
@@ -1,4 +1,4 @@
# 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)
# 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)
Package `socket` provides a low-level network connection type which integrates
with Go's runtime network poller to provide asynchronous I/O and deadline
-1
View File
@@ -1,5 +1,4 @@
//go:build dragonfly || freebsd || illumos || linux
// +build dragonfly freebsd illumos linux
package socket
+40 -32
View File
@@ -120,7 +120,7 @@ func (c *Conn) ReadContext(ctx context.Context, b []byte) (int, error) {
b = b[:maxRW]
}
n, err := readT(c, ctx, "read", func(fd int) (int, error) {
n, err := readT(ctx, c, "read", func(fd int) (int, error) {
return unix.Read(fd, b)
})
if n == 0 && err == nil && c.facts.zeroReadIsEOF {
@@ -142,12 +142,12 @@ func (c *Conn) WriteContext(ctx context.Context, b []byte) (int, error) {
)
doErr := c.write(ctx, "write", func(fd int) error {
max := len(b)
if c.facts.isStream && max-nn > maxRW {
max = nn + maxRW
lenb := len(b)
if c.facts.isStream && lenb-nn > maxRW {
lenb = nn + maxRW
}
n, err = unix.Write(fd, b[nn:max])
n, err = unix.Write(fd, b[nn:lenb])
if n > 0 {
nn += n
}
@@ -418,7 +418,7 @@ func (c *Conn) Accept(ctx context.Context, flags int) (*Conn, unix.Sockaddr, err
sa unix.Sockaddr
}
r, err := readT(c, ctx, sysAccept, func(fd int) (ret, error) {
r, err := readT(ctx, c, sysAccept, func(fd int) (ret, error) {
// Either accept(2) or accept4(2) depending on the OS.
nfd, sa, err := accept(fd, flags|socketFlags)
return ret{nfd, sa}, err
@@ -464,7 +464,7 @@ func (c *Conn) Connect(ctx context.Context, sa unix.Sockaddr) (unix.Sockaddr, er
// have an explicit WaitWrite call like internal/poll does, so we have
// to wait until the runtime calls the closure again to indicate we can
// write.
progress uint32
progress atomic.Uint32
// Capture closure sockaddr and error.
rsa unix.Sockaddr
@@ -472,7 +472,7 @@ func (c *Conn) Connect(ctx context.Context, sa unix.Sockaddr) (unix.Sockaddr, er
)
doErr := c.write(ctx, op, func(fd int) error {
if atomic.AddUint32(&progress, 1) == 1 {
if progress.Add(1) == 1 {
// First call: initiate connect.
return unix.Connect(fd, sa)
}
@@ -569,7 +569,7 @@ func (c *Conn) Recvmsg(ctx context.Context, p, oob []byte, flags int) (int, int,
from unix.Sockaddr
}
r, err := readT(c, ctx, "recvmsg", func(fd int) (ret, error) {
r, err := readT(ctx, c, "recvmsg", func(fd int) (ret, error) {
n, oobn, recvflags, from, err := unix.Recvmsg(fd, p, oob, flags)
return ret{n, oobn, recvflags, from}, err
})
@@ -587,7 +587,7 @@ func (c *Conn) Recvfrom(ctx context.Context, p []byte, flags int) (int, unix.Soc
addr unix.Sockaddr
}
out, err := readT(c, ctx, "recvfrom", func(fd int) (ret, error) {
out, err := readT(ctx, c, "recvfrom", func(fd int) (ret, error) {
n, addr, err := unix.Recvfrom(fd, p, flags)
return ret{n, addr}, err
})
@@ -600,11 +600,18 @@ func (c *Conn) Recvfrom(ctx context.Context, p []byte, flags int) (int, unix.Soc
// Sendmsg wraps sendmsg(2).
func (c *Conn) Sendmsg(ctx context.Context, p, oob []byte, to unix.Sockaddr, flags int) (int, error) {
return writeT(c, ctx, "sendmsg", func(fd int) (int, error) {
return writeT(ctx, c, "sendmsg", func(fd int) (int, error) {
return unix.SendmsgN(fd, p, oob, to, flags)
})
}
// SendmsgBuffers wraps sendmsg(2) with scatter-gather I/O support.
func (c *Conn) SendmsgBuffers(ctx context.Context, buffers [][]byte, oob []byte, to unix.Sockaddr, flags int) (int, error) {
return writeT(ctx, c, "sendmsg", func(fd int) (int, error) {
return unix.SendmsgBuffers(fd, buffers, oob, to, flags)
})
}
// Sendto wraps sendto(2).
func (c *Conn) Sendto(ctx context.Context, p []byte, flags int, to unix.Sockaddr) error {
return c.write(ctx, "sendto", func(fd int) error {
@@ -645,7 +652,7 @@ func (c *Conn) Shutdown(how int) error {
// read wraps readT to execute a function and capture its error result. This is
// a convenience wrapper for functions which don't return any extra values.
func (c *Conn) read(ctx context.Context, op string, f func(fd int) error) error {
_, err := readT(c, ctx, op, func(fd int) (struct{}, error) {
_, err := readT(ctx, c, op, func(fd int) (struct{}, error) {
return struct{}{}, f(fd)
})
return err
@@ -654,7 +661,7 @@ func (c *Conn) read(ctx context.Context, op string, f func(fd int) error) error
// 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(ctx context.Context, op string, f func(fd int) error) error {
_, err := writeT(c, ctx, op, func(fd int) (struct{}, error) {
_, err := writeT(ctx, c, op, func(fd int) (struct{}, error) {
return struct{}{}, f(fd)
})
return err
@@ -662,7 +669,7 @@ func (c *Conn) write(ctx context.Context, op string, f func(fd int) error) error
// readT executes c.rc.Read for op using the input function, returning a newly
// allocated result T.
func readT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) {
func readT[T any](ctx context.Context, c *Conn, op string, f func(fd int) (T, error)) (T, error) {
return rwT(c, rwContext[T]{
Context: ctx,
Type: read,
@@ -673,7 +680,7 @@ func readT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, er
// writeT executes c.rc.Write for op using the input function, returning a newly
// allocated result T.
func writeT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) {
func writeT[T any](ctx context.Context, c *Conn, op string, f func(fd int) (T, error)) (T, error) {
return rwT(c, rwContext[T]{
Context: ctx,
Type: write,
@@ -750,12 +757,6 @@ func rwT[T any](c *Conn, rw rwContext[T]) (T, error) {
needDisarm atomic.Bool
)
// On cancel, clean up the watcher.
defer func() {
close(doneC)
wg.Wait()
}()
if d, ok := rw.Context.Deadline(); ok {
// The context has an explicit deadline. We will use it for cancelation
// but disarm it after poll for the next call.
@@ -764,17 +765,17 @@ func rwT[T any](c *Conn, rw rwContext[T]) (T, error) {
}
setDeadline = true
needDisarm.Store(true)
} else {
// The context does not have an explicit deadline. We have to watch for
// cancelation so we can propagate that signal to immediately unblock
// the runtime network poller.
//
// TODO(mdlayher): is it possible to detect a background context vs a
// context with possible future cancel?
wg.Add(1)
go func() {
defer wg.Done()
}
if rw.Context.Done() != nil {
// The context can be canceled, regardless of whether or not it also
// carries a deadline. We have to watch for cancelation so we can
// propagate that signal to immediately unblock the runtime network
// poller rather than waiting for a possibly distant deadline to expire.
//
// A nil Done channel means the context can never be canceled (such as
// context.Background), so no watcher is necessary.
wg.Go(func() {
select {
case <-rw.Context.Done():
// Cancel the operation. Make the caller disarm after poll
@@ -784,7 +785,7 @@ func rwT[T any](c *Conn, rw rwContext[T]) (T, error) {
case <-doneC:
// Nothing to do.
}
}()
})
}
var (
@@ -797,6 +798,13 @@ func rwT[T any](c *Conn, rw rwContext[T]) (T, error) {
return ready(err)
})
// Stop the watcher and wait for it to exit before checking whether we
// must disarm the deadline. Otherwise the watcher could observe
// cancelation and arm a deadline in the past after we have already
// disarmed, leaving the next call to fail immediately.
close(doneC)
wg.Wait()
if needDisarm.Load() {
_ = deadline(time.Time{})
}
+62 -1
View File
@@ -1,5 +1,4 @@
//go:build linux
// +build linux
package socket
@@ -96,6 +95,68 @@ func (c *Conn) SetsockoptSockFprog(level, opt int, fprog *unix.SockFprog) error
})
}
// GetsockoptBytes wraps getsockopt(2) for raw byte slice values on Linux. The
// existing contents of b are passed to the kernel as the input buffer with
// optlen set to len(b), and the kernel overwrites b in place. The returned int
// is the optlen value reported by the kernel on return, which may be smaller
// than len(b). These in/out semantics are required for socket options which
// both read from and write to the same buffer.
//
// No allocation or copying is performed and b is passed to the kernel
// verbatim. If len(b) is zero, a nil buffer with optlen 0 is passed.
func (c *Conn) GetsockoptBytes(level, opt int, b []byte) (int, error) {
return controlT(c, "getsockopt", func(fd int) (int, error) {
var p unsafe.Pointer
if len(b) > 0 {
p = unsafe.Pointer(&b[0])
}
optlen := uint32(len(b))
_, _, errno := unix.Syscall6(
unix.SYS_GETSOCKOPT,
uintptr(fd),
uintptr(level),
uintptr(opt),
uintptr(p),
uintptr(unsafe.Pointer(&optlen)),
0,
)
if errno != 0 {
return 0, errno
}
return int(optlen), nil
})
}
// SetsockoptBytes wraps setsockopt(2) for raw byte slice values on Linux. The
// bytes of b are passed to the kernel verbatim with optlen set to len(b) and no
// NUL handling of any kind is performed. If len(b) is zero, a nil buffer with
// optlen 0 is passed.
func (c *Conn) SetsockoptBytes(level, opt int, b []byte) error {
return c.control("setsockopt", func(fd int) error {
var p unsafe.Pointer
if len(b) > 0 {
p = unsafe.Pointer(&b[0])
}
_, _, errno := unix.Syscall6(
unix.SYS_SETSOCKOPT,
uintptr(fd),
uintptr(level),
uintptr(opt),
uintptr(p),
uintptr(len(b)),
0,
)
if errno != 0 {
return errno
}
return nil
})
}
// GetsockoptTpacketStats wraps getsockopt(2) for unix.TpacketStats values.
func (c *Conn) GetsockoptTpacketStats(level, name int) (*unix.TpacketStats, error) {
return controlT(c, "getsockopt", func(fd int) (*unix.TpacketStats, error) {
+1 -2
View File
@@ -1,5 +1,4 @@
//go:build linux
// +build linux
package socket
@@ -65,7 +64,7 @@ func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) {
// No more thread-local state manipulation; return the new Conn.
runtime.UnlockOSThread()
conn = c
return nil
return err
})
if err := eg.Wait(); err != nil {
-1
View File
@@ -1,5 +1,4 @@
//go:build linux
// +build linux
package socket
-1
View File
@@ -1,5 +1,4 @@
//go:build !darwin
// +build !darwin
package socket