ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

curl_global_sslset:libcurl 运行时选择 SSL 后端的完整指南

curl_global_sslset:libcurl 运行时选择 SSL 后端的完整指南 curl_global_sslsetlibcurl 运行时选择 SSL 后端的完整指南【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl本文基于 curl 仓库中的官方文档 docs/libcurl/curl_global_sslset.md 编写系统讲解curl_global_sslset()的函数原型、参数语义、返回码与后端命名规则并结合 lib/vtls/vtls.c、lib/easy.c 的源码实现剖析多后端multi-SSL构建下后端选择、环境变量的回退机制以及线程安全的底层细节帮助你在同一份 libcurl 二进制中按需切换 GnuTLS、OpenSSL、wolfSSL、mbedTLS、Rustls、Schannel 等不同 TLS 栈。1. 这个函数解决什么问题libcurl 编译时可以同时链接多个 TLS 库所谓 multi-SSL 构建例如同时编入 OpenSSL 和 GnuTLS。默认情况下库在初始化时选择某一个后端而curl_global_sslset()允许应用程序在运行时、且必须在curl_global_init()之前显式地指定要使用哪一个后端或先枚举当前构建中实际可用哪些后端。该函数自 libcurl 7.56.0 起提供见文档 front-matter 的Added-in: 7.56.0。2. 函数原型与数据结构头文件 include/curl/curl.h 中的声明如下#include curl/curl.h CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail);三个参数参数说明id后端枚举值如CURLSSLBACKEND_OPENSSL传CURLSSLBACKEND_NONE表示不按 id 指定name后端的字符串名大小写不敏感匹配当id与name同时给出时name被忽略avail输出参数指向后端的const curl_ssl_backend **列表的指针以 NULL 结尾传 NULL 表示不关心列表支持的后端名称大小写不敏感为GnuTLS、mbedTLS、OpenSSL、Rustls、Schannel、wolfSSL。其中 OpenSSL 这个名字涵盖了所有 OpenSSL 及其分支/衍生版本——AmiSSL、AWS-LC、BoringSSL、LibreSSL、quictls 等在curl_global_sslset()眼里都叫 OpenSSL因为它们大体提供相同的 API若要知道确切的分支与版本号应通过curl_version_info()获取。2.1 curl_sslbackend 枚举完整枚举同样定义在 include/curl/curl.h 附近文档 docs/libcurl/curl_global_sslset.md 给出了等价摘录typedef struct { curl_sslbackend id; const char *name; } curl_ssl_backend; typedef enum { CURLSSLBACKEND_NONE 0, CURLSSLBACKEND_OPENSSL 1, /* or one of its forks */ CURLSSLBACKEND_GNUTLS 2, CURLSSLBACKEND_NSS 3, CURLSSLBACKEND_GSKIT 5, /* deprecated */ CURLSSLBACKEND_POLARSSL 6, /* deprecated */ CURLSSLBACKEND_WOLFSSL 7, CURLSSLBACKEND_SCHANNEL 8, CURLSSLBACKEND_SECURETRANSPORT 9, /* deprecated */ CURLSSLBACKEND_AXTLS 10, /* deprecated */ CURLSSLBACKEND_MBEDTLS 11, CURLSSLBACKEND_MESALINK 12, /* deprecated */ CURLSSLBACKEND_BEARSSL 13, /* deprecated */ CURLSSLBACKEND_RUSTLS 14 } curl_sslbackend;注意 GnuTLS、NSS、GSKIT、POLARSSL、SECURETRANSPORT、AXTLS、MESALINK、BEARSSL 等条目中标注了deprecated实际可用的后端集合取决于你的 libcurl 是如何构建的。2.2 CURLsslset 返回码typedef enum { CURLSSLSET_OK 0, CURLSSLSET_UNKNOWN_BACKEND, CURLSSLSET_TOO_LATE, CURLSSLSET_NO_BACKENDS /* libcurl was built without any SSL support */ } CURLsslset;各返回值的准确含义来自文档 RETURN VALUE 一节CURLSSLSET_OK后端选择成功。CURLSSLSET_UNKNOWN_BACKEND指定的后端未知或该后端没有被编译进这份 libcurl。此时函数会把avail指向一个 NULL 结尾的可用后端列表你可以据此重新选择另一个后端再调用一次。CURLSSLSET_TOO_LATE后端之前已经设置过或者curl_global_init()已经被调用过——后端只能设置一次。CURLSSLSET_NO_BACKENDS这份 libcurl 完全不带 SSL 支持没有编译任何后端。3. 参数组合的典型用法调用方式行为curl_global_sslset(CURLSSLBACKEND_WOLFSSL, NULL, NULL)按 id 选择 wolfSSLcurl_global_sslset(CURLSSLBACKEND_NONE, openssl, NULL)按名字大小写不敏感选择后端curl_global_sslset(CURLSSLBACKEND_NONE, NULL, list)id 与 name 都不指定返回CURLSSLSET_UNKNOWN_BACKEND并把avail设为当前构建中所有可用后端的 NULL 结尾列表用于枚举curl_global_sslset(id, name, NULL)id 与 name 同时给出时name 被忽略仅按 id 匹配一个值得记住的版本行为差异自 libcurl 7.60.0 起只要avail指针非 NULL它就总是会被设置为备选后端列表——而不仅仅是失败时。3.1 文档附带的示例程序int main(void) { const curl_ssl_backend **list; int i; /* choose a specific backend */ curl_global_sslset(CURLSSLBACKEND_WOLFSSL, NULL, NULL); /* list the available ones */ curl_global_sslset(CURLSSLBACKEND_NONE, NULL, list); for(i 0; list[i]; i) printf(SSL backend #%d: %s (ID: %u)\n, i, list[i]-name, list[i]-id); }3.2 仓库中的完整示例docs/examples/sslbackend.c仓库提供了一个更贴近实战的官方示例 docs/examples/sslbackend.c它支持三种命令行输入int main(int argc, const char *argv[]) { const char *name argc 1 ? argv[1] : openssl; CURLsslset result; if(!strcmp(list, name)) { const curl_ssl_backend **list; int i; (void)curl_global_sslset(CURLSSLBACKEND_NONE, NULL, list); for(i 0; list[i]; i) printf(SSL backend #%d: %s (ID: %d)\n, i, list[i]-name, (int)list[i]-id); return 0; } else if(isdigit((int)(unsigned char)*name)) { int id atoi(name); result curl_global_sslset((curl_sslbackend)id, NULL, NULL); } else result curl_global_sslset(CURLSSLBACKEND_NONE, name, NULL); if(result CURLSSLSET_UNKNOWN_BACKEND) { fprintf(stderr, Unknown SSL backend id: %s\n, name); return 1; } printf(Version with SSL backend %s:\n\n\t%s\n, name, curl_version()); return 0; }该示例覆盖了三种调用姿势传list时仅枚举后端不真正选定传数字时按 id 选择否则按名字选择最后用curl_version()打印当前生效后端的版本串以供确认。注意该示例要求 libcurl 至少 7.56.0且编译时至少带有一个 SSL 后端。4. 源码级实现剖析4.1 入口全局锁包裹的薄封装对外函数体只有十几行位于 lib/easy.cCURLsslset curl_global_sslset(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail) { CURLsslset rc; global_init_lock(); rc Curl_init_sslset_nolock(id, name, avail); global_init_unlock(); return rc; }可以看到它先获取与curl_global_init()共享的全局初始化锁global_init_lock()再调用真正的选择逻辑Curl_init_sslset_nolock()。这也解释了文档中线程安全性条目的来源锁保护了选择后端这一全局状态变更过程。4.2 核心选择逻辑Curl_init_sslset_nolock真正的实现在 lib/vtls/vtls.c多后端分支CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail) { int i; if(avail) *avail (const curl_ssl_backend **)available_backends; if(Curl_ssl ! Curl_ssl_multi) return id Curl_ssl-info.id || (name curl_strequal(name, Curl_ssl-info.name)) ? CURLSSLSET_OK : #ifdef CURL_WITH_MULTI_SSL CURLSSLSET_TOO_LATE; #else CURLSSLSET_UNKNOWN_BACKEND; #endif for(i 0; available_backends[i]; i) { if(available_backends[i]-info.id id || (name curl_strequal(available_backends[i]-info.name, name))) { multissl_setup(available_backends[i]); return CURLSSLSET_OK; } } return CURLSSLSET_UNKNOWN_BACKEND; }从源码结构可以确认文档描述的每条行为avail 总是被填充函数第一行就把*avail指向内部available_backends数组只要传入了非 NULL 指针这正是 7.60.0 起avail 总是被设置的实现。后端已选定时的行为全局变量Curl_ssl一旦不等于哨兵值Curl_ssl_multi说明后端已被确定包括被curl_global_init()隐式确定的情况再调用本函数时如果传入的 id 或 name 恰好就是当前已选中的那个后端返回CURLSSLSET_OK幂等重试不报错否则多 SSL 构建返回CURLSSLSET_TOO_LATE单后端构建则返回CURLSSLSET_UNKNOWN_BACKEND——这解释了为什么单后端 libcurl 上选错后端不会得到 TOO_LATE 而会得到 UNKNOWN_BACKEND。未选定时遍历匹配逐个比较available_backends[i]的info.id与 id、info.name与 name用curl_strequal做大小写不敏感比较命中后调用multissl_setup()完成切换并返回 OK一个都没命中则返回CURLSSLSET_UNKNOWN_BACKEND。完全无 SSL 的构建在#else /* USE_SSL */分支lib/vtls/vtls.c中函数直接返回CURLSSLSET_NO_BACKENDS。4.3 multissl_setup环境变量与编译期默认值选定后端后实际执行切换的是 multissl_setup()从源码结构看它体现了完整的回退链static int multissl_setup(const struct Curl_ssl *backend) { ... if(backend) { Curl_ssl backend; return 0; } if(!available_backends[0]) return 1; env curl_getenv(CURL_SSL_BACKEND); if(env) { for(i 0; available_backends[i]; i) { if(curl_strequal(env, available_backends[i]-info.name)) { Curl_ssl available_backends[i]; curlx_free(env); return 0; } } } #ifdef CURL_DEFAULT_SSL_BACKEND ... /* 匹配编译期默认后端 */ #endif /* Fall back to first available backend */ Curl_ssl available_backends[0]; curlx_free(env); return 0; }通过curl_global_sslset()显式选定时backend非空直接生效未显式选择时先读环境变量CURL_SSL_BACKEND与后端名字字符串做大小写不敏感匹配再匹配编译期定义的CURL_DEFAULT_SSL_BACKEND若构建时设置了该宏都不满足则回退到可用列表中的第一个后端。这提示运维侧即使不改代码也可以在多后端构建中用CURL_SSL_BACKEND环境变量影响默认选择而程序代码中调用curl_global_sslset()的优先级则体现在显式传参时直接覆盖上述回退链。4.4 与 curl_global_init 的先后关系官方文档 docs/libcurl/curl_global_init.md 强调curl_global_init()应在应用程序中恰好调用一次且先于其他 libcurl 函数而curl_global_sslset()必须在其之前调用且一生只能有效设置一次。从实现上看curl_easy_init()lib/easy.c在未显式初始化时会内部触发global_init()同样会把后端确定下来——所以哪怕你从未调用过curl_global_init()只要已经创建过 easy handle之后再调curl_global_sslset()选不同后端也会得到CURLSSLSET_TOO_LATE。5. 线程安全说明文档明确自 libcurl 7.84.0 起若curl_version_info(3)参见 docs/libcurl/curl_version_info.md返回的 features 位中设置了CURL_VERSION_THREADSAFE在 include/curl/curl.h 中定义为130覆盖大多数平台本函数是线程安全的。若该构建不具备线程安全约束则非常严格不能在任何其他线程共享同一块内存的线程而不仅仅是其他使用 libcurl 的线程正在运行时调用它。结合 4.1 节的实现可以推断锁保护的是选择动作本身但旧构建下全局初始化状态的其他读写路径未必都在同一把锁内因此文档才要求全进程级别的互斥。6. 测试与符号导出佐证库级测试 tests/libtest/lib758.c 展示了标准调用顺序if(curl_global_sslset(CURLSSLBACKEND_OPENSSL, NULL, NULL) ! CURLSSLSET_OK) { t758_msg(could not set OpenSSL as backend); result CURLE_FAILED_INIT; return result; } res_global_init(CURL_GLOBAL_ALL);即先 sslset、再 global_init并检查返回值是否为CURLSSLSET_OK这是推荐的防御式写法。回归测试 tests/data/test1135 校验 libcurl 的CURL_EXTERN导出符号顺序VMS 与 OS/400 构建依赖此顺序curl_global_sslset位于导出表curl_global_trace之后保证二进制兼容性的约束同样适用于该 API。7. 实践要点清单调用时机curl_global_sslset()必须早于curl_global_init()且早于任何 easy/multi handle 的创建后端选定后不可再改CURLSSLSET_TOO_LATE。id 优先于 name两者同时给出时按 id 匹配只想按名字选时 id 传CURLSSLBACKEND_NONE。枚举可用后端传(CURLSSLBACKEND_NONE, NULL, list)拿到 NULL 结尾列表7.60.0 起avail非 NULL 时始终被填充可放心用于先探测、再选择的两段式流程。区分两个错误码CURLSSLSET_UNKNOWN_BACKEND意味着这份构建里没有你要的后端可重试其他后端CURLSSLSET_TOO_LATE意味着选择窗口已关闭只能重新进程级初始化。确认最终生效后端选择后用curl_version()/curl_version_info()打印ssl_version字段核对尤其是 OpenSSL 家族各分支在该 API 眼中同名的问题只有版本信息能揭示真实身份。【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表