Skip to content

Add ConnectionFilter trait for pre-TLS connection filtering #669

@jsulmont

Description

@jsulmont

What is the problem your feature solves, or the need it fulfills?

Currently, Pingora has no mechanism to filter or drop connections at the TCP level before the TLS handshake occurs. All connection filtering must happen after TLS negotiation completes (in early_request_filter or later), which means:

  • Unwanted connections consume resources completing expensive TLS handshakes
  • Malicious IPs can overwhelm the server with TLS negotiation attempts
  • Geoblocking and IP filtering happen too late in the connection lifecycle

This affects anyone who needs to:

  • Implement IP-based access control
  • Block connections from specific geographic regions
  • Protect against DDoS attacks at the connection level
  • Enforce security policies before TLS negotiation

Describe the solution you'd like

Add a ConnectionFilter trait that allows users to implement custom connection filtering logic, similar to how the ProxyHttp trait works:

// In pingora-core
#[async_trait]
pub trait ConnectionFilter: Send + Sync {
    /// Called when a new TCP connection is accepted, before TLS handshake
    /// Return true to accept the connection, false to drop it
    async fn should_accept(&self, addr: &SocketAddr) -> bool {
        true  // Default: accept all
    }
}

// Default implementation that accepts everything
pub struct AcceptAllFilter;

impl ConnectionFilter for AcceptAllFilter {
    // Uses default implementation
}

The trait would be called in the connection accept loop:

let (socket, addr) = listener.accept().await?;

// Apply connection filter
if !self.connection_filter.should_accept(&addr).await {
    // Log, update metrics, etc.
    drop(socket); 
    continue;
}

// Proceed with TLS handshake...

This approach:

  • Keeps Pingora unopinionated about specific filtering implementations
  • Allows async operations for complex filtering logic (database lookups, etc.)
  • Follows existing Pingora patterns
  • Minimal performance impact (one trait call per connection)

Describe alternatives you've considered

  1. Built-in IP filtering (as suggested in Implement TCP Connection IP allowlist/blocklist in Pingora #297): This would add IP allowlist/blocklist directly to Pingora. However, this is too opinionated and doesn't cover all use cases (e.g., geoblocking, rate limiting).

  2. Infrastructure-level filtering (iptables, cloud WAF): Works but requires external configuration and doesn't integrate with application logic or metrics.

  3. Existing early_request_filter: Already available but runs after TLS handshake, wasting resources on unwanted connections.

  4. Synchronous trait: Simpler but would block the accept loop for database lookups or other I/O operations.

The proposed async trait solution provides maximum flexibility while maintaining Pingora's philosophy of being an unopinionated framework.

Additional context

  • Related to issue Implement TCP Connection IP allowlist/blocklist in Pingora #297 which requests IP filtering functionality
  • Similar pattern already exists in Pingora with the ProxyHttp trait for HTTP request handling
  • This would enable fixing issues like geoblocking happening after TLS handshake (which wastes resources)
  • Performance impact should be minimal as it's a single trait call before expensive TLS operations

I'm willing to implement this feature if the approach is approved.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions