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 18:57:25 +00:00
committed by GitHub
parent 19e03dbd2e
commit 6cd351ec7c
105 changed files with 19 additions and 8623 deletions
@@ -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
@@ -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();
}
}
@@ -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());
}
}
}
@@ -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);
}
}
@@ -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 {}
}
@@ -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();
}
}