mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-03 20:27:35 +03:00
uTLS is not yet bumped to the new version, so this commit breaks the dependencies relationship by getting rid of the local replace.
886 lines
27 KiB
Go
886 lines
27 KiB
Go
package quic
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
tls "github.com/refraction-networking/utls"
|
|
|
|
"github.com/refraction-networking/uquic/internal/handshake"
|
|
"github.com/refraction-networking/uquic/internal/protocol"
|
|
"github.com/refraction-networking/uquic/internal/qerr"
|
|
"github.com/refraction-networking/uquic/internal/utils"
|
|
"github.com/refraction-networking/uquic/internal/wire"
|
|
"github.com/refraction-networking/uquic/logging"
|
|
)
|
|
|
|
// ErrServerClosed is returned by the Listener or EarlyListener's Accept method after a call to Close.
|
|
var ErrServerClosed = errors.New("quic: server closed")
|
|
|
|
// packetHandler handles packets
|
|
type packetHandler interface {
|
|
handlePacket(receivedPacket)
|
|
shutdown()
|
|
destroy(error)
|
|
getPerspective() protocol.Perspective
|
|
}
|
|
|
|
type packetHandlerManager interface {
|
|
Get(protocol.ConnectionID) (packetHandler, bool)
|
|
GetByResetToken(protocol.StatelessResetToken) (packetHandler, bool)
|
|
AddWithConnID(protocol.ConnectionID, protocol.ConnectionID, func() (packetHandler, bool)) bool
|
|
Close(error)
|
|
CloseServer()
|
|
connRunner
|
|
}
|
|
|
|
type quicConn interface {
|
|
EarlyConnection
|
|
earlyConnReady() <-chan struct{}
|
|
handlePacket(receivedPacket)
|
|
GetVersion() protocol.VersionNumber
|
|
getPerspective() protocol.Perspective
|
|
run() error
|
|
destroy(error)
|
|
shutdown()
|
|
}
|
|
|
|
type zeroRTTQueue struct {
|
|
packets []receivedPacket
|
|
expiration time.Time
|
|
}
|
|
|
|
// A Listener of QUIC
|
|
type baseServer struct {
|
|
mutex sync.Mutex
|
|
|
|
acceptEarlyConns bool
|
|
|
|
tlsConf *tls.Config
|
|
config *Config
|
|
|
|
conn rawConn
|
|
|
|
tokenGenerator *handshake.TokenGenerator
|
|
|
|
connIDGenerator ConnectionIDGenerator
|
|
connHandler packetHandlerManager
|
|
onClose func()
|
|
|
|
receivedPackets chan receivedPacket
|
|
|
|
nextZeroRTTCleanup time.Time
|
|
zeroRTTQueues map[protocol.ConnectionID]*zeroRTTQueue // only initialized if acceptEarlyConns == true
|
|
|
|
// set as a member, so they can be set in the tests
|
|
newConn func(
|
|
sendConn,
|
|
connRunner,
|
|
protocol.ConnectionID, /* original dest connection ID */
|
|
*protocol.ConnectionID, /* retry src connection ID */
|
|
protocol.ConnectionID, /* client dest connection ID */
|
|
protocol.ConnectionID, /* destination connection ID */
|
|
protocol.ConnectionID, /* source connection ID */
|
|
ConnectionIDGenerator,
|
|
protocol.StatelessResetToken,
|
|
*Config,
|
|
*tls.Config,
|
|
*handshake.TokenGenerator,
|
|
bool, /* client address validated by an address validation token */
|
|
logging.ConnectionTracer,
|
|
uint64,
|
|
utils.Logger,
|
|
protocol.VersionNumber,
|
|
) quicConn
|
|
|
|
serverError error
|
|
errorChan chan struct{}
|
|
closed bool
|
|
running chan struct{} // closed as soon as run() returns
|
|
versionNegotiationQueue chan receivedPacket
|
|
invalidTokenQueue chan receivedPacket
|
|
|
|
connQueue chan quicConn
|
|
connQueueLen int32 // to be used as an atomic
|
|
|
|
tracer logging.Tracer
|
|
|
|
logger utils.Logger
|
|
}
|
|
|
|
// A Listener listens for incoming QUIC connections.
|
|
// It returns connections once the handshake has completed.
|
|
type Listener struct {
|
|
baseServer *baseServer
|
|
}
|
|
|
|
// Accept returns new connections. It should be called in a loop.
|
|
func (l *Listener) Accept(ctx context.Context) (Connection, error) {
|
|
return l.baseServer.Accept(ctx)
|
|
}
|
|
|
|
// Close the server. All active connections will be closed.
|
|
func (l *Listener) Close() error {
|
|
return l.baseServer.Close()
|
|
}
|
|
|
|
// Addr returns the local network address that the server is listening on.
|
|
func (l *Listener) Addr() net.Addr {
|
|
return l.baseServer.Addr()
|
|
}
|
|
|
|
// An EarlyListener listens for incoming QUIC connections, and returns them before the handshake completes.
|
|
// For connections that don't use 0-RTT, this allows the server to send 0.5-RTT data.
|
|
// This data is encrypted with forward-secure keys, however, the client's identity has not yet been verified.
|
|
// For connection using 0-RTT, this allows the server to accept and respond to streams that the client opened in the
|
|
// 0-RTT data it sent. Note that at this point during the handshake, the live-ness of the
|
|
// client has not yet been confirmed, and the 0-RTT data could have been replayed by an attacker.
|
|
type EarlyListener struct {
|
|
baseServer *baseServer
|
|
}
|
|
|
|
// Accept returns a new connections. It should be called in a loop.
|
|
func (l *EarlyListener) Accept(ctx context.Context) (EarlyConnection, error) {
|
|
return l.baseServer.accept(ctx)
|
|
}
|
|
|
|
// Close the server. All active connections will be closed.
|
|
func (l *EarlyListener) Close() error {
|
|
return l.baseServer.Close()
|
|
}
|
|
|
|
// Addr returns the local network addr that the server is listening on.
|
|
func (l *EarlyListener) Addr() net.Addr {
|
|
return l.baseServer.Addr()
|
|
}
|
|
|
|
// ListenAddr creates a QUIC server listening on a given address.
|
|
// See Listen for more details.
|
|
func ListenAddr(addr string, tlsConf *tls.Config, config *Config) (*Listener, error) {
|
|
conn, err := listenUDP(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return (&Transport{
|
|
Conn: conn,
|
|
createdConn: true,
|
|
isSingleUse: true,
|
|
}).Listen(tlsConf, config)
|
|
}
|
|
|
|
// ListenAddrEarly works like ListenAddr, but it returns connections before the handshake completes.
|
|
func ListenAddrEarly(addr string, tlsConf *tls.Config, config *Config) (*EarlyListener, error) {
|
|
conn, err := listenUDP(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return (&Transport{
|
|
Conn: conn,
|
|
createdConn: true,
|
|
isSingleUse: true,
|
|
}).ListenEarly(tlsConf, config)
|
|
}
|
|
|
|
func listenUDP(addr string) (*net.UDPConn, error) {
|
|
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return net.ListenUDP("udp", udpAddr)
|
|
}
|
|
|
|
// Listen listens for QUIC connections on a given net.PacketConn.
|
|
// If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does),
|
|
// ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP
|
|
// will be used instead of ReadFrom and WriteTo to read/write packets.
|
|
// A single net.PacketConn can only be used for a single call to Listen.
|
|
//
|
|
// The tls.Config must not be nil and must contain a certificate configuration.
|
|
// Furthermore, it must define an application control (using NextProtos).
|
|
// The quic.Config may be nil, in that case the default values will be used.
|
|
//
|
|
// This is a convenience function. More advanced use cases should instantiate a Transport,
|
|
// which offers configuration options for a more fine-grained control of the connection establishment,
|
|
// including reusing the underlying UDP socket for outgoing QUIC connections.
|
|
func Listen(conn net.PacketConn, tlsConf *tls.Config, config *Config) (*Listener, error) {
|
|
tr := &Transport{Conn: conn, isSingleUse: true}
|
|
return tr.Listen(tlsConf, config)
|
|
}
|
|
|
|
// ListenEarly works like Listen, but it returns connections before the handshake completes.
|
|
func ListenEarly(conn net.PacketConn, tlsConf *tls.Config, config *Config) (*EarlyListener, error) {
|
|
tr := &Transport{Conn: conn, isSingleUse: true}
|
|
return tr.ListenEarly(tlsConf, config)
|
|
}
|
|
|
|
func newServer(
|
|
conn rawConn,
|
|
connHandler packetHandlerManager,
|
|
connIDGenerator ConnectionIDGenerator,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
tracer logging.Tracer,
|
|
onClose func(),
|
|
acceptEarly bool,
|
|
) (*baseServer, error) {
|
|
tokenGenerator, err := handshake.NewTokenGenerator(rand.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s := &baseServer{
|
|
conn: conn,
|
|
tlsConf: tlsConf,
|
|
config: config,
|
|
tokenGenerator: tokenGenerator,
|
|
connIDGenerator: connIDGenerator,
|
|
connHandler: connHandler,
|
|
connQueue: make(chan quicConn),
|
|
errorChan: make(chan struct{}),
|
|
running: make(chan struct{}),
|
|
receivedPackets: make(chan receivedPacket, protocol.MaxServerUnprocessedPackets),
|
|
versionNegotiationQueue: make(chan receivedPacket, 4),
|
|
invalidTokenQueue: make(chan receivedPacket, 4),
|
|
newConn: newConnection,
|
|
tracer: tracer,
|
|
logger: utils.DefaultLogger.WithPrefix("server"),
|
|
acceptEarlyConns: acceptEarly,
|
|
onClose: onClose,
|
|
}
|
|
if acceptEarly {
|
|
s.zeroRTTQueues = map[protocol.ConnectionID]*zeroRTTQueue{}
|
|
}
|
|
go s.run()
|
|
go s.runSendQueue()
|
|
s.logger.Debugf("Listening for %s connections on %s", conn.LocalAddr().Network(), conn.LocalAddr().String())
|
|
return s, nil
|
|
}
|
|
|
|
func (s *baseServer) run() {
|
|
defer close(s.running)
|
|
for {
|
|
select {
|
|
case <-s.errorChan:
|
|
return
|
|
default:
|
|
}
|
|
select {
|
|
case <-s.errorChan:
|
|
return
|
|
case p := <-s.receivedPackets:
|
|
if bufferStillInUse := s.handlePacketImpl(p); !bufferStillInUse {
|
|
p.buffer.Release()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *baseServer) runSendQueue() {
|
|
for {
|
|
select {
|
|
case <-s.running:
|
|
return
|
|
case p := <-s.versionNegotiationQueue:
|
|
s.maybeSendVersionNegotiationPacket(p)
|
|
case p := <-s.invalidTokenQueue:
|
|
s.maybeSendInvalidToken(p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Accept returns connections that already completed the handshake.
|
|
// It is only valid if acceptEarlyConns is false.
|
|
func (s *baseServer) Accept(ctx context.Context) (Connection, error) {
|
|
return s.accept(ctx)
|
|
}
|
|
|
|
func (s *baseServer) accept(ctx context.Context) (quicConn, error) {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case conn := <-s.connQueue:
|
|
atomic.AddInt32(&s.connQueueLen, -1)
|
|
return conn, nil
|
|
case <-s.errorChan:
|
|
return nil, s.serverError
|
|
}
|
|
}
|
|
|
|
// Close the server
|
|
func (s *baseServer) Close() error {
|
|
s.mutex.Lock()
|
|
if s.closed {
|
|
s.mutex.Unlock()
|
|
return nil
|
|
}
|
|
if s.serverError == nil {
|
|
s.serverError = ErrServerClosed
|
|
}
|
|
s.closed = true
|
|
close(s.errorChan)
|
|
s.mutex.Unlock()
|
|
|
|
<-s.running
|
|
s.onClose()
|
|
return nil
|
|
}
|
|
|
|
func (s *baseServer) setCloseError(e error) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
if s.closed {
|
|
return
|
|
}
|
|
s.closed = true
|
|
s.serverError = e
|
|
close(s.errorChan)
|
|
}
|
|
|
|
// Addr returns the server's network address
|
|
func (s *baseServer) Addr() net.Addr {
|
|
return s.conn.LocalAddr()
|
|
}
|
|
|
|
func (s *baseServer) handlePacket(p receivedPacket) {
|
|
select {
|
|
case s.receivedPackets <- p:
|
|
default:
|
|
s.logger.Debugf("Dropping packet from %s (%d bytes). Server receive queue full.", p.remoteAddr, p.Size())
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropDOSPrevention)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer still in use? */ {
|
|
if !s.nextZeroRTTCleanup.IsZero() && p.rcvTime.After(s.nextZeroRTTCleanup) {
|
|
defer s.cleanupZeroRTTQueues(p.rcvTime)
|
|
}
|
|
|
|
if wire.IsVersionNegotiationPacket(p.data) {
|
|
s.logger.Debugf("Dropping Version Negotiation packet.")
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeVersionNegotiation, p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return false
|
|
}
|
|
// Short header packets should never end up here in the first place
|
|
if !wire.IsLongHeaderPacket(p.data[0]) {
|
|
panic(fmt.Sprintf("misrouted packet: %#v", p.data))
|
|
}
|
|
v, err := wire.ParseVersion(p.data)
|
|
// drop the packet if we failed to parse the protocol version
|
|
if err != nil {
|
|
s.logger.Debugf("Dropping a packet with an unknown version")
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return false
|
|
}
|
|
// send a Version Negotiation Packet if the client is speaking a different protocol version
|
|
if !protocol.IsSupportedVersion(s.config.Versions, v) {
|
|
if s.config.DisableVersionNegotiationPackets {
|
|
return false
|
|
}
|
|
|
|
if p.Size() < protocol.MinUnknownVersionPacketSize {
|
|
s.logger.Debugf("Dropping a packet with an unsupported version number %d that is too small (%d bytes)", v, p.Size())
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return false
|
|
}
|
|
return s.enqueueVersionNegotiationPacket(p)
|
|
}
|
|
|
|
if wire.Is0RTTPacket(p.data) {
|
|
if !s.acceptEarlyConns {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return false
|
|
}
|
|
return s.handle0RTTPacket(p)
|
|
}
|
|
|
|
// If we're creating a new connection, the packet will be passed to the connection.
|
|
// The header will then be parsed again.
|
|
hdr, _, _, err := wire.ParsePacket(p.data)
|
|
if err != nil {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropHeaderParseError)
|
|
}
|
|
s.logger.Debugf("Error parsing packet: %s", err)
|
|
return false
|
|
}
|
|
if hdr.Type == protocol.PacketTypeInitial && p.Size() < protocol.MinInitialPacketSize {
|
|
s.logger.Debugf("Dropping a packet that is too small to be a valid Initial (%d bytes)", p.Size())
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return false
|
|
}
|
|
|
|
if hdr.Type != protocol.PacketTypeInitial {
|
|
// Drop long header packets.
|
|
// There's little point in sending a Stateless Reset, since the client
|
|
// might not have received the token yet.
|
|
s.logger.Debugf("Dropping long header packet of type %s (%d bytes)", hdr.Type, len(p.data))
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeFromHeader(hdr), p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return false
|
|
}
|
|
|
|
s.logger.Debugf("<- Received Initial packet.")
|
|
|
|
if err := s.handleInitialImpl(p, hdr); err != nil {
|
|
s.logger.Errorf("Error occurred handling initial packet: %s", err)
|
|
}
|
|
// Don't put the packet buffer back.
|
|
// handleInitialImpl deals with the buffer.
|
|
return true
|
|
}
|
|
|
|
func (s *baseServer) handle0RTTPacket(p receivedPacket) bool {
|
|
connID, err := wire.ParseConnectionID(p.data, 0)
|
|
if err != nil {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropHeaderParseError)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// check again if we might have a connection now
|
|
if handler, ok := s.connHandler.Get(connID); ok {
|
|
handler.handlePacket(p)
|
|
return true
|
|
}
|
|
|
|
if q, ok := s.zeroRTTQueues[connID]; ok {
|
|
if len(q.packets) >= protocol.Max0RTTQueueLen {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention)
|
|
}
|
|
return false
|
|
}
|
|
q.packets = append(q.packets, p)
|
|
return true
|
|
}
|
|
|
|
if len(s.zeroRTTQueues) >= protocol.Max0RTTQueues {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention)
|
|
}
|
|
return false
|
|
}
|
|
queue := &zeroRTTQueue{packets: make([]receivedPacket, 1, 8)}
|
|
queue.packets[0] = p
|
|
expiration := p.rcvTime.Add(protocol.Max0RTTQueueingDuration)
|
|
queue.expiration = expiration
|
|
if s.nextZeroRTTCleanup.IsZero() || s.nextZeroRTTCleanup.After(expiration) {
|
|
s.nextZeroRTTCleanup = expiration
|
|
}
|
|
s.zeroRTTQueues[connID] = queue
|
|
return true
|
|
}
|
|
|
|
func (s *baseServer) cleanupZeroRTTQueues(now time.Time) {
|
|
// Iterate over all queues to find those that are expired.
|
|
// This is ok since we're placing a pretty low limit on the number of queues.
|
|
var nextCleanup time.Time
|
|
for connID, q := range s.zeroRTTQueues {
|
|
if q.expiration.After(now) {
|
|
if nextCleanup.IsZero() || nextCleanup.After(q.expiration) {
|
|
nextCleanup = q.expiration
|
|
}
|
|
continue
|
|
}
|
|
for _, p := range q.packets {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketType0RTT, p.Size(), logging.PacketDropDOSPrevention)
|
|
}
|
|
p.buffer.Release()
|
|
}
|
|
delete(s.zeroRTTQueues, connID)
|
|
if s.logger.Debug() {
|
|
s.logger.Debugf("Removing 0-RTT queue for %s.", connID)
|
|
}
|
|
}
|
|
s.nextZeroRTTCleanup = nextCleanup
|
|
}
|
|
|
|
// validateToken returns false if:
|
|
// - address is invalid
|
|
// - token is expired
|
|
// - token is null
|
|
func (s *baseServer) validateToken(token *handshake.Token, addr net.Addr) bool {
|
|
if token == nil {
|
|
return false
|
|
}
|
|
if !token.ValidateRemoteAddr(addr) {
|
|
return false
|
|
}
|
|
if !token.IsRetryToken && time.Since(token.SentTime) > s.config.MaxTokenAge {
|
|
return false
|
|
}
|
|
if token.IsRetryToken && time.Since(token.SentTime) > s.config.MaxRetryTokenAge {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error {
|
|
if len(hdr.Token) == 0 && hdr.DestConnectionID.Len() < protocol.MinConnectionIDLenInitial {
|
|
p.buffer.Release()
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return errors.New("too short connection ID")
|
|
}
|
|
|
|
// The server queues packets for a while, and we might already have established a connection by now.
|
|
// This results in a second check in the connection map.
|
|
// That's ok since it's not the hot path (it's only taken by some Initial and 0-RTT packets).
|
|
if handler, ok := s.connHandler.Get(hdr.DestConnectionID); ok {
|
|
handler.handlePacket(p)
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
token *handshake.Token
|
|
retrySrcConnID *protocol.ConnectionID
|
|
)
|
|
origDestConnID := hdr.DestConnectionID
|
|
if len(hdr.Token) > 0 {
|
|
tok, err := s.tokenGenerator.DecodeToken(hdr.Token)
|
|
if err == nil {
|
|
if tok.IsRetryToken {
|
|
origDestConnID = tok.OriginalDestConnectionID
|
|
retrySrcConnID = &tok.RetrySrcConnectionID
|
|
}
|
|
token = tok
|
|
}
|
|
}
|
|
|
|
clientAddrIsValid := s.validateToken(token, p.remoteAddr)
|
|
if token != nil && !clientAddrIsValid {
|
|
// For invalid and expired non-retry tokens, we don't send an INVALID_TOKEN error.
|
|
// We just ignore them, and act as if there was no token on this packet at all.
|
|
// This also means we might send a Retry later.
|
|
if !token.IsRetryToken {
|
|
token = nil
|
|
} else {
|
|
// For Retry tokens, we send an INVALID_ERROR if
|
|
// * the token is too old, or
|
|
// * the token is invalid, in case of a retry token.
|
|
s.enqueueInvalidToken(p)
|
|
return nil
|
|
}
|
|
}
|
|
if token == nil && s.config.RequireAddressValidation(p.remoteAddr) {
|
|
// Retry invalidates all 0-RTT packets sent.
|
|
delete(s.zeroRTTQueues, hdr.DestConnectionID)
|
|
go func() {
|
|
defer p.buffer.Release()
|
|
if err := s.sendRetry(p.remoteAddr, hdr, p.info); err != nil {
|
|
s.logger.Debugf("Error sending Retry: %s", err)
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
if queueLen := atomic.LoadInt32(&s.connQueueLen); queueLen >= protocol.MaxAcceptQueueSize {
|
|
s.logger.Debugf("Rejecting new connection. Server currently busy. Accept queue length: %d (max %d)", queueLen, protocol.MaxAcceptQueueSize)
|
|
go func() {
|
|
defer p.buffer.Release()
|
|
if err := s.sendConnectionRefused(p.remoteAddr, hdr, p.info); err != nil {
|
|
s.logger.Debugf("Error rejecting connection: %s", err)
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
connID, err := s.connIDGenerator.GenerateConnectionID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.logger.Debugf("Changing connection ID to %s.", connID)
|
|
var conn quicConn
|
|
tracingID := nextConnTracingID()
|
|
if added := s.connHandler.AddWithConnID(hdr.DestConnectionID, connID, func() (packetHandler, bool) {
|
|
config := s.config
|
|
if s.config.GetConfigForClient != nil {
|
|
conf, err := s.config.GetConfigForClient(&ClientHelloInfo{RemoteAddr: p.remoteAddr})
|
|
if err != nil {
|
|
s.logger.Debugf("Rejecting new connection due to GetConfigForClient callback")
|
|
return nil, false
|
|
}
|
|
config = populateConfig(conf)
|
|
}
|
|
var tracer logging.ConnectionTracer
|
|
if config.Tracer != nil {
|
|
// Use the same connection ID that is passed to the client's GetLogWriter callback.
|
|
connID := hdr.DestConnectionID
|
|
if origDestConnID.Len() > 0 {
|
|
connID = origDestConnID
|
|
}
|
|
tracer = config.Tracer(context.WithValue(context.Background(), ConnectionTracingKey, tracingID), protocol.PerspectiveServer, connID)
|
|
}
|
|
conn = s.newConn(
|
|
newSendConnWithPacketInfo(s.conn, p.remoteAddr, p.info),
|
|
s.connHandler,
|
|
origDestConnID,
|
|
retrySrcConnID,
|
|
hdr.DestConnectionID,
|
|
hdr.SrcConnectionID,
|
|
connID,
|
|
s.connIDGenerator,
|
|
s.connHandler.GetStatelessResetToken(connID),
|
|
config,
|
|
s.tlsConf,
|
|
s.tokenGenerator,
|
|
clientAddrIsValid,
|
|
tracer,
|
|
tracingID,
|
|
s.logger,
|
|
hdr.Version,
|
|
)
|
|
conn.handlePacket(p)
|
|
|
|
if q, ok := s.zeroRTTQueues[hdr.DestConnectionID]; ok {
|
|
for _, p := range q.packets {
|
|
conn.handlePacket(p)
|
|
}
|
|
delete(s.zeroRTTQueues, hdr.DestConnectionID)
|
|
}
|
|
|
|
return conn, true
|
|
}); !added {
|
|
go func() {
|
|
defer p.buffer.Release()
|
|
if err := s.sendConnectionRefused(p.remoteAddr, hdr, p.info); err != nil {
|
|
s.logger.Debugf("Error rejecting connection: %s", err)
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
go conn.run()
|
|
go s.handleNewConn(conn)
|
|
if conn == nil {
|
|
p.buffer.Release()
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *baseServer) handleNewConn(conn quicConn) {
|
|
connCtx := conn.Context()
|
|
if s.acceptEarlyConns {
|
|
// wait until the early connection is ready (or the handshake fails)
|
|
select {
|
|
case <-conn.earlyConnReady():
|
|
case <-connCtx.Done():
|
|
return
|
|
}
|
|
} else {
|
|
// wait until the handshake is complete (or fails)
|
|
select {
|
|
case <-conn.HandshakeComplete():
|
|
case <-connCtx.Done():
|
|
return
|
|
}
|
|
}
|
|
|
|
atomic.AddInt32(&s.connQueueLen, 1)
|
|
select {
|
|
case s.connQueue <- conn:
|
|
// blocks until the connection is accepted
|
|
case <-connCtx.Done():
|
|
atomic.AddInt32(&s.connQueueLen, -1)
|
|
// don't pass connections that were already closed to Accept()
|
|
}
|
|
}
|
|
|
|
func (s *baseServer) sendRetry(remoteAddr net.Addr, hdr *wire.Header, info packetInfo) error {
|
|
// Log the Initial packet now.
|
|
// If no Retry is sent, the packet will be logged by the connection.
|
|
(&wire.ExtendedHeader{Header: *hdr}).Log(s.logger)
|
|
srcConnID, err := s.connIDGenerator.GenerateConnectionID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
token, err := s.tokenGenerator.NewRetryToken(remoteAddr, hdr.DestConnectionID, srcConnID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
replyHdr := &wire.ExtendedHeader{}
|
|
replyHdr.Type = protocol.PacketTypeRetry
|
|
replyHdr.Version = hdr.Version
|
|
replyHdr.SrcConnectionID = srcConnID
|
|
replyHdr.DestConnectionID = hdr.SrcConnectionID
|
|
replyHdr.Token = token
|
|
if s.logger.Debug() {
|
|
s.logger.Debugf("Changing connection ID to %s.", srcConnID)
|
|
s.logger.Debugf("-> Sending Retry")
|
|
replyHdr.Log(s.logger)
|
|
}
|
|
|
|
buf := getPacketBuffer()
|
|
defer buf.Release()
|
|
buf.Data, err = replyHdr.Append(buf.Data, hdr.Version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// append the Retry integrity tag
|
|
tag := handshake.GetRetryIntegrityTag(buf.Data, hdr.DestConnectionID, hdr.Version)
|
|
buf.Data = append(buf.Data, tag[:]...)
|
|
if s.tracer != nil {
|
|
s.tracer.SentPacket(remoteAddr, &replyHdr.Header, protocol.ByteCount(len(buf.Data)), nil)
|
|
}
|
|
_, err = s.conn.WritePacket(buf.Data, uint16(len(buf.Data)), remoteAddr, info.OOB())
|
|
return err
|
|
}
|
|
|
|
func (s *baseServer) enqueueInvalidToken(p receivedPacket) {
|
|
select {
|
|
case s.invalidTokenQueue <- p:
|
|
default:
|
|
// it's fine to drop INVALID_TOKEN packets when we are busy
|
|
p.buffer.Release()
|
|
}
|
|
}
|
|
|
|
func (s *baseServer) maybeSendInvalidToken(p receivedPacket) {
|
|
defer p.buffer.Release()
|
|
|
|
hdr, _, _, err := wire.ParsePacket(p.data)
|
|
if err != nil {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropHeaderParseError)
|
|
}
|
|
s.logger.Debugf("Error parsing packet: %s", err)
|
|
return
|
|
}
|
|
|
|
// Only send INVALID_TOKEN if we can unprotect the packet.
|
|
// This makes sure that we won't send it for packets that were corrupted.
|
|
sealer, opener := handshake.NewInitialAEAD(hdr.DestConnectionID, protocol.PerspectiveServer, hdr.Version)
|
|
data := p.data[:hdr.ParsedLen()+hdr.Length]
|
|
extHdr, err := unpackLongHeader(opener, hdr, data, hdr.Version)
|
|
// Only send INVALID_TOKEN if we can unprotect the packet.
|
|
// This makes sure that we won't send it for packets that were corrupted.
|
|
if err != nil {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropHeaderParseError)
|
|
}
|
|
return
|
|
}
|
|
hdrLen := extHdr.ParsedLen()
|
|
if _, err := opener.Open(data[hdrLen:hdrLen], data[hdrLen:], extHdr.PacketNumber, data[:hdrLen]); err != nil {
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeInitial, p.Size(), logging.PacketDropPayloadDecryptError)
|
|
}
|
|
return
|
|
}
|
|
if s.logger.Debug() {
|
|
s.logger.Debugf("Client sent an invalid retry token. Sending INVALID_TOKEN to %s.", p.remoteAddr)
|
|
}
|
|
if err := s.sendError(p.remoteAddr, hdr, sealer, qerr.InvalidToken, p.info); err != nil {
|
|
s.logger.Debugf("Error sending INVALID_TOKEN error: %s", err)
|
|
}
|
|
}
|
|
|
|
func (s *baseServer) sendConnectionRefused(remoteAddr net.Addr, hdr *wire.Header, info packetInfo) error {
|
|
sealer, _ := handshake.NewInitialAEAD(hdr.DestConnectionID, protocol.PerspectiveServer, hdr.Version)
|
|
return s.sendError(remoteAddr, hdr, sealer, qerr.ConnectionRefused, info)
|
|
}
|
|
|
|
// sendError sends the error as a response to the packet received with header hdr
|
|
func (s *baseServer) sendError(remoteAddr net.Addr, hdr *wire.Header, sealer handshake.LongHeaderSealer, errorCode qerr.TransportErrorCode, info packetInfo) error {
|
|
b := getPacketBuffer()
|
|
defer b.Release()
|
|
|
|
ccf := &wire.ConnectionCloseFrame{ErrorCode: uint64(errorCode)}
|
|
|
|
replyHdr := &wire.ExtendedHeader{}
|
|
replyHdr.Type = protocol.PacketTypeInitial
|
|
replyHdr.Version = hdr.Version
|
|
replyHdr.SrcConnectionID = hdr.DestConnectionID
|
|
replyHdr.DestConnectionID = hdr.SrcConnectionID
|
|
replyHdr.PacketNumberLen = protocol.PacketNumberLen4
|
|
replyHdr.Length = 4 /* packet number len */ + ccf.Length(hdr.Version) + protocol.ByteCount(sealer.Overhead())
|
|
var err error
|
|
b.Data, err = replyHdr.Append(b.Data, hdr.Version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
payloadOffset := len(b.Data)
|
|
|
|
b.Data, err = ccf.Append(b.Data, hdr.Version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_ = sealer.Seal(b.Data[payloadOffset:payloadOffset], b.Data[payloadOffset:], replyHdr.PacketNumber, b.Data[:payloadOffset])
|
|
b.Data = b.Data[0 : len(b.Data)+sealer.Overhead()]
|
|
|
|
pnOffset := payloadOffset - int(replyHdr.PacketNumberLen)
|
|
sealer.EncryptHeader(
|
|
b.Data[pnOffset+4:pnOffset+4+16],
|
|
&b.Data[0],
|
|
b.Data[pnOffset:payloadOffset],
|
|
)
|
|
|
|
replyHdr.Log(s.logger)
|
|
wire.LogFrame(s.logger, ccf, true)
|
|
if s.tracer != nil {
|
|
s.tracer.SentPacket(remoteAddr, &replyHdr.Header, protocol.ByteCount(len(b.Data)), []logging.Frame{ccf})
|
|
}
|
|
_, err = s.conn.WritePacket(b.Data, uint16(len(b.Data)), remoteAddr, info.OOB())
|
|
return err
|
|
}
|
|
|
|
func (s *baseServer) enqueueVersionNegotiationPacket(p receivedPacket) (bufferInUse bool) {
|
|
select {
|
|
case s.versionNegotiationQueue <- p:
|
|
return true
|
|
default:
|
|
// it's fine to not send version negotiation packets when we are busy
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *baseServer) maybeSendVersionNegotiationPacket(p receivedPacket) {
|
|
defer p.buffer.Release()
|
|
|
|
v, err := wire.ParseVersion(p.data)
|
|
if err != nil {
|
|
s.logger.Debugf("failed to parse version for sending version negotiation packet: %s", err)
|
|
return
|
|
}
|
|
|
|
_, src, dest, err := wire.ParseArbitraryLenConnectionIDs(p.data)
|
|
if err != nil { // should never happen
|
|
s.logger.Debugf("Dropping a packet with an unknown version for which we failed to parse connection IDs")
|
|
if s.tracer != nil {
|
|
s.tracer.DroppedPacket(p.remoteAddr, logging.PacketTypeNotDetermined, p.Size(), logging.PacketDropUnexpectedPacket)
|
|
}
|
|
return
|
|
}
|
|
|
|
s.logger.Debugf("Client offered version %s, sending Version Negotiation", v)
|
|
|
|
data := wire.ComposeVersionNegotiation(dest, src, s.config.Versions)
|
|
if s.tracer != nil {
|
|
s.tracer.SentVersionNegotiationPacket(p.remoteAddr, src, dest, s.config.Versions)
|
|
}
|
|
if _, err := s.conn.WritePacket(data, uint16(len(data)), p.remoteAddr, p.info.OOB()); err != nil {
|
|
s.logger.Debugf("Error sending Version Negotiation: %s", err)
|
|
}
|
|
}
|