package quic
import (
"encoding/binary"
"errors"
"log"
"net"
"net/netip"
"os"
"strconv"
"sync"
"syscall"
"unsafe"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"golang.org/x/sys/unix"
"github.com/quic-go/quic-go/internal/monotime"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
)
const (
ecnMask = 0x3
oobBufferSize = 128
)
var _ ipv4 .Message = ipv6 .Message {}
type batchConn interface {
ReadBatch(ms []ipv4 .Message , flags int ) (int , error )
}
func inspectReadBuffer(c syscall .RawConn ) (int , error ) {
var size int
var serr error
if err := c .Control (func (fd uintptr ) {
size , serr = unix .GetsockoptInt (int (fd ), unix .SOL_SOCKET , unix .SO_RCVBUF )
}); err != nil {
return 0 , err
}
return size , serr
}
func inspectWriteBuffer(c syscall .RawConn ) (int , error ) {
var size int
var serr error
if err := c .Control (func (fd uintptr ) {
size , serr = unix .GetsockoptInt (int (fd ), unix .SOL_SOCKET , unix .SO_SNDBUF )
}); err != nil {
return 0 , err
}
return size , serr
}
func isECNDisabledUsingEnv() bool {
disabled , err := strconv .ParseBool (os .Getenv ("QUIC_GO_DISABLE_ECN" ))
return err == nil && disabled
}
type oobConn struct {
OOBCapablePacketConn
batchConn batchConn
readPos uint8
messages []ipv4 .Message
buffers [batchSize ]*packetBuffer
cap connCapabilities
}
var _ rawConn = &oobConn {}
func newConn(c OOBCapablePacketConn , supportsDF bool ) (*oobConn , error ) {
rawConn , err := c .SyscallConn ()
if err != nil {
return nil , err
}
var needsPacketInfo bool
if udpAddr , ok := c .LocalAddr ().(*net .UDPAddr ); ok && udpAddr .IP .IsUnspecified () {
needsPacketInfo = true
}
var errECNIPv4 , errECNIPv6 , errPIIPv4 , errPIIPv6 error
if err := rawConn .Control (func (fd uintptr ) {
errECNIPv4 = unix .SetsockoptInt (int (fd ), unix .IPPROTO_IP , unix .IP_RECVTOS , 1 )
errECNIPv6 = unix .SetsockoptInt (int (fd ), unix .IPPROTO_IPV6 , unix .IPV6_RECVTCLASS , 1 )
if needsPacketInfo {
errPIIPv4 = unix .SetsockoptInt (int (fd ), unix .IPPROTO_IP , ipv4PKTINFO , 1 )
errPIIPv6 = unix .SetsockoptInt (int (fd ), unix .IPPROTO_IPV6 , unix .IPV6_RECVPKTINFO , 1 )
}
}); err != nil {
return nil , err
}
switch {
case errECNIPv4 == nil && errECNIPv6 == nil :
utils .DefaultLogger .Debugf ("Activating reading of ECN bits for IPv4 and IPv6." )
case errECNIPv4 == nil && errECNIPv6 != nil :
utils .DefaultLogger .Debugf ("Activating reading of ECN bits for IPv4." )
case errECNIPv4 != nil && errECNIPv6 == nil :
utils .DefaultLogger .Debugf ("Activating reading of ECN bits for IPv6." )
case errECNIPv4 != nil && errECNIPv6 != nil :
return nil , errors .New ("activating ECN failed for both IPv4 and IPv6" )
}
if needsPacketInfo {
switch {
case errPIIPv4 == nil && errPIIPv6 == nil :
utils .DefaultLogger .Debugf ("Activating reading of packet info for IPv4 and IPv6." )
case errPIIPv4 == nil && errPIIPv6 != nil :
utils .DefaultLogger .Debugf ("Activating reading of packet info bits for IPv4." )
case errPIIPv4 != nil && errPIIPv6 == nil :
utils .DefaultLogger .Debugf ("Activating reading of packet info bits for IPv6." )
case errPIIPv4 != nil && errPIIPv6 != nil :
return nil , errors .New ("activating packet info failed for both IPv4 and IPv6" )
}
}
var bc batchConn
if ibc , ok := c .(batchConn ); ok {
bc = ibc
} else {
bc = ipv4 .NewPacketConn (c )
}
msgs := make ([]ipv4 .Message , batchSize )
for i := range msgs {
msgs [i ].Buffers = make ([][]byte , 1 )
}
oobConn := &oobConn {
OOBCapablePacketConn : c ,
batchConn : bc ,
messages : msgs ,
readPos : batchSize ,
cap : connCapabilities {
DF : supportsDF ,
GSO : isGSOEnabled (rawConn ),
ECN : isECNEnabled (),
},
}
for i := 0 ; i < batchSize ; i ++ {
oobConn .messages [i ].OOB = make ([]byte , oobBufferSize )
}
return oobConn , nil
}
var invalidCmsgOnceV4, invalidCmsgOnceV6 sync .Once
func (c *oobConn ) ReadPacket () (receivedPacket , error ) {
if len (c .messages ) == int (c .readPos ) {
c .messages = c .messages [:batchSize ]
for i := uint8 (0 ); i < c .readPos ; i ++ {
buffer := getPacketBuffer ()
buffer .Data = buffer .Data [:protocol .MaxPacketBufferSize ]
c .buffers [i ] = buffer
c .messages [i ].Buffers [0 ] = c .buffers [i ].Data
}
c .readPos = 0
n , err := c .batchConn .ReadBatch (c .messages , 0 )
if n == 0 || err != nil {
return receivedPacket {}, err
}
c .messages = c .messages [:n ]
}
msg := c .messages [c .readPos ]
buffer := c .buffers [c .readPos ]
c .readPos ++
data := msg .OOB [:msg .NN ]
p := receivedPacket {
remoteAddr : msg .Addr ,
rcvTime : monotime .Now (),
data : msg .Buffers [0 ][:msg .N ],
buffer : buffer ,
}
for len (data ) > 0 {
hdr , body , remainder , err := unix .ParseOneSocketControlMessage (data )
if err != nil {
return receivedPacket {}, err
}
if hdr .Level == unix .IPPROTO_IP {
switch hdr .Type {
case msgTypeIPTOS :
if len (body ) != 1 {
return receivedPacket {}, errors .New ("invalid IPTOS size" )
}
p .ecn = protocol .ParseECNHeaderBits (body [0 ] & ecnMask )
case ipv4PKTINFO :
ip , ifIndex , ok := parseIPv4PktInfo (body )
if ok {
p .info .addr = ip
p .info .ifIndex = ifIndex
} else {
invalidCmsgOnceV4 .Do (func () {
log .Printf ("Received invalid IPv4 packet info control message: %+x. " +
"This should never occur, please open a new issue and include details about the architecture." , body )
})
}
}
}
if hdr .Level == unix .IPPROTO_IPV6 {
switch hdr .Type {
case unix .IPV6_TCLASS :
if len (body ) != 4 {
return receivedPacket {}, errors .New ("invalid IPV6_TCLASS size" )
}
bits := uint8 (binary .NativeEndian .Uint32 (body )) & ecnMask
p .ecn = protocol .ParseECNHeaderBits (bits )
case unix .IPV6_PKTINFO :
if len (body ) == 20 {
p .info .addr = netip .AddrFrom16 (*(*[16 ]byte )(body [:16 ])).Unmap ()
p .info .ifIndex = binary .NativeEndian .Uint32 (body [16 :])
} else {
invalidCmsgOnceV6 .Do (func () {
log .Printf ("Received invalid IPv6 packet info control message: %+x. " +
"This should never occur, please open a new issue and include details about the architecture." , body )
})
}
}
}
data = remainder
}
return p , nil
}
func (c *oobConn ) WritePacket (b []byte , addr net .Addr , packetInfoOOB []byte , gsoSize uint16 , ecn protocol .ECN ) (int , error ) {
oob := packetInfoOOB
if gsoSize > 0 {
if !c .capabilities ().GSO {
panic ("GSO disabled" )
}
oob = appendUDPSegmentSizeMsg (oob , gsoSize )
}
if ecn != protocol .ECNUnsupported {
if !c .capabilities ().ECN {
panic ("tried to send an ECN-marked packet although ECN is disabled" )
}
if remoteUDPAddr , ok := addr .(*net .UDPAddr ); ok {
if remoteUDPAddr .IP .To4 () != nil {
oob = appendIPv4ECNMsg (oob , ecn )
} else {
oob = appendIPv6ECNMsg (oob , ecn )
}
}
}
n , _ , err := c .WriteMsgUDP (b , oob , addr .(*net .UDPAddr ))
return n , err
}
func (c *oobConn ) capabilities () connCapabilities {
return c .cap
}
type packetInfo struct {
addr netip .Addr
ifIndex uint32
}
func (info *packetInfo ) OOB () []byte {
if info == nil {
return nil
}
if info .addr .Is4 () {
ip := info .addr .As4 ()
cm := ipv4 .ControlMessage {
Src : ip [:],
IfIndex : int (info .ifIndex ),
}
return cm .Marshal ()
} else if info .addr .Is6 () {
ip := info .addr .As16 ()
cm := ipv6 .ControlMessage {
Src : ip [:],
IfIndex : int (info .ifIndex ),
}
return cm .Marshal ()
}
return nil
}
func appendIPv4ECNMsg(b []byte , val protocol .ECN ) []byte {
startLen := len (b )
b = append (b , make ([]byte , unix .CmsgSpace (ecnIPv4DataLen ))...)
h := (*unix .Cmsghdr )(unsafe .Pointer (&b [startLen ]))
h .Level = syscall .IPPROTO_IP
h .Type = unix .IP_TOS
h .SetLen (unix .CmsgLen (ecnIPv4DataLen ))
offset := startLen + unix .CmsgSpace (0 )
b [offset ] = val .ToHeaderBits ()
return b
}
func appendIPv6ECNMsg(b []byte , val protocol .ECN ) []byte {
startLen := len (b )
const dataLen = 4
b = append (b , make ([]byte , unix .CmsgSpace (dataLen ))...)
h := (*unix .Cmsghdr )(unsafe .Pointer (&b [startLen ]))
h .Level = syscall .IPPROTO_IPV6
h .Type = unix .IPV6_TCLASS
h .SetLen (unix .CmsgLen (dataLen ))
offset := startLen + unix .CmsgSpace (0 )
binary .NativeEndian .PutUint32 (b [offset :offset +dataLen ], uint32 (val .ToHeaderBits ()))
return b
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .