diff --git a/CHANGES.txt b/CHANGES.txt index c51c9b1d8f..190994165a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -18,6 +18,8 @@ * cqlsh: Fix handling of $$-escaped strings (CASSANDRA-12189) * Fix SSL JMX requiring truststore containing server cert (CASSANDRA-12109) Merged from 3.0: + * Respond with v1/v2 protocol header when responding to driver that attempts + to connect with too low of a protocol version (CASSANDRA-11464) * NullPointerExpception when reading/compacting table (CASSANDRA-11988) * Fix problem with undeleteable rows on upgrade to new sstable format (CASSANDRA-12144) Merged from 2.2: diff --git a/src/java/org/apache/cassandra/transport/Frame.java b/src/java/org/apache/cassandra/transport/Frame.java index f620bc5dac..d0d4aeead1 100644 --- a/src/java/org/apache/cassandra/transport/Frame.java +++ b/src/java/org/apache/cassandra/transport/Frame.java @@ -176,7 +176,8 @@ public class Frame int version = firstByte & PROTOCOL_VERSION_MASK; if (version < Server.MIN_SUPPORTED_VERSION || version > Server.CURRENT_VERSION) throw new ProtocolException(String.format("Invalid or unsupported protocol version (%d); the lowest supported version is %d and the greatest is %d", - version, Server.MIN_SUPPORTED_VERSION, Server.CURRENT_VERSION)); + version, Server.MIN_SUPPORTED_VERSION, Server.CURRENT_VERSION), + version); // Wait until we have the complete header if (readableBytes < Header.LENGTH) @@ -275,6 +276,8 @@ public class Frame header.writeByte(type.direction.addToVersion(frame.header.version)); header.writeByte(Header.Flag.serialize(frame.header.flags)); + // Continue to support writing pre-v3 headers so that we can give proper error messages to drivers that + // connect with the v1/v2 protocol. See CASSANDRA-11464. if (frame.header.version >= Server.VERSION_3) header.writeShort(frame.header.streamId); else diff --git a/src/java/org/apache/cassandra/transport/Message.java b/src/java/org/apache/cassandra/transport/Message.java index 01a0794e50..7bfa194c4e 100644 --- a/src/java/org/apache/cassandra/transport/Message.java +++ b/src/java/org/apache/cassandra/transport/Message.java @@ -150,6 +150,7 @@ public abstract class Message private int streamId; private Frame sourceFrame; private Map customPayload; + protected Integer forcedProtocolVersion = null; protected Message(Type type) { @@ -389,7 +390,12 @@ public abstract class Message throw e; } - results.add(Frame.create(message.type, message.getStreamId(), version, flags, body)); + // if the driver attempted to connect with a protocol version lower than the minimum supported + // version, respond with a protocol error message with the correct frame header for that version + int responseVersion = message.forcedProtocolVersion == null + ? version + : message.forcedProtocolVersion; + results.add(Frame.create(message.type, message.getStreamId(), responseVersion, flags, body)); } catch (Throwable e) { diff --git a/src/java/org/apache/cassandra/transport/ProtocolException.java b/src/java/org/apache/cassandra/transport/ProtocolException.java index 9af9dc0395..a589e9b507 100644 --- a/src/java/org/apache/cassandra/transport/ProtocolException.java +++ b/src/java/org/apache/cassandra/transport/ProtocolException.java @@ -25,13 +25,31 @@ import org.apache.cassandra.exceptions.TransportException; */ public class ProtocolException extends RuntimeException implements TransportException { + private final Integer attemptedLowProtocolVersion; + public ProtocolException(String msg) + { + this(msg, null); + } + + public ProtocolException(String msg, Integer attemptedLowProtocolVersion) { super(msg); + this.attemptedLowProtocolVersion = attemptedLowProtocolVersion; } public ExceptionCode code() { return ExceptionCode.PROTOCOL_ERROR; } + + /** + * If the ProtocolException is due to a connection being made with a protocol version that is lower + * than Server.MIN_SUPPORTED_VERSION, this will return that unsupported protocol version. Otherwise, + * null is returned. + */ + public Integer getAttemptedLowProtocolVersion() + { + return attemptedLowProtocolVersion; + } } diff --git a/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java b/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java index 021db5ae74..8f45d4d1de 100644 --- a/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/ErrorMessage.java @@ -331,7 +331,18 @@ public class ErrorMessage extends Message.Response } if (e instanceof TransportException) - return new ErrorMessage((TransportException)e, streamId); + { + ErrorMessage message = new ErrorMessage((TransportException) e, streamId); + if (e instanceof ProtocolException) + { + // if the driver attempted to connect with a protocol version lower than the minimum supported + // version, respond with a protocol error message with the correct frame header for that version + Integer attemptedLowProtocolVersion = ((ProtocolException) e).getAttemptedLowProtocolVersion(); + if (attemptedLowProtocolVersion != null) + message.forcedProtocolVersion = attemptedLowProtocolVersion; + } + return message; + } // Unexpected exception if (unexpectedExceptionHandler == null || !unexpectedExceptionHandler.apply(e))