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  }