Skip to content

Commit

Permalink
Reduce memory copies when using OpenSslEngine with SslHandler
Browse files Browse the repository at this point in the history
Motivation:

When using OpenSslEngine with the SslHandler it is possible to reduce memory copies by unwrap(...) multiple ByteBuffers at the same time. This way we can eliminate a memory copy that is needed otherwise to cumulate partial received data.

Modifications:

- Add OpenSslEngine.unwrap(ByteBuffer[],...) method that can be used to unwrap multiple src ByteBuffer a the same time
- Use a CompositeByteBuffer in SslHandler for inbound data so we not need to memory copy
- Add OpenSslEngine.unwrap(ByteBuffer[],...) in SslHandler if OpenSslEngine is used and the inbound ByteBuf is backed by more then one ByteBuffer
- Reduce object allocation

Result:

SslHandler is faster when using OpenSslEngine and produce less GC
  • Loading branch information
normanmaurer committed Jan 12, 2015
1 parent 3ebe2ee commit 1bb818b
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 61 deletions.
90 changes: 69 additions & 21 deletions handler/src/main/java/io/netty/handler/ssl/OpenSslEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.logging.InternalLogger;
Expand Down Expand Up @@ -120,6 +121,8 @@ enum ClientAuthMode {

private static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL";

private static final long EMPTY_ADDR = Buffer.address(Unpooled.EMPTY_BUFFER.nioBuffer());

// OpenSSL state
private long ssl;
private long networkBIO;
Expand Down Expand Up @@ -147,8 +150,6 @@ enum ClientAuthMode {
private boolean isOutboundDone;
private boolean engineClosed;

private int lastPrimingReadResult;

private final boolean clientMode;
private final ByteBufAllocator alloc;
private final String fallbackApplicationProtocol;
Expand Down Expand Up @@ -252,7 +253,7 @@ private int writePlaintextData(final ByteBuffer src) {
}

/**
* Write encrypted data to the OpenSSL network BIO
* Write encrypted data to the OpenSSL network BIO.
*/
private int writeEncryptedData(final ByteBuffer src) {
final int pos = src.position();
Expand All @@ -262,7 +263,6 @@ private int writeEncryptedData(final ByteBuffer src) {
final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
if (netWrote >= 0) {
src.position(pos + netWrote);
lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read
return netWrote;
}
} else {
Expand All @@ -275,7 +275,6 @@ private int writeEncryptedData(final ByteBuffer src) {
final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
if (netWrote >= 0) {
src.position(pos + netWrote);
lastPrimingReadResult = SSL.readFromSSL(ssl, addr, 0); // priming read
return netWrote;
} else {
src.position(pos);
Expand All @@ -285,7 +284,7 @@ private int writeEncryptedData(final ByteBuffer src) {
}
}

return 0;
return -1;
}

/**
Expand Down Expand Up @@ -464,31 +463,36 @@ public synchronized SSLEngineResult wrap(
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}

@Override
public synchronized SSLEngineResult unwrap(
final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException {
final ByteBuffer[] srcs, int srcsOffset, final int srcsLength,
final ByteBuffer[] dsts, final int dstsOffset, final int dstsLength) throws SSLException {

// Check to make sure the engine has not been closed
if (destroyed != 0) {
return new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0);
}

// Throw requried runtime exceptions
if (src == null) {
throw new NullPointerException("src");
if (srcs == null) {
throw new NullPointerException("srcs");
}
if (srcsOffset >= srcs.length
|| srcsOffset + srcsLength > srcs.length) {
throw new IndexOutOfBoundsException(
"offset: " + srcsOffset + ", length: " + srcsLength +
" (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))");
}
if (dsts == null) {
throw new NullPointerException("dsts");
}
if (offset >= dsts.length || offset + length > dsts.length) {
if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) {
throw new IndexOutOfBoundsException(
"offset: " + offset + ", length: " + length +
" (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))");
"offset: " + dstsOffset + ", length: " + dstsLength +
" (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))");
}

int capacity = 0;
final int endOffset = offset + length;
for (int i = offset; i < endOffset; i ++) {
final int endOffset = dstsOffset + dstsLength;
for (int i = dstsOffset; i < endOffset; i ++) {
ByteBuffer dst = dsts[i];
if (dst == null) {
throw new IllegalArgumentException();
Expand All @@ -511,8 +515,18 @@ public synchronized SSLEngineResult unwrap(
return new SSLEngineResult(getEngineStatus(), NEED_WRAP, 0, 0);
}

final int srcsEndOffset = srcsOffset + srcsLength;
int len = 0;
for (int i = srcsOffset; i < srcsEndOffset; i++) {
ByteBuffer src = srcs[i];
if (src == null) {
throw new NullPointerException("srcs[" + i + ']');
}
len += src.remaining();
}

// protect against protocol overflow attack vector
if (src.remaining() > MAX_ENCRYPTED_PACKET_LENGTH) {
if (len > MAX_ENCRYPTED_PACKET_LENGTH) {
isInboundDone = true;
isOutboundDone = true;
engineClosed = true;
Expand All @@ -521,13 +535,37 @@ public synchronized SSLEngineResult unwrap(
}

// Write encrypted data to network BIO
int bytesConsumed = 0;
lastPrimingReadResult = 0;
int bytesConsumed = -1;
int lastPrimingReadResult = 0;
try {
bytesConsumed += writeEncryptedData(src);
while (srcsOffset < srcsEndOffset) {
ByteBuffer src = srcs[srcsOffset];
int remaining = src.remaining();
int written = writeEncryptedData(src);
if (written >= 0) {
if (bytesConsumed == -1) {
bytesConsumed = written;
} else {
bytesConsumed += written;
}
if (written == remaining) {
srcsOffset ++;
} else if (written == 0) {
break;
}
} else {
break;
}
}
} catch (Exception e) {
throw new SSLException(e);
}
if (bytesConsumed >= 0) {
lastPrimingReadResult = SSL.readFromSSL(ssl, EMPTY_ADDR, 0); // priming read
} else {
// Reset to 0 as -1 is used to signal that nothing was written and no priming read needs to be done
bytesConsumed = 0;
}

// Check for OpenSSL errors caused by the priming read
long error = SSL.getLastErrorNumber();
Expand All @@ -554,7 +592,7 @@ public synchronized SSLEngineResult unwrap(

// Write decrypted data to dsts buffers
int bytesProduced = 0;
int idx = offset;
int idx = dstsOffset;
while (idx < endOffset) {
ByteBuffer dst = dsts[idx];
if (!dst.hasRemaining()) {
Expand Down Expand Up @@ -595,6 +633,16 @@ public synchronized SSLEngineResult unwrap(
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}

public SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException {
return unwrap(srcs, 0, srcs.length, dsts, 0, dsts.length);
}

@Override
public SSLEngineResult unwrap(
final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException {
return unwrap(new ByteBuffer[] { src }, 0, 1, dsts, offset, length);
}

@Override
public Runnable getDelegatedTask() {
// Currently, we do not delegate SSL computation tasks
Expand Down
Loading

0 comments on commit 1bb818b

Please sign in to comment.