From 3cfde5d4a15e815d77ea9894edc67a3ab27991a4 Mon Sep 17 00:00:00 2001 From: jianglai Date: Mon, 15 Oct 2018 16:51:43 -0700 Subject: [PATCH] Fix EPP quota handling bug We limit the maximum number of concurrent connections that a client can make the proxy. The quota is implemented as a (thread-safe) map of client certificate hash to available number of connections. When a new connection is made, we decrement the availability counter by one. When the counter hits zero, no more connections can be made and any new connection from the same client is terminated by the proxy. Currently, the counter is incremented when a connection is terminated, including connections that are terminated *because* the quota is reached (i. e. the connections for which the counter is not decremented because the counter is already zero). This means that the first time the quota is reached, the next connection is dropped, the counter is incremented to 1 and new connections can be made again, bypassing the quota. This process can be repeated to achieve, theoretically, infinite quota. This CL fixes this bug by only incrementing the counter, upon connection termination, for connections that have decremented the counter in the first place. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217231593 --- java/google/registry/proxy/handler/QuotaHandler.java | 4 +++- .../google/registry/proxy/handler/EppQuotaHandlerTest.java | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/java/google/registry/proxy/handler/QuotaHandler.java b/java/google/registry/proxy/handler/QuotaHandler.java index 1381d72c9..183e89a7b 100644 --- a/java/google/registry/proxy/handler/QuotaHandler.java +++ b/java/google/registry/proxy/handler/QuotaHandler.java @@ -156,7 +156,9 @@ public abstract class QuotaHandler extends ChannelInboundHandlerAdapter { public void channelInactive(ChannelHandlerContext ctx) { // If no reads occurred before the connection is inactive (for example when the handshake // is not successful), no quota is leased and therefore no return is needed. - if (quotaResponse != null) { + // Note that the quota response can be a failure, in which case no token was leased to us from + // the token store. Consequently no return is necessary. + if (quotaResponse != null && quotaResponse.success()) { Future unusedFuture = quotaManager.releaseQuota(QuotaRebate.create(quotaResponse)); } ctx.fireChannelInactive(); diff --git a/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java b/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java index 1669df3a0..df2211aa9 100644 --- a/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java +++ b/javatests/google/registry/proxy/handler/EppQuotaHandlerTest.java @@ -109,7 +109,11 @@ public class EppQuotaHandlerTest { .thenReturn(QuotaResponse.create(false, clientCertHash, now)); OverQuotaException e = assertThrows(OverQuotaException.class, () -> channel.writeInbound(message)); + ChannelFuture unusedFuture = channel.close(); assertThat(e).hasMessageThat().contains(clientCertHash); + verify(quotaManager).acquireQuota(QuotaRequest.create(clientCertHash)); + // Make sure that quotaManager.releaseQuota() is not called when the channel closes. + verifyNoMoreInteractions(quotaManager); verify(metrics).registerQuotaRejection("epp", clientCertHash); verifyNoMoreInteractions(metrics); }