2009-11-16 50 views
0

我用SSL做了一個服務器,並且用阻塞了套接字。 當我連接到telnet(所以它不會握手)時,SSL_accept將無限期地阻塞,並阻止每個新的握手/接受(並按定義新連接)。SSL_accept with blocking socket

我該如何解決這個可怕的問題?

回答

1

爲什麼不直接調用SSL_accept()之前設置流套接字到非阻塞模式,然後用超時上像select()阻止如果SSL_accept()返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE?或者,您可以在調用SSL_accept()之前阻塞select()。要麼應該工作。這樣,您至少可以限制連接因類似行爲/攻擊而被阻止的時間。

請記住,SSL/TLS是面向記錄的,這意味着您必須循環直到讀取完整記錄。 SSL_pending()可以幫助在這種情況下。

1

請勿使用telnet連接?

通常情況下,可以使用TLS或純文本連接的服務通過以明文形式建立連接起作用,然後提供命令以請求「升級」連接以使用TLS。擴展SMTP和「STARTTLS」命令就是一個例子。

沒有這種命令的服務通常對TLS和非TLS流量使用不同的端口。例如,端口80上的HTTP和端口443上的HTTPS。到端口443的明文連接不起作用。

你想看什麼樣的行爲?

+0

是的,這就是我正在做的事情,但想象一下客戶端在斷開握手時斷開連接,它會阻止其他握手。 我的意思是: - 在TCP接受主線程 完成 - 在SSL_accept由每個新線程 做 - 如果一個握手(在一個單獨的線程)的塊,它會阻止每一個握手 我想行爲看到的不是阻止每一次新的握手 – fedj 2009-11-16 20:12:50

0

您可以將套接字置於非阻塞模式,然後您將從SSL_accept中獲得SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE的情況。然後你可以睡一會兒,然後再次嘗試SSL_accept。在超時值後,您可以退出並關閉ssl和套接字句柄。

請注意,這將影響所有SSL操作,這意味着您需要爲所有讀取/寫入/關閉調用執行類似的循環。基本上,任何可以返回WANT_READ或WANT_WRITE的調用。

如果您不喜歡輪詢的想法,您可以使用select來判斷您的套接字上是否有可用的數據......但這可能會有點複雜。

您也可以嘗試在SSL_accept循環之後將套接字置回阻塞模式,並繼續使用您的應用程序。

0

我認爲下面的代碼可能有助於他人解決問題。它沒有經過充分測試,以此爲靈感。

//Nonblocking SSL accept based on ACE/ace/SSL/SSL_SOCK_Acceptor.cpp 
    SSL_CTX* ctx; 
    ctx = initServerCTX(); // initialize SSL 
    loadCertificates(ctx, certificate, privateKey); // load certs 

    ... 

    SSL* ssl = SSL_new(ctx); /* get new SSL state with context */ 
    SSL_set_fd(ssl, fd); /* set connection socket to SSL state */ 

    int flags = fcntl(fd, F_GETFL, 0); 
    if (flags < 0) 
    { 
    printf("fcntl: F_GETFL \n"); 
    return false; 
    } 
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) 
    { 
    printf("fcntl: F_SETFL \n"); 
    return false; 
    } 

    int status = -1; 
    struct timeval tv, tvRestore; 
    tv.tv_sec = 2; 
    tv.tv_usec = 0; 
    tvRestore = tv; 

    fd_set writeFdSet; 
    fd_set readFdSet; 

    do 
    { 
    tv = tvRestore; 
    FD_ZERO(&writeFdSet); 
    FD_ZERO(&readFdSet); 

    status = ::SSL_accept(ssl); 
    switch (::SSL_get_error(ssl, status)) 
    { 
    case SSL_ERROR_NONE: 
     status = 0; // To tell caller about success 
     break; // Done 

    case SSL_ERROR_WANT_WRITE: 
     FD_SET(fd, &writeFdSet); 
     status = 1; // Wait for more activity 
     break; 

    case SSL_ERROR_WANT_READ: 
     FD_SET(fd, &readFdSet); 
     status = 1; // Wait for more activity 
     break; 

    case SSL_ERROR_ZERO_RETURN: 
    case SSL_ERROR_SYSCALL: 
     // The peer has notified us that it is shutting down via 
     // the SSL "close_notify" message so we need to 
     // shutdown, too. 
     printf("Peer closed connection during SSL handshake,status:%d", status); 
     status = -1; 
     break; 
    default: 
     printf("Unexpected error during SSL handshake,status:%d", status); 
     status = -1; 
     break; 
    } 

    if (status == 1) 
    { 
     // Must have at least one handle to wait for at this point. 
     status = select(fd + 1, &readFdSet, &writeFdSet, NULL, &tv); 

     // 0 is timeout, so we're done. 
     // -1 is error, so we're done. 
     // Could be both handles set (same handle in both masks) so 
     // set to 1. 
     if (status >= 1) 
     { 
     status = 1; 
     } 
     else // Timeout or failure 
     { 
     printf("SSL handshake - peer timeout or failure"); 
     status = -1; 
     } 
    } 

    } 
    while (status == 1 && !SSL_is_init_finished(ssl)); 

    flags = fcntl(fd, F_GETFL, 0); 
    if (flags < 0) 
    { 
    printf("fcntl: F_GETFL \n"); 
    return false; 
    } 
    if (fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0) 
    { 
    printf("fcntl: F_SETFL \n"); 
    return false; 
    } 


    return (status >= 0);