Browse Source

crypto: lower RSS usage for TLSCallbacks

Don't allocate any BIO buffers initially, do this on a first read from
the TCP connection. Allocate different amount of data for initial read
and for consequent reads: small buffer for hello+certificate, big buffer
for better throughput.

see #8416
v0.11.15-release
Fedor Indutny 10 years ago
parent
commit
2122a77f51
  1. 75
      src/node_crypto_bio.cc
  2. 38
      src/node_crypto_bio.h
  3. 9
      src/tls_wrap.cc
  4. 6
      src/tls_wrap.h

75
src/node_crypto_bio.cc

@ -272,6 +272,8 @@ size_t NodeBIO::Read(char* out, size_t size) {
void NodeBIO::FreeEmpty() { void NodeBIO::FreeEmpty() {
if (write_head_ == NULL)
return;
Buffer* child = write_head_->next_; Buffer* child = write_head_->next_;
if (child == write_head_ || child == read_head_) if (child == write_head_ || child == read_head_)
return; return;
@ -281,13 +283,6 @@ void NodeBIO::FreeEmpty() {
Buffer* prev = child; Buffer* prev = child;
while (cur != read_head_) { while (cur != read_head_) {
// Skip embedded buffer, and continue deallocating again starting from it
if (cur == &head_) {
prev->next_ = cur;
prev = cur;
cur = head_.next_;
continue;
}
assert(cur != write_head_); assert(cur != write_head_);
assert(cur->write_pos_ == cur->read_pos_); assert(cur->write_pos_ == cur->read_pos_);
@ -295,7 +290,6 @@ void NodeBIO::FreeEmpty() {
delete cur; delete cur;
cur = next; cur = next;
} }
assert(prev == child || prev == &head_);
prev->next_ = cur; prev->next_ = cur;
} }
@ -330,7 +324,7 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) {
} }
// Move to next buffer // Move to next buffer
if (current->read_pos_ + avail == kBufferLength) { if (current->read_pos_ + avail == current->len_) {
current = current->next_; current = current->next_;
} }
} }
@ -343,10 +337,14 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) {
void NodeBIO::Write(const char* data, size_t size) { void NodeBIO::Write(const char* data, size_t size) {
size_t offset = 0; size_t offset = 0;
size_t left = size; size_t left = size;
// Allocate initial buffer if the ring is empty
TryAllocateForWrite(left);
while (left > 0) { while (left > 0) {
size_t to_write = left; size_t to_write = left;
assert(write_head_->write_pos_ <= kBufferLength); assert(write_head_->write_pos_ <= write_head_->len_);
size_t avail = kBufferLength - write_head_->write_pos_; size_t avail = write_head_->len_ - write_head_->write_pos_;
if (to_write > avail) if (to_write > avail)
to_write = avail; to_write = avail;
@ -361,12 +359,12 @@ void NodeBIO::Write(const char* data, size_t size) {
offset += to_write; offset += to_write;
length_ += to_write; length_ += to_write;
write_head_->write_pos_ += to_write; write_head_->write_pos_ += to_write;
assert(write_head_->write_pos_ <= kBufferLength); assert(write_head_->write_pos_ <= write_head_->len_);
// Go to next buffer if there still are some bytes to write // Go to next buffer if there still are some bytes to write
if (left != 0) { if (left != 0) {
assert(write_head_->write_pos_ == kBufferLength); assert(write_head_->write_pos_ == write_head_->len_);
TryAllocateForWrite(); TryAllocateForWrite(left);
write_head_ = write_head_->next_; write_head_ = write_head_->next_;
// Additionally, since we're moved to the next buffer, read head // Additionally, since we're moved to the next buffer, read head
@ -379,7 +377,9 @@ void NodeBIO::Write(const char* data, size_t size) {
char* NodeBIO::PeekWritable(size_t* size) { char* NodeBIO::PeekWritable(size_t* size) {
size_t available = kBufferLength - write_head_->write_pos_; TryAllocateForWrite(*size);
size_t available = write_head_->len_ - write_head_->write_pos_;
if (*size != 0 && available > *size) if (*size != 0 && available > *size)
available = *size; available = *size;
else else
@ -392,12 +392,12 @@ char* NodeBIO::PeekWritable(size_t* size) {
void NodeBIO::Commit(size_t size) { void NodeBIO::Commit(size_t size) {
write_head_->write_pos_ += size; write_head_->write_pos_ += size;
length_ += size; length_ += size;
assert(write_head_->write_pos_ <= kBufferLength); assert(write_head_->write_pos_ <= write_head_->len_);
// Allocate new buffer if write head is full, // Allocate new buffer if write head is full,
// and there're no other place to go // and there're no other place to go
TryAllocateForWrite(); TryAllocateForWrite(0);
if (write_head_->write_pos_ == kBufferLength) { if (write_head_->write_pos_ == write_head_->len_) {
write_head_ = write_head_->next_; write_head_ = write_head_->next_;
// Additionally, since we're moved to the next buffer, read head // Additionally, since we're moved to the next buffer, read head
@ -407,19 +407,35 @@ void NodeBIO::Commit(size_t size) {
} }
void NodeBIO::TryAllocateForWrite() { void NodeBIO::TryAllocateForWrite(size_t hint) {
Buffer* w = write_head_;
Buffer* r = read_head_;
// If write head is full, next buffer is either read head or not empty. // If write head is full, next buffer is either read head or not empty.
if (write_head_->write_pos_ == kBufferLength && if (w == NULL ||
(write_head_->next_ == read_head_ || (w->write_pos_ == w->len_ &&
write_head_->next_->write_pos_ != 0)) { (w->next_ == r || w->next_->write_pos_ != 0))) {
Buffer* next = new Buffer(); size_t len = w == NULL ? initial_ :
next->next_ = write_head_->next_; kThroughputBufferLength;
write_head_->next_ = next; if (len < hint)
len = hint;
Buffer* next = new Buffer(len);
if (w == NULL) {
next->next_ = next;
write_head_ = next;
read_head_ = next;
} else {
next->next_ = w->next_;
w->next_ = next;
}
} }
} }
void NodeBIO::Reset() { void NodeBIO::Reset() {
if (read_head_ == NULL)
return;
while (read_head_->read_pos_ != read_head_->write_pos_) { while (read_head_->read_pos_ != read_head_->write_pos_) {
assert(read_head_->write_pos_ > read_head_->read_pos_); assert(read_head_->write_pos_ > read_head_->read_pos_);
@ -435,12 +451,15 @@ void NodeBIO::Reset() {
NodeBIO::~NodeBIO() { NodeBIO::~NodeBIO() {
Buffer* current = head_.next_; if (read_head_ == NULL)
while (current != &head_) { return;
Buffer* current = read_head_;
do {
Buffer* next = current->next_; Buffer* next = current->next_;
delete current; delete current;
current = next; current = next;
} } while (current != read_head_);
read_head_ = NULL; read_head_ = NULL;
write_head_ = NULL; write_head_ = NULL;

38
src/node_crypto_bio.h

@ -29,9 +29,10 @@ namespace node {
class NodeBIO { class NodeBIO {
public: public:
NodeBIO() : length_(0), read_head_(&head_), write_head_(&head_) { NodeBIO() : initial_(kInitialBufferLength),
// Loop head length_(0),
head_.next_ = &head_; read_head_(NULL),
write_head_(NULL) {
} }
~NodeBIO(); ~NodeBIO();
@ -42,7 +43,7 @@ class NodeBIO {
void TryMoveReadHead(); void TryMoveReadHead();
// Allocate new buffer for write if needed // Allocate new buffer for write if needed
void TryAllocateForWrite(); void TryAllocateForWrite(size_t hint);
// Read `len` bytes maximum into `out`, return actual number of read bytes // Read `len` bytes maximum into `out`, return actual number of read bytes
size_t Read(char* out, size_t size); size_t Read(char* out, size_t size);
@ -76,11 +77,16 @@ class NodeBIO {
// Commit reserved data // Commit reserved data
void Commit(size_t size); void Commit(size_t size);
// Return size of buffer in bytes // Return size of buffer in bytes
size_t inline Length() { inline size_t Length() const {
return length_; return length_;
} }
inline void set_initial(size_t initial) {
initial_ = initial;
}
static inline NodeBIO* FromBIO(BIO* bio) { static inline NodeBIO* FromBIO(BIO* bio) {
assert(bio->ptr != NULL); assert(bio->ptr != NULL);
return static_cast<NodeBIO*>(bio->ptr); return static_cast<NodeBIO*>(bio->ptr);
@ -95,24 +101,34 @@ class NodeBIO {
static int Gets(BIO* bio, char* out, int size); static int Gets(BIO* bio, char* out, int size);
static long Ctrl(BIO* bio, int cmd, long num, void* ptr); static long Ctrl(BIO* bio, int cmd, long num, void* ptr);
// NOTE: Size is maximum TLS frame length, this is required if we want // Enough to handle the most of the client hellos
// to fit whole ClientHello into one Buffer of NodeBIO. static const size_t kInitialBufferLength = 1024;
static const size_t kBufferLength = 16 * 1024 + 5; static const size_t kThroughputBufferLength = 16384;
static const BIO_METHOD method; static const BIO_METHOD method;
class Buffer { class Buffer {
public: public:
Buffer() : read_pos_(0), write_pos_(0), next_(NULL) { explicit Buffer(size_t len) : read_pos_(0),
write_pos_(0),
len_(len),
next_(NULL) {
data_ = new char[len];
}
~Buffer() {
delete[] data_;
} }
size_t read_pos_; size_t read_pos_;
size_t write_pos_; size_t write_pos_;
size_t len_;
Buffer* next_; Buffer* next_;
char data_[kBufferLength]; char* data_;
}; };
size_t initial_;
size_t length_; size_t length_;
Buffer head_;
Buffer* read_head_; Buffer* read_head_;
Buffer* write_head_; Buffer* write_head_;
}; };

9
src/tls_wrap.cc

@ -192,6 +192,8 @@ void TLSCallbacks::InitSSL() {
if (is_server()) { if (is_server()) {
SSL_set_accept_state(ssl_); SSL_set_accept_state(ssl_);
} else if (is_client()) { } else if (is_client()) {
// Enough space for server response (hello, cert)
NodeBIO::FromBIO(enc_in_)->set_initial(kInitialClientBufferLength);
SSL_set_connect_state(ssl_); SSL_set_connect_state(ssl_);
} else { } else {
// Unexpected // Unexpected
@ -254,6 +256,7 @@ void TLSCallbacks::Receive(const FunctionCallbackInfo<Value>& args) {
wrap->DoAlloc(reinterpret_cast<uv_handle_t*>(stream), len, &buf); wrap->DoAlloc(reinterpret_cast<uv_handle_t*>(stream), len, &buf);
size_t copy = buf.len > len ? len : buf.len; size_t copy = buf.len > len ? len : buf.len;
memcpy(buf.base, data, copy); memcpy(buf.base, data, copy);
buf.len = copy;
wrap->DoRead(stream, buf.len, &buf, UV_UNKNOWN_HANDLE); wrap->DoRead(stream, buf.len, &buf, UV_UNKNOWN_HANDLE);
data += copy; data += copy;
@ -615,8 +618,9 @@ void TLSCallbacks::AfterWrite(WriteWrap* w) {
void TLSCallbacks::DoAlloc(uv_handle_t* handle, void TLSCallbacks::DoAlloc(uv_handle_t* handle,
size_t suggested_size, size_t suggested_size,
uv_buf_t* buf) { uv_buf_t* buf) {
buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&suggested_size); size_t size = 0;
buf->len = suggested_size; buf->base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&size);
buf->len = size;
} }
@ -720,6 +724,7 @@ void TLSCallbacks::EnableHelloParser(const FunctionCallbackInfo<Value>& args) {
TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.Holder()); TLSCallbacks* wrap = Unwrap<TLSCallbacks>(args.Holder());
NodeBIO::FromBIO(wrap->enc_in_)->set_initial(kMaxHelloLength);
wrap->hello_parser_.Start(SSLWrap<TLSCallbacks>::OnClientHello, wrap->hello_parser_.Start(SSLWrap<TLSCallbacks>::OnClientHello,
OnClientHelloParseEnd, OnClientHelloParseEnd,
wrap); wrap);

6
src/tls_wrap.h

@ -74,6 +74,12 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
protected: protected:
static const int kClearOutChunkSize = 1024; static const int kClearOutChunkSize = 1024;
// Maximum number of bytes for hello parser
static const int kMaxHelloLength = 16384;
// Usual ServerHello + Certificate size
static const int kInitialClientBufferLength = 4096;
// Maximum number of buffers passed to uv_write() // Maximum number of buffers passed to uv_write()
static const int kSimultaneousBufferCount = 10; static const int kSimultaneousBufferCount = 10;

Loading…
Cancel
Save