Fix WHOIS issues

[1] Web whois should redirect to www.registry.google. whois.registry.google also points to the proxy IP, so redirecting to whois.registry.google just makes it loop. Also allow HEAD in web whois request in case that is used in monitoring.

[2] Separately, there's a bug introduced in [] where exception handling of inbound messages is moved to HttpsRelayServiceHandler. However the quota handlers are installed behind the HttpServiceServiceHandler in the channel pipeline, therefore the exception thrown in quota handlers never got processed. This results in hung connection when quota exceeded.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=208651011
This commit is contained in:
jianglai 2018-08-14 08:31:50 -07:00
parent 0e64015cdf
commit 2e2898e17c
4 changed files with 20 additions and 8 deletions

View file

@ -79,10 +79,7 @@ public abstract class QuotaHandler extends ChannelInboundHandlerAdapter {
static class OverQuotaException extends Exception { static class OverQuotaException extends Exception {
OverQuotaException(String protocol, String userId) { OverQuotaException(String protocol, String userId) {
super( super(String.format("Quota exceeded for: PROTOCOL: %s, USER ID: %s", protocol, userId));
String.format(
"\nPROTOCOL: %s\nUSER ID: %s\nQuota exceeded, terminating connection.",
protocol, userId));
} }
} }

View file

@ -17,6 +17,7 @@ package google.registry.proxy.handler;
import static google.registry.proxy.Protocol.PROTOCOL_KEY; import static google.registry.proxy.Protocol.PROTOCOL_KEY;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import google.registry.proxy.handler.QuotaHandler.OverQuotaException;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -70,6 +71,17 @@ public class RelayHandler<I> extends SimpleChannelInboundHandler<I> {
} }
} }
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof OverQuotaException) {
logger.atWarning().withCause(cause).log(
"Channel %s closed due to quota exceeded", ctx.channel());
ChannelFuture unusedFuture = ctx.close();
} else {
ctx.fireExceptionCaught(cause);
}
}
public static void writeToRelayChannel( public static void writeToRelayChannel(
Channel channel, Channel relayChannel, Object msg, boolean retry) { Channel channel, Channel relayChannel, Object msg, boolean retry) {
ChannelFuture unusedFuture = ChannelFuture unusedFuture =

View file

@ -22,6 +22,7 @@ import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN; import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
import static io.netty.handler.codec.http.HttpMethod.GET; import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.HEAD;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.FOUND; import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;
@ -32,6 +33,7 @@ import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
@ -39,6 +41,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpUtil;
import java.time.Duration; import java.time.Duration;
@ -75,6 +78,7 @@ public class WebWhoisRedirectHandler extends SimpleChannelInboundHandler<HttpReq
private static final String HSTS_HEADER_NAME = "Strict-Transport-Security"; private static final String HSTS_HEADER_NAME = "Strict-Transport-Security";
private static final Duration HSTS_MAX_AGE = Duration.ofDays(365); private static final Duration HSTS_MAX_AGE = Duration.ofDays(365);
private static final ImmutableList<HttpMethod> ALLOWED_METHODS = ImmutableList.of(GET, HEAD);
private final boolean isHttps; private final boolean isHttps;
private final String redirectHost; private final String redirectHost;
@ -87,8 +91,7 @@ public class WebWhoisRedirectHandler extends SimpleChannelInboundHandler<HttpReq
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) { protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) {
FullHttpResponse response; FullHttpResponse response;
// We only support GET, any other HTTP method should result in 405 error. if (!ALLOWED_METHODS.contains(msg.method())) {
if (!msg.method().equals(GET)) {
response = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); response = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED);
} else if (Strings.isNullOrEmpty(msg.headers().get(HOST))) { } else if (Strings.isNullOrEmpty(msg.headers().get(HOST))) {
response = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST); response = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST);

View file

@ -62,7 +62,7 @@ public class WebWhoisRedirectHandlerTest {
// HTTP redirect tests. // HTTP redirect tests.
@Test @Test
public void testSuccess_http_notGet() { public void testSuccess_http_methodNotAllowed() {
setupChannel(false); setupChannel(false);
request = makeHttpPostRequest("", TARGET_HOST, "/"); request = makeHttpPostRequest("", TARGET_HOST, "/");
// No inbound message passed to the next handler. // No inbound message passed to the next handler.
@ -156,7 +156,7 @@ public class WebWhoisRedirectHandlerTest {
// HTTPS redirect tests. // HTTPS redirect tests.
@Test @Test
public void testSuccess_https_notGet() { public void testSuccess_https_methodNotAllowed() {
setupChannel(true); setupChannel(true);
request = makeHttpPostRequest("", TARGET_HOST, "/"); request = makeHttpPostRequest("", TARGET_HOST, "/");
// No inbound message passed to the next handler. // No inbound message passed to the next handler.