Optimize SslHandler in an OpenSslEngine-friendly way

Motivation:

Previous fix for the OpenSslEngine compatibility issue (#2216 and
18b0e95659) was to feed SSL records one by
one to OpenSslEngine.unwrap().  It is not optimal because it will result
in more JNI calls.

Modifications:

- Do not feed SSL records one by one.
- Feed as many records as possible up to MAX_ENCRYPTED_PACKET_LENGTH
- Deduplicate MAX_ENCRYPTED_PACKET_LENGTH definitions

Result:

- No allocation of intemediary arrays
- Reduced number of calls to SSLEngine and thus its underlying JNI calls
- A tad bit increase in throughput, probably reverting the tiny drop
  caused by 18b0e95659
This commit is contained in:
Trustin Lee 2014-05-13 19:42:04 +09:00
parent cb4020d4be
commit 53988376db
3 changed files with 27 additions and 56 deletions

View File

@ -65,7 +65,9 @@ public final class OpenSslEngine extends SSLEngine {
private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024;
private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024;
private static final int MAX_ENCRYPTED_PACKET = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256;
// Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256)
static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256;
private static final AtomicIntegerFieldUpdater<OpenSslEngine> DESTROYED_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(OpenSslEngine.class, "destroyed");
@ -432,7 +434,7 @@ public final class OpenSslEngine extends SSLEngine {
}
// protect against protocol overflow attack vector
if (src.remaining() > MAX_ENCRYPTED_PACKET) {
if (src.remaining() > MAX_ENCRYPTED_PACKET_LENGTH) {
isInboundDone = true;
isOutboundDone = true;
engineClosed = true;
@ -689,7 +691,7 @@ public final class OpenSslEngine extends SSLEngine {
}
public int getPacketBufferSize() {
return MAX_ENCRYPTED_PACKET;
return MAX_ENCRYPTED_PACKET_LENGTH;
}
public int getApplicationBufferSize() {

View File

@ -38,8 +38,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class SslBufferPool {
// Add 1024 as a room for compressed data and another 1024 for Apache Harmony compatibility.
private static final int MAX_PACKET_SIZE = 16665 + 2048;
private static final int MAX_PACKET_SIZE_ALIGNED = (MAX_PACKET_SIZE / 128 + 1) * 128;
private static final int MAX_PACKET_SIZE_ALIGNED = (OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH / 128 + 1) * 128;
private static final int DEFAULT_POOL_SIZE = MAX_PACKET_SIZE_ALIGNED * 1024;
@ -156,7 +155,7 @@ public class SslBufferPool {
// Note that we can allocate more buffers than maxBufferCount.
// We will discard the buffers allocated after numAllocations reached maxBufferCount in releaseBuffer().
numAllocations.incrementAndGet();
buf = allocate(MAX_PACKET_SIZE);
buf = allocate(OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH);
}
}

View File

@ -182,8 +182,7 @@ import static org.jboss.netty.channel.Channels.*;
public class SslHandler extends FrameDecoder
implements ChannelDownstreamHandler {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(SslHandler.class);
private static final InternalLogger logger = InternalLoggerFactory.getInstance(SslHandler.class);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
@ -851,31 +850,25 @@ public class SslHandler extends FrameDecoder
protected Object decode(
final ChannelHandlerContext ctx, Channel channel, ChannelBuffer in) throws Exception {
// Keeps the list of the length of every SSL record in the input buffer.
int[] recordLengths = null;
int nRecords = 0;
final int startOffset = in.readerIndex();
final int endOffset = in.writerIndex();
int offset = startOffset;
int totalLength = 0;
// If we calculated the length of the current SSL record before, use that information.
if (packetLength > 0) {
if (endOffset - startOffset < packetLength) {
return null;
} else {
recordLengths = new int[4];
recordLengths[0] = packetLength;
nRecords = 1;
offset += packetLength;
totalLength = packetLength;
packetLength = 0;
}
}
boolean nonSslRecord = false;
for (;;) {
while (totalLength < OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) {
final int readableBytes = endOffset - offset;
if (readableBytes < 5) {
break;
@ -895,23 +888,18 @@ public class SslHandler extends FrameDecoder
break;
}
// We have a whole packet.
// Remember the offset and length of the current packet.
if (recordLengths == null) {
recordLengths = new int[4];
int newTotalLength = totalLength + packetLength;
if (newTotalLength > OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) {
// Don't read too much.
break;
}
if (nRecords == recordLengths.length) {
int[] newRecordLengths = new int[recordLengths.length << 1];
System.arraycopy(recordLengths, 0, newRecordLengths, 0, recordLengths.length);
recordLengths = newRecordLengths;
}
recordLengths[nRecords ++] = packetLength;
// We have a whole packet.
// Increment the offset to handle the next packet.
offset += packetLength;
totalLength = newTotalLength;
}
final int totalLength = offset - startOffset;
ChannelBuffer unwrapped = null;
if (totalLength > 0) {
// The buffer contains one or more full SSL records.
@ -924,8 +912,10 @@ public class SslHandler extends FrameDecoder
// 4) unwrapLater(...) calls decode(...)
//
// See https://github.com/netty/netty/issues/1534
assert recordLengths != null;
unwrapped = unwrapMultiple(ctx, channel, in, totalLength, recordLengths, nRecords);
final ByteBuffer inNetBuf = in.toByteBuffer(in.readerIndex(), totalLength);
unwrapped = unwrap(ctx, channel, in, inNetBuf, totalLength);
assert !inNetBuf.hasRemaining() || engine.isInboundDone();
}
if (nonSslRecord) {
@ -1238,43 +1228,23 @@ public class SslHandler extends FrameDecoder
* Calls {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer)} with an empty buffer to handle handshakes, etc.
*/
private void unwrapNonAppData(ChannelHandlerContext ctx, Channel channel) throws SSLException {
unwrapSingle(ctx, channel, ChannelBuffers.EMPTY_BUFFER, EMPTY_BUFFER, null, -1);
unwrap(ctx, channel, ChannelBuffers.EMPTY_BUFFER, EMPTY_BUFFER, -1);
}
/**
* Unwraps multiple inbound SSL records.
* Unwraps inbound SSL records.
*/
private ChannelBuffer unwrapMultiple(
ChannelHandlerContext ctx, Channel channel,
ChannelBuffer buffer, int totalLength, int[] recordLengths, int nRecords) throws SSLException {
final ByteBuffer inNetBuf = buffer.toByteBuffer(buffer.readerIndex(), totalLength);
ChannelBuffer frame = null;
for (int i = 0; i < nRecords; i ++) {
inNetBuf.limit(inNetBuf.position() + recordLengths[i]);
frame = unwrapSingle(ctx, channel, buffer, inNetBuf, frame, totalLength);
if (engine.isInboundDone()) {
break;
}
assert !inNetBuf.hasRemaining();
}
return frame;
}
/**
* Unwraps a single SSL record.
*/
private ChannelBuffer unwrapSingle(
private ChannelBuffer unwrap(
ChannelHandlerContext ctx, Channel channel,
ChannelBuffer nettyInNetBuf, ByteBuffer nioInNetBuf,
ChannelBuffer nettyOutAppBuf, int initialNettyOutAppBufCapacity) throws SSLException {
int initialNettyOutAppBufCapacity) throws SSLException {
final int nettyInNetBufStartOffset = nettyInNetBuf.readerIndex();
final int nioInNetBufStartOffset = nioInNetBuf.position();
final ByteBuffer nioOutAppBuf = bufferPool.acquireBuffer();
ChannelBuffer nettyOutAppBuf = null;
try {
boolean needsWrap = false;
for (;;) {