1
0
mirror of https://github.com/google/nomulus synced 2026-01-03 11:45:39 +00:00

Remove WHOIS classes and configuration (#2859)

This is steps one and two of b/454947209

We already haven't been serving WHOIS for a while, so there's no point
in keeping the old code around. This can simplify some code paths in the
future (like, certain foreign-key-loads that are only used in WHOIS
queries).
This commit is contained in:
gbrodman
2025-10-27 14:57:25 -04:00
committed by GitHub
parent 19e03dbd2e
commit 6cd351ec7c
105 changed files with 19 additions and 8623 deletions

View File

@@ -29,7 +29,6 @@ public class Prober {
public static void main(String[] args) {
// Obtains WebWhois Sequence provided by proberComponent
ImmutableSet<ProbingSequence> sequences = ImmutableSet.copyOf(proberComponent.sequences());
// Tells Sequences to start running

View File

@@ -20,7 +20,6 @@ import dagger.Provides;
import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.module.CertificateModule;
import google.registry.monitoring.blackbox.module.EppModule;
import google.registry.monitoring.blackbox.module.WebWhoisModule;
import google.registry.networking.handler.SslClientInitializer;
import google.registry.util.Clock;
import google.registry.util.SystemClock;
@@ -102,13 +101,10 @@ public class ProberModule {
@Component(
modules = {
ProberModule.class,
WebWhoisModule.class,
EppModule.class,
CertificateModule.class
})
public interface ProberComponent {
// Standard WebWhois sequence
Set<ProbingSequence> sequences();
}
}

View File

@@ -1,180 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.handler;
import com.google.common.flogger.FluentLogger;
import google.registry.monitoring.blackbox.connection.ProbingAction;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.exception.ConnectionException;
import google.registry.monitoring.blackbox.exception.FailureException;
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
import google.registry.monitoring.blackbox.message.InboundMessageType;
import google.registry.monitoring.blackbox.module.WebWhoisModule.HttpWhoisProtocol;
import google.registry.monitoring.blackbox.module.WebWhoisModule.HttpsWhoisProtocol;
import google.registry.monitoring.blackbox.module.WebWhoisModule.WebWhoisProtocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpResponseStatus;
import jakarta.inject.Inject;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.joda.time.Duration;
/**
* Subclass of {@link ActionHandler} that deals with the WebWhois Sequence
*
* <p>Main purpose is to verify {@link HttpResponseMessage} received is valid. If the response
* implies a redirection it follows the redirection until either an Error Response is received, or
* {@link HttpResponseStatus#OK} is received
*/
public class WebWhoisActionHandler extends ActionHandler {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/* Dagger injected components necessary for redirect responses: */
/** {@link Bootstrap} necessary for remaking connection on redirect response. */
private final Bootstrap bootstrap;
/** {@link Protocol} for when redirected to http endpoint. */
private final Protocol httpWhoisProtocol;
/** {@link Protocol} for when redirected to https endpoint. */
private final Protocol httpsWhoisProtocol;
/** {@link HttpRequestMessage} that represents default GET message to be sent on redirect. */
private final HttpRequestMessage requestMessage;
@Inject
public WebWhoisActionHandler(
@WebWhoisProtocol Bootstrap bootstrap,
@HttpWhoisProtocol Protocol httpWhoisProtocol,
@HttpsWhoisProtocol Protocol httpsWhoisProtocol,
HttpRequestMessage requestMessage) {
this.bootstrap = bootstrap;
this.httpWhoisProtocol = httpWhoisProtocol;
this.httpsWhoisProtocol = httpsWhoisProtocol;
this.requestMessage = requestMessage;
}
/**
* After receiving {@link HttpResponseMessage}, either notes success and marks future as finished,
* notes an error in the received {@link URL} and throws a {@link ConnectionException}, received a
* response indicating a Failure, or receives a redirection response, where it follows the
* redirects until receiving one of the previous three responses.
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, InboundMessageType msg)
throws FailureException, UndeterminedStateException {
HttpResponseMessage response = (HttpResponseMessage) msg;
if (response.status().equals(HttpResponseStatus.OK)) {
logger.atInfo().log("Received Successful HttpResponseStatus");
logger.atInfo().log("Response Received: %s", response);
// On success, we always pass message to ActionHandler's channelRead0 method.
super.channelRead0(ctx, msg);
} else if (response.status().equals(HttpResponseStatus.MOVED_PERMANENTLY)
|| response.status().equals(HttpResponseStatus.FOUND)) {
// TODO - Fix checker to better determine when we have encountered a redirection response.
// Obtain url to be redirected to
URL url;
try {
url = new URI(response.headers().get("Location")).toURL();
} catch (MalformedURLException | URISyntaxException | IllegalArgumentException e) {
// in case of error, log it, and let ActionHandler's exceptionThrown method deal with it
throw new FailureException(
"Redirected Location was invalid. Given Location was: "
+ response.headers().get("Location"));
}
// From url, extract new host, port, and path
String newHost = url.getHost();
String newPath = url.getPath();
logger.atInfo().log(
String.format(
"Redirected to %s with host: %s, port: %d, and path: %s",
url, newHost, url.getDefaultPort(), newPath));
// Construct new Protocol to reflect redirected host, path, and port
Protocol newProtocol;
if (url.getProtocol().equals(httpWhoisProtocol.name())) {
newProtocol = httpWhoisProtocol;
} else if (url.getProtocol().equals(httpsWhoisProtocol.name())) {
newProtocol = httpsWhoisProtocol;
} else {
throw new FailureException(
"Redirection Location port was invalid. Given protocol name was: " + url.getProtocol());
}
// Obtain HttpRequestMessage with modified headers to reflect new host and path.
HttpRequestMessage httpRequest = requestMessage.modifyMessage(newHost, newPath);
// Create new probingAction that takes in the new Protocol and HttpRequestMessage with no
// delay
ProbingAction redirectedAction =
ProbingAction.builder()
.setBootstrap(bootstrap)
.setProtocol(newProtocol)
.setOutboundMessage(httpRequest)
.setDelay(Duration.ZERO)
.setHost(newHost)
.build();
// close this channel as we no longer need it
ChannelFuture future = ctx.close();
future.addListener(
f -> {
if (f.isSuccess()) {
logger.atInfo().log("Successfully closed connection.");
} else {
logger.atWarning().log("Channel was unsuccessfully closed.");
}
// Once channel is closed, establish new connection to redirected host, and repeat
// same actions
ChannelFuture secondFuture = redirectedAction.call();
// Once we have a successful call, set original ChannelPromise as success to tell
// ProbingStep we can move on
secondFuture.addListener(
f2 -> {
if (f2.isSuccess()) {
super.channelRead0(ctx, msg);
} else {
if (f2 instanceof FailureException) {
throw new FailureException(f2.cause());
} else {
throw new UndeterminedStateException(f2.cause());
}
}
});
});
} else {
// Add in metrics Handling that informs MetricsCollector the response was a FAILURE
logger.atWarning().log(String.format("Received unexpected response: %s", response.status()));
throw new FailureException("Response received from remote site was: " + response.status());
}
}
}

View File

@@ -1,55 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.handler;
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
import google.registry.monitoring.blackbox.message.InboundMessageType;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.FullHttpResponse;
import jakarta.inject.Inject;
/**
* {@link io.netty.channel.ChannelHandler} that converts inbound {@link FullHttpResponse} to custom
* type {@link HttpResponseMessage} and retains {@link HttpRequestMessage} in case of reuse for
* redirection.
*/
public class WebWhoisMessageHandler extends ChannelDuplexHandler {
@Inject
public WebWhoisMessageHandler() {}
/** Retains {@link HttpRequestMessage} and calls super write method. */
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
HttpRequestMessage request = (HttpRequestMessage) msg;
request.retain();
super.write(ctx, request, promise);
}
/**
* Converts {@link FullHttpResponse} to {@link HttpResponseMessage}, so it is an {@link
* InboundMessageType} instance.
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
FullHttpResponse originalResponse = (FullHttpResponse) msg;
HttpResponseMessage response = new HttpResponseMessage(originalResponse);
super.channelRead(ctx, response);
}
}

View File

@@ -1,227 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.module;
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.networking.handler.SslClientInitializer.createSslClientInitializerWithSystemTrustStore;
import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import google.registry.monitoring.blackbox.ProbingSequence;
import google.registry.monitoring.blackbox.ProbingStep;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.handler.WebWhoisActionHandler;
import google.registry.monitoring.blackbox.handler.WebWhoisMessageHandler;
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
import google.registry.monitoring.blackbox.metric.MetricsCollector;
import google.registry.monitoring.blackbox.token.WebWhoisToken;
import google.registry.networking.handler.SslClientInitializer;
import google.registry.util.Clock;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.ssl.SslProvider;
import jakarta.inject.Provider;
import jakarta.inject.Qualifier;
import jakarta.inject.Singleton;
import org.joda.time.Duration;
/**
* A module that provides the components necessary for and the overall {@link ProbingSequence} to
* probe WebWHOIS.
*/
@Module
public class WebWhoisModule {
private static final String HTTP_PROTOCOL_NAME = "http";
private static final String HTTPS_PROTOCOL_NAME = "https";
private static final int HTTP_WHOIS_PORT = 80;
private static final int HTTPS_WHOIS_PORT = 443;
/** Standard length of messages used by Proxy. Equates to 0.5 MB. */
private static final int maximumMessageLengthBytes = 512 * 1024;
/** {@link Provides} only step used in WebWhois sequence. */
@Provides
@WebWhoisProtocol
static ProbingStep provideWebWhoisStep(
@HttpWhoisProtocol Protocol httpWhoisProtocol,
@WebWhoisProtocol Bootstrap bootstrap,
HttpRequestMessage messageTemplate,
Duration duration) {
return ProbingStep.builder()
.setProtocol(httpWhoisProtocol)
.setBootstrap(bootstrap)
.setMessageTemplate(messageTemplate)
.setDuration(duration)
.build();
}
/** {@link Provides} the {@link Protocol} that corresponds to http connection. */
@Singleton
@Provides
@HttpWhoisProtocol
static Protocol provideHttpWhoisProtocol(
@HttpWhoisProtocol int httpWhoisPort,
@HttpWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
return Protocol.builder()
.setName(HTTP_PROTOCOL_NAME)
.setPort(httpWhoisPort)
.setHandlerProviders(handlerProviders)
.setPersistentConnection(false)
.build();
}
/** {@link Provides} the {@link Protocol} that corresponds to https connection. */
@Singleton
@Provides
@HttpsWhoisProtocol
static Protocol provideHttpsWhoisProtocol(
@HttpsWhoisProtocol int httpsWhoisPort,
@HttpsWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
return Protocol.builder()
.setName(HTTPS_PROTOCOL_NAME)
.setPort(httpsWhoisPort)
.setHandlerProviders(handlerProviders)
.setPersistentConnection(false)
.build();
}
/**
* {@link Provides} the list of providers of {@link ChannelHandler}s that are used for http
* protocol.
*/
@Provides
@HttpWhoisProtocol
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpWhoisHandlerProviders(
Provider<HttpClientCodec> httpClientCodecProvider,
Provider<HttpObjectAggregator> httpObjectAggregatorProvider,
Provider<WebWhoisMessageHandler> messageHandlerProvider,
Provider<WebWhoisActionHandler> webWhoisActionHandlerProvider) {
return ImmutableList.of(
httpClientCodecProvider,
httpObjectAggregatorProvider,
messageHandlerProvider,
webWhoisActionHandlerProvider);
}
/**
* {@link Provides} the list of providers of {@link ChannelHandler}s that are used for https
* protocol.
*/
@Provides
@HttpsWhoisProtocol
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpsWhoisHandlerProviders(
@HttpsWhoisProtocol
Provider<SslClientInitializer<NioSocketChannel>> sslClientInitializerProvider,
Provider<HttpClientCodec> httpClientCodecProvider,
Provider<HttpObjectAggregator> httpObjectAggregatorProvider,
Provider<WebWhoisMessageHandler> messageHandlerProvider,
Provider<WebWhoisActionHandler> webWhoisActionHandlerProvider) {
return ImmutableList.of(
sslClientInitializerProvider,
httpClientCodecProvider,
httpObjectAggregatorProvider,
messageHandlerProvider,
webWhoisActionHandlerProvider);
}
@Provides
static HttpClientCodec provideHttpClientCodec() {
return new HttpClientCodec();
}
@Provides
static HttpObjectAggregator provideHttpObjectAggregator(@WebWhoisProtocol int maxContentLength) {
return new HttpObjectAggregator(maxContentLength);
}
/** {@link Provides} the {@link SslClientInitializer} used for the {@link HttpsWhoisProtocol}. */
@Provides
@HttpsWhoisProtocol
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
SslProvider sslProvider) {
return createSslClientInitializerWithSystemTrustStore(
sslProvider,
channel -> channel.attr(REMOTE_ADDRESS_KEY).get(),
channel -> channel.attr(PROTOCOL_KEY).get().port());
}
/** {@link Provides} the {@link Bootstrap} used by the WebWhois sequence. */
@Singleton
@Provides
@WebWhoisProtocol
static Bootstrap provideBootstrap(
EventLoopGroup eventLoopGroup, Class<? extends Channel> channelClazz) {
return new Bootstrap().group(eventLoopGroup).channel(channelClazz);
}
/** {@link Provides} standard WebWhois sequence. */
@Provides
@Singleton
@IntoSet
ProbingSequence provideWebWhoisSequence(
@WebWhoisProtocol ProbingStep probingStep,
WebWhoisToken webWhoisToken,
MetricsCollector metrics,
Clock clock) {
return new ProbingSequence.Builder(webWhoisToken, metrics, clock).add(probingStep).build();
}
@Provides
@WebWhoisProtocol
int provideMaximumMessageLengthBytes() {
return maximumMessageLengthBytes;
}
/** {@link Provides} the list of top level domains to be probed */
@Singleton
@Provides
@WebWhoisProtocol
ImmutableList<String> provideTopLevelDomains() {
return ImmutableList.of("how", "soy", "xn--q9jyb4c");
}
@Provides
@HttpWhoisProtocol
int provideHttpWhoisPort() {
return HTTP_WHOIS_PORT;
}
@Provides
@HttpsWhoisProtocol
int provideHttpsWhoisPort() {
return HTTPS_WHOIS_PORT;
}
/** Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. */
@Qualifier
public @interface HttpWhoisProtocol {}
/** Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings. */
@Qualifier
public @interface HttpsWhoisProtocol {}
/** Dagger qualifier to provide any WebWhois related bindings. */
@Qualifier
public @interface WebWhoisProtocol {}
}

View File

@@ -1,73 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.token;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
import google.registry.monitoring.blackbox.message.OutboundMessageType;
import google.registry.monitoring.blackbox.module.WebWhoisModule.WebWhoisProtocol;
import jakarta.inject.Inject;
/**
* {@link Token} subtype designed for WebWhois sequence.
*
* <p>Between loops of a WebWhois sequence the only thing changing is the tld we are probing. As a
* result, we maintain the list of {@code topLevelDomains} and on each call to next, have our index
* looking at the next {@code topLevelDomain}.
*/
public class WebWhoisToken extends Token {
/** For each top level domain (tld), we probe "prefix.tld". */
private static final String PREFIX = "whois.nic.";
/** {@link PeekingIterator} over a cycle of all top level domains to be probed. */
private final PeekingIterator<String> tldCycleIterator;
@Inject
public WebWhoisToken(@WebWhoisProtocol ImmutableList<String> topLevelDomainsList) {
checkArgument(!topLevelDomainsList.isEmpty(), "topLevelDomainsList must not be empty.");
this.tldCycleIterator =
Iterators.peekingIterator(Iterables.cycle(topLevelDomainsList).iterator());
}
/** Moves on to next top level domain in {@code tldCycleIterator}. */
@Override
public WebWhoisToken next() {
tldCycleIterator.next();
return this;
}
/** Modifies message to reflect the new host coming from the new top level domain. */
@Override
public OutboundMessageType modifyMessage(OutboundMessageType original)
throws UndeterminedStateException {
return original.modifyMessage(host());
}
/**
* Returns host as the concatenation of fixed {@code prefix} and current value of {@code
* topLevelDomains}.
*/
@Override
public String host() {
return PREFIX + tldCycleIterator.peek();
}
}

View File

@@ -1,216 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.handler;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.monitoring.blackbox.connection.ProbingAction.CONNECTION_FUTURE_KEY;
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpGetRequest;
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpResponse;
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeRedirectResponse;
import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.connection.Protocol;
import google.registry.monitoring.blackbox.exception.FailureException;
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
import google.registry.monitoring.blackbox.testserver.TestServer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import jakarta.inject.Provider;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link WebWhoisActionHandler}.
*
* <p>Attempts to test how well {@link WebWhoisActionHandler} works when responding to all possible
* types of responses
*/
class WebWhoisActionHandlerTest {
private static final int HTTP_PORT = 80;
private static final String HTTP_REDIRECT = "http://";
private static final String TARGET_HOST = "whois.nic.tld";
private static final String DUMMY_URL = "__WILL_NOT_WORK__";
private final Protocol standardProtocol =
Protocol.builder()
.setHandlerProviders(
ImmutableList.of(() -> new WebWhoisActionHandler(null, null, null, null)))
.setName("http")
.setPersistentConnection(false)
.setPort(HTTP_PORT)
.build();
private EmbeddedChannel channel;
private ActionHandler actionHandler;
private Provider<? extends ChannelHandler> actionHandlerProvider;
private Protocol initialProtocol;
private HttpRequestMessage msg;
/** Creates default protocol with empty list of handlers and specified other inputs */
private Protocol createProtocol(String name, int port, boolean persistentConnection) {
return Protocol.builder()
.setName(name)
.setPort(port)
.setHandlerProviders(ImmutableList.of(actionHandlerProvider))
.setPersistentConnection(persistentConnection)
.build();
}
/** Initializes new WebWhoisActionHandler */
private void setupActionHandler(Bootstrap bootstrap, HttpRequestMessage messageTemplate) {
actionHandler =
new WebWhoisActionHandler(bootstrap, standardProtocol, standardProtocol, messageTemplate);
actionHandlerProvider = () -> actionHandler;
}
/** Sets up testing channel with requisite attributes */
private void setupChannel(Protocol protocol) {
channel = new EmbeddedChannel(actionHandler);
channel.attr(PROTOCOL_KEY).set(protocol);
channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture());
}
private Bootstrap makeBootstrap(EventLoopGroup group) {
return new Bootstrap().group(group).channel(LocalChannel.class);
}
private void setup(String hostName, Bootstrap bootstrap, boolean persistentConnection) {
msg = new HttpRequestMessage(makeHttpGetRequest(hostName, ""));
setupActionHandler(bootstrap, msg);
initialProtocol = createProtocol("testProtocol", 0, persistentConnection);
}
@Test
void testBasic_responseOk() {
// setup
setup("", null, true);
setupChannel(initialProtocol);
// stores future
ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg);
FullHttpResponse response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK));
// assesses that future listener isn't triggered yet.
assertThat(future.isDone()).isFalse();
channel.writeInbound(response);
// assesses that we successfully received good response and protocol is unchanged
assertThat(future.isSuccess()).isTrue();
}
@Test
void testBasic_responseFailure_badRequest() {
// setup
setup("", null, false);
setupChannel(initialProtocol);
// Stores future that informs when action is completed.
ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg);
FullHttpResponse response =
new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
// Assesses that future listener isn't triggered yet.
assertThat(future.isDone()).isFalse();
channel.writeInbound(response);
// Assesses that listener is triggered, but event is not success
assertThat(future.isDone()).isTrue();
assertThat(future.isSuccess()).isFalse();
// Ensures that we fail as a result of a FailureException.
assertThat(future.cause() instanceof FailureException).isTrue();
}
@Test
void testBasic_responseFailure_badURL() {
// setup
setup("", null, false);
setupChannel(initialProtocol);
// stores future
ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg);
FullHttpResponse response =
new HttpResponseMessage(
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, DUMMY_URL, true));
// assesses that future listener isn't triggered yet.
assertThat(future.isDone()).isFalse();
channel.writeInbound(response);
// assesses that listener is triggered, and event is success
assertThat(future.isDone()).isTrue();
assertThat(future.isSuccess()).isFalse();
// Ensures that we fail as a result of a FailureException.
assertThat(future.cause()).isInstanceOf(FailureException.class);
}
@Test
void testAdvanced_redirect() {
// Sets up EventLoopGroup with 1 thread to be blocking.
EventLoopGroup group = new NioEventLoopGroup(1);
// Sets up embedded channel.
setup("", makeBootstrap(group), false);
setupChannel(initialProtocol);
// Initializes LocalAddress with unique String.
LocalAddress address = new LocalAddress(TARGET_HOST);
// stores future
ChannelFuture future = actionHandler.getFinishedFuture();
channel.writeOutbound(msg);
// Path that we test WebWhoisActionHandler uses.
String path = "/test";
// Sets up the local server that the handler will be redirected to.
TestServer.webWhoisServer(group, address, "", TARGET_HOST, path);
FullHttpResponse response =
new HttpResponseMessage(
makeRedirectResponse(
HttpResponseStatus.MOVED_PERMANENTLY, HTTP_REDIRECT + TARGET_HOST + path, true));
// checks that future has not been set to successful or a failure
assertThat(future.isDone()).isFalse();
channel.writeInbound(response);
// makes sure old channel is shut down when attempting redirection
assertThat(channel.isActive()).isFalse();
// assesses that we successfully received good response and protocol is unchanged
assertThat(future.syncUninterruptibly().isSuccess()).isTrue();
}
}

