pingora icon indicating copy to clipboard operation
pingora copied to clipboard

Implement TCP Connection IP allowlist/blocklist in Pingora

Open lithbitren opened this issue 1 year ago • 2 comments

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

Enable granular management of client IPs during TCP connection setup in Pingora. The goal is to restrict gateway access to internal networks and selected CDNs, implementing IP allowlisting/blocklisting to deny connections from unauthorized or potentially malicious IPs early in the TCP cycle.

Describe the solution you'd like

Introduce an initial IP validation phase at the start of each connection's lifecycle. Upon receiving a connection, verify the client's IP against predefined rules. If the IP doesn't meet criteria, terminate the connection immediately, bypassing further HTTP header processing or coroutine creation.

Describe alternatives you've considered

...

Additional context

As exemplified in frameworks like Hyper, implementing IP filtering directly in the TCP listener's accept loop enhances both security and efficiency.

...
loop {
    let (socket, addr) = listener.accept().await.unwrap();
    ...
    if !allow_list.contains(&addr.ip()) {
        continue;
    }
    ...
    tokio::spawn(async move {
        ...
    });
}

lithbitren avatar Jun 20 '24 08:06 lithbitren

Hi everyone. I'd also be interested by this functionality.

However, I don't think it is necessary to bring in IP filtering (allow_list etc) -- too intrusive. I'd rather suggest bringing in a trait/hook à la ProxyHttp.

Something like:

// 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
}

Then somewhere in the accept loop:

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

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

// Proceed with TLS handshake...

This approach would:

  • Keep Pingora unopinionated about filtering logic
  • Allow users to implement any filtering they need (IP lists, geoblocking, rate limiting, etc.)
  • Be consistent with Pingora's existing patterns (like ProxyHttp)
  • Support async operations in the filter

Users could then implement their own logic as needed. For example, for geoblocking or IP filtering, they would implement the trait with their specific requirements.

Would be happy to work on a PR if this approach sounds good to the maintainers.

jsulmont avatar Jul 31 '25 09:07 jsulmont

Hi @eaufavor I've submitted this PR in case you find it useful.

jsulmont avatar Aug 04 '25 09:08 jsulmont