1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2025-08-10 06:10:52 +02:00

avformat/whip: Add WHIP muxer support for subsecond latency streaming

0. WHIP Version 3.
1. The WHIP muxer has been renamed and refined,
    with improved logging context and error messages for SSL, DTLS, and RTC.
2. Magic numbers have been replaced with macros and extracted to functions,
    and log levels have been altered for better clarity.
3. DTLS curve list has been updated,
    and SRTP profile names have been refined for FFmpeg and OpenSSL.
4. ICE STUN magic number has been refined,
    and RTP payload types have been updated based on Chrome's definition.
5. Fixed frame size has been refined to rtc->audio_par->frame_size,
    and h264_mp4toannexb is now used to convert MP4/ISOM to annexb.
6. OPUS timestamp issue has been addressed,
    and marker setting has been corrected after utilizing BSF.
7. DTLS handshake and ICE handling have been optimized for improved performance,
    with a single handshake timeout and server role to prevent ARQ.
8. Consolidated ICE request/response handling and DTLS handshake into a single function,
    and fixed OpenSSL build errors to work with Pion.
9. Merge TLS & DTLS implementation, shared BIO callbacks, read, write,
    print_ssl_error, openssl_init_ca_key_cert,
    init_bio_method function and shared same data structure
10. Modify configure that whip is enabled only dtls is
    enabled(just support openssl for now) to fix build error

Co-authored-by: winlin <winlinvip@gmail.com>
Co-authored-by: yangrtc <yangrtc@aliyun.com>
Co-authored-by: cloudwebrtc <duanweiwei1982@gmail.com>
Co-authored-by: Haibo Chen <495810242@qq.com>
Co-authored-by: Steven Liu <lq@chinaffmpeg.org>
Co-authored-by: Jun Zhao <barryjzhao@tencent.com>
Signed-off-by: Jack Lau <jacklau1222@qq.com>
Signed-off-by: Steven Liu <lq@chinaffmpeg.org>
This commit is contained in:
Jack Lau
2025-05-16 20:15:05 +08:00
committed by Steven Liu
parent d4556c98f0
commit 167e343bbe
13 changed files with 2925 additions and 56 deletions

12
configure vendored
View File

@@ -3749,6 +3749,7 @@ wav_demuxer_select="riffdec"
wav_muxer_select="riffenc"
webm_chunk_muxer_select="webm_muxer"
webm_dash_manifest_demuxer_select="matroska_demuxer"
whip_muxer_deps_any="dtls_protocol"
wtv_demuxer_select="mpegts_demuxer riffdec"
wtv_muxer_select="mpegts_muxer riffenc"
xmv_demuxer_select="riffdec"
@@ -3847,6 +3848,9 @@ srtp_protocol_select="rtp_protocol srtp"
tcp_protocol_select="network"
tls_protocol_deps_any="gnutls openssl schannel securetransport libtls mbedtls"
tls_protocol_select="tcp_protocol"
# TODO: Support libtls, mbedtls, and gnutls.
dtls_protocol_deps_any="openssl"
dtls_protocol_select="udp_protocol"
udp_protocol_select="network"
udplite_protocol_select="network"
unix_protocol_deps="sys_un_h"
@@ -7198,6 +7202,14 @@ enabled rkmpp && { require_pkg_config rkmpp rockchip_mpp rockchip/r
}
enabled vapoursynth && require_headers "vapoursynth/VSScript4.h vapoursynth/VapourSynth4.h"
enabled openssl && {
enabled whip_muxer && {
$pkg_config --exists --print-errors "openssl >= 1.0.1k" ||
require_pkg_config openssl "openssl >= 1.0.1k" openssl/ssl.h SSL_library_init ||
require_pkg_config openssl "openssl >= 1.0.1k" openssl/ssl.h OPENSSL_init_ssl
}
}
if enabled gcrypt; then
GCRYPT_CONFIG="${cross_prefix}libgcrypt-config"

View File

@@ -3879,4 +3879,51 @@ ffmpeg -f webm_dash_manifest -i video1.webm \
manifest.xml
@end example
@anchor{whip}
@section whip
WebRTC (Real-Time Communication) muxer that supports sub-second latency streaming according to
the WHIP (WebRTC-HTTP ingestion protocol) specification.
It uses HTTP as a signaling protocol to exchange SDP capabilities and ICE lite candidates. Then,
it uses STUN binding requests and responses to establish a session over UDP. Subsequently, it
initiates a DTLS handshake to exchange the SRTP encryption keys. Lastly, it splits video and
audio frames into RTP packets and encrypts them using SRTP.
Ensure that you use H.264 without B frames and Opus for the audio codec. For example, to convert
an input file with @command{ffmpeg} to WebRTC:
@example
ffmpeg -re -i input.mp4 -acodec libopus -ar 48000 -ac 2 \
-vcodec libx264 -profile:v baseline -tune zerolatency -threads 1 -bf 0 \
-f whip "http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream"
@end example
For this example, we have employed low latency options, resulting in an end-to-end latency of
approximately 150ms.
@subsection Options
This muxer supports the following options:
@table @option
@item handshake_timeout @var{integer}
Set the timeout in milliseconds for ICE and DTLS handshake.
Default value is 5000.
@item pkt_size @var{integer}
Set the maximum size, in bytes, of RTP packets that send out.
Default value is 1500.
@item authorization @var{string}
The optional Bearer token for WHIP Authorization.
@item cert_file @var{string}
The optional certificate file path for DTLS.
@item key_file @var{string}
The optional private key file path for DTLS.
@end table
@c man end MUXERS

View File

@@ -638,6 +638,7 @@ OBJS-$(CONFIG_WEBM_CHUNK_MUXER) += webm_chunk.o
OBJS-$(CONFIG_WEBP_MUXER) += webpenc.o
OBJS-$(CONFIG_WEBVTT_DEMUXER) += webvttdec.o subtitles.o
OBJS-$(CONFIG_WEBVTT_MUXER) += webvttenc.o
OBJS-$(CONFIG_WHIP_MUXER) += whip.o avc.o http.o srtp.o tls_openssl.o
OBJS-$(CONFIG_WSAUD_DEMUXER) += westwood_aud.o
OBJS-$(CONFIG_WSAUD_MUXER) += westwood_audenc.o
OBJS-$(CONFIG_WSD_DEMUXER) += wsddec.o rawdec.o

