2016-08-20 17 views
2

私は、opensslを使ってCで小さなIRCボットを作成して、安全なソケットを開始します。最も美しく書かれたボットではありませんが、主にopenssl APIがどのように動作するかを見るだけです。現在、私は次のコードを持っています:BIO_do_connectの後にノンブロッキングBIOとハングアップ

#include <stdio.h> 
#include <string.h> 
#include <openssl/bio.h> 
#include <openssl/ssl.h> 
#include <openssl/err.h> 

int main() { 
    SSL_load_error_strings(); 
    ERR_load_BIO_strings(); 
    OpenSSL_add_all_algorithms(); 

    BIO *bio; 
    SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method()); 
    SSL * ssl; 

    SSL_CTX_load_verify_locations(ctx, NULL, "/etc/ssl/certs/"); 
    bio = BIO_new_ssl_connect(ctx); 
    BIO_get_ssl(bio, & ssl); 
    SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); 
    BIO_set_nbio(bio, 1); 
    BIO_set_conn_hostname(bio, "irc.freenode.net:6697"); 
    BIO_do_connect(bio); 

    if(SSL_get_verify_result(ssl) != X509_V_OK) { 
     printf("error\n"); 
    } 

    char irc1[] = "NICK bartender\r\n"; 
    char irc2[] = "USER bartender * * :serve(&drinks);\r\n"; 

    BIO_write(bio, irc1, strlen(irc1)); 
    BIO_write(bio, irc2, strlen(irc2)); 

    fd_set read_set; 
    int sock = BIO_get_fd(bio, NULL); 

    while(1) { 
     FD_ZERO(&read_set); 
     FD_SET(sock, &read_set); 

     struct timeval timeout = { 0, 0 }; 
     select(sock+1, &read_set, NULL, NULL, &timeout); 

     if(FD_ISSET(sock, &read_set)) { 
      char buf[21]; 
      size_t x = BIO_read(bio, buf, 20); 

      if(x == 0) { 
       continue; 
      } else if(x == -1){ 
       int code = ERR_get_error(); 

       if(code == 0) { 
        continue; 
       } 

       printf("(%d)%s\n", code, ERR_error_string(code, NULL)); 
      } else { 
       buf[x] = '\0'; 
       printf("%s", buf); 
      } 
     } 
    } 
} 

このコードをコンパイルして実行すると、何も表示されず、何も印刷されません。しかし、20行目(現在はソケットを非ブロックモードにしています)を削除しても正常に動作します。ソケットを非ブロッキングモードにすると、この動作が起こるのはなぜですか?ありがとう、素晴らしい一日を!

+0

任意のソケットをJon-blockingモードにして、同じコードが動作し続けることを期待してください。より多くの作業が必要であり、SSLソケットのケースは非常に多いです。 OpenSSLのドキュメントを参照してください。広すぎます。 – EJP

+1

私は「BIOオブジェクトを非ブロッキングモードにする」方法が広すぎます。それ以上のことが必要であると確信しています。そうでなければ、それは働くでしょう。何をする必要がありますか?私はすでにopensslのマニュアルページを読んで、googleと以前のSOの回答に相談しました。私は私がする必要がある何かを見つけることができません。 – DTSCode

+1

もっと多くの帽子が必要ですが、* man select *とOpenSSLのドキュメントにはすべて記載されており、ここでは答えが広すぎます。 – EJP

答えて

1

このコードを実行すると、たびにハングアップして何も印刷されません。しかし、20行目(現在はソケットを非ブロックモードにしています)を削除しても正常に動作します。

BIO_do_connectは、直ちにノンブロッキングモードで戻る。 BIO_should_retryにループする必要があります。マニュアルページの内容は、BIO_do_connectです。

BIO_do_connect()は、指定されたBIOを接続しようとします。 が正常に確立された場合は1を返します。接続が確立できなかった場合は、0または負の値 が返されます。 BIO_should_retry()は、コールを再試行する必要があるかどうかを判断するために、接続していないBIOを使用する必要があります。


なぜこのような現象を発生させる非ブロックモードのソケットを入れていますか?

BIO_do_connectの呼び出しはすぐに戻ります。ソケット/バイオはおそらくデータの準備が整っていません(まだ)。


BIO_do_connect/BIO_should_retryにループする代わりに、基本となるファイルディスクリプタを待つことです。 ocspサブコマンドでのOpenSSLで使用されるその技術(ソースは<openssl src>/apps/ocsp.cで見つけることができます):

if (req_timeout != -1) 
    BIO_set_nbio(cbio, 1); 

rv = BIO_do_connect(cbio); 

if ((rv <= 0) && ((req_timeout == -1) || !BIO_should_retry(cbio))) { 
    BIO_puts(err, "Error connecting BIO\n"); 
    return NULL; 
} 

if (BIO_get_fd(cbio, &fd) < 0) { 
    BIO_puts(bio_err, "Can't get connection fd\n"); 
    goto err; 
} 

if (req_timeout != -1 && rv <= 0) { 
    FD_ZERO(&confds); 
    openssl_fdset(fd, &confds); 
    tv.tv_usec = 0; 
    tv.tv_sec = req_timeout; 
    rv = select(fd + 1, NULL, (void *)&confds, NULL, &tv); 
    if (rv == 0) { 
     BIO_puts(err, "Timeout on connect\n"); 
     return NULL; 
    } 
} 

はまた、メーリングリストのOpenSSLユーザーにNon-blocking BIO and BIO_do_connect problemを参照してください。そこfew hits on Stack Overflowもありますが、私はこの質問に最も適しているかわからないんだけど:

あなただけの変更はできません
+0

あなたのコードはあなたの見積もりに同意しません。 'BIO_should_retry()'が真である間にループする必要があります。 'BIO_do_cnnect()'が失敗している間にループすると、証明が間違っています。 – EJP

+0

@EJP - * "BIO_do_cnnect()が失敗している間にループしていると証明が間違っています...." * - あなたはOpenSSLの実践に慣れておくべきです。例えば、[Non-blocking BIOとBIO_do_connectの問題](http://openssl.6102.n7.nabble.com/Non-blocking-BIO-and-BIO-do-connect-problem-td6934.html)を参照してください。 OpenSSLメーリングリスト。ヘンソン博士はメンテナーの一人ですから、彼が何を言わなければならないかに特に注意を払ってください。 – jww

関連する問題