View File

@@ -1,165 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.testserver;
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeHttpResponse;
import static google.registry.monitoring.blackbox.util.WebWhoisUtils.makeRedirectResponse;
import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.exception.FailureException;
import google.registry.monitoring.blackbox.message.EppMessage;
import google.registry.monitoring.blackbox.message.HttpResponseMessage;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
/**
* Mock Server Superclass whose subclasses implement specific behaviors we expect blackbox server to
* perform
*/
public class TestServer {
public TestServer(LocalAddress localAddress, ImmutableList<? extends ChannelHandler> handlers) {
this(new NioEventLoopGroup(1), localAddress, handlers);
}
private TestServer(
EventLoopGroup eventLoopGroup,
LocalAddress localAddress,
ImmutableList<? extends ChannelHandler> handlers) {
// Creates ChannelInitializer with handlers specified
ChannelInitializer<LocalChannel> serverInitializer =
new ChannelInitializer<>() {
@Override
protected void initChannel(LocalChannel ch) {
for (ChannelHandler handler : handlers) {
ch.pipeline().addLast(handler);
}
}
};
// Sets up serverBootstrap with specified initializer, eventLoopGroup, and using
// LocalServerChannel class
ServerBootstrap serverBootstrap =
new ServerBootstrap()
.group(eventLoopGroup)
.channel(LocalServerChannel.class)
.childHandler(serverInitializer);
ChannelFuture unusedFuture = serverBootstrap.bind(localAddress).syncUninterruptibly();
}
public static TestServer webWhoisServer(
EventLoopGroup eventLoopGroup,
LocalAddress localAddress,
String redirectInput,
String destinationInput,
String destinationPath) {
return new TestServer(
eventLoopGroup,
localAddress,
ImmutableList.of(new RedirectHandler(redirectInput, destinationInput, destinationPath)));
}
public static TestServer eppServer(EventLoopGroup eventLoopGroup, LocalAddress localAddress) {
// TODO - add LengthFieldBasedFrameDecoder to handlers in pipeline if necessary for future
// tests.
return new TestServer(eventLoopGroup, localAddress, ImmutableList.of(new EppHandler()));
}
/** Handler that will wither redirect client, give successful response, or give error messge */
@Sharable
static class RedirectHandler extends SimpleChannelInboundHandler<HttpRequest> {
private String redirectInput;
private String destinationInput;
private String destinationPath;
/**
* @param redirectInput - Server will send back redirect to {@code destinationInput} when
* receiving a request with this host location
* @param destinationInput - Server will send back an {@link HttpResponseStatus} OK response
* when receiving a request with this host location
*/
RedirectHandler(String redirectInput, String destinationInput, String destinationPath) {
this.redirectInput = redirectInput;
this.destinationInput = destinationInput;
this.destinationPath = destinationPath;
}
/**
* Reads input {@link HttpRequest}, and creates appropriate {@link HttpResponseMessage} based on
* what header location is
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpRequest request) {
HttpResponse response;
if (request.headers().get("host").equals(redirectInput)) {
response =
new HttpResponseMessage(
makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, destinationInput, true));
} else if (request.headers().get("host").equals(destinationInput)
&& request.uri().equals(destinationPath)) {
response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK));
} else {
response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST));
}
ChannelFuture unusedFuture = ctx.channel().writeAndFlush(response);
}
}
private static class EppHandler extends ChannelDuplexHandler {
private ChannelPromise future;
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
// TODO - pass EppMessage into future to easily read attributes of message.
future = ctx.newPromise();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
byte[] messageBytes = new byte[buf.readableBytes()];
buf.readBytes(messageBytes);
// TODO - Break ByteBuf into multiple pieces to test how well it works with
// LengthFieldBasedFrameDecoder.
try {
EppMessage.byteArrayToXmlDoc(messageBytes);
ChannelFuture unusedFuture = future.setSuccess();
} catch (FailureException e) {
ChannelFuture unusedFuture = future.setFailure(e);
}
}
}
}

View File

@@ -23,7 +23,7 @@ import io.netty.channel.Channel;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
/** Unit Tests for each {@link Token} subtype (just {@link WebWhoisToken} for now) */
/** Unit Tests for each {@link Token} subtype (just {@link EppToken} for now) */
class EppTokenTest {
private static String TEST_HOST = "host";

View File

@@ -1,67 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.token;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
import google.registry.monitoring.blackbox.message.HttpRequestMessage;
import org.junit.jupiter.api.Test;
/** Unit Tests for {@link WebWhoisToken} */
class WebWhoisTokenTest {
private static final String PREFIX = "whois.nic.";
private static final String HOST = "starter";
private static final String FIRST_TLD = "first_test";
private static final String SECOND_TLD = "second_test";
private static final String THIRD_TLD = "third_test";
private final ImmutableList<String> testDomains =
ImmutableList.of(FIRST_TLD, SECOND_TLD, THIRD_TLD);
private Token webToken = new WebWhoisToken(testDomains);
@Test
void testMessageModification() throws UndeterminedStateException {
// creates Request message with header
HttpRequestMessage message = new HttpRequestMessage();
message.headers().set("host", HOST);
// attempts to use Token's method for modifying the method based on its stored host
HttpRequestMessage secondMessage = (HttpRequestMessage) webToken.modifyMessage(message);
assertThat(secondMessage.headers().get("host")).isEqualTo(PREFIX + FIRST_TLD);
}
@Test
void testHostOfToken() {
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
}
@Test
void testNextToken() {
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
webToken = webToken.next();
assertThat(webToken.host()).isEqualTo(PREFIX + SECOND_TLD);
webToken = webToken.next();
assertThat(webToken.host()).isEqualTo(PREFIX + THIRD_TLD);
webToken = webToken.next();
assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD);
}
}

View File

@@ -1,65 +0,0 @@
// Copyright 2019 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.monitoring.blackbox.util;
import static java.nio.charset.StandardCharsets.US_ASCII;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
/** Houses static utility functions for testing WebWHOIS components of Prober. */
public class WebWhoisUtils {
public static FullHttpRequest makeHttpGetRequest(String host, String path) {
FullHttpRequest request =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.headers().set("host", host).setInt("content-length", 0);
return request;
}
public static FullHttpResponse makeHttpResponse(String content, HttpResponseStatus status) {
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
response.headers().setInt("content-length", buf.readableBytes());
return response;
}
public static FullHttpResponse makeHttpResponse(HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
response.headers().setInt("content-length", 0);
return response;
}
/** Creates HttpResponse given status, redirection location, and other necessary inputs */
public static FullHttpResponse makeRedirectResponse(
HttpResponseStatus status, String location, boolean keepAlive) {
FullHttpResponse response = makeHttpResponse("", status);
response.headers().set("content-type", "text/plain");
if (location != null) {
response.headers().set("location", location);
}
if (keepAlive) {
response.headers().set("connection", "keep-alive");
}
return response;
}
}