Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avformat/tls_gnutls: add dtls support (PR #20254)
@ 2025-08-16 10:45 Jack Lau via ffmpeg-devel
  0 siblings, 0 replies; only message in thread
From: Jack Lau via ffmpeg-devel @ 2025-08-16 10:45 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Jack Lau

PR #20254 opened by Jack Lau (JackLau)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20254
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20254.patch


From a303893bdfb1ca740a36ba517d5780ec828ad850 Mon Sep 17 00:00:00 2001
From: Jack Lau <jacklau1222@qq.com>
Date: Wed, 13 Aug 2025 10:04:33 +0800
Subject: [PATCH 1/2] avformat/tls_gnutls: cleanup the pointer name

Pure cleanup, no functional changes
Unify local pointer names to `TLSContext *c` and `TLSShared *s` to
reduce confusion from inconsistent names (e.g. p, s, c)

Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
 libavformat/tls_gnutls.c | 58 ++++++++++++++++++++--------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/libavformat/tls_gnutls.c b/libavformat/tls_gnutls.c
index df251ad79c..e894765d7c 100644
--- a/libavformat/tls_gnutls.c
+++ b/libavformat/tls_gnutls.c
@@ -149,66 +149,66 @@ static ssize_t gnutls_url_push(gnutls_transport_ptr_t transport,
 
 static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
 {
-    TLSContext *p = h->priv_data;
-    TLSShared *c = &p->tls_shared;
+    TLSContext *c = h->priv_data;
+    TLSShared *s = &c->tls_shared;
     int ret;
 
     ff_gnutls_init();
 
-    if ((ret = ff_tls_open_underlying(c, h, uri, options)) < 0)
+    if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
         goto fail;
 
-    gnutls_init(&p->session, c->listen ? GNUTLS_SERVER : GNUTLS_CLIENT);
-    if (!c->listen && !c->numerichost)
-        gnutls_server_name_set(p->session, GNUTLS_NAME_DNS, c->host, strlen(c->host));
-    gnutls_certificate_allocate_credentials(&p->cred);
-    if (c->ca_file) {
-        ret = gnutls_certificate_set_x509_trust_file(p->cred, c->ca_file, GNUTLS_X509_FMT_PEM);
+    gnutls_init(&c->session, s->listen ? GNUTLS_SERVER : GNUTLS_CLIENT);
+    if (!s->listen && !s->numerichost)
+        gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, s->host, strlen(s->host));
+    gnutls_certificate_allocate_credentials(&c->cred);
+    if (s->ca_file) {
+        ret = gnutls_certificate_set_x509_trust_file(c->cred, s->ca_file, GNUTLS_X509_FMT_PEM);
         if (ret < 0)
             av_log(h, AV_LOG_ERROR, "%s\n", gnutls_strerror(ret));
     }
 #if GNUTLS_VERSION_NUMBER >= 0x030020
     else
-        gnutls_certificate_set_x509_system_trust(p->cred);
+        gnutls_certificate_set_x509_system_trust(c->cred);
 #endif
-    gnutls_certificate_set_verify_flags(p->cred, c->verify ?
+    gnutls_certificate_set_verify_flags(c->cred, s->verify ?
                                         GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT : 0);
-    if (c->cert_file && c->key_file) {
-        ret = gnutls_certificate_set_x509_key_file(p->cred,
-                                                   c->cert_file, c->key_file,
+    if (s->cert_file && s->key_file) {
+        ret = gnutls_certificate_set_x509_key_file(c->cred,
+                                                   s->cert_file, s->key_file,
                                                    GNUTLS_X509_FMT_PEM);
         if (ret < 0) {
             av_log(h, AV_LOG_ERROR,
                    "Unable to set cert/key files %s and %s: %s\n",
-                   c->cert_file, c->key_file, gnutls_strerror(ret));
+                   s->cert_file, s->key_file, gnutls_strerror(ret));
             ret = AVERROR(EIO);
             goto fail;
         }
-    } else if (c->cert_file || c->key_file)
+    } else if (s->cert_file || s->key_file)
         av_log(h, AV_LOG_ERROR, "cert and key required\n");
-    gnutls_credentials_set(p->session, GNUTLS_CRD_CERTIFICATE, p->cred);
-    gnutls_transport_set_pull_function(p->session, gnutls_url_pull);
-    gnutls_transport_set_push_function(p->session, gnutls_url_push);
-    gnutls_transport_set_ptr(p->session, p);
-    gnutls_set_default_priority(p->session);
+    gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred);
+    gnutls_transport_set_pull_function(c->session, gnutls_url_pull);
+    gnutls_transport_set_push_function(c->session, gnutls_url_push);
+    gnutls_transport_set_ptr(c->session, c);
+    gnutls_set_default_priority(c->session);
     do {
         if (ff_check_interrupt(&h->interrupt_callback)) {
             ret = AVERROR_EXIT;
             goto fail;
         }
 
-        ret = gnutls_handshake(p->session);
+        ret = gnutls_handshake(c->session);
         if (gnutls_error_is_fatal(ret)) {
             ret = print_tls_error(h, ret);
             goto fail;
         }
     } while (ret);
-    p->need_shutdown = 1;
-    if (c->verify) {
+    c->need_shutdown = 1;
+    if (s->verify) {
         unsigned int status, cert_list_size;
         gnutls_x509_crt_t cert;
         const gnutls_datum_t *cert_list;
-        if ((ret = gnutls_certificate_verify_peers2(p->session, &status)) < 0) {
+        if ((ret = gnutls_certificate_verify_peers2(c->session, &status)) < 0) {
             av_log(h, AV_LOG_ERROR, "Unable to verify peer certificate: %s\n",
                                     gnutls_strerror(ret));
             ret = AVERROR(EIO);
@@ -219,19 +219,19 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
             ret = AVERROR(EIO);
             goto fail;
         }
-        if (gnutls_certificate_type_get(p->session) != GNUTLS_CRT_X509) {
+        if (gnutls_certificate_type_get(c->session) != GNUTLS_CRT_X509) {
             av_log(h, AV_LOG_ERROR, "Unsupported certificate type\n");
             ret = AVERROR(EIO);
             goto fail;
         }
         gnutls_x509_crt_init(&cert);
-        cert_list = gnutls_certificate_get_peers(p->session, &cert_list_size);
+        cert_list = gnutls_certificate_get_peers(c->session, &cert_list_size);
         gnutls_x509_crt_import(cert, cert_list, GNUTLS_X509_FMT_DER);
-        ret = gnutls_x509_crt_check_hostname(cert, c->host);
+        ret = gnutls_x509_crt_check_hostname(cert, s->host);
         gnutls_x509_crt_deinit(cert);
         if (!ret) {
             av_log(h, AV_LOG_ERROR,
-                   "The certificate's owner does not match hostname %s\n", c->host);
+                   "The certificate's owner does not match hostname %s\n", s->host);
             ret = AVERROR(EIO);
             goto fail;
         }
-- 
2.49.1


From 1bfba8ae15c53001fd10c812a11bf8338697592a Mon Sep 17 00:00:00 2001
From: Jack Lau <jacklau1222@qq.com>
Date: Tue, 12 Aug 2025 15:35:01 +0800
Subject: [PATCH 2/2] avformat/tls_gnutls: add dtls support

Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
 configure                |   2 +-
 libavformat/tls_gnutls.c | 498 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 484 insertions(+), 16 deletions(-)

diff --git a/configure b/configure
index e1809a3e58..7f14576390 100755
--- a/configure
+++ b/configure
@@ -3884,7 +3884,7 @@ 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 schannel"
+dtls_protocol_deps_any="openssl schannel gnutls"
 dtls_protocol_select="udp_protocol"
 udp_protocol_select="network"
 udplite_protocol_select="network"
diff --git a/libavformat/tls_gnutls.c b/libavformat/tls_gnutls.c
index e894765d7c..cfd81ec575 100644
--- a/libavformat/tls_gnutls.c
+++ b/libavformat/tls_gnutls.c
@@ -22,6 +22,7 @@
 #include <errno.h>
 
 #include <gnutls/gnutls.h>
+#include <gnutls/dtls.h>
 #include <gnutls/x509.h>
 
 #include "avformat.h"
@@ -29,8 +30,11 @@
 #include "os_support.h"
 #include "url.h"
 #include "tls.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/thread.h"
+#include "libavutil/random_seed.h"
 
 #ifndef GNUTLS_VERSION_NUMBER
 #define GNUTLS_VERSION_NUMBER LIBGNUTLS_VERSION_NUMBER
@@ -41,6 +45,293 @@
 GCRY_THREAD_OPTION_PTHREAD_IMPL;
 #endif
 
+static int pkey_to_pem_string(gnutls_x509_privkey_t key, char *out, size_t out_sz)
+{
+    size_t size = 0;
+    int ret = 0;
+
+    ret = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, NULL, &size);
+    if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_privkey_export size: %s\n", gnutls_strerror(ret));
+        return AVERROR(EINVAL);
+    }
+
+    if (size > out_sz) {
+        av_log(NULL, AV_LOG_ERROR, "Private key PEM string size %zu is large than %zu\n", size, out_sz);
+        return AVERROR(EINVAL);
+    }
+
+    ret = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, out, &size);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_privkey_export: %s\n", gnutls_strerror(ret));
+        return AVERROR(EINVAL);
+    }
+    out[size] = '\0';
+    return ret;
+}
+
+static int crt_to_pem_string(gnutls_x509_crt_t crt, char *out, size_t out_sz)
+{
+    size_t size = 0;
+    int ret = 0;
+
+    ret = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, NULL, &size);
+    if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_export size: %s\n", gnutls_strerror(ret));
+        return AVERROR(EINVAL);
+    }
+
+    if (size > out_sz) {
+        av_log(NULL, AV_LOG_ERROR, "Certificate PEM string size %zu is large than %zu\n", size, out_sz);
+        return AVERROR(EINVAL);
+    }
+
+    ret = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, out, &size);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_export: %s\n", gnutls_strerror(ret));
+        return AVERROR(EINVAL);
+    }
+    out[size] = '\0';
+    return ret;
+}
+
+static int gnutls_x509_fingerprint(gnutls_x509_crt_t cert, char **fingerprint)
+{
+    unsigned char *buf = NULL;
+    size_t buf_size = 0;
+    AVBPrint pbuf;
+    int ret;
+
+    ret = gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, NULL, &buf_size);
+    if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+        return AVERROR(EINVAL);
+    }
+    buf = av_malloc(buf_size);
+    if (!buf)
+        return AVERROR(ENOMEM);
+    if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, buf, &buf_size)) {
+        av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate fingerprint, %s\n",
+               gnutls_strerror(ret));
+        return AVERROR(ENOMEM);
+    }
+
+    av_bprint_init(&pbuf, buf_size*3, buf_size*3);
+
+    for (int i = 0; i < buf_size - 1; i++)
+        av_bprintf(&pbuf, "%02X:", buf[i]);
+    av_bprintf(&pbuf, "%02X", buf[buf_size - 1]);
+
+    return av_bprint_finalize(&pbuf, fingerprint);
+}
+
+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;
+    AVBPrint key_bp, cert_bp;
+    gnutls_x509_crt_t crt = NULL;
+    gnutls_x509_privkey_t key = NULL;
+    gnutls_datum_t temp;
+
+    av_bprint_init(&key_bp, 1, MAX_CERTIFICATE_SIZE);
+    av_bprint_init(&cert_bp, 1, MAX_CERTIFICATE_SIZE);
+
+    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;
+    }
+
+    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;
+    }
+
+    ret = gnutls_x509_privkey_init(&key);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_privkey_init: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_crt_init(&crt);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_init: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    temp.data = key_bp.str;
+    temp.size = key_bp.len;
+    ret = gnutls_x509_privkey_import(key, &temp, GNUTLS_X509_FMT_PEM);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_privkey_import: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    temp.data = cert_bp.str;
+    temp.size = cert_bp.len;
+    ret = gnutls_x509_crt_import(crt, &temp, GNUTLS_X509_FMT_PEM);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_import: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = pkey_to_pem_string(key, key_buf, key_sz);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to convert private key to PEM string\n");
+        goto end;
+    }
+
+    ret = crt_to_pem_string(crt, cert_buf, cert_sz);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to convert certificate to PEM string\n");
+        goto end;
+    }
+
+    ret = gnutls_x509_fingerprint(crt, fingerprint);
+    if (ret < 0)
+        av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate fingerprint\n");
+end:
+    av_bprint_finalize(&key_bp, NULL);
+    av_bprint_finalize(&cert_bp, NULL);
+    if (crt)
+        gnutls_x509_crt_deinit(crt);
+    if (key)
+        gnutls_x509_privkey_deinit(key);
+    return ret;
+}
+
+static int gnutls_gen_private_key(gnutls_x509_privkey_t *key)
+{
+    int ret = 0;
+
+    ret = gnutls_x509_privkey_init(key);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_privkey_init: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_privkey_generate(*key, GNUTLS_PK_ECDSA, 
+                                       gnutls_sec_param_to_pk_bits(GNUTLS_PK_ECDSA, GNUTLS_SEC_PARAM_MEDIUM), 0);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_privkey_generate: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+end:
+    return ret;
+}
+
+static int gnutls_gen_certificate(gnutls_x509_privkey_t key, gnutls_x509_crt_t *crt, char **fingerprint)
+{
+    int ret = 0;
+    uint64_t serial;
+    unsigned char buf[8];
+    const char *dn = "CN=lavf";
+
+    ret = gnutls_x509_crt_init(crt);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_init: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_crt_set_version(*crt, 3);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_set_version: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    /** 
+     * See https://gnutls.org/manual/gnutls.html#gnutls_005fx509_005fcrt_005fset_005fserial-1
+     * The provided serial should be a big-endian positive number (i.e. its leftmost bit should be zero).
+     */
+    serial = av_get_random_seed();
+    AV_WB64(buf, serial);
+    buf[0] &= 0x7F;
+    ret = gnutls_x509_crt_set_serial(*crt, buf, sizeof(buf));
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_set_serial: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_crt_set_activation_time(*crt, time(NULL));
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_set_activation_time: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+    
+    ret = gnutls_x509_crt_set_expiration_time(*crt, time(NULL) + 365 * 24 * 60 * 60);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_set_expiration_time: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_crt_set_dn(*crt, dn, NULL);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_set_dn: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_crt_set_issuer_dn(*crt, dn, NULL);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_set_issuer_dn: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_crt_set_key(*crt, key);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_set_key: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_crt_sign2(*crt, *crt, key, GNUTLS_DIG_SHA256, 0);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_x509_crt_sign2: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_x509_fingerprint(*crt, fingerprint);
+    if (ret < 0)
+        av_log(NULL, AV_LOG_ERROR, "TLS: Failed to generate fingerprint\n");
+end:
+    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;
+    gnutls_x509_crt_t crt = NULL;
+    gnutls_x509_privkey_t key = NULL;
+
+    ret = gnutls_gen_private_key(&key);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_gen_private_key: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = gnutls_gen_certificate(key, &crt, fingerprint);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "gnutls_gen_certificate: %s\n", gnutls_strerror(ret));
+        goto end;
+    }
+
+    ret = pkey_to_pem_string(key, key_buf, key_sz);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to convert private key to PEM string\n");
+        goto end;
+    }
+
+    ret = crt_to_pem_string(crt, cert_buf, cert_sz);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to convert certificate to PEM string\n");
+        goto end;
+    }
+end:
+    if (crt)
+        gnutls_x509_crt_deinit(crt);
+    if (key)
+        gnutls_x509_privkey_deinit(key);
+    return ret;
+}
+
 typedef struct TLSContext {
     const AVClass *class;
     TLSShared tls_shared;
@@ -48,6 +339,8 @@ typedef struct TLSContext {
     gnutls_certificate_credentials_t cred;
     int need_shutdown;
     int io_err;
+    struct sockaddr_storage dest_addr;
+    socklen_t dest_addr_len;
 } TLSContext;
 
 static AVMutex gnutls_mutex = AV_MUTEX_INITIALIZER;
@@ -97,16 +390,44 @@ static int print_tls_error(URLContext *h, int ret)
     return AVERROR(EIO);
 }
 
