diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-http/http-client-host.c | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/src/lib-http/http-client-host.c b/src/lib-http/http-client-host.c new file mode 100644 index 0000000..647ab66 --- /dev/null +++ b/src/lib-http/http-client-host.c @@ -0,0 +1,500 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "str.h" +#include "hash.h" +#include "array.h" +#include "llist.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "time-util.h" +#include "dns-lookup.h" +#include "http-response-parser.h" + +#include "http-client-private.h" + +#define HTTP_CLIENT_HOST_MINIMUM_IDLE_TIMEOUT_MSECS 100 + +static void http_client_host_lookup_done(struct http_client_host *host); +static void +http_client_host_lookup_failure(struct http_client_host *host, + const char *error); +static bool http_client_host_is_idle(struct http_client_host *host); +static void http_client_host_free_shared(struct http_client_host **_host); + +/* + * Host (shared) + */ + +static void +http_client_host_shared_idle_timeout(struct http_client_host_shared *hshared) +{ + e_debug(hshared->event, "Idle host timed out"); + http_client_host_shared_free(&hshared); +} + +static void +http_client_host_shared_check_idle(struct http_client_host_shared *hshared) +{ + struct http_client_host *host; + int timeout = 0; + + if (hshared->destroyed) + return; + if (hshared->to_idle != NULL) + return; + + host = hshared->hosts_list; + while (host != NULL) { + if (!http_client_host_is_idle(host)) + return; + host = host->shared_next; + } + + if (!hshared->unix_local && !hshared->explicit_ip && + hshared->ips_timeout.tv_sec > 0) { + timeout = timeval_diff_msecs(&hshared->ips_timeout, + &ioloop_timeval); + } + + if (timeout <= HTTP_CLIENT_HOST_MINIMUM_IDLE_TIMEOUT_MSECS) + timeout = HTTP_CLIENT_HOST_MINIMUM_IDLE_TIMEOUT_MSECS; + + hshared->to_idle = timeout_add_to(hshared->cctx->ioloop, timeout, + http_client_host_shared_idle_timeout, + hshared); + + e_debug(hshared->event, "Host is idle (timeout = %u msecs)", timeout); +} + +static void +http_client_host_shared_lookup_failure(struct http_client_host_shared *hshared, + const char *error) +{ + struct http_client_host *host; + + e_debug(hshared->event, "DNS lookup failed: %s", error); + + error = t_strdup_printf("Failed to lookup host %s: %s", + hshared->name, error); + + host = hshared->hosts_list; + while (host != NULL) { + http_client_host_lookup_failure(host, error); + host = host->shared_next; + } + + http_client_host_shared_check_idle(hshared); +} + +static void +http_client_host_shared_lookup_success(struct http_client_host_shared *hshared, + const struct ip_addr *ips, + unsigned int ips_count) +{ + struct http_client_context *cctx = hshared->cctx; + + i_assert(ips_count > 0); + + e_debug(hshared->event, + "DNS lookup successful; got %d IPs", ips_count); + + hshared->ips = i_realloc_type(hshared->ips, struct ip_addr, + hshared->ips_count, ips_count); + hshared->ips_count = ips_count; + memcpy(hshared->ips, ips, sizeof(struct ip_addr) * ips_count); + + hshared->ips_timeout = ioloop_timeval; + i_assert(cctx->dns_ttl_msecs > 0); + timeval_add_msecs(&hshared->ips_timeout, cctx->dns_ttl_msecs); +} + +static void +http_client_host_shared_dns_callback(const struct dns_lookup_result *result, + struct http_client_host_shared *hshared) +{ + struct http_client_host *host; + + hshared->dns_lookup = NULL; + + if (result->ret != 0) { + /* Lookup failed */ + http_client_host_shared_lookup_failure(hshared, result->error); + return; + } + + http_client_host_shared_lookup_success(hshared, result->ips, + result->ips_count); + + /* Notify all sessions */ + host = hshared->hosts_list; + while (host != NULL) { + http_client_host_lookup_done(host); + host = host->shared_next; + } +} + +static void +http_client_host_shared_lookup(struct http_client_host_shared *hshared) +{ + struct http_client_context *cctx = hshared->cctx; + struct dns_lookup_settings dns_set; + int ret; + + i_assert(!hshared->explicit_ip); + i_assert(hshared->dns_lookup == NULL); + + if (cctx->dns_client != NULL) { + e_debug(hshared->event, "Performing asynchronous DNS lookup"); + (void)dns_client_lookup(cctx->dns_client, hshared->name, + http_client_host_shared_dns_callback, + hshared, &hshared->dns_lookup); + } else if (cctx->dns_client_socket_path != NULL) { + i_assert(cctx->dns_lookup_timeout_msecs > 0); + e_debug(hshared->event, "Performing asynchronous DNS lookup"); + i_zero(&dns_set); + dns_set.dns_client_socket_path = cctx->dns_client_socket_path; + dns_set.timeout_msecs = cctx->dns_lookup_timeout_msecs; + dns_set.ioloop = cctx->ioloop; + dns_set.event_parent = hshared->event; + (void)dns_lookup(hshared->name, &dns_set, + http_client_host_shared_dns_callback, + hshared, &hshared->dns_lookup); + } else { + struct ip_addr *ips; + unsigned int ips_count; + + ret = net_gethostbyname(hshared->name, &ips, &ips_count); + if (ret != 0) { + http_client_host_shared_lookup_failure( + hshared, net_gethosterror(ret)); + return; + } + + http_client_host_shared_lookup_success(hshared, ips, ips_count); + } +} + +static int +http_client_host_shared_refresh(struct http_client_host_shared *hshared) +{ + if (hshared->unix_local) + return 0; + if (hshared->explicit_ip) + return 0; + + if (hshared->dns_lookup != NULL) + return -1; + + if (hshared->ips_count == 0) { + e_debug(hshared->event, "Need to perform DNS lookup"); + } else { + if (timeval_cmp(&hshared->ips_timeout, &ioloop_timeval) > 0) + return 0; + + e_debug(hshared->event, "IPs have expired; " + "need to refresh DNS lookup"); + } + + http_client_host_shared_lookup(hshared); + if (hshared->dns_lookup != NULL) + return -1; + return (hshared->ips_count > 0 ? 1 : -1); +} + +static struct http_client_host_shared * +http_client_host_shared_create(struct http_client_context *cctx, + const char *name) +{ + struct http_client_host_shared *hshared; + + // FIXME: limit the maximum number of inactive cached hosts + hshared = i_new(struct http_client_host_shared, 1); + hshared->cctx = cctx; + hshared->name = i_strdup(name); + hshared->event = event_create(cctx->event); + event_set_append_log_prefix(hshared->event, + t_strdup_printf("host %s: ", name)); + DLLIST_PREPEND(&cctx->hosts_list, hshared); + + return hshared; +} + +static struct http_client_host_shared * +http_client_host_shared_get(struct http_client_context *cctx, + const struct http_url *host_url) +{ + struct http_client_host_shared *hshared; + + if (host_url == NULL) { + hshared = cctx->unix_host; + if (hshared == NULL) { + hshared = http_client_host_shared_create( + cctx, "[unix]"); + hshared->name = i_strdup("[unix]"); + hshared->unix_local = TRUE; + + cctx->unix_host = hshared; + + e_debug(hshared->event, "Unix host created"); + } + + } else { + const char *hostname = host_url->host.name; + struct ip_addr ip = host_url->host.ip; + + hshared = hash_table_lookup(cctx->hosts, hostname); + if (hshared == NULL) { + hshared = http_client_host_shared_create( + cctx, hostname); + hostname = hshared->name; + hash_table_insert(cctx->hosts, hostname, hshared); + + if (ip.family != 0 || + net_addr2ip(hshared->name, &ip) == 0) { + hshared->ips_count = 1; + hshared->ips = i_new(struct ip_addr, + hshared->ips_count); + hshared->ips[0] = ip; + hshared->explicit_ip = TRUE; + } + + e_debug(hshared->event, "Host created"); + } + } + return hshared; +} + +void http_client_host_shared_free(struct http_client_host_shared **_hshared) +{ + struct http_client_host_shared *hshared = *_hshared; + struct http_client_context *cctx = hshared->cctx; + struct http_client_host *host; + const char *hostname = hshared->name; + + if (hshared->destroyed) + return; + hshared->destroyed = TRUE; + + e_debug(hshared->event, "Host destroy"); + + timeout_remove(&hshared->to_idle); + + DLLIST_REMOVE(&cctx->hosts_list, hshared); + if (hshared == cctx->unix_host) + cctx->unix_host = NULL; + else + hash_table_remove(cctx->hosts, hostname); + + if (hshared->dns_lookup != NULL) + dns_lookup_abort(&hshared->dns_lookup); + + /* Drop client sessions */ + while (hshared->hosts_list != NULL) { + host = hshared->hosts_list; + http_client_host_free_shared(&host); + } + + event_unref(&hshared->event); + i_free(hshared->ips); + i_free(hshared->name); + i_free(hshared); + + *_hshared = NULL; +} + +static void +http_client_host_shared_request_submitted( + struct http_client_host_shared *hshared) +{ + /* Cancel host idle timeout */ + timeout_remove(&hshared->to_idle); +} + +void http_client_host_shared_switch_ioloop( + struct http_client_host_shared *hshared) +{ + struct http_client_context *cctx = hshared->cctx; + + if (hshared->dns_lookup != NULL && cctx->dns_client == NULL) + dns_lookup_switch_ioloop(hshared->dns_lookup); + if (hshared->to_idle != NULL) + hshared->to_idle = io_loop_move_timeout(&hshared->to_idle); +} + +/* + * Host + */ + +struct http_client_host * +http_client_host_get(struct http_client *client, + const struct http_url *host_url) +{ + struct http_client_host_shared *hshared; + struct http_client_host *host; + + hshared = http_client_host_shared_get(client->cctx, host_url); + + host = hshared->hosts_list; + while (host != NULL) { + if (host->client == client) + break; + host = host->shared_next; + } + + if (host == NULL) { + host = i_new(struct http_client_host, 1); + host->client = client; + host->shared = hshared; + i_array_init(&host->queues, 4); + DLLIST_PREPEND_FULL(&hshared->hosts_list, + host, shared_prev, shared_next); + DLLIST_PREPEND_FULL(&client->hosts_list, host, + client_prev, client_next); + + e_debug(hshared->event, "Host session created"); + } + + return host; +} + +static void http_client_host_free_shared(struct http_client_host **_host) +{ + struct http_client_host *host = *_host; + struct http_client *client = host->client; + struct http_client_host_shared *hshared = host->shared; + struct http_client_queue *queue; + ARRAY_TYPE(http_client_queue) queues; + + *_host = NULL; + + e_debug(hshared->event, "Host session destroy"); + + DLLIST_REMOVE_FULL(&hshared->hosts_list, host, + shared_prev, shared_next); + DLLIST_REMOVE_FULL(&client->hosts_list, host, + client_prev, client_next); + + /* Drop request queues */ + t_array_init(&queues, array_count(&host->queues)); + array_copy(&queues.arr, 0, &host->queues.arr, 0, + array_count(&host->queues)); + array_clear(&host->queues); + array_foreach_elem(&queues, queue) + http_client_queue_free(queue); + array_free(&host->queues); + + i_free(host); +} + +void http_client_host_free(struct http_client_host **_host) +{ + struct http_client_host *host = *_host; + struct http_client_host_shared *hshared = host->shared; + + http_client_host_free_shared(_host); + + http_client_host_shared_check_idle(hshared); +} + +static void http_client_host_lookup_done(struct http_client_host *host) +{ + struct http_client *client = host->client; + struct http_client_queue *queue; + unsigned int requests = 0; + + /* Notify all queues */ + array_foreach_elem(&host->queues, queue) + requests += http_client_queue_host_lookup_done(queue); + + if (requests == 0 && client->waiting) + io_loop_stop(client->ioloop); +} + +static void +http_client_host_lookup_failure(struct http_client_host *host, + const char *error) +{ + struct http_client_queue *queue; + + array_foreach_elem(&host->queues, queue) + http_client_queue_host_lookup_failure(queue, error); +} + +void http_client_host_submit_request(struct http_client_host *host, + struct http_client_request *req) +{ + struct http_client *client = req->client; + struct http_client_queue *queue; + struct http_client_peer_addr addr; + const char *error; + + req->host = host; + + http_client_request_get_peer_addr(req, &addr); + if (http_client_peer_addr_is_https(&addr) && + client->ssl_ctx == NULL) { + if (http_client_init_ssl_ctx(client, &error) < 0) { + http_client_request_error( + &req, HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, + error); + return; + } + } + + /* Add request to queue */ + queue = http_client_queue_get(host, &addr); + http_client_queue_submit_request(queue, req); + + /* Update shared host object (idle timeout) */ + http_client_host_shared_request_submitted(host->shared); + + /* Queue will trigger host lookup once the request is activated + (may be delayed) */ +} + +static bool http_client_host_is_idle(struct http_client_host *host) +{ + struct http_client_queue *queue; + unsigned int requests = 0; + + array_foreach_elem(&host->queues, queue) + requests += http_client_queue_requests_active(queue); + + return (requests == 0); +} + +void http_client_host_check_idle(struct http_client_host *host) +{ + http_client_host_shared_check_idle(host->shared); +} + +int http_client_host_refresh(struct http_client_host *host) +{ + return http_client_host_shared_refresh(host->shared); +} + +bool http_client_host_get_ip_idx(struct http_client_host *host, + const struct ip_addr *ip, unsigned int *idx_r) +{ + struct http_client_host_shared *hshared = host->shared; + unsigned int i; + + for (i = 0; i < hshared->ips_count; i++) { + if (net_ip_compare(&hshared->ips[i], ip)) { + *idx_r = i; + return TRUE; + } + } + return FALSE; +} + +void http_client_host_switch_ioloop(struct http_client_host *host) +{ + struct http_client_queue *queue; + + array_foreach_elem(&host->queues, queue) + http_client_queue_switch_ioloop(queue); +} |