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 }