+int ff_tls_set_external_socket(URLContext *h, URLContext *sock)
+{
+    TLSContext *c = h->priv_data;
+    TLSShared *s = &c->tls_shared;
+
+    if (s->is_dtls)
+        c->tls_shared.udp = sock;
+    else
+        c->tls_shared.tcp = sock;
+
+    return 0;
+}
+
+int ff_dtls_export_materials(URLContext *h, char *dtls_srtp_materials, size_t materials_sz)
+{
+    int ret = 0;
+    TLSContext *c = h->priv_data;
+
+    ret = gnutls_srtp_get_keys(c->session, dtls_srtp_materials, materials_sz, NULL, NULL, NULL, NULL);
+    if (ret < 0) {
+        av_log(c, AV_LOG_ERROR, "Failed to export SRTP material, %s\n", gnutls_strerror(ret));
+        return -1;
+    }
+    return 0;
+}
+
 static int tls_close(URLContext *h)
 {
     TLSContext *c = h->priv_data;
+    TLSShared *s = &c->tls_shared;
     if (c->need_shutdown)
         gnutls_bye(c->session, GNUTLS_SHUT_WR);
     if (c->session)
         gnutls_deinit(c->session);
     if (c->cred)
         gnutls_certificate_free_credentials(c->cred);
-    ffurl_closep(&c->tls_shared.tcp);
+    if (!s->external_sock)
+        ffurl_closep(s->is_dtls ? &s->udp : &s->tcp);
     ff_gnutls_deinit();
     return 0;
 }