View File

@@ -515,6 +515,7 @@ extern const FFOutputFormat ff_webp_muxer;
extern const FFInputFormat ff_webvtt_demuxer;
extern const FFOutputFormat ff_webvtt_muxer;
extern const FFInputFormat ff_wsaud_demuxer;
extern const FFOutputFormat ff_whip_muxer;
extern const FFOutputFormat ff_wsaud_muxer;
extern const FFInputFormat ff_wsd_demuxer;
extern const FFInputFormat ff_wsvqa_demuxer;

View File

@@ -339,8 +339,9 @@ static const struct URLProtocol *url_find_protocol(const char *filename)
}
}
av_freep(&protocols);
if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))
av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL) ||
av_strstart(filename, "dtls:", NULL))
av_log(NULL, AV_LOG_WARNING, "https or dtls protocol not found, recompile FFmpeg with "
"openssl, gnutls or securetransport enabled.\n");
return NULL;

View File

@@ -562,6 +562,12 @@ int ff_http_averror(int status_code, int default_averror)
return default_averror;
}
const char* ff_http_get_new_location(URLContext *h)
{
HTTPContext *s = h->priv_data;
return s->new_location;
}
static int http_write_reply(URLContext* h, int status_code)
{
int ret, body = 0, reply_code, message_len;

View File

@@ -62,4 +62,6 @@ int ff_http_do_new_request2(URLContext *h, const char *uri, AVDictionary **optio
int ff_http_averror(int status_code, int default_averror);
const char* ff_http_get_new_location(URLContext *h);
#endif /* AVFORMAT_HTTP_H */

View File

@@ -62,6 +62,7 @@ extern const URLProtocol ff_subfile_protocol;
extern const URLProtocol ff_tee_protocol;
extern const URLProtocol ff_tcp_protocol;
extern const URLProtocol ff_tls_protocol;
extern const URLProtocol ff_dtls_protocol;
extern const URLProtocol ff_udp_protocol;
extern const URLProtocol ff_udplite_protocol;
extern const URLProtocol ff_unix_protocol;

View File

@@ -27,7 +27,7 @@
struct AVAES;
struct AVHMAC;
struct SRTPContext {
typedef struct SRTPContext {
struct AVAES *aes;
struct AVHMAC *hmac;
int rtp_hmac_size, rtcp_hmac_size;
@@ -40,7 +40,7 @@ struct SRTPContext {
uint32_t roc;
uint32_t rtcp_index;
};
} SRTPContext;
int ff_srtp_set_crypto(struct SRTPContext *s, const char *suite,
const char *params);

View File

@@ -1,6 +1,7 @@
/*
* TLS/SSL Protocol
* TLS/DTLS/SSL Protocol
* Copyright (c) 2011 Martin Storsjo
* Copyright (c) 2025 Jack Lau
*
* This file is part of FFmpeg.
*
@@ -20,6 +21,7 @@
*/
#include "avformat.h"
#include "internal.h"
#include "network.h"
#include "os_support.h"
#include "url.h"
@@ -93,7 +95,7 @@ int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AV
c->listen = 1;
}
ff_url_join(buf, sizeof(buf), "tcp", NULL, c->underlying_host, port, "%s", p);
ff_url_join(buf, sizeof(buf), c->is_dtls ? "udp" : "tcp", NULL, c->underlying_host, port, "%s", p);
hints.ai_flags = AI_NUMERICHOST;
if (!getaddrinfo(c->underlying_host, NULL, &hints, &ai)) {
@@ -124,7 +126,65 @@ int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AV
}
freeenv_utf8(env_http_proxy);
return ffurl_open_whitelist(&c->tcp, buf, AVIO_FLAG_READ_WRITE,
&parent->interrupt_callback, options,
parent->protocol_whitelist, parent->protocol_blacklist, parent);
if (c->is_dtls) {
av_dict_set_int(options, "connect", 1, 0);
av_dict_set_int(options, "fifo_size", 0, 0);
/* Set the max packet size to the buffer size. */
av_dict_set_int(options, "pkt_size", c->mtu, 0);
}
ret = ffurl_open_whitelist(c->is_dtls ? &c->udp : &c->tcp, buf, AVIO_FLAG_READ_WRITE,
&parent->interrupt_callback, options,
parent->protocol_whitelist, parent->protocol_blacklist, parent);
if (c->is_dtls) {
if (ret < 0) {
av_log(c, AV_LOG_ERROR, "WHIP: Failed to connect udp://%s:%d\n", c->underlying_host, port);
return ret;
}
/* Make the socket non-blocking, set to READ and WRITE mode after connected */
ff_socket_nonblock(ffurl_get_file_handle(c->udp), 1);
c->udp->flags |= AVIO_FLAG_READ | AVIO_FLAG_NONBLOCK;
}
return ret;
}
/**
* Read all data from the given URL url and store it in the given buffer bp.
*/
int ff_url_read_all(const char *url, AVBPrint *bp)
{
int ret = 0;
AVDictionary *opts = NULL;
URLContext *uc = NULL;
char buf[MAX_URL_SIZE];
ret = ffurl_open_whitelist(&uc, url, AVIO_FLAG_READ, NULL, &opts, NULL, NULL, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to open url %s\n", url);
goto end;
}
while (1) {
ret = ffurl_read(uc, buf, sizeof(buf));
if (ret == AVERROR_EOF) {
/* Reset the error because we read all response as answer util EOF. */
ret = 0;
break;
}
if (ret <= 0) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to read from url=%s, key is %s\n", url, bp->str);
goto end;
}
av_bprintf(bp, "%.*s", ret, buf);
if (!av_bprint_is_complete(bp)) {
av_log(NULL, AV_LOG_ERROR, "TLS: Exceed max size %.*s, %s\n", ret, buf, bp->str);
ret = AVERROR(EIO);
goto end;
}
}
end:
ffurl_closep(&uc);
av_dict_free(&opts);
return ret;
}

View File

