diff --git a/README.md b/README.md index 5a1fc5fcae..bd1f04ffbf 100755 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ For previous versions, please read: ## V4 changes +* v4.0, 2020-11-03, For [#1657][bug #1657-1], support HTTPS client, for http-callback. 4.0.45 * v4.0, 2020-10-31, Support gdb/srs.py to stat coroutines. 4.0.44 * v4.0, 2020-09-19, RTC: Extract resource manager. Use any UDP packet to keep alive. 4.0.43 * v4.0, 2020-09-09, RTC: Refine NACK RTT and efficiency. 4.0.42 @@ -1781,6 +1782,7 @@ Winlin [bug #1636]: https://github.com/ossrs/srs/issues/1636 [bug #1657]: https://github.com/ossrs/srs/issues/1657 [bug #1830]: https://github.com/ossrs/srs/issues/1830 +[bug #1657-1]: https://github.com/ossrs/srs/issues/1657#issuecomment-720889906 [bug #zzzzzzzzzzzzz]: https://github.com/ossrs/srs/issues/zzzzzzzzzzzzz [exo #828]: https://github.com/google/ExoPlayer/pull/828 diff --git a/trunk/auto/auto_headers.sh b/trunk/auto/auto_headers.sh index 60c00db9a6..00e7b9b040 100755 --- a/trunk/auto/auto_headers.sh +++ b/trunk/auto/auto_headers.sh @@ -105,6 +105,12 @@ else srs_undefine_macro "SRS_GB28181" $SRS_AUTO_HEADERS_H fi +if [ $SRS_HTTPS = YES ]; then + srs_define_macro "SRS_HTTPS" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_HTTPS" $SRS_AUTO_HEADERS_H +fi + if [ $SRS_MEM_WATCH = YES ]; then srs_define_macro "SRS_MEM_WATCH" $SRS_AUTO_HEADERS_H else diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh index 6aecb5ac25..b562fcbb16 100755 --- a/trunk/auto/options.sh +++ b/trunk/auto/options.sh @@ -35,6 +35,7 @@ SRS_GPROF=NO # Performance test: gprof SRS_STREAM_CASTER=YES SRS_INGEST=YES SRS_SSL=YES +SRS_HTTPS=NO SRS_STAT=YES SRS_TRANSCODE=YES SRS_HTTP_CALLBACK=YES @@ -145,6 +146,7 @@ Features: -h, --help Print this message and exit 0. --ssl=on|off Whether build the rtmp complex handshake, requires openssl-devel installed. + --https=on|off Whether enable HTTPS client and server. Default: off --hds=on|off Whether build the hds streaming, mux RTMP to F4M/F4V files. --stream-caster=on|off Whether build the stream caster to serve other stream over other protocol. --stat=on|off Whether build the the data statistic, for http api. @@ -272,6 +274,7 @@ function parse_user_option() { --with-ssl) SRS_SSL=YES ;; --ssl) if [[ $value == off ]]; then SRS_SSL=NO; else SRS_SSL=YES; fi ;; + --https) if [[ $value == off ]]; then SRS_HTTPS=NO; else SRS_HTTPS=YES; fi ;; --with-hds) SRS_HDS=YES ;; --without-hds) SRS_HDS=NO ;; @@ -526,6 +529,7 @@ function regenerate_options() { if [ $SRS_HDS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --hds=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --hds=off"; fi if [ $SRS_DVR = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --dvr=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --dvr=off"; fi if [ $SRS_SSL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ssl=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ssl=off"; fi + if [ $SRS_HTTPS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --https=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --https=off"; fi if [ $SRS_USE_SYS_SSL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --sys-ssl=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --sys-ssl=off"; fi if [ $SRS_TRANSCODE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --transcode=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --transcode=off"; fi if [ $SRS_INGEST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ingest=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ingest=off"; fi diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 809eb1cb28..33fe0c001f 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -1090,6 +1090,8 @@ vhost hooks.callback.srs.com { # 0 # support multiple api hooks, format: # on_connect http://xxx/api0 http://xxx/api1 http://xxx/apiN + # @remark For SRS4, the HTTPS url is supported, for example: + # on_connect https://xxx/api0 https://xxx/api1 https://xxx/apiN on_connect http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; # when client close/disconnect to vhost/app/stream, call the hook, # the request in the POST data string is a object encode by json: @@ -1104,6 +1106,8 @@ vhost hooks.callback.srs.com { # 0 # support multiple api hooks, format: # on_close http://xxx/api0 http://xxx/api1 http://xxx/apiN + # @remark For SRS4, the HTTPS url is supported, for example: + # on_close https://xxx/api0 https://xxx/api1 https://xxx/apiN on_close http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; # when client(encoder) publish to vhost/app/stream, call the hook, # the request in the POST data string is a object encode by json: @@ -1118,6 +1122,8 @@ vhost hooks.callback.srs.com { # 0 # support multiple api hooks, format: # on_publish http://xxx/api0 http://xxx/api1 http://xxx/apiN + # @remark For SRS4, the HTTPS url is supported, for example: + # on_publish https://xxx/api0 https://xxx/api1 https://xxx/apiN on_publish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; # when client(encoder) stop publish to vhost/app/stream, call the hook, # the request in the POST data string is a object encode by json: @@ -1132,6 +1138,8 @@ vhost hooks.callback.srs.com { # 0 # support multiple api hooks, format: # on_unpublish http://xxx/api0 http://xxx/api1 http://xxx/apiN + # @remark For SRS4, the HTTPS url is supported, for example: + # on_unpublish https://xxx/api0 https://xxx/api1 https://xxx/apiN on_unpublish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; # when client start to play vhost/app/stream, call the hook, # the request in the POST data string is a object encode by json: @@ -1147,6 +1155,8 @@ vhost hooks.callback.srs.com { # 0 # support multiple api hooks, format: # on_play http://xxx/api0 http://xxx/api1 http://xxx/apiN + # @remark For SRS4, the HTTPS url is supported, for example: + # on_play https://xxx/api0 https://xxx/api1 https://xxx/apiN on_play http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; # when client stop to play vhost/app/stream, call the hook, # the request in the POST data string is a object encode by json: @@ -1161,6 +1171,8 @@ vhost hooks.callback.srs.com { # 0 # support multiple api hooks, format: # on_stop http://xxx/api0 http://xxx/api1 http://xxx/apiN + # @remark For SRS4, the HTTPS url is supported, for example: + # on_stop https://xxx/api0 https://xxx/api1 https://xxx/apiN on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; # when srs reap a dvr file, call the hook, # the request in the POST data string is a object encode by json: diff --git a/trunk/configure b/trunk/configure index 72b3e80737..bf358a3826 100755 --- a/trunk/configure +++ b/trunk/configure @@ -699,6 +699,11 @@ if [ $SRS_RTC = YES ]; then else echo -e "${GREEN}Warning: RTC is disabled.${BLACK}" fi +if [ $SRS_HTTPS = YES ]; then + echo -e "${YELLOW}Experiment: HTTPS is enabled. https://github.com/ossrs/srs/issues/1657${BLACK}" +else + echo -e "${GREEN}Warning: HTTPS is disabled.${BLACK}" +fi if [ $SRS_DVR = YES ]; then echo -e "${GREEN}DVR is enabled.${BLACK}" else diff --git a/trunk/src/app/srs_app_heartbeat.cpp b/trunk/src/app/srs_app_heartbeat.cpp index 8463e18a68..f193d71b02 100644 --- a/trunk/src/app/srs_app_heartbeat.cpp +++ b/trunk/src/app/srs_app_heartbeat.cpp @@ -87,7 +87,7 @@ srs_error_t SrsHttpHeartbeat::do_heartbeat() } SrsHttpClient http; - if ((err = http.initialize(uri.get_host(), uri.get_port())) != srs_success) { + if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) { return srs_error_wrap(err, "init uri=%s", uri.get_url().c_str()); } diff --git a/trunk/src/app/srs_app_http_hooks.cpp b/trunk/src/app/srs_app_http_hooks.cpp index 61dd72348d..d899570e2e 100644 --- a/trunk/src/app/srs_app_http_hooks.cpp +++ b/trunk/src/app/srs_app_http_hooks.cpp @@ -371,7 +371,7 @@ srs_error_t SrsHttpHooks::on_hls_notify(SrsContextId c, std::string url, SrsRequ } SrsHttpClient http; - if ((err = http.initialize(uri.get_host(), uri.get_port(), SRS_HLS_NOTIFY_TIMEOUT)) != srs_success) { + if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port(), SRS_HLS_NOTIFY_TIMEOUT)) != srs_success) { return srs_error_wrap(err, "http: init client for %s", url.c_str()); } @@ -478,7 +478,7 @@ srs_error_t SrsHttpHooks::do_post(SrsHttpClient* hc, std::string url, std::strin return srs_error_wrap(err, "http: post failed. url=%s", url.c_str()); } - if ((err = hc->initialize(uri.get_host(), uri.get_port())) != srs_success) { + if ((err = hc->initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) { return srs_error_wrap(err, "http: init client"); } diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp index 2f78b29b9a..90305fccdb 100644 --- a/trunk/src/app/srs_app_rtc_dtls.cpp +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -40,20 +40,8 @@ using namespace std; #include #include -// The return value of verify_callback controls the strategy of the further verification process. If verify_callback -// returns 0, the verification process is immediately stopped with "verification failed" state. If SSL_VERIFY_PEER is -// set, a verification failure alert is sent to the peer and the TLS/SSL handshake is terminated. If verify_callback -// returns 1, the verification process is continued. If verify_callback always returns 1, the TLS/SSL handshake will -// not be terminated with respect to verification failures and the connection will be established. The calling process -// can however retrieve the error code of the last verification error using SSL_get_verify_result(3) or by maintaining -// its own error storage managed by verify_callback. -// @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html -int srs_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) -{ - // Always OK, we don't check the certificate of client, - // because we allow client self-sign certificate. - return 1; -} +// Defined in HTTP/HTTPS client. +extern int srs_verify_callback(int preverify_ok, X509_STORE_CTX *ctx); // Print the information of SSL, DTLS alert as such. void ssl_on_info(const SSL* dtls, int where, int ret) @@ -213,6 +201,11 @@ srs_error_t SrsDtlsCertificate::initialize() // @see https://www.openssl.org/docs/man1.1.0/man3/OpenSSL_add_ssl_algorithms.html // @see https://web.archive.org/web/20150806185102/http://sctp.fh-muenster.de:80/dtls/dtls_udp_echo.c OpenSSL_add_ssl_algorithms(); +#else + // As of version 1.1.0 OpenSSL will automatically allocate all resources that it needs so no explicit + // initialisation is required. Similarly it will also automatically deinitialise as required. + // @see https://www.openssl.org/docs/man1.1.0/man3/OPENSSL_init_ssl.html + // OPENSSL_init_ssl(); #endif // Initialize SRTP first. @@ -456,6 +449,7 @@ srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) srs_error_t err = srs_success; int r0 = 0; + // TODO: FIXME: Why reset it before writing? if ((r0 = BIO_reset(bio_in)) != 1) { return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset r0=%d", r0); } diff --git a/trunk/src/core/srs_core_version4.hpp b/trunk/src/core/srs_core_version4.hpp index c736bd1977..2ab680a522 100644 --- a/trunk/src/core/srs_core_version4.hpp +++ b/trunk/src/core/srs_core_version4.hpp @@ -24,6 +24,6 @@ #ifndef SRS_CORE_VERSION4_HPP #define SRS_CORE_VERSION4_HPP -#define SRS_VERSION4_REVISION 44 +#define SRS_VERSION4_REVISION 45 #endif diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 80a43cf689..e0b8288f5d 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -324,6 +324,10 @@ #define ERROR_HTTP_302_INVALID 4038 #define ERROR_BASE64_DECODE 4039 #define ERROR_HTTP_STREAM_EOF 4040 +#define ERROR_HTTPS_NOT_SUPPORTED 4041 +#define ERROR_HTTPS_HANDSHAKE 4042 +#define ERROR_HTTPS_READ 4043 +#define ERROR_HTTPS_WRITE 4044 /////////////////////////////////////////////////////// // RTC protocol error. diff --git a/trunk/src/main/srs_main_ingest_hls.cpp b/trunk/src/main/srs_main_ingest_hls.cpp index 91895f422d..aabae1635d 100644 --- a/trunk/src/main/srs_main_ingest_hls.cpp +++ b/trunk/src/main/srs_main_ingest_hls.cpp @@ -370,7 +370,7 @@ int SrsIngestHlsInput::parseM3u8(SrsHttpUri* url, double& td, double& duration) SrsHttpClient client; srs_trace("parse input hls %s", url->get_url().c_str()); - if ((err = client.initialize(url->get_host(), url->get_port())) != srs_success) { + if ((err = client.initialize(url->get_schema(), url->get_host(), url->get_port())) != srs_success) { // TODO: FIXME: Use error ret = srs_error_code(err); srs_freep(err); @@ -609,7 +609,7 @@ int SrsIngestHlsInput::SrsTsPiece::fetch(string m3u8) } // initialize the fresh http client. - if ((ret = client.initialize(uri.get_host(), uri.get_port()) != ERROR_SUCCESS)) { + if ((ret = client.initialize(uri.get_schema(), uri.get_host(), uri.get_port()) != ERROR_SUCCESS)) { return ret; } diff --git a/trunk/src/protocol/srs_service_http_client.cpp b/trunk/src/protocol/srs_service_http_client.cpp index 527aa4f43f..2b9e717f46 100644 --- a/trunk/src/protocol/srs_service_http_client.cpp +++ b/trunk/src/protocol/srs_service_http_client.cpp @@ -35,9 +35,225 @@ using namespace std; #include #include +// The return value of verify_callback controls the strategy of the further verification process. If verify_callback +// returns 0, the verification process is immediately stopped with "verification failed" state. If SSL_VERIFY_PEER is +// set, a verification failure alert is sent to the peer and the TLS/SSL handshake is terminated. If verify_callback +// returns 1, the verification process is continued. If verify_callback always returns 1, the TLS/SSL handshake will +// not be terminated with respect to verification failures and the connection will be established. The calling process +// can however retrieve the error code of the last verification error using SSL_get_verify_result(3) or by maintaining +// its own error storage managed by verify_callback. +// @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html +int srs_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + // Always OK, we don't check the certificate of client, + // because we allow client self-sign certificate. + return 1; +} + +SrsSslClient::SrsSslClient(SrsTcpClient* tcp) +{ + transport = tcp; + ssl_ctx = NULL; + ssl = NULL; +} + +SrsSslClient::~SrsSslClient() +{ + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + ssl_ctx = NULL; + } + + if (ssl) { + // this function will free bio_in and bio_out + SSL_free(ssl); + ssl = NULL; + } +} + +srs_error_t SrsSslClient::handshake() +{ + srs_error_t err = srs_success; + + // For HTTPS, try to connect over security transport. + SSL_CTX* ssl_ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, srs_verify_callback); + srs_assert(SSL_CTX_set_cipher_list(ssl_ctx, "ALL") == 1); + + // TODO: Setup callback, see SSL_set_ex_data and SSL_set_info_callback + if ((ssl = SSL_new(ssl_ctx)) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "SSL_new ssl"); + } + + if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new in"); + } + + if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { + BIO_free(bio_in); + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new out"); + } + + SSL_set_bio(ssl, bio_in, bio_out); + + // SSL setup active, as client role. + SSL_set_connect_state(ssl); + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + + // Send ClientHello. + int r0 = SSL_do_handshake(ssl); int r1 = SSL_get_error(ssl, r0); + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + if (!data || size <= 0) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake data=%p, size=%d", data, size); + } + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_trace("https: ClientHello done"); + + // Receive ServerHello, Certificate, Server Key Exchange, Server Hello Done + while (true) { + char buf[512]; ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + if ((r0 = SSL_do_handshake(ssl)) != -1 || (r1 = SSL_get_error(ssl, r0)) != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + if ((size = BIO_get_mem_data(bio_out, &data)) > 0) { + // OK, reset it for the next write. + if ((r0 = BIO_reset(bio_in)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + break; + } + } + + srs_trace("https: ServerHello done"); + + // Send Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_trace("https: Client done"); + + // Receive New Session Ticket, Change Cipher Spec, Encrypted Handshake Message + while (true) { + char buf[128]; + ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + r0 = SSL_do_handshake(ssl); r1 = SSL_get_error(ssl, r0); + if (r0 == 1 && r1 == SSL_ERROR_NONE) { + break; + } + + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + } + + srs_trace("https: Server done"); + + return err; +} + +srs_error_t SrsSslClient::read(void* plaintext, size_t nn_plaintext, ssize_t* nread) +{ + srs_error_t err = srs_success; + + // TODO: Can we avoid copy? + int nn_cipher = nn_plaintext; + char* cipher = new char[nn_cipher]; + SrsAutoFreeA(char, cipher); + + ssize_t nn = 0; + // Read the cipher from SSL. + if ((err = transport->read(cipher, nn_cipher, &nn)) != srs_success) { + return srs_error_wrap(err, "https: read"); + } + + int r0 = BIO_write(bio_in, cipher, nn); + if (r0 <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_READ, "BIO_write r0=%d, cipher=%p, size=%d", r0, cipher, nn); + } + + r0 = SSL_read(ssl, plaintext, nn); + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_READ, "SSL_read r0=%d, cipher=%p, size=%d", r0, cipher, nn); + } + + srs_assert(r0 <= nn_plaintext); + if (nread) { + *nread = r0; + } + + return err; +} + +srs_error_t SrsSslClient::write(void* plaintext, size_t nn_plaintext, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + for (char* p = (char*)plaintext; p < (char*)plaintext + nn_plaintext;) { + int left = (int)nn_plaintext - (p - (char*)plaintext); + int r0 = SSL_write(ssl, (const void*)p, left); + int r1 = SSL_get_error(ssl, r0); + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_WRITE, "https: write data=%p, size=%d, r0=%d, r1=%d", p, left, r0, r1); + } + + // Move p to the next writing position. + p += r0; + if (nwrite) { + *nwrite += (ssize_t)r0; + } + + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "https: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_WRITE, "BIO_reset r0=%d", r0); + } + } + + return err; +} + SrsHttpClient::SrsHttpClient() { transport = NULL; + ssl_transport = NULL; clk = new SrsWallClock(); kbps = new SrsKbps(clk); parser = NULL; @@ -54,7 +270,7 @@ SrsHttpClient::~SrsHttpClient() srs_freep(parser); } -srs_error_t SrsHttpClient::initialize(string h, int p, srs_utime_t tm) +srs_error_t SrsHttpClient::initialize(string schema, string h, int p, srs_utime_t tm) { srs_error_t err = srs_success; @@ -66,6 +282,7 @@ srs_error_t SrsHttpClient::initialize(string h, int p, srs_utime_t tm) } // Always disconnect the transport. + schema_ = schema; host = h; port = p; recv_timeout = timeout = tm; @@ -116,16 +333,16 @@ srs_error_t SrsHttpClient::post(string path, string req, ISrsHttpMessage** ppmsg ss << key << ": " << value << SRS_HTTP_CRLF; } ss << SRS_HTTP_CRLF << req; - + std::string data = ss.str(); - if ((err = transport->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { + if ((err = writer()->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { // Disconnect the transport when channel error, reconnect for next operation. disconnect(); return srs_error_wrap(err, "http: write"); } ISrsHttpMessage* msg = NULL; - if ((err = parser->parse_message(transport, &msg)) != srs_success) { + if ((err = parser->parse_message(reader(), &msg)) != srs_success) { return srs_error_wrap(err, "http: parse response"); } srs_assert(msg); @@ -164,14 +381,14 @@ srs_error_t SrsHttpClient::get(string path, string req, ISrsHttpMessage** ppmsg) ss << SRS_HTTP_CRLF << req; std::string data = ss.str(); - if ((err = transport->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { + if ((err = writer()->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { // Disconnect the transport when channel error, reconnect for next operation. disconnect(); return srs_error_wrap(err, "http: write"); } ISrsHttpMessage* msg = NULL; - if ((err = parser->parse_message(transport, &msg)) != srs_success) { + if ((err = parser->parse_message(reader(), &msg)) != srs_success) { return srs_error_wrap(err, "http: parse response"); } srs_assert(msg); @@ -207,6 +424,7 @@ void SrsHttpClient::kbps_sample(const char* label, int64_t age) void SrsHttpClient::disconnect() { kbps->set_io(NULL, NULL); + srs_freep(ssl_transport); srs_freep(transport); } @@ -222,8 +440,8 @@ srs_error_t SrsHttpClient::connect() transport = new SrsTcpClient(host, port, timeout); if ((err = transport->connect()) != srs_success) { disconnect(); - return srs_error_wrap(err, "http: tcp connect %s:%d to=%dms, rto=%dms", - host.c_str(), port, srsu2msi(timeout), srsu2msi(recv_timeout)); + return srs_error_wrap(err, "http: tcp connect %s %s:%d to=%dms, rto=%dms", + schema_.c_str(), host.c_str(), port, srsu2msi(timeout), srsu2msi(recv_timeout)); } // Set the recv/send timeout in srs_utime_t. @@ -231,7 +449,40 @@ srs_error_t SrsHttpClient::connect() transport->set_send_timeout(timeout); kbps->set_io(transport, transport); - + + if (schema_ != "https") { + return err; + } + +#if !defined(SRS_HTTPS) + return srs_error_new(ERROR_HTTPS_NOT_SUPPORTED, "should configure with --https=on"); +#else + srs_assert(!ssl_transport); + ssl_transport = new SrsSslClient(transport); + + if ((err = ssl_transport->handshake()) != srs_success) { + disconnect(); + return srs_error_wrap(err, "http: ssl connect %s %s:%d to=%dms, rto=%dms", + schema_.c_str(), host.c_str(), port, srsu2msi(timeout), srsu2msi(recv_timeout)); + } + return err; +#endif +} + +ISrsStreamWriter* SrsHttpClient::writer() +{ + if (ssl_transport) { + return ssl_transport; + } + return transport; +} + +ISrsReader* SrsHttpClient::reader() +{ + if (ssl_transport) { + return ssl_transport; + } + return transport; } diff --git a/trunk/src/protocol/srs_service_http_client.hpp b/trunk/src/protocol/srs_service_http_client.hpp index 12193809e3..3c61221644 100644 --- a/trunk/src/protocol/srs_service_http_client.hpp +++ b/trunk/src/protocol/srs_service_http_client.hpp @@ -29,6 +29,14 @@ #include #include +#include + +#ifdef SRS_HTTPS +#if (OPENSSL_VERSION_NUMBER < 0x10002000L) // v1.0.2 + #error "For https, we requires openssl 1.0.2+" +#endif +#endif + #include #include @@ -43,6 +51,26 @@ class SrsTcpClient; // The default timeout for http client. #define SRS_HTTP_CLIENT_TIMEOUT (30 * SRS_UTIME_SECONDS) +// The SSL client over TCP transport. +class SrsSslClient : virtual public ISrsReader, virtual public ISrsStreamWriter +{ +private: + SrsTcpClient* transport; +private: + SSL_CTX* ssl_ctx; + SSL* ssl; + BIO* bio_in; + BIO* bio_out; +public: + SrsSslClient(SrsTcpClient* tcp); + virtual ~SrsSslClient(); +public: + virtual srs_error_t handshake(); +public: + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); + virtual srs_error_t write(void* buf, size_t size, ssize_t* nwrite); +}; + // The client to GET/POST/PUT/DELETE over HTTP. // @remark We will reuse the TCP transport until initialize or channel error, // such as send/recv failed. @@ -64,17 +92,21 @@ class SrsHttpClient // The timeout in srs_utime_t. srs_utime_t timeout; srs_utime_t recv_timeout; - // The host name or ip. + // The schema, host name or ip. + std::string schema_; std::string host; int port; +private: + SrsSslClient* ssl_transport; public: SrsHttpClient(); virtual ~SrsHttpClient(); public: // Initliaze the client, disconnect the transport, renew the HTTP parser. + // @param schema Should be http or https. // @param tm The underlayer TCP transport timeout in srs_utime_t. // @remark we will set default values in headers, which can be override by set_header. - virtual srs_error_t initialize(std::string h, int p, srs_utime_t tm = SRS_HTTP_CLIENT_TIMEOUT); + virtual srs_error_t initialize(std::string schema, std::string h, int p, srs_utime_t tm = SRS_HTTP_CLIENT_TIMEOUT); // Set HTTP request header in header[k]=v. // @return the HTTP client itself. virtual SrsHttpClient* set_header(std::string k, std::string v); @@ -98,6 +130,8 @@ class SrsHttpClient private: virtual void disconnect(); virtual srs_error_t connect(); + ISrsStreamWriter* writer(); + ISrsReader* reader(); }; #endif