@@ -115,7 +436,7 @@ static ssize_t gnutls_url_pull(gnutls_transport_ptr_t transport,
                                void *buf, size_t len)
 {
     TLSContext *c = (TLSContext*) transport;
-    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;
     if (ret == AVERROR_EXIT)
@@ -133,7 +454,7 @@ static ssize_t gnutls_url_push(gnutls_transport_ptr_t transport,
                                const void *buf, size_t len)
 {
     TLSContext *c = (TLSContext*) transport;
-    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;
     if (ret == AVERROR_EXIT)
@@ -147,18 +468,88 @@ static ssize_t gnutls_url_push(gnutls_transport_ptr_t transport,
     return -1;
 }
 
-static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
+static int gnutls_pull_timeout(gnutls_transport_ptr_t ptr, unsigned int ms) {
+    TLSContext *c = (TLSContext*)ptr;
+    TLSShared *s = &c->tls_shared;
+    int ret;
+    fd_set read_fds;
+    struct timeval timeout;
+    int sockfd = ffurl_get_file_handle(s->udp);
+    if (sockfd < 0)
+        return 0;
+    
+    FD_ZERO(&read_fds);
+    FD_SET(sockfd, &read_fds);
+    
+    timeout.tv_sec = ms / 1000;
+    timeout.tv_usec = (ms % 1000) * 1000;
+    
+    ret = select(sockfd + 1, &read_fds, NULL, NULL, &timeout);
+    if (ret > 0)
+        return 1;
+    return 0;
+}
+
+static int dtls_handshake(URLContext *h)
 {
     TLSContext *c = h->priv_data;
     TLSShared *s = &c->tls_shared;
     int ret;
 
+    s->udp->flags &= ~AVIO_FLAG_NONBLOCK;
+
+    if (s->listen && !c->dest_addr_len) {
+        char buf[1500];
+        while(1) {
+            ret = ffurl_read(s->udp, buf, sizeof(buf));
+            if (ret == AVERROR(EAGAIN))
+                continue;
+            if (ret > 0)
+                break;
+            if (ret < 0) {
+                c->io_err = ret;
+                return ret;
+            }
+        }
+
+        ff_udp_get_last_recv_addr(s->udp, &c->dest_addr, &c->dest_addr_len);
+        ret = ff_udp_set_remote_addr(s->udp, (struct sockaddr *)&c->dest_addr, c->dest_addr_len, 1);
+        if (ret < 0) {
+            av_log(c, AV_LOG_ERROR, "Failed connecting udp context\n");
+            return ret;
+        }
+        av_log(c, AV_LOG_TRACE, "Set UDP remote addr on UDP socket, now 'connected'\n");
+    }
+
+    ret = gnutls_handshake(c->session);
+    if (ret < 0) {
+        av_log(h, AV_LOG_ERROR, "Handshake failed: %s\n", gnutls_strerror(ret));
+        return ret;
+    }
+
+    return 0;
+}
+
+static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
+{
+    TLSContext *c = h->priv_data;
+    TLSShared *s = &c->tls_shared;
+    int ret;
+    unsigned int gnutls_flags = s->is_dtls ? GNUTLS_DATAGRAM : 0;
+
     ff_gnutls_init();
 
-    if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
-        goto fail;
+    if (!s->external_sock) {
+        if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
+            goto fail;
+    }
 
-    gnutls_init(&c->session, s->listen ? GNUTLS_SERVER : GNUTLS_CLIENT);
+    if (s->listen)
+        gnutls_flags |= GNUTLS_SERVER;
+    else
+        gnutls_flags |= GNUTLS_CLIENT;
+
+    gnutls_init(&c->session, gnutls_flags);
     if (!s->listen && !s->numerichost)
         gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, s->host, strlen(s->host));
     gnutls_certificate_allocate_credentials(&c->cred);
@@ -184,20 +575,64 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
             ret = AVERROR(EIO);
             goto fail;
         }
-    } else if (s->cert_file || s->key_file)
-        av_log(h, AV_LOG_ERROR, "cert and key required\n");
+    } else if (s->cert_buf && s->key_buf) {
+        gnutls_datum_t crt_data, key_data;
+        crt_data.data = s->cert_buf;
+        crt_data.size = strlen(s->cert_buf);
+        key_data.data = s->key_buf;
+        key_data.size = strlen(s->key_buf);
+        ret = gnutls_certificate_set_x509_key_mem(c->cred, &crt_data, &key_data, GNUTLS_X509_FMT_PEM);
+        if (ret < 0) {
+            av_log(h, AV_LOG_ERROR, "Unable to set cert/key memory: %s\n", gnutls_strerror(ret));
+            ret = AVERROR(EIO);
+            goto fail;
+        }
+    } else if (s->listen && !s->cert_buf && !s->key_buf && !s->cert_file && !s->key_file) {
+        gnutls_x509_crt_t crt = NULL;
+        gnutls_x509_privkey_t key = NULL;
+
+        av_log(h, AV_LOG_VERBOSE, "No server certificate provided, using self-signed\n");
+        ret = gnutls_gen_private_key(&key);
+        if (ret < 0)
+            goto fail;
+        
+        ret = gnutls_gen_certificate(key, &crt, NULL);
+        if (ret < 0)
+            goto fail;
+        ret = gnutls_certificate_set_x509_key(c->cred, &crt, 1, key);
+        if (ret < 0) {
+            av_log(h, AV_LOG_ERROR, "Unable to set certificate and key: %s\n", gnutls_strerror(ret));
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+    }
     gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred);
     gnutls_transport_set_pull_function(c->session, gnutls_url_pull);
     gnutls_transport_set_push_function(c->session, gnutls_url_push);
     gnutls_transport_set_ptr(c->session, c);
