 Chromium Code Reviews
 Chromium Code Reviews Issue 2915243002:
  Fixing SSL error that occurs when underlying socket is blocked.  (Closed)
    
  
    Issue 2915243002:
  Fixing SSL error that occurs when underlying socket is blocked.  (Closed) 
  | Index: webrtc/base/openssladapter.cc | 
| diff --git a/webrtc/base/openssladapter.cc b/webrtc/base/openssladapter.cc | 
| index bc7b99b97a61449ab785eb24c8697e0dcfdf485b..d40e69b8fb4fc39c6c313f521a59df99aba9c426 100644 | 
| --- a/webrtc/base/openssladapter.cc | 
| +++ b/webrtc/base/openssladapter.cc | 
| @@ -334,6 +334,11 @@ OpenSSLAdapter::BeginSSL() { | 
| SSL_set_app_data(ssl_, this); | 
| SSL_set_bio(ssl_, bio, bio); | 
| + // SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER allows different buffers to be passed | 
| + // into SSL_write when a record could only be partially transmitted (and thus | 
| + // requires another call to SSL_write to finish transmission). This allows us | 
| + // to copy the data into our own buffer when this occurs, since the original | 
| + // buffer can't safely be accessed after control exits Send. | 
| SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE | | 
| SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); | 
| 
juberti1
2017/06/02 05:35:44
From the chat with davidben, I'm not sure we want
 
Taylor Brandstetter
2017/06/02 06:49:13
Probably. But it's unrelated to this CL so I'll le
 | 
