Class ClientIpResolver
java.lang.Object
org.frontcache.core.ClientIpResolver
Resolves the client IP address that rate limiting counts against.
This is deliberately NOT
FCUtils.getClientIP(HttpServletRequest). That method walks
thirteen candidate headers and returns the first non-empty one, which is right for a
best-effort log column and unusable as a limiter key: every one of those headers is
client-supplied, so a client sending "X-Forwarded-For: <random>" on each request would get a
fresh counter on each request and defeat the limiter entirely - while passing every test that
does not try it. The two resolutions coexist on purpose; see docs/archive/guard-rate-limit-proposal.md ยง3.
The rules here:
- A forwarding header is read ONLY when the socket peer is a configured trusted proxy. Otherwise the peer address itself is the key and every header is ignored.
- The header is walked RIGHT TO LEFT, returning the first entry that is not itself a trusted proxy. The leftmost entry - which most implementations use - is whatever the client wrote; the rightmost non-trusted entry is what the last proxy we trust actually observed.
- The result is canonicalized so that the same address always produces the same key: IPv6 literals are expanded to one textual form, and IPv4-mapped IPv6 (::ffff:203.0.113.7) folds to its IPv4 form so a dual-stack container does not split one client in two.
- No prefix, no subnet, no CIDR grouping: one address is one counter. The consequence - an IPv6 client holding a /64 can rotate addresses and evade the limit - is accepted and documented, in exchange for never limiting a shared-NAT office because of one user behind it.
-
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic ClientIpResolverbooleanstatic voidinstall(ClientIpResolver resolver) for tests - installs a resolver without going through FCConfigbooleanisTrustedPeer(jakarta.servlet.http.HttpServletRequest request) Whether the socket peer of this request is a configured trusted proxy - i.e.static voidreload()Re-reads the configuration.resolve(jakarta.servlet.http.HttpServletRequest request)
-
Field Details
-
TRUSTED_PROXIES_PROPERTY
- See Also:
-
HEADER_PROPERTY
- See Also:
-
-
Constructor Details
-
ClientIpResolver
-
-
Method Details
-
getInstance
- Returns:
- the resolver built from the current configuration, created on first use
-
reload
public static void reload()Re-reads the configuration. Called from GuardRuleEngine.load(), so an edited frontcache.properties is picked up by the next engine reload rather than only by a restart. -
install
for tests - installs a resolver without going through FCConfig -
hasTrustedProxies
public boolean hasTrustedProxies()- Returns:
- true when at least one trusted proxy is configured. Rate limiting behind a reverse proxy with none configured keys every request on the proxy's own address, so the guard engine warns about it at load time.
-
isTrustedPeer
public boolean isTrustedPeer(jakarta.servlet.http.HttpServletRequest request) Whether the socket peer of this request is a configured trusted proxy - i.e. whether the forwarding headers it carries were written by something we control. Exposed forX-Forwarded-Proto, which is subject to exactly the same rule asX-Forwarded-Forand for the same reason: the scheme ends up in a redirect a browser follows, so a client must not be able to choose it by sending a header. Kept here rather than reimplemented at the call site so there is one definition of "trusted" and one CIDR list.- Parameters:
request- current request; may be null or container-recycled- Returns:
- true only when the peer address parses and falls inside a configured CIDR
-
resolve
- Parameters:
request- current request; may be null or container-recycled- Returns:
- canonical client address, or null when it cannot be established
-