github.com/sagernet/quic-go@v0.43.1-beta.1/ech/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 }