// // Copyright (c) 2013-2025 The SRS Authors // // SPDX-License-Identifier: MIT // #include #include #include using namespace std; #include #include #include #include #include #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_) { // this function will free bio_in_ and bio_out_ SSL_free(ssl_); ssl_ = NULL; } if (ssl_ctx_) { SSL_CTX_free(ssl_ctx_); ssl_ctx_ = NULL; } } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" srs_error_t SrsSslClient::handshake(const std::string &host) { srs_error_t err = srs_success; // For HTTPS, try to connect over security transport. #if (OPENSSL_VERSION_NUMBER < 0x10002000L) // v1.0.2 ssl_ctx_ = SSL_CTX_new(TLS_method()); #else ssl_ctx_ = SSL_CTX_new(TLSv1_2_method()); #endif 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_TLS_HANDSHAKE, "SSL_new ssl"); } if ((bio_in_ = BIO_new(BIO_s_mem())) == NULL) { return srs_error_new(ERROR_TLS_HANDSHAKE, "BIO_new in"); } if ((bio_out_ = BIO_new(BIO_s_mem())) == NULL) { BIO_free(bio_in_); return srs_error_new(ERROR_TLS_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); // If the server address is not in IP address format, set the host in the Server Name Indication (SNI) field. if (!srs_net_is_valid_ip(host)) { SSL_set_tlsext_host_name(ssl_, host.c_str()); } // Send ClientHello. int r0 = SSL_do_handshake(ssl_); int r1 = SSL_get_error(ssl_, r0); ERR_clear_error(); if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { return srs_error_new(ERROR_TLS_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_TLS_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_TLS_HANDSHAKE, "BIO_reset r0=%d", r0); } srs_info("tls: 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_TLS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); } r0 = SSL_do_handshake(ssl_); r1 = SSL_get_error(ssl_, r0); ERR_clear_error(); if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { return srs_error_new(ERROR_TLS_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_TLS_HANDSHAKE, "BIO_reset r0=%d", r0); } break; } } srs_info("tls: 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_TLS_HANDSHAKE, "BIO_reset r0=%d", r0); } srs_info("tls: 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_TLS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); } r0 = SSL_do_handshake(ssl_); r1 = SSL_get_error(ssl_, r0); ERR_clear_error(); if (r0 == 1 && r1 == SSL_ERROR_NONE) { break; } if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { return srs_error_new(ERROR_TLS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); } } srs_info("tls: Server done"); return err; } #pragma GCC diagnostic pop srs_error_t SrsSslClient::read(void *plaintext, size_t nn_plaintext, ssize_t *nread) { srs_error_t err = srs_success; while (true) { int r0 = SSL_read(ssl_, plaintext, nn_plaintext); int r1 = SSL_get_error(ssl_, r0); ERR_clear_error(); int r2 = BIO_ctrl_pending(bio_in_); int r3 = SSL_is_init_finished(ssl_); // OK, got data. if (r0 > 0) { srs_assert(r0 <= (int)nn_plaintext); if (nread) { *nread = r0; } return err; } // Need to read more data to feed SSL. if (r0 == -1 && r1 == SSL_ERROR_WANT_READ) { // TODO: Can we avoid copy? int nn_cipher = nn_plaintext; SrsUniquePtr cipher(new char[nn_cipher]); // Read the cipher from SSL. ssize_t nn = 0; if ((err = transport_->read(cipher.get(), nn_cipher, &nn)) != srs_success) { return srs_error_wrap(err, "https: read"); } int r0 = BIO_write(bio_in_, cipher.get(), nn); if (r0 <= 0) { // TODO: 0 or -1 maybe block, use BIO_should_retry to check. return srs_error_new(ERROR_TLS_READ, "BIO_write r0=%d, cipher=%p, size=%d", r0, cipher.get(), nn); } continue; } // Fail for error. if (r0 <= 0) { return srs_error_new(ERROR_TLS_READ, "SSL_read r0=%d, r1=%d, r2=%d, r3=%d", r0, r1, r2, r3); } } } 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); ERR_clear_error(); if (r0 <= 0) { return srs_error_new(ERROR_TLS_WRITE, "tls: 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, "tls: write data=%p, size=%d", data, size); } if ((r0 = BIO_reset(bio_out_)) != 1) { return srs_error_new(ERROR_TLS_WRITE, "BIO_reset r0=%d", r0); } } return err; } ISrsHttpClient::ISrsHttpClient() { } ISrsHttpClient::~ISrsHttpClient() { } SrsHttpClient::SrsHttpClient() { transport_ = NULL; ssl_transport_ = NULL; kbps_ = new SrsNetworkKbps(); parser_ = NULL; recv_timeout_ = timeout_ = SRS_UTIME_NO_TIMEOUT; port_ = 0; } SrsHttpClient::~SrsHttpClient() { disconnect(); srs_freep(kbps_); srs_freep(parser_); } srs_error_t SrsHttpClient::initialize(string schema, string h, int p, srs_utime_t tm) { srs_error_t err = srs_success; srs_freep(parser_); parser_ = new SrsHttpParser(); if ((err = parser_->initialize(HTTP_RESPONSE)) != srs_success) { return srs_error_wrap(err, "http: init parser"); } // Always disconnect the transport. schema_ = schema; host_ = h; port_ = p; recv_timeout_ = timeout_ = tm; disconnect(); // ep used for host in header. string ep = host_; if (port_ > 0 && port_ != SRS_CONSTS_HTTP_DEFAULT_PORT) { ep += ":" + srs_strconv_format_int(port_); } // Set default value for headers. headers_["Host"] = ep; headers_["Connection"] = "Keep-Alive"; headers_["User-Agent"] = RTMP_SIG_SRS_SERVER; headers_["Content-Type"] = "application/json"; return err; } SrsHttpClient *SrsHttpClient::set_header(string k, string v) { headers_[k] = v; return this; } srs_error_t SrsHttpClient::post(string path, string req, ISrsHttpMessage **ppmsg) { *ppmsg = NULL; srs_error_t err = srs_success; // always set the content length. headers_["Content-Length"] = srs_strconv_format_int(req.length()); if ((err = connect()) != srs_success) { return srs_error_wrap(err, "http: connect server"); } if (path.size() == 0) { path = "/"; } // TODO: FIXME: Use SrsHttpMessageWriter, never use stringstream and headers_. // send POST request to uri // POST %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s std::stringstream ss; ss << "POST " << path << " " << "HTTP/1.1" << SRS_HTTP_CRLF; for (map::iterator it = headers_.begin(); it != headers_.end(); ++it) { string key = it->first; string value = it->second; ss << key << ": " << value << SRS_HTTP_CRLF; } ss << SRS_HTTP_CRLF << req; std::string data = ss.str(); 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(reader(), &msg)) != srs_success) { return srs_error_wrap(err, "http: parse response"); } srs_assert(msg); if (ppmsg) { *ppmsg = msg; } else { srs_freep(msg); } return err; } srs_error_t SrsHttpClient::get(string path, string req, ISrsHttpMessage **ppmsg) { *ppmsg = NULL; srs_error_t err = srs_success; // always set the content length. headers_["Content-Length"] = srs_strconv_format_int(req.length()); if ((err = connect()) != srs_success) { return srs_error_wrap(err, "http: connect server"); } // send POST request to uri // GET %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s std::stringstream ss; ss << "GET " << path << " " << "HTTP/1.1" << SRS_HTTP_CRLF; for (map::iterator it = headers_.begin(); it != headers_.end(); ++it) { string key = it->first; string value = it->second; ss << key << ": " << value << SRS_HTTP_CRLF; } ss << SRS_HTTP_CRLF << req; std::string data = ss.str(); 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(reader(), &msg)) != srs_success) { return srs_error_wrap(err, "http: parse response"); } srs_assert(msg); if (ppmsg) { *ppmsg = msg; } else { srs_freep(msg); } return err; } void SrsHttpClient::set_recv_timeout(srs_utime_t tm) { recv_timeout_ = tm; } void SrsHttpClient::kbps_sample(const char *label, srs_utime_t age) { kbps_->sample(); int sr = kbps_->get_send_kbps(); int sr30s = kbps_->get_send_kbps_30s(); int sr5m = kbps_->get_send_kbps_5m(); int rr = kbps_->get_recv_kbps(); int rr30s = kbps_->get_recv_kbps_30s(); int rr5m = kbps_->get_recv_kbps_5m(); srs_trace("<- %s time=%" PRId64 ", okbps=%d,%d,%d, ikbps=%d,%d,%d", label, srsu2ms(age), sr, sr30s, sr5m, rr, rr30s, rr5m); } void SrsHttpClient::disconnect() { kbps_->set_io(NULL, NULL); srs_freep(ssl_transport_); srs_freep(transport_); } srs_error_t SrsHttpClient::connect() { srs_error_t err = srs_success; // When transport connected, ignore. if (transport_) { return err; } transport_ = new SrsTcpClient(host_, port_, timeout_); if ((err = transport_->connect()) != srs_success) { disconnect(); 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. transport_->set_recv_timeout(recv_timeout_); 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_); srs_utime_t starttime = srs_time_now_realtime(); if ((err = ssl_transport_->handshake(host_)) != 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_)); } int cost = srsu2msi(srs_time_now_realtime() - starttime); srs_trace("https: connected to %s://%s:%d, cost=%dms", schema_.c_str(), host_.c_str(), port_, cost); 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_; }