Reduce log level when invalid messages received from peers (#274)

* Throw FramingException when snappy decompression fails. 
* Handle FramingException in DeFramer as a routine event representing a malformed message.
Adrian Sutton 6 years ago committed by GitHub
parent f1b1323ceb
commit c8549bdc9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 14
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/netty/DeFramer.java
  3. 25
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/framing/CompressionException.java
  4. 47
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/framing/Compressor.java
  5. 20
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/framing/Framer.java
  6. 5
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/framing/SnappyCompressor.java
  7. 4
      ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/rlpx/framing/FramerTest.java
  8. 4
      pantheon/src/main/java/tech/pegasys/pantheon/Pantheon.java

@ -16,6 +16,7 @@
- ECRec precompile should return empty instead of 32 zero bytes when the input is invalid (PR [#227](https://github.com/PegaSysEng/pantheon/pull/227)) - ECRec precompile should return empty instead of 32 zero bytes when the input is invalid (PR [#227](https://github.com/PegaSysEng/pantheon/pull/227))
- File name too long error while building from source ([#215](https://github.com/PegaSysEng/pantheon/issues/215) thanks to [@5chdn](https://github.com/5chdn) for reporting) (PR [#221](https://github.com/PegaSysEng/pantheon/pull/221)) - File name too long error while building from source ([#215](https://github.com/PegaSysEng/pantheon/issues/215) thanks to [@5chdn](https://github.com/5chdn) for reporting) (PR [#221](https://github.com/PegaSysEng/pantheon/pull/221))
- Loop syntax in `runPantheonPrivateNetwork.sh` (PR [#237](https://github.com/PegaSysEng/pantheon/pull/237) thanks to [@matt9ucci](https://github.com/matt9ucci)) - Loop syntax in `runPantheonPrivateNetwork.sh` (PR [#237](https://github.com/PegaSysEng/pantheon/pull/237) thanks to [@matt9ucci](https://github.com/matt9ucci))
- Fix `CompressionException: Snappy decompression failed` errors ([#251](https://github.com/PegaSysEng/pantheon/issues/251) thanks to [@5chdn](https://github.com/5chdn) for reporting) (PR [#274](https://github.com/PegaSysEng/pantheon/pull/274))
### Additions and Improvements ### Additions and Improvements
- Added `--ropsten` command line argument to make syncing to Ropsten easier ([#186](https://github.com/PegaSysEng/pantheon/issues/186)) (PR [#197](https://github.com/PegaSysEng/pantheon/pull/197) with thanks to [@jvirtanen](https://github.com/jvirtanen)) - Added `--ropsten` command line argument to make syncing to Ropsten easier ([#186](https://github.com/PegaSysEng/pantheon/issues/186)) (PR [#197](https://github.com/PegaSysEng/pantheon/pull/197) with thanks to [@jvirtanen](https://github.com/jvirtanen))

@ -16,6 +16,7 @@ import tech.pegasys.pantheon.ethereum.p2p.api.MessageData;
import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection;
import tech.pegasys.pantheon.ethereum.p2p.netty.exceptions.IncompatiblePeerException; import tech.pegasys.pantheon.ethereum.p2p.netty.exceptions.IncompatiblePeerException;
import tech.pegasys.pantheon.ethereum.p2p.rlpx.framing.Framer; import tech.pegasys.pantheon.ethereum.p2p.rlpx.framing.Framer;
import tech.pegasys.pantheon.ethereum.p2p.rlpx.framing.FramingException;
import tech.pegasys.pantheon.ethereum.p2p.wire.PeerInfo; import tech.pegasys.pantheon.ethereum.p2p.wire.PeerInfo;
import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol; import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol;
import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason;
@ -31,6 +32,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.timeout.IdleStateHandler;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -117,7 +119,17 @@ final class DeFramer extends ByteToMessageDecoder {
@Override @Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable throwable) public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable throwable)
throws Exception { throws Exception {
if (throwable instanceof IOException) { final Throwable cause =
throwable instanceof DecoderException && throwable.getCause() != null
? throwable.getCause()
: throwable;
if (cause instanceof FramingException) {
LOG.debug("Invalid incoming message", throwable);
if (connectFuture.isDone()) {
connectFuture.get().disconnect(DisconnectReason.BREACH_OF_PROTOCOL);
return;
}
} else if (cause instanceof IOException) {
// IO failures are routine when communicating with random peers across the network. // IO failures are routine when communicating with random peers across the network.
LOG.debug("IO error while processing incoming message", throwable); LOG.debug("IO error while processing incoming message", throwable);
} else { } else {

@ -1,25 +0,0 @@
/*
* Copyright 2018 ConsenSys AG.
*
* 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 tech.pegasys.pantheon.ethereum.p2p.rlpx.framing;
/** Thrown when an error occurs during compression and decompression of payloads. */
public class CompressionException extends RuntimeException {
public CompressionException(final String message) {
super(message);
}
public CompressionException(final String message, final Throwable cause) {
super(message, cause);
}
}

@ -1,47 +0,0 @@
/*
* Copyright 2018 ConsenSys AG.
*
* 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 tech.pegasys.pantheon.ethereum.p2p.rlpx.framing;
/** A strategy for compressing and decompressing devp2p subprotocol messages. */
public interface Compressor {
/**
* Compresses the provided payload.
*
* @param decompressed The original payload.
* @throws CompressionException Thrown if an error occurs during compression; expect to find the
* root cause inside.
* @return The compressed payload.
*/
byte[] compress(byte[] decompressed) throws CompressionException;
/**
* Decompresses the provided payload.
*
* @param compressed The compressed payload.
* @throws CompressionException Thrown if an error occurs during decompression; expect to find the
* root cause inside.
* @return The original payload.
*/
byte[] decompress(byte[] compressed) throws CompressionException;
/**
* Return the length when uncompressed
*
* @param compressed The compressed payload.
* @return The length of the payload when uncompressed.
* @throws CompressionException Thrown if the size cannot be calculated from the available data;
* expect to find the root cause inside;
*/
int uncompressedLength(byte[] compressed) throws CompressionException;
}

@ -34,6 +34,7 @@ import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.SICBlockCipher; import org.bouncycastle.crypto.modes.SICBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.crypto.params.ParametersWithIV;
import org.iq80.snappy.CorruptionException;
/** /**
* This component is responsible for reading and composing RLPx protocol frames, conformant to the * This component is responsible for reading and composing RLPx protocol frames, conformant to the
@ -68,7 +69,7 @@ public class Framer {
.extractArray(); .extractArray();
private final HandshakeSecrets secrets; private final HandshakeSecrets secrets;
private static final Compressor compressor = new SnappyCompressor(); private static final SnappyCompressor compressor = new SnappyCompressor();
private final StreamCipher encryptor; private final StreamCipher encryptor;
private final StreamCipher decryptor; private final StreamCipher decryptor;
private final BlockCipher macEncryptor; private final BlockCipher macEncryptor;
@ -254,14 +255,17 @@ public class Framer {
// Write message data to ByteBuf, decompressing as necessary // Write message data to ByteBuf, decompressing as necessary
final BytesValue data; final BytesValue data;
if (compressionEnabled) { if (compressionEnabled) {
// Decompress data before writing to ByteBuf
final byte[] compressedMessageData = Arrays.copyOfRange(frameData, 1, frameData.length - pad); final byte[] compressedMessageData = Arrays.copyOfRange(frameData, 1, frameData.length - pad);
// Check message length final int uncompressedLength = compressor.uncompressedLength(compressedMessageData);
Preconditions.checkState( if (uncompressedLength >= LENGTH_MAX_MESSAGE_FRAME) {
compressor.uncompressedLength(compressedMessageData) < LENGTH_MAX_MESSAGE_FRAME, throw error("Message size %s in excess of maximum length.", uncompressedLength);
"Message size in excess of maximum length."); }
final byte[] decompressedMessageData = compressor.decompress(compressedMessageData); try {
data = BytesValue.wrap(decompressedMessageData); final byte[] decompressedMessageData = compressor.decompress(compressedMessageData);
data = BytesValue.wrap(decompressedMessageData);
} catch (final CorruptionException e) {
throw new FramingException("Decompression failed", e);
}
} else { } else {
// Move data to a ByteBuf // Move data to a ByteBuf
final int messageLength = frameSize - LENGTH_MESSAGE_ID; final int messageLength = frameSize - LENGTH_MESSAGE_ID;

@ -21,21 +21,18 @@ import org.iq80.snappy.Snappy;
* *
* @see <a href="https://google.github.io/snappy/">Snappy algorithm</a> * @see <a href="https://google.github.io/snappy/">Snappy algorithm</a>
*/ */
public class SnappyCompressor implements Compressor { public class SnappyCompressor {
@Override
public byte[] compress(final byte[] uncompressed) { public byte[] compress(final byte[] uncompressed) {
checkNotNull(uncompressed, "input data must not be null"); checkNotNull(uncompressed, "input data must not be null");
return Snappy.compress(uncompressed); return Snappy.compress(uncompressed);
} }
@Override
public byte[] decompress(final byte[] compressed) { public byte[] decompress(final byte[] compressed) {
checkNotNull(compressed, "input data must not be null"); checkNotNull(compressed, "input data must not be null");
return Snappy.uncompress(compressed, 0, compressed.length); return Snappy.uncompress(compressed, 0, compressed.length);
} }
@Override
public int uncompressedLength(final byte[] compressed) { public int uncompressedLength(final byte[] compressed) {
checkNotNull(compressed, "input data must not be null"); checkNotNull(compressed, "input data must not be null");
return Snappy.getUncompressedLength(compressed, 0); return Snappy.getUncompressedLength(compressed, 0);

@ -86,9 +86,9 @@ public class FramerTest {
final Framer deframer = new Framer(deframeSecrets); final Framer deframer = new Framer(deframeSecrets);
deframer.enableCompression(); deframer.enableCompression();
assertThatExceptionOfType(IllegalStateException.class) assertThatExceptionOfType(FramingException.class)
.isThrownBy(() -> deframer.deframe(framedMessage)) .isThrownBy(() -> deframer.deframe(framedMessage))
.withMessageContaining("Message size in excess of maximum length."); .withMessageContaining("Message size 16777216 in excess of maximum length.");
} }
@Test @Test

@ -16,7 +16,7 @@ import static picocli.CommandLine.defaultExceptionHandler;
import tech.pegasys.pantheon.cli.PantheonCommand; import tech.pegasys.pantheon.cli.PantheonCommand;
import tech.pegasys.pantheon.cli.PantheonControllerBuilder; import tech.pegasys.pantheon.cli.PantheonControllerBuilder;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration.Builder; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.util.BlockImporter; import tech.pegasys.pantheon.util.BlockImporter;
import picocli.CommandLine.RunLast; import picocli.CommandLine.RunLast;
@ -32,7 +32,7 @@ public final class Pantheon {
new BlockImporter(), new BlockImporter(),
new RunnerBuilder(), new RunnerBuilder(),
new PantheonControllerBuilder(), new PantheonControllerBuilder(),
new Builder()); new SynchronizerConfiguration.Builder());
pantheonCommand.parse( pantheonCommand.parse(
new RunLast().andExit(SUCCESS_EXIT_CODE), new RunLast().andExit(SUCCESS_EXIT_CODE),

Loading…
Cancel
Save