github.com/tumi8/quic-go@v0.37.4-tum/sys_conn_oob.go (about) 1 //go:build darwin || linux || freebsd 2 3 package quic 4 5 import ( 6 "encoding/binary" 7 "errors" 8 "fmt" 9 "log" 10 "net" 11 "net/netip" 12 "sync" 13 "syscall" 14 "time" 15 16 "golang.org/x/net/ipv4" 17 "golang.org/x/net/ipv6" 18 "golang.org/x/sys/unix" 19 20 "github.com/tumi8/quic-go/noninternal/protocol" 21 "github.com/tumi8/quic-go/noninternal/utils" 22 ) 23 24 const ( 25 ecnMask = 0x3 26 oobBufferSize = 128 27 ) 28 29 // Contrary to what the naming suggests, the ipv{4,6}.Message is not dependent on the IP version. 30 // They're both just aliases for x/net/noninternal/socket.Message. 31 // This means we can use this struct to read from a socket that receives both IPv4 and IPv6 messages. 32 var _ ipv4.Message = ipv6.Message{} 33 34 type batchConn interface { 35 ReadBatch(ms []ipv4.Message, flags int) (int, error) 36 } 37 38 func inspectReadBuffer(c syscall.RawConn) (int, error) { 39 var size int 40 var serr error 41 if err := c.Control(func(fd uintptr) { 42 size, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF) 43 }); err != nil { 44 return 0, err 45 } 46 return size, serr 47 } 48 49 func inspectWriteBuffer(c syscall.RawConn) (int, error) { 50 var size int 51 var serr error 52 if err := c.Control(func(fd uintptr) { 53 size, serr = unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF) 54 }); err != nil { 55 return 0, err 56 } 57 return size, serr 58 } 59 60 type oobConn struct { 61 OOBCapablePacketConn 62 batchConn batchConn 63 64 readPos uint8 65 // Packets received from the kernel, but not yet returned by ReadPacket(). 66 messages []ipv4.Message 67 buffers [batchSize]*packetBuffer 68 69 cap connCapabilities 70 } 71 72 var _ rawConn = &oobConn{} 73 74 func newConn(c OOBCapablePacketConn, supportsDF bool) (*oobConn, error) { 75 rawConn, err := c.SyscallConn() 76 if err != nil { 77 return nil, err 78 } 79 needsPacketInfo := false 80 if udpAddr, ok := c.LocalAddr().(*net.UDPAddr); ok && udpAddr.IP.IsUnspecified() { 81 needsPacketInfo = true 82 } 83 // We don't know if this a IPv4-only, IPv6-only or a IPv4-and-IPv6 connection. 84 // Try enabling receiving of ECN and packet info for both IP versions. 85 // We expect at least one of those syscalls to succeed. 86 var errECNIPv4, errECNIPv6, errPIIPv4, errPIIPv6 error 87 if err := rawConn.Control(func(fd uintptr) { 88 errECNIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVTOS, 1) 89 errECNIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1) 90 91 if needsPacketInfo { 92 errPIIPv4 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, ipv4PKTINFO, 1) 93 errPIIPv6 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1) 94 } 95 }); err != nil { 96 return nil, err 97 } 98 switch { 99 case errECNIPv4 == nil && errECNIPv6 == nil: 100 utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4 and IPv6.") 101 case errECNIPv4 == nil && errECNIPv6 != nil: 102 utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv4.") 103 case errECNIPv4 != nil && errECNIPv6 == nil: 104 utils.DefaultLogger.Debugf("Activating reading of ECN bits for IPv6.") 105 case errECNIPv4 != nil && errECNIPv6 != nil: 106 return nil, errors.New("activating ECN failed for both IPv4 and IPv6") 107 } 108 if needsPacketInfo { 109 switch { 110 case errPIIPv4 == nil && errPIIPv6 == nil: 111 utils.DefaultLogger.Debugf("Activating reading of packet info for IPv4 and IPv6.") 112 case errPIIPv4 == nil && errPIIPv6 != nil: 113 utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv4.") 114 case errPIIPv4 != nil && errPIIPv6 == nil: 115 utils.DefaultLogger.Debugf("Activating reading of packet info bits for IPv6.") 116 case errPIIPv4 != nil && errPIIPv6 != nil: 117 return nil, errors.New("activating packet info failed for both IPv4 and IPv6") 118 } 119 } 120 121 // Allows callers to pass in a connection that already satisfies batchConn interface 122 // to make use of the optimisation. Otherwise, ipv4.NewPacketConn would unwrap the file descriptor 123 // via SyscallConn(), and read it that way, which might not be what the caller wants. 124 var bc batchConn 125 if ibc, ok := c.(batchConn); ok { 126 bc = ibc 127 } else { 128 bc = ipv4.NewPacketConn(c) 129 } 130 131 // Try enabling GSO. 132 // This will only succeed on Linux, and only for kernels > 4.18. 133 supportsGSO := maybeSetGSO(rawConn) 134 135 msgs := make([]ipv4.Message, batchSize) 136 for i := range msgs { 137 // preallocate the [][]byte 138 msgs[i].Buffers = make([][]byte, 1) 139 } 140 oobConn := &oobConn{ 141 OOBCapablePacketConn: c, 142 batchConn: bc, 143 messages: msgs, 144 readPos: batchSize, 145 } 146 oobConn.cap.DF = supportsDF 147 oobConn.cap.GSO = supportsGSO 148 for i := 0; i < batchSize; i++ { 149 oobConn.messages[i].OOB = make([]byte, oobBufferSize) 150 } 151 return oobConn, nil 152 } 153 154 var invalidCmsgOnceV4, invalidCmsgOnceV6 sync.Once 155 156 func (c *oobConn) ReadPacket() (receivedPacket, error) { 157 if len(c.messages) == int(c.readPos) { // all messages read. Read the next batch of messages. 158 c.messages = c.messages[:batchSize] 159 // replace buffers data buffers up to the packet that has been consumed during the last ReadBatch call 160 for i := uint8(0); i < c.readPos; i++ { 161 buffer := getPacketBuffer() 162 buffer.Data = buffer.Data[:protocol.MaxPacketBufferSize] 163 c.buffers[i] = buffer 164 c.messages[i].Buffers[0] = c.buffers[i].Data 165 } 166 c.readPos = 0 167 168 n, err := c.batchConn.ReadBatch(c.messages, 0) 169 if n == 0 || err != nil { 170 return receivedPacket{}, err 171 } 172 c.messages = c.messages[:n] 173 } 174 175 msg := c.messages[c.readPos] 176 buffer := c.buffers[c.readPos] 177 c.readPos++ 178 179 data := msg.OOB[:msg.NN] 180 p := receivedPacket{ 181 remoteAddr: msg.Addr, 182 rcvTime: time.Now(), 183 data: msg.Buffers[0][:msg.N], 184 buffer: buffer, 185 } 186 for len(data) > 0 { 187 hdr, body, remainder, err := unix.ParseOneSocketControlMessage(data) 188 if err != nil { 189 return receivedPacket{}, err 190 } 191 if hdr.Level == unix.IPPROTO_IP { 192 switch hdr.Type { 193 case msgTypeIPTOS: 194 p.ecn = protocol.ECN(body[0] & ecnMask) 195 case ipv4PKTINFO: 196 ip, ifIndex, ok := parseIPv4PktInfo(body) 197 if ok { 198 p.info.addr = ip 199 p.info.ifIndex = ifIndex 200 } else { 201 invalidCmsgOnceV4.Do(func() { 202 log.Printf("Received invalid IPv4 packet info control message: %+x. "+ 203 "This should never occur, please open a new issue and include details about the architecture.", body) 204 }) 205 } 206 } 207 } 208 if hdr.Level == unix.IPPROTO_IPV6 { 209 switch hdr.Type { 210 case unix.IPV6_TCLASS: 211 p.ecn = protocol.ECN(body[0] & ecnMask) 212 case unix.IPV6_PKTINFO: 213 // struct in6_pktinfo { 214 // struct in6_addr ipi6_addr; /* src/dst IPv6 address */ 215 // unsigned int ipi6_ifindex; /* send/recv interface index */ 216 // }; 217 if len(body) == 20 { 218 p.info.addr = netip.AddrFrom16(*(*[16]byte)(body[:16])) 219 p.info.ifIndex = binary.LittleEndian.Uint32(body[16:]) 220 } else { 221 invalidCmsgOnceV6.Do(func() { 222 log.Printf("Received invalid IPv6 packet info control message: %+x. "+ 223 "This should never occur, please open a new issue and include details about the architecture.", body) 224 }) 225 } 226 } 227 } 228 data = remainder 229 } 230 return p, nil 231 } 232 233 // WritePacket writes a new packet. 234 // If the connection supports GSO (and we activated GSO support before), 235 // it appends the UDP_SEGMENT size message to oob. 236 // Callers are advised to make sure that oob has a sufficient capacity, 237 // such that appending the UDP_SEGMENT size message doesn't cause an allocation. 238 func (c *oobConn) WritePacket(b []byte, packetSize uint16, addr net.Addr, oob []byte) (n int, err error) { 239 if c.cap.GSO { 240 oob = appendUDPSegmentSizeMsg(oob, packetSize) 241 } else if uint16(len(b)) != packetSize { 242 panic(fmt.Sprintf("inconsistent length. got: %d. expected %d", packetSize, len(b))) 243 } 244 n, _, err = c.OOBCapablePacketConn.WriteMsgUDP(b, oob, addr.(*net.UDPAddr)) 245 return n, err 246 } 247 248 func (c *oobConn) capabilities() connCapabilities { 249 return c.cap 250 } 251 252 type packetInfo struct { 253 addr netip.Addr 254 ifIndex uint32 255 } 256 257 func (info *packetInfo) OOB() []byte { 258 if info == nil { 259 return nil 260 } 261 if info.addr.Is4() { 262 ip := info.addr.As4() 263 // struct in_pktinfo { 264 // unsigned int ipi_ifindex; /* Interface index */ 265 // struct in_addr ipi_spec_dst; /* Local address */ 266 // struct in_addr ipi_addr; /* Header Destination address */ 267 // }; 268 cm := ipv4.ControlMessage{ 269 Src: ip[:], 270 IfIndex: int(info.ifIndex), 271 } 272 return cm.Marshal() 273 } else if info.addr.Is6() { 274 ip := info.addr.As16() 275 // struct in6_pktinfo { 276 // struct in6_addr ipi6_addr; /* src/dst IPv6 address */ 277 // unsigned int ipi6_ifindex; /* send/recv interface index */ 278 // }; 279 cm := ipv6.ControlMessage{ 280 Src: ip[:], 281 IfIndex: int(info.ifIndex), 282 } 283 return cm.Marshal() 284 } 285 return nil 286 }