diff --git a/prober/.gitignore b/prober/.gitignore index c86568e76..77b1e2de2 100644 --- a/prober/.gitignore +++ b/prober/.gitignore @@ -1,2 +1,2 @@ out/ -src/main/resources/google/registry/monitoring/blackbox/modules/secrets/ +src/main/resources/google/registry/monitoring/blackbox/modules/secrets/ \ No newline at end of file diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/Prober.java b/prober/src/main/java/google/registry/monitoring/blackbox/Prober.java index 433d7361d..83bffcda8 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/Prober.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/Prober.java @@ -23,19 +23,16 @@ import google.registry.monitoring.blackbox.ProberModule.ProberComponent; */ public class Prober { - /** - * Main Dagger Component - */ - private static ProberComponent proberComponent = DaggerProberModule_ProberComponent.builder() - .build(); - + /** Main Dagger Component */ + private static ProberComponent proberComponent = + DaggerProberModule_ProberComponent.builder().build(); public static void main(String[] args) { - //Obtains WebWhois Sequence provided by proberComponent + // Obtains WebWhois Sequence provided by proberComponent ImmutableSet sequences = ImmutableSet.copyOf(proberComponent.sequences()); - //Tells Sequences to start running + // Tells Sequences to start running for (ProbingSequence sequence : sequences) { sequence.start(); } diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/ProberModule.java b/prober/src/main/java/google/registry/monitoring/blackbox/ProberModule.java index e5a716361..bd88f3a5f 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/ProberModule.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/ProberModule.java @@ -17,6 +17,11 @@ package google.registry.monitoring.blackbox; import dagger.Component; import dagger.Module; import dagger.Provides; +import google.registry.monitoring.blackbox.connection.ProbingAction; +import google.registry.monitoring.blackbox.modules.CertificateModule; +import google.registry.monitoring.blackbox.modules.EppModule; +import google.registry.monitoring.blackbox.modules.WebWhoisModule; +import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; @@ -35,10 +40,8 @@ import org.joda.time.Duration; @Module public class ProberModule { - /** - * Default {@link Duration} chosen to be time between each {@link ProbingAction} call. - */ - private static final Duration DEFAULT_DURATION = Duration.standardSeconds(4); + /** Default {@link Duration} chosen to be time between each {@link ProbingAction} call. */ + private static final Duration DEFAULT_PROBER_INTERVAL = Duration.standardSeconds(4); /** * {@link Provides} the {@link SslProvider} used by instances of {@link @@ -51,9 +54,7 @@ public class ProberModule { return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK; } - /** - * {@link Provides} one global {@link EventLoopGroup} shared by each {@link ProbingSequence}. - */ + /** {@link Provides} one global {@link EventLoopGroup} shared by each {@link ProbingSequence}. */ @Provides @Singleton EventLoopGroup provideEventLoopGroup() { @@ -71,27 +72,36 @@ public class ProberModule { } /** - * {@link Provides} above {@code DEFAULT_DURATION} for all provided {@link ProbingStep}s to use. + * {@link Provides} above {@code DEFAULT_PROBER_INTERVAL} for all provided {@link ProbingStep}s to + * use. */ @Provides @Singleton - Duration provideDuration() { - return DEFAULT_DURATION; + Duration provideProbeInterval() { + return DEFAULT_PROBER_INTERVAL; } /** - * Root level {@link Component} that provides each {@link ProbingSequence}. + * {@link Provides} general {@link Bootstrap} for which a new instance is provided in any {@link + * ProbingSequence}. */ + @Provides + Bootstrap provideBootstrap(EventLoopGroup eventLoopGroup) { + return new Bootstrap().group(eventLoopGroup).channel(NioSocketChannel.class); + } + + /** Root level {@link Component} that provides each {@link ProbingSequence}. */ @Singleton @Component( modules = { - ProberModule.class, - WebWhoisModule.class, + ProberModule.class, + WebWhoisModule.class, + EppModule.class, + CertificateModule.class }) public interface ProberComponent { - //Standard WebWhois sequence + // Standard WebWhois sequence Set sequences(); - } } diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingAction.java b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingAction.java deleted file mode 100644 index bfda98232..000000000 --- a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingAction.java +++ /dev/null @@ -1,316 +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; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.flogger.StackSize.SMALL; -import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; - -import com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.FluentLogger; -import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; -import google.registry.monitoring.blackbox.handlers.ActionHandler; -import google.registry.monitoring.blackbox.messages.OutboundMessageType; -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.ChannelPromise; -import io.netty.channel.local.LocalAddress; -import io.netty.util.AttributeKey; -import io.netty.util.HashedWheelTimer; -import io.netty.util.Timer; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.UnknownHostException; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import javax.inject.Provider; -import org.joda.time.Duration; - -/** - * AutoValue class that represents action generated by {@link ProbingStep} - * - *

Inherits from {@link Callable}, as it has can be called - * to perform its specified task, and return the {@link ChannelFuture} that will be informed when - * the task has been completed

- * - *

Is an immutable class, as it is comprised of the tools necessary for making a specific type - * of connection. It goes hand in hand with {@link Protocol}, which specifies the kind of overall - * connection to be made. {@link Protocol} gives the outline and {@link ProbingAction} gives the - * details of that connection.

- * - *

In its build, if there is no channel supplied, it will create a channel from the attributes - * already supplied. Then, it only sends the {@link OutboundMessageType} down the pipeline when - * informed that the connection is successful. If the channel is supplied, the connection future is - * automatically set to successful.

- */ - -@AutoValue -public abstract class ProbingAction implements Callable { - - /** - * {@link AttributeKey} in channel that gives {@link ChannelFuture} that is set to success when - * channel is active. - */ - public static final AttributeKey CONNECTION_FUTURE_KEY = AttributeKey - .valueOf("CONNECTION_FUTURE_KEY"); - /** - * {@link AttributeKey} in channel that gives the information of the channel's host. - */ - public static final AttributeKey REMOTE_ADDRESS_KEY = AttributeKey - .valueOf("REMOTE_ADDRESS_KEY"); - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - /** - * {@link Timer} that rate limits probing - */ - private static final Timer timer = new HashedWheelTimer(); - - public static Builder builder() { - return new AutoValue_ProbingAction.Builder(); - } - - /** - * Adds provided {@link ChannelHandler}s to the {@link ChannelPipeline} specified - * - * @param channelPipeline is pipeline associated with channel that we want to add handlers to - * @param handlerProviders are a list of provider objects that give us the requisite handlers Adds - * to the pipeline, the list of handlers in the order specified - */ - private static void addHandlers( - ChannelPipeline channelPipeline, - ImmutableList> handlerProviders) { - for (Provider handlerProvider : handlerProviders) { - channelPipeline.addLast(handlerProvider.get()); - } - } - - /** - * Actual {@link Duration} of this delay - */ - public abstract Duration delay(); - - /** - * {@link OutboundMessageType} instance that we write and flush down pipeline to server - */ - public abstract OutboundMessageType outboundMessage(); - - /** - * {@link Channel} object that either created by or passed into this {@link ProbingAction} - * instance - */ - public abstract Channel channel(); - - /** - * The {@link Protocol} instance that specifies type of connection - */ - public abstract Protocol protocol(); - - /** - * The hostname of the remote host we have a connection or will make a connection to - */ - public abstract String host(); - - /** - * Performs the work of the actual action. - * - *

First, checks if channel is active by setting a listener to perform the bulk of the work - * when the connection future is successful.

- * - *

Once the connection is successful, we establish which of the handlers in the pipeline is - * the {@link ActionHandler}.From that, we can obtain a future that is marked as a success when we - * receive an expected response from the server.

- * - *

Next, we set a timer set to a specified delay. After the delay has passed, we send the - * {@code outboundMessage} down the channel pipeline, and when we observe a success or failure, we - * inform the {@link ProbingStep} of this.

- * - * @return {@link ChannelFuture} that denotes when the action has been successfully performed. - */ - @Override - public ChannelFuture call() { - //ChannelPromise that we return - ChannelPromise finished = channel().newPromise(); - - //Ensures channel has been set up with connection future as an attribute - checkNotNull(channel().attr(CONNECTION_FUTURE_KEY).get()); - - //When connection is established call super.call and set returned listener to success - channel().attr(CONNECTION_FUTURE_KEY).get().addListener( - (ChannelFuture connectionFuture) -> { - if (connectionFuture.isSuccess()) { - logger.atInfo().log(String - .format("Successful connection to remote host: %s at port: %d", host(), - protocol().port())); - - ActionHandler actionHandler; - try { - actionHandler = channel().pipeline().get(ActionHandler.class); - } catch (ClassCastException e) { - //If we don't actually have an ActionHandler instance, we have an issue, and throw - // an UndeterminedStateException - logger.atSevere().withStackTrace(SMALL).log("ActionHandler not in Channel Pipeline"); - throw new UndeterminedStateException("No Action Handler found in pipeline"); - } - ChannelFuture channelFuture = actionHandler.getFinishedFuture(); - - timer.newTimeout(timeout -> { - // Write appropriate outboundMessage to pipeline - ChannelFuture unusedFutureWriteAndFlush = - channel().writeAndFlush(outboundMessage()); - channelFuture.addListeners( - future -> { - if (future.isSuccess()) { - ChannelFuture unusedFuture = finished.setSuccess(); - } else { - ChannelFuture unusedFuture = finished.setFailure(future.cause()); - } - }, - //If we don't have a persistent connection, close the connection to this - // channel - future -> { - if (!protocol().persistentConnection()) { - - ChannelFuture closedFuture = channel().close(); - closedFuture.addListener( - f -> { - if (f.isSuccess()) { - logger.atInfo() - .log("Closed stale channel. Moving on to next ProbingStep"); - } else { - logger.atWarning() - .log( - "Could not close channel. Stale connection still exists" - + "."); - } - } - ); - } - } - ); - }, - delay().getStandardSeconds(), - TimeUnit.SECONDS); - } else { - //if we receive a failure, log the failure, and close the channel - logger.atSevere().withCause(connectionFuture.cause()).log( - "Cannot connect to relay channel for %s channel: %s.", - protocol().name(), this.channel()); - ChannelFuture unusedFuture = channel().close(); - } - } - ); - return finished; - } - - @Override - public final String toString() { - return String.format( - "ProbingAction with delay: %d\n" - + "outboundMessage: %s\n" - + "protocol: %s\n" - + "host: %s\n", - delay().getStandardSeconds(), - outboundMessage(), - protocol(), - host() - ); - } - - /** - * {@link AutoValue.Builder} that does work of creating connection when not already present. - */ - @AutoValue.Builder - public abstract static class Builder { - - private Bootstrap bootstrap; - - public Builder setBootstrap(Bootstrap bootstrap) { - this.bootstrap = bootstrap; - return this; - } - - public abstract Builder setDelay(Duration value); - - public abstract Builder setOutboundMessage(OutboundMessageType value); - - public abstract Builder setProtocol(Protocol value); - - public abstract Builder setHost(String value); - - public abstract Builder setChannel(Channel channel); - - abstract Protocol protocol(); - - abstract Channel channel(); - - abstract String host(); - - abstract ProbingAction autoBuild(); - - public ProbingAction build() { - // Sets SocketAddress to bind to. - SocketAddress address; - try { - InetAddress hostAddress = InetAddress.getByName(host()); - address = new InetSocketAddress(hostAddress, protocol().port()); - } catch (UnknownHostException e) { - address = new LocalAddress(host()); - } - - //Sets channel supplied or to be created. - Channel channel; - try { - channel = channel(); - } catch (IllegalStateException e) { - channel = null; - } - - checkArgument(channel == null ^ bootstrap == null, - "One and only one of bootstrap and channel must be supplied."); - //If a channel is supplied, nothing is needed to be done - - //Otherwise, a Bootstrap must be supplied and be used for creating the channel - if (channel == null) { - bootstrap.handler( - new ChannelInitializer() { - @Override - protected void initChannel(Channel outboundChannel) - throws Exception { - //Uses Handlers from Protocol to fill pipeline - addHandlers(outboundChannel.pipeline(), protocol().handlerProviders()); - } - }) - .attr(PROTOCOL_KEY, protocol()) - .attr(REMOTE_ADDRESS_KEY, host()); - - logger.atInfo().log("Initialized bootstrap with channel Handlers"); - //ChannelFuture that performs action when connection is established - ChannelFuture connectionFuture = bootstrap.connect(address); - - setChannel(connectionFuture.channel()); - connectionFuture.channel().attr(CONNECTION_FUTURE_KEY).set(connectionFuture); - } - - //now we can actually build the ProbingAction - return autoBuild(); - } - } -} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java index c9757c006..0ef6aad36 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingSequence.java @@ -15,6 +15,7 @@ package google.registry.monitoring.blackbox; import com.google.common.flogger.FluentLogger; +import google.registry.monitoring.blackbox.connection.ProbingAction; import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException; import google.registry.monitoring.blackbox.tokens.Token; import google.registry.util.CircularList; @@ -26,29 +27,25 @@ import io.netty.channel.EventLoopGroup; /** * Represents Sequence of {@link ProbingStep}s that the Prober performs in order. * - *

Inherits from {@link CircularList}, with element type of - * {@link ProbingStep} as the manner in which the sequence is carried out is similar to the {@link - * CircularList}. However, the {@link Builder} of {@link ProbingSequence} override - * {@link CircularList.AbstractBuilder} allowing for more customized flows, that are looped, but - * not necessarily entirely circular. - * Example: first -> second -> third -> fourth -> second -> third -> fourth -> second -> ... - *

+ *

Inherits from {@link CircularList}, with element type of {@link ProbingStep} as the manner in + * which the sequence is carried out is similar to the {@link CircularList}. However, the {@link + * Builder} of {@link ProbingSequence} override {@link CircularList.AbstractBuilder} allowing for + * more customized flows, that are looped, but not necessarily entirely circular. Example: first -> + * second -> third -> fourth -> second -> third -> fourth -> second -> ... * *

Created with {@link Builder} where we specify {@link EventLoopGroup}, {@link AbstractChannel} * class type, then sequentially add in the {@link ProbingStep.Builder}s in order and mark which one - * is the first repeated step.

+ * is the first repeated step. * *

{@link ProbingSequence} implicitly points each {@link ProbingStep} to the next one, so once * the first one is activated with the requisite {@link Token}, the {@link ProbingStep}s do the rest - * of the work.

+ * of the work. */ public class ProbingSequence extends CircularList { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - /** - * Each {@link ProbingSequence} requires a start token to begin running. - */ + /** Each {@link ProbingSequence} requires a start token to begin running. */ private Token startToken; /** @@ -57,38 +54,27 @@ public class ProbingSequence extends CircularList { */ private boolean lastStep = false; - /** - * {@link ProbingSequence} object that represents first step in the sequence. - */ + /** {@link ProbingSequence} object that represents first step in the sequence. */ private ProbingSequence first; - - /** - * Standard constructor for {@link ProbingSequence} in the list that assigns value and token. - */ + /** Standard constructor for {@link ProbingSequence} in the list that assigns value and token. */ private ProbingSequence(ProbingStep value, Token startToken) { super(value); this.startToken = startToken; } - /** - * Method used in {@link Builder} to mark the last step in the sequence. - */ + /** Method used in {@link Builder} to mark the last step in the sequence. */ private void markLast() { lastStep = true; } - /** - * Obtains next {@link ProbingSequence} in sequence instead of next {@link CircularList}. - */ + /** Obtains next {@link ProbingSequence} in sequence. */ @Override public ProbingSequence next() { return (ProbingSequence) super.next(); } - /** - * Starts ProbingSequence by calling first {@code runStep} with {@code startToken}. - */ + /** Starts ProbingSequence by calling first {@code runStep} with {@code startToken}. */ public void start() { runStep(startToken); } @@ -97,16 +83,16 @@ public class ProbingSequence extends CircularList { * Generates new {@link ProbingAction} from {@link ProbingStep}, calls the action, then retrieves * the result of the action. * - * @param token - used to generate the {@link ProbingAction} by calling {@code - * get().generateAction}. - * - *

Calls {@code runNextStep} to have next {@link ProbingSequence} call {@code runStep} - * with next token depending on if the current step is the last one in the sequence. + *

Calls {@code runNextStep} to have next {@link ProbingSequence} call {@code runStep} with + * next token depending on if the current step is the last one in the sequence. * *

If unable to generate the action, or the calling the action results in an immediate error, * we note an error. Otherwise, if the future marked as finished when the action is completed is * marked as a success, we note a success. Otherwise, if the cause of failure will either be a - * failure or error.

+ * failure or error. + * + * @param token - used to generate the {@link ProbingAction} by calling {@code + * get().generateAction}. */ private void runStep(Token token) { ProbingAction currentAction; @@ -121,8 +107,7 @@ public class ProbingSequence extends CircularList { } catch (UnrecoverableStateException e) { // On an UnrecoverableStateException, terminate the sequence. - logger.atSevere().withCause(e).log( - "Unrecoverable error in generating or calling action."); + logger.atSevere().withCause(e).log("Unrecoverable error in generating or calling action."); return; } catch (Exception e) { @@ -134,32 +119,32 @@ public class ProbingSequence extends CircularList { return; } - future.addListener(f -> { - if (f.isSuccess()) { - // On a successful result, we log as a successful step, and note a success. - logger.atInfo().log(String.format("Successfully completed Probing Step: %s", this)); + future.addListener( + f -> { + if (f.isSuccess()) { + // On a successful result, we log as a successful step, and note a success. + logger.atInfo().log(String.format("Successfully completed Probing Step: %s", this)); - } else { - // On a failed result, we log the failure and note either a failure or error. - logger.atSevere().withCause(f.cause()).log("Did not result in future success"); + } else { + // On a failed result, we log the failure and note either a failure or error. + logger.atSevere().withCause(f.cause()).log("Did not result in future success"); - // If not unrecoverable, we restart the sequence. - if (!(f.cause() instanceof UnrecoverableStateException)) { - restartSequence(); - } - // Otherwise, we just terminate the full sequence. - return; - } + // If not unrecoverable, we restart the sequence. + if (!(f.cause() instanceof UnrecoverableStateException)) { + restartSequence(); + } + // Otherwise, we just terminate the full sequence. + return; + } - if (get().protocol().persistentConnection()) { - // If the connection is persistent, we store the channel in the token. - token.setChannel(currentAction.channel()); - } + if (get().protocol().persistentConnection()) { + // If the connection is persistent, we store the channel in the token. + token.setChannel(currentAction.channel()); + } - //Calls next runStep - runNextStep(token); - - }); + // Calls next runStep + runNextStep(token); + }); } /** @@ -204,9 +189,7 @@ public class ProbingSequence extends CircularList { this.startToken = startToken; } - /** - * We take special note of the first repeated step. - */ + /** We take special note of the first repeated step. */ public Builder markFirstRepeated() { firstRepeatedSequenceStep = current; return this; diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java index 614e67403..b23ea2584 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/ProbingStep.java @@ -15,6 +15,8 @@ package google.registry.monitoring.blackbox; import com.google.auto.value.AutoValue; +import google.registry.monitoring.blackbox.connection.ProbingAction; +import google.registry.monitoring.blackbox.connection.Protocol; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.messages.OutboundMessageType; import google.registry.monitoring.blackbox.tokens.Token; @@ -25,10 +27,10 @@ import org.joda.time.Duration; * {@link AutoValue} class that represents generator of actions performed at each step in {@link * ProbingSequence}. * - *

Holds the unchanged components in a given step of the {@link ProbingSequence}, which are - * the {@link OutboundMessageType}, {@link Protocol}, {@link Duration}, and {@link Bootstrap} - * instances. It then modifies these components on each loop iteration with the consumed {@link - * Token} and from that, generates a new {@link ProbingAction} to call.

+ *

Holds the unchanged components in a given step of the {@link ProbingSequence}, which are the + * {@link OutboundMessageType}, {@link Protocol}, {@link Duration}, and {@link Bootstrap} instances. + * It then modifies these components on each loop iteration with the consumed {@link Token} and from + * that, generates a new {@link ProbingAction} to call. */ @AutoValue public abstract class ProbingStep { @@ -37,14 +39,10 @@ public abstract class ProbingStep { return new AutoValue_ProbingStep.Builder(); } - /** - * Time delay duration between actions. - */ + /** Time delay duration between actions. */ abstract Duration duration(); - /** - * {@link Protocol} type for this step. - */ + /** {@link Protocol} type for this step. */ abstract Protocol protocol(); /** @@ -63,11 +61,12 @@ public abstract class ProbingStep { */ public ProbingAction generateAction(Token token) throws UndeterminedStateException { OutboundMessageType message = token.modifyMessage(messageTemplate()); - ProbingAction.Builder probingActionBuilder = ProbingAction.builder() - .setDelay(duration()) - .setProtocol(protocol()) - .setOutboundMessage(message) - .setHost(token.host()); + ProbingAction.Builder probingActionBuilder = + ProbingAction.builder() + .setDelay(duration()) + .setProtocol(protocol()) + .setOutboundMessage(message) + .setHost(token.host()); if (token.channel() != null) { probingActionBuilder.setChannel(token.channel()); @@ -80,15 +79,12 @@ public abstract class ProbingStep { @Override public final String toString() { - return String.format("ProbingStep with Protocol: %s\n" - + "OutboundMessage: %s\n", - protocol(), - messageTemplate().getClass().getName()); + return String.format( + "ProbingStep with Protocol: %s\n" + "OutboundMessage: %s\n", + protocol(), messageTemplate().getClass().getName()); } - /** - * Standard {@link AutoValue.Builder} for {@link ProbingStep}. - */ + /** Standard {@link AutoValue.Builder} for {@link ProbingStep}. */ @AutoValue.Builder public abstract static class Builder { @@ -103,4 +99,3 @@ public abstract class ProbingStep { public abstract ProbingStep build(); } } - diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/connection/ProbingAction.java b/prober/src/main/java/google/registry/monitoring/blackbox/connection/ProbingAction.java new file mode 100644 index 000000000..95662fa01 --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/connection/ProbingAction.java @@ -0,0 +1,293 @@ +// 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.connection; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.flogger.StackSize.SMALL; +import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY; + +import com.google.auto.value.AutoValue; +import com.google.common.flogger.FluentLogger; +import google.registry.monitoring.blackbox.ProbingStep; +import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; +import google.registry.monitoring.blackbox.handlers.ActionHandler; +import google.registry.monitoring.blackbox.messages.OutboundMessageType; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPromise; +import io.netty.channel.local.LocalAddress; +import io.netty.util.AttributeKey; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import javax.inject.Provider; +import org.joda.time.Duration; + +/** + * AutoValue class that represents action generated by {@link ProbingStep} + * + *

Inherits from {@link Callable}, as it has can be called to perform its + * specified task, and return the {@link ChannelFuture} that will be informed when the task has been + * completed + * + *

Is an immutable class, as it is comprised of the tools necessary for making a specific type of + * connection. It goes hand in hand with {@link Protocol}, which specifies the kind of overall + * connection to be made. {@link Protocol} gives the outline and {@link ProbingAction} gives the + * details of that connection. + * + *

In its build, if there is no channel supplied, it will create a channel from the attributes + * already supplied. Then, it only sends the {@link OutboundMessageType} down the pipeline when + * informed that the connection is successful. If the channel is supplied, the connection future is + * automatically set to successful. + */ +@AutoValue +public abstract class ProbingAction implements Callable { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + /** + * {@link AttributeKey} in channel that gives {@link ChannelFuture} that is set to success when + * channel is active. + */ + public static final AttributeKey CONNECTION_FUTURE_KEY = + AttributeKey.valueOf("CONNECTION_FUTURE_KEY"); + + /** {@link AttributeKey} in channel that gives the information of the channel's host. */ + public static final AttributeKey REMOTE_ADDRESS_KEY = + AttributeKey.valueOf("REMOTE_ADDRESS_KEY"); + + /** {@link Timer} that rate limits probing */ + private static final Timer timer = new HashedWheelTimer(); + + public static Builder builder() { + return new AutoValue_ProbingAction.Builder(); + } + + /** Actual {@link Duration} of this delay */ + public abstract Duration delay(); + + /** {@link OutboundMessageType} instance that we write and flush down pipeline to server */ + public abstract OutboundMessageType outboundMessage(); + + /** + * {@link Channel} object that is either created by or passed into this {@link ProbingAction} + * instance + */ + public abstract Channel channel(); + + /** The {@link Protocol} instance that specifies type of connection */ + public abstract Protocol protocol(); + + /** The hostname of the remote host we have a connection or will make a connection to */ + public abstract String host(); + + /** + * Performs the work of the actual action. + * + *

First, checks if channel is active by setting a listener to perform the bulk of the work + * when the connection future is successful. + * + *

Once the connection is successful, we establish which of the handlers in the pipeline is the + * {@link ActionHandler}.From that, we can obtain a future that is marked as a success when we + * receive an expected response from the server. + * + *

Next, we set a timer set to a specified delay. After the delay has passed, we send the + * {@code outboundMessage} down the channel pipeline, and when we observe a success or failure, we + * inform the {@link ProbingStep} of this. + * + * @return {@link ChannelFuture} that denotes when the action has been successfully performed. + */ + @Override + public ChannelFuture call() { + // ChannelPromise that we return + ChannelPromise finished = channel().newPromise(); + + // Ensures channel has been set up with connection future as an attribute + checkNotNull(channel().attr(CONNECTION_FUTURE_KEY).get()); + + // When connection is established call super.call and set returned listener to success + channel() + .attr(CONNECTION_FUTURE_KEY) + .get() + .addListener( + (ChannelFuture connectionFuture) -> { + if (connectionFuture.isSuccess()) { + logger.atInfo().log( + String.format( + "Successful connection to remote host: %s at port: %d", + host(), protocol().port())); + + ActionHandler actionHandler; + try { + actionHandler = channel().pipeline().get(ActionHandler.class); + } catch (ClassCastException e) { + // If we don't actually have an ActionHandler instance, we have an issue, and + // throw an UndeterminedStateException. + logger.atSevere().withStackTrace(SMALL).log( + "ActionHandler not in Channel Pipeline"); + throw new UndeterminedStateException("No Action Handler found in pipeline"); + } + ChannelFuture channelFuture = actionHandler.getFinishedFuture(); + + timer.newTimeout( + timeout -> { + // Write appropriate outboundMessage to pipeline + ChannelFuture unusedFutureWriteAndFlush = + channel().writeAndFlush(outboundMessage()); + channelFuture.addListeners( + future -> { + if (future.isSuccess()) { + ChannelFuture unusedFuture = finished.setSuccess(); + } else { + ChannelFuture unusedFuture = finished.setFailure(future.cause()); + } + }, + // If we don't have a persistent connection, close the connection to this + // channel + future -> { + if (!protocol().persistentConnection()) { + + ChannelFuture closedFuture = channel().close(); + closedFuture.addListener( + f -> { + if (f.isSuccess()) { + logger.atInfo().log( + "Closed stale channel. Moving on to next ProbingStep"); + } else { + logger.atWarning().log( + "Issue closing stale channel. Most likely already " + + "closed."); + } + }); + } + }); + }, + delay().getStandardSeconds(), + TimeUnit.SECONDS); + } else { + // if we receive a failure, log the failure, and close the channel + logger.atSevere().withCause(connectionFuture.cause()).log( + "Cannot connect to relay channel for %s channel: %s.", + protocol().name(), this.channel()); + ChannelFuture unusedFuture = channel().close(); + } + }); + return finished; + } + + @Override + public final String toString() { + return String.format( + "ProbingAction with delay: %d\n" + + "outboundMessage: %s\n" + + "protocol: %s\n" + + "host: %s\n", + delay().getStandardSeconds(), outboundMessage(), protocol(), host()); + } + + /** {@link AutoValue.Builder} that does work of creating connection when not already present. */ + @AutoValue.Builder + public abstract static class Builder { + + private Bootstrap bootstrap; + + public Builder setBootstrap(Bootstrap bootstrap) { + this.bootstrap = bootstrap; + return this; + } + + public abstract Builder setDelay(Duration value); + + public abstract Builder setOutboundMessage(OutboundMessageType value); + + public abstract Builder setProtocol(Protocol value); + + public abstract Builder setHost(String value); + + public abstract Builder setChannel(Channel channel); + + abstract Protocol protocol(); + + abstract Channel channel(); + + abstract String host(); + + abstract ProbingAction autoBuild(); + + public ProbingAction build() { + // Sets SocketAddress to bind to. + SocketAddress address; + try { + InetAddress hostAddress = InetAddress.getByName(host()); + address = new InetSocketAddress(hostAddress, protocol().port()); + } catch (UnknownHostException e) { + // If the supplied host isn't a valid one, we assume we are using a test host, meaning we + // are using a LocalAddress as our SocketAddress. If this isn't the case, we will anyways + // throw an error from now being able to connect to the LocalAddress. + address = new LocalAddress(host()); + } + + // Sets channel supplied or to be created. + Channel channel; + try { + channel = channel(); + } catch (IllegalStateException e) { + channel = null; + } + + checkArgument( + channel == null ^ bootstrap == null, + "One and only one of bootstrap and channel must be supplied."); + // If a channel is supplied, nothing is needed to be done + + // Otherwise, a Bootstrap must be supplied and be used for creating the channel + if (channel == null) { + bootstrap + .handler( + new ChannelInitializer() { + @Override + protected void initChannel(Channel outboundChannel) throws Exception { + // Uses Handlers from Protocol to fill pipeline in order of provided handlers. + for (Provider handlerProvider : + protocol().handlerProviders()) { + outboundChannel.pipeline().addLast(handlerProvider.get()); + } + } + }) + .attr(PROTOCOL_KEY, protocol()) + .attr(REMOTE_ADDRESS_KEY, host()); + + logger.atInfo().log("Initialized bootstrap with channel Handlers"); + // ChannelFuture that performs action when connection is established + ChannelFuture connectionFuture = bootstrap.connect(address); + + setChannel(connectionFuture.channel()); + connectionFuture.channel().attr(CONNECTION_FUTURE_KEY).set(connectionFuture); + } + + // now we can actually build the ProbingAction + return autoBuild(); + } + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/Protocol.java b/prober/src/main/java/google/registry/monitoring/blackbox/connection/Protocol.java similarity index 69% rename from prober/src/main/java/google/registry/monitoring/blackbox/Protocol.java rename to prober/src/main/java/google/registry/monitoring/blackbox/connection/Protocol.java index ea2a61303..50865aeb6 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/Protocol.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/connection/Protocol.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.monitoring.blackbox; +package google.registry.monitoring.blackbox.connection; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; @@ -20,15 +20,11 @@ import io.netty.channel.ChannelHandler; import io.netty.util.AttributeKey; import javax.inject.Provider; -/** - * {@link AutoValue} class that stores all unchanged variables necessary for type of connection. - */ +/** {@link AutoValue} class that stores all unchanged variables necessary for type of connection. */ @AutoValue public abstract class Protocol { - /** - * {@link AttributeKey} that lets channel reference {@link Protocol} that created it. - */ + /** {@link AttributeKey} that lets channel reference {@link Protocol} that created it. */ public static final AttributeKey PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL_KEY"); public static Builder builder() { @@ -39,30 +35,20 @@ public abstract class Protocol { public abstract int port(); - /** - * The {@link ChannelHandler} providers to use for the protocol, in order. - */ - abstract ImmutableList> handlerProviders(); + /** The {@link ChannelHandler} providers to use for the protocol, in order. */ + public abstract ImmutableList> handlerProviders(); - /** - * Boolean that notes if connection associated with Protocol is persistent. - */ - abstract boolean persistentConnection(); + /** Boolean that notes if connection associated with Protocol is persistent. */ + public abstract boolean persistentConnection(); @Override public final String toString() { return String.format( "Protocol with name: %s, port: %d, providers: %s, and persistent connection: %s", - name(), - port(), - handlerProviders(), - persistentConnection() - ); + name(), port(), handlerProviders(), persistentConnection()); } - /** - * Default {@link AutoValue.Builder} for {@link Protocol}. - */ + /** Default {@link AutoValue.Builder} for {@link Protocol}. */ @AutoValue.Builder public abstract static class Builder { diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/EppClientException.java b/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/EppClientException.java new file mode 100644 index 000000000..528b2ddbc --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/EppClientException.java @@ -0,0 +1,30 @@ +// 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.exceptions; + +/** + * Subclass of {@link UndeterminedStateException} that represents all instances when the action + * performed failed due to the fault of the Prober when the action is an EPP action. + */ +public class EppClientException extends UndeterminedStateException { + + public EppClientException(String msg) { + super(msg); + } + + public EppClientException(Throwable e) { + super(e); + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/FailureException.java b/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/FailureException.java index 36687e7ac..332e990e1 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/FailureException.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/exceptions/FailureException.java @@ -14,9 +14,7 @@ package google.registry.monitoring.blackbox.exceptions; -/** - * Base exception class for all instances when the status of the action performed is FAILURE. - */ +/** Base exception class for all instances when the status of the action performed is FAILURE. */ public class FailureException extends Exception { public FailureException(String msg) { diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/ActionHandler.java b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/ActionHandler.java index 1e1a83423..8b93e7f36 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/ActionHandler.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/ActionHandler.java @@ -15,7 +15,7 @@ package google.registry.monitoring.blackbox.handlers; import com.google.common.flogger.FluentLogger; -import google.registry.monitoring.blackbox.ProbingAction; +import google.registry.monitoring.blackbox.connection.ProbingAction; import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.messages.InboundMessageType; @@ -27,50 +27,40 @@ import io.netty.channel.SimpleChannelInboundHandler; /** * Superclass of all {@link io.netty.channel.ChannelHandler}s placed at end of channel pipeline * - *

{@link ActionHandler} inherits from {@link SimpleChannelInboundHandler}, - * as it should only be passed in messages that implement the {@link InboundMessageType} - * interface.

+ *

{@link ActionHandler} inherits from {@link SimpleChannelInboundHandler}, + * as it should only be passed in messages that implement the {@link InboundMessageType} interface. * - *

The {@link ActionHandler} skeleton exists for a few main purposes. First, it returns a - * {@link ChannelPromise}, which informs the {@link ProbingAction} in charge that a response has - * been read. Second, with any exception thrown, the connection is closed, and the ProbingAction - * governing this channel is informed of the error. If the error is an instance of a {@link - * FailureException} {@code finished} is marked as a failure with cause {@link FailureException}. If - * it is any other type of error, it is treated as an {@link UndeterminedStateException} and {@code - * finished} set as a failure with the same cause as what caused the exception. Lastly, if no error - * is thrown, we know the action completed as a success, and, as such, we mark {@code finished} as a - * success.

+ *

The {@link ActionHandler} skeleton exists for a few main purposes. First, it returns a {@link + * ChannelPromise}, which informs the {@link ProbingAction} in charge that a response has been read. + * Second, with any exception thrown, the connection is closed, and the ProbingAction governing this + * channel is informed of the error. If the error is an instance of a {@link FailureException} + * {@code finished} is marked as a failure with cause {@link FailureException}. If it is any other + * type of error, it is treated as an {@link UndeterminedStateException} and {@code finished} set as + * a failure with the same cause as what caused the exception. Lastly, if no error is thrown, we + * know the action completed as a success, and, as such, we mark {@code finished} as a success. * - *

Subclasses specify further work to be done for specific kinds of channel pipelines.

+ *

Subclasses specify further work to be done for specific kinds of channel pipelines. */ public abstract class ActionHandler extends SimpleChannelInboundHandler { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - /** - * {@link ChannelPromise} that informs {@link ProbingAction} if response has been received. - */ - private ChannelPromise finished; + /** {@link ChannelPromise} that informs {@link ProbingAction} if response has been received. */ + protected ChannelPromise finished; - /** - * Returns initialized {@link ChannelPromise} to {@link ProbingAction}. - */ + /** Returns initialized {@link ChannelPromise} to {@link ProbingAction}. */ public ChannelFuture getFinishedFuture() { return finished; } - /** - * Initializes {@link ChannelPromise} - */ + /** Initializes {@link ChannelPromise} */ @Override public void handlerAdded(ChannelHandlerContext ctx) { - //Once handler is added to channel pipeline, initialize channel and future for this handler + // Once handler is added to channel pipeline, initialize channel and future for this handler finished = ctx.newPromise(); } - /** - * Marks {@link ChannelPromise} as success - */ + /** Marks {@link ChannelPromise} as success */ @Override public void channelRead0(ChannelHandlerContext ctx, InboundMessageType inboundMessage) throws FailureException, UndeterminedStateException { @@ -84,28 +74,28 @@ public abstract class ActionHandler extends SimpleChannelInboundHandler logger.atInfo().log("Unsuccessful channel connection closed")); } diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/EppActionHandler.java b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/EppActionHandler.java new file mode 100644 index 000000000..824923f06 --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/EppActionHandler.java @@ -0,0 +1,54 @@ +// 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.handlers; + +import google.registry.monitoring.blackbox.exceptions.FailureException; +import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; +import google.registry.monitoring.blackbox.messages.EppResponseMessage; +import google.registry.monitoring.blackbox.messages.InboundMessageType; +import io.netty.channel.ChannelHandlerContext; +import javax.inject.Inject; + +/** + * Subclass of {@link ActionHandler} that deals with the Epp Sequence + * + *

Main purpose is to verify {@link EppResponseMessage} received is valid. If not it throws the + * requisite error which is dealt with by the parent {@link ActionHandler} + */ +public class EppActionHandler extends ActionHandler { + + @Inject + public EppActionHandler() {} + + /** + * Decodes the received response to ensure that it is what we expect and resets future in case + * {@link EppActionHandler} is reused. + * + * @throws FailureException if we receive a failed response from the server + */ + @Override + public void channelRead0(ChannelHandlerContext ctx, InboundMessageType msg) + throws FailureException, UndeterminedStateException { + EppResponseMessage response = (EppResponseMessage) msg; + + // Based on the expected response type, will throw ResponseFailure if we don't receive a + // successful EPP response. + response.verify(); + super.channelRead0(ctx, msg); + + // Reset future as there is potential to reuse same ActionHandler for a different ProbingAction + finished = ctx.channel().newPromise(); + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/EppMessageHandler.java b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/EppMessageHandler.java new file mode 100644 index 000000000..b7c4afba5 --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/EppMessageHandler.java @@ -0,0 +1,83 @@ +// 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.handlers; + +import com.google.common.flogger.FluentLogger; +import google.registry.monitoring.blackbox.exceptions.FailureException; +import google.registry.monitoring.blackbox.messages.EppRequestMessage; +import google.registry.monitoring.blackbox.messages.EppResponseMessage; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import javax.inject.Inject; +import javax.inject.Named; + +/** + * {@link io.netty.channel.ChannelHandler} that converts inbound {@link ByteBuf} to custom type + * {@link EppResponseMessage} and similarly converts the outbound {@link EppRequestMessage} to a + * {@link ByteBuf}. Always comes right before {@link EppActionHandler} in channel pipeline. + */ +public class EppMessageHandler extends ChannelDuplexHandler { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + /** + * Corresponding {@link EppResponseMessage} that we expect to receive back from server. + * + *

We always expect the first response to be an {@link EppResponseMessage.Greeting}. + */ + private EppResponseMessage response; + + @Inject + public EppMessageHandler(@Named("greeting") EppResponseMessage greetingResponse) { + response = greetingResponse; + } + + /** Performs conversion to {@link ByteBuf}. */ + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) + throws Exception { + + // If the message is Hello, don't actually pass it on, just wait for server greeting. + // otherwise, we store what we expect a successful response to be + EppRequestMessage request = (EppRequestMessage) msg; + response = request.getExpectedResponse(); + + if (!response.name().equals("greeting")) { + // then we write the ByteBuf representation of the EPP message to the server + ChannelFuture unusedFuture = ctx.write(request.bytes(), promise); + } + } + + /** Performs conversion from {@link ByteBuf} */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws FailureException { + try { + // attempt to get response document from ByteBuf + ByteBuf buf = (ByteBuf) msg; + response.getDocument(buf); + logger.atInfo().log(response.toString()); + } catch (FailureException e) { + + // otherwise we log that it was unsuccessful and throw the requisite error + logger.atInfo().withCause(e).log("Failure in current step."); + throw e; + } + // pass response to the ActionHandler in the pipeline + ctx.fireChannelRead(response); + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/SslClientInitializer.java b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/SslClientInitializer.java index c62668b19..8a30c6a71 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/SslClientInitializer.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/SslClientInitializer.java @@ -15,12 +15,12 @@ package google.registry.monitoring.blackbox.handlers; import static com.google.common.base.Preconditions.checkNotNull; -import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY; -import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; +import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY; +import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY; import com.google.common.annotations.VisibleForTesting; import com.google.common.flogger.FluentLogger; -import google.registry.monitoring.blackbox.Protocol; +import google.registry.monitoring.blackbox.connection.Protocol; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelInitializer; @@ -38,8 +38,8 @@ import javax.net.ssl.SSLParameters; /** * Adds a client side SSL handler to the channel pipeline. * - *

Code is close to unchanged from {@link SslClientInitializer}

in proxy, but is modified - * for revised overall structure of connections, and to accomdate EPP connections

+ *

Code is close to unchanged from {@link SslClientInitializer} in proxy, but is modified for + * revised overall structure of connections, and to accomdate EPP connections * *

This must be the first handler provided for any handler provider list, if it is * provided. The type parameter {@code C} is needed so that unit tests can construct this handler @@ -56,16 +56,17 @@ public class SslClientInitializer extends ChannelInitializer< private final Supplier privateKeySupplier; private final Supplier certificateSupplier; - public SslClientInitializer(SslProvider sslProvider) { // null uses the system default trust store. - //Used for WebWhois, so we don't care about privateKey and certificates, setting them to null + // Used for WebWhois, so we don't care about privateKey and certificates, setting them to null this(sslProvider, null, null, null); } - public SslClientInitializer(SslProvider sslProvider, Supplier privateKeySupplier, + public SslClientInitializer( + SslProvider sslProvider, + Supplier privateKeySupplier, Supplier certificateSupplier) { - //We use the default trust store here as well, setting trustCertificates to null + // We use the default trust store here as well, setting trustCertificates to null this(sslProvider, null, privateKeySupplier, certificateSupplier); } @@ -92,20 +93,17 @@ public class SslClientInitializer extends ChannelInitializer< Protocol protocol = channel.attr(PROTOCOL_KEY).get(); String host = channel.attr(REMOTE_ADDRESS_KEY).get(); - //Builds SslHandler from Protocol, and based on if we require a privateKey and certificate + // Builds SslHandler from Protocol, and based on if we require a privateKey and certificate checkNotNull(protocol, "Protocol is not set for channel: %s", channel); SslContextBuilder sslContextBuilder = - SslContextBuilder.forClient() - .sslProvider(sslProvider) - .trustManager(trustedCertificates); + SslContextBuilder.forClient().sslProvider(sslProvider).trustManager(trustedCertificates); if (privateKeySupplier != null && certificateSupplier != null) { - sslContextBuilder = sslContextBuilder - .keyManager(privateKeySupplier.get(), certificateSupplier.get()); + sslContextBuilder = + sslContextBuilder.keyManager(privateKeySupplier.get(), certificateSupplier.get()); } - SslHandler sslHandler = sslContextBuilder - .build() - .newHandler(channel.alloc(), host, protocol.port()); + SslHandler sslHandler = + sslContextBuilder.build().newHandler(channel.alloc(), host, protocol.port()); // Enable hostname verification. SSLEngine sslEngine = sslHandler.engine(); @@ -116,4 +114,3 @@ public class SslClientInitializer extends ChannelInitializer< channel.pipeline().addLast(sslHandler); } } - diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandler.java b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandler.java index c56c8c7d9..38044c559 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandler.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandler.java @@ -15,17 +15,17 @@ package google.registry.monitoring.blackbox.handlers; import com.google.common.flogger.FluentLogger; -import google.registry.monitoring.blackbox.ProbingAction; -import google.registry.monitoring.blackbox.Protocol; -import google.registry.monitoring.blackbox.WebWhoisModule.HttpWhoisProtocol; -import google.registry.monitoring.blackbox.WebWhoisModule.HttpsWhoisProtocol; -import google.registry.monitoring.blackbox.WebWhoisModule.WebWhoisProtocol; +import google.registry.monitoring.blackbox.connection.ProbingAction; +import google.registry.monitoring.blackbox.connection.Protocol; import google.registry.monitoring.blackbox.exceptions.ConnectionException; import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.messages.HttpRequestMessage; import google.registry.monitoring.blackbox.messages.HttpResponseMessage; import google.registry.monitoring.blackbox.messages.InboundMessageType; +import google.registry.monitoring.blackbox.modules.WebWhoisModule.HttpWhoisProtocol; +import google.registry.monitoring.blackbox.modules.WebWhoisModule.HttpsWhoisProtocol; +import google.registry.monitoring.blackbox.modules.WebWhoisModule.WebWhoisProtocol; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; @@ -38,9 +38,9 @@ import org.joda.time.Duration; /** * Subclass of {@link ActionHandler} that deals with the WebWhois Sequence * - *

Main purpose is to verify {@link HttpResponseMessage} received is valid. If the response + *

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

+ * {@link HttpResponseStatus.OK} is received */ public class WebWhoisActionHandler extends ActionHandler { @@ -48,24 +48,16 @@ public class WebWhoisActionHandler extends ActionHandler { /** Dagger injected components necessary for redirect responses: */ - /** - * {@link Bootstrap} necessary for remaking connection on redirect response. - */ + /** {@link Bootstrap} necessary for remaking connection on redirect response. */ private final Bootstrap bootstrap; - /** - * {@link Protocol} for when redirected to http endpoint. - */ + /** {@link Protocol} for when redirected to http endpoint. */ private final Protocol httpWhoisProtocol; - /** - * {@link Protocol} for when redirected to https endpoint. - */ + /** {@link Protocol} for when redirected to https endpoint. */ private final Protocol httpsWhoisProtocol; - /** - * {@link HttpRequestMessage} that represents default GET message to be sent on redirect. - */ + /** {@link HttpRequestMessage} that represents default GET message to be sent on redirect. */ private final HttpRequestMessage requestMessage; @Inject @@ -81,7 +73,6 @@ public class WebWhoisActionHandler extends ActionHandler { 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 @@ -98,32 +89,33 @@ public class WebWhoisActionHandler extends ActionHandler { logger.atInfo().log("Received Successful HttpResponseStatus"); logger.atInfo().log("Response Received: " + response); - //On success, we always pass message to ActionHandler's channelRead0 method. + // 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. + // TODO - Fix checker to better determine when we have encountered a redirection response. - //Obtain url to be redirected to + // Obtain url to be redirected to URL url; try { url = new URL(response.headers().get("Location")); } catch (MalformedURLException e) { - //in case of error, log it, and let ActionHandler's exceptionThrown method deal with it + // 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")); + "Redirected Location was invalid. Given Location was: " + + response.headers().get("Location")); } - //From url, extract new host, port, and path + // 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)); + 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 + // Construct new Protocol to reflect redirected host, path, and port Protocol newProtocol; if (url.getProtocol().equals(httpWhoisProtocol.name())) { newProtocol = httpWhoisProtocol; @@ -134,19 +126,21 @@ public class WebWhoisActionHandler extends ActionHandler { "Redirection Location port was invalid. Given protocol name was: " + url.getProtocol()); } - //Obtain HttpRequestMessage with modified headers to reflect new host and path. + // 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(); + // 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 + // close this channel as we no longer need it ChannelFuture future = ctx.close(); future.addListener( f -> { @@ -156,34 +150,29 @@ public class WebWhoisActionHandler extends ActionHandler { logger.atWarning().log("Channel was unsuccessfully closed."); } - //Once channel is closed, establish new connection to redirected host, and repeat + // 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 + // 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()); - } - } - - }); - } - ); + 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 + // 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()); - } } - - } - diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisMessageHandler.java b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisMessageHandler.java index 576d6a04d..8811ebba6 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisMessageHandler.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/handlers/WebWhoisMessageHandler.java @@ -31,12 +31,9 @@ import javax.inject.Inject; public class WebWhoisMessageHandler extends ChannelDuplexHandler { @Inject - public WebWhoisMessageHandler() { - } + public WebWhoisMessageHandler() {} - /** - * Retains {@link HttpRequestMessage} and calls super write method. - */ + /** Retains {@link HttpRequestMessage} and calls super write method. */ @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { @@ -45,7 +42,6 @@ public class WebWhoisMessageHandler extends ChannelDuplexHandler { super.write(ctx, request, promise); } - /** * Converts {@link FullHttpResponse} to {@link HttpResponseMessage}, so it is an {@link * InboundMessageType} instance. diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppMessage.java b/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppMessage.java new file mode 100644 index 000000000..b422a62f9 --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppMessage.java @@ -0,0 +1,454 @@ +// 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.messages; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static google.registry.util.ResourceUtils.readResourceBytes; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.flogger.FluentLogger; +import google.registry.monitoring.blackbox.exceptions.EppClientException; +import google.registry.monitoring.blackbox.exceptions.FailureException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * Superclass of {@link EppRequestMessage} and {@link EppResponseMessage} that represents skeleton + * of any kind of EPP message, whether inbound or outbound. + * + *

NOTE: Most static methods are copied over from + * //java/com/google/domain/registry/monitoring/blackbox/EppHelper.java + * + *

Houses number of static methods for use of conversion between String and bytes to {@link + * Document} type, which represents an XML Document, the type of which is used for EPP messages. + */ +public class EppMessage { + + /** Key that allows for substitution of {@code domainName} to xml template. */ + public static final String DOMAIN_KEY = "//domainns:name"; + + /** Key that allows for substitution of epp user id to xml template. */ + public static final String CLIENT_ID_KEY = "//eppns:clID"; + + /** Key that allows for substitution of epp password to xml template. */ + public static final String CLIENT_PASSWORD_KEY = "//eppns:pw"; + + /** Key that allows for substitution of{@code clTrid} to xml template. */ + public static final String CLIENT_TRID_KEY = "//eppns:clTRID"; + + /** Key that allows for substitution of{@code svTrid} to xml template. */ + public static final String SERVER_TRID_KEY = "//eppns:svTRID"; + + /** + * Expression that expresses a result code in the {@link EppResponseMessage} that means success. + */ + @VisibleForTesting + public static final String XPASS_EXPRESSION = + String.format("//eppns:result[@code>='%s'][@code<'%s']", 1000, 2000); + /** + * Expression that expresses a result code in the {@link EppResponseMessage} that means failure. + */ + @VisibleForTesting + public static final String XFAIL_EXPRESSION = + String.format("//eppns:result[@code>='%s'][@code<'%s']", 2000, 3000); + // "Security" errors from RFC 5730, plus the error we get when we end + // up no longer logged (see b/28196510). + // 2002 "Command use error" + // 2200 "Authentication error" + // 2201 "Authorization error" + // 2202 "Invalid authorization information" + @VisibleForTesting + static final String AUTHENTICATION_ERROR = + "//eppns:epp/eppns:response/eppns:result[@code!='2002' and @code!='2200' " + + "and @code!='2201' and @code!='2202']"; + + static final String VALID_SLD_LABEL_REGEX; + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + /** + * Static variables necessary for static methods that serve as tools for {@link Document} + * creation, conversion, and verification. + */ + private static final DocumentBuilderFactory docBuilderFactory = + DocumentBuilderFactory.newInstance(); + + private static final XPath xpath; + private static final Schema eppSchema; + // As per RFC 1035 section 2.3.4 http://tools.ietf.org/html/rfc1035#page-10 and updated by + // http://tools.ietf.org/html/rfc1123#page-13 which suggests a domain part length + // of 255 SHOULD be supported, so we are permitting something close to that, but reserve + // at least two characters for a TLD and a full stop (".") character. + private static final int MAX_DOMAIN_PART_LENGTH = 252; + private static final int MAX_SLD_DOMAIN_LABEL_LENGTH = 255; + private static final String VALID_DOMAIN_PART_REGEX; + private static final String VALID_TLD_PART_REGEX; + /** Standard EPP header number of bytes (size of int). */ + protected static int HEADER_LENGTH = 4; + + static { + // tld label part may contain a dot and end with a dot, and must + // start and end with 0-9 or a-zA-Z but may contain any number of + // dashes (minus signs "-") in between (even consecutive) + VALID_TLD_PART_REGEX = + String.format( + "\\p{Alnum}[-\\p{Alnum}]{0,%s}\\.{0,1}[-\\p{Alnum}]{0,%s}\\p{Alnum}", + (MAX_DOMAIN_PART_LENGTH - 2), (MAX_DOMAIN_PART_LENGTH - 2)); + // domain label part ("left" of the tld) must start and end with 0-9 or a-zA-Z but + // may contain any number of dashes (minus signs "-") in between (even consecutive) + VALID_DOMAIN_PART_REGEX = + String.format("\\p{Alnum}[-\\p{Alnum}]{0,%s}\\p{Alnum}", (MAX_DOMAIN_PART_LENGTH - 2)); + VALID_SLD_LABEL_REGEX = + String.format( + "(?=.{4,%s})%s\\.%s(\\.)*", + MAX_SLD_DOMAIN_LABEL_LENGTH, VALID_DOMAIN_PART_REGEX, VALID_TLD_PART_REGEX); + + xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(new EppNamespaceContext()); + docBuilderFactory.setNamespaceAware(true); + + String path = "./xsd/"; + StreamSource[] sources; + try { + sources = + new StreamSource[] { + new StreamSource(readResource(path + "eppcom.xsd")), + new StreamSource(readResource(path + "epp.xsd")), + new StreamSource(readResource(path + "host.xsd")), + new StreamSource(readResource(path + "contact.xsd")), + new StreamSource(readResource(path + "domain.xsd")), + new StreamSource(readResource(path + "rgp.xsd")), + new StreamSource(readResource(path + "mark.xsd")), + new StreamSource(readResource(path + "dsig.xsd")), + new StreamSource(readResource(path + "smd.xsd")), + new StreamSource(readResource(path + "launch.xsd")), + }; + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + eppSchema = schemaFactory.newSchema(sources); + } catch (SAXException | IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * {@link Document} that represents the actual XML document sent inbound or outbound through + * channel pipeline. + */ + protected Document message; + + /** Helper method that reads resource existing in same package as {@link EppMessage} class. */ + private static InputStream readResource(String filename) throws IOException { + return readResourceBytes(EppMessage.class, filename).openStream(); + } + + /** + * Validate an EPP XML document against the set of XSD files that make up the EPP XML Schema. + * + *

Note that the document must have the namespace attributes set where expected (i.e. must be + * built using a DocumentBuilder with namespace awareness set if using a DocumentBuilder). + * + * @param xml an XML Document to validate + * @throws SAXException if the document is not valid + */ + public static void eppValidate(Document xml) throws SAXException, IOException { + Validator schemaValidator = eppSchema.newValidator(); + schemaValidator.validate(new DOMSource(xml)); + } + + /** + * Verify an XML Document as an EPP Response using the provided XPath expressions. + * + *

This will first validate the document against the EPP schema, then run through the list of + * xpath expressions -- so those need only look for specific EPP elements + values. + * + * @param xml the XML Document containing the EPP reponse to verify + * @param expressions a list of XPath expressions to query the document with. + * @param validate a boolean flag to control if schema validation occurs (useful for testing) + * @throws IOException if InputStream throws one + * @throws EppClientException if the EPP response cannot be read, parsed, or doesn't containing + * matching data specified in expressions + */ + protected static void verifyEppResponse(Document xml, List expressions, boolean validate) + throws FailureException { + if (validate) { + try { + eppValidate(xml); + } catch (SAXException | IOException e) { + throw new FailureException(e); + } + } + try { + for (String exp : expressions) { + NodeList nodes = (NodeList) xpath.evaluate(exp, xml, XPathConstants.NODESET); + if (nodes.getLength() == 0) { + throw new FailureException("invalid EPP response. failed expression " + exp); + } + } + } catch (XPathExpressionException e) { + throw new FailureException(e); + } + } + + /** + * A helper method to extract a value from an element in an XML document. + * + * @return the text value for the element, or null is the element is not found + */ + public static String getElementValue(Document xml, String expression) { + try { + return (String) xpath.evaluate(expression, xml, XPathConstants.STRING); + } catch (XPathExpressionException e) { + logger.atSevere().withCause(e).log("Bad expression: %s", expression); + return null; + } + } + + /** + * A helper method to transform an XML Document to a string. - e.g. a returned string might look + * like the following for a Document with a root element of "foo" that has a child element of + * "bar" which has text of "baz":
+ * {@code 'baz'} + * + * @param xml the Document to transform + * @return the resulting string or {@code null} if {@code xml} is {@code null}. + * @throws EppClientException if the transform fails + */ + @Nullable + public static String xmlDocToString(@Nullable Document xml) throws EppClientException { + if (xml == null) { + return null; + } + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StreamResult result = new StreamResult(new StringWriter()); + DOMSource source = new DOMSource(xml); + transformer.transform(source, result); + return result.getWriter().toString(); + } catch (TransformerException e) { + throw new EppClientException(e); + } + } + + /** + * A helper method to transform an XML Document to a byte array using the XML Encoding when + * converting from a String (see xmlDocToString). + * + * @param xml the Document to transform + * @return the resulting byte array. + * @throws EppClientException if the transform fails + */ + public static byte[] xmlDocToByteArray(Document xml) throws EppClientException { + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StreamResult result = new StreamResult(new StringWriter()); + DOMSource source = new DOMSource(xml); + transformer.transform(source, result); + String resultString = result.getWriter().toString(); + if (isNullOrEmpty(resultString)) { + throw new EppClientException("unknown error converting Document to intermediate string"); + } + String encoding = xml.getXmlEncoding(); + // this is actually not a problem since we can just use the default + if (encoding == null) { + encoding = Charset.defaultCharset().name(); + } + return resultString.getBytes(encoding); + } catch (TransformerException | UnsupportedEncodingException e) { + throw new EppClientException(e); + } + } + + /** + * A helper method to transform an byte array to an XML {@link Document} using {@code + * docBuilderFactory} + * + * @param responseBuffer the byte array to transform + * @return the resulting Document + * @throws EppClientException if the transform fails + */ + public static Document byteArrayToXmlDoc(byte[] responseBuffer) throws FailureException { + Document xml; + try { + DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); + ByteArrayInputStream byteStream = new ByteArrayInputStream(responseBuffer); + xml = builder.parse(byteStream); + } catch (ParserConfigurationException | SAXException | IOException e) { + throw new FailureException(e); + } + return xml; + } + + /** + * Reads one of a set of EPP templates (included as resources in our jar) and finds nodes using an + * xpath expression, then replaces the node value of the first child, returning the transformed + * XML as a Document. + * + *

E.g. to replace the value "@@CLTRID@@" in the {@code } node with a client + * transaction ID, use the mapping {@code <"//domainns:clTRID", "AAA-123-BBBB">} (or whatever the + * ID is). + * + * @param template the relative (unqualified) name of the template file to use + * @param replacements a map of strings to replace in the template keyed by the xpath expression + * to use to find the nodes to operate on with the value being the text to use as the + * replacement + * @return the transformed EPP document + * @throws IOException if the template cannot be read + * @throws EppClientException if there are issues parsing the template or evaluating the xpath + * expression, or if the resulting document is not valid EPP + * @throws IllegalArgumentException if the xpath expression query yields anything other than an + * Element node type + */ + public static Document getEppDocFromTemplate(String template, Map replacements) + throws IOException, EppClientException { + Document xmlDoc; + + try (InputStream is = readResource(template)) { + DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); + xmlDoc = builder.parse(is); + for (String key : replacements.keySet()) { + NodeList nodes = (NodeList) xpath.evaluate(key, xmlDoc, XPathConstants.NODESET); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeType() != Node.ELEMENT_NODE) { + throw new IllegalArgumentException( + String.format( + "xpath expression (%s) must result in Element nodes, got %s", + key, node.getNodeType())); + } + node.getFirstChild().setNodeValue(replacements.get(key)); + } + } + + eppValidate(xmlDoc); + } catch (SAXException | XPathExpressionException | ParserConfigurationException e) { + throw new EppClientException(e); + } + return xmlDoc; + } + + @Nullable + public String getElementValue(String expression) { + try { + return (String) xpath.evaluate(expression, message, XPathConstants.STRING); + } catch (XPathExpressionException e) { + logger.atSevere().withCause(e).log("Bad expression: %s", expression); + return null; + } + } + + @Override + public String toString() { + try { + return xmlDocToString(message); + } catch (EppClientException e) { + return "No Message Found"; + } + } + + /** + * Implements the {@link NamespaceContext} interface and adds an EPP namespace URI (prefix eppns). + */ + static class EppNamespaceContext implements NamespaceContext { + + final Map nsPrefixMap = new HashMap<>(); + final Map> nsUriMap = new HashMap<>(); + + public EppNamespaceContext() { + try { + addNamespace(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI); + addNamespace(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI); + addNamespace("eppns", "urn:ietf:params:xml:ns:epp-1.0"); + addNamespace("contactns", "urn:ietf:params:xml:ns:contact-1.0"); + addNamespace("domainns", "urn:ietf:params:xml:ns:domain-1.0"); + addNamespace("hostns", "urn:ietf:params:xml:ns:host-1.0"); + addNamespace("launchns", "urn:ietf:params:xml:ns:launch-1.0"); + } catch (Exception e) { + // this should never happen here but if it does, we just turn it into a runtime exception + throw new IllegalArgumentException(e); + } + } + + void addNamespace(String prefix, String namespaceURI) throws Exception { + checkArgument(!isNullOrEmpty(prefix), "prefix"); + checkArgument(!isNullOrEmpty(namespaceURI), "namespaceURI"); + if (nsPrefixMap.containsKey(prefix)) { + throw new RuntimeException("key already exists: " + prefix); + } + nsPrefixMap.put(prefix, namespaceURI); + if (nsUriMap.containsKey(namespaceURI)) { + nsUriMap.get(namespaceURI).add(prefix); + } else { + Set prefixSet = new HashSet<>(); + prefixSet.add(prefix); + nsUriMap.put(namespaceURI, prefixSet); + } + } + + @Override + public String getNamespaceURI(String prefix) { + checkArgument(!isNullOrEmpty(prefix), "prefix"); + return nsPrefixMap.getOrDefault(prefix, XMLConstants.NULL_NS_URI); + } + + @Override + public String getPrefix(String namespaceURI) { + checkArgument(!isNullOrEmpty(namespaceURI), "namespaceURI"); + return getPrefixes(namespaceURI).next(); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + checkArgument(!isNullOrEmpty(namespaceURI), "namespaceURI"); + if (nsUriMap.containsKey(namespaceURI)) { + return nsUriMap.get(namespaceURI).iterator(); + } else { + return Collections.emptyIterator(); + } + } + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppRequestMessage.java b/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppRequestMessage.java new file mode 100644 index 000000000..39e8b92eb --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppRequestMessage.java @@ -0,0 +1,132 @@ +// 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.messages; + +import com.google.common.collect.ImmutableMap; +import google.registry.monitoring.blackbox.exceptions.EppClientException; +import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.io.IOException; +import java.util.Map; +import java.util.function.BiFunction; + +/** + * {@link EppMessage} subclass that implements {@link OutboundMessageType}, which represents an + * outbound Epp message. + * + *

In modifying the {@code getReplacements} field of {@link EppRequestMessage} and the template, + * we can represent the the 5 basic EPP commands we are attempting to probe. The original 5 are: + * LOGIN, CREATE, CHECK, DELETE, LOGOUT. + * + *

In turn, we equivalently create 10 different EPP commands probed: Hello - checks for Greeting + * response, Login expecting success, Login expecting Failure, Create expecting Success, Create + * expecting Failure, Check that the domain exists, Check that the domain doesn't exist, Delete + * expecting Success, Delete expecting Failure, and Logout expecting Success. + * + *

The main difference is that we added a hello command that simply waits for the server to send + * a greeting, then moves on to the Login action. + * + *

Stores a clTRID and domainName which is modified each time the token calls {@code + * modifyMessage}. These will also modify the EPP request sent to the server. + */ +public class EppRequestMessage extends EppMessage implements OutboundMessageType { + + /** Corresponding {@link EppResponseMessage} that we expect to receive on a successful request. */ + private EppResponseMessage expectedResponse; + + /** Filename for template of current request type. */ + private String template; + + /** + * {@link ImmutableMap} of replacements that is indicative of each type of {@link + * EppRequestMessage} + */ + private BiFunction> getReplacements; + + /** + * Private constructor for {@link EppRequestMessage} that its subclasses use for instantiation. + */ + public EppRequestMessage( + EppResponseMessage expectedResponse, + String template, + BiFunction> getReplacements) { + + this.expectedResponse = expectedResponse; + this.template = template; + this.getReplacements = getReplacements; + } + + /** + * From the input {@code clTrid} and {@code domainName}, modifies the template EPP XML document + * and the {@code expectedResponse} to reflect new parameters. + * + * @param args - should always be two Strings: The first one is {@code clTrid} and the second one + * is {@code domainName}. + * @return the current {@link EppRequestMessage} instance. + * @throws EppClientException - On the occasion that the prober can't appropriately modify the EPP + * XML document, the blame falls on the prober, not the server, so it throws an {@link + * EppClientException}, which is a subclass of the {@link UndeterminedStateException}. + */ + @Override + public EppRequestMessage modifyMessage(String... args) throws EppClientException { + // First argument should always be clTRID. + String clTrid = args[0]; + + // Second argument should always be domainName. + String domainName = args[1]; + + if (template != null) { + // Checks if we are sending an actual EPP request to the server (if template is null, than + // we just expect a response. + try { + message = getEppDocFromTemplate(template, getReplacements.apply(clTrid, domainName)); + } catch (IOException e) { + throw new EppClientException(e); + } + } + // Update the EppResponseMessage associated with this EppRequestMessage to reflect changed + // parameters on this step. + expectedResponse.updateInformation(clTrid, domainName); + return this; + } + + /** + * Converts the current {@link org.w3c.dom.Document} message to a {@link ByteBuf} with the + * requisite bytes + * + * @return the {@link ByteBuf} instance that stores the bytes representing the requisite EPP + * Request + * @throws EppClientException- On the occasion that the prober can't appropriately convert the EPP + * XML document to a {@link ByteBuf}, the blame falls on the prober, not the server, so it + * throws an {@link EppClientException}, which is a subclass of the {@link + * UndeterminedStateException}. + */ + public ByteBuf bytes() throws EppClientException { + // obtain byte array of our modified xml document + byte[] bytestream = xmlDocToByteArray(message); + + // Move bytes to a ByteBuf. + ByteBuf buf = Unpooled.buffer(bytestream.length); + buf.writeBytes(bytestream); + + return buf; + } + + /** */ + public EppResponseMessage getExpectedResponse() { + return expectedResponse; + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppResponseMessage.java b/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppResponseMessage.java new file mode 100644 index 000000000..5d833c046 --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/messages/EppResponseMessage.java @@ -0,0 +1,87 @@ +// 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.messages; + +import google.registry.monitoring.blackbox.exceptions.FailureException; +import io.netty.buffer.ByteBuf; +import java.util.List; +import java.util.function.BiFunction; + +/** + * {@link EppMessage} subclass that implements {@link InboundMessageType}, which represents an + * inbound EPP message and serves to verify the response received from the server. + * + *

There are 4 created types of this {@link EppRequestMessage}, which represent the expected + * successful response types. Their names are: success, failure greeting, domainExists, + * domainNotExists. Success implies a response declaring the command was completed successfully. + * Failure implies a response declaring the command was not completed successfully. Greeting is the + * standard initial response the server sends after a connection is established. DomainExists is a + * response to a Check request saying the domain exists on the server. DomainNotExists is a response + * that essentially says the opposite. + * + *

Stores an expected clTRID and domainName which are the ones used by the {@link + * EppRequestMessage} pointing to this {@link EppRequestMessage}. + * + *

From the {@link ByteBuf} input, stores the corresponding {@link org.w3c.dom.Document} + * represented and to be validated. + */ +public class EppResponseMessage extends EppMessage implements InboundMessageType { + + /** + * Specifies type of {@link EppResponseMessage}. + * + *

All possible names are: success, failure, domainExists, domainNotExists, greeting. + */ + private final String name; + + /** Lambda expression that returns a checkList from input clTRID and domain Strings. */ + private final BiFunction> getCheckList; + + /** Domain name we expect to receive in current response. */ + private String expectedDomainName; + + /** ClTRID we expect to receive in current response. */ + private String expectedClTrid; + + /** Verifies that the response recorded is what we expect from the request sent. */ + public void verify() throws FailureException { + verifyEppResponse(message, getCheckList.apply(expectedClTrid, expectedDomainName), true); + } + + /** Extracts {@link org.w3c.dom.Document} from the {@link ByteBuf} input. */ + public void getDocument(ByteBuf buf) throws FailureException { + // Convert ByteBuf to byte array. + byte[] response = new byte[buf.readableBytes()]; + buf.readBytes(response); + + // Convert byte array to Document. + message = byteArrayToXmlDoc(response); + } + + /** Updates {@code expectedClTrid} and {@code expectedDomainName} fields. */ + void updateInformation(String expectedClTrid, String expectedDomainName) { + this.expectedClTrid = expectedClTrid; + this.expectedDomainName = expectedDomainName; + } + + public EppResponseMessage(String name, BiFunction> getCheckList) { + this.name = name; + this.getCheckList = getCheckList; + } + + public String name() { + return name; + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpRequestMessage.java b/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpRequestMessage.java index b850cfb3b..91e6c9214 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpRequestMessage.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpRequestMessage.java @@ -25,9 +25,9 @@ import javax.inject.Inject; /** * {@link OutboundMessageType} subtype that acts identically to {@link DefaultFullHttpRequest}. * - *

As it is an {@link OutboundMessageType} subtype, there is a {@code modifyMessage} method - * that modifies the request to reflect the new host and optional path. We also implement a {@code - * name} method, which returns a standard name and the current hostname.

+ *

As it is an {@link OutboundMessageType} subtype, there is a {@code modifyMessage} method that + * modifies the request to reflect the new host and optional path. We also implement a {@code name} + * method, which returns a standard name and the current hostname. */ public class HttpRequestMessage extends DefaultFullHttpRequest implements OutboundMessageType { @@ -40,15 +40,12 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou super(httpVersion, method, uri); } - private HttpRequestMessage(HttpVersion httpVersion, HttpMethod method, String uri, - ByteBuf content) { + private HttpRequestMessage( + HttpVersion httpVersion, HttpMethod method, String uri, ByteBuf content) { super(httpVersion, method, uri, content); } - - /** - * Used for conversion from {@link FullHttpRequest} to {@link HttpRequestMessage} - */ + /** Used for conversion from {@link FullHttpRequest} to {@link HttpRequestMessage} */ public HttpRequestMessage(FullHttpRequest request) { this(request.protocolVersion(), request.method(), request.uri(), request.content()); request.headers().forEach((entry) -> headers().set(entry.getKey(), entry.getValue())); @@ -60,9 +57,7 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou return this; } - /** - * Modifies headers to reflect new host and new path if applicable. - */ + /** Modifies headers to reflect new host and new path if applicable. */ @Override public HttpRequestMessage modifyMessage(String... args) throws IllegalArgumentException { if (args.length == 1 || args.length == 2) { @@ -78,7 +73,8 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou String.format( "Wrong number of arguments present for modifying HttpRequestMessage." + " Received %d arguments instead of 2. Received arguments: " - + Arrays.toString(args), args.length)); + + Arrays.toString(args), + args.length)); } } @@ -86,5 +82,4 @@ public class HttpRequestMessage extends DefaultFullHttpRequest implements Outbou public String toString() { return String.format("Http(s) Request on: %s", headers().get("host")); } - } diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpResponseMessage.java b/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpResponseMessage.java index 7d2ff7220..929c9c898 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpResponseMessage.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/messages/HttpResponseMessage.java @@ -20,18 +20,14 @@ import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; -/** - * {@link InboundMessageType} subtype that acts identically to {@link DefaultFullHttpResponse} - */ +/** {@link InboundMessageType} subtype that acts identically to {@link DefaultFullHttpResponse} */ public class HttpResponseMessage extends DefaultFullHttpResponse implements InboundMessageType { private HttpResponseMessage(HttpVersion version, HttpResponseStatus status, ByteBuf content) { super(version, status, content); } - /** - * Used for pipeline conversion from {@link FullHttpResponse} to {@link HttpResponseMessage} - */ + /** Used for pipeline conversion from {@link FullHttpResponse} to {@link HttpResponseMessage} */ public HttpResponseMessage(FullHttpResponse response) { this(response.protocolVersion(), response.status(), response.content()); diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/messages/InboundMessageType.java b/prober/src/main/java/google/registry/monitoring/blackbox/messages/InboundMessageType.java index 0a584dfa2..84b77f89d 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/messages/InboundMessageType.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/messages/InboundMessageType.java @@ -18,6 +18,4 @@ package google.registry.monitoring.blackbox.messages; * Marker Interface that is implemented by all classes that serve as {@code inboundMessages} in * channel pipeline */ -public interface InboundMessageType { - -} +public interface InboundMessageType {} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/modules/CertificateModule.java b/prober/src/main/java/google/registry/monitoring/blackbox/modules/CertificateModule.java new file mode 100644 index 000000000..a064e869b --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/modules/CertificateModule.java @@ -0,0 +1,125 @@ +// 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.modules; + +import static com.google.common.base.Suppliers.memoizeWithExpiration; +import static google.registry.util.ResourceUtils.readResourceBytes; +import static google.registry.util.ResourceUtils.readResourceUtf8; + +import dagger.Module; +import dagger.Provides; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import javax.inject.Provider; +import javax.inject.Qualifier; +import javax.inject.Singleton; +import org.joda.time.Duration; + +/** + * Dagger module that provides bindings needed to inject server certificate chain and private key. + * + *

Currently the sandbox certificates and private key are stored as local secrets in a .p12 file, + * however, in production, all certificates will be stored in a .pem file that is encrypted by Cloud + * KMS. The .pem file can be generated by concatenating the .crt certificate files on the chain and + * the .key private file. + * + *

The production certificates in the .pem file must be stored in order, where the next + * certificate's subject is the previous certificate's issuer. + * + * @see Cloud Key Management Service + */ +@Module +public class CertificateModule { + + /** {@link Qualifier} to identify components provided from Local Secrets. */ + // TODO - remove this qualifier and replace it with using KMS to retrieve private key and + // certificate + @Qualifier + public @interface LocalSecrets {} + + private static InputStream readResource(String filename) throws IOException { + return readResourceBytes(CertificateModule.class, filename).openStream(); + } + + @Provides + @LocalSecrets + static Duration provideCacheDuration() { + return Duration.standardSeconds(2); + } + + @Singleton + @Provides + @LocalSecrets + static String keystorePasswordProvider() { + return readResourceUtf8(CertificateModule.class, "secrets/keystore_password.txt"); + } + + @Provides + @LocalSecrets + static PrivateKey providePrivateKey(@LocalSecrets Provider passwordProvider) { + try { + InputStream inStream = readResource("secrets/prober-client-tls-sandbox.p12"); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(inStream, passwordProvider.get().toCharArray()); + + String alias = ks.aliases().nextElement(); + return (PrivateKey) ks.getKey(alias, passwordProvider.get().toCharArray()); + } catch (IOException | GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + @Provides + @LocalSecrets + static X509Certificate[] provideCertificates(@LocalSecrets Provider passwordProvider) { + try { + InputStream inStream = readResource("secrets/prober-client-tls-sandbox.p12"); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(inStream, passwordProvider.get().toCharArray()); + + String alias = ks.aliases().nextElement(); + return new X509Certificate[] {(X509Certificate) ks.getCertificate(alias)}; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Singleton + @Provides + @LocalSecrets + static Supplier providePrivatekeySupplier( + @LocalSecrets Provider privateKeyProvider, @LocalSecrets Duration duration) { + return memoizeWithExpiration( + privateKeyProvider::get, duration.getStandardSeconds(), TimeUnit.SECONDS); + } + + @Singleton + @Provides + @LocalSecrets + static Supplier provideCertificatesSupplier( + @LocalSecrets Provider certificatesProvider, + @LocalSecrets Duration duration) { + return memoizeWithExpiration( + certificatesProvider::get, duration.getStandardSeconds(), TimeUnit.SECONDS); + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/modules/EppModule.java b/prober/src/main/java/google/registry/monitoring/blackbox/modules/EppModule.java new file mode 100644 index 000000000..91bb284be --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/modules/EppModule.java @@ -0,0 +1,582 @@ +// 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.modules; + +import static google.registry.monitoring.blackbox.messages.EppRequestMessage.CLIENT_ID_KEY; +import static google.registry.monitoring.blackbox.messages.EppRequestMessage.CLIENT_PASSWORD_KEY; +import static google.registry.monitoring.blackbox.messages.EppRequestMessage.CLIENT_TRID_KEY; +import static google.registry.monitoring.blackbox.messages.EppRequestMessage.DOMAIN_KEY; +import static google.registry.util.ResourceUtils.readResourceUtf8; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +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.handlers.EppActionHandler; +import google.registry.monitoring.blackbox.handlers.EppMessageHandler; +import google.registry.monitoring.blackbox.handlers.SslClientInitializer; +import google.registry.monitoring.blackbox.messages.EppMessage; +import google.registry.monitoring.blackbox.messages.EppRequestMessage; +import google.registry.monitoring.blackbox.messages.EppResponseMessage; +import google.registry.monitoring.blackbox.modules.CertificateModule.LocalSecrets; +import google.registry.monitoring.blackbox.tokens.EppToken; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.ssl.SslProvider; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.function.Supplier; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Qualifier; +import javax.inject.Singleton; +import org.joda.time.Duration; + +/** + * A module that provides the components necessary for and the overall {@link ProbingSequence} to + * probe EPP. + */ +@Module +public class EppModule { + + private static final int EPP_PORT = 700; + private static final String EPP_PROTOCOL_NAME = "epp"; + + /** Maximum length of EPP messages received. Same as max length for HTTP messages, is 0.5 MB. */ + // TODO - replace with config specified maximum length. + private static final int maximumMessageLengthBytes = 512 * 1024; + + /** Standard EPP header length. */ + // TODO - replace with config specified header length (still 4). + private static final int eppHeaderLengthBytes = 4; + + /** Dagger provided {@link ProbingSequence} that probes EPP login and logout actions. */ + @Provides + @Singleton + @EppProtocol + static Bootstrap provideEppBootstrap(Provider bootstrapProvider) { + return bootstrapProvider.get(); + } + + /** + * Dagger provided {@link ProbingSequence} that probes EPP login, create, check, and delete + * actions with a persistent connection. + */ + @Provides + @Singleton + @IntoSet + static ProbingSequence provideEppLoginCreateCheckDeleteCheckProbingSequence( + EppToken.Persistent token, + @Named("hello") ProbingStep helloStep, + @Named("loginSuccess") ProbingStep loginSuccessStep, + @Named("createSuccess") ProbingStep createSuccessStep, + @Named("checkExists") ProbingStep checkStepFirst, + @Named("deleteSuccess") ProbingStep deleteSuccessStep, + @Named("checkNotExists") ProbingStep checkStepSecond) { + return new ProbingSequence.Builder(token) + .add(helloStep) + .add(loginSuccessStep) + .add(createSuccessStep) + .markFirstRepeated() + .add(checkStepFirst) + .add(deleteSuccessStep) + .add(checkStepSecond) + .build(); + } + + /** + * Dagger provided {@link ProbingSequence} that probes EPP login, create, check, delete, and + * logout actions with a transient connection. + */ + @Provides + @Singleton + @IntoSet + static ProbingSequence provideEppLoginCreateCheckDeleteCheckLogoutProbingSequence( + EppToken.Transient token, + @Named("hello") ProbingStep helloStep, + @Named("loginSuccess") ProbingStep loginSuccessStep, + @Named("createSuccess") ProbingStep createSuccessStep, + @Named("checkExists") ProbingStep checkStepFirst, + @Named("deleteSuccess") ProbingStep deleteSuccessStep, + @Named("checkNotExists") ProbingStep checkStepSecond, + @Named("logout") ProbingStep logoutStep) { + return new ProbingSequence.Builder(token) + .add(helloStep) + .add(loginSuccessStep) + .add(createSuccessStep) + .add(checkStepFirst) + .add(deleteSuccessStep) + .add(checkStepSecond) + .add(logoutStep) + .build(); + } + + /** + * Provides {@link ProbingStep} that establishes initial connection to EPP server. + * + *

Always necessary as first step for any EPP {@link ProbingSequence} and first repeated step + * for any {@link ProbingSequence} that doesn't stay logged in (transient). + */ + @Provides + @Named("hello") + static ProbingStep provideEppHelloStep( + @EppProtocol Protocol eppProtocol, + Duration duration, + @Named("hello") EppRequestMessage helloRequest, + @EppProtocol Bootstrap bootstrap) { + return ProbingStep.builder() + .setProtocol(eppProtocol) + .setDuration(duration) + .setMessageTemplate(helloRequest) + .setBootstrap(bootstrap) + .build(); + } + + /** {@link Provides} {@link ProbingStep} that logs into the EPP server. */ + @Provides + @Named("loginSuccess") + static ProbingStep provideEppLoginSuccessStep( + @EppProtocol Protocol eppProtocol, + Duration duration, + @Named("loginSuccess") EppRequestMessage loginSuccessRequest, + @EppProtocol Bootstrap bootstrap) { + return ProbingStep.builder() + .setProtocol(eppProtocol) + .setDuration(duration) + .setMessageTemplate(loginSuccessRequest) + .setBootstrap(bootstrap) + .build(); + } + + /** {@link Provides} {@link ProbingStep} that creates a new domain on EPP server. */ + @Provides + @Named("createSuccess") + static ProbingStep provideEppCreateSuccessStep( + @EppProtocol Protocol eppProtocol, + Duration duration, + @Named("createSuccess") EppRequestMessage createSuccessRequest, + @EppProtocol Bootstrap bootstrap) { + return ProbingStep.builder() + .setProtocol(eppProtocol) + .setDuration(duration) + .setMessageTemplate(createSuccessRequest) + .setBootstrap(bootstrap) + .build(); + } + + /** {@link Provides} {@link ProbingStep} that built, checks a domain exists on EPP server. */ + @Provides + @Named("checkExists") + static ProbingStep provideEppCheckExistsStep( + @EppProtocol Protocol eppProtocol, + Duration duration, + @Named("checkExists") EppRequestMessage checkExistsRequest, + @EppProtocol Bootstrap bootstrap) { + return ProbingStep.builder() + .setProtocol(eppProtocol) + .setDuration(duration) + .setMessageTemplate(checkExistsRequest) + .setBootstrap(bootstrap) + .build(); + } + + /** {@link Provides} {@link ProbingStep} that checks a domain doesn't exist on EPP server. */ + @Provides + @Named("checkNotExists") + static ProbingStep provideEppCheckNotExistsStep( + @EppProtocol Protocol eppProtocol, + Duration duration, + @Named("checkNotExists") EppRequestMessage checkNotExistsRequest, + @EppProtocol Bootstrap bootstrap) { + return ProbingStep.builder() + .setProtocol(eppProtocol) + .setDuration(duration) + .setMessageTemplate(checkNotExistsRequest) + .setBootstrap(bootstrap) + .build(); + } + + /** {@link Provides} {@link ProbingStep} that deletes a domain from EPP server. */ + @Provides + @Named("deleteSuccess") + static ProbingStep provideEppDeleteSuccessStep( + @EppProtocol Protocol eppProtocol, + Duration duration, + @Named("deleteSuccess") EppRequestMessage deleteSuccessRequest, + @EppProtocol Bootstrap bootstrap) { + return ProbingStep.builder() + .setProtocol(eppProtocol) + .setDuration(duration) + .setMessageTemplate(deleteSuccessRequest) + .setBootstrap(bootstrap) + .build(); + } + + /** {@link Provides} {@link ProbingStep} that logs out of EPP server. */ + @Provides + @Named("logout") + static ProbingStep provideEppLogoutStep( + @EppProtocol Protocol eppProtocol, + Duration duration, + @Named("logout") EppRequestMessage logoutRequest, + @EppProtocol Bootstrap bootstrap) { + return ProbingStep.builder() + .setProtocol(eppProtocol) + .setDuration(duration) + .setMessageTemplate(logoutRequest) + .setBootstrap(bootstrap) + .build(); + } + + /** {@link Provides} hello {@link EppRequestMessage} with only expected response of greeting. */ + @Provides + @Named("hello") + static EppRequestMessage provideHelloRequestMessage( + @Named("greeting") EppResponseMessage greetingResponse) { + + return new EppRequestMessage(greetingResponse, null, (a, b) -> ImmutableMap.of()); + } + + /** + * Set of all possible {@link EppRequestMessage}s paired with their expected {@link + * EppResponseMessage}s. + */ + + /** {@link Provides} login {@link EppRequestMessage} with expected response of success. */ + @Provides + @Named("loginSuccess") + static EppRequestMessage provideLoginSuccessRequestMessage( + @Named("success") EppResponseMessage successResponse, + @Named("login") String loginTemplate, + @Named("eppUserId") String userId, + @Named("eppPassword") String userPassword) { + return new EppRequestMessage( + successResponse, + loginTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + CLIENT_ID_KEY, userId, + CLIENT_PASSWORD_KEY, userPassword)); + } + + /** {@link Provides} login {@link EppRequestMessage} with expected response of failure. */ + @Provides + @Named("loginFailure") + static EppRequestMessage provideLoginFailureRequestMessage( + @Named("failure") EppResponseMessage failureResponse, + @Named("login") String loginTemplate, + @Named("eppUserId") String userId, + @Named("eppPassword") String userPassword) { + return new EppRequestMessage( + failureResponse, + loginTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + CLIENT_ID_KEY, userId, + CLIENT_PASSWORD_KEY, userPassword)); + } + + /** {@link Provides} create {@link EppRequestMessage} with expected response of success. */ + @Provides + @Named("createSuccess") + static EppRequestMessage provideCreateSuccessRequestMessage( + @Named("success") EppResponseMessage successResponse, + @Named("create") String createTemplate) { + return new EppRequestMessage( + successResponse, + createTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** {@link Provides} create {@link EppRequestMessage} with expected response of failure. */ + @Provides + @Named("createFailure") + static EppRequestMessage provideCreateFailureRequestMessage( + @Named("failure") EppResponseMessage failureResponse, + @Named("create") String createTemplate) { + return new EppRequestMessage( + failureResponse, + createTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** {@link Provides} delete {@link EppRequestMessage} with expected response of success. */ + @Provides + @Named("deleteSuccess") + static EppRequestMessage provideDeleteSuccessRequestMessage( + @Named("success") EppResponseMessage successResponse, + @Named("delete") String deleteTemplate) { + return new EppRequestMessage( + successResponse, + deleteTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** {@link Provides} delete {@link EppRequestMessage} with expected response of failure. */ + @Provides + @Named("deleteFailure") + static EppRequestMessage provideDeleteFailureRequestMessage( + @Named("failure") EppResponseMessage failureResponse, + @Named("delete") String deleteTemplate) { + return new EppRequestMessage( + failureResponse, + deleteTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** {@link Provides} logout {@link EppRequestMessage} with expected response of success. */ + @Provides + @Named("logout") + static EppRequestMessage provideLogoutSuccessRequestMessage( + @Named("success") EppResponseMessage successResponse, + @Named("logout") String logoutTemplate) { + return new EppRequestMessage( + successResponse, + logoutTemplate, + (clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid)); + } + + /** {@link Provides} check {@link EppRequestMessage} with expected response of domainExists. */ + @Provides + @Named("checkExists") + static EppRequestMessage provideCheckExistsRequestMessage( + @Named("domainExists") EppResponseMessage domainExistsResponse, + @Named("check") String checkTemplate) { + return new EppRequestMessage( + domainExistsResponse, + checkTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** {@link Provides} check {@link EppRequestMessage} with expected response of domainExists. */ + @Provides + @Named("checkNotExists") + static EppRequestMessage provideCheckNotExistsRequestMessage( + @Named("domainNotExists") EppResponseMessage domainNotExistsResponse, + @Named("check") String checkTemplate) { + return new EppRequestMessage( + domainNotExistsResponse, + checkTemplate, + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + @Provides + @Named("success") + static EppResponseMessage provideSuccessResponse() { + return new EppResponseMessage( + "success", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XPASS_EXPRESSION)); + } + + @Provides + @Named("failure") + static EppResponseMessage provideFailureResponse() { + return new EppResponseMessage( + "failure", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XFAIL_EXPRESSION)); + } + + @Provides + @Named("domainExists") + static EppResponseMessage provideDomainExistsResponse() { + return new EppResponseMessage( + "domainExists", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), + String.format("//domainns:name[@avail='false'][.='%s']", domain), + EppMessage.XPASS_EXPRESSION)); + } + + @Provides + @Named("domainNotExists") + static EppResponseMessage provideDomainNotExistsResponse() { + return new EppResponseMessage( + "domainNotExists", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), + String.format("//domainns:name[@avail='true'][.='%s']", domain), + EppMessage.XPASS_EXPRESSION)); + } + + @Provides + @Named("greeting") + static EppResponseMessage provideGreetingResponse() { + return new EppResponseMessage( + "greeting", (clTrid, domain) -> ImmutableList.of("//eppns:greeting")); + } + + /** {@link Provides} filename of template for login EPP request. */ + @Provides + @Named("login") + static String provideLoginTemplate() { + return "login.xml"; + } + + /** {@link Provides} filename of template for create EPP request. */ + @Provides + @Named("create") + static String provideCreateTemplate() { + return "create.xml"; + } + + /** {@link Provides} filename of template for delete EPP request. */ + @Provides + @Named("delete") + static String provideDeleteTemplate() { + return "delete.xml"; + } + + /** {@link Provides} filename of template for logout EPP request. */ + @Provides + @Named("logout") + static String provideLogoutTemplate() { + return "logout.xml"; + } + + /** {@link Provides} filename of template for check EPP request. */ + @Provides + @Named("check") + static String provideCheckTemplate() { + return "check.xml"; + } + + /** {@link Provides} {@link Protocol} that represents an EPP connection. */ + @Singleton + @Provides + @EppProtocol + static Protocol provideEppProtocol( + @EppProtocol int eppPort, + @EppProtocol ImmutableList> handlerProviders) { + return Protocol.builder() + .setName(EPP_PROTOCOL_NAME) + .setPort(eppPort) + .setHandlerProviders(handlerProviders) + .setPersistentConnection(true) + .build(); + } + + /** + * {@link Provides} the list of providers of {@link ChannelHandler}s that are used for the EPP + * Protocol. + */ + @Provides + @EppProtocol + static ImmutableList> provideEppHandlerProviders( + @EppProtocol Provider> sslClientInitializerProvider, + Provider lengthFieldBasedFrameDecoderProvider, + Provider lengthFieldPrependerProvider, + Provider eppMessageHandlerProvider, + Provider eppActionHandlerProvider) { + return ImmutableList.of( + sslClientInitializerProvider, + lengthFieldBasedFrameDecoderProvider, + lengthFieldPrependerProvider, + eppMessageHandlerProvider, + eppActionHandlerProvider); + } + + @Provides + static LengthFieldBasedFrameDecoder provideLengthFieldBasedFrameDecoder() { + return new LengthFieldBasedFrameDecoder( + maximumMessageLengthBytes, + 0, + eppHeaderLengthBytes, + -eppHeaderLengthBytes, + eppHeaderLengthBytes); + } + + @Provides + static LengthFieldPrepender provideLengthFieldPrepender() { + return new LengthFieldPrepender(eppHeaderLengthBytes, true); + } + + /** {@link Provides} the {@link SslClientInitializer} used for the {@link EppProtocol}. */ + @Provides + @EppProtocol + static SslClientInitializer provideSslClientInitializer( + SslProvider sslProvider, + @LocalSecrets Supplier privateKeySupplier, + @LocalSecrets Supplier certificatesSupplier) { + + return new SslClientInitializer<>(sslProvider, privateKeySupplier, certificatesSupplier); + } + + @Provides + @Named("eppUserId") + static String provideEppUserId() { + return readResourceUtf8(EppModule.class, "secrets/user_id.txt"); + } + + @Provides + @Named("eppPassword") + static String provideEppPassphrase() { + return readResourceUtf8(EppModule.class, "secrets/password.txt"); + } + + @Provides + @Named("eppHost") + static String provideEppHost() { + return readResourceUtf8(EppModule.class, "secrets/epp_host.txt"); + } + + @Provides + @Named("eppTld") + static String provideTld() { + return "oa-0.test"; + } + + @Provides + @EppProtocol + static int provideEppPort() { + return EPP_PORT; + } + + /** Dagger qualifier to provide EPP protocol related handlers and other bindings. */ + @Qualifier + public @interface EppProtocol {} +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/WebWhoisModule.java b/prober/src/main/java/google/registry/monitoring/blackbox/modules/WebWhoisModule.java similarity index 77% rename from prober/src/main/java/google/registry/monitoring/blackbox/WebWhoisModule.java rename to prober/src/main/java/google/registry/monitoring/blackbox/modules/WebWhoisModule.java index 1b9a7eb64..67d5abcb7 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/WebWhoisModule.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/modules/WebWhoisModule.java @@ -12,12 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.monitoring.blackbox; +package google.registry.monitoring.blackbox.modules; 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.handlers.SslClientInitializer; import google.registry.monitoring.blackbox.handlers.WebWhoisActionHandler; import google.registry.monitoring.blackbox.handlers.WebWhoisMessageHandler; @@ -38,7 +41,8 @@ import javax.inject.Singleton; import org.joda.time.Duration; /** - * A module that provides the {@link Protocol}s to send HTTP(S) web WHOIS requests. + * A module that provides the components necessary for and the overall {@link ProbingSequence} to + * probe WebWHOIS. */ @Module public class WebWhoisModule { @@ -48,14 +52,10 @@ public class WebWhoisModule { 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. - */ + /** 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. - */ + /** {@link Provides} only step used in WebWhois sequence. */ @Provides @WebWhoisProtocol static ProbingStep provideWebWhoisStep( @@ -72,9 +72,7 @@ public class WebWhoisModule { .build(); } - /** - * {@link Provides} the {@link Protocol} that corresponds to http connection. - */ + /** {@link Provides} the {@link Protocol} that corresponds to http connection. */ @Singleton @Provides @HttpWhoisProtocol @@ -89,9 +87,7 @@ public class WebWhoisModule { .build(); } - /** - * {@link Provides} the {@link Protocol} that corresponds to https connection. - */ + /** {@link Provides} the {@link Protocol} that corresponds to https connection. */ @Singleton @Provides @HttpsWhoisProtocol @@ -123,7 +119,6 @@ public class WebWhoisModule { messageHandlerProvider, webWhoisActionHandlerProvider); } - /** * {@link Provides} the list of providers of {@link ChannelHandler}s that are used for https * protocol. @@ -155,9 +150,7 @@ public class WebWhoisModule { return new HttpObjectAggregator(maxContentLength); } - /** - * {@link Provides} the {@link SslClientInitializer} used for the {@link HttpsWhoisProtocol}. - */ + /** {@link Provides} the {@link SslClientInitializer} used for the {@link HttpsWhoisProtocol}. */ @Provides @HttpsWhoisProtocol static SslClientInitializer provideSslClientInitializer( @@ -165,33 +158,23 @@ public class WebWhoisModule { return new SslClientInitializer<>(sslProvider); } - /** - * {@link Provides} the {@link Bootstrap} used by the WebWhois sequence. - */ + /** {@link Provides} the {@link Bootstrap} used by the WebWhois sequence. */ @Singleton @Provides @WebWhoisProtocol static Bootstrap provideBootstrap( - EventLoopGroup eventLoopGroup, - Class channelClazz) { - return new Bootstrap() - .group(eventLoopGroup) - .channel(channelClazz); + EventLoopGroup eventLoopGroup, Class channelClazz) { + return new Bootstrap().group(eventLoopGroup).channel(channelClazz); } - /** - * {@link Provides} standard WebWhois sequence. - */ + /** {@link Provides} standard WebWhois sequence. */ @Provides @Singleton @IntoSet ProbingSequence provideWebWhoisSequence( - @WebWhoisProtocol ProbingStep probingStep, - WebWhoisToken webWhoisToken) { + @WebWhoisProtocol ProbingStep probingStep, WebWhoisToken webWhoisToken) { - return new ProbingSequence.Builder(webWhoisToken) - .add(probingStep) - .build(); + return new ProbingSequence.Builder(webWhoisToken).add(probingStep).build(); } @Provides @@ -200,16 +183,12 @@ public class WebWhoisModule { return maximumMessageLengthBytes; } - /** - * {@link Provides} the list of top level domains to be probed - */ + /** {@link Provides} the list of top level domains to be probed */ @Singleton @Provides @WebWhoisProtocol CircularList provideTopLevelDomains() { - return new CircularList.Builder() - .add("how", "soy", "xn--q9jyb4c") - .build(); + return new CircularList.Builder().add("how", "soy", "xn--q9jyb4c").build(); } @Provides @@ -224,28 +203,15 @@ public class WebWhoisModule { return HTTPS_WHOIS_PORT; } - /** - * Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. - */ + /** Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. */ @Qualifier - public @interface HttpWhoisProtocol { + public @interface HttpWhoisProtocol {} - } - - /** - * Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings. - */ + /** Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings. */ @Qualifier - public @interface HttpsWhoisProtocol { + public @interface HttpsWhoisProtocol {} - } - - /** - * Dagger qualifier to provide any WebWhois related bindings. - */ + /** Dagger qualifier to provide any WebWhois related bindings. */ @Qualifier - public @interface WebWhoisProtocol { - - } + public @interface WebWhoisProtocol {} } - diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/tokens/EppToken.java b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/EppToken.java new file mode 100644 index 000000000..20f9ebf2a --- /dev/null +++ b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/EppToken.java @@ -0,0 +1,138 @@ +// 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.tokens; + +import com.google.common.annotations.VisibleForTesting; +import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; +import google.registry.monitoring.blackbox.messages.EppRequestMessage; +import google.registry.monitoring.blackbox.messages.OutboundMessageType; +import io.netty.channel.Channel; +import java.util.concurrent.atomic.AtomicInteger; +import javax.inject.Inject; +import javax.inject.Named; + +/** {@link Token} subtype that deals performs specified actions for the EPP sequence. */ +public abstract class EppToken extends Token { + + /** Describes the maximum possible length of generated domain name. */ + private static final int MAX_DOMAIN_PART_LENGTH = 50; + + /** Suffix added for differentiation between two TRID in case they have the same timestamp. */ + private static AtomicInteger clientIdSuffix = new AtomicInteger(); + + protected final String tld; + private String host; + private String currentDomainName; + + /** + * Always the constructor used to provide any {@link EppToken}, with {@code tld} and {@code host} + * specified by Dagger. + */ + protected EppToken(String tld, String host) { + this.tld = tld; + this.host = host; + currentDomainName = newDomainName(getNewTRID()); + } + + /** Constructor used when passing on same {@link Channel} to next {@link Token}. */ + protected EppToken(String tld, String host, Channel channel) { + this(tld, host); + setChannel(channel); + } + + /** Modifies the message to reflect the new domain name and TRID */ + @Override + public OutboundMessageType modifyMessage(OutboundMessageType originalMessage) + throws UndeterminedStateException { + return ((EppRequestMessage) originalMessage).modifyMessage(getNewTRID(), currentDomainName); + } + + @Override + public String host() { + return host; + } + + @VisibleForTesting + String getCurrentDomainName() { + return currentDomainName; + } + + /** + * Return a unique string usable as an EPP client transaction ID. + * + *

Warning: The prober cleanup servlet relies on the timestamp being in the third + * position when splitting on dashes. Do not change this format without updating that code as + * well. + */ + private String getNewTRID() { + return String.format( + "prober-%s-%d-%d", + "localhost", System.currentTimeMillis(), clientIdSuffix.incrementAndGet()); + } + + /** Return a fully qualified domain label to use, derived from the client transaction ID. */ + private String newDomainName(String clTrid) { + String sld; + // not sure if the local hostname will stick to RFC validity rules + if (clTrid.length() > MAX_DOMAIN_PART_LENGTH) { + sld = clTrid.substring(clTrid.length() - MAX_DOMAIN_PART_LENGTH); + } else { + sld = clTrid; + } + // insert top level domain here + return String.format("%s.%s", sld, tld); + } + + /** + * {@link EppToken} Subclass that represents a token used in a transient sequence, meaning the + * connection is remade on each new iteration of the {@link + * google.registry.monitoring.blackbox.ProbingSequence}. + */ + public static class Transient extends EppToken { + + @Inject + public Transient(@Named("eppTld") String tld, @Named("eppHost") String host) { + super(tld, host); + } + + @Override + public Token next() { + return new Transient(tld, host()); + } + } + + /** + * {@link EppToken} Subclass that represents a token used in a persistent sequence, meaning the + * connection is maintained on each new iteration of the {@link + * google.registry.monitoring.blackbox.ProbingSequence}. + */ + public static class Persistent extends EppToken { + + @Inject + public Persistent(@Named("eppTld") String tld, @Named("eppHost") String host) { + super(tld, host); + } + + /** Constructor used on call to {@code next} to preserve channel. */ + private Persistent(String tld, String host, Channel channel) { + super(tld, host, channel); + } + + @Override + public Token next() { + return new Persistent(tld, host(), channel()); + } + } +} diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/tokens/Token.java b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/Token.java index b10c45dae..d00f09930 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/tokens/Token.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/Token.java @@ -24,12 +24,12 @@ import io.netty.channel.Channel; * Superclass that represents information passed to each {@link ProbingStep} in a single loop of a * {@link ProbingSequence}. * - *

Modifies the message passed in to reflect information relevant to a single loop - * in a {@link ProbingSequence}. Additionally, passes on channel that remains unchanged within a - * loop of the sequence.

+ *

Modifies the message passed in to reflect information relevant to a single loop in a {@link + * ProbingSequence}. Additionally, passes on channel that remains unchanged within a loop of the + * sequence. * - *

Also obtains the next {@link Token} corresponding to the next iteration of a loop - * in the sequence.

+ *

Also obtains the next {@link Token} corresponding to the next iteration of a loop in the + * sequence. */ public abstract class Token { @@ -40,32 +40,22 @@ public abstract class Token { */ protected Channel channel; - /** - * Obtains next {@link Token} for next loop in sequence. - */ + /** Obtains next {@link Token} for next loop in sequence. */ public abstract Token next(); - /** - * String corresponding to host that is relevant for loop in sequence. - */ + /** String corresponding to host that is relevant for loop in sequence. */ public abstract String host(); - /** - * Modifies the {@link OutboundMessageType} in the manner necessary for each loop - */ + /** Modifies the {@link OutboundMessageType} in the manner necessary for each loop */ public abstract OutboundMessageType modifyMessage(OutboundMessageType messageType) throws UndeterminedStateException; - /** - * Set method for {@code channel} - */ + /** Set method for {@code channel} */ public void setChannel(Channel channel) { this.channel = channel; } - /** - * Get method for {@code channel}. - */ + /** Get method for {@code channel}. */ public Channel channel() { return this.channel; } diff --git a/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java index 91160f5c6..fadcf3937 100644 --- a/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java +++ b/prober/src/main/java/google/registry/monitoring/blackbox/tokens/WebWhoisToken.java @@ -15,29 +15,25 @@ package google.registry.monitoring.blackbox.tokens; import com.google.common.collect.ImmutableList; -import google.registry.monitoring.blackbox.WebWhoisModule.WebWhoisProtocol; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.messages.OutboundMessageType; +import google.registry.monitoring.blackbox.modules.WebWhoisModule.WebWhoisProtocol; import google.registry.util.CircularList; import javax.inject.Inject; /** * {@link Token} subtype designed for WebWhois sequence. * - *

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}.

+ *

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". - */ + /** For each top level domain (tld), we probe "prefix.tld". */ private static final String PREFIX = "whois.nic."; - /** - * {@link ImmutableList} of all top level domains to be probed. - */ + /** {@link ImmutableList} of all top level domains to be probed. */ private CircularList topLevelDomainsList; @Inject @@ -46,18 +42,14 @@ public class WebWhoisToken extends Token { this.topLevelDomainsList = topLevelDomainsList; } - /** - * Moves on to next top level domain in {@code topLevelDomainsList}. - */ + /** Moves on to next top level domain in {@code topLevelDomainsList}. */ @Override public WebWhoisToken next() { topLevelDomainsList = topLevelDomainsList.next(); return this; } - /** - * Modifies message to reflect the new host coming from the new top level domain. - */ + /** Modifies message to reflect the new host coming from the new top level domain. */ @Override public OutboundMessageType modifyMessage(OutboundMessageType original) throws UndeterminedStateException { @@ -73,4 +65,3 @@ public class WebWhoisToken extends Token { return PREFIX + topLevelDomainsList.get(); } } - diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/check.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/check.xml new file mode 100644 index 000000000..a0dfdcf3f --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/check.xml @@ -0,0 +1,11 @@ + + + + + + @@DOMAINNAME@@ + + + @@CLTRID@@ + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/claimscheck.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/claimscheck.xml new file mode 100644 index 000000000..d5792b8c1 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/claimscheck.xml @@ -0,0 +1,17 @@ + + + + + + @@DOMAINNAME@@ + + + + + claims + + + @@CLTRID@@ + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/create.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/create.xml new file mode 100644 index 000000000..111344025 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/create.xml @@ -0,0 +1,21 @@ + + + + + + @@DOMAINNAME@@ + 1 + + ns.fake-domain.tld + + google-mon + google-mon + google-mon + + insecure + + + + @@CLTRID@@ + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/delete.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/delete.xml new file mode 100644 index 000000000..8c8d1cc9e --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/delete.xml @@ -0,0 +1,11 @@ + + + + + + @@DOMAINNAME@@ + + + @@CLTRID@@ + + \ No newline at end of file diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/domain_exists.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/domain_exists.xml new file mode 100644 index 000000000..439f3b182 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/domain_exists.xml @@ -0,0 +1,19 @@ + + + + + Generic Message + + + + + @@DOMAINNAME@@ + + + + + @@CLTRID@@ + @@SVTRID@@ + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/domain_not_exists.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/domain_not_exists.xml new file mode 100644 index 000000000..b91b5af50 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/domain_not_exists.xml @@ -0,0 +1,19 @@ + + + + + Generic Message + + + + + @@DOMAINNAME@@ + + + + + @@CLTRID@@ + @@SVTRID@@ + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/failed_response.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/failed_response.xml new file mode 100644 index 000000000..c93c203c9 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/failed_response.xml @@ -0,0 +1,12 @@ + + + + + Throwaway Message + + + @@CLTRID@@ + @@SVTRID@@ + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/greeting.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/greeting.xml new file mode 100644 index 000000000..775de6d00 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/greeting.xml @@ -0,0 +1,21 @@ + + + Test EPP Server + 2000-06-08T22:00:00.0Z + + 1.0 + en + urn:ietf:params:xml:ns:contact-1.0 + urn:ietf:params:xml:ns:domain-1.0 + urn:ietf:params:xml:ns:host-1.0 + + + + + + + + + + + \ No newline at end of file diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/login.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/login.xml new file mode 100644 index 000000000..b4c72cd61 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/login.xml @@ -0,0 +1,22 @@ + + + + + @@CLID@@ + @@PWD@@ + + 1.0 + en + + + urn:ietf:params:xml:ns:domain-1.0 + urn:ietf:params:xml:ns:contact-1.0 + urn:ietf:params:xml:ns:host-1.0 + + urn:ietf:params:xml:ns:launch-1.0 + + + + @@CLTRID@@ + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/logout.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/logout.xml new file mode 100644 index 000000000..dc49dca14 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/logout.xml @@ -0,0 +1,7 @@ + + + + + ABC-12345 + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/success_response.xml b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/success_response.xml new file mode 100644 index 000000000..f0357ccf1 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/success_response.xml @@ -0,0 +1,12 @@ + + + + + Throwaway Message + + + @@CLTRID@@ + @@SVTRID@@ + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/contact.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/contact.xsd new file mode 100644 index 000000000..de07e13d5 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/contact.xsd @@ -0,0 +1,389 @@ + + + + + + + + + + + Extensible Provisioning Protocol v1.0 + contact provisioning schema. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/domain.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/domain.xsd new file mode 100644 index 000000000..fb72f3341 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/domain.xsd @@ -0,0 +1,434 @@ + + + + + + + + + + + + Extensible Provisioning Protocol v1.0 + domain provisioning schema. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/dsig.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/dsig.xsd new file mode 100644 index 000000000..5680a4868 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/dsig.xsd @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/epp.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/epp.xsd new file mode 100644 index 000000000..6dad49765 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/epp.xsd @@ -0,0 +1,445 @@ + + + + + + + + + + Extensible Provisioning Protocol v1.0 schema. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/eppcom.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/eppcom.xsd new file mode 100644 index 000000000..0eb3ec18b --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/eppcom.xsd @@ -0,0 +1,107 @@ + + + + + + + Extensible Provisioning Protocol v1.0 + shared structures schema. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/host.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/host.xsd new file mode 100644 index 000000000..62a7784bb --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/host.xsd @@ -0,0 +1,242 @@ + + + + + + + + + + + Extensible Provisioning Protocol v1.0 + host provisioning schema. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/launch.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/launch.xsd new file mode 100644 index 000000000..2b70c1fe3 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/launch.xsd @@ -0,0 +1,246 @@ + + + + + + + + + + + + + Extensible Provisioning Protocol v1.0 + domain name extension + schema + for the launch phase processing. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/mark.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/mark.xsd new file mode 100644 index 000000000..cdbbb09da --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/mark.xsd @@ -0,0 +1,246 @@ + + + + + + + Schema for representing a Trademark, also referred to + as Mark. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/rgp.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/rgp.xsd new file mode 100644 index 000000000..1e95d2166 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/rgp.xsd @@ -0,0 +1,133 @@ + + + + + + + Extensible Provisioning Protocol v1.0 + domain name extension schema for registry grace period + processing. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/smd.xsd b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/smd.xsd new file mode 100644 index 000000000..076e254f6 --- /dev/null +++ b/prober/src/main/resources/google/registry/monitoring/blackbox/messages/xsd/smd.xsd @@ -0,0 +1,71 @@ + + + + + + + Schema for representing a Signed Trademark. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java index 97ea630f9..eebfd8467 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingSequenceTest.java @@ -22,6 +22,8 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import google.registry.monitoring.blackbox.connection.ProbingAction; +import google.registry.monitoring.blackbox.connection.Protocol; import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.exceptions.UnrecoverableStateException; @@ -38,22 +40,20 @@ import org.mockito.Mockito; /** * Unit Tests on {@link ProbingSequence} * - *

First tests the construction of sequences and ensures the ordering is exactly how - * we expect it to be.

+ *

First tests the construction of sequences and ensures the ordering is exactly how we expect it + * to be. * - *

Then tests the execution of each step, by ensuring the methods treatment of any kind - * of response from the {@link ProbingStep}s or {@link ProbingAction}s is what is expected.

+ *

Then tests the execution of each step, by ensuring the methods treatment of any kind of + * response from the {@link ProbingStep}s or {@link ProbingAction}s is what is expected. * - *

On every test that runs the sequence, in order for the sequence to stop, we throw an - * {@link UnrecoverableStateException}, using mocks of the steps or actions, as the sequences - * are run using the main thread (with {@link EmbeddedChannel}).

+ *

On every test that runs the sequence, in order for the sequence to stop, we throw an {@link + * UnrecoverableStateException}, using mocks of the steps or actions, as the sequences are run using + * the main thread (with {@link EmbeddedChannel}). */ @RunWith(JUnit4.class) public class ProbingSequenceTest { - /** - * Default mock {@link ProbingAction} returned when generating an action with a mockStep. - */ + /** Default mock {@link ProbingAction} returned when generating an action with a mockStep. */ private ProbingAction mockAction = Mockito.mock(ProbingAction.class); /** @@ -62,10 +62,7 @@ public class ProbingSequenceTest { */ private ProbingStep mockStep = Mockito.mock(ProbingStep.class); - - /** - * Default mock {@link Token} that is passed into each {@link ProbingSequence} tested. - */ + /** Default mock {@link Token} that is passed into each {@link ProbingSequence} tested. */ private Token mockToken = Mockito.mock(Token.class); /** @@ -104,11 +101,12 @@ public class ProbingSequenceTest { ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingStep thirdStep = Mockito.mock(ProbingStep.class); - ProbingSequence sequence = new ProbingSequence.Builder(mockToken) - .add(firstStep) - .add(secondStep) - .add(thirdStep) - .build(); + ProbingSequence sequence = + new ProbingSequence.Builder(mockToken) + .add(firstStep) + .add(secondStep) + .add(thirdStep) + .build(); assertThat(sequence.get()).isEqualTo(firstStep); sequence = sequence.next(); @@ -128,12 +126,13 @@ public class ProbingSequenceTest { ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingStep thirdStep = Mockito.mock(ProbingStep.class); - ProbingSequence sequence = new ProbingSequence.Builder(mockToken) - .add(thirdStep) - .add(secondStep) - .markFirstRepeated() - .add(firstStep) - .build(); + ProbingSequence sequence = + new ProbingSequence.Builder(mockToken) + .add(thirdStep) + .add(secondStep) + .markFirstRepeated() + .add(firstStep) + .build(); assertThat(sequence.get()).isEqualTo(thirdStep); sequence = sequence.next(); @@ -145,47 +144,43 @@ public class ProbingSequenceTest { sequence = sequence.next(); assertThat(sequence.get()).isEqualTo(secondStep); - } @Test public void testRunStep_Success() throws UndeterminedStateException { - //Always returns a succeeded future on call to mockAction. + // Always returns a succeeded future on call to mockAction. doReturn(channel.newSucceededFuture()).when(mockAction).call(); - // Has mockStep always return mockAction on call to generateAction + // Has mockStep always return mockAction on call to generateAction. doReturn(mockAction).when(mockStep).generateAction(any(Token.class)); - //Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on. + // Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on. ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingAction secondAction = Mockito.mock(ProbingAction.class); - doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))).when(secondAction) + doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))) + .when(secondAction) .call(); doReturn(secondAction).when(secondStep).generateAction(mockToken); - //Build testable sequence from mocked components. - ProbingSequence sequence = new ProbingSequence.Builder(mockToken) - .add(mockStep) - .add(secondStep) - .build(); + // Build testable sequence from mocked components. + ProbingSequence sequence = + new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build(); sequence.start(); // We expect to have only generated actions from mockStep once, and we expect to have called // this generated action only once, as when we move on to secondStep, it terminates the // sequence. - verify(mockStep).generateAction(any(Token.class)); verify(mockStep).generateAction(mockToken); verify(mockAction).call(); // Similarly, we expect to generate actions and call the action from the secondStep once, as // after calling it, the sequence should be terminated - verify(secondStep).generateAction(any(Token.class)); verify(secondStep).generateAction(mockToken); verify(secondAction).call(); - //We should have modified the token's channel after the first, succeeded step. + // We should have modified the token's channel after the first, succeeded step. assertThat(mockToken.channel()).isEqualTo(channel); } @@ -201,7 +196,7 @@ public class ProbingSequenceTest { ProbingStep secondStep = Mockito.mock(ProbingStep.class); ProbingAction secondAction = Mockito.mock(ProbingAction.class); - // Necessary for success of ProbingSequence runStep method as it calls get().protocol() + // Necessary for success of ProbingSequence runStep method as it calls get().protocol(). doReturn(mockProtocol).when(secondStep).protocol(); // We ensure that secondStep has necessary attributes to be successful step to pass on to @@ -220,18 +215,15 @@ public class ProbingSequenceTest { doReturn(channel.newFailedFuture(new UnrecoverableStateException(""))).when(thirdAction).call(); doReturn(thirdAction).when(mockStep).generateAction(secondToken); - //Build testable sequence from mocked components. - ProbingSequence sequence = new ProbingSequence.Builder(mockToken) - .add(mockStep) - .add(secondStep) - .build(); + // Build testable sequence from mocked components. + ProbingSequence sequence = + new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build(); sequence.start(); // We expect to have generated actions from mockStep twice (once for mockToken and once for // secondToken), and we expectto have called each generated action only once, as when we move // on to mockStep the second time, it will terminate the sequence after calling thirdAction. - verify(mockStep, times(2)).generateAction(any(Token.class)); verify(mockStep).generateAction(mockToken); verify(mockStep).generateAction(secondToken); verify(mockAction).call(); @@ -239,23 +231,22 @@ public class ProbingSequenceTest { // Similarly, we expect to generate actions and call the action from the secondStep once, as // after calling it, we move on to mockStep again, which terminates the sequence. - verify(secondStep).generateAction(any(Token.class)); verify(secondStep).generateAction(mockToken); verify(secondAction).call(); - //We should have modified the token's channel after the first, succeeded step. + // We should have modified the token's channel after the first, succeeded step. assertThat(mockToken.channel()).isEqualTo(channel); } /** - * Test for when we expect Failure within try catch block of generating and calling a - * {@link ProbingAction}. + * Test for when we expect Failure within try catch block of generating and calling a {@link + * ProbingAction}. * - * @throws UndeterminedStateException - necessary for having mock return anything on a call to - * {@code generateAction}. + * @throws UndeterminedStateException - necessary for having mock return anything on a call to + * {@code generateAction}. */ private void testActionFailure() throws UndeterminedStateException { - //Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on. + // Dummy step that server purpose of placeholder to test ability of ProbingSequence to move on. ProbingStep secondStep = Mockito.mock(ProbingStep.class); // We create a second token that when used to generate an action throws an @@ -264,18 +255,15 @@ public class ProbingSequenceTest { doReturn(secondToken).when(mockToken).next(); doThrow(new UnrecoverableStateException("")).when(mockStep).generateAction(secondToken); - //Build testable sequence from mocked components. - ProbingSequence sequence = new ProbingSequence.Builder(mockToken) - .add(mockStep) - .add(secondStep) - .build(); + // Build testable sequence from mocked components. + ProbingSequence sequence = + new ProbingSequence.Builder(mockToken).add(mockStep).add(secondStep).build(); sequence.start(); // We expect that we have generated actions twice. First, when we actually test generateAction // with an actual call using mockToken, and second when we throw an // UnrecoverableStateException with secondToken. - verify(mockStep, times(2)).generateAction(any(Token.class)); verify(mockStep).generateAction(mockToken); verify(mockStep).generateAction(secondToken); @@ -296,17 +284,14 @@ public class ProbingSequenceTest { // Returns mock action on call to generate action for ProbingStep. doReturn(mockAction).when(mockStep).generateAction(mockToken); - //Tests generic behavior we expect when we fail in generating or calling an action. + // Tests generic behavior we expect when we fail in generating or calling an action. testActionFailure(); // We only expect to have called this action once, as we only get it from one generateAction // call. verify(mockAction).call(); - - } - @Test public void testRunStep_FailureGenerating() throws UndeterminedStateException { // Create a mock first step that returns the dummy action when called to generate an action. diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java index 132f19f8b..a05e9d543 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/ProbingStepTest.java @@ -15,12 +15,14 @@ package google.registry.monitoring.blackbox; import static com.google.common.truth.Truth.assertThat; -import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY; +import static google.registry.monitoring.blackbox.connection.ProbingAction.CONNECTION_FUTURE_KEY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import com.google.common.collect.ImmutableList; +import google.registry.monitoring.blackbox.connection.ProbingAction; +import google.registry.monitoring.blackbox.connection.Protocol; import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; import google.registry.monitoring.blackbox.handlers.ActionHandler; import google.registry.monitoring.blackbox.handlers.ConversionHandler; @@ -48,31 +50,26 @@ import org.mockito.Mockito; */ public class ProbingStepTest { - /** - * Basic Constants necessary for tests - */ + /** Basic Constants necessary for tests */ private static final String ADDRESS_NAME = "TEST_ADDRESS"; + private static final String PROTOCOL_NAME = "TEST_PROTOCOL"; private static final int PROTOCOL_PORT = 0; private static final String TEST_MESSAGE = "TEST_MESSAGE"; private static final String SECONDARY_TEST_MESSAGE = "SECONDARY_TEST_MESSAGE"; private static final LocalAddress address = new LocalAddress(ADDRESS_NAME); private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1); - private final Bootstrap bootstrap = new Bootstrap() - .group(eventLoopGroup) - .channel(LocalChannel.class); - /** - * Used for testing how well probing step can create connection to blackbox server - */ - @Rule - public NettyRule nettyRule = new NettyRule(eventLoopGroup); - + private final Bootstrap bootstrap = + new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class); + /** Used for testing how well probing step can create connection to blackbox server */ + @Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup); /** * The two main handlers we need in any test pipeline used that connects to {@link NettyRule's * server} - **/ + */ private ActionHandler testHandler = new TestActionHandler(); + private ChannelHandler conversionHandler = new ConversionHandler(); /** @@ -92,12 +89,13 @@ public class ProbingStepTest { @Test public void testProbingActionGenerate_embeddedChannel() throws UndeterminedStateException { // Sets up Protocol to represent existing channel connection. - Protocol testProtocol = Protocol.builder() - .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) - .setName(PROTOCOL_NAME) - .setPort(PROTOCOL_PORT) - .setPersistentConnection(true) - .build(); + Protocol testProtocol = + Protocol.builder() + .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) + .setName(PROTOCOL_NAME) + .setPort(PROTOCOL_PORT) + .setPersistentConnection(true) + .build(); // Sets up an embedded channel to contain the two handlers we created already. EmbeddedChannel channel = new EmbeddedChannel(conversionHandler, testHandler); @@ -109,12 +107,13 @@ public class ProbingStepTest { doReturn(channel).when(testToken).channel(); // Sets up generic {@link ProbingStep} that we are testing. - ProbingStep testStep = ProbingStep.builder() - .setMessageTemplate(new TestMessage(TEST_MESSAGE)) - .setBootstrap(bootstrap) - .setDuration(Duration.ZERO) - .setProtocol(testProtocol) - .build(); + ProbingStep testStep = + ProbingStep.builder() + .setMessageTemplate(new TestMessage(TEST_MESSAGE)) + .setBootstrap(bootstrap) + .setDuration(Duration.ZERO) + .setProtocol(testProtocol) + .build(); ProbingAction testAction = testStep.generateAction(testToken); @@ -123,27 +122,27 @@ public class ProbingStepTest { assertThat(testAction.outboundMessage().toString()).isEqualTo(SECONDARY_TEST_MESSAGE); assertThat(testAction.host()).isEqualTo(SECONDARY_TEST_MESSAGE); assertThat(testAction.protocol()).isEqualTo(testProtocol); - - } @Test public void testProbingActionGenerate_newChannel() throws UndeterminedStateException { // Sets up Protocol for when we create a new channel. - Protocol testProtocol = Protocol.builder() - .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) - .setName(PROTOCOL_NAME) - .setPort(PROTOCOL_PORT) - .setPersistentConnection(false) - .build(); + Protocol testProtocol = + Protocol.builder() + .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) + .setName(PROTOCOL_NAME) + .setPort(PROTOCOL_PORT) + .setPersistentConnection(false) + .build(); // Sets up generic ProbingStep that we are testing. - ProbingStep testStep = ProbingStep.builder() - .setMessageTemplate(new TestMessage(TEST_MESSAGE)) - .setBootstrap(bootstrap) - .setDuration(Duration.ZERO) - .setProtocol(testProtocol) - .build(); + ProbingStep testStep = + ProbingStep.builder() + .setMessageTemplate(new TestMessage(TEST_MESSAGE)) + .setBootstrap(bootstrap) + .setDuration(Duration.ZERO) + .setProtocol(testProtocol) + .build(); // Sets up testToken to return arbitrary values, and no channel. Used when we create a new // channel. @@ -162,7 +161,5 @@ public class ProbingStepTest { assertThat(testAction.outboundMessage().toString()).isEqualTo(ADDRESS_NAME); assertThat(testAction.host()).isEqualTo(ADDRESS_NAME); assertThat(testAction.protocol()).isEqualTo(testProtocol); - - } } diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingActionTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/connection/ProbingActionTest.java similarity index 65% rename from prober/src/test/java/google/registry/monitoring/blackbox/ProbingActionTest.java rename to prober/src/test/java/google/registry/monitoring/blackbox/connection/ProbingActionTest.java index df3296438..0135dfb55 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/ProbingActionTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/connection/ProbingActionTest.java @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.monitoring.blackbox; +package google.registry.monitoring.blackbox.connection; import static com.google.common.truth.Truth.assertThat; -import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY; +import static google.registry.monitoring.blackbox.connection.ProbingAction.CONNECTION_FUTURE_KEY; import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.UTF_8; @@ -46,7 +46,7 @@ import org.junit.runners.JUnit4; * Unit tests for {@link ProbingAction} subtypes * *

Attempts to test how well each {@link ProbingAction} works with an {@link ActionHandler} - * subtype when receiving to all possible types of responses

+ * subtype when receiving to all possible types of responses */ @RunWith(JUnit4.class) public class ProbingActionTest { @@ -58,53 +58,55 @@ public class ProbingActionTest { private static final int TEST_PORT = 0; private static final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1); - /** - * Used for testing how well probing step can create connection to blackbox server - */ - @Rule - public NettyRule nettyRule = new NettyRule(eventLoopGroup); + + /** Used for testing how well probing step can create connection to blackbox server */ + @Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup); + /** * We use custom Test {@link ActionHandler} and {@link ConversionHandler} so test depends only on * {@link ProbingAction} */ private ActionHandler testHandler = new TestActionHandler(); + private ChannelHandler conversionHandler = new ConversionHandler(); - //TODO - Currently, this test fails to receive outbound messages from the embedded channel, which + // TODO - Currently, this test fails to receive outbound messages from the embedded channel, which // we will fix in a later release. @Ignore @Test public void testSuccess_existingChannel() { - //setup + // setup EmbeddedChannel channel = new EmbeddedChannel(conversionHandler, testHandler); channel.attr(CONNECTION_FUTURE_KEY).set(channel.newSucceededFuture()); // Sets up a Protocol corresponding to when a connection exists. - Protocol protocol = Protocol.builder() - .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) - .setName(PROTOCOL_NAME) - .setPort(TEST_PORT) - .setPersistentConnection(true) - .build(); + Protocol protocol = + Protocol.builder() + .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) + .setName(PROTOCOL_NAME) + .setPort(TEST_PORT) + .setPersistentConnection(true) + .build(); // Sets up a ProbingAction that creates a channel using test specified attributes. - ProbingAction action = ProbingAction.builder() - .setChannel(channel) - .setProtocol(protocol) - .setDelay(Duration.ZERO) - .setOutboundMessage(new TestMessage(TEST_MESSAGE)) - .setHost("") - .build(); + ProbingAction action = + ProbingAction.builder() + .setChannel(channel) + .setProtocol(protocol) + .setDelay(Duration.ZERO) + .setOutboundMessage(new TestMessage(TEST_MESSAGE)) + .setHost("") + .build(); - //tests main function of ProbingAction + // tests main function of ProbingAction ChannelFuture future = action.call(); - //Obtains the outboundMessage passed through pipeline after delay + // Obtains the outboundMessage passed through pipeline after delay Object msg = null; while (msg == null) { msg = channel.readOutbound(); } - //tests the passed message is exactly what we expect + // tests the passed message is exactly what we expect assertThat(msg).isInstanceOf(ByteBuf.class); String request = ((ByteBuf) msg).toString(UTF_8); assertThat(request).isEqualTo(TEST_MESSAGE); @@ -112,7 +114,8 @@ public class ProbingActionTest { // Ensures that we haven't marked future as done until response is received. assertThat(future.isDone()).isFalse(); - //after writing inbound, we should have a success + // After writing inbound, we should have a success as we use an EmbeddedChannel, ensuring all + // operations happen on the main thread - i.e. synchronously. channel.writeInbound(Unpooled.wrappedBuffer(SECONDARY_TEST_MESSAGE.getBytes(US_ASCII))); assertThat(future.isSuccess()).isTrue(); @@ -121,42 +124,41 @@ public class ProbingActionTest { @Test public void testSuccess_newChannel() throws Exception { - //setup + // setup LocalAddress address = new LocalAddress(ADDRESS_NAME); - Bootstrap bootstrap = new Bootstrap() - .group(eventLoopGroup) - .channel(LocalChannel.class); + Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class); // Sets up a Protocol corresponding to when a new connection is created. - Protocol protocol = Protocol.builder() - .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) - .setName(PROTOCOL_NAME) - .setPort(TEST_PORT) - .setPersistentConnection(false) - .build(); + Protocol protocol = + Protocol.builder() + .setHandlerProviders(ImmutableList.of(() -> conversionHandler, () -> testHandler)) + .setName(PROTOCOL_NAME) + .setPort(TEST_PORT) + .setPersistentConnection(false) + .build(); nettyRule.setUpServer(address); // Sets up a ProbingAction with existing channel using test specified attributes. - ProbingAction action = ProbingAction.builder() - .setBootstrap(bootstrap) - .setProtocol(protocol) - .setDelay(Duration.ZERO) - .setOutboundMessage(new TestMessage(TEST_MESSAGE)) - .setHost(ADDRESS_NAME) - .build(); + ProbingAction action = + ProbingAction.builder() + .setBootstrap(bootstrap) + .setProtocol(protocol) + .setDelay(Duration.ZERO) + .setOutboundMessage(new TestMessage(TEST_MESSAGE)) + .setHost(ADDRESS_NAME) + .build(); - //tests main function of ProbingAction + // tests main function of ProbingAction ChannelFuture future = action.call(); - //Tests to see if message is properly sent to remote server + // Tests to see if message is properly sent to remote server nettyRule.assertReceivedMessage(TEST_MESSAGE); future = future.syncUninterruptibly(); - //Tests to see that, since server responds, we have set future to true + // Tests to see that, since server responds, we have set future to true assertThat(future.isSuccess()).isTrue(); assertThat(((TestActionHandler) testHandler).getResponse().toString()).isEqualTo(TEST_MESSAGE); } } - diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/ConversionHandler.java b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/ConversionHandler.java index 987abbfd7..561dd0b38 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/ConversionHandler.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/ConversionHandler.java @@ -31,24 +31,20 @@ import io.netty.channel.ChannelPromise; * {@link ChannelHandler} used in tests to convert {@link OutboundMessageType} to to {@link * ByteBuf}s and convert {@link ByteBuf}s to {@link InboundMessageType} * - *

Specific type of {@link OutboundMessageType} and {@link InboundMessageType} - * used for conversion is the {@link TestMessage} type.

+ *

Specific type of {@link OutboundMessageType} and {@link InboundMessageType} used for + * conversion is the {@link TestMessage} type. */ public class ConversionHandler extends ChannelDuplexHandler { - /** - * Handles inbound conversion - */ + /** Handles inbound conversion */ @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf buf = (ByteBuf) msg; ctx.fireChannelRead(new TestMessage(buf.toString(UTF_8))); buf.release(); } - /** - * Handles outbound conversion - */ + /** Handles outbound conversion */ @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { @@ -57,4 +53,3 @@ public class ConversionHandler extends ChannelDuplexHandler { super.write(ctx, buf, promise); } } - diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/EppActionHandlerTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/EppActionHandlerTest.java new file mode 100644 index 000000000..7aa1fa206 --- /dev/null +++ b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/EppActionHandlerTest.java @@ -0,0 +1,240 @@ +// 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.handlers; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.JUnitBackports.assertThrows; + +import google.registry.monitoring.blackbox.exceptions.EppClientException; +import google.registry.monitoring.blackbox.exceptions.FailureException; +import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; +import google.registry.monitoring.blackbox.messages.EppRequestMessage; +import google.registry.monitoring.blackbox.messages.EppResponseMessage; +import google.registry.monitoring.blackbox.util.EppUtils; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.embedded.EmbeddedChannel; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Unit tests for {@link EppActionHandler} and {@link EppMessageHandler} as well as integration + * tests for both of them. + * + *

Attempts to test how well {@link EppActionHandler} works when responding to all possible types + * of {@link EppResponseMessage}s with corresponding {@link EppRequestMessage} sent down channel + * pipeline. + */ +@RunWith(Parameterized.class) +public class EppActionHandlerTest { + + private static final String USER_ID = "TEST_ID"; + private static final String USER_PASSWORD = "TEST_PASSWORD"; + private static final String USER_CLIENT_TRID = "prober-localhost-1234567891011-0"; + private static final String FAILURE_TRID = "TEST_FAILURE_TRID"; + private static final String DOMAIN_NAME = "TEST_DOMAIN_NAME.test"; + private static final String SERVER_ID = "TEST_SERVER_ID"; + + @Parameter(0) + public EppRequestMessage message; + + private EmbeddedChannel channel; + private EppActionHandler actionHandler = new EppActionHandler(); + private EppMessageHandler messageHandler = new EppMessageHandler(EppUtils.getGreetingResponse()); + + // We test all relevant EPP actions + @Parameters(name = "{0}") + public static EppRequestMessage[] data() { + return new EppRequestMessage[] { + EppUtils.getHelloMessage(EppUtils.getGreetingResponse()), + EppUtils.getLoginMessage(EppUtils.getSuccessResponse(), USER_ID, USER_PASSWORD), + EppUtils.getCreateMessage(EppUtils.getSuccessResponse()), + EppUtils.getCreateMessage(EppUtils.getFailureResponse()), + EppUtils.getDeleteMessage(EppUtils.getSuccessResponse()), + EppUtils.getDeleteMessage(EppUtils.getFailureResponse()), + EppUtils.getLogoutMessage(EppUtils.getSuccessResponse()), + EppUtils.getCheckMessage(EppUtils.getDomainExistsResponse()), + EppUtils.getCheckMessage(EppUtils.getDomainNotExistsResponse()) + }; + } + + /** Setup main three handlers to be used in pipeline. */ + @Before + public void setup() throws EppClientException { + message.modifyMessage(USER_CLIENT_TRID, DOMAIN_NAME); + } + + private void setupEmbeddedChannel(ChannelHandler... handlers) { + channel = new EmbeddedChannel(handlers); + } + + private Document getResponse(EppResponseMessage response, boolean fail, String clTrid) + throws IOException, EppClientException { + if (response.name().equals("greeting")) { + if (fail) { + return EppUtils.getBasicResponse(true, clTrid, SERVER_ID); + } else { + return EppUtils.getGreeting(); + } + } else if (response.name().equals("domainExists")) { + return EppUtils.getDomainCheck(!fail, clTrid, SERVER_ID, DOMAIN_NAME); + + } else if (response.name().equals("domainNotExists")) { + return EppUtils.getDomainCheck(fail, clTrid, SERVER_ID, DOMAIN_NAME); + + } else if (response.name().equals("success")) { + return EppUtils.getBasicResponse(!fail, clTrid, SERVER_ID); + + } else { + return EppUtils.getBasicResponse(fail, clTrid, SERVER_ID); + } + } + + @Test + public void testBasicAction_Success_Embedded() + throws SAXException, IOException, EppClientException, FailureException { + // We simply use an embedded channel in this instance + setupEmbeddedChannel(actionHandler); + + ChannelFuture future = actionHandler.getFinishedFuture(); + + EppResponseMessage response = message.getExpectedResponse(); + + response.getDocument( + EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, USER_CLIENT_TRID))); + + channel.writeInbound(response); + + ChannelFuture unusedFuture = future.syncUninterruptibly(); + + assertThat(future.isSuccess()).isTrue(); + } + + @Test + public void testBasicAction_FailCode_Embedded() + throws SAXException, IOException, EppClientException, FailureException { + // We simply use an embedded channel in this instance + setupEmbeddedChannel(actionHandler); + + ChannelFuture future = actionHandler.getFinishedFuture(); + + EppResponseMessage response = message.getExpectedResponse(); + + response.getDocument( + EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), true, USER_CLIENT_TRID))); + + channel.writeInbound(response); + + assertThrows( + FailureException.class, + () -> { + ChannelFuture unusedFuture = future.syncUninterruptibly(); + }); + } + + @Test + public void testBasicAction_FailTRID_Embedded() + throws SAXException, IOException, EppClientException, FailureException { + // We simply use an embedded channel in this instance + setupEmbeddedChannel(actionHandler); + + ChannelFuture future = actionHandler.getFinishedFuture(); + + EppResponseMessage response = message.getExpectedResponse(); + + response.getDocument( + EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, FAILURE_TRID))); + + channel.writeInbound(response); + + if (message.getExpectedResponse().name().equals("greeting")) { + ChannelFuture unusedFuture = future.syncUninterruptibly(); + assertThat(future.isSuccess()).isTrue(); + } else { + assertThrows( + FailureException.class, + () -> { + ChannelFuture unusedFuture = future.syncUninterruptibly(); + }); + } + } + + @Test + public void testIntegratedAction_Success_Embedded() + throws IOException, SAXException, UndeterminedStateException { + // We simply use an embedded channel in this instance + setupEmbeddedChannel(messageHandler, actionHandler); + + ChannelFuture future = actionHandler.getFinishedFuture(); + channel.writeOutbound(message); + + channel.writeInbound( + EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, USER_CLIENT_TRID))); + + ChannelFuture unusedFuture = future.syncUninterruptibly(); + + assertThat(future.isSuccess()).isTrue(); + } + + @Test + public void testIntegratedAction_FailCode_Embedded() + throws IOException, SAXException, UndeterminedStateException { + // We simply use an embedded channel in this instance + setupEmbeddedChannel(messageHandler, actionHandler); + + ChannelFuture future = actionHandler.getFinishedFuture(); + channel.writeOutbound(message); + + channel.writeInbound( + EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), true, USER_CLIENT_TRID))); + + assertThrows( + FailureException.class, + () -> { + ChannelFuture unusedFuture = future.syncUninterruptibly(); + }); + } + + @Test + public void testIntegratedAction_FailTRID_Embedded() + throws IOException, SAXException, UndeterminedStateException { + // We simply use an embedded channel in this instance + setupEmbeddedChannel(messageHandler, actionHandler); + + ChannelFuture future = actionHandler.getFinishedFuture(); + channel.writeOutbound(message); + + channel.writeInbound( + EppUtils.docToByteBuf(getResponse(message.getExpectedResponse(), false, FAILURE_TRID))); + + if (message.getExpectedResponse().name().equals("greeting")) { + ChannelFuture unusedFuture = future.syncUninterruptibly(); + assertThat(future.isSuccess()).isTrue(); + } else { + assertThrows( + FailureException.class, + () -> { + ChannelFuture unusedFuture = future.syncUninterruptibly(); + }); + } + } +} diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/NettyRule.java b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/NettyRule.java index 4b6eed9c1..95d90d851 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/NettyRule.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/NettyRule.java @@ -16,8 +16,8 @@ package google.registry.monitoring.blackbox.handlers; import static com.google.common.base.Preconditions.checkState; import static com.google.common.truth.Truth.assertThat; -import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY; -import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; +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.testing.JUnitBackports.assertThrows; import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.UTF_8; @@ -25,9 +25,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.truth.ThrowableSubject; -import google.registry.monitoring.blackbox.ProbingActionTest; import google.registry.monitoring.blackbox.ProbingStepTest; -import google.registry.monitoring.blackbox.Protocol; +import google.registry.monitoring.blackbox.connection.ProbingActionTest; +import google.registry.monitoring.blackbox.connection.Protocol; import google.registry.monitoring.blackbox.testservers.TestServer; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; @@ -51,18 +51,18 @@ import org.junit.rules.ExternalResource; /** * Helper for setting up and testing client / server connection with netty. * - *

Code based on and almost identical to {@code NettyRule} in the proxy. - * Used in {@link SslClientInitializerTest}, {@link ProbingActionTest}, and {@link ProbingStepTest} - *

+ *

Code based on and almost identical to {@code NettyRule} in the proxy. Used in {@link + * SslClientInitializerTest}, {@link ProbingActionTest}, and {@link ProbingStepTest} */ public final class NettyRule extends ExternalResource { - private final EventLoopGroup eventLoopGroup; + // Handler attached to server's channel to record the request received. private EchoHandler echoHandler; // Handler attached to client's channel to record the response received. private DumpHandler dumpHandler; + private Channel channel; // All I/O operations are done inside the single thread within this event loop group, which is @@ -81,25 +81,20 @@ public final class NettyRule extends ExternalResource { channel.writeAndFlush(Unpooled.wrappedBuffer(data.getBytes(US_ASCII))); } - /** - * Sets up a server channel bound to the given local address. - */ + /** Sets up a server channel bound to the given local address. */ public void setUpServer(LocalAddress localAddress, ChannelHandler... handlers) { checkState(echoHandler == null, "Can't call setUpServer twice"); echoHandler = new EchoHandler(); - new TestServer(eventLoopGroup, localAddress, + new TestServer( + eventLoopGroup, + localAddress, ImmutableList.builder().add(handlers).add(echoHandler).build()); } - /** - * Sets up a client channel connecting to the give local address. - */ + /** Sets up a client channel connecting to the give local address. */ void setUpClient( - LocalAddress localAddress, - Protocol protocol, - String host, - ChannelHandler handler) { + LocalAddress localAddress, Protocol protocol, String host, ChannelHandler handler) { checkState(echoHandler != null, "Must call setUpServer before setUpClient"); checkState(dumpHandler == null, "Can't call setUpClient twice"); dumpHandler = new DumpHandler(); @@ -128,20 +123,17 @@ public final class NettyRule extends ExternalResource { checkState(channel != null, "Must call setUpClient to finish NettyRule setup"); } - /** - * Test that custom setup to send message to current server sends right message - */ + /** Test that custom setup to send message to current server sends right message */ public void assertReceivedMessage(String message) throws Exception { assertThat(echoHandler.getRequestFuture().get()).isEqualTo(message); - } /** * Test that a message can go through, both inbound and outbound. * - *

The client writes the message to the server, which echos it back and saves the string in - * its promise. The client receives the echo and saves it in its promise. All these activities - * happens in the I/O thread, and this call itself returns immediately. + *

The client writes the message to the server, which echos it back and saves the string in its + * promise. The client receives the echo and saves it in its promise. All these activities happens + * in the I/O thread, and this call itself returns immediately. */ void assertThatMessagesWork() throws Exception { checkReady(); @@ -199,9 +191,7 @@ public final class NettyRule extends ExternalResource { ctx.writeAndFlush(msg).addListener(f -> requestFuture.complete(request)); } - /** - * Saves any inbound error as the cause of the promise failure. - */ + /** Saves any inbound error as the cause of the promise failure. */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ChannelFuture unusedFuture = @@ -209,9 +199,7 @@ public final class NettyRule extends ExternalResource { } } - /** - * A handler that dumps its inbound message to a promise that can be inspected later. - */ + /** A handler that dumps its inbound message to a promise that can be inspected later. */ private static class DumpHandler extends ChannelInboundHandlerAdapter { private final CompletableFuture responseFuture = new CompletableFuture<>(); @@ -232,13 +220,10 @@ public final class NettyRule extends ExternalResource { responseFuture.complete(response); } - /** - * Saves any inbound error into the failure cause of the promise. - */ + /** Saves any inbound error into the failure cause of the promise. */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.channel().closeFuture().addListener(f -> responseFuture.completeExceptionally(cause)); } } } - diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslClientInitializerTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslClientInitializerTest.java index 2a9f9758e..45c6b6430 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslClientInitializerTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslClientInitializerTest.java @@ -15,14 +15,14 @@ package google.registry.monitoring.blackbox.handlers; import static com.google.common.truth.Truth.assertThat; -import static google.registry.monitoring.blackbox.ProbingAction.REMOTE_ADDRESS_KEY; -import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; +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.monitoring.blackbox.handlers.SslInitializerTestUtils.getKeyPair; import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.setUpSslChannel; import static google.registry.monitoring.blackbox.handlers.SslInitializerTestUtils.signKeyPair; import com.google.common.collect.ImmutableList; -import google.registry.monitoring.blackbox.Protocol; +import google.registry.monitoring.blackbox.connection.Protocol; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; import io.netty.channel.embedded.EmbeddedChannel; @@ -62,39 +62,33 @@ import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class SslClientInitializerTest { - /** - * Fake host to test if the SSL engine gets the correct peer host. - */ + /** Fake host to test if the SSL engine gets the correct peer host. */ private static final String SSL_HOST = "www.example.tld"; - /** - * Fake port to test if the SSL engine gets the correct peer port. - */ + /** Fake port to test if the SSL engine gets the correct peer port. */ private static final int SSL_PORT = 12345; - /** - * Fake protocol saved in channel attribute. - */ - private static final Protocol PROTOCOL = Protocol.builder() - .setName("ssl") - .setPort(SSL_PORT) - .setHandlerProviders(ImmutableList.of()) - .setPersistentConnection(false) - .build(); - @Rule - public NettyRule nettyRule = new NettyRule(); + /** Fake protocol saved in channel attribute. */ + private static final Protocol PROTOCOL = + Protocol.builder() + .setName("ssl") + .setPort(SSL_PORT) + .setHandlerProviders(ImmutableList.of()) + .setPersistentConnection(false) + .build(); + + @Rule public NettyRule nettyRule = new NettyRule(); + @Parameter(0) public SslProvider sslProvider; - /** - * Saves the SNI hostname received by the server, if sent by the client. - */ + /** Saves the SNI hostname received by the server, if sent by the client. */ private String sniHostReceived; // We do our best effort to test all available SSL providers. @Parameters(name = "{0}") public static SslProvider[] data() { return OpenSsl.isAvailable() - ? new SslProvider[]{SslProvider.JDK, SslProvider.OPENSSL} - : new SslProvider[]{SslProvider.JDK}; + ? new SslProvider[] {SslProvider.JDK, SslProvider.OPENSSL} + : new SslProvider[] {SslProvider.JDK}; } private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate certificate) @@ -170,7 +164,7 @@ public class SslClientInitializerTest { // Set up the client to trust the self signed cert used to sign the cert that server provides. SslClientInitializer sslClientInitializer = - new SslClientInitializer<>(sslProvider, new X509Certificate[]{ssc.cert()}); + new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()}); nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer); @@ -199,7 +193,7 @@ public class SslClientInitializerTest { // Set up the client to trust the self signed cert used to sign the cert that server provides. SslClientInitializer sslClientInitializer = - new SslClientInitializer<>(sslProvider, new X509Certificate[]{ssc.cert()}); + new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()}); nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer); @@ -211,4 +205,3 @@ public class SslClientInitializerTest { assertThat(nettyRule.getChannel().isActive()).isFalse(); } } - diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslInitializerTestUtils.java b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslInitializerTestUtils.java index 68b291914..6c27a5bba 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslInitializerTestUtils.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/SslInitializerTestUtils.java @@ -33,9 +33,7 @@ import javax.security.auth.x500.X500Principal; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.x509.X509V3CertificateGenerator; -/** - * Utility class that provides methods used by {@link SslClientInitializerTest} - */ +/** Utility class that provides methods used by {@link SslClientInitializerTest} */ public class SslInitializerTestUtils { static { @@ -74,10 +72,7 @@ public class SslInitializerTestUtils { * @param certs The certificate that the server should provide. * @return The SSL session in current channel, can be used for further validation. */ - static SSLSession setUpSslChannel( - Channel channel, - X509Certificate... certs) - throws Exception { + static SSLSession setUpSslChannel(Channel channel, X509Certificate... certs) throws Exception { SslHandler sslHandler = channel.pipeline().get(SslHandler.class); // Wait till the handshake is complete. sslHandler.handshakeFuture().get(); @@ -92,4 +87,3 @@ public class SslInitializerTestUtils { return sslHandler.engine().getSession(); } } - diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/TestActionHandler.java b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/TestActionHandler.java index 19e608c97..7ab0b5f95 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/TestActionHandler.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/TestActionHandler.java @@ -37,6 +37,4 @@ public class TestActionHandler extends ActionHandler { public InboundMessageType getResponse() { return receivedMessage; } - } - diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandlerTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandlerTest.java index a4c2c57dc..4cd8700c3 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandlerTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/handlers/WebWhoisActionHandlerTest.java @@ -15,14 +15,14 @@ package google.registry.monitoring.blackbox.handlers; import static com.google.common.truth.Truth.assertThat; -import static google.registry.monitoring.blackbox.ProbingAction.CONNECTION_FUTURE_KEY; -import static google.registry.monitoring.blackbox.Protocol.PROTOCOL_KEY; -import static google.registry.monitoring.blackbox.TestUtils.makeHttpGetRequest; -import static google.registry.monitoring.blackbox.TestUtils.makeHttpResponse; -import static google.registry.monitoring.blackbox.TestUtils.makeRedirectResponse; +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.Protocol; +import google.registry.monitoring.blackbox.connection.Protocol; import google.registry.monitoring.blackbox.exceptions.FailureException; import google.registry.monitoring.blackbox.messages.HttpRequestMessage; import google.registry.monitoring.blackbox.messages.HttpResponseMessage; @@ -45,8 +45,8 @@ import org.junit.runners.JUnit4; /** * Unit tests for {@link WebWhoisActionHandler}. * - *

Attempts to test how well {@link WebWhoisActionHandler} works - * when responding to all possible types of responses

+ *

Attempts to test how well {@link WebWhoisActionHandler} works when responding to all possible + * types of responses */ @RunWith(JUnit4.class) public class WebWhoisActionHandlerTest { @@ -55,14 +55,14 @@ public class WebWhoisActionHandlerTest { 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 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; @@ -70,10 +70,7 @@ public class WebWhoisActionHandlerTest { private Protocol initialProtocol; private HttpRequestMessage msg; - - /** - * Creates default protocol with empty list of handlers and specified other inputs - */ + /** 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) @@ -83,22 +80,14 @@ public class WebWhoisActionHandlerTest { .build(); } - /** - * Initializes new WebWhoisActionHandler - */ + /** Initializes new WebWhoisActionHandler */ private void setupActionHandler(Bootstrap bootstrap, HttpRequestMessage messageTemplate) { - actionHandler = new WebWhoisActionHandler( - bootstrap, - standardProtocol, - standardProtocol, - messageTemplate - ); + actionHandler = + new WebWhoisActionHandler(bootstrap, standardProtocol, standardProtocol, messageTemplate); actionHandlerProvider = () -> actionHandler; } - /** - * Sets up testing channel with requisite attributes - */ + /** Sets up testing channel with requisite attributes */ private void setupChannel(Protocol protocol) { channel = new EmbeddedChannel(actionHandler); channel.attr(PROTOCOL_KEY).set(protocol); @@ -106,43 +95,39 @@ public class WebWhoisActionHandlerTest { } private Bootstrap makeBootstrap(EventLoopGroup group) { - return new Bootstrap() - .group(group) - .channel(LocalChannel.class); + 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 public void testBasic_responseOk() { - //setup + // setup setup("", null, true); setupChannel(initialProtocol); - //stores future + // stores future ChannelFuture future = actionHandler.getFinishedFuture(); channel.writeOutbound(msg); FullHttpResponse response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.OK)); - //assesses that future listener isn't triggered yet. + // 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 + // assesses that we successfully received good response and protocol is unchanged assertThat(future.isSuccess()).isTrue(); } @Test public void testBasic_responseFailure_badRequest() { - //setup + // setup setup("", null, false); setupChannel(initialProtocol); @@ -150,8 +135,8 @@ public class WebWhoisActionHandlerTest { ChannelFuture future = actionHandler.getFinishedFuture(); channel.writeOutbound(msg); - FullHttpResponse response = new HttpResponseMessage( - makeHttpResponse(HttpResponseStatus.BAD_REQUEST)); + FullHttpResponse response = + new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST)); // Assesses that future listener isn't triggered yet. assertThat(future.isDone()).isFalse(); @@ -168,23 +153,24 @@ public class WebWhoisActionHandlerTest { @Test public void testBasic_responseFailure_badURL() { - //setup + // setup setup("", null, false); setupChannel(initialProtocol); - //stores future + // stores future ChannelFuture future = actionHandler.getFinishedFuture(); channel.writeOutbound(msg); - FullHttpResponse response = new HttpResponseMessage( - makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, DUMMY_URL, true)); + FullHttpResponse response = + new HttpResponseMessage( + makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, DUMMY_URL, true)); - //assesses that future listener isn't triggered yet. + // assesses that future listener isn't triggered yet. assertThat(future.isDone()).isFalse(); channel.writeInbound(response); - //assesses that listener is triggered, and event is success + // assesses that listener is triggered, and event is success assertThat(future.isDone()).isTrue(); assertThat(future.isSuccess()).isFalse(); @@ -204,7 +190,7 @@ public class WebWhoisActionHandlerTest { // Initializes LocalAddress with unique String. LocalAddress address = new LocalAddress(TARGET_HOST); - //stores future + // stores future ChannelFuture future = actionHandler.getFinishedFuture(); channel.writeOutbound(msg); @@ -215,18 +201,19 @@ public class WebWhoisActionHandlerTest { TestServer.webWhoisServer(group, address, "", TARGET_HOST, path); FullHttpResponse response = - new HttpResponseMessage(makeRedirectResponse(HttpResponseStatus.MOVED_PERMANENTLY, - HTTP_REDIRECT + TARGET_HOST + path, true)); + new HttpResponseMessage( + makeRedirectResponse( + HttpResponseStatus.MOVED_PERMANENTLY, HTTP_REDIRECT + TARGET_HOST + path, true)); - //checks that future has not been set to successful or a failure + // 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 + // 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 + // assesses that we successfully received good response and protocol is unchanged assertThat(future.syncUninterruptibly().isSuccess()).isTrue(); } } diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/messages/EppMessageTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/messages/EppMessageTest.java new file mode 100644 index 000000000..e359189e1 --- /dev/null +++ b/prober/src/test/java/google/registry/monitoring/blackbox/messages/EppMessageTest.java @@ -0,0 +1,223 @@ +// 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.messages; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.JUnitBackports.assertThrows; +import static java.nio.charset.StandardCharsets.UTF_8; + +import google.registry.monitoring.blackbox.exceptions.EppClientException; +import google.registry.monitoring.blackbox.exceptions.FailureException; +import google.registry.monitoring.blackbox.util.EppUtils; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +@RunWith(JUnit4.class) +public class EppMessageTest { + + private Document xmlDoc = null; + private Document greeting = null; + private DocumentBuilder builder = null; + + @Before + public void setUp() throws Exception { + String xmlString = + "" + + "text1" + + "text2" + + "" + + "text3" + + "" + + ""; + ByteArrayInputStream byteStream = new ByteArrayInputStream(xmlString.getBytes(UTF_8)); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + builder = factory.newDocumentBuilder(); + xmlDoc = builder.parse(byteStream); + + greeting = EppUtils.getGreeting(); + } + + @Test + public void xmlDocToStringSuccess() throws Exception { + Document xml = builder.newDocument(); + Element doc = xml.createElement("doc"); + Element title = xml.createElement("title"); + title.setTextContent("test"); + Element meta = xml.createElement("meta"); + meta.setAttribute("version", "1.0"); + doc.appendChild(title); + doc.appendChild(meta); + xml.appendChild(doc); + + // note that setting the version just ensures this will either be the same in the result, + // or the result won't support the version and this will throw an exception. + xml.setXmlVersion("1.0"); + // setting stand alone to true removes this from the processing instructions + xml.setXmlStandalone(true); + String expected = + "" + + "test"; + String actual = EppMessage.xmlDocToString(xml); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void xmlDoctoByteArraySuccess() throws Exception { + Document xml = builder.newDocument(); + Element doc = xml.createElement("doc"); + Element title = xml.createElement("title"); + title.setTextContent("test"); + Element meta = xml.createElement("meta"); + meta.setAttribute("version", "1.0"); + doc.appendChild(title); + doc.appendChild(meta); + xml.appendChild(doc); + + // note that setting the version just ensures this will either be the same in the result, + // or the result won't support the version and this will throw an exception. + xml.setXmlVersion("1.0"); + // setting stand alone to true removes this from the processing instructions + xml.setXmlStandalone(true); + String expected = + "" + + "test"; + byte[] actual = EppMessage.xmlDocToByteArray(xml); + assertThat(actual).isEqualTo(expected.getBytes(UTF_8)); + } + + @Test + public void eppValidateSuccess() throws Exception { + EppMessage.eppValidate(greeting); + } + + @Test + public void eppValidateFail() { + assertThrows(SAXException.class, () -> EppMessage.eppValidate(xmlDoc)); + } + + /** + * These test a "success" without validating a document. This is to make it easier to test the + * XPath part of the verify in isolation. See EppClientConnectionTest for tests that verify an + * actual (greeting) respoonse. + */ + @Test + public void verifyResponseSuccess() throws Exception { + ArrayList list = new ArrayList<>(); + list.add("epp"); + list.add("//textAndAttr[@myAttr1='1'] | //textAndAttr[child::text()='text1']"); + list.add("//textAndAttr[child::text()='text1']"); + list.add("//textAndAttr/@myAttr1"); + list.add("//textAndAttrSplitRepeated[@myAttr3='3']"); + + EppMessage.verifyEppResponse(xmlDoc, list, false); + } + + @Test + public void verifyEppResponseSuccess() throws Exception { + ArrayList list = new ArrayList<>(); + list.add("*"); + list.add("/eppns:epp"); + list.add("/eppns:epp/eppns:greeting"); + list.add("//eppns:greeting"); + list.add("//eppns:svID"); + + EppMessage.verifyEppResponse(greeting, list, false); + } + + @Test + public void verifyResponseMissingTextFail() { + ArrayList list = new ArrayList<>(); + list.add("epp"); + list.add("//textAndAttr[child::text()='text2']"); + + assertThrows(FailureException.class, () -> EppMessage.verifyEppResponse(xmlDoc, list, false)); + } + + @Test + public void verifyResponseMissingAttrFail() { + ArrayList list = new ArrayList<>(); + list.add("epp"); + list.add("//textAndAttr/@myAttr2"); + + assertThrows(FailureException.class, () -> EppMessage.verifyEppResponse(xmlDoc, list, false)); + } + + @Test + public void verifyResponseSplitTextAttrFail() { + ArrayList list = new ArrayList<>(); + list.add("epp"); + list.add("//textAndAttrSplitRepeated[@myAttr3='3'][child::text()='text3']"); + + assertThrows(FailureException.class, () -> EppMessage.verifyEppResponse(xmlDoc, list, false)); + } + + @Test + public void getEppDocFromTemplateTest() throws Exception { + Map replaceMap = new HashMap<>(); + replaceMap.put("//eppns:expectedClTRID", "ABC-1234-CBA"); + replaceMap.put("//domainns:name", "foo"); + replaceMap.put("//domainns:pw", "bar"); + Document epp = EppMessage.getEppDocFromTemplate("create.xml", replaceMap); + List noList = Collections.emptyList(); + EppMessage.verifyEppResponse(epp, noList, true); + } + + @Test + public void getEppDocFromTemplateTestFail() { + Map replaceMap = new HashMap<>(); + replaceMap.put("//eppns:create", "ABC-1234-CBA"); + replaceMap.put("//domainns:name", "foo"); + replaceMap.put("//domainns:pw", "bar"); + assertThrows( + EppClientException.class, () -> EppMessage.getEppDocFromTemplate("create.xml", replaceMap)); + } + + @Test + public void checkValidDomainName() { + String domainName = "good.tld"; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue(); + domainName = "good.tld."; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue(); + domainName = "g-o-o-d.tld."; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue(); + domainName = "good.cc.tld"; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue(); + domainName = "good.cc.tld."; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isTrue(); + domainName = "too-short"; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse(); + domainName = "too-short."; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse(); + // TODO(rgr): sync up how many dots is actually too many + domainName = "too.many.dots.tld."; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse(); + domainName = "too.many.dots.tld"; + assertThat(domainName.matches(EppMessage.VALID_SLD_LABEL_REGEX)).isFalse(); + } +} diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/testservers/TestServer.java b/prober/src/test/java/google/registry/monitoring/blackbox/testservers/TestServer.java index c39fe5543..aaa114f33 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/testservers/TestServer.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/testservers/TestServer.java @@ -14,17 +14,22 @@ package google.registry.monitoring.blackbox.testservers; -import static google.registry.monitoring.blackbox.TestUtils.makeHttpResponse; -import static google.registry.monitoring.blackbox.TestUtils.makeRedirectResponse; +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.exceptions.FailureException; +import google.registry.monitoring.blackbox.messages.EppMessage; import google.registry.monitoring.blackbox.messages.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; @@ -34,6 +39,7 @@ 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; +import org.w3c.dom.Document; /** * Mock Server Superclass whose subclasses implement specific behaviors we expect blackbox server to @@ -45,18 +51,21 @@ public class TestServer { this(new NioEventLoopGroup(1), localAddress, handlers); } - public TestServer(EventLoopGroup eventLoopGroup, LocalAddress localAddress, + public TestServer( + EventLoopGroup eventLoopGroup, + LocalAddress localAddress, ImmutableList handlers) { - //Creates ChannelInitializer with handlers specified - ChannelInitializer 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 + // Creates ChannelInitializer with handlers specified + ChannelInitializer 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() @@ -65,22 +74,27 @@ public class TestServer { .childHandler(serverInitializer); ChannelFuture unusedFuture = serverBootstrap.bind(localAddress).syncUninterruptibly(); - } - public static TestServer webWhoisServer(EventLoopGroup eventLoopGroup, - LocalAddress localAddress, String redirectInput, String destinationInput, + 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)) - ); + ImmutableList.of(new RedirectHandler(redirectInput, destinationInput, destinationPath))); } - /** - * Handler that will wither redirect client, give successful response, or give error messge - */ + 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 { @@ -90,9 +104,9 @@ public class TestServer { /** * @param redirectInput - Server will send back redirect to {@code destinationInput} when - * receiving a request with this host location + * 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 + * when receiving a request with this host location */ public RedirectHandler(String redirectInput, String destinationInput, String destinationPath) { this.redirectInput = redirectInput; @@ -108,8 +122,9 @@ public class TestServer { 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)); + 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)); @@ -117,7 +132,36 @@ public class TestServer { response = new HttpResponseMessage(makeHttpResponse(HttpResponseStatus.BAD_REQUEST)); } ChannelFuture unusedFuture = ctx.channel().writeAndFlush(response); + } + } + private static class EppHandler extends ChannelDuplexHandler { + + Document doc; + 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 { + doc = EppMessage.byteArrayToXmlDoc(messageBytes); + ChannelFuture unusedFuture = future.setSuccess(); + } catch (FailureException e) { + ChannelFuture unusedFuture = future.setFailure(e); + } } } } diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/tokens/EppTokenTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/tokens/EppTokenTest.java new file mode 100644 index 000000000..2005757d9 --- /dev/null +++ b/prober/src/test/java/google/registry/monitoring/blackbox/tokens/EppTokenTest.java @@ -0,0 +1,91 @@ +// 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.tokens; + +import static com.google.common.truth.Truth.assertThat; + +import google.registry.monitoring.blackbox.exceptions.UndeterminedStateException; +import google.registry.monitoring.blackbox.messages.EppRequestMessage; +import google.registry.monitoring.blackbox.util.EppUtils; +import io.netty.channel.Channel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mockito; + +/** Unit Tests for each {@link Token} subtype (just {@link WebWhoisToken} for now) */ +@RunWith(JUnit4.class) +public class EppTokenTest { + + private static String TEST_HOST = "host"; + private static String TEST_TLD = "tld"; + + private EppToken persistentEppToken = new EppToken.Persistent(TEST_TLD, TEST_HOST); + private EppToken transientEppToken = new EppToken.Transient(TEST_TLD, TEST_HOST); + + @Test + public void testMessageModificationSuccess_PersistentToken() throws UndeterminedStateException { + + EppRequestMessage originalMessage = EppUtils.getCreateMessage(EppUtils.getSuccessResponse()); + String domainName = persistentEppToken.getCurrentDomainName(); + String clTrid = domainName.substring(0, domainName.indexOf('.')); + + EppRequestMessage modifiedMessage = + (EppRequestMessage) persistentEppToken.modifyMessage(originalMessage); + + // ensure element values are what they should be + assertThat(modifiedMessage.getElementValue("//domainns:name")).isEqualTo(domainName); + assertThat(modifiedMessage.getElementValue("//eppns:clTRID")).isNotEqualTo(clTrid); + } + + @Test + public void testMessageModificationSuccess_TransientToken() throws UndeterminedStateException { + + EppRequestMessage originalMessage = EppUtils.getCreateMessage(EppUtils.getSuccessResponse()); + String domainName = transientEppToken.getCurrentDomainName(); + String clTrid = domainName.substring(0, domainName.indexOf('.')); + + EppRequestMessage modifiedMessage = + (EppRequestMessage) transientEppToken.modifyMessage(originalMessage); + + // ensure element values are what they should be + assertThat(modifiedMessage.getElementValue("//domainns:name")).isEqualTo(domainName); + assertThat(modifiedMessage.getElementValue("//eppns:clTRID")).isNotEqualTo(clTrid); + } + + @Test + public void testNext_persistentToken() { + String domainName = persistentEppToken.getCurrentDomainName(); + Channel mockChannel = Mockito.mock(Channel.class); + persistentEppToken.setChannel(mockChannel); + + EppToken nextToken = (EppToken) persistentEppToken.next(); + + assertThat(nextToken.getCurrentDomainName()).isNotEqualTo(domainName); + assertThat(nextToken.channel()).isEqualTo(mockChannel); + } + + @Test + public void testNext_transientToken() { + String domainName = transientEppToken.getCurrentDomainName(); + Channel mockChannel = Mockito.mock(Channel.class); + transientEppToken.setChannel(mockChannel); + + EppToken nextToken = (EppToken) transientEppToken.next(); + + assertThat(nextToken.getCurrentDomainName()).isNotEqualTo(domainName); + assertThat(nextToken.channel()).isNull(); + } +} diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java b/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java index 148558a2b..f861fc7ac 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/tokens/WebWhoisTokenTest.java @@ -23,9 +23,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Unit Tests for {@link WebWhoisToken} - */ +/** Unit Tests for {@link WebWhoisToken} */ @RunWith(JUnit4.class) public class WebWhoisTokenTest { @@ -35,19 +33,17 @@ public class WebWhoisTokenTest { private static final String SECOND_TLD = "second_test"; private static final String THIRD_TLD = "third_test"; private final CircularList testDomains = - new CircularList.Builder() - .add(FIRST_TLD, SECOND_TLD, THIRD_TLD) - .build(); + new CircularList.Builder().add(FIRST_TLD, SECOND_TLD, THIRD_TLD).build(); public Token webToken = new WebWhoisToken(testDomains); @Test public void testMessageModification() throws UndeterminedStateException { - //creates Request message with header + // 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 + // 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); } @@ -69,5 +65,4 @@ public class WebWhoisTokenTest { assertThat(webToken.host()).isEqualTo(PREFIX + FIRST_TLD); } - } diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/util/EppUtils.java b/prober/src/test/java/google/registry/monitoring/blackbox/util/EppUtils.java new file mode 100644 index 000000000..266dcc723 --- /dev/null +++ b/prober/src/test/java/google/registry/monitoring/blackbox/util/EppUtils.java @@ -0,0 +1,197 @@ +// 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 google.registry.monitoring.blackbox.messages.EppMessage.CLIENT_ID_KEY; +import static google.registry.monitoring.blackbox.messages.EppMessage.CLIENT_PASSWORD_KEY; +import static google.registry.monitoring.blackbox.messages.EppMessage.CLIENT_TRID_KEY; +import static google.registry.monitoring.blackbox.messages.EppMessage.DOMAIN_KEY; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import google.registry.monitoring.blackbox.exceptions.EppClientException; +import google.registry.monitoring.blackbox.messages.EppMessage; +import google.registry.monitoring.blackbox.messages.EppRequestMessage; +import google.registry.monitoring.blackbox.messages.EppResponseMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.io.IOException; +import org.w3c.dom.Document; + +/** Houses static utility functions for testing EPP components of Prober. */ +public class EppUtils { + + /** Return a simple default greeting as a {@link Document}. */ + public static Document getGreeting() throws IOException, EppClientException { + return EppMessage.getEppDocFromTemplate("greeting.xml", ImmutableMap.of()); + } + + /** + * Return a basic response as a {@link Document}. + * + * @param success specifies if response shows a success or failure + * @param clTrid the client transaction ID + * @param svTrid the server transaction ID + * @return the EPP basic response as a {@link Document}. + */ + public static Document getBasicResponse(boolean success, String clTrid, String svTrid) + throws IOException, EppClientException { + + String template = success ? "success_response.xml" : "failed_response.xml"; + + return EppMessage.getEppDocFromTemplate( + template, + ImmutableMap.of( + EppRequestMessage.CLIENT_TRID_KEY, clTrid, EppMessage.SERVER_TRID_KEY, svTrid)); + } + + /** + * Return a domain check response as a {@link Document}. + * + * @param exists specifies if response shows that domain name exists on server or doesn't + * @param clTrid the client transaction ID + * @param svTrid the server transaction ID + * @param domain the domain the check success is for + * @return the EPP check response as a {@link Document}. + */ + public static Document getDomainCheck(boolean exists, String clTrid, String svTrid, String domain) + throws IOException, EppClientException { + + String template = exists ? "domain_exists.xml" : "domain_not_exists.xml"; + + return EppMessage.getEppDocFromTemplate( + template, + ImmutableMap.of( + EppRequestMessage.CLIENT_TRID_KEY, clTrid, + EppMessage.SERVER_TRID_KEY, svTrid, + EppMessage.DOMAIN_KEY, domain)); + } + + /** Converts {@link Document} to {@link ByteBuf}. */ + public static ByteBuf docToByteBuf(Document message) throws EppClientException { + byte[] bytestream = EppMessage.xmlDocToByteArray(message); + + ByteBuf buf = Unpooled.buffer(bytestream.length); + + buf.writeBytes(bytestream); + + return buf; + } + + /** Returns standard hello request with supplied response. */ + public static EppRequestMessage getHelloMessage(EppResponseMessage greetingResponse) { + return new EppRequestMessage(greetingResponse, null, (a, b) -> ImmutableMap.of()); + } + + /** Returns standard login request with supplied userId, userPassword, and response. */ + public static EppRequestMessage getLoginMessage( + EppResponseMessage response, String userId, String userPassword) { + return new EppRequestMessage( + response, + "login.xml", + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + CLIENT_ID_KEY, userId, + CLIENT_PASSWORD_KEY, userPassword)); + } + + /** Returns standard create request with supplied response. */ + public static EppRequestMessage getCreateMessage(EppResponseMessage response) { + return new EppRequestMessage( + response, + "create.xml", + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** Returns standard delete request with supplied response. */ + public static EppRequestMessage getDeleteMessage(EppResponseMessage response) { + return new EppRequestMessage( + response, + "delete.xml", + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** Returns standard logout request with supplied response. */ + public static EppRequestMessage getLogoutMessage(EppResponseMessage successResponse) { + return new EppRequestMessage( + successResponse, + "logout.xml", + (clTrid, domain) -> ImmutableMap.of(CLIENT_TRID_KEY, clTrid)); + } + + /** Returns standard check request with supplied response. */ + public static EppRequestMessage getCheckMessage(EppResponseMessage response) { + return new EppRequestMessage( + response, + "check.xml", + (clTrid, domain) -> + ImmutableMap.of( + CLIENT_TRID_KEY, clTrid, + DOMAIN_KEY, domain)); + } + + /** Returns standard success response. */ + public static EppResponseMessage getSuccessResponse() { + return new EppResponseMessage( + "success", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XPASS_EXPRESSION)); + } + + /** Returns standard failure response. */ + public static EppResponseMessage getFailureResponse() { + return new EppResponseMessage( + "failure", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), EppMessage.XFAIL_EXPRESSION)); + } + + /** Returns standard domainExists response. */ + public static EppResponseMessage getDomainExistsResponse() { + return new EppResponseMessage( + "domainExists", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), + String.format("//domainns:name[@avail='false'][.='%s']", domain), + EppMessage.XPASS_EXPRESSION)); + } + + /** Returns standard domainNotExists response. */ + public static EppResponseMessage getDomainNotExistsResponse() { + return new EppResponseMessage( + "domainNotExists", + (clTrid, domain) -> + ImmutableList.of( + String.format("//eppns:clTRID[.='%s']", clTrid), + String.format("//domainns:name[@avail='true'][.='%s']", domain), + EppMessage.XPASS_EXPRESSION)); + } + + /** Returns standard greeting response. */ + public static EppResponseMessage getGreetingResponse() { + return new EppResponseMessage( + "greeting", (clTrid, domain) -> ImmutableList.of("//eppns:greeting")); + } +} diff --git a/prober/src/test/java/google/registry/monitoring/blackbox/TestUtils.java b/prober/src/test/java/google/registry/monitoring/blackbox/util/WebWhoisUtils.java similarity index 90% rename from prober/src/test/java/google/registry/monitoring/blackbox/TestUtils.java rename to prober/src/test/java/google/registry/monitoring/blackbox/util/WebWhoisUtils.java index 66e2928f4..02b0e1f88 100644 --- a/prober/src/test/java/google/registry/monitoring/blackbox/TestUtils.java +++ b/prober/src/test/java/google/registry/monitoring/blackbox/util/WebWhoisUtils.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.monitoring.blackbox; +package google.registry.monitoring.blackbox.util; import static java.nio.charset.StandardCharsets.US_ASCII; @@ -26,10 +26,8 @@ import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; -/** - * Utility class for various helper methods used in testing. - */ -public class TestUtils { +/** Houses static utility functions for testing WebWHOIS components of Prober. */ +public class WebWhoisUtils { public static FullHttpRequest makeHttpGetRequest(String host, String path) { FullHttpRequest request = @@ -51,9 +49,7 @@ public class TestUtils { return response; } - /** - * Creates HttpResponse given status, redirection location, and other necessary inputs - */ + /** Creates HttpResponse given status, redirection location, and other necessary inputs */ public static FullHttpResponse makeRedirectResponse( HttpResponseStatus status, String location, boolean keepAlive) { FullHttpResponse response = makeHttpResponse("", status); @@ -67,4 +63,3 @@ public class TestUtils { return response; } } - diff --git a/util/src/main/java/google/registry/util/CircularList.java b/util/src/main/java/google/registry/util/CircularList.java index 86f6229f4..729a5cd23 100644 --- a/util/src/main/java/google/registry/util/CircularList.java +++ b/util/src/main/java/google/registry/util/CircularList.java @@ -15,69 +15,51 @@ package google.registry.util; /** - * Class that stores value {@param }, and points in circle to other {@link CircularList} - * objects. + * Class that stores value {@param }, and points in circle to other {@link CircularList} objects. + * + *

In its construction, we create a sequence of {@link CircularList} objects, each storing an * + * instance of T. They each point to each other in a circular manner, such that we can perform * + * circular iteration on the elements. Once finished building, we return this first {@link * + * CircularList} object in the sequence. * * @param - Element type stored in the {@link CircularList} - * - *

In its construction, we create a sequence of {@link CircularList} objects, each storing an - * instance of T. They each point to each other in a circular manner, such that we can perform - * circular iteration on the elements. Once finished building, we return this first {@link - * CircularList} object in the sequence.

*/ public class CircularList { - /** - * T instance stored in current node of list. - */ + /** T instance stored in current node of list. */ private final T value; - /** - * Pointer to next node of list. - */ + /** Pointer to next node of list. */ private CircularList next; - /** - * Standard constructor for {@link CircularList} that initializes its stored {@code value}. - */ + /** Standard constructor for {@link CircularList} that initializes its stored {@code value}. */ protected CircularList(T value) { this.value = value; } - /** - * Standard get method to retrieve {@code value}. - */ + /** Standard get method to retrieve {@code value}. */ public T get() { return value; } - /** - * Standard method to obtain {@code next} node in list. - */ + /** Standard method to obtain {@code next} node in list. */ public CircularList next() { return next; } - /** - * Setter method only used in builder to point one node to the next. - */ + /** Setter method only used in builder to point one node to the next. */ public void setNext(CircularList next) { this.next = next; } - /** - * Default Builder to create a standard instance of a {@link CircularList}. - */ + /** Default Builder to create a standard instance of a {@link CircularList}. */ public static class Builder extends AbstractBuilder> { - /** - * Simply calls on constructor to {@link CircularList} to create new instance. - */ + /** Simply calls on constructor to {@link CircularList} to create new instance. */ @Override protected CircularList create(T value) { return new CircularList<>(value); } - } /** @@ -85,26 +67,20 @@ public class CircularList { * create the full list. * * @param - Matching element type of iterator - * - *

Supports adding in element at a time, adding an {@link Iterable} - * of elements, and adding an variable number of elemetns.

- * - *

Sets first element added to {@code first}, and when built, points last element to the - * {@code first} element.

+ *

Supports adding in element at a time, adding an {@link Iterable} of elements, and adding + * an variable number of elemetns. + *

Sets first element added to {@code first}, and when built, points last element to the + * {@code first} element. */ public abstract static class AbstractBuilder> { protected C first; protected C current; - /** - * Necessary to instantiate each {@code C} object from {@code value}. - */ + /** Necessary to instantiate each {@code C} object from {@code value}. */ protected abstract C create(T value); - /** - * Sets current {@code C} to element added and points previous {@code C} to this one. - */ + /** Sets current {@code C} to element added and points previous {@code C} to this one. */ public AbstractBuilder add(T value) { C c = create(value); if (current == null) { @@ -116,9 +92,7 @@ public class CircularList { return this; } - /** - * Simply calls {@code addElement}, for each element in {@code elements}. - */ + /** Simply calls {@code addElement}, for each element in {@code elements}. */ public AbstractBuilder add(T... values) { for (T element : values) { add(element); @@ -126,21 +100,16 @@ public class CircularList { return this; } - /** - * Simply calls {@code addElement}, for each element in {@code elements}. - */ + /** Simply calls {@code addElement}, for each element in {@code elements}. */ public AbstractBuilder add(Iterable values) { values.forEach(this::add); return this; } - /** - * Sets the next {@code C} of the list to be the first {@code C} in the list. - */ + /** Sets the next {@code C} of the list to be the first {@code C} in the list. */ public C build() { current.setNext(first); return first; } } - }