diff --git a/udp/raw_sendmmsg_linux.go b/udp/raw_sendmmsg_linux.go deleted file mode 100644 index ae4dcdc6..00000000 --- a/udp/raw_sendmmsg_linux.go +++ /dev/null @@ -1,62 +0,0 @@ -//go:build !android && !e2e_testing -// +build !android,!e2e_testing - -package udp - -import ( - "net" - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -// rawSendmmsg performs sendmmsg(2) over a syscall.RawConn without -// allocating a closure per call. The struct holds preallocated in/out -// scratch (chunk/sent/errno) and a method-value bound at construction so -// rawConn.Write receives a stable function pointer instead of a fresh -// closure on every send. -type rawSendmmsg struct { - msgs []rawMessage - chunk int - sent int - errno syscall.Errno - callback func(fd uintptr) bool -} - -// bind wires r.callback to r.run. Must be called once after r.msgs is set; -// subsequent send calls invoke r.callback without rebinding. -func (r *rawSendmmsg) bind() { r.callback = r.run } - -// run is the preallocated callback rawConn.Write invokes. It reads its -// input (r.chunk) and writes its outputs (r.sent, r.errno) through the -// rawSendmmsg fields so the method value does not capture per-call locals -// and therefore does not heap-allocate. -func (r *rawSendmmsg) run(fd uintptr) bool { - r1, _, errno := unix.Syscall6(unix.SYS_SENDMMSG, fd, - uintptr(unsafe.Pointer(&r.msgs[0])), uintptr(r.chunk), - 0, 0, 0, - ) - if errno == syscall.EAGAIN || errno == syscall.EWOULDBLOCK { - return false - } - r.sent = int(r1) - r.errno = errno - return true -} - -// send issues sendmmsg over rc against the first n entries of r.msgs. -// Returns the number of entries the kernel processed and any error; -// matches the original sendmmsg helper's contract. -func (r *rawSendmmsg) send(rc syscall.RawConn, n int) (int, error) { - r.chunk = n - r.sent = 0 - r.errno = 0 - if err := rc.Write(r.callback); err != nil { - return r.sent, err - } - if r.errno != 0 { - return r.sent, &net.OpError{Op: "sendmmsg", Err: r.errno} - } - return r.sent, nil -} diff --git a/udp/udp_linux.go b/udp/udp_linux.go index b309c806..f65b1214 100644 --- a/udp/udp_linux.go +++ b/udp/udp_linux.go @@ -50,10 +50,6 @@ type StdConn struct { // into mmsghdr entry e. Used to rewind `i` on partial sendmmsg success. writeEntryEnd []int - // rawSend wraps the sendmmsg(2) callback in a closure-free helper so - // the hot path doesn't heap-allocate a fresh closure per call. - rawSend rawSendmmsg - // UDP GSO (sendmsg with UDP_SEGMENT cmsg) support. gsoSupported is // probed once at socket creation. When true, WriteBatch packs same- // destination consecutive packets into a single sendmmsg entry with a @@ -115,8 +111,6 @@ func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) out := &StdConn{sysFd: fd, isV4: ip.Is4(), l: l, batch: batch} out.prepareWriteMessages(MaxWriteBatch) - out.rawSend.msgs = out.writeMsgs - out.rawSend.bind() out.prepareGSO() // GRO delivers coalesced superpackets that need a cmsg to split back @@ -203,19 +197,11 @@ const maxGSOBytes = 65000 func (u *StdConn) prepareGSO() { u.maxGSOSegments = 63 //gotta be one less than the max so we can still attach a header - var probeErr error - if err := u.rawConn.Control(func(fd uintptr) { - probeErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT, 0) - }); err != nil { + if err := unix.SetsockoptInt(u.sysFd, unix.IPPROTO_UDP, unix.UDP_SEGMENT, 0); err != nil { u.l.Info("udp: GSO disabled", "reason", "rawconn control failed", "error", err) recordCapability("udp.gso.enabled", false) return } - if probeErr != nil { - u.l.Info("udp: GSO disabled", "reason", "kernel rejected probe", "error", probeErr) - recordCapability("udp.gso.enabled", false) - return - } var un unix.Utsname if err := unix.Uname(&un); err != nil { @@ -262,16 +248,9 @@ const udpGROCmsgPayload = 4 // datagrams into one recvmmsg entry, with a cmsg carrying the gso_size used // to split them back apart on the application side. func (u *StdConn) prepareGRO() { - var probeErr error - if err := u.rawConn.Control(func(fd uintptr) { - probeErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_GRO, 1) - }); err != nil { - u.l.Info("udp: GRO disabled", "reason", "rawconn control failed", "error", err) - recordCapability("udp.gro.enabled", false) - return - } - if probeErr != nil { - u.l.Info("udp: GRO disabled", "reason", "kernel rejected probe", "error", probeErr) + err := unix.SetsockoptInt(u.sysFd, unix.IPPROTO_UDP, unix.UDP_GRO, 1) + if err != nil { + u.l.Info("udp: GRO disabled", "reason", "kernel rejected probe", "error", err) recordCapability("udp.gro.enabled", false) return } @@ -287,15 +266,10 @@ func (u *StdConn) prepareGRO() { // Best-effort: we keep going on failure. func (u *StdConn) prepareECNRecv() { var v4err, v6err error - if err := u.rawConn.Control(func(fd uintptr) { - v4err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVTOS, 1) - if !u.isV4 { - v6err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1) - } - }); err != nil { - u.l.Info("udp: outer-ECN RX disabled", "reason", "rawconn control failed", "error", err) - recordCapability("udp.ecn_rx.enabled", false) - return + + v4err = unix.SetsockoptInt(u.sysFd, unix.IPPROTO_IP, unix.IP_RECVTOS, 1) + if !u.isV4 { + v6err = unix.SetsockoptInt(u.sysFd, unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1) } if u.isV4 { //only check the V4 attempt if v4err != nil { @@ -507,15 +481,6 @@ func (u *StdConn) ListenOut(r EncReader, flush func()) error { } } -// headerCounter returns the big-endian uint64 message counter at bytes -// [8:16] of a nebula packet, or 0 if the buffer is too short. -func headerCounter(buf []byte) uint64 { - if len(buf) < 16 { - return 0 - } - return binary.BigEndian.Uint64(buf[8:16]) -} - // parseRecvCmsg walks the per-slot ancillary buffer once and extracts up to // two values of interest in a single pass: the UDP_GRO gso_size (when // wantGRO is true) and the outer IP-level ECN codepoint stamped on the @@ -873,10 +838,18 @@ func (u *StdConn) writeEntryCmsg(entry, runLen, segSize int, ecn byte, dstIsV4 b } // sendmmsg issues sendmmsg(2) over u.rawConn against the first n entries -// of u.writeMsgs. Routes through u.rawSend so the per-call kernel callback -// stays alloc-free. +// of u.writeMsgs. func (u *StdConn) sendmmsg(n int) (int, error) { - return u.rawSend.send(u.rawConn, n) + r1, _, errno := unix.Syscall6(unix.SYS_SENDMMSG, uintptr(u.sysFd), + uintptr(unsafe.Pointer(&u.writeMsgs[0])), uintptr(n), + 0, 0, 0, + ) + sent := int(r1) + + if errno != 0 { + return sent, &net.OpError{Op: "sendmmsg", Err: errno} + } + return sent, nil } // writeSockaddr encodes addr into buf (which must be at least