github.com/sagernet/quic-go@v0.43.1-beta.1/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  var kernelVersionMajor int
    27  
    28  func init() {
    29  	kernelVersionMajor, _ = kernelVersion()
    30  }
    31  
    32  func forceSetReceiveBuffer(c syscall.RawConn, bytes int) error {
    33  	var serr error
    34  	if err := c.Control(func(fd uintptr) {
    35  		serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes)
    36  	}); err != nil {
    37  		return err
    38  	}
    39  	return serr
    40  }
    41  
    42  func forceSetSendBuffer(c syscall.RawConn, bytes int) error {
    43  	var serr error
    44  	if err := c.Control(func(fd uintptr) {
    45  		serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes)
    46  	}); err != nil {
    47  		return err
    48  	}
    49  	return serr
    50  }
    51  
    52  func parseIPv4PktInfo(body []byte) (ip netip.Addr, ifIndex uint32, ok bool) {
    53  	// struct in_pktinfo {
    54  	// 	unsigned int   ipi_ifindex;  /* Interface index */
    55  	// 	struct in_addr ipi_spec_dst; /* Local address */
    56  	// 	struct in_addr ipi_addr;     /* Header Destination address */
    57  	// };
    58  	if len(body) != 12 {
    59  		return netip.Addr{}, 0, false
    60  	}
    61  	return netip.AddrFrom4(*(*[4]byte)(body[8:12])), binary.LittleEndian.Uint32(body), true
    62  }
    63  
    64  // isGSOEnabled tests if the kernel supports GSO.
    65  // Sending with GSO might still fail later on, if the interface doesn't support it (see isGSOError).
    66  func isGSOEnabled(conn syscall.RawConn) bool {
    67  	if kernelVersionMajor < 5 {
    68  		return false
    69  	}
    70  	disabled, err := strconv.ParseBool(os.Getenv("QUIC_GO_DISABLE_GSO"))
    71  	if err == nil && disabled {
    72  		return false
    73  	}
    74  	var serr error
    75  	if err := conn.Control(func(fd uintptr) {
    76  		_, serr = unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT)
    77  	}); err != nil {
    78  		return false
    79  	}
    80  	return serr == nil
    81  }
    82  
    83  func appendUDPSegmentSizeMsg(b []byte, size uint16) []byte {
    84  	startLen := len(b)
    85  	const dataLen = 2 // payload is a uint16
    86  	b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
    87  	h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
    88  	h.Level = syscall.IPPROTO_UDP
    89  	h.Type = unix.UDP_SEGMENT
    90  	h.SetLen(unix.CmsgLen(dataLen))
    91  
    92  	// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
    93  	offset := startLen + unix.CmsgSpace(0)
    94  	*(*uint16)(unsafe.Pointer(&b[offset])) = size
    95  	return b
    96  }
    97  
    98  func isGSOError(err error) bool {
    99  	var serr *os.SyscallError
   100  	if errors.As(err, &serr) {
   101  		// EIO is returned by udp_send_skb() if the device driver does not have tx checksums enabled,
   102  		// which is a hard requirement of UDP_SEGMENT. See:
   103  		// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man7/udp.7?id=806eabd74910447f21005160e90957bde4db0183#n228
   104  		// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/udp.c?h=v6.2&id=c9c3395d5e3dcc6daee66c6908354d47bf98cb0c#n942
   105  		return serr.Err == unix.EIO || serr.Err == unix.EINVAL
   106  	}
   107  	return false
   108  }
   109  
   110  // The first sendmsg call on a new UDP socket sometimes errors on Linux.
   111  // It's not clear why this happens.
   112  // See https://github.com/golang/go/issues/63322.
   113  func isPermissionError(err error) bool {
   114  	var serr *os.SyscallError
   115  	if errors.As(err, &serr) {
   116  		return serr.Syscall == "sendmsg" && serr.Err == unix.EPERM
   117  	}
   118  	return false
   119  }
   120  
   121  func isECNEnabled() bool {
   122  	return kernelVersionMajor >= 5 && !isECNDisabledUsingEnv()
   123  }
   124  
   125  // kernelVersion returns major and minor kernel version numbers, parsed from
   126  // the syscall.Uname's Release field, or 0, 0 if the version can't be obtained
   127  // or parsed.
   128  //
   129  // copied from the standard library's internal/syscall/unix/kernel_version_linux.go
   130  func kernelVersion() (major, minor int) {
   131  	var uname syscall.Utsname
   132  	if err := syscall.Uname(&uname); err != nil {
   133  		return
   134  	}
   135  
   136  	var (
   137  		values    [2]int
   138  		value, vi int
   139  	)
   140  	for _, c := range uname.Release {
   141  		if '0' <= c && c <= '9' {
   142  			value = (value * 10) + int(c-'0')
   143  		} else {
   144  			// Note that we're assuming N.N.N here.
   145  			// If we see anything else, we are likely to mis-parse it.
   146  			values[vi] = value
   147  			vi++
   148  			if vi >= len(values) {
   149  				break
   150  			}
   151  			value = 0
   152  		}
   153  	}
   154  
   155  	return values[0], values[1]
   156  }