@@ -1,6 +1,7 @@
/*
* TLS/SSL Protocol
* TLS/DTLS/SSL Protocol
* Copyright (c) 2011 Martin Storsjo
* Copyright (c) 2025 Jack Lau
*
* This file is part of FFmpeg.
*
@@ -22,10 +23,27 @@
#ifndef AVFORMAT_TLS_H
#define AVFORMAT_TLS_H
#include "libavutil/bprint.h"
#include "libavutil/opt.h"
#include "url.h"
/**
* Maximum size limit of a certificate and private key size.
*/
#define MAX_CERTIFICATE_SIZE 8192
enum DTLSState {
DTLS_STATE_NONE,
/* Whether DTLS handshake is finished. */
DTLS_STATE_FINISHED,
/* Whether DTLS session is closed. */
DTLS_STATE_CLOSED,
/* Whether DTLS handshake is failed. */
DTLS_STATE_FAILED,
};
typedef struct TLSShared {
char *ca_file;
int verify;
@@ -40,6 +58,25 @@ typedef struct TLSShared {
int numerichost;
URLContext *tcp;
int is_dtls;
enum DTLSState state;
int use_external_udp;
URLContext *udp;
/* The fingerprint of certificate, used in SDP offer. */
char *fingerprint;
/* The certificate and private key content used for DTLS handshake */
char* cert_buf;
char* key_buf;
/**
* The size of RTP packet, should generally be set to MTU.
* Note that pion requires a smaller value, for example, 1200.
*/
int mtu;
} TLSShared;
#define TLS_OPTFL (AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM)
@@ -51,10 +88,27 @@ typedef struct TLSShared {
{"key_file", "Private key file", offsetof(pstruct, options_field . key_file), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \
{"listen", "Listen for incoming connections", offsetof(pstruct, options_field . listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = TLS_OPTFL }, \
{"verifyhost", "Verify against a specific hostname", offsetof(pstruct, options_field . host), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \
{"http_proxy", "Set proxy to tunnel through", offsetof(pstruct, options_field . http_proxy), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }
{"http_proxy", "Set proxy to tunnel through", offsetof(pstruct, options_field . http_proxy), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL }, \
{"use_external_udp", "Use external UDP from muxer or demuxer", offsetof(pstruct, options_field . use_external_udp), AV_OPT_TYPE_INT, { .i64 = 0}, 0, 1, .flags = TLS_OPTFL }, \
{"mtu", "Maximum Transmission Unit", offsetof(pstruct, options_field . mtu), AV_OPT_TYPE_INT, { .i64 = 0}, INT64_MIN, INT64_MAX, .flags = TLS_OPTFL}, \
{"fingerprint", "The optional fingerprint for DTLS", offsetof(pstruct, options_field . fingerprint), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL}, \
{"cert_buf", "The optional certificate buffer for DTLS", offsetof(pstruct, options_field . cert_buf), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL}, \
{"key_buf", "The optional private key buffer for DTLS", offsetof(pstruct, options_field . key_buf), AV_OPT_TYPE_STRING, .flags = TLS_OPTFL}
int ff_tls_open_underlying(TLSShared *c, URLContext *parent, const char *uri, AVDictionary **options);
int ff_url_read_all(const char *url, AVBPrint *bp);
int ff_dtls_set_udp(URLContext *h, URLContext *udp);
int ff_dtls_export_materials(URLContext *h, char *dtls_srtp_materials, size_t materials_sz);
int ff_dtls_state(URLContext *h);
int ff_ssl_read_key_cert(char *key_url, char *cert_url, char *key_buf, size_t key_sz, char *cert_buf, size_t cert_sz, char **fingerprint);
int ff_ssl_gen_key_cert(char *key_buf, size_t key_sz, char *cert_buf, size_t cert_sz, char **fingerprint);
void ff_gnutls_init(void);
void ff_gnutls_deinit(void);

View File

@@ -1,6 +1,7 @@
/*
* TLS/SSL Protocol
* TLS/DTLS/SSL Protocol
* Copyright (c) 2011 Martin Storsjo
* Copyright (c) 2025 Jack Lau
*
* This file is part of FFmpeg.
*
@@ -19,8 +20,10 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "libavutil/mem.h"
#include "network.h"
#include "os_support.h"
#include "libavutil/random_seed.h"
#include "url.h"
#include "tls.h"
#include "libavutil/opt.h"
@@ -29,6 +32,436 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
/**
* Returns a heap‐allocated null‐terminated string containing
* the PEM‐encoded public key. Caller must free.
*/
static char *pkey_to_pem_string(EVP_PKEY *pkey) {
BIO *mem = NULL;
BUF_MEM *bptr = NULL;
char *pem_str = NULL;
// Create a memory BIO
if (!(mem = BIO_new(BIO_s_mem())))
goto err;
// Write public key in PEM form
if (!PEM_write_bio_PrivateKey(mem, pkey, NULL, NULL, 0, NULL, NULL))
goto err;
// Extract pointer/length
BIO_get_mem_ptr(mem, &bptr);
if (!bptr || !bptr->length)
goto err;
// Allocate string (+1 for NUL)
pem_str = av_malloc(bptr->length + 1);
if (!pem_str)
goto err;
// Copy data & NUL‐terminate
memcpy(pem_str, bptr->data, bptr->length);
pem_str[bptr->length] = '\0';
cleanup:
BIO_free(mem);
return pem_str;
err:
// error path: free and return NULL
free(pem_str);
pem_str = NULL;
goto cleanup;
}
/**
* Serialize an X509 certificate to a av_malloc’d PEM string.
* Caller must free the returned pointer.
*/
static char *cert_to_pem_string(X509 *cert)
{
BIO *mem = BIO_new(BIO_s_mem());
BUF_MEM *bptr = NULL;
char *out = NULL;
if (!mem) goto err;
/* Write the PEM certificate */
if (!PEM_write_bio_X509(mem, cert))
goto err;
BIO_get_mem_ptr(mem, &bptr);
if (!bptr || !bptr->length) goto err;
out = av_malloc(bptr->length + 1);
if (!out) goto err;
memcpy(out, bptr->data, bptr->length);
out[bptr->length] = '\0';
cleanup:
BIO_free(mem);
return out;
err:
free(out);
out = NULL;
goto cleanup;
}
/**
* Generate a SHA-256 fingerprint of an X.509 certificate.
*
* @param ctx AVFormatContext for logging (can be NULL)
* @param cert X509 certificate to fingerprint
* @return Newly allocated fingerprint string in "AA:BB:CC:…" format,
* or NULL on error (logs via av_log if ctx is not NULL).
* Caller must free() the returned string.
*/
static char *generate_fingerprint(X509 *cert)
{
unsigned char md[EVP_MAX_MD_SIZE];
int n = 0;
AVBPrint fingerprint;
char *result = NULL;
int i;
/* To prevent a crash during cleanup, always initialize it. */
av_bprint_init(&fingerprint, 0, AV_BPRINT_SIZE_UNLIMITED);
if (X509_digest(cert, EVP_sha256(), md, &n) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate fingerprint, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto end;
}
for (i = 0; i < n; i++) {
av_bprintf(&fingerprint, "%02X", md[i]);
if (i + 1 < n)
av_bprintf(&fingerprint, ":");
}
if (!fingerprint.str || !strlen(fingerprint.str)) {
av_log(NULL, AV_LOG_ERROR, "TLS: Fingerprint is empty\n");
goto end;
}
result = av_strdup(fingerprint.str);
if (!result) {
av_log(NULL, AV_LOG_ERROR, "TLS: Out of memory generating fingerprint\n");
}
end:
av_bprint_finalize(&fingerprint, NULL);
return result;
}
int ff_ssl_read_key_cert(char *key_url, char *cert_url, char *key_buf, size_t key_sz, char *cert_buf, size_t cert_sz, char **fingerprint)
{
int ret = 0;
BIO *key_b = NULL, *cert_b = NULL;
AVBPrint key_bp, cert_bp;
EVP_PKEY *pkey;
X509 *cert;
char *key_tem = NULL, *cert_tem = NULL;
/* To prevent a crash during cleanup, always initialize it. */
av_bprint_init(&key_bp, 1, MAX_CERTIFICATE_SIZE);
av_bprint_init(&cert_bp, 1, MAX_CERTIFICATE_SIZE);
/* Read key file. */
ret = ff_url_read_all(key_url, &key_bp);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to open key file %s\n", key_url);
goto end;
}
if (!(key_b = BIO_new(BIO_s_mem()))) {
ret = AVERROR(ENOMEM);
goto end;
}
BIO_write(key_b, key_bp.str, key_bp.len);
pkey = PEM_read_bio_PrivateKey(key_b, NULL, NULL, NULL);
if (!pkey) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to read private key from %s\n", key_url);
ret = AVERROR(EIO);
goto end;
}
/* Read certificate. */
ret = ff_url_read_all(cert_url, &cert_bp);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to open cert file %s\n", cert_url);
goto end;
}
if (!(cert_b = BIO_new(BIO_s_mem()))) {
ret = AVERROR(ENOMEM);
goto end;
}
BIO_write(cert_b, cert_bp.str, cert_bp.len);
cert = PEM_read_bio_X509(cert_b, NULL, NULL, NULL);
if (!cert) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to read certificate from %s\n", cert_url);
ret = AVERROR(EIO);
goto end;
}
key_tem = pkey_to_pem_string(pkey);
cert_tem = cert_to_pem_string(cert);
snprintf(key_buf, key_sz, "%s", key_tem);
snprintf(cert_buf, cert_sz, "%s", cert_tem);
/* Generate fingerprint. */
*fingerprint = generate_fingerprint(cert);
if (!*fingerprint) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate fingerprint from %s\n", cert_url);
ret = AVERROR(EIO);
goto end;
}
end:
BIO_free(key_b);
av_bprint_finalize(&key_bp, NULL);
BIO_free(cert_b);
av_bprint_finalize(&cert_bp, NULL);
if (key_tem) av_free(key_tem);
if (cert_tem) av_free(cert_tem);
return ret;
}
static int openssl_gen_private_key(EVP_PKEY **pkey, EC_KEY **eckey)
{
int ret = 0;
/**
* Note that secp256r1 in openssl is called NID_X9_62_prime256v1 or prime256v1 in string,
* not NID_secp256k1 or secp256k1 in string.
*
* TODO: Should choose the curves in ClientHello.supported_groups, for example:
* Supported Group: x25519 (0x001d)
* Supported Group: secp256r1 (0x0017)
* Supported Group: secp384r1 (0x0018)
*/
#if OPENSSL_VERSION_NUMBER < 0x30000000L /* OpenSSL 3.0 */
EC_GROUP *ecgroup = NULL;
int curve = NID_X9_62_prime256v1;
#else
const char *curve = SN_X9_62_prime256v1;
#endif
#if OPENSSL_VERSION_NUMBER < 0x30000000L /* OpenSSL 3.0 */
*pkey = EVP_PKEY_new();
*eckey = EC_KEY_new();
ecgroup = EC_GROUP_new_by_curve_name(curve);
if (!ecgroup) {
av_log(NULL, AV_LOG_ERROR, "TLS: Create EC group by curve=%d failed, %s", curve, ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x
/* For openssl 1.0, we must set the group parameters, so that cert is ok. */
EC_GROUP_set_asn1_flag(ecgroup, OPENSSL_EC_NAMED_CURVE);
#endif
if (EC_KEY_set_group(*eckey, ecgroup) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Generate private key, EC_KEY_set_group failed, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (EC_KEY_generate_key(*eckey) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Generate private key, EC_KEY_generate_key failed, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (EVP_PKEY_set1_EC_KEY(*pkey, *eckey) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Generate private key, EVP_PKEY_set1_EC_KEY failed, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
#else
*pkey = EVP_EC_gen(curve);
if (!*pkey) {
av_log(NULL, AV_LOG_ERROR, "TLS: Generate private key, EVP_EC_gen curve=%s failed, %s\n", curve, ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
#endif
goto end;
einval_end:
ret = AVERROR(EINVAL);
end:
#if OPENSSL_VERSION_NUMBER < 0x30000000L /* OpenSSL 3.0 */
EC_GROUP_free(ecgroup);
#endif
return ret;
}
static int openssl_gen_certificate(EVP_PKEY *pkey, X509 **cert, char **fingerprint)
{
int ret = 0, serial, expire_day;
const char *aor = "lavf";
X509_NAME* subject = NULL;
*cert= X509_new();
if (!*cert) {
goto enomem_end;
}
// TODO: Support non-self-signed certificate, for example, load from a file.
subject = X509_NAME_new();
if (!subject) {
goto enomem_end;
}
serial = (int)av_get_random_seed();
if (ASN1_INTEGER_set(X509_get_serialNumber(*cert), serial) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set serial, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, aor, strlen(aor), -1, 0) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set CN, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (X509_set_issuer_name(*cert, subject) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set issuer, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (X509_set_subject_name(*cert, subject) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set subject name, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
expire_day = 365;
if (!X509_gmtime_adj(X509_get_notBefore(*cert), 0)) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set notBefore, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (!X509_gmtime_adj(X509_get_notAfter(*cert), 60*60*24*expire_day)) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set notAfter, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (X509_set_version(*cert, 2) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set version, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (X509_set_pubkey(*cert, pkey) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set public key, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
if (!X509_sign(*cert, pkey, EVP_sha1())) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to sign certificate, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
}
*fingerprint = generate_fingerprint(*cert);
if (!*fingerprint) {
goto enomem_end;
}
goto end;
enomem_end:
ret = AVERROR(ENOMEM);
goto end;
einval_end:
ret = AVERROR(EINVAL);
end:
X509_NAME_free(subject);
//av_bprint_finalize(&fingerprint, NULL);
return ret;
}
int ff_ssl_gen_key_cert(char *key_buf, size_t key_sz, char *cert_buf, size_t cert_sz, char **fingerprint)
{
int ret = 0;
EVP_PKEY *pkey = NULL;
EC_KEY *ec_key = NULL;
X509 *cert = NULL;
char *key_tem = NULL, *cert_tem = NULL;
ret = openssl_gen_private_key(&pkey, &ec_key);
if (ret < 0) goto error;
ret = openssl_gen_certificate(pkey, &cert, fingerprint);
if (ret < 0) goto error;
key_tem = pkey_to_pem_string(pkey);
cert_tem = cert_to_pem_string(cert);
snprintf(key_buf, key_sz, "%s", key_tem);
snprintf(cert_buf, cert_sz, "%s", cert_tem);
if (key_tem) av_free(key_tem);
if (cert_tem) av_free(cert_tem);
error:
return ret;
}
/**
* Deserialize a PEM‐encoded private or public key from a NUL-terminated C string.
*
* @param pem_str The PEM text, e.g.
* "-----BEGIN PRIVATE KEY-----\n…\n-----END PRIVATE KEY-----\n"
* @param is_priv If non-zero, parse as a PRIVATE key; otherwise, parse as a PUBLIC key.
* @return EVP_PKEY* on success (must EVP_PKEY_free()), or NULL on error.
*/
static EVP_PKEY *pkey_from_pem_string(const char *pem_str, int is_priv)
{
BIO *mem = BIO_new_mem_buf(pem_str, -1);
if (!mem) {
av_log(NULL, AV_LOG_ERROR, "BIO_new_mem_buf failed\n");
return NULL;
}
EVP_PKEY *pkey = NULL;
if (is_priv) {
pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, NULL);
} else {
pkey = PEM_read_bio_PUBKEY(mem, NULL, NULL, NULL);
}
if (!pkey)
av_log(NULL, AV_LOG_ERROR, "Failed to parse %s key from string\n",
is_priv ? "private" : "public");
BIO_free(mem);
return pkey;
}
/**
* Deserialize a PEM‐encoded certificate from a NUL-terminated C string.
*
* @param pem_str The PEM text, e.g.
* "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----\n"
* @return X509* on success (must X509_free()), or NULL on error.
*/
static X509 *cert_from_pem_string(const char *pem_str)
{
BIO *mem = BIO_new_mem_buf(pem_str, -1);
if (!mem) {
av_log(NULL, AV_LOG_ERROR, "BIO_new_mem_buf failed\n");
return NULL;
}
X509 *cert = PEM_read_bio_X509(mem, NULL, NULL, NULL);
if (!cert) {
av_log(NULL, AV_LOG_ERROR, "Failed to parse certificate from string\n");
return NULL;
}
BIO_free(mem);
return cert;
}
typedef struct TLSContext {
const AVClass *class;
TLSShared tls_shared;
@@ -38,8 +471,56 @@ typedef struct TLSContext {
BIO_METHOD* url_bio_method;
#endif
int io_err;
char error_message[256];
} TLSContext;
/**
* Retrieves the error message for the latest OpenSSL error.
*
* This function retrieves the error code from the thread's error queue, converts it
* to a human-readable string, and stores it in the TLSContext's error_message field.
* The error queue is then cleared using ERR_clear_error().
*/
static const char* openssl_get_error(TLSContext *ctx)
{
int r2 = ERR_get_error();
if (r2) {
ERR_error_string_n(r2, ctx->error_message, sizeof(ctx->error_message));
} else
ctx->error_message[0] = '\0';
ERR_clear_error();
return ctx->error_message;
}
int ff_dtls_set_udp(URLContext *h, URLContext *udp)
{
TLSContext *c = h->priv_data;
c->tls_shared.udp = udp;
return 0;
}
int ff_dtls_export_materials(URLContext *h, char *dtls_srtp_materials, size_t materials_sz)
{
int ret = 0;
const char* dst = "EXTRACTOR-dtls_srtp";
TLSContext *c = h->priv_data;
ret = SSL_export_keying_material(c->ssl, dtls_srtp_materials, materials_sz,
dst, strlen(dst), NULL, 0, 0);
if (!ret) {
av_log(c, AV_LOG_ERROR, "TLS: Failed to export SRTP material, %s\n", openssl_get_error(c));
return -1;
}
return 0;
}
int ff_dtls_state(URLContext *h)
{
TLSContext *c = h->priv_data;
return c->tls_shared.state;
}
/* OpenSSL 1.0.2 or below, then you would use SSL_library_init. If you are
* using OpenSSL 1.1.0 or above, then the library will initialize
* itself automatically.
@@ -121,7 +602,7 @@ void ff_openssl_deinit(void)
}
#endif
static int print_tls_error(URLContext *h, int ret)
static int print_ssl_error(URLContext *h, int ret)
{
TLSContext *c = h->priv_data;
int printed = 0, e, averr = AVERROR(EIO);
@@ -193,7 +674,7 @@ static int url_bio_destroy(BIO *b)
static int url_bio_bread(BIO *b, char *buf, int len)
{
TLSContext *c = GET_BIO_DATA(b);
int ret = ffurl_read(c->tls_shared.tcp, buf, len);
int ret = ffurl_read(c->tls_shared.is_dtls ? c->tls_shared.udp : c->tls_shared.tcp, buf, len);
if (ret >= 0)
return ret;
BIO_clear_retry_flags(b);
@@ -209,7 +690,7 @@ static int url_bio_bread(BIO *b, char *buf, int len)
static int url_bio_bwrite(BIO *b, const char *buf, int len)
{
TLSContext *c = GET_BIO_DATA(b);
int ret = ffurl_write(c->tls_shared.tcp, buf, len);
int ret = ffurl_write(c->tls_shared.is_dtls ? c->tls_shared.udp : c->tls_shared.tcp, buf, len);
if (ret >= 0)
return ret;
BIO_clear_retry_flags(b);
@@ -250,11 +731,300 @@ static BIO_METHOD url_bio_method = {
};
#endif
static av_cold void init_bio_method(URLContext *h)
{
TLSContext *p = h->priv_data;
BIO *bio;
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
p->url_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "urlprotocol bio");
BIO_meth_set_write(p->url_bio_method, url_bio_bwrite);
BIO_meth_set_read(p->url_bio_method, url_bio_bread);
BIO_meth_set_puts(p->url_bio_method, url_bio_bputs);
BIO_meth_set_ctrl(p->url_bio_method, url_bio_ctrl);
BIO_meth_set_create(p->url_bio_method, url_bio_create);
BIO_meth_set_destroy(p->url_bio_method, url_bio_destroy);
bio = BIO_new(p->url_bio_method);
BIO_set_data(bio, p);
#else
bio = BIO_new(&url_bio_method);
bio->ptr = p;
#endif
SSL_set_bio(p->ssl, bio, bio);
}
static void openssl_info_callback(const SSL *ssl, int where, int ret) {
const char *method = "undefined";
TLSContext *ctx = (TLSContext*)SSL_get_ex_data(ssl, 0);
if (where & SSL_ST_CONNECT) {
method = "SSL_connect";
} else if (where & SSL_ST_ACCEPT)
method = "SSL_accept";
if (where & SSL_CB_LOOP) {
av_log(ctx, AV_LOG_DEBUG, "Info method=%s state=%s(%s), where=%d, ret=%d\n",
method, SSL_state_string(ssl), SSL_state_string_long(ssl), where, ret);
} else if (where & SSL_CB_ALERT) {
method = (where & SSL_CB_READ) ? "read":"write";
av_log(ctx, AV_LOG_DEBUG, "Alert method=%s state=%s(%s), where=%d, ret=%d\n",
method, SSL_state_string(ssl), SSL_state_string_long(ssl), where, ret);
}
}
/**
* Always return 1 to accept any certificate. This is because we allow the peer to
* use a temporary self-signed certificate for DTLS.
*/
static int openssl_dtls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
return 1;
}
static int dtls_handshake(URLContext *h)
{
int ret = 0, r0, r1;
TLSContext *p = h->priv_data;
r0 = SSL_do_handshake(p->ssl);
r1 = SSL_get_error(p->ssl, r0);
if (r0 <= 0) {
if (r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE && r1 != SSL_ERROR_ZERO_RETURN) {
av_log(p, AV_LOG_ERROR, "TLS: Read failed, r0=%d, r1=%d %s\n", r0, r1, openssl_get_error(p));
ret = AVERROR(EIO);
goto end;
}
} else {
av_log(p, AV_LOG_TRACE, "TLS: Read %d bytes, r0=%d, r1=%d\n", r0, r0, r1);
}
/* Check whether the DTLS is completed. */
if (SSL_is_init_finished(p->ssl) != 1)
goto end;
p->tls_shared.state = DTLS_STATE_FINISHED;
end:
return ret;
}
static av_cold int openssl_init_ca_key_cert(URLContext *h)
{
int ret;
TLSContext *p = h->priv_data;
TLSShared *c = &p->tls_shared;
EVP_PKEY *pkey = NULL;
X509 *cert = NULL;
/* setup ca, private key, certificate */
if (c->ca_file) {
if (!SSL_CTX_load_verify_locations(p->ctx, c->ca_file, NULL))
av_log(h, AV_LOG_ERROR, "SSL_CTX_load_verify_locations %s\n", openssl_get_error(p));
}
if (c->cert_file) {
ret = SSL_CTX_use_certificate_chain_file(p->ctx, c->cert_file);
if (ret <= 0) {
av_log(h, AV_LOG_ERROR, "Unable to load cert file %s: %s\n",
c->cert_file, openssl_get_error(p));
ret = AVERROR(EIO);
goto fail;
}
} else if (p->tls_shared.cert_buf) {
cert = cert_from_pem_string(p->tls_shared.cert_buf);
if (SSL_CTX_use_certificate(p->ctx, cert) != 1) {
av_log(p, AV_LOG_ERROR, "SSL: Init SSL_CTX_use_certificate failed, %s\n", openssl_get_error(p));
ret = AVERROR(EINVAL);
return ret;
}
} else if (p->tls_shared.is_dtls){
av_log(p, AV_LOG_ERROR, "TLS: Init cert failed, %s\n", openssl_get_error(p));
ret = AVERROR(EINVAL);
goto fail;
}
if (c->key_file) {
ret = SSL_CTX_use_PrivateKey_file(p->ctx, c->key_file, SSL_FILETYPE_PEM);
if (ret <= 0) {
av_log(h, AV_LOG_ERROR, "Unable to load key file %s: %s\n",
c->key_file, openssl_get_error(p));
ret = AVERROR(EIO);
goto fail;
}
} else if (p->tls_shared.key_buf) {
pkey = pkey_from_pem_string(p->tls_shared.key_buf, 1);
if (SSL_CTX_use_PrivateKey(p->ctx, pkey) != 1) {
av_log(p, AV_LOG_ERROR, "TLS: Init SSL_CTX_use_PrivateKey failed, %s\n", openssl_get_error(p));
ret = AVERROR(EINVAL);
return ret;
}
} else if (p->tls_shared.is_dtls){
av_log(p, AV_LOG_ERROR, "TLS: Init pkey failed, %s\n", openssl_get_error(p));
ret = AVERROR(EINVAL);
goto fail;
}
ret = 0;
fail:
return ret;
}
/**
* Once the DTLS role has been negotiated - active for the DTLS client or passive for the
* DTLS server - we proceed to set up the DTLS state and initiate the handshake.
*/
static int dtls_start(URLContext *h, const char *url, int flags, AVDictionary **options)
{
TLSContext *p = h->priv_data;
TLSShared *c = &p->tls_shared;
int ret = 0;
c->is_dtls = 1;
const char* ciphers = "ALL";
/**
* The profile for OpenSSL's SRTP is SRTP_AES128_CM_SHA1_80, see ssl/d1_srtp.c.
* The profile for FFmpeg's SRTP is SRTP_AES128_CM_HMAC_SHA1_80, see libavformat/srtp.c.
*/
const char* profiles = "SRTP_AES128_CM_SHA1_80";
/* Refer to the test cases regarding these curves in the WebRTC code. */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L /* OpenSSL 1.1.0 */
const char* curves = "X25519:P-256:P-384:P-521";
#elif OPENSSL_VERSION_NUMBER >= 0x10002000L /* OpenSSL 1.0.2 */
const char* curves = "P-256:P-384:P-521";
#endif
#if OPENSSL_VERSION_NUMBER < 0x10002000L /* OpenSSL v1.0.2 */
p->ctx = SSL_CTX_new(DTLSv1_method());
#else
p->ctx = SSL_CTX_new(DTLS_method());
#endif
if (!p->ctx) {
ret = AVERROR(ENOMEM);
goto fail;
}
#if OPENSSL_VERSION_NUMBER >= 0x10002000L /* OpenSSL 1.0.2 */
/* For ECDSA, we could set the curves list. */
if (SSL_CTX_set1_curves_list(p->ctx, curves) != 1) {
av_log(p, AV_LOG_ERROR, "TLS: Init SSL_CTX_set1_curves_list failed, curves=%s, %s\n",
curves, openssl_get_error(p));
ret = AVERROR(EINVAL);
return ret;
}
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x
#if OPENSSL_VERSION_NUMBER < 0x10002000L // v1.0.2
if (ctx->dtls_eckey)
SSL_CTX_set_tmp_ecdh(p->ctx, p->dtls_eckey);
#else
SSL_CTX_set_ecdh_auto(p->ctx, 1);
#endif
#endif
/**
* We activate "ALL" cipher suites to align with the peer's capabilities,
* ensuring maximum compatibility.
*/
if (SSL_CTX_set_cipher_list(p->ctx, ciphers) != 1) {
av_log(p, AV_LOG_ERROR, "TLS: Init SSL_CTX_set_cipher_list failed, ciphers=%s, %s\n",
ciphers, openssl_get_error(p));
ret = AVERROR(EINVAL);
return ret;
}
ret = openssl_init_ca_key_cert(h);
if (ret < 0) goto fail;
/* Server will send Certificate Request. */
SSL_CTX_set_verify(p->ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, openssl_dtls_verify_callback);
/* The depth count is "level 0:peer certificate", "level 1: CA certificate",
* "level 2: higher level CA certificate", and so on. */
SSL_CTX_set_verify_depth(p->ctx, 4);
/* Whether we should read as many input bytes as possible (for non-blocking reads) or not. */
SSL_CTX_set_read_ahead(p->ctx, 1);
/* Setup the SRTP context */
if (SSL_CTX_set_tlsext_use_srtp(p->ctx, profiles)) {
av_log(p, AV_LOG_ERROR, "TLS: Init SSL_CTX_set_tlsext_use_srtp failed, profiles=%s, %s\n",
profiles, openssl_get_error(p));
ret = AVERROR(EINVAL);
return ret;
}
/* The ssl should not be created unless the ctx has been initialized. */
p->ssl = SSL_new(p->ctx);
if (!p->ssl) {
ret = AVERROR(ENOMEM);
goto fail;
}
/* Setup the callback for logging. */
SSL_set_ex_data(p->ssl, 0, p);
SSL_set_info_callback(p->ssl, openssl_info_callback);
/**
* We have set the MTU to fragment the DTLS packet. It is important to note that the
* packet is split to ensure that each handshake packet is smaller than the MTU.
*/
SSL_set_options(p->ssl, SSL_OP_NO_QUERY_MTU);
SSL_set_mtu(p->ssl, p->tls_shared.mtu);
#if OPENSSL_VERSION_NUMBER >= 0x100010b0L /* OpenSSL 1.0.1k */
DTLS_set_link_mtu(p->ssl, p->tls_shared.mtu);
#endif
init_bio_method(h);
if (p->tls_shared.use_external_udp != 1) {
if ((ret = ff_tls_open_underlying(&p->tls_shared, h, url, options)) < 0) {
av_log(p, AV_LOG_ERROR, "Failed to connect %s\n", url);
return ret;
}
}
/* Setup DTLS as passive, which is server role. */
c->listen ? SSL_set_accept_state(p->ssl) : SSL_set_connect_state(p->ssl);
/**
* During initialization, we only need to call SSL_do_handshake once because SSL_read consumes
* the handshake message if the handshake is incomplete.
* To simplify maintenance, we initiate the handshake for both the DTLS server and client after
* sending out the ICE response in the start_active_handshake function. It's worth noting that
* although the DTLS server may receive the ClientHello immediately after sending out the ICE
* response, this shouldn't be an issue as the handshake function is called before any DTLS
* packets are received.
*
* The SSL_do_handshake can't be called if DTLS hasn't prepare for udp.
*/
if (p->tls_shared.use_external_udp != 1) {
ret = dtls_handshake(h);
// Fatal SSL error, for example, no available suite when peer is DTLS 1.0 while we are DTLS 1.2.
if (ret < 0) {
av_log(p, AV_LOG_ERROR, "TLS: Failed to drive SSL context, ret=%d\n", ret);
return AVERROR(EIO);
}
}
av_log(p, AV_LOG_VERBOSE, "TLS: Setup ok, MTU=%d, fingerprint %s\n",
p->tls_shared.mtu, p->tls_shared.fingerprint);
ret = 0;
fail:
return ret;
}
/**
* Cleanup the DTLS context.
*/
static av_cold int dtls_close(URLContext *h)
{
TLSContext *ctx = h->priv_data;
SSL_free(ctx->ssl);
SSL_CTX_free(ctx->ctx);
av_freep(&ctx->tls_shared.fingerprint);
av_freep(&ctx->tls_shared.cert_buf);
av_freep(&ctx->tls_shared.key_buf);
#if OPENSSL_VERSION_NUMBER < 0x30000000L /* OpenSSL 3.0 */
EC_KEY_free(ctx->dtls_eckey);
#endif
return 0;
}
static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
{
TLSContext *p = h->priv_data;
TLSShared *c = &p->tls_shared;
BIO *bio;
int ret;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -271,52 +1041,26 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
// support for the old protocols immediately after creating the context.
p->ctx = SSL_CTX_new(c->listen ? SSLv23_server_method() : SSLv23_client_method());
if (!p->ctx) {
av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL));
av_log(h, AV_LOG_ERROR, "%s\n", openssl_get_error(p));
ret = AVERROR(EIO);
goto fail;
}
SSL_CTX_set_options(p->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
if (c->ca_file) {
if (!SSL_CTX_load_verify_locations(p->ctx, c->ca_file, NULL))
av_log(h, AV_LOG_ERROR, "SSL_CTX_load_verify_locations %s\n", ERR_error_string(ERR_get_error(), NULL));
}
if (c->cert_file && !SSL_CTX_use_certificate_chain_file(p->ctx, c->cert_file)) {
av_log(h, AV_LOG_ERROR, "Unable to load cert file %s: %s\n",
c->cert_file, ERR_error_string(ERR_get_error(), NULL));
ret = AVERROR(EIO);
goto fail;
}
if (c->key_file && !SSL_CTX_use_PrivateKey_file(p->ctx, c->key_file, SSL_FILETYPE_PEM)) {
av_log(h, AV_LOG_ERROR, "Unable to load key file %s: %s\n",
c->key_file, ERR_error_string(ERR_get_error(), NULL));
ret = AVERROR(EIO);
goto fail;
}
ret = openssl_init_ca_key_cert(h);
if (ret < 0) goto fail;
// Note, this doesn't check that the peer certificate actually matches
// the requested hostname.
if (c->verify)
SSL_CTX_set_verify(p->ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
p->ssl = SSL_new(p->ctx);
if (!p->ssl) {
av_log(h, AV_LOG_ERROR, "%s\n", ERR_error_string(ERR_get_error(), NULL));
av_log(h, AV_LOG_ERROR, "%s\n", openssl_get_error(p));
ret = AVERROR(EIO);
goto fail;
}
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
p->url_bio_method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "urlprotocol bio");
BIO_meth_set_write(p->url_bio_method, url_bio_bwrite);
BIO_meth_set_read(p->url_bio_method, url_bio_bread);
BIO_meth_set_puts(p->url_bio_method, url_bio_bputs);
BIO_meth_set_ctrl(p->url_bio_method, url_bio_ctrl);
BIO_meth_set_create(p->url_bio_method, url_bio_create);
BIO_meth_set_destroy(p->url_bio_method, url_bio_destroy);
bio = BIO_new(p->url_bio_method);
BIO_set_data(bio, p);
#else
bio = BIO_new(&url_bio_method);
bio->ptr = p;
#endif
SSL_set_bio(p->ssl, bio, bio);
SSL_set_ex_data(p->ssl, 0, p);
SSL_CTX_set_info_callback(p->ctx, openssl_info_callback);
init_bio_method(h);
if (!c->listen && !c->numerichost)
SSL_set_tlsext_host_name(p->ssl, c->host);
ret = c->listen ? SSL_accept(p->ssl) : SSL_connect(p->ssl);
@@ -325,7 +1069,7 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
ret = AVERROR(EIO);
goto fail;
} else if (ret < 0) {
ret = print_tls_error(h, ret);
ret = print_ssl_error(h, ret);
goto fail;
}
@@ -338,31 +1082,35 @@ fail:
static int tls_read(URLContext *h, uint8_t *buf, int size)
{
TLSContext *c = h->priv_data;
URLContext *uc = c->tls_shared.is_dtls ? c->tls_shared.udp
: c->tls_shared.tcp;
int ret;
// Set or clear the AVIO_FLAG_NONBLOCK on c->tls_shared.tcp
c->tls_shared.tcp->flags &= ~AVIO_FLAG_NONBLOCK;
c->tls_shared.tcp->flags |= h->flags & AVIO_FLAG_NONBLOCK;
uc->flags &= ~AVIO_FLAG_NONBLOCK;
uc->flags |= h->flags & AVIO_FLAG_NONBLOCK;
ret = SSL_read(c->ssl, buf, size);
if (ret > 0)
return ret;
if (ret == 0)
return AVERROR_EOF;
return print_tls_error(h, ret);
return print_ssl_error(h, ret);
}
static int tls_write(URLContext *h, const uint8_t *buf, int size)
{
TLSContext *c = h->priv_data;
URLContext *uc = c->tls_shared.is_dtls ? c->tls_shared.udp
: c->tls_shared.tcp;
int ret;
// Set or clear the AVIO_FLAG_NONBLOCK on c->tls_shared.tcp
c->tls_shared.tcp->flags &= ~AVIO_FLAG_NONBLOCK;
c->tls_shared.tcp->flags |= h->flags & AVIO_FLAG_NONBLOCK;
uc->flags &= ~AVIO_FLAG_NONBLOCK;
uc->flags |= h->flags & AVIO_FLAG_NONBLOCK;
ret = SSL_write(c->ssl, buf, size);
if (ret > 0)
return ret;
if (ret == 0)
return AVERROR_EOF;
return print_tls_error(h, ret);
return print_ssl_error(h, ret);
}
static int tls_get_file_handle(URLContext *h)
@@ -401,3 +1149,22 @@ const URLProtocol ff_tls_protocol = {
.flags = URL_PROTOCOL_FLAG_NETWORK,
.priv_data_class = &tls_class,
};
static const AVClass dtls_class = {
.class_name = "dtls",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
const URLProtocol ff_dtls_protocol = {
.name = "dtls",
.url_open2 = dtls_start,
.url_handshake = dtls_handshake,
.url_close = dtls_close,
.url_read = tls_read,
.url_write = tls_write,
.priv_data_size = sizeof(TLSContext),
.flags = URL_PROTOCOL_FLAG_NETWORK,
.priv_data_class = &dtls_class,
};

1917
libavformat/whip.c Normal file

File diff suppressed because it is too large Load Diff