tls_wrap: propagate errors to write callbacks

fix #6903
This commit is contained in:
Fedor Indutny 2014-01-24 22:08:25 +04:00
parent d019eac5b5
commit 640912d18a
8 changed files with 141 additions and 53 deletions

View File

@ -651,7 +651,7 @@ Socket.prototype._writeGeneric = function(writev, data, encoding, cb) {
} }
if (err) if (err)
return this._destroy(errnoException(err, 'write'), cb); return this._destroy(errnoException(err, 'write', req.error), cb);
this._bytesDispatched += req.bytes; this._bytesDispatched += req.bytes;
@ -745,7 +745,7 @@ Socket.prototype.__defineGetter__('bytesWritten', function() {
}); });
function afterWrite(status, handle, req) { function afterWrite(status, handle, req, err) {
var self = handle.owner; var self = handle.owner;
if (self !== process.stderr && self !== process.stdout) if (self !== process.stderr && self !== process.stdout)
debug('afterWrite', status); debug('afterWrite', status);
@ -757,7 +757,7 @@ function afterWrite(status, handle, req) {
} }
if (status < 0) { if (status < 0) {
var ex = errnoException(status, 'write'); var ex = errnoException(status, 'write', err);
debug('write failure', ex); debug('write failure', ex);
self._destroy(ex, req.cb); self._destroy(ex, req.cb);
return; return;

View File

@ -677,10 +677,13 @@ exports.pump = exports.deprecate(function(readStream, writeStream, callback) {
var uv; var uv;
exports._errnoException = function(err, syscall) { exports._errnoException = function(err, syscall, original) {
if (isUndefined(uv)) uv = process.binding('uv'); if (isUndefined(uv)) uv = process.binding('uv');
var errname = uv.errname(err); var errname = uv.errname(err);
var e = new Error(syscall + ' ' + errname); var message = syscall + ' ' + errname;
if (original)
message += ' ' + original;
var e = new Error(message);
e.code = errname; e.code = errname;
e.errno = errname; e.errno = errname;
e.syscall = syscall; e.syscall = syscall;

View File

@ -69,6 +69,7 @@ namespace node {
V(domain_string, "domain") \ V(domain_string, "domain") \
V(enter_string, "enter") \ V(enter_string, "enter") \
V(errno_string, "errno") \ V(errno_string, "errno") \
V(error_string, "error") \
V(exit_string, "exit") \ V(exit_string, "exit") \
V(exponent_string, "exponent") \ V(exponent_string, "exponent") \
V(exports_string, "exports") \ V(exports_string, "exports") \

View File

@ -215,6 +215,9 @@ void StreamWrap::WriteBuffer(const FunctionCallbackInfo<Value>& args) {
req_wrap->Dispatched(); req_wrap->Dispatched();
req_wrap_obj->Set(env->bytes_string(), req_wrap_obj->Set(env->bytes_string(),
Integer::NewFromUnsigned(length, node_isolate)); Integer::NewFromUnsigned(length, node_isolate));
const char* msg = wrap->callbacks()->Error();
if (msg != NULL)
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
if (err) { if (err) {
req_wrap->~WriteWrap(); req_wrap->~WriteWrap();
@ -300,6 +303,9 @@ void StreamWrap::WriteStringImpl(const FunctionCallbackInfo<Value>& args) {
req_wrap->Dispatched(); req_wrap->Dispatched();
req_wrap->object()->Set(env->bytes_string(), req_wrap->object()->Set(env->bytes_string(),
Integer::NewFromUnsigned(data_size, node_isolate)); Integer::NewFromUnsigned(data_size, node_isolate));
const char* msg = wrap->callbacks()->Error();
if (msg != NULL)
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
if (err) { if (err) {
req_wrap->~WriteWrap(); req_wrap->~WriteWrap();
@ -401,6 +407,9 @@ void StreamWrap::Writev(const FunctionCallbackInfo<Value>& args) {
req_wrap->Dispatched(); req_wrap->Dispatched();
req_wrap->object()->Set(env->bytes_string(), req_wrap->object()->Set(env->bytes_string(),
Number::New(node_isolate, bytes)); Number::New(node_isolate, bytes));
const char* msg = wrap->callbacks()->Error();
if (msg != NULL)
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
if (err) { if (err) {
req_wrap->~WriteWrap(); req_wrap->~WriteWrap();
@ -441,14 +450,19 @@ void StreamWrap::AfterWrite(uv_write_t* req, int status) {
// Unref handle property // Unref handle property
Local<Object> req_wrap_obj = req_wrap->object(); Local<Object> req_wrap_obj = req_wrap->object();
req_wrap_obj->Delete(env->handle_string()); req_wrap_obj->Delete(env->handle_string());
wrap->callbacks_->AfterWrite(req_wrap); wrap->callbacks()->AfterWrite(req_wrap);
Local<Value> argv[] = { Local<Value> argv[] = {
Integer::New(status, node_isolate), Integer::New(status, node_isolate),
wrap->object(), wrap->object(),
req_wrap_obj req_wrap_obj,
Undefined()
}; };
const char* msg = wrap->callbacks()->Error();
if (msg != NULL)
argv[3] = OneByteString(env->isolate(), msg);
req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv);
req_wrap->~WriteWrap(); req_wrap->~WriteWrap();
@ -499,6 +513,11 @@ void StreamWrap::AfterShutdown(uv_shutdown_t* req, int status) {
} }
const char* StreamWrapCallbacks::Error() {
return NULL;
}
int StreamWrapCallbacks::DoWrite(WriteWrap* w, int StreamWrapCallbacks::DoWrite(WriteWrap* w,
uv_buf_t* bufs, uv_buf_t* bufs,
size_t count, size_t count,

View File

@ -73,6 +73,7 @@ class StreamWrapCallbacks {
virtual ~StreamWrapCallbacks() { virtual ~StreamWrapCallbacks() {
} }
virtual const char* Error();
virtual int DoWrite(WriteWrap* w, virtual int DoWrite(WriteWrap* w,
uv_buf_t* bufs, uv_buf_t* bufs,
size_t count, size_t count,

View File

@ -58,6 +58,10 @@ static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
| XN_FLAG_FN_SN; | XN_FLAG_FN_SN;
size_t TLSCallbacks::error_off_;
char TLSCallbacks::error_buf_[1024];
TLSCallbacks::TLSCallbacks(Environment* env, TLSCallbacks::TLSCallbacks(Environment* env,
Kind kind, Kind kind,
Handle<Object> sc, Handle<Object> sc,
@ -71,15 +75,16 @@ TLSCallbacks::TLSCallbacks(Environment* env,
enc_out_(NULL), enc_out_(NULL),
clear_in_(NULL), clear_in_(NULL),
write_size_(0), write_size_(0),
pending_write_item_(NULL),
started_(false), started_(false),
established_(false), established_(false),
shutdown_(false), shutdown_(false),
error_(NULL),
eof_(false) { eof_(false) {
node::Wrap<TLSCallbacks>(object(), this); node::Wrap<TLSCallbacks>(object(), this);
// Initialize queue for clearIn writes // Initialize queue for clearIn writes
QUEUE_INIT(&write_item_queue_); QUEUE_INIT(&write_item_queue_);
QUEUE_INIT(&pending_write_items_);
// We've our own session callbacks // We've our own session callbacks
SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap<TLSCallbacks>::GetSessionCallback); SSL_CTX_sess_set_get_cb(sc_->ctx_, SSLWrap<TLSCallbacks>::GetSessionCallback);
@ -102,25 +107,52 @@ TLSCallbacks::~TLSCallbacks() {
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
sni_context_.Dispose(); sni_context_.Dispose();
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
// Move all writes to pending
MakePending();
// And destroy
while (!QUEUE_EMPTY(&pending_write_items_)) {
QUEUE* q = QUEUE_HEAD(&pending_write_items_);
QUEUE_REMOVE(q);
WriteItem* wi = QUEUE_DATA(q, WriteItem, member_);
delete wi;
}
} }
void TLSCallbacks::InvokeQueued(int status) { void TLSCallbacks::MakePending() {
// Empty queue - ignore call // Aliases
if (pending_write_item_ == NULL) QUEUE* from = &write_item_queue_;
QUEUE* to = &pending_write_items_;
if (QUEUE_EMPTY(from))
return; return;
QUEUE* q = &pending_write_item_->member_; // Add items to pending
pending_write_item_ = NULL; QUEUE_ADD(to, from);
// Empty original queue
QUEUE_INIT(from);
}
bool TLSCallbacks::InvokeQueued(int status) {
if (QUEUE_EMPTY(&pending_write_items_))
return false;
// Process old queue // Process old queue
while (q != &write_item_queue_) { while (!QUEUE_EMPTY(&pending_write_items_)) {
QUEUE* next = static_cast<QUEUE*>(QUEUE_NEXT(q)); QUEUE* q = QUEUE_HEAD(&pending_write_items_);
WriteItem* wi = CONTAINER_OF(q, WriteItem, member_); QUEUE_REMOVE(q);
WriteItem* wi = QUEUE_DATA(q, WriteItem, member_);
wi->cb_(&wi->w_->req_, status); wi->cb_(&wi->w_->req_, status);
delete wi; delete wi;
q = next;
} }
return true;
} }
@ -276,15 +308,12 @@ void TLSCallbacks::EncOut() {
return; return;
// Split-off queue // Split-off queue
if (established_ && !QUEUE_EMPTY(&write_item_queue_)) { if (established_ && !QUEUE_EMPTY(&write_item_queue_))
pending_write_item_ = CONTAINER_OF(QUEUE_NEXT(&write_item_queue_), MakePending();
WriteItem,
member_);
QUEUE_INIT(&write_item_queue_);
}
// No data to write // No data to write
if (BIO_pending(enc_out_) == 0) { if (BIO_pending(enc_out_) == 0) {
if (clear_in_->Length() == 0)
InvokeQueued(0); InvokeQueued(0);
return; return;
} }
@ -314,7 +343,6 @@ void TLSCallbacks::EncOut() {
void TLSCallbacks::EncOutCb(uv_write_t* req, int status) { void TLSCallbacks::EncOutCb(uv_write_t* req, int status) {
TLSCallbacks* callbacks = static_cast<TLSCallbacks*>(req->data); TLSCallbacks* callbacks = static_cast<TLSCallbacks*>(req->data);
Environment* env = callbacks->env();
// Handle error // Handle error
if (status) { if (status) {
@ -323,12 +351,6 @@ void TLSCallbacks::EncOutCb(uv_write_t* req, int status) {
return; return;
// Notify about error // Notify about error
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
Local<Value> arg = String::Concat(
FIXED_ONE_BYTE_STRING(node_isolate, "write cb error, status: "),
Integer::New(status, node_isolate)->ToString());
callbacks->MakeCallback(env->onerror_string(), 1, &arg);
callbacks->InvokeQueued(status); callbacks->InvokeQueued(status);
return; return;
} }
@ -342,7 +364,33 @@ void TLSCallbacks::EncOutCb(uv_write_t* req, int status) {
} }
Local<Value> TLSCallbacks::GetSSLError(int status, int* err) { int TLSCallbacks::PrintErrorsCb(const char* str, size_t len, void* arg) {
size_t to_copy = error_off_;
size_t avail = sizeof(error_buf_) - error_off_ - 1;
if (avail > to_copy)
to_copy = avail;
memcpy(error_buf_, str, avail);
error_off_ += avail;
assert(error_off_ < sizeof(error_buf_));
// Zero-terminate
error_buf_[error_off_] = '\0';
return 0;
}
const char* TLSCallbacks::PrintErrors() {
error_off_ = 0;
ERR_print_errors_cb(PrintErrorsCb, this);
return error_buf_;
}
Local<Value> TLSCallbacks::GetSSLError(int status, int* err, const char** msg) {
HandleScope scope(node_isolate); HandleScope scope(node_isolate);
*err = SSL_get_error(ssl_, status); *err = SSL_get_error(ssl_, status);
@ -356,19 +404,18 @@ Local<Value> TLSCallbacks::GetSSLError(int status, int* err) {
break; break;
default: default:
{ {
BUF_MEM* mem;
BIO* bio;
assert(*err == SSL_ERROR_SSL || *err == SSL_ERROR_SYSCALL); assert(*err == SSL_ERROR_SSL || *err == SSL_ERROR_SYSCALL);
bio = BIO_new(BIO_s_mem()); const char* buf = PrintErrors();
assert(bio != NULL);
ERR_print_errors(bio);
BIO_get_mem_ptr(bio, &mem);
Local<String> message = Local<String> message =
OneByteString(node_isolate, mem->data, mem->length); OneByteString(node_isolate, buf, strlen(buf));
Local<Value> exception = Exception::Error(message); Local<Value> exception = Exception::Error(message);
BIO_free_all(bio);
if (msg != NULL) {
assert(*msg == NULL);
*msg = buf;
}
return scope.Close(exception); return scope.Close(exception);
} }
@ -409,7 +456,7 @@ void TLSCallbacks::ClearOut() {
if (read == -1) { if (read == -1) {
int err; int err;
Handle<Value> arg = GetSSLError(read, &err); Local<Value> arg = GetSSLError(read, &err, NULL);
if (!arg.IsEmpty()) { if (!arg.IsEmpty()) {
MakeCallback(env()->onerror_string(), 1, &arg); MakeCallback(env()->onerror_string(), 1, &arg);
@ -445,15 +492,25 @@ bool TLSCallbacks::ClearIn() {
// Error or partial write // Error or partial write
int err; int err;
Handle<Value> arg = GetSSLError(written, &err); Local<Value> arg = GetSSLError(written, &err, &error_);
if (!arg.IsEmpty()) { if (!arg.IsEmpty()) {
MakeCallback(env()->onerror_string(), 1, &arg); MakePending();
if (!InvokeQueued(UV_EPROTO))
error_ = NULL;
clear_in_->Reset();
} }
return false; return false;
} }
const char* TLSCallbacks::Error() {
const char* ret = error_;
error_ = NULL;
return ret;
}
int TLSCallbacks::DoWrite(WriteWrap* w, int TLSCallbacks::DoWrite(WriteWrap* w,
uv_buf_t* bufs, uv_buf_t* bufs,
size_t count, size_t count,
@ -508,11 +565,9 @@ int TLSCallbacks::DoWrite(WriteWrap* w,
int err; int err;
HandleScope handle_scope(env()->isolate()); HandleScope handle_scope(env()->isolate());
Context::Scope context_scope(env()->context()); Context::Scope context_scope(env()->context());
Handle<Value> arg = GetSSLError(written, &err); Local<Value> arg = GetSSLError(written, &err, &error_);
if (!arg.IsEmpty()) { if (!arg.IsEmpty())
MakeCallback(env()->onerror_string(), 1, &arg); return UV_EPROTO;
return -1;
}
// No errors, queue rest // No errors, queue rest
for (; i < count; i++) for (; i < count; i++)

View File

@ -50,6 +50,7 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
v8::Handle<v8::Value> unused, v8::Handle<v8::Value> unused,
v8::Handle<v8::Context> context); v8::Handle<v8::Context> context);
const char* Error();
int DoWrite(WriteWrap* w, int DoWrite(WriteWrap* w,
uv_buf_t* bufs, uv_buf_t* bufs,
size_t count, size_t count,
@ -98,7 +99,8 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
static void EncOutCb(uv_write_t* req, int status); static void EncOutCb(uv_write_t* req, int status);
bool ClearIn(); bool ClearIn();
void ClearOut(); void ClearOut();
void InvokeQueued(int status); void MakePending();
bool InvokeQueued(int status);
inline void Cycle() { inline void Cycle() {
ClearIn(); ClearIn();
@ -106,7 +108,10 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
EncOut(); EncOut();
} }
v8::Local<v8::Value> GetSSLError(int status, int* err); v8::Local<v8::Value> GetSSLError(int status, int* err, const char** msg);
const char* PrintErrors();
static int PrintErrorsCb(const char* str, size_t len, void* arg);
static void OnClientHelloParseEnd(void* arg); static void OnClientHelloParseEnd(void* arg);
static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args); static void Wrap(const v8::FunctionCallbackInfo<v8::Value>& args);
@ -133,10 +138,11 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
size_t write_size_; size_t write_size_;
size_t write_queue_size_; size_t write_queue_size_;
QUEUE write_item_queue_; QUEUE write_item_queue_;
WriteItem* pending_write_item_; QUEUE pending_write_items_;
bool started_; bool started_;
bool established_; bool established_;
bool shutdown_; bool shutdown_;
const char* error_;
// If true - delivered EOF to the js-land, either after `close_notify`, or // If true - delivered EOF to the js-land, either after `close_notify`, or
// after the `UV_EOF` on socket. // after the `UV_EOF` on socket.
@ -145,6 +151,9 @@ class TLSCallbacks : public crypto::SSLWrap<TLSCallbacks>,
#ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
v8::Persistent<v8::Value> sni_context_; v8::Persistent<v8::Value> sni_context_;
#endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB
static size_t error_off_;
static char error_buf_[1024];
}; };
} // namespace node } // namespace node

View File

@ -83,7 +83,7 @@ var server = tls.createServer({ ca: ca, cert: cert, key: key }, function(conn) {
connectError = err; connectError = err;
this.destroy(); this.destroy();
server.close(); server.close();
}); }).write('123');
}); });
process.on('exit', function() { process.on('exit', function() {