github.com/daeuniverse/quic-go@v0.0.0-20240413031024-943f218e0810/sys_conn_helper_linux.go (about)

     1  //go:build linux
     2  
     3  package quic
     4  
     5  import (
     6  	"encoding/binary"
     7  	"errors"
     8  	"net/netip"
     9  	"os"
    10  	"strconv"
    11  	"syscall"
    12  	"unsafe"
    13  
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  const (
    18  	msgTypeIPTOS = unix.IP_TOS
    19  	ipv4PKTINFO  = unix.IP_PKTINFO
    20  )
    21  
    22  const ecnIPv4DataLen = 1
    23  
    24  const batchSize = 8 // needs to smaller than MaxUint8 (otherwise the type of oobConn.readPos has to be changed)
    25  
    26  func forceSetReceiveBuffer(c syscall.RawConn, bytes int) error {
    27  	var serr error
    28  	if err := c.Control(func(fd uintptr) {
    29  		serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes)
    30  	}); err != nil {
    31  		return err
    32  	}
    33  	return serr
    34  }
    35  
    36  func forceSetSendBuffer(c syscall.RawConn, bytes int) error {
    37  	var serr error
    38  	if err := c.Control(func(fd uintptr) {
    39  		serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes)
    40  	}); err != nil {
    41  		return err
    42  	}
    43  	return serr
    44  }
    45  
    46  func parseIPv4PktInfo(body []byte) (ip netip.Addr, ifIndex uint32, ok bool) {
    47  	// struct in_pktinfo {
    48  	// 	unsigned int   ipi_ifindex;  /* Interface index */
    49  	// 	struct in_addr ipi_spec_dst; /* Local address */
    50  	// 	struct in_addr ipi_addr;     /* Header Destination address */
    51  	// };
    52  	if len(body) != 12 {
    53  		return netip.Addr{}, 0, false
    54  	}
    55  	return netip.AddrFrom4(*(*[4]byte)(body[8:12])), binary.LittleEndian.Uint32(body), true
    56  }
    57  
    58  // isGSOSupported tests if the kernel supports GSO.
    59  // Sending with GSO might still fail later on, if the interface doesn't support it (see isGSOError).
    60  func isGSOSupported(conn syscall.RawConn) bool {
    61  	disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_GSO"))
    62  	if err == nil && disabled {
    63  		return false
    64  	}
    65  	var serr error
    66  	if err := conn.Control(func(fd uintptr) {
    67  		_, serr = unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT)
    68  	}); err != nil {
    69  		return false
    70  	}
    71  	return serr == nil
    72  }
    73  
    74  func appendUDPSegmentSizeMsg(b []byte, size uint16) []byte {
    75  	startLen := len(b)
    76  	const dataLen = 2 // payload is a uint16
    77  	b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
    78  	h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
    79  	h.Level = syscall.IPPROTO_UDP
    80  	h.Type = unix.UDP_SEGMENT
    81  	h.SetLen(unix.CmsgLen(dataLen))
    82  
    83  	// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
    84  	offset := startLen + unix.CmsgSpace(0)
    85  	*(*uint16)(unsafe.Pointer(&b[offset])) = size
    86  	return b
    87  }
    88  
    89  func isGSOError(err error) bool {
    90  	var serr *os.SyscallError
    91  	if errors.As(err, &serr) {
    92  		// EIO is returned by udp_send_skb() if the device driver does not have tx checksums enabled,
    93  		// which is a hard requirement of UDP_SEGMENT. See:
    94  		// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man7/udp.7?id=806eabd74910447f21005160e90957bde4db0183#n228
    95  		// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/udp.c?h=v6.2&id=c9c3395d5e3dcc6daee66c6908354d47bf98cb0c#n942
    96  		return serr.Err == unix.EIO || serr.Err == unix.EINVAL
    97  	}
    98  	return false
    99  }
   100  
   101  // The first sendmsg call on a new UDP socket sometimes errors on Linux.
   102  // It's not clear why this happens.
   103  // See https://github.com/golang/go/issues/63322.
   104  func isPermissionError(err error) bool {
   105  	var serr *os.SyscallError
   106  	if errors.As(err, &serr) {
   107  		return serr.Syscall == "sendmsg" && serr.Err == unix.EPERM
   108  	}
   109  	return false
   110  }