From 8f088aa0f7ba5a4b955a20ad2854465af3522e84 Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Sun, 16 Oct 2022 17:03:53 +0800 Subject: [PATCH] res_http_media_cache: Introduce options and customize Make the existing CURL parameters configurable and allow to specify the usable protocols, proxy and DNS timeout. ASTERISK-30340 Change-Id: I2eb02ef44190e026716720419bcbdbcc8125777b --- .../samples/res_http_media_cache.conf.sample | 69 +++++ doc/CHANGES-staging/res_http_media_cache.txt | 12 + res/res_http_media_cache.c | 257 +++++++++++++++++- 3 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 configs/samples/res_http_media_cache.conf.sample create mode 100644 doc/CHANGES-staging/res_http_media_cache.txt diff --git a/configs/samples/res_http_media_cache.conf.sample b/configs/samples/res_http_media_cache.conf.sample new file mode 100644 index 0000000000..925156bcbd --- /dev/null +++ b/configs/samples/res_http_media_cache.conf.sample @@ -0,0 +1,69 @@ +; +; Sample configuration for res_http_media_cache +; +; res_http_media_cache is the HTTP backend for the core media cache. The +; following options can be used to tune the behavior of the implementation +; or left as default. +; +; See the module's and cURL's documentation for the exact meaning of these +; options. + + +[general] +; Maximum time in seconds the transfer is allowed to complete in. +; +; See https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html for details. +; +;timeout_secs = 180 + + +; The HTTP User-Agent to use for requests. +; +; See https://curl.se/libcurl/c/CURLOPT_USERAGENT.html for details. +; +;user_agent = asterisk-libcurl-agent/1.0 + + +; Follow HTTP 3xx redirects on requests. This can be combined with the +; max_redirects option to limit the number of times a redirect will be +; followed per request. +; +; See https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html for details. +; +;follow_location = false + + +; The maximum number of redirects to follow. +; +; See https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html for details. +; +;max_redirects = 8 + +; The HTTP/HTTPS proxy to use for requests. Leave unspecified to not use +; a proxy. This can be a URL with scheme, host and port. +; +; See https://curl.se/libcurl/c/CURLOPT_PROXY.html for details. +; +;proxy = https://localhost:1234 + + +; The life-time for DNS cache entries. +; +; See https://curl.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html for details. +; +;dns_cache_timeout_secs = 60 + + +; The comma separated list of allowed protocols for the request. Available with +; cURL version 7.85.0 or later. +; See https://curl.se/libcurl/c/CURLOPT_PROTOCOLS_STR.html for details. +; +;protocols = http,https + +; The comma separated list of allowed protocols for redirects. Available with +; cURL version 7.85.0 or later. This can be used to prevent a redirect from +; a protocol like HTTPS to another supported protocol of cURL. +; +; See https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html for details. +; +;redirect_protocols = http,https diff --git a/doc/CHANGES-staging/res_http_media_cache.txt b/doc/CHANGES-staging/res_http_media_cache.txt new file mode 100644 index 0000000000..79223c0339 --- /dev/null +++ b/doc/CHANGES-staging/res_http_media_cache.txt @@ -0,0 +1,12 @@ +Subject: res_http_media_cache + +The res_http_media_cache module now attempts to load +configuration from the res_http_media_cache.conf file. +The following options were added: + * timeout_secs + * user_agent + * follow_location + * max_redirects + * protocols + * redirect_protocols + * dns_cache_timeout_secs diff --git a/res/res_http_media_cache.c b/res/res_http_media_cache.c index b4c4c65c3e..73386febef 100644 --- a/res/res_http_media_cache.c +++ b/res/res_http_media_cache.c @@ -31,6 +31,41 @@ core ***/ +/*** DOCUMENTATION + + HTTP media cache + + + General configuration + + The maximum time the transfer is allowed to complete in seconds. See https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html for details. + + + The HTTP User-Agent to use for requests. See https://curl.se/libcurl/c/CURLOPT_USERAGENT.html for details. + + + Follow HTTP 3xx redirects on requests. See https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html for details. + + + The maximum number of redirects to follow. See https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html for details. + + + The proxy to use for requests. See https://curl.se/libcurl/c/CURLOPT_PROXY.html for details. + + + The comma separated list of allowed protocols for the request. Available with cURL 7.85.0 or later. See https://curl.se/libcurl/c/CURLOPT_PROTOCOLS_STR.html for details. + + + The comma separated list of allowed protocols for redirects. Available with cURL 7.85.0 or later. See https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html for details. + + + The life-time for DNS cache entries. See https://curl.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html for details. + + + + +***/ + #include "asterisk.h" #include @@ -44,6 +79,123 @@ #define MAX_HEADER_LENGTH 1023 +#ifdef CURL_AT_LEAST_VERSION +#if CURL_AT_LEAST_VERSION(7, 85, 0) +#define AST_CURL_HAS_PROTOCOLS_STR 1 +#endif +#endif + +static int http_media_cache_config_pre_apply(void); + +/*! \brief General configuration options for http media cache. */ +struct conf_general_options { + /*! \brief Request timeout to use */ + int curl_timeout; + + /*! \brief Follow 3xx redirects automatically. */ + int curl_followlocation; + + /*! \brief Number of redirects to follow for one request. */ + int curl_maxredirs; + + /*! \brief Life-time of CURL DNS cache entries. */ + int curl_dns_cache_timeout; + + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(curl_useragent); /*! \brief User-agent to use for requests. */ + AST_STRING_FIELD(curl_proxy); /*! \brief Proxy to use for requests. None by default. */ + AST_STRING_FIELD(curl_protocols); /*! \brief Allowed protocols to use for requests. All by default. */ + AST_STRING_FIELD(curl_redir_protocols); /*! \brief Allowed protocols to use on redirect. All by default. */ + ); +}; + +/*! \brief All configuration options for http media cache. */ +struct conf { + /*! The general section configuration options. */ + struct conf_general_options *general; +}; + +/*! \brief Locking container for safe configuration access. */ +static AO2_GLOBAL_OBJ_STATIC(confs); + +/*! \brief Mapping of the http media cache conf struct's general to the general context in the config file. */ +static struct aco_type general_option = { + .type = ACO_GLOBAL, + .name = "general", + .item_offset = offsetof(struct conf, general), + .category = "general", + .category_match = ACO_WHITELIST_EXACT, +}; + +static struct aco_type *general_options[] = ACO_TYPES(&general_option); + +/*! \brief Disposes of the http media cache conf object */ +static void conf_destructor(void *obj) +{ + struct conf *cfg = obj; + ast_string_field_free_memory(cfg->general); + ao2_cleanup(cfg->general); +} + +/*! \brief Creates the http media cache conf object. */ +static void *conf_alloc(void) +{ + struct conf *cfg; + + if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) { + return NULL; + } + + if (!(cfg->general = ao2_alloc(sizeof(*cfg->general), NULL))) { + ao2_ref(cfg, -1); + return NULL; + } + + if (ast_string_field_init(cfg->general, 256)) { + ao2_ref(cfg, -1); + return NULL; + } + + return cfg; +} + +/*! \brief The conf file that's processed for the module. */ +static struct aco_file conf_file = { + /*! The config file name. */ + .filename = "res_http_media_cache.conf", + /*! The mapping object types to be processed. */ + .types = ACO_TYPES(&general_option), +}; + +CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc, + .pre_apply_config = http_media_cache_config_pre_apply, + .files = ACO_FILES(&conf_file)); + +/*! + * \brief Pre-apply callback for the config framework. + * + * This validates that used options match the ones supported by CURL. + */ +static int http_media_cache_config_pre_apply(void) +{ +#ifndef AST_CURL_HAS_PROTOCOLS_STR + struct conf *cfg = aco_pending_config(&cfg_info); + + if (!ast_strlen_zero(cfg->general->curl_protocols)) { + ast_log(AST_LOG_ERROR, "'protocols' not supported by linked CURL library. Please recompile against newer CURL.\n"); + return -1; + } + + if (!ast_strlen_zero(cfg->general->curl_redir_protocols)) { + ast_log(AST_LOG_ERROR, "'redirect_protocols' not supported by linked CURL library. Please recompile against newer CURL.\n"); + return -1; + } +#endif + + return 0; +} + + /*! \brief Data passed to cURL callbacks */ struct curl_bucket_file_data { /*! The \c ast_bucket_file object that caused the operation */ @@ -324,6 +476,8 @@ static int bucket_file_expired(struct ast_bucket_file *bucket_file) */ static CURL *get_curl_instance(struct curl_bucket_file_data *cb_data) { + RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup); + CURLcode rc; CURL *curl; curl = curl_easy_init(); @@ -332,14 +486,47 @@ static CURL *get_curl_instance(struct curl_bucket_file_data *cb_data) } curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 180); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback); - curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 8); curl_easy_setopt(curl, CURLOPT_URL, ast_sorcery_object_get_id(cb_data->bucket_file)); curl_easy_setopt(curl, CURLOPT_HEADERDATA, cb_data); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, cfg->general->curl_timeout); + curl_easy_setopt(curl, CURLOPT_USERAGENT, cfg->general->curl_useragent); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, cfg->general->curl_followlocation ? 1 : 0); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, cfg->general->curl_maxredirs); + + if (!ast_strlen_zero(cfg->general->curl_proxy)) { + curl_easy_setopt(curl, CURLOPT_PROXY, cfg->general->curl_proxy); + } + + if (!ast_strlen_zero(cfg->general->curl_protocols)) { +#ifdef AST_CURL_HAS_PROTOCOLS_STR + CURLcode rc = curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, cfg->general->curl_protocols); + if (rc != CURLE_OK) { + ast_log(AST_LOG_ERROR, "Setting protocols to '%s' failed: %d\n", cfg->general->curl_protocols, rc); + curl_easy_cleanup(curl); + return NULL; + } +#endif + } + if (!ast_strlen_zero(cfg->general->curl_redir_protocols)) { +#ifdef AST_CURL_HAS_PROTOCOLS_STR + CURLcode rc = curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, cfg->general->curl_redir_protocols); + if (rc != CURLE_OK) { + ast_log(AST_LOG_ERROR, "Setting redirect_protocols to '%s' failed: %d\n", cfg->general->curl_redir_protocols, rc); + curl_easy_cleanup(curl); + return NULL; + } +#endif + } + + rc = curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, cfg->general->curl_dns_cache_timeout); + if (rc != CURLE_OK) { + ast_log(AST_LOG_ERROR, "Setting dns_cache_timeout to '%d' failed: %d\n", cfg->general->curl_dns_cache_timeout, rc); + curl_easy_cleanup(curl); + return NULL; + } + return curl; } @@ -541,11 +728,73 @@ static struct ast_sorcery_wizard https_bucket_file_wizard = { static int unload_module(void) { + aco_info_destroy(&cfg_info); + ao2_global_obj_release(confs); return 0; } static int load_module(void) { + if (aco_info_init(&cfg_info)) { + aco_info_destroy(&cfg_info); + return AST_MODULE_LOAD_DECLINE; + } + + + aco_option_register(&cfg_info, "timeout_secs", ACO_EXACT, general_options, + "180", OPT_INT_T, 0, + FLDSET(struct conf_general_options, curl_timeout)); + + aco_option_register(&cfg_info, "user_agent", ACO_EXACT, general_options, + AST_CURL_USER_AGENT, OPT_STRINGFIELD_T, 0, + STRFLDSET(struct conf_general_options, curl_useragent)); + + aco_option_register(&cfg_info, "follow_location", ACO_EXACT, general_options, + "yes", OPT_BOOL_T, 1, + FLDSET(struct conf_general_options, curl_followlocation)); + + aco_option_register(&cfg_info, "max_redirects", ACO_EXACT, general_options, + "8", OPT_INT_T, 0, + FLDSET(struct conf_general_options, curl_maxredirs)); + + aco_option_register(&cfg_info, "proxy", ACO_EXACT, general_options, + NULL, OPT_STRINGFIELD_T, 1, + STRFLDSET(struct conf_general_options, curl_proxy)); + + aco_option_register(&cfg_info, "dns_cache_timeout_secs", ACO_EXACT, general_options, + "60", OPT_INT_T, 0, + FLDSET(struct conf_general_options, curl_dns_cache_timeout)); + + aco_option_register(&cfg_info, "protocols", ACO_EXACT, general_options, + NULL, OPT_STRINGFIELD_T, 1, + STRFLDSET(struct conf_general_options, curl_protocols)); + + aco_option_register(&cfg_info, "redirect_protocols", ACO_EXACT, general_options, + NULL, OPT_STRINGFIELD_T, 1, + STRFLDSET(struct conf_general_options, curl_redir_protocols)); + + + if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) { + struct conf *cfg; + + ast_log(LOG_NOTICE, "Could not load res_http_media_cache config; using defaults\n"); + cfg = conf_alloc(); + if (!cfg) { + aco_info_destroy(&cfg_info); + return AST_MODULE_LOAD_DECLINE; + } + + if (aco_set_defaults(&general_option, "general", cfg->general)) { + ast_log(LOG_ERROR, "Failed to initialize res_http_media_cache defaults.\n"); + ao2_ref(cfg, -1); + aco_info_destroy(&cfg_info); + return AST_MODULE_LOAD_DECLINE; + } + + ao2_global_obj_replace_unref(confs, cfg); + ao2_ref(cfg, -1); + } + if (ast_bucket_scheme_register("http", &http_bucket_wizard, &http_bucket_file_wizard, NULL, NULL)) { ast_log(LOG_ERROR, "Failed to register Bucket HTTP wizard scheme implementation\n");