| @@ -441,6 +446,80 @@ OpenSSLAdapter::Cleanup() { | 
| Thread::Current()->Clear(this, MSG_TIMEOUT); | 
| } | 
| +int OpenSSLAdapter::DoSslWrite(const void* pv, size_t cb) { | 
| + // If we're in "pending write" mode, we should only be calling SSL_write with | 
| + // the pending data. | 
| + RTC_DCHECK(!pending_write_ || pv == pending_data_.data()); | 
| + | 
| + ssl_write_needs_read_ = false; | 
| + int ret = SSL_write(ssl_, pv, checked_cast<int>(cb)); | 
| + int error = SSL_get_error(ssl_, ret); | 
| + switch (error) { | 
| + case SSL_ERROR_SSL: | 
| + // Walk down the error stack to find the SSL error. | 
| + uint32_t error_code; | 
| 
juberti1
2017/06/02 05:35:44
This feels like it should be factored into a funct
 
Taylor Brandstetter
2017/06/02 06:49:13
Done.
 | 
| + const char* file; | 
| + int line; | 
| + do { | 
| + error_code = ERR_get_error_line(&file, &line); | 
| + if (ERR_GET_LIB(error_code) == ERR_LIB_SSL) { | 
| + LOG(LS_ERROR) << "ERR_LIB_SSL: " << error_code << ", " << file << ":" | 
| + << line; | 
| + break; | 
| + } | 
| + } while (error_code != 0); | 
| + Error("SSL_write", ret ? ret : -1, false); | 
| + break; | 
| + case SSL_ERROR_NONE: | 
| + // Success! | 
| 
juberti1
2017/06/02 05:35:44
Maybe this case should come first?
 
Taylor Brandstetter
2017/06/02 06:49:13
Done.
 | 
| + return ret; | 
| + case SSL_ERROR_WANT_READ: | 
| + LOG(LS_INFO) << " -- error want read"; | 
| + ssl_write_needs_read_ = true; | 
| + SetError(EWOULDBLOCK); | 
| + break; | 
| + case SSL_ERROR_WANT_WRITE: | 
| + LOG(LS_INFO) << " -- error want write"; | 
| + SetError(EWOULDBLOCK); | 
| + break; | 
| + case SSL_ERROR_ZERO_RETURN: | 
| + // LOG(LS_INFO) << " -- remote side closed"; | 
| + SetError(EWOULDBLOCK); | 
| + // do we need to signal closure? | 
| + break; | 
| + default: | 
| + LOG(LS_WARNING) << "Unknown error from SSL_write: " << error; | 
| + Error("SSL_write", ret ? ret : -1, false); | 
| + break; | 
| + } | 
| + | 
| + // If SSL_write fails with SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, this | 
| + // means the underlying socket is blocked on reading or (more typically) | 
| + // writing. When this happens, OpenSSL requires that the next call to | 
| + // SSL_write uses the same arguments (though, with | 
| + // SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER, the actual buffer pointer may be | 
| + // different). | 
| + // | 
| + // However, after Send exits, we will have lost access to data the user of | 
| + // this class is trying to send, and there's no guarantee that the user of | 
| + // this class will call Send with the same arguements when it fails. So, we | 
| + // buffer the data ourselves. When we know the underlying socket is writable | 
| + // again from OnWriteEvent (or if Send is called again before that happens), | 
| + // we'll retry sending this buffered data. | 
| + if ((error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) && | 
| 
juberti1
2017/06/02 05:35:43
It seems like this should be a low-level routine -
 
Taylor Brandstetter
2017/06/02 06:49:13
Ok. Somehow the fact that the write failed due to
 | 
| + !pending_write_) { | 
| + LOG(LS_WARNING) | 
| + << "SSL_write couldn't write to the underlying socket; buffering data."; | 
| + pending_write_ = true; | 
| + pending_data_.SetData(static_cast<const uint8_t*>(pv), cb); | 
| + // Since we're taking responsibility for sending this data, return its full | 
| + // size. The user of this class can consider it sent. | 
| + return cb; | 
| + } | 
| + | 
| + return SOCKET_ERROR; | 
| +} | 
| + | 
| // | 
| // AsyncSocket Implementation | 
| // | 
| @@ -466,38 +545,25 @@ OpenSSLAdapter::Send(const void* pv, size_t cb) { | 
| return SOCKET_ERROR; | 
| } | 
| + if (pending_write_) { | 
| + int ret = DoSslWrite(pending_data_.data(), pending_data_.size()); | 
| + if (ret == static_cast<int>(pending_data_.size())) { | 
| + // We completed sending the data previously passed into SSL_write! Now | 
| + // we're allowed to send more data. | 
| + pending_write_ = false; | 
| + } else { | 
| + // We couldn't finish sending the pending data, so we definitely can't | 
| + // send any more data. Return with an EWOULDBLOCK error. | 
| + SetError(EWOULDBLOCK); | 
| + return -1; | 
| 
pthatcher1
2017/06/02 04:58:21
return SOCKET_ERROR
or just:
return ret;
 
Taylor Brandstetter
2017/06/02 06:49:13
Done.
 | 
| + } | 
| 
pthatcher1
2017/06/02 04:58:21
Might be more clear with an early return:
if (pen
 
Taylor Brandstetter
2017/06/02 06:49:13
I don't like "MaybeRetrySslWrite" because it's not
 | 
| + } | 
| + | 
| // OpenSSL will return an error if we try to write zero bytes | 
| if (cb == 0) | 
| return 0; | 
| - ssl_write_needs_read_ = false; | 
| - | 
| - int code = SSL_write(ssl_, pv, checked_cast<int>(cb)); | 
| - switch (SSL_get_error(ssl_, code)) { | 
| - case SSL_ERROR_NONE: | 
| - //LOG(LS_INFO) << " -- success"; | 
| - return code; | 
| - case SSL_ERROR_WANT_READ: | 
| - //LOG(LS_INFO) << " -- error want read"; | 
| - ssl_write_needs_read_ = true; | 
| - SetError(EWOULDBLOCK); | 
| - break; | 
| - case SSL_ERROR_WANT_WRITE: | 
| - //LOG(LS_INFO) << " -- error want write"; | 
| - SetError(EWOULDBLOCK); | 
| - break; | 
| - case SSL_ERROR_ZERO_RETURN: | 
| - //LOG(LS_INFO) << " -- remote side closed"; | 
| - SetError(EWOULDBLOCK); | 
| - // do we need to signal closure? | 
| - break; | 
| - default: | 
| - //LOG(LS_INFO) << " -- error " << code; | 
| - Error("SSL_write", (code ? code : -1), false); | 
| - break; | 
| - } | 
| - | 
| - return SOCKET_ERROR; | 
| + return DoSslWrite(pv, cb); | 
| } | 
| int | 
| @@ -682,6 +748,15 @@ OpenSSLAdapter::OnWriteEvent(AsyncSocket* socket) { | 
| AsyncSocketAdapter::OnReadEvent(socket); | 
| } | 
| + // If a previous SSL_write failed due to the underlying socket being blocked, | 
| + // this will attempt finishing the write operation. | 
| + if (pending_write_) { | 
| + if (DoSslWrite(pending_data_.data(), pending_data_.size()) == | 
| + static_cast<int>(pending_data_.size())) { | 
| + pending_write_ = false; | 
| 
pthatcher1
2017/06/02 04:58:21
Shouldn't we clear the pending_data_?
 
Taylor Brandstetter
2017/06/02 06:49:13
Would have been pointless before, but since I got
 | 
| + } | 
| + } | 
| 
pthatcher1
2017/06/02 04:58:21
With the helper method, this could just be:
Maybe
 | 
| + | 
| //LOG(LS_INFO) << " -- onStreamWriteable"; | 
| AsyncSocketAdapter::OnWriteEvent(socket); | 
| } |