+    if (s->is_dtls) {
+        gnutls_transport_set_pull_timeout_function(c->session, gnutls_pull_timeout);
+        if (s->mtu)
+            gnutls_dtls_set_mtu(c->session, s->mtu);
+    } 
     gnutls_set_default_priority(c->session);
+    if (s->use_srtp) {
+        int ret = gnutls_srtp_set_profile(c->session, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80);
+        if (ret < 0) {
+            av_log(c, AV_LOG_ERROR, "Unable to set SRTP profile: %s\n", gnutls_strerror(ret));
+        }
+    }
     do {
+        if (s->external_sock)
+            break;
         if (ff_check_interrupt(&h->interrupt_callback)) {
             ret = AVERROR_EXIT;
             goto fail;
         }
-
-        ret = gnutls_handshake(c->session);
+        if (s->is_dtls)
+            ret = dtls_handshake(h);
+        else
+            ret = gnutls_handshake(c->session);
         if (gnutls_error_is_fatal(ret)) {
             ret = print_tls_error(h, ret);
             goto fail;
@@ -243,13 +678,23 @@ fail:
     return ret;
 }
 
+static int dtls_open(URLContext *h, const char *uri, int flags, AVDictionary **options)
+{
+    TLSContext *c = h->priv_data;
+    TLSShared *s = &c->tls_shared;
+    s->is_dtls = 1;
+    return tls_open(h, uri, flags, options);
+}
+
 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 = gnutls_record_recv(c->session, buf, size);
     if (ret > 0)
         return ret;
@@ -261,10 +706,12 @@ static int tls_read(URLContext *h, uint8_t *buf, int size)
 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 = gnutls_record_send(c->session, buf, size);
     if (ret > 0)
         return ret;
@@ -309,3 +756,24 @@ 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_open,
+    .url_handshake  = dtls_handshake,
+    .url_read       = tls_read,
+    .url_write      = tls_write,
+    .url_close      = tls_close,
+    .url_get_file_handle = tls_get_file_handle,
+    .url_get_short_seek  = tls_get_short_seek,
+    .priv_data_size = sizeof(TLSContext),
+    .flags          = URL_PROTOCOL_FLAG_NETWORK,
+    .priv_data_class = &dtls_class,
+};
-- 
2.49.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-08-16 10:45 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-08-16 10:45 [FFmpeg-devel] [PATCH] avformat/tls_gnutls: add dtls support (PR #20254) Jack Lau via ffmpeg-devel

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git