Skip to content

Commit 3253ad2

Browse files
committed
proxyd: exemption based on X-Forwarded-For
1 parent a95503a commit 3253ad2

File tree

4 files changed

+40
-10
lines changed

4 files changed

+40
-10
lines changed

proxyd/config.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@ type MetricsConfig struct {
5656
}
5757

5858
type RateLimitConfig struct {
59-
UseRedis bool `toml:"use_redis"`
60-
BaseRate int `toml:"base_rate"`
61-
BaseInterval TOMLDuration `toml:"base_interval"`
62-
ExemptOrigins []string `toml:"exempt_origins"`
63-
ExemptUserAgents []string `toml:"exempt_user_agents"`
64-
ErrorMessage string `toml:"error_message"`
65-
MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"`
66-
IPHeaderOverride string `toml:"ip_header_override"`
59+
UseRedis bool `toml:"use_redis"`
60+
BaseRate int `toml:"base_rate"`
61+
BaseInterval TOMLDuration `toml:"base_interval"`
62+
ExemptProxyClients []string `toml:"exempt_proxy_clients"`
63+
ExemptOrigins []string `toml:"exempt_origins"`
64+
ExemptUserAgents []string `toml:"exempt_user_agents"`
65+
ErrorMessage string `toml:"error_message"`
66+
MethodOverrides map[string]*RateLimitMethodOverride `toml:"method_overrides"`
67+
IPHeaderOverride string `toml:"ip_header_override"`
6768
}
6869

6970
type RateLimitMethodOverride struct {

proxyd/integration_tests/rate_limit_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ func TestFrontendMaxRPSLimit(t *testing.T) {
3939
RequireEqualJSON(t, []byte(frontendOverLimitResponseWithID), limitedRes)
4040
})
4141

42+
t.Run("exempt proxy client", func(t *testing.T) {
43+
h := make(http.Header)
44+
h.Set("X-Forwarded-For", "1.2.3.4")
45+
client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h)
46+
_, codes := spamReqs(t, client, ethChainID, 429, 3)
47+
require.Equal(t, 3, codes[200])
48+
})
49+
4250
t.Run("exempt user agent over limit", func(t *testing.T) {
4351
h := make(http.Header)
4452
h.Set("User-Agent", "exempt_agent")

proxyd/integration_tests/testdata/frontend_rate_limit.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ base_rate = 2
2323
base_interval = "1s"
2424
exempt_origins = ["exempt_origin"]
2525
exempt_user_agents = ["exempt_agent"]
26+
exempt_proxy_clients = [ "1.2.3.4" ]
2627
error_message = "over rate limit with special message"
2728

2829
[rate_limit.method_overrides.eth_foobar]
@@ -32,4 +33,4 @@ interval = "1s"
3233
[rate_limit.method_overrides.eth_baz]
3334
limit = 1
3435
interval = "1s"
35-
global = true
36+
global = true

proxyd/server.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type Server struct {
7272
overrideLims map[string]FrontendRateLimiter
7373
senderLim FrontendRateLimiter
7474
allowedChainIds []*big.Int
75+
limExemptProxyClients []*regexp.Regexp
7576
limExemptOrigins []*regexp.Regexp
7677
limExemptUserAgents []*regexp.Regexp
7778
globallyLimitedMethods map[string]bool
@@ -131,10 +132,18 @@ func NewServer(
131132
}
132133

133134
var mainLim FrontendRateLimiter
135+
limExemptProxyClients := make([]*regexp.Regexp, 0)
134136
limExemptOrigins := make([]*regexp.Regexp, 0)
135137
limExemptUserAgents := make([]*regexp.Regexp, 0)
136138
if rateLimitConfig.BaseRate > 0 {
137139
mainLim = limiterFactory(time.Duration(rateLimitConfig.BaseInterval), rateLimitConfig.BaseRate, "main")
140+
for _, client := range rateLimitConfig.ExemptProxyClients {
141+
pattern, err := regexp.Compile(client)
142+
if err != nil {
143+
return nil, err
144+
}
145+
limExemptProxyClients = append(limExemptProxyClients, pattern)
146+
}
138147
for _, origin := range rateLimitConfig.ExemptOrigins {
139148
pattern, err := regexp.Compile(origin)
140149
if err != nil {
@@ -194,6 +203,7 @@ func NewServer(
194203
globallyLimitedMethods: globalMethodLims,
195204
senderLim: senderLim,
196205
allowedChainIds: senderRateLimitConfig.AllowedChainIds,
206+
limExemptProxyClients: limExemptProxyClients,
197207
limExemptOrigins: limExemptOrigins,
198208
limExemptUserAgents: limExemptUserAgents,
199209
rateLimitHeader: rateLimitHeader,
@@ -269,6 +279,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
269279
userAgent := r.Header.Get("User-Agent")
270280
// Use XFF in context since it will automatically be replaced by the remote IP
271281
xff := stripXFF(GetXForwardedFor(ctx))
282+
isUnlimitedProxyClient := s.isUnlimitedProxyClient(xff)
272283
isUnlimitedOrigin := s.isUnlimitedOrigin(origin)
273284
isUnlimitedUserAgent := s.isUnlimitedUserAgent(userAgent)
274285

@@ -279,7 +290,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
279290

280291
isLimited := func(method string) bool {
281292
isGloballyLimitedMethod := s.isGlobalLimit(method)
282-
if !isGloballyLimitedMethod && (isUnlimitedOrigin || isUnlimitedUserAgent) {
293+
if !isGloballyLimitedMethod && (isUnlimitedProxyClient || isUnlimitedOrigin || isUnlimitedUserAgent) {
283294
return false
284295
}
285296

@@ -802,6 +813,15 @@ func randStr(l int) string {
802813
return hex.EncodeToString(b)
803814
}
804815

816+
func (s *Server) isUnlimitedProxyClient(client string) bool {
817+
for _, pat := range s.limExemptProxyClients {
818+
if pat.MatchString(client) {
819+
return true
820+
}
821+
}
822+
823+
return false
824+
}
805825
func (s *Server) isUnlimitedOrigin(origin string) bool {
806826
for _, pat := range s.limExemptOrigins {
807827
if pat.MatchString(origin) {

0 commit comments

Comments
 (0)