TUN-8701: Add metrics and adjust logs for datagram v3
Closes TUN-8701
This commit is contained in:
		
							parent
							
								
									952622a965
								
							
						
					
					
						commit
						1f3e3045ad
					
				| 
						 | 
					@ -10,6 +10,7 @@ import (
 | 
				
			||||||
	"github.com/quic-go/quic-go"
 | 
						"github.com/quic-go/quic-go"
 | 
				
			||||||
	"github.com/rs/zerolog"
 | 
						"github.com/rs/zerolog"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/cloudflare/cloudflared/management"
 | 
				
			||||||
	cfdquic "github.com/cloudflare/cloudflared/quic/v3"
 | 
						cfdquic "github.com/cloudflare/cloudflared/quic/v3"
 | 
				
			||||||
	"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
 | 
						"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -25,9 +26,15 @@ func NewDatagramV3Connection(ctx context.Context,
 | 
				
			||||||
	conn quic.Connection,
 | 
						conn quic.Connection,
 | 
				
			||||||
	sessionManager cfdquic.SessionManager,
 | 
						sessionManager cfdquic.SessionManager,
 | 
				
			||||||
	index uint8,
 | 
						index uint8,
 | 
				
			||||||
 | 
						metrics cfdquic.Metrics,
 | 
				
			||||||
	logger *zerolog.Logger,
 | 
						logger *zerolog.Logger,
 | 
				
			||||||
) DatagramSessionHandler {
 | 
					) DatagramSessionHandler {
 | 
				
			||||||
	datagramMuxer := cfdquic.NewDatagramConn(conn, sessionManager, index, logger)
 | 
						log := logger.
 | 
				
			||||||
 | 
							With().
 | 
				
			||||||
 | 
							Int(management.EventTypeKey, int(management.UDP)).
 | 
				
			||||||
 | 
							Uint8(LogFieldConnIndex, index).
 | 
				
			||||||
 | 
							Logger()
 | 
				
			||||||
 | 
						datagramMuxer := cfdquic.NewDatagramConn(conn, sessionManager, index, metrics, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &datagramV3Connection{
 | 
						return &datagramV3Connection{
 | 
				
			||||||
		conn,
 | 
							conn,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,11 +13,11 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	// ErrSessionNotFound indicates that a session has not been registered yet for the request id.
 | 
						// ErrSessionNotFound indicates that a session has not been registered yet for the request id.
 | 
				
			||||||
	ErrSessionNotFound = errors.New("session not found")
 | 
						ErrSessionNotFound = errors.New("flow not found")
 | 
				
			||||||
	// ErrSessionBoundToOtherConn is returned when a registration already exists for a different connection.
 | 
						// ErrSessionBoundToOtherConn is returned when a registration already exists for a different connection.
 | 
				
			||||||
	ErrSessionBoundToOtherConn = errors.New("session is in use by another connection")
 | 
						ErrSessionBoundToOtherConn = errors.New("flow is in use by another connection")
 | 
				
			||||||
	// ErrSessionAlreadyRegistered is returned when a registration already exists for this connection.
 | 
						// ErrSessionAlreadyRegistered is returned when a registration already exists for this connection.
 | 
				
			||||||
	ErrSessionAlreadyRegistered = errors.New("session is already registered for this connection")
 | 
						ErrSessionAlreadyRegistered = errors.New("flow is already registered for this connection")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SessionManager interface {
 | 
					type SessionManager interface {
 | 
				
			||||||
| 
						 | 
					@ -39,12 +39,14 @@ type DialUDP func(dest netip.AddrPort) (*net.UDPConn, error)
 | 
				
			||||||
type sessionManager struct {
 | 
					type sessionManager struct {
 | 
				
			||||||
	sessions map[RequestID]Session
 | 
						sessions map[RequestID]Session
 | 
				
			||||||
	mutex    sync.RWMutex
 | 
						mutex    sync.RWMutex
 | 
				
			||||||
 | 
						metrics  Metrics
 | 
				
			||||||
	log      *zerolog.Logger
 | 
						log      *zerolog.Logger
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewSessionManager(log *zerolog.Logger, originDialer DialUDP) SessionManager {
 | 
					func NewSessionManager(metrics Metrics, log *zerolog.Logger, originDialer DialUDP) SessionManager {
 | 
				
			||||||
	return &sessionManager{
 | 
						return &sessionManager{
 | 
				
			||||||
		sessions: make(map[RequestID]Session),
 | 
							sessions: make(map[RequestID]Session),
 | 
				
			||||||
 | 
							metrics:  metrics,
 | 
				
			||||||
		log:      log,
 | 
							log:      log,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -65,7 +67,7 @@ func (s *sessionManager) RegisterSession(request *UDPSessionRegistrationDatagram
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Create and insert the new session in the map
 | 
						// Create and insert the new session in the map
 | 
				
			||||||
	session := NewSession(request.RequestID, request.IdleDurationHint, origin, conn, s.log)
 | 
						session := NewSession(request.RequestID, request.IdleDurationHint, origin, conn, s.metrics, s.log)
 | 
				
			||||||
	s.sessions[request.RequestID] = session
 | 
						s.sessions[request.RequestID] = session
 | 
				
			||||||
	return session, nil
 | 
						return session, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRegisterSession(t *testing.T) {
 | 
					func TestRegisterSession(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	manager := v3.NewSessionManager(&log, ingress.DialUDPAddrPort)
 | 
						manager := v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	request := v3.UDPSessionRegistrationDatagram{
 | 
						request := v3.UDPSessionRegistrationDatagram{
 | 
				
			||||||
		RequestID:        testRequestID,
 | 
							RequestID:        testRequestID,
 | 
				
			||||||
| 
						 | 
					@ -71,7 +71,7 @@ func TestRegisterSession(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestGetSession_Empty(t *testing.T) {
 | 
					func TestGetSession_Empty(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	manager := v3.NewSessionManager(&log, ingress.DialUDPAddrPort)
 | 
						manager := v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err := manager.GetSession(testRequestID)
 | 
						_, err := manager.GetSession(testRequestID)
 | 
				
			||||||
	if !errors.Is(err, v3.ErrSessionNotFound) {
 | 
						if !errors.Is(err, v3.ErrSessionNotFound) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					package v3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						namespace = "cloudflared"
 | 
				
			||||||
 | 
						subsystem = "udp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Metrics interface {
 | 
				
			||||||
 | 
						IncrementFlows()
 | 
				
			||||||
 | 
						DecrementFlows()
 | 
				
			||||||
 | 
						PayloadTooLarge()
 | 
				
			||||||
 | 
						RetryFlowResponse()
 | 
				
			||||||
 | 
						MigrateFlow()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type metrics struct {
 | 
				
			||||||
 | 
						activeUDPFlows     prometheus.Gauge
 | 
				
			||||||
 | 
						totalUDPFlows      prometheus.Counter
 | 
				
			||||||
 | 
						payloadTooLarge    prometheus.Counter
 | 
				
			||||||
 | 
						retryFlowResponses prometheus.Counter
 | 
				
			||||||
 | 
						migratedFlows      prometheus.Counter
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *metrics) IncrementFlows() {
 | 
				
			||||||
 | 
						m.totalUDPFlows.Inc()
 | 
				
			||||||
 | 
						m.activeUDPFlows.Inc()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *metrics) DecrementFlows() {
 | 
				
			||||||
 | 
						m.activeUDPFlows.Dec()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *metrics) PayloadTooLarge() {
 | 
				
			||||||
 | 
						m.payloadTooLarge.Inc()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *metrics) RetryFlowResponse() {
 | 
				
			||||||
 | 
						m.retryFlowResponses.Inc()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *metrics) MigrateFlow() {
 | 
				
			||||||
 | 
						m.migratedFlows.Inc()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewMetrics(registerer prometheus.Registerer) Metrics {
 | 
				
			||||||
 | 
						m := &metrics{
 | 
				
			||||||
 | 
							activeUDPFlows: prometheus.NewGauge(prometheus.GaugeOpts{
 | 
				
			||||||
 | 
								Namespace: namespace,
 | 
				
			||||||
 | 
								Subsystem: subsystem,
 | 
				
			||||||
 | 
								Name:      "active_flows",
 | 
				
			||||||
 | 
								Help:      "Concurrent count of UDP flows that are being proxied to any origin",
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							totalUDPFlows: prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
								Namespace: namespace,
 | 
				
			||||||
 | 
								Subsystem: subsystem,
 | 
				
			||||||
 | 
								Name:      "total_flows",
 | 
				
			||||||
 | 
								Help:      "Total count of UDP flows that have been proxied to any origin",
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							payloadTooLarge: prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
								Namespace: namespace,
 | 
				
			||||||
 | 
								Subsystem: subsystem,
 | 
				
			||||||
 | 
								Name:      "payload_too_large",
 | 
				
			||||||
 | 
								Help:      "Total count of UDP flows that have had origin payloads that are too large to proxy",
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							retryFlowResponses: prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
								Namespace: namespace,
 | 
				
			||||||
 | 
								Subsystem: subsystem,
 | 
				
			||||||
 | 
								Name:      "retry_flow_responses",
 | 
				
			||||||
 | 
								Help:      "Total count of UDP flows that have had to send their registration response more than once",
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							migratedFlows: prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
								Namespace: namespace,
 | 
				
			||||||
 | 
								Subsystem: subsystem,
 | 
				
			||||||
 | 
								Name:      "migrated_flows",
 | 
				
			||||||
 | 
								Help:      "Total count of UDP flows have been migrated across local connections",
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						registerer.MustRegister(
 | 
				
			||||||
 | 
							m.activeUDPFlows,
 | 
				
			||||||
 | 
							m.totalUDPFlows,
 | 
				
			||||||
 | 
							m.payloadTooLarge,
 | 
				
			||||||
 | 
							m.retryFlowResponses,
 | 
				
			||||||
 | 
							m.migratedFlows,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						return m
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					package v3_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type noopMetrics struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (noopMetrics) IncrementFlows()    {}
 | 
				
			||||||
 | 
					func (noopMetrics) DecrementFlows()    {}
 | 
				
			||||||
 | 
					func (noopMetrics) PayloadTooLarge()   {}
 | 
				
			||||||
 | 
					func (noopMetrics) RetryFlowResponse() {}
 | 
				
			||||||
 | 
					func (noopMetrics) MigrateFlow()       {}
 | 
				
			||||||
| 
						 | 
					@ -45,18 +45,20 @@ type datagramConn struct {
 | 
				
			||||||
	conn           QuicConnection
 | 
						conn           QuicConnection
 | 
				
			||||||
	index          uint8
 | 
						index          uint8
 | 
				
			||||||
	sessionManager SessionManager
 | 
						sessionManager SessionManager
 | 
				
			||||||
 | 
						metrics        Metrics
 | 
				
			||||||
	logger         *zerolog.Logger
 | 
						logger         *zerolog.Logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	datagrams  chan []byte
 | 
						datagrams  chan []byte
 | 
				
			||||||
	readErrors chan error
 | 
						readErrors chan error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewDatagramConn(conn QuicConnection, sessionManager SessionManager, index uint8, logger *zerolog.Logger) DatagramConn {
 | 
					func NewDatagramConn(conn QuicConnection, sessionManager SessionManager, index uint8, metrics Metrics, logger *zerolog.Logger) DatagramConn {
 | 
				
			||||||
	log := logger.With().Uint8("datagramVersion", 3).Logger()
 | 
						log := logger.With().Uint8("datagramVersion", 3).Logger()
 | 
				
			||||||
	return &datagramConn{
 | 
						return &datagramConn{
 | 
				
			||||||
		conn:           conn,
 | 
							conn:           conn,
 | 
				
			||||||
		index:          index,
 | 
							index:          index,
 | 
				
			||||||
		sessionManager: sessionManager,
 | 
							sessionManager: sessionManager,
 | 
				
			||||||
 | 
							metrics:        metrics,
 | 
				
			||||||
		logger:         &log,
 | 
							logger:         &log,
 | 
				
			||||||
		datagrams:      make(chan []byte, demuxChanCapacity),
 | 
							datagrams:      make(chan []byte, demuxChanCapacity),
 | 
				
			||||||
		readErrors:     make(chan error, 2),
 | 
							readErrors:     make(chan error, 2),
 | 
				
			||||||
| 
						 | 
					@ -143,11 +145,12 @@ func (c *datagramConn) Serve(ctx context.Context) error {
 | 
				
			||||||
					c.logger.Err(err).Msgf("unable to unmarshal session registration datagram")
 | 
										c.logger.Err(err).Msgf("unable to unmarshal session registration datagram")
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									logger := c.logger.With().Str(logFlowID, reg.RequestID.String()).Logger()
 | 
				
			||||||
				// We bind the new session to the quic connection context instead of cloudflared context to allow for the
 | 
									// We bind the new session to the quic connection context instead of cloudflared context to allow for the
 | 
				
			||||||
				// quic connection to close and close only the sessions bound to it. Closing of cloudflared will also
 | 
									// quic connection to close and close only the sessions bound to it. Closing of cloudflared will also
 | 
				
			||||||
				// initiate the close of the quic connection, so we don't have to worry about the application context
 | 
									// initiate the close of the quic connection, so we don't have to worry about the application context
 | 
				
			||||||
				// in the scope of a session.
 | 
									// in the scope of a session.
 | 
				
			||||||
				c.handleSessionRegistrationDatagram(connCtx, reg)
 | 
									c.handleSessionRegistrationDatagram(connCtx, reg, &logger)
 | 
				
			||||||
			case UDPSessionPayloadType:
 | 
								case UDPSessionPayloadType:
 | 
				
			||||||
				payload := &UDPSessionPayloadDatagram{}
 | 
									payload := &UDPSessionPayloadDatagram{}
 | 
				
			||||||
				err := payload.UnmarshalBinary(datagram)
 | 
									err := payload.UnmarshalBinary(datagram)
 | 
				
			||||||
| 
						 | 
					@ -155,7 +158,8 @@ func (c *datagramConn) Serve(ctx context.Context) error {
 | 
				
			||||||
					c.logger.Err(err).Msgf("unable to unmarshal session payload datagram")
 | 
										c.logger.Err(err).Msgf("unable to unmarshal session payload datagram")
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				c.handleSessionPayloadDatagram(payload)
 | 
									logger := c.logger.With().Str(logFlowID, payload.RequestID.String()).Logger()
 | 
				
			||||||
 | 
									c.handleSessionPayloadDatagram(payload, &logger)
 | 
				
			||||||
			case UDPSessionRegistrationResponseType:
 | 
								case UDPSessionRegistrationResponseType:
 | 
				
			||||||
				// cloudflared should never expect to receive UDP session responses as it will not initiate new
 | 
									// cloudflared should never expect to receive UDP session responses as it will not initiate new
 | 
				
			||||||
				// sessions towards the edge.
 | 
									// sessions towards the edge.
 | 
				
			||||||
| 
						 | 
					@ -169,31 +173,33 @@ func (c *datagramConn) Serve(ctx context.Context) error {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This method handles new registrations of a session and the serve loop for the session.
 | 
					// This method handles new registrations of a session and the serve loop for the session.
 | 
				
			||||||
func (c *datagramConn) handleSessionRegistrationDatagram(ctx context.Context, datagram *UDPSessionRegistrationDatagram) {
 | 
					func (c *datagramConn) handleSessionRegistrationDatagram(ctx context.Context, datagram *UDPSessionRegistrationDatagram, logger *zerolog.Logger) {
 | 
				
			||||||
	session, err := c.sessionManager.RegisterSession(datagram, c)
 | 
						session, err := c.sessionManager.RegisterSession(datagram, c)
 | 
				
			||||||
	switch err {
 | 
						switch err {
 | 
				
			||||||
	case nil:
 | 
						case nil:
 | 
				
			||||||
		// Continue as normal
 | 
							// Continue as normal
 | 
				
			||||||
	case ErrSessionAlreadyRegistered:
 | 
						case ErrSessionAlreadyRegistered:
 | 
				
			||||||
		// Session is already registered and likely the response got lost
 | 
							// Session is already registered and likely the response got lost
 | 
				
			||||||
		c.handleSessionAlreadyRegistered(datagram.RequestID)
 | 
							c.handleSessionAlreadyRegistered(datagram.RequestID, logger)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	case ErrSessionBoundToOtherConn:
 | 
						case ErrSessionBoundToOtherConn:
 | 
				
			||||||
		// Session is already registered but to a different connection
 | 
							// Session is already registered but to a different connection
 | 
				
			||||||
		c.handleSessionMigration(datagram.RequestID)
 | 
							c.handleSessionMigration(datagram.RequestID, logger)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		c.logger.Err(err).Msgf("session registration failure")
 | 
							logger.Err(err).Msgf("flow registration failure")
 | 
				
			||||||
		c.handleSessionRegistrationFailure(datagram.RequestID)
 | 
							c.handleSessionRegistrationFailure(datagram.RequestID, logger)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						c.metrics.IncrementFlows()
 | 
				
			||||||
	// Make sure to eventually remove the session from the session manager when the session is closed
 | 
						// Make sure to eventually remove the session from the session manager when the session is closed
 | 
				
			||||||
	defer c.sessionManager.UnregisterSession(session.ID())
 | 
						defer c.sessionManager.UnregisterSession(session.ID())
 | 
				
			||||||
 | 
						defer c.metrics.DecrementFlows()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Respond that we are able to process the new session
 | 
						// Respond that we are able to process the new session
 | 
				
			||||||
	err = c.SendUDPSessionResponse(datagram.RequestID, ResponseOk)
 | 
						err = c.SendUDPSessionResponse(datagram.RequestID, ResponseOk)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Err(err).Msgf("session registration failure: unable to send session registration response")
 | 
							logger.Err(err).Msgf("flow registration failure: unable to send session registration response")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -203,24 +209,24 @@ func (c *datagramConn) handleSessionRegistrationDatagram(ctx context.Context, da
 | 
				
			||||||
	if err == nil {
 | 
						if err == nil {
 | 
				
			||||||
		// We typically don't expect a session to close without some error response. [SessionIdleErr] is the typical
 | 
							// We typically don't expect a session to close without some error response. [SessionIdleErr] is the typical
 | 
				
			||||||
		// expected error response.
 | 
							// expected error response.
 | 
				
			||||||
		c.logger.Warn().Msg("session was closed without explicit close or timeout")
 | 
							logger.Warn().Msg("flow was closed without explicit close or timeout")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// SessionIdleErr and SessionCloseErr are valid and successful error responses to end a session.
 | 
						// SessionIdleErr and SessionCloseErr are valid and successful error responses to end a session.
 | 
				
			||||||
	if errors.Is(err, SessionIdleErr{}) || errors.Is(err, SessionCloseErr) {
 | 
						if errors.Is(err, SessionIdleErr{}) || errors.Is(err, SessionCloseErr) {
 | 
				
			||||||
		c.logger.Debug().Msg(err.Error())
 | 
							logger.Debug().Msg(err.Error())
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// All other errors should be reported as errors
 | 
						// All other errors should be reported as errors
 | 
				
			||||||
	c.logger.Err(err).Msgf("session was closed with an error")
 | 
						logger.Err(err).Msgf("flow was closed with an error")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *datagramConn) handleSessionAlreadyRegistered(requestID RequestID) {
 | 
					func (c *datagramConn) handleSessionAlreadyRegistered(requestID RequestID, logger *zerolog.Logger) {
 | 
				
			||||||
	// Send another registration response since the session is already active
 | 
						// Send another registration response since the session is already active
 | 
				
			||||||
	err := c.SendUDPSessionResponse(requestID, ResponseOk)
 | 
						err := c.SendUDPSessionResponse(requestID, ResponseOk)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Err(err).Msgf("session registration failure: unable to send an additional session registration response")
 | 
							logger.Err(err).Msgf("flow registration failure: unable to send an additional flow registration response")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -233,9 +239,10 @@ func (c *datagramConn) handleSessionAlreadyRegistered(requestID RequestID) {
 | 
				
			||||||
	// The session is already running in another routine so we want to restart the idle timeout since no proxied
 | 
						// The session is already running in another routine so we want to restart the idle timeout since no proxied
 | 
				
			||||||
	// packets have come down yet.
 | 
						// packets have come down yet.
 | 
				
			||||||
	session.ResetIdleTimer()
 | 
						session.ResetIdleTimer()
 | 
				
			||||||
 | 
						c.metrics.RetryFlowResponse()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *datagramConn) handleSessionMigration(requestID RequestID) {
 | 
					func (c *datagramConn) handleSessionMigration(requestID RequestID, logger *zerolog.Logger) {
 | 
				
			||||||
	// We need to migrate the currently running session to this edge connection.
 | 
						// We need to migrate the currently running session to this edge connection.
 | 
				
			||||||
	session, err := c.sessionManager.GetSession(requestID)
 | 
						session, err := c.sessionManager.GetSession(requestID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -250,29 +257,29 @@ func (c *datagramConn) handleSessionMigration(requestID RequestID) {
 | 
				
			||||||
	// Send another registration response since the session is already active
 | 
						// Send another registration response since the session is already active
 | 
				
			||||||
	err = c.SendUDPSessionResponse(requestID, ResponseOk)
 | 
						err = c.SendUDPSessionResponse(requestID, ResponseOk)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Err(err).Msgf("session registration failure: unable to send an additional session registration response")
 | 
							logger.Err(err).Msgf("flow registration failure: unable to send an additional flow registration response")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *datagramConn) handleSessionRegistrationFailure(requestID RequestID) {
 | 
					func (c *datagramConn) handleSessionRegistrationFailure(requestID RequestID, logger *zerolog.Logger) {
 | 
				
			||||||
	err := c.SendUDPSessionResponse(requestID, ResponseUnableToBindSocket)
 | 
						err := c.SendUDPSessionResponse(requestID, ResponseUnableToBindSocket)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Err(err).Msgf("unable to send session registration error response (%d)", ResponseUnableToBindSocket)
 | 
							logger.Err(err).Msgf("unable to send flow registration error response (%d)", ResponseUnableToBindSocket)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Handles incoming datagrams that need to be sent to a registered session.
 | 
					// Handles incoming datagrams that need to be sent to a registered session.
 | 
				
			||||||
func (c *datagramConn) handleSessionPayloadDatagram(datagram *UDPSessionPayloadDatagram) {
 | 
					func (c *datagramConn) handleSessionPayloadDatagram(datagram *UDPSessionPayloadDatagram, logger *zerolog.Logger) {
 | 
				
			||||||
	s, err := c.sessionManager.GetSession(datagram.RequestID)
 | 
						s, err := c.sessionManager.GetSession(datagram.RequestID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Err(err).Msgf("unable to find session")
 | 
							logger.Err(err).Msgf("unable to find flow")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// We ignore the bytes written to the socket because any partial write must return an error.
 | 
						// We ignore the bytes written to the socket because any partial write must return an error.
 | 
				
			||||||
	_, err = s.Write(datagram.Payload)
 | 
						_, err = s.Write(datagram.Payload)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Err(err).Msgf("unable to write payload for unavailable session")
 | 
							logger.Err(err).Msgf("unable to write payload for the flow")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,7 +72,7 @@ func (m *mockEyeball) SendUDPSessionResponse(id v3.RequestID, resp v3.SessionReg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestDatagramConn_New(t *testing.T) {
 | 
					func TestDatagramConn_New(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	conn := v3.NewDatagramConn(newMockQuicConn(), v3.NewSessionManager(&log, ingress.DialUDPAddrPort), 0, &log)
 | 
						conn := v3.NewDatagramConn(newMockQuicConn(), v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort), 0, &noopMetrics{}, &log)
 | 
				
			||||||
	if conn == nil {
 | 
						if conn == nil {
 | 
				
			||||||
		t.Fatal("expected valid connection")
 | 
							t.Fatal("expected valid connection")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ func TestDatagramConn_New(t *testing.T) {
 | 
				
			||||||
func TestDatagramConn_SendUDPSessionDatagram(t *testing.T) {
 | 
					func TestDatagramConn_SendUDPSessionDatagram(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&log, ingress.DialUDPAddrPort), 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort), 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	payload := []byte{0xef, 0xef}
 | 
						payload := []byte{0xef, 0xef}
 | 
				
			||||||
	conn.SendUDPSessionDatagram(payload)
 | 
						conn.SendUDPSessionDatagram(payload)
 | 
				
			||||||
| 
						 | 
					@ -94,7 +94,7 @@ func TestDatagramConn_SendUDPSessionDatagram(t *testing.T) {
 | 
				
			||||||
func TestDatagramConn_SendUDPSessionResponse(t *testing.T) {
 | 
					func TestDatagramConn_SendUDPSessionResponse(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&log, ingress.DialUDPAddrPort), 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort), 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	conn.SendUDPSessionResponse(testRequestID, v3.ResponseDestinationUnreachable)
 | 
						conn.SendUDPSessionResponse(testRequestID, v3.ResponseDestinationUnreachable)
 | 
				
			||||||
	resp := <-quic.recv
 | 
						resp := <-quic.recv
 | 
				
			||||||
| 
						 | 
					@ -115,7 +115,7 @@ func TestDatagramConn_SendUDPSessionResponse(t *testing.T) {
 | 
				
			||||||
func TestDatagramConnServe_ApplicationClosed(t *testing.T) {
 | 
					func TestDatagramConnServe_ApplicationClosed(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&log, ingress.DialUDPAddrPort), 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort), 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						defer cancel()
 | 
				
			||||||
| 
						 | 
					@ -131,7 +131,7 @@ func TestDatagramConnServe_ConnectionClosed(t *testing.T) {
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						defer cancel()
 | 
				
			||||||
	quic.ctx = ctx
 | 
						quic.ctx = ctx
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&log, ingress.DialUDPAddrPort), 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort), 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := conn.Serve(context.Background())
 | 
						err := conn.Serve(context.Background())
 | 
				
			||||||
	if !errors.Is(err, context.DeadlineExceeded) {
 | 
						if !errors.Is(err, context.DeadlineExceeded) {
 | 
				
			||||||
| 
						 | 
					@ -142,7 +142,7 @@ func TestDatagramConnServe_ConnectionClosed(t *testing.T) {
 | 
				
			||||||
func TestDatagramConnServe_ReceiveDatagramError(t *testing.T) {
 | 
					func TestDatagramConnServe_ReceiveDatagramError(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	quic := &mockQuicConnReadError{err: net.ErrClosed}
 | 
						quic := &mockQuicConnReadError{err: net.ErrClosed}
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&log, ingress.DialUDPAddrPort), 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, v3.NewSessionManager(&noopMetrics{}, &log, ingress.DialUDPAddrPort), 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := conn.Serve(context.Background())
 | 
						err := conn.Serve(context.Background())
 | 
				
			||||||
	if !errors.Is(err, net.ErrClosed) {
 | 
						if !errors.Is(err, net.ErrClosed) {
 | 
				
			||||||
| 
						 | 
					@ -177,7 +177,7 @@ func TestDatagramConnServe_ErrorDatagramTypes(t *testing.T) {
 | 
				
			||||||
			log := zerolog.New(logOutput)
 | 
								log := zerolog.New(logOutput)
 | 
				
			||||||
			quic := newMockQuicConn()
 | 
								quic := newMockQuicConn()
 | 
				
			||||||
			quic.send <- test.input
 | 
								quic.send <- test.input
 | 
				
			||||||
			conn := v3.NewDatagramConn(quic, &mockSessionManager{}, 0, &log)
 | 
								conn := v3.NewDatagramConn(quic, &mockSessionManager{}, 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 | 
								ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
 | 
				
			||||||
			defer cancel()
 | 
								defer cancel()
 | 
				
			||||||
| 
						 | 
					@ -218,7 +218,7 @@ func TestDatagramConnServe_RegisterSession_SessionManagerError(t *testing.T) {
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	expectedErr := errors.New("unable to register session")
 | 
						expectedErr := errors.New("unable to register session")
 | 
				
			||||||
	sessionManager := mockSessionManager{expectedRegErr: expectedErr}
 | 
						sessionManager := mockSessionManager{expectedRegErr: expectedErr}
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, &sessionManager, 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, &sessionManager, 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Setup the muxer
 | 
						// Setup the muxer
 | 
				
			||||||
	ctx, cancel := context.WithCancelCause(context.Background())
 | 
						ctx, cancel := context.WithCancelCause(context.Background())
 | 
				
			||||||
| 
						 | 
					@ -253,7 +253,7 @@ func TestDatagramConnServe(t *testing.T) {
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	session := newMockSession()
 | 
						session := newMockSession()
 | 
				
			||||||
	sessionManager := mockSessionManager{session: &session}
 | 
						sessionManager := mockSessionManager{session: &session}
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, &sessionManager, 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, &sessionManager, 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Setup the muxer
 | 
						// Setup the muxer
 | 
				
			||||||
	ctx, cancel := context.WithCancelCause(context.Background())
 | 
						ctx, cancel := context.WithCancelCause(context.Background())
 | 
				
			||||||
| 
						 | 
					@ -298,7 +298,7 @@ func TestDatagramConnServe_RegisterTwice(t *testing.T) {
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	session := newMockSession()
 | 
						session := newMockSession()
 | 
				
			||||||
	sessionManager := mockSessionManager{session: &session}
 | 
						sessionManager := mockSessionManager{session: &session}
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, &sessionManager, 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, &sessionManager, 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Setup the muxer
 | 
						// Setup the muxer
 | 
				
			||||||
	ctx, cancel := context.WithCancelCause(context.Background())
 | 
						ctx, cancel := context.WithCancelCause(context.Background())
 | 
				
			||||||
| 
						 | 
					@ -360,9 +360,9 @@ func TestDatagramConnServe_MigrateConnection(t *testing.T) {
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	session := newMockSession()
 | 
						session := newMockSession()
 | 
				
			||||||
	sessionManager := mockSessionManager{session: &session}
 | 
						sessionManager := mockSessionManager{session: &session}
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, &sessionManager, 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, &sessionManager, 0, &noopMetrics{}, &log)
 | 
				
			||||||
	quic2 := newMockQuicConn()
 | 
						quic2 := newMockQuicConn()
 | 
				
			||||||
	conn2 := v3.NewDatagramConn(quic2, &sessionManager, 1, &log)
 | 
						conn2 := v3.NewDatagramConn(quic2, &sessionManager, 1, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Setup the muxer
 | 
						// Setup the muxer
 | 
				
			||||||
	ctx, cancel := context.WithCancelCause(context.Background())
 | 
						ctx, cancel := context.WithCancelCause(context.Background())
 | 
				
			||||||
| 
						 | 
					@ -443,7 +443,7 @@ func TestDatagramConnServe_Payload_GetSessionError(t *testing.T) {
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	// mockSessionManager will return the ErrSessionNotFound for any session attempting to be queried by the muxer
 | 
						// mockSessionManager will return the ErrSessionNotFound for any session attempting to be queried by the muxer
 | 
				
			||||||
	sessionManager := mockSessionManager{session: nil, expectedGetErr: v3.ErrSessionNotFound}
 | 
						sessionManager := mockSessionManager{session: nil, expectedGetErr: v3.ErrSessionNotFound}
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, &sessionManager, 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, &sessionManager, 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Setup the muxer
 | 
						// Setup the muxer
 | 
				
			||||||
	ctx, cancel := context.WithCancelCause(context.Background())
 | 
						ctx, cancel := context.WithCancelCause(context.Background())
 | 
				
			||||||
| 
						 | 
					@ -471,7 +471,7 @@ func TestDatagramConnServe_Payload(t *testing.T) {
 | 
				
			||||||
	quic := newMockQuicConn()
 | 
						quic := newMockQuicConn()
 | 
				
			||||||
	session := newMockSession()
 | 
						session := newMockSession()
 | 
				
			||||||
	sessionManager := mockSessionManager{session: &session}
 | 
						sessionManager := mockSessionManager{session: &session}
 | 
				
			||||||
	conn := v3.NewDatagramConn(quic, &sessionManager, 0, &log)
 | 
						conn := v3.NewDatagramConn(quic, &sessionManager, 0, &noopMetrics{}, &log)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Setup the muxer
 | 
						// Setup the muxer
 | 
				
			||||||
	ctx, cancel := context.WithCancelCause(context.Background())
 | 
						ctx, cancel := context.WithCancelCause(context.Background())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,10 +21,12 @@ const (
 | 
				
			||||||
	// read 1500 bytes from the origin, we limit the amount of bytes to be proxied to less than
 | 
						// read 1500 bytes from the origin, we limit the amount of bytes to be proxied to less than
 | 
				
			||||||
	// this value (maxDatagramPayloadLen).
 | 
						// this value (maxDatagramPayloadLen).
 | 
				
			||||||
	maxOriginUDPPacketSize = 1500
 | 
						maxOriginUDPPacketSize = 1500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logFlowID = "flowID"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SessionCloseErr indicates that the session's Close method was called.
 | 
					// SessionCloseErr indicates that the session's Close method was called.
 | 
				
			||||||
var SessionCloseErr error = errors.New("session was closed")
 | 
					var SessionCloseErr error = errors.New("flow was closed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SessionIdleErr is returned when the session was closed because there was no communication
 | 
					// SessionIdleErr is returned when the session was closed because there was no communication
 | 
				
			||||||
// in either direction over the session for the timeout period.
 | 
					// in either direction over the session for the timeout period.
 | 
				
			||||||
| 
						 | 
					@ -33,7 +35,7 @@ type SessionIdleErr struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (e SessionIdleErr) Error() string {
 | 
					func (e SessionIdleErr) Error() string {
 | 
				
			||||||
	return fmt.Sprintf("session idle for %v", e.timeout)
 | 
						return fmt.Sprintf("flow idle for %v", e.timeout)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (e SessionIdleErr) Is(target error) bool {
 | 
					func (e SessionIdleErr) Is(target error) bool {
 | 
				
			||||||
| 
						 | 
					@ -63,10 +65,12 @@ type session struct {
 | 
				
			||||||
	// activeAtChan is used to communicate the last read/write time
 | 
						// activeAtChan is used to communicate the last read/write time
 | 
				
			||||||
	activeAtChan chan time.Time
 | 
						activeAtChan chan time.Time
 | 
				
			||||||
	closeChan    chan error
 | 
						closeChan    chan error
 | 
				
			||||||
 | 
						metrics      Metrics
 | 
				
			||||||
	log          *zerolog.Logger
 | 
						log          *zerolog.Logger
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewSession(id RequestID, closeAfterIdle time.Duration, origin io.ReadWriteCloser, eyeball DatagramConn, log *zerolog.Logger) Session {
 | 
					func NewSession(id RequestID, closeAfterIdle time.Duration, origin io.ReadWriteCloser, eyeball DatagramConn, metrics Metrics, log *zerolog.Logger) Session {
 | 
				
			||||||
 | 
						logger := log.With().Str(logFlowID, id.String()).Logger()
 | 
				
			||||||
	session := &session{
 | 
						session := &session{
 | 
				
			||||||
		id:             id,
 | 
							id:             id,
 | 
				
			||||||
		closeAfterIdle: closeAfterIdle,
 | 
							closeAfterIdle: closeAfterIdle,
 | 
				
			||||||
| 
						 | 
					@ -76,7 +80,8 @@ func NewSession(id RequestID, closeAfterIdle time.Duration, origin io.ReadWriteC
 | 
				
			||||||
		// drop instead of blocking because last active time only needs to be an approximation
 | 
							// drop instead of blocking because last active time only needs to be an approximation
 | 
				
			||||||
		activeAtChan: make(chan time.Time, 1),
 | 
							activeAtChan: make(chan time.Time, 1),
 | 
				
			||||||
		closeChan:    make(chan error, 1),
 | 
							closeChan:    make(chan error, 1),
 | 
				
			||||||
		log:          log,
 | 
							metrics:      metrics,
 | 
				
			||||||
 | 
							log:          &logger,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	session.eyeball.Store(&eyeball)
 | 
						session.eyeball.Store(&eyeball)
 | 
				
			||||||
	return session
 | 
						return session
 | 
				
			||||||
| 
						 | 
					@ -99,6 +104,7 @@ func (s *session) Migrate(eyeball DatagramConn) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// The session is already running so we want to restart the idle timeout since no proxied packets have come down yet.
 | 
						// The session is already running so we want to restart the idle timeout since no proxied packets have come down yet.
 | 
				
			||||||
	s.markActive()
 | 
						s.markActive()
 | 
				
			||||||
 | 
						s.metrics.MigrateFlow()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *session) Serve(ctx context.Context) error {
 | 
					func (s *session) Serve(ctx context.Context) error {
 | 
				
			||||||
| 
						 | 
					@ -114,18 +120,19 @@ func (s *session) Serve(ctx context.Context) error {
 | 
				
			||||||
			// Read from the origin UDP socket
 | 
								// Read from the origin UDP socket
 | 
				
			||||||
			n, err := s.origin.Read(readBuffer[DatagramPayloadHeaderLen:])
 | 
								n, err := s.origin.Read(readBuffer[DatagramPayloadHeaderLen:])
 | 
				
			||||||
			if errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
 | 
								if errors.Is(err, net.ErrClosed) || errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
 | 
				
			||||||
				s.log.Debug().Msg("Session (origin) connection closed")
 | 
									s.log.Debug().Msg("Flow (origin) connection closed")
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				s.closeChan <- err
 | 
									s.closeChan <- err
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if n < 0 {
 | 
								if n < 0 {
 | 
				
			||||||
				s.log.Warn().Int("packetSize", n).Msg("Session (origin) packet read was negative and was dropped")
 | 
									s.log.Warn().Int("packetSize", n).Msg("Flow (origin) packet read was negative and was dropped")
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if n > maxDatagramPayloadLen {
 | 
								if n > maxDatagramPayloadLen {
 | 
				
			||||||
				s.log.Error().Int("packetSize", n).Msg("Session (origin) packet read was too large and was dropped")
 | 
									s.metrics.PayloadTooLarge()
 | 
				
			||||||
 | 
									s.log.Error().Int("packetSize", n).Msg("Flow (origin) packet read was too large and was dropped")
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// We need to synchronize on the eyeball in-case that the connection was migrated. This should be rarely a point
 | 
								// We need to synchronize on the eyeball in-case that the connection was migrated. This should be rarely a point
 | 
				
			||||||
| 
						 | 
					@ -148,12 +155,12 @@ func (s *session) Serve(ctx context.Context) error {
 | 
				
			||||||
func (s *session) Write(payload []byte) (n int, err error) {
 | 
					func (s *session) Write(payload []byte) (n int, err error) {
 | 
				
			||||||
	n, err = s.origin.Write(payload)
 | 
						n, err = s.origin.Write(payload)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.log.Err(err).Msg("Failed to write payload to session (remote)")
 | 
							s.log.Err(err).Msg("Failed to write payload to flow (remote)")
 | 
				
			||||||
		return n, err
 | 
							return n, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Write must return a non-nil error if it returns n < len(p). https://pkg.go.dev/io#Writer
 | 
						// Write must return a non-nil error if it returns n < len(p). https://pkg.go.dev/io#Writer
 | 
				
			||||||
	if n < len(payload) {
 | 
						if n < len(payload) {
 | 
				
			||||||
		s.log.Err(io.ErrShortWrite).Msg("Failed to write the full payload to session (remote)")
 | 
							s.log.Err(io.ErrShortWrite).Msg("Failed to write the full payload to flow (remote)")
 | 
				
			||||||
		return n, io.ErrShortWrite
 | 
							return n, io.ErrShortWrite
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Mark the session as active since we proxied a packet to the origin.
 | 
						// Mark the session as active since we proxied a packet to the origin.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ var expectedContextCanceled = errors.New("expected context canceled")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestSessionNew(t *testing.T) {
 | 
					func TestSessionNew(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	session := v3.NewSession(testRequestID, 5*time.Second, nil, &noopEyeball{}, &log)
 | 
						session := v3.NewSession(testRequestID, 5*time.Second, nil, &noopEyeball{}, &noopMetrics{}, &log)
 | 
				
			||||||
	if testRequestID != session.ID() {
 | 
						if testRequestID != session.ID() {
 | 
				
			||||||
		t.Fatalf("session id doesn't match: %s != %s", testRequestID, session.ID())
 | 
							t.Fatalf("session id doesn't match: %s != %s", testRequestID, session.ID())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ func TestSessionNew(t *testing.T) {
 | 
				
			||||||
func testSessionWrite(t *testing.T, payload []byte) {
 | 
					func testSessionWrite(t *testing.T, payload []byte) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	origin := newTestOrigin(makePayload(1280))
 | 
						origin := newTestOrigin(makePayload(1280))
 | 
				
			||||||
	session := v3.NewSession(testRequestID, 5*time.Second, &origin, &noopEyeball{}, &log)
 | 
						session := v3.NewSession(testRequestID, 5*time.Second, &origin, &noopEyeball{}, &noopMetrics{}, &log)
 | 
				
			||||||
	n, err := session.Write(payload)
 | 
						n, err := session.Write(payload)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ func testSessionServe_Origin(t *testing.T, payload []byte) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	eyeball := newMockEyeball()
 | 
						eyeball := newMockEyeball()
 | 
				
			||||||
	origin := newTestOrigin(payload)
 | 
						origin := newTestOrigin(payload)
 | 
				
			||||||
	session := v3.NewSession(testRequestID, 3*time.Second, &origin, &eyeball, &log)
 | 
						session := v3.NewSession(testRequestID, 3*time.Second, &origin, &eyeball, &noopMetrics{}, &log)
 | 
				
			||||||
	defer session.Close()
 | 
						defer session.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx, cancel := context.WithCancelCause(context.Background())
 | 
						ctx, cancel := context.WithCancelCause(context.Background())
 | 
				
			||||||
| 
						 | 
					@ -103,7 +103,7 @@ func TestSessionServe_OriginTooLarge(t *testing.T) {
 | 
				
			||||||
	eyeball := newMockEyeball()
 | 
						eyeball := newMockEyeball()
 | 
				
			||||||
	payload := makePayload(1281)
 | 
						payload := makePayload(1281)
 | 
				
			||||||
	origin := newTestOrigin(payload)
 | 
						origin := newTestOrigin(payload)
 | 
				
			||||||
	session := v3.NewSession(testRequestID, 2*time.Second, &origin, &eyeball, &log)
 | 
						session := v3.NewSession(testRequestID, 2*time.Second, &origin, &eyeball, &noopMetrics{}, &log)
 | 
				
			||||||
	defer session.Close()
 | 
						defer session.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	done := make(chan error)
 | 
						done := make(chan error)
 | 
				
			||||||
| 
						 | 
					@ -127,7 +127,7 @@ func TestSessionServe_Migrate(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	eyeball := newMockEyeball()
 | 
						eyeball := newMockEyeball()
 | 
				
			||||||
	pipe1, pipe2 := net.Pipe()
 | 
						pipe1, pipe2 := net.Pipe()
 | 
				
			||||||
	session := v3.NewSession(testRequestID, 2*time.Second, pipe2, &eyeball, &log)
 | 
						session := v3.NewSession(testRequestID, 2*time.Second, pipe2, &eyeball, &noopMetrics{}, &log)
 | 
				
			||||||
	defer session.Close()
 | 
						defer session.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	done := make(chan error)
 | 
						done := make(chan error)
 | 
				
			||||||
| 
						 | 
					@ -165,7 +165,7 @@ func TestSessionServe_Migrate(t *testing.T) {
 | 
				
			||||||
func TestSessionClose_Multiple(t *testing.T) {
 | 
					func TestSessionClose_Multiple(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	origin := newTestOrigin(makePayload(128))
 | 
						origin := newTestOrigin(makePayload(128))
 | 
				
			||||||
	session := v3.NewSession(testRequestID, 5*time.Second, &origin, &noopEyeball{}, &log)
 | 
						session := v3.NewSession(testRequestID, 5*time.Second, &origin, &noopEyeball{}, &noopMetrics{}, &log)
 | 
				
			||||||
	err := session.Close()
 | 
						err := session.Close()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
| 
						 | 
					@ -184,7 +184,7 @@ func TestSessionServe_IdleTimeout(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	origin := newTestIdleOrigin(10 * time.Second) // Make idle time longer than closeAfterIdle
 | 
						origin := newTestIdleOrigin(10 * time.Second) // Make idle time longer than closeAfterIdle
 | 
				
			||||||
	closeAfterIdle := 2 * time.Second
 | 
						closeAfterIdle := 2 * time.Second
 | 
				
			||||||
	session := v3.NewSession(testRequestID, closeAfterIdle, &origin, &noopEyeball{}, &log)
 | 
						session := v3.NewSession(testRequestID, closeAfterIdle, &origin, &noopEyeball{}, &noopMetrics{}, &log)
 | 
				
			||||||
	err := session.Serve(context.Background())
 | 
						err := session.Serve(context.Background())
 | 
				
			||||||
	if !errors.Is(err, v3.SessionIdleErr{}) {
 | 
						if !errors.Is(err, v3.SessionIdleErr{}) {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
| 
						 | 
					@ -206,7 +206,7 @@ func TestSessionServe_ParentContextCanceled(t *testing.T) {
 | 
				
			||||||
	origin := newTestIdleOrigin(10 * time.Second)
 | 
						origin := newTestIdleOrigin(10 * time.Second)
 | 
				
			||||||
	closeAfterIdle := 10 * time.Second
 | 
						closeAfterIdle := 10 * time.Second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	session := v3.NewSession(testRequestID, closeAfterIdle, &origin, &noopEyeball{}, &log)
 | 
						session := v3.NewSession(testRequestID, closeAfterIdle, &origin, &noopEyeball{}, &noopMetrics{}, &log)
 | 
				
			||||||
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
 | 
						ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
 | 
				
			||||||
	defer cancel()
 | 
						defer cancel()
 | 
				
			||||||
	err := session.Serve(ctx)
 | 
						err := session.Serve(ctx)
 | 
				
			||||||
| 
						 | 
					@ -227,7 +227,7 @@ func TestSessionServe_ParentContextCanceled(t *testing.T) {
 | 
				
			||||||
func TestSessionServe_ReadErrors(t *testing.T) {
 | 
					func TestSessionServe_ReadErrors(t *testing.T) {
 | 
				
			||||||
	log := zerolog.Nop()
 | 
						log := zerolog.Nop()
 | 
				
			||||||
	origin := newTestErrOrigin(net.ErrClosed, nil)
 | 
						origin := newTestErrOrigin(net.ErrClosed, nil)
 | 
				
			||||||
	session := v3.NewSession(testRequestID, 30*time.Second, &origin, &noopEyeball{}, &log)
 | 
						session := v3.NewSession(testRequestID, 30*time.Second, &origin, &noopEyeball{}, &noopMetrics{}, &log)
 | 
				
			||||||
	err := session.Serve(context.Background())
 | 
						err := session.Serve(context.Background())
 | 
				
			||||||
	if !errors.Is(err, net.ErrClosed) {
 | 
						if !errors.Is(err, net.ErrClosed) {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ import (
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
	"github.com/quic-go/quic-go"
 | 
						"github.com/quic-go/quic-go"
 | 
				
			||||||
	"github.com/rs/zerolog"
 | 
						"github.com/rs/zerolog"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,12 +83,14 @@ func NewSupervisor(config *TunnelConfig, orchestrator *orchestration.Orchestrato
 | 
				
			||||||
	edgeAddrHandler := NewIPAddrFallback(config.MaxEdgeAddrRetries)
 | 
						edgeAddrHandler := NewIPAddrFallback(config.MaxEdgeAddrRetries)
 | 
				
			||||||
	edgeBindAddr := config.EdgeBindAddr
 | 
						edgeBindAddr := config.EdgeBindAddr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sessionManager := v3.NewSessionManager(config.Log, ingress.DialUDPAddrPort)
 | 
						datagramMetrics := v3.NewMetrics(prometheus.DefaultRegisterer)
 | 
				
			||||||
 | 
						sessionManager := v3.NewSessionManager(datagramMetrics, config.Log, ingress.DialUDPAddrPort)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	edgeTunnelServer := EdgeTunnelServer{
 | 
						edgeTunnelServer := EdgeTunnelServer{
 | 
				
			||||||
		config:            config,
 | 
							config:            config,
 | 
				
			||||||
		orchestrator:      orchestrator,
 | 
							orchestrator:      orchestrator,
 | 
				
			||||||
		sessionManager:    sessionManager,
 | 
							sessionManager:    sessionManager,
 | 
				
			||||||
 | 
							datagramMetrics:   datagramMetrics,
 | 
				
			||||||
		edgeAddrs:         edgeIPs,
 | 
							edgeAddrs:         edgeIPs,
 | 
				
			||||||
		edgeAddrHandler:   edgeAddrHandler,
 | 
							edgeAddrHandler:   edgeAddrHandler,
 | 
				
			||||||
		edgeBindAddr:      edgeBindAddr,
 | 
							edgeBindAddr:      edgeBindAddr,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -176,6 +176,7 @@ type EdgeTunnelServer struct {
 | 
				
			||||||
	config            *TunnelConfig
 | 
						config            *TunnelConfig
 | 
				
			||||||
	orchestrator      *orchestration.Orchestrator
 | 
						orchestrator      *orchestration.Orchestrator
 | 
				
			||||||
	sessionManager    v3.SessionManager
 | 
						sessionManager    v3.SessionManager
 | 
				
			||||||
 | 
						datagramMetrics   v3.Metrics
 | 
				
			||||||
	edgeAddrHandler   EdgeAddrHandler
 | 
						edgeAddrHandler   EdgeAddrHandler
 | 
				
			||||||
	edgeAddrs         *edgediscovery.Edge
 | 
						edgeAddrs         *edgediscovery.Edge
 | 
				
			||||||
	edgeBindAddr      net.IP
 | 
						edgeBindAddr      net.IP
 | 
				
			||||||
| 
						 | 
					@ -607,6 +608,7 @@ func (e *EdgeTunnelServer) serveQUIC(
 | 
				
			||||||
			conn,
 | 
								conn,
 | 
				
			||||||
			e.sessionManager,
 | 
								e.sessionManager,
 | 
				
			||||||
			connIndex,
 | 
								connIndex,
 | 
				
			||||||
 | 
								e.datagramMetrics,
 | 
				
			||||||
			connLogger.Logger(),
 | 
								connLogger.Logger(),
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue