![](/img/trans.png)
[英]In openssl TLS1.3, will SSL_write yield SSL_ERROR_WANT_READ ? And will SSL_read yield SSL_ERROR_WANT_WRITE?
[英]ssl_read() in TLS/SSL with TCP stream not returning the whole buffer written by BIO_write()
以下代碼部分的目的是輪詢套接字 fd-set,如果數據(ssl 加密)可用,則讀取它並通過 openssl 庫解密。 底層傳輸層是 TCP Stream,因此數據以流的形式出現(而不是數據包)。
現在,如果從對等方快速連續發送了多個數據包(假設有 2 個長度為 85 字節的數據包),那么 TCP 接收將返回同一緩沖區中的兩個數據包,接收的字節數為 170。因此,我們有一個攜帶 2 個 ssl 加密數據包(或 n 個數據包)的緩沖區。 對於 ssl 解密,我們需要調用 BIO_write() 將緩沖區寫入 ssl_bio,然后調用 ssl_read() 檢索解密的緩沖區。 但是,盡管 BIO_write() 正在向 bio 中寫入 170 個字節,但 ssl_read() 似乎只返回一個解密的數據包(43 個字節)。 沒有返回錯誤。 如何知道 bio 中是否還有未處理的字節。 有什么出路嗎或者代碼中有什么錯誤?
當在 tcp recv() 中接收到單個數據包時,代碼工作正常。
int iReadyFds = poll( PollFdSet, iFdCount, iTimeout);
for(iFdIndx = 0; iFdIndx < (iFdCount) && (iReadyFds>0); ++iFdIndx)
{
if((PollFdSet[iFdIndx].events == 0) ||
(PollFdSet[iFdIndx].fd == 0) ||
(PollFdSet[iFdIndx].revents != POLLIN)
)
{
continue;
}
/* we have data to read */
int iMsgLen = 0;
int iFd = PollFdSet[iFdIndx].fd;
/*This is TCP Receive. Returns 170 bytes*/
iRcvdBytes = recv( iSocketId, ( void* )pcInBuffer, PN_TCP_STREAM_MAX_RX_BUFF_SIZE, 0 );
/*Writing into SSL BIO, this will be retrieved by ssl_read*/
/*iNoOFBytes = 170*/
iNoOFBytes = BIO_write(m_pRead_bio, pcInBuffer, iRcvdBytes);
if(iNoOFBytes <= 0)
{
printf("Error");
return -1;
}
char* pcDecodedBuff = (char*)malloc(1024);
/*here it returns 43 bytes of decrypted buffer(1 packet). the other packet vanishes*/
iReadData = SSL_read(m_psSSL, pcDecodedBuff, 1024);
if ((iReadData == -1) || (iReadData == 0))
{
error = SSL_get_error(psPskTls->m_psSSL, iReadData);
if(error == SSL_ERROR_ZERO_RETURN
|| error == SSL_ERROR_NONE
|| error == SSL_ERROR_WANT_READ)
{
printf("Error");
}
}
iReadyFds--;
}
OpenSSL 通常一次只讀取和解密一條記錄。 再次調用 SSL_read 將為您提供下一條記錄。 如果您不知道是否有另一條記錄要讀取,您可以詢問底層傳輸它當前是否“可讀”——或者只是調用 SSL_read() 並處理錯誤(如果使用非阻塞 IO)。
在某些情況下(例如,如果您使用“read_ahead”功能),OpenSSL 可能會在內部緩沖一些數據。 您可以使用 SSL_has_pending()(對於 OpenSSL 1.1.0)或 SSL_pending()(對於 OpenSSL 1.0.2+)來查明是否有緩沖的內部數據。 請參閱https://www.openssl.org/docs/man1.1.0/ssl/SSL_has_pending.html 。
經過幾次實驗和閱讀 openssl 文檔后,我已經能夠解決這個問題。 據我了解,在相當高的速度下(每秒通過 ssl 連接處理超過 1000 個應用程序數據事務),在使用異步 openssl 實現的情況下肯定會出現此問題。
根據openssl的實現,得出答案-
由於這個問題與 ssl_read() 相關,我們從上面的第二點繼續我們的討論。 據我了解,ssl_read() 實際上是執行 openssl 的內部 fsm,它一次從 read_bio 讀取和處理一個數據包的緩沖區。 緩沖區的其余部分(如果有)作為未處理的緩沖區位於讀取 bio 中。 因此, ssl_pending() 永遠不會返回任何內容,因為沒有處理的緩沖區可供讀取。 確定是否有任何東西在 bio 中需要處理和讀取的唯一方法是連續調用 ssl_read() 直到它返回接收到的 0 個字節。
修改后的代碼應該是這樣的——
int iReadyFds = poll( PollFdSet, iFdCount, iTimeout);
for(iFdIndx = 0; iFdIndx < (iFdCount) && (iReadyFds>0); ++iFdIndx)
{
if((PollFdSet[iFdIndx].events == 0) ||
(PollFdSet[iFdIndx].fd == 0) ||
(PollFdSet[iFdIndx].revents != POLLIN)
)
{
continue;
}
/* we have data to read */
int iMsgLen = 0;
int iFd = PollFdSet[iFdIndx].fd;
/*This is TCP Receive. Returns 170 bytes*/
iRcvdBytes = recv( iSocketId, ( void* )pcInBuffer, PN_TCP_STREAM_MAX_RX_BUFF_SIZE, 0 );
/*Writing into SSL BIO, this will be retrieved by ssl_read*/
/*iNoOFBytes = 170*/
iNoOFBytes = BIO_write(m_pRead_bio, pcInBuffer, iRcvdBytes);
if(iNoOFBytes <= 0)
{
printf("Error");
return -1;
}
char* pcDecodedBuff = (char*)malloc(1024);
/*here it returns 43 bytes of decrypted buffer(1 packet).
So we keep on reading until all the packets are processed and read*/
while(iReadData = SSL_read(m_psSSL, pcDecodedBuff, 1024) > 0)
{
doSomething(pcDecodedBuff, iReadData);**
}
if ((iReadData == -1) || (iReadData == 0))
{
error = SSL_get_error(psPskTls->m_psSSL, iReadData);
if(error == SSL_ERROR_ZERO_RETURN
|| error == SSL_ERROR_NONE
|| error == SSL_ERROR_WANT_READ)
{
printf("Error");
}
}
iReadyFds--;
}
檢查如何連續使用 ssl_read() 以確保 read_BIO 中沒有未處理的數據。
while(iReadData = SSL_read(m_psSSL, pcDecodedBuff, 1024) > 0) { doSomething(pcDecodedBuff, iReadData); }
使用此代碼,我面臨的問題得到了解決。 希望它也能幫助其他人。
我還使用內存 BIO 將 SSL 與非阻塞套接字一起使用(使用輪詢)。 我最終得到的代碼看起來與您的解決方案相似,但多了一個步驟; 在調用SSL_get_error
之后,您需要檢查 SSL 是否已請求寫入操作。 SSL_read
可能導致 SSL 對象需要執行套接字寫入; 如果對等方請求重新協商,就會發生這種情況。
下面是ssl_server_nonblock.c 上完整代碼的片段......它的部分執行BIO_write
和SSL_read
/* Process SSL bytes received from the peer. The data needs to be fed into the
SSL object to be unencrypted. On success returns 0, on SSL error -1. */
int on_read_cb(char* src, size_t len) {
char buf[DEFAULT_BUF_SIZE]; /* used for copying bytes out of SSL/BIO */
enum sslstatus status;
int n;
while (len > 0) {
n = BIO_write(client.rbio, src, len);
if (n<=0)
return -1; /* if BIO write fails, assume unrecoverable */
src += n;
len -= n;
if (!SSL_is_init_finished(client.ssl)) {
n = SSL_accept(client.ssl);
status = get_sslstatus(client.ssl, n);
/* Did SSL request to write bytes? */
if (status == SSLSTATUS_WANT_IO)
do {
n = BIO_read(client.wbio, buf, sizeof(buf));
if (n > 0)
queue_encrypted_bytes(buf, n);
else if (!BIO_should_retry(client.wbio))
return -1;
} while (n>0);
if (status == SSLSTATUS_FAIL)
return -1;
if (!SSL_is_init_finished(client.ssl))
return 0;
}
/* The encrypted data is now in the input bio so now we can perform actual
* read of unencrypted data. */
do {
n = SSL_read(client.ssl, buf, sizeof(buf));
if (n > 0)
client.do_something(buf, (size_t)n);
} while (n > 0);
status = get_sslstatus(client.ssl, n);
/* Did SSL request to write bytes? This can happen if peer has requested SSL
* renegotiation. */
if (status == SSLSTATUS_WANT_IO)
do {
n = BIO_read(client.wbio, buf, sizeof(buf));
if (n > 0)
queue_encrypted_bytes(buf, n);
else if (!BIO_should_retry(client.wbio))
return -1;
} while (n>0);
if (status == SSLSTATUS_FAIL)
return -1;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.