[ZBXNEXT-3623] Decide in runtime which features of libcurl are available Created: 2016 Dec 21 Updated: 2024 Aug 09 Resolved: 2024 Apr 05 |
|
Status: | Closed |
Project: | ZABBIX FEATURE REQUESTS |
Component/s: | Agent (G), Proxy (P), Server (S) |
Affects Version/s: | 3.4.0alpha1 |
Fix Version/s: | 7.0.0beta2, 7.0 (plan) |
Type: | Change Request | Priority: | Minor |
Reporter: | Glebs Ivanovskis (Inactive) | Assignee: | dimir |
Resolution: | Fixed | Votes: | 5 |
Labels: | authentication, email, smtp | ||
Remaining Estimate: | Not Specified | ||
Time Spent: | Not Specified | ||
Original Estimate: | Not Specified |
Attachments: |
![]() |
||||||||||||||||||||||||||||
Issue Links: |
|
||||||||||||||||||||||||||||
Team: | |||||||||||||||||||||||||||||
Sprint: | S2401-1, S2401-2, S24-W6/7, S24-W12/13, S24-W14/15 | ||||||||||||||||||||||||||||
Story Points: | 12 |
Description |
libcurl has a very stable ABI, major so-version remains the same since 2006 Currently Zabbix decides which features of libcurl are available at compilation stage: # if 0x071004 >= LIBCURL_VERSION_NUM /* version 7.16.4 */ # define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD # endif # if 0x071400 <= LIBCURL_VERSION_NUM /* version 7.20.0 */ # define HAVE_SMTP_AUTHENTICATION 1 # endif Therefore, if Zabbix was compiled with old libcurl headers, upgrading libcurl will not enable missing features without Zabbix recompilation ( Zabbix should be able to adjust it's behaviour in runtime based on version information it can get from a libcurl binary currently loaded. |
Comments |
Comment by Glebs Ivanovskis [ 2019 Jun 26 ] |
Comment by bunkzilla [ 2019 Oct 01 ] |
This only seems logical that you'd want to base the code action based on what the running environment reflects. This would save customers the extra hurdle of having to compile from source to use elasticsearch.
|
Comment by Glebs Ivanovskis [ 2019 Dec 18 ] |
In addition, from libcurl tutorial:
|
Comment by dimir [ 2024 Jan 05 ] |
Implementing this would avoid having issues like |
Comment by dimir [ 2024 Jan 05 ] |
Especially so because now some distributions ship smaller libcurl-minimal packages, that miss many cURL features. |
Comment by dimir [ 2024 Jan 05 ] |
Related issue: |
Comment by dimir [ 2024 Jan 09 ] |
An example of curl_version_info usage to detect if run-time cURL library supports "smtp" and "smtp" protocols required for sending "non-plain text" mail. Instead of "Unsupported protocol" user will see an error message like cURL library was compiled without support for "smtps" protocol: ZBXNEXT-3623-zabbix-7.0.0alpha9-curl-protocols.patch In case of "plain-text" (no SSL, no auth) Emil media type Zabbix does not use cURL but sends e-mail using TCP protocol. |
Comment by dimir [ 2024 Jan 09 ] |
Mini specIn short, what needs to be done:
$ grep LIBCURL_ include/common/config.h.in #undef LIBCURL_FEATURE_ASYNCHDNS #undef LIBCURL_FEATURE_IDN #undef LIBCURL_FEATURE_IPV6 #undef LIBCURL_FEATURE_KRB4 #undef LIBCURL_FEATURE_LIBZ #undef LIBCURL_FEATURE_NTLM #undef LIBCURL_FEATURE_SSL #undef LIBCURL_FEATURE_SSPI #undef LIBCURL_PROTOCOL_DICT #undef LIBCURL_PROTOCOL_FILE #undef LIBCURL_PROTOCOL_FTP #undef LIBCURL_PROTOCOL_FTPS #undef LIBCURL_PROTOCOL_HTTP #undef LIBCURL_PROTOCOL_HTTPS #undef LIBCURL_PROTOCOL_LDAP #undef LIBCURL_PROTOCOL_TELNET #undef LIBCURL_PROTOCOL_TFTP
$ grep LIBCURL_VERSION_NUM include/common/zbxsysinc.h -B3 -A8 # if !defined(HAVE_FUNCTION_CURL_EASY_ESCAPE) # define curl_easy_escape(handle, string, length) curl_escape(string, length) # endif # if 0x071004 >= LIBCURL_VERSION_NUM /* version 7.16.4 */ # define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD # endif # if 0x071400 <= LIBCURL_VERSION_NUM /* version 7.20.0 */ # define HAVE_SMTP_AUTHENTICATION 1 # endif # if 0x071501 <= LIBCURL_VERSION_NUM /* version 7.21.6 */ # define ZBX_CURLOPT_ACCEPT_ENCODING CURLOPT_ACCEPT_ENCODING # else # define ZBX_CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING # endif # if 0x073700 <= LIBCURL_VERSION_NUM /* version 7.55.0 */ # define ZBX_CURLINFO_SPEED_DOWNLOAD CURLINFO_SPEED_DOWNLOAD_T # define ZBX_CURLINFO_SPEED_DOWNLOAD_TYPE curl_off_t # define ZBX_CURLINFO_SPEED_DOWNLOAD_FMT "%" CURL_FORMAT_CURL_OFF_T # else # define ZBX_CURLINFO_SPEED_DOWNLOAD CURLINFO_SPEED_DOWNLOAD # define ZBX_CURLINFO_SPEED_DOWNLOAD_TYPE double # define ZBX_CURLINFO_SPEED_DOWNLOAD_FMT ZBX_FS_DBL # endif
In addition it is suggested to increase curl version requirement to 7.19.0 or later. It was lowered to 7.13.1 in Probably all the places that need to be checked: grep -E '#.*if([n]?def)?.*CURL' * -r Approvals on the spec: |
Comment by Juris Lambda [ 2024 Jan 18 ] |
This report conflates features and interfaces. The first test, # if 0x071004 >= LIBCURL_VERSION_NUM, works around the fact that the CURLOPT_SSLKEYPASSWD option was renamed to CURLOPT_KEYPASSWD in 7.16.4, and itself was a rename of CURLOPT_CERTPASSWD in 7.9.2. This is a pre-processor symbol, which is obviously not something that can be decided upon in runtime. This is done to avoid #ifdef dances at call sites, as one, and only one, path will ever be valid. The same applies to the tests for CURLOPT_ACCEPT_ENCODING (which was called CURLOPT_ENCODING prior to 7.21.6), and the CURLOPT_SPEED_DOWNLOAD* symbols, which is due to CURLOPT_SPEED_DOWNLOAD being deprecated (not renamed) in favor of CURLOPT_SPEED_DOWNLOAD_T, which returns the result as a different type than the former (curl_off_t vs double), and again, is done to avoid duplicating code at call sites. These are interface issues, and their discrepancies need to be resolved during the pre-processing stage, as we'll fail compilation otherwise (missing symbols, data type mismatching). The only test that is out of place is the SMTP auth one, but only due to the way it is implemented (should be done via autoconf, which is what it is for, and not this odd version check in a header). In this case, the correct way to deal with this should be via attempting to set the CURLOPT_PROTOCOLS_STR option on the transfer handle to the protocol(s) we're interested in (SMTP and SMTPS in this case), and checking the return value from the curl_easy_setopt() call for the CURLE_UNSUPPORTED_PROTOCOL value. As for run-time feature detection - all libcurl functions returning a CURLcode type can return CURLE_NOT_BUILT_IN, to indicate that support for a particular protocol or feature was not built into the linked libcurl. This is the most straight forward and robust manner to do this. Another way to detect feature presence would be to interrogating the curl_version_info_data structure, as returned by a call to curl_version_info(), but due to the structure member presence being version dependent, this could be unreliable. Most notably, in the case when the struct definition during compile-time comes from an older version of libcurl than the version linked during run-time, making the later added members inaccessible to the application/wrapper library (admittedly, this would be an unusual situation). In my opinion, this is an inferior solution to just checking for CURLE_NOT_BUILT_IN, as this would require all handle setup code to have a preamble performing these checks, resulting in sprinkling functions with them, instead of just trying to do what needs to be done and failing in a straight forward manner. Overall, I have to concede that the libcurl wrapping code is a hot mess of hacks and should be refactored, but that is not really in the scope of this particular issue. |
Comment by Glebs Ivanovskis [ 2024 Jan 19 ] |
What do you mean? I would say, that it is Zabbix code that conflates features and interfaces. The fact, that code is compiled with libcurl headers >= 7.55 (and has CURLOPT_SPEED_DOWNLOAD_T defined) does not mean that it will run with libcurl shared library >= 7.55 and using CURLOPT_SPEED_DOWNLOAD_T is the best approach. I didn't say it explicitly in the issue Description, but I meant that in case Zabbix code does not have a libcurl header that defines CURLOPT_SPEED_DOWNLOAD_T, Zabbix code should define this constant itself using a value looked up in the latest libcurl headers, e.g. (based on https://github.com/curl/curl/blob/524253dc9078e69300c5390eda496603067f325f/include/curl/curl.h): # if 0x073700 <= LIBCURL_VERSION_NUM /* version 7.55.0 */ # define ZBX_CURLINFO_SPEED_DOWNLOAD_T CURLINFO_SPEED_DOWNLOAD_T # define ZBX_CURLINFO_SPEED_DOWNLOAD CURLINFO_DOUBLE + 9 # else # define ZBX_CURLINFO_SPEED_DOWNLOAD_T CURLINFO_OFF_T + 9 # define ZBX_CURLINFO_SPEED_DOWNLOAD CURLINFO_SPEED_DOWNLOAD # endif At runtime I would expect Zabbix to try in turn both ZBX_CURLINFO_SPEED_DOWNLOAD_T and ZBX_CURLINFO_SPEED_DOWNLOAD before failing. Of course, it is sufficient to check return values at call sites ("ask for forgiveness" and all that jazz), but it would be also nice and user-friendly to fail/detect issues as soon as possible. Over the years Zabbix already did the research, which versions of libcurl support what. curl_version_info_data is guaranteed to contain version number. It is a matter of a simple if on Zabbix startup to see if a certain libcurl-based functionality is supposed to work. |
Comment by Juris Lambda [ 2024 Jan 22 ] |
Hey, cyclone! Allow me to clarify my earlier statement that "This report conflates features and interfaces". What I meant was runtime-features and source-interfaces. These symbols (for example, CURLINFO_SPEED_DOWNLOAD_T) are resolved during the preprocessing stage, and depending on the version of the libcurl development header and library version on the build system, one or the other symbol may not be present; hence the conditional definition of ZBX_CURLINFO_SPEED_DOWNLOAD_T. This may be the case when the development header comes from a version of libcurl that didn't yet define CURLINFO_SPEED_DOWNLOAD_T (anything prior to 7.55.0), or the opposite case, when building with a header that no longer ships the old symbol (CURLINFO_SPEED_DOWNLOAD). The default preference is to use the newer option, if present. This is that best approach in action.
I'm not sure what you mean by the following: The fact, that code is compiled with libcurl headers >= 7.55 (and has CURLOPT_SPEED_DOWNLOAD_T defined) does not mean that it will run with libcurl shared library >= 7.55 and using CURLOPT_SPEED_DOWNLOAD_T is the best approach. As you yourself previously mentioned, libcurl's ABI is very stable, which is exactly why the above allows Zabbix to work with arbitrary versions of libcurl linked at runtime - newer or older. The immediate example of that here are the CURLOPT_CERTPASSWD / CURLOPT_SSLKEYPASSWD / CURLOPT_KEYPASSWD options, which changed names, but reused the value, to stay ABI compatible. Moreover, a library header version >= 7.55.x is much less likely to need as many of these wrappers to account for source interface changes than something like 7.13.1, which is the current minimum supported version. As soon as we raise the minimum version requirement, they should be removed.
Regarding: I didn't say it explicitly in the issue Description, but I meant that in case Zabbix code does not have a libcurl header that defines CURLOPT_SPEED_DOWNLOAD_T, Zabbix code should define this constant itself using a value looked up in the latest libcurl headers, e.g. (based on https://github.com/curl/curl/blob/524253dc9078e69300c5390eda496603067f325f/include/curl/curl.h): [...] At runtime I would expect Zabbix to try in turn both ZBX_CURLINFO_SPEED_DOWNLOAD_T and ZBX_CURLINFO_SPEED_DOWNLOAD before failing. As for the definition of the CURLINFO_SPEED_DOWNLOAD_T being done by Zabbix itself – I'm not entirely sure why we would want to do that. The development library installation already provides the header, and having a copy of a fragment, that then requires maintenance to stay in sync with upstream's choice of defining the symbol, is simply unnecessary. This is why we define the ZBX_CURLINFO_* symbols to whatever they resolve to, and only work around the fact that one or the other symbol may or may not be defined by the header. To the second portion of the quote - this only applies to symbols that have been deprecated and not removed (as is the case with CURLINFO_SPEED_DOWNLOAD_T). We would still have to work with the fact that the installed library may be older than the runtime, and thus be unaware of the new option (as that symbol wasn't defined for the preprocessor) and type(s) (which were not defined by the interface header). IMHO, at this point, I would consider creating a wrapper (i.e. libzbxcurl) for libcurl, that would perform the feature and option detection as needed, depending on the option requested. As the number of these cases is small, the wrapper would be terse and compact, and most cases would be mostly direct parameters passing. This would
This will not, however, resolve the fact that the user code will still be dependent on what symbols were provided by the development header, and not all cases will always be covered. |
Comment by Glebs Ivanovskis [ 2024 Jan 22 ] |
Apparently, your definition of "work" is very generous. I would advice you to read through
If you would compile Zabbix with newer libcurl headers and then downgrade libcurl, the situation would be symmetric, but equally bad - Zabbix would claim that SMTP is supported, but would fail to send emails. Zabbix only "works with arbitrary versions of libcurl" only if you are ready to compile Zabbix from source yourself. If you upgrade or downgrade libcurl after compiling Zabbix, Zabbix will not adjust. The whole point of this feature request is to make Zabbix aware of libcurl version that is currently loaded. Zabbix should be able to work with arbitrary versions of libcurl without recompilation. |
Comment by Glebs Ivanovskis [ 2024 Jan 22 ] |
It is necessary for cases when available development headers are too old to too new to provide all potentially useful constants. Speaking of staying in sync, I don't see a problem there.
In my opinion it is safe to conclude, that numeric values of the constants will not change in the foreseeable future, hence there will be no need to sync them. |
Comment by Juris Lambda [ 2024 Jan 23 ] |
Hi, cyclone.
Regarding your first bullet point in your first comment:
We build against the version of libcurl development library as shipped by the distributions we build packages for. If the user chooses to install the package on a system that ships a different version and feature set of a library, the user should expect discrepancies. What you're asking, in brief, requires that we
We do (1) already, as the autoconf generated configure will refuse to generate the build system, if the minimum version is met. There is the issue that the current minimum version requirement is out of date, and needs to be updated, as we unconditionally rely on interfaces only available starting with 7.19.2. This is captured in ZBX-22649. (2) is not free. For a start, some policy needs to be in place, deciding the upstream channel we intend to build against; most likely, a choice between current development and latest stable release. I personally would opt for stability, to have prep time for addressing any breaking changes that may occur in development. We generally don't practice merging individual artifacts from our dependency upstream (with some key exceptions), nor would we want to, as it introduces additional maintenance burden, and is a clunky process to go about in general. This would thus require updating the build environment creation and possibly the build procedure itself, to include the building of the development library to be used in the building of Zabbix libraries and applications themselves. It is generally the responsibility of whomever builds the package to provide the build process with the appropriate build environment for their configuration. I'm not against this, and believe this to be a generally sound approach, as it would allow the user to always make use of the most recent stable version features of the library, be it libcurl or any other library. This is also technically viable. I suppose the deciding factor here is work resource. I think it would be worthwhile consider this as a generally policy for all external library dependencies, where viable. (3) we already do, except that the driver for introducing new abstractions are the versions provided by the distributions we build packages for, and not builds of any latest versions (see (2)).
Regarding your second comment: Again, I think it to be unnecessary to maintain a copy of the interface header, as it is already provided by a built development library package, and (2) from the previous section would solve this issue, as the header used during pre-processing would not really be "too new" ever again. The additional necessary work I see this requiring is:
(1) requires further elaboration on when and where to do it, but it's safe to say this is viable as well. Makes me lean even more towards introducing a libzbxcurl for handling all these issues. (2) can be solved via CURLE_UNSUPPORTED_PROTOCOL and CURLE_NOT_BUILT_IN handling as I mentioned previously. Again, to reiterate, I see this as the best option.
In essence, to resolve the issues you mentioned, and avoid breaking user expectations as to feature availability, we should do (2) from this posts response to your first comment, and (2) from the second one. Both are technically viable and I personally agree with the final benefit. |
Comment by dimir [ 2024 Feb 15 ] |
Fixed in development branch |
Comment by dimir [ 2024 Mar 07 ] |
Great cURL page where you can seek information about every public symbol and cURL version in which it was added: |
Comment by dimir [ 2024 Mar 13 ] |
(5) Found one case where the feature was decided at build-time, file src/libs/zbxhttp/http.c: #ifdef CURLH_HEADER ... #else ... #endif Need to change that in run-time detection of CURLH. <dimir> RESOLVED in 352b97a917d <yurii> CLOSED |
Comment by dimir [ 2024 Mar 15 ] |
OverviewTL;DRThis changes the way Zabbix detects curl library features when working with it. Before this change:
After this change:
More detailsNow when you e. g. upgrade your curl library that has new features these features will be available in Zabbix upon restart. Re-compilation of Zabbix is no longer required. This is true for the following Zabbix components:
In addition, what was changed:
Fixed in
|
Comment by Martins Valkovskis [ 2024 Mar 18 ] |
Updated documentation:
|