cloudflared/quic/datagram.go
Nuno Diegues 475939a77f TUN-6191: Update quic-go to v0.27.1 and with custom patch to allow keep alive period to be configurable
The idle period is set to 5sec.

We now also ping every second since last activity.
This makes the quic.Connection less prone to being closed with
no network activity, since we send multiple pings per idle
period, and thus a single packet loss cannot cause the problem.
2022-06-07 12:25:18 +01:00

92 lines
3.1 KiB
Go

package quic
import (
"fmt"
"github.com/google/uuid"
"github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
const (
sessionIDLen = len(uuid.UUID{})
)
type DatagramMuxer struct {
session quic.Connection
logger *zerolog.Logger
}
func NewDatagramMuxer(quicSession quic.Connection, logger *zerolog.Logger) (*DatagramMuxer, error) {
return &DatagramMuxer{
session: quicSession,
logger: logger,
}, nil
}
// SendTo suffix the session ID to the payload so the other end of the QUIC session can demultiplex
// the payload from multiple datagram sessions
func (dm *DatagramMuxer) SendTo(sessionID uuid.UUID, payload []byte) error {
if len(payload) > maxDatagramPayloadSize {
// TODO: TUN-5302 return ICMP packet too big message
return fmt.Errorf("origin UDP payload has %d bytes, which exceeds transport MTU %d", len(payload), dm.MTU())
}
msgWithID, err := suffixSessionID(sessionID, payload)
if err != nil {
return errors.Wrap(err, "Failed to suffix session ID to datagram, it will be dropped")
}
if err := dm.session.SendMessage(msgWithID); err != nil {
return errors.Wrap(err, "Failed to send datagram back to edge")
}
dm.logger.Debug().Str("sessionID", sessionID.String()).Int("bytes", len(payload)).Msg("Send datagram back to edge")
return nil
}
// ReceiveFrom extracts datagram session ID, then sends the session ID and payload to session manager
// which determines how to proxy to the origin. It assumes the datagram session has already been
// registered with session manager through other side channel
func (dm *DatagramMuxer) ReceiveFrom() (uuid.UUID, []byte, error) {
msg, err := dm.session.ReceiveMessage()
if err != nil {
return uuid.Nil, nil, err
}
sessionID, payload, err := extractSessionID(msg)
if err != nil {
return uuid.Nil, nil, err
}
dm.logger.Debug().Str("sessionID", sessionID.String()).Int("bytes", len(payload)).Msg("Received datagram from edge")
return sessionID, payload, nil
}
// Maximum application payload to send to / receive from QUIC datagram frame
func (dm *DatagramMuxer) MTU() int {
return maxDatagramPayloadSize
}
// Each QUIC datagram should be suffixed with session ID.
// extractSessionID extracts the session ID and a slice with only the payload
func extractSessionID(b []byte) (uuid.UUID, []byte, error) {
msgLen := len(b)
if msgLen < sessionIDLen {
return uuid.Nil, nil, fmt.Errorf("session ID has %d bytes, but data only has %d", sessionIDLen, len(b))
}
// Parse last 16 bytess as UUID and remove it from slice
sessionID, err := uuid.FromBytes(b[len(b)-sessionIDLen:])
if err != nil {
return uuid.Nil, nil, err
}
b = b[:len(b)-sessionIDLen]
return sessionID, b, nil
}
// SuffixSessionID appends the session ID at the end of the payload. Suffix is more performant than prefix because
// the payload slice might already have enough capacity to append the session ID at the end
func suffixSessionID(sessionID uuid.UUID, b []byte) ([]byte, error) {
if len(b)+len(sessionID) > MaxDatagramFrameSize {
return nil, fmt.Errorf("datagram size exceed %d", MaxDatagramFrameSize)
}
b = append(b, sessionID[:]...)
return b, nil
}