Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
import google.registry.config.RegistryConfigSettings;
import google.registry.eppserver.Protocol.FrontendProtocol;
import google.registry.eppserver.handler.EppServiceHandler;
import google.registry.eppserver.quota.QuotaManager;
import google.registry.eppserver.quota.EppServerQuotaManager;
import google.registry.networking.handler.SslServerInitializer;
import google.registry.quota.GenericValkeyQuotaManager;
import io.netty.channel.ChannelHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
Expand Down Expand Up @@ -137,8 +138,9 @@ static SslServerInitializer<NioSocketChannel> provideSslServerInitializer(
@Provides
@Singleton
@CommandQuota
static QuotaManager provideCommandQuotaManager(
static EppServerQuotaManager provideCommandQuotaManager(
@Config("eppServerQuota") RegistryConfigSettings.Quota quota, Optional<UnifiedJedis> jedis) {
return new QuotaManager(quota, jedis.orElse(null), "command");
return new EppServerQuotaManager(
quota, new GenericValkeyQuotaManager(jedis.orElse(null), "command"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import google.registry.config.RegistryConfig.Config;
import google.registry.eppserver.EppProtocolModule.CommandQuota;
import google.registry.eppserver.metric.FrontendMetrics;
import google.registry.eppserver.quota.EppServerQuotaManager;
import google.registry.eppserver.quota.LocalConnectionLimiter;
import google.registry.eppserver.quota.QuotaManager;
import google.registry.module.RegistryServlet;
import google.registry.request.RequestHandler;
import google.registry.util.FakeHttpServletRequest;
Expand Down Expand Up @@ -76,7 +76,7 @@ public class EppServiceHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final byte[] helloBytes;
private final FrontendMetrics metrics;
private final LocalConnectionLimiter localConnectionLimiter;
private final QuotaManager commandQuotaManager;
private final EppServerQuotaManager commandQuotaManager;
private final Supplier<String> idTokenSupplier;
private final String projectId;
private final int preLoginReadTimeoutSeconds;
Expand All @@ -98,7 +98,7 @@ public EppServiceHandler(
@Named("hello") byte[] helloBytes,
FrontendMetrics metrics,
LocalConnectionLimiter localConnectionLimiter,
@CommandQuota QuotaManager commandQuotaManager,
@CommandQuota EppServerQuotaManager commandQuotaManager,
@Named("idToken") Supplier<String> idTokenSupplier,
@Config("projectId") String projectId,
@Config("eppServerPreLoginReadTimeoutSeconds") int preLoginReadTimeoutSeconds) {
Expand Down Expand Up @@ -227,7 +227,7 @@ private boolean acquireCommandQuota(ChannelHandlerContext ctx) {
String throttleId =
(authenticatedRegistrarId != null) ? authenticatedRegistrarId : sslClientCertificateHash;
if (throttleId != null) {
if (!commandQuotaManager.acquireQuota(new QuotaManager.QuotaRequest(throttleId)).success()) {
if (!commandQuotaManager.acquireQuota(throttleId)) {
metrics.registerQuotaRejection("epp_command", throttleId);
closeConnection(ctx);
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package google.registry.eppserver.quota;

import com.google.common.collect.ImmutableMap;
import google.registry.config.RegistryConfigSettings.Quota;
import google.registry.config.RegistryConfigSettings.Quota.QuotaGroup;
import google.registry.quota.GenericValkeyQuotaManager;
import java.time.Duration;
import javax.annotation.concurrent.ThreadSafe;

/**
* Quota management for the EPP server using Redis/Valkey.
*
* <p>Handles primarily configuration lookup and delegation to the generic quota manager.
*/
@ThreadSafe
public class EppServerQuotaManager {

private static final Duration DEFAULT_TTL = Duration.ofHours(1);

private final GenericValkeyQuotaManager quotaManager;
private final QuotaGroup defaultQuota;
private final ImmutableMap<String, QuotaGroup> customQuotas;

public EppServerQuotaManager(Quota quota, GenericValkeyQuotaManager quotaManager) {
this.quotaManager = quotaManager;
this.defaultQuota = quota.defaultQuota;

ImmutableMap.Builder<String, QuotaGroup> builder = ImmutableMap.builder();
quota.customQuota.forEach(group -> group.userId.forEach(userId -> builder.put(userId, group)));
this.customQuotas = builder.build();
}

/** Attempts to acquire a quota token from Redis. */
public boolean acquireQuota(String userId) {
QuotaGroup group = customQuotas.getOrDefault(userId, defaultQuota);

// Unlimited quota check
if (group.tokenAmount < 0) {
return true;
}

String redisId = getRedisId(group, userId);
return quotaManager.acquireQuota(redisId, group.tokenAmount, getTtl(group));
}

/** Refreshes the TTL of an existing quota token. */
public void refreshQuota(String userId) {
QuotaGroup group = customQuotas.getOrDefault(userId, defaultQuota);
if (group.tokenAmount < 0) {
return;
}

String redisId = getRedisId(group, userId);
quotaManager.refreshQuota(redisId, getTtl(group));
}

/** Returns a token to the pool (used for connection throttling). */
public void releaseQuota(String userId) {
QuotaGroup group = customQuotas.getOrDefault(userId, defaultQuota);
if (group.tokenAmount < 0) {
return;
}

String redisId = getRedisId(group, userId);
quotaManager.releaseQuota(redisId, group.tokenAmount);
}

private String getRedisId(QuotaGroup group, String userId) {
// Use the first ID as the virtual group identity if it's a custom group,
// otherwise isolate each default user by their actual ID.
return (group == defaultQuota || group.userId.isEmpty()) ? userId : group.userId.get(0);
}

private Duration getTtl(QuotaGroup group) {
return group.refillSeconds > 0 ? Duration.ofSeconds(group.refillSeconds) : DEFAULT_TTL;
}
}
171 changes: 0 additions & 171 deletions core/src/main/java/google/registry/eppserver/quota/QuotaManager.java

This file was deleted.

Loading
Loading