Long: resolve
-Arg: <host:port:address>
+Arg: <host:port:address[,address]...>
Help: Resolve the host+port to this address
Added: 7.21.3
---
Support for providing the IP address within [brackets] was added in 7.57.0.
+Support for providing multiple IP addresses per entry was added in 7.59.0.
+
This option can be used many times to add many host names to resolve.
to clean up an entire list.
Each single name resolve string should be written using the format
-HOST:PORT:ADDRESS where HOST is the name libcurl will try to resolve, PORT is
-the port number of the service where libcurl wants to connect to the HOST and
-ADDRESS is the numerical IP address. If libcurl is built to support IPv6,
-ADDRESS can of course be either IPv4 or IPv6 style addressing.
+HOST:PORT:ADDRESS[,ADDRESS]... where HOST is the name libcurl will try
+to resolve, PORT is the port number of the service where libcurl wants
+to connect to the HOST and ADDRESS is one or more numerical IP
+addresses. If you specify multiple ip addresses they need to be
+separated by comma. If libcurl is built to support IPv6, each of the
+ADDRESS entries can of course be either IPv4 or IPv6 style addressing.
This option effectively pre-populates the DNS cache with entries for the
host+port pair so redirects and everything that operations against the
and port number must exactly match what was already added previously.
Support for providing the ADDRESS within [brackets] was added in 7.57.0.
+
+Support for providing multiple IP addresses per entry was added in 7.59.0.
.SH DEFAULT
NULL
.SH PROTOCOLS
/* retrieves ip address and port from a sockaddr structure.
note it calls Curl_inet_ntop which sets errno on fail, not SOCKERRNO. */
-static bool getaddressinfo(struct sockaddr *sa, char *addr,
- long *port)
+bool Curl_getaddressinfo(struct sockaddr *sa, char *addr,
+ long *port)
{
unsigned short us_port;
struct sockaddr_in *si = NULL;
return;
}
- if(!getaddressinfo((struct sockaddr*)&ssrem,
- conn->primary_ip, &conn->primary_port)) {
+ if(!Curl_getaddressinfo((struct sockaddr*)&ssrem,
+ conn->primary_ip, &conn->primary_port)) {
failf(data, "ssrem inet_ntop() failed with errno %d: %s",
errno, Curl_strerror(conn, errno));
return;
}
memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN);
- if(!getaddressinfo((struct sockaddr*)&ssloc,
- conn->local_ip, &conn->local_port)) {
+ if(!Curl_getaddressinfo((struct sockaddr*)&ssloc,
+ conn->local_ip, &conn->local_port)) {
failf(data, "ssloc inet_ntop() failed with errno %d: %s",
errno, Curl_strerror(conn, errno));
return;
return CURLE_OK;
/* store remote address and port used in this connection attempt */
- if(!getaddressinfo((struct sockaddr*)&addr.sa_addr,
- ipaddress, &port)) {
+ if(!Curl_getaddressinfo((struct sockaddr*)&addr.sa_addr,
+ ipaddress, &port)) {
/* malformed address or bug in inet_ntop, try next address */
failf(data, "sa_addr inet_ntop() failed with errno %d: %s",
errno, Curl_strerror(conn, errno));
void Curl_persistconninfo(struct connectdata *conn);
int Curl_closesocket(struct connectdata *conn, curl_socket_t sock);
+/*
+ * Get presentation format IP address and port from a sockaddr.
+ */
+bool Curl_getaddressinfo(struct sockaddr *sa, char *addr, long *port);
+
/*
* The Curl_sockaddr_ex structure is basically libcurl's external API
* curl_sockaddr structure with enough space available to directly hold any
{
struct curl_slist *hostp;
char hostname[256];
- int port;
+ int port = 0;
for(hostp = data->change.resolve; hostp; hostp = hostp->next) {
if(!hostp->data)
}
else {
struct Curl_dns_entry *dns;
- Curl_addrinfo *addr;
+ Curl_addrinfo *head = NULL, *tail = NULL;
char *entry_id;
size_t entry_len;
- char buffer[256];
- char *address = &buffer[0];
+ char address[64];
+ char *addresses;
+ char *addr_begin;
+ char *addr_end;
+ char *port_ptr;
+ char *end_ptr;
+ char *host_end;
+ unsigned long tmp_port;
+ bool error = true;
+
+ host_end = strchr(hostp->data, ':');
+ if(!host_end ||
+ ((host_end - hostp->data) >= (ptrdiff_t)sizeof(hostname)))
+ goto err;
+
+ memcpy(hostname, hostp->data, host_end - hostp->data);
+ hostname[host_end - hostp->data] = '\0';
+
+ port_ptr = host_end + 1;
+ tmp_port = strtoul(port_ptr, &end_ptr, 10);
+ if(end_ptr == port_ptr || tmp_port > USHRT_MAX || *end_ptr != ':')
+ goto err;
+
+ port = (int)tmp_port;
+ addresses = end_ptr + 1;
+
+ while(*end_ptr) {
+ size_t alen;
+ Curl_addrinfo *ai;
+
+ addr_begin = end_ptr + 1;
+ addr_end = strchr(addr_begin, ',');
+ if(!addr_end)
+ addr_end = addr_begin + strlen(addr_begin);
+ end_ptr = addr_end;
+
+ /* allow IP(v6) address within [brackets] */
+ if(*addr_begin == '[') {
+ if(addr_end == addr_begin || *(addr_end - 1) != ']')
+ goto err;
+ ++addr_begin;
+ --addr_end;
+ }
- if(3 != sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port,
- address)) {
- infof(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'!\n",
- hostp->data);
- continue;
- }
+ alen = addr_end - addr_begin;
+ if(!alen)
+ continue;
- /* allow IP(v6) address within [brackets] */
- if(address[0] == '[') {
- size_t alen = strlen(address);
- if(address[alen-1] != ']')
- /* it needs to also end with ] to be valid */
+ if(alen >= sizeof(address))
+ goto err;
+
+ memcpy(address, addr_begin, alen);
+ address[alen] = '\0';
+
+#ifndef ENABLE_IPV6
+ if(strchr(address, ':')) {
+ infof(data, "Ignoring resolve address '%s', missing IPv6 support.\n",
+ address);
continue;
- address[alen-1] = 0; /* zero terminate there */
- address++; /* pass the open bracket */
+ }
+#endif
+
+ ai = Curl_str2addr(address, port);
+ if(!ai) {
+ infof(data, "Resolve address '%s' found illegal!\n", address);
+ goto err;
+ }
+
+ if(tail) {
+ tail->ai_next = ai;
+ tail = tail->ai_next;
+ }
+ else {
+ head = tail = ai;
+ }
}
- addr = Curl_str2addr(address, port);
- if(!addr) {
- infof(data, "Address in '%s' found illegal!\n", hostp->data);
+ if(!head)
+ goto err;
+
+ error = false;
+ err:
+ if(error) {
+ infof(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'!\n",
+ hostp->data);
+ Curl_freeaddrinfo(head);
continue;
}
entry_id = create_hostcache_id(hostname, port);
/* If we can't create the entry id, fail */
if(!entry_id) {
- Curl_freeaddrinfo(addr);
+ Curl_freeaddrinfo(head);
return CURLE_OUT_OF_MEMORY;
}
-
entry_len = strlen(entry_id);
if(data->share)
if(!dns) {
/* if not in the cache already, put this host in the cache */
- dns = Curl_cache_addr(data, addr, hostname, port);
+ dns = Curl_cache_addr(data, head, hostname, port);
if(dns) {
dns->timestamp = 0; /* mark as added by CURLOPT_RESOLVE */
/* release the returned reference; the cache itself will keep the
else {
/* this is a duplicate, free it again */
infof(data, "RESOLVE %s:%d is already cached, %s not stored!\n",
- hostname, port, address);
- Curl_freeaddrinfo(addr);
+ hostname, port, addresses);
+ Curl_freeaddrinfo(head);
}
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
if(!dns) {
- Curl_freeaddrinfo(addr);
+ Curl_freeaddrinfo(head);
return CURLE_OUT_OF_MEMORY;
}
infof(data, "Added %s:%d:%s to DNS cache\n",
- hostname, port, address);
+ hostname, port, addresses);
}
}
data->change.resolve = NULL; /* dealt with now */
test1533 test1534 test1535 test1536 test1537 test1538 \
test1540 \
test1550 test1551 test1552 test1553 test1554 test1555 test1556 \
-test1600 test1601 test1602 test1603 test1604 test1605 test1606 \
+test1600 test1601 test1602 test1603 test1604 test1605 test1606 test1607 \
\
test1700 test1701 test1702 \
\
--- /dev/null
+<testcase>
+<info>
+<keywords>
+unittest
+CURLOPT_RESOLVE
+</keywords>
+</info>
+
+#
+# Client-side
+<client>
+<server>
+none
+</server>
+<features>
+unittest
+</features>
+ <name>
+CURLOPT_RESOLVE parsing
+ </name>
+<tool>
+unit1607
+</tool>
+</client>
+
+</testcase>
unit1308 unit1309 unit1323 \
unit1330 unit1394 unit1395 unit1396 unit1397 unit1398 \
unit1399 \
- unit1600 unit1601 unit1602 unit1603 unit1604 unit1605 unit1606
+ unit1600 unit1601 unit1602 unit1603 unit1604 unit1605 unit1606 unit1607
unit1300_SOURCES = unit1300.c $(UNITFILES)
unit1300_CPPFLAGS = $(AM_CPPFLAGS)
unit1606_SOURCES = unit1606.c $(UNITFILES)
unit1606_CPPFLAGS = $(AM_CPPFLAGS)
+
+unit1607_SOURCES = unit1607.c $(UNITFILES)
+unit1607_CPPFLAGS = $(AM_CPPFLAGS)
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "curlcheck.h"
+
+#include "urldata.h"
+#include "connect.h"
+#include "share.h"
+
+#include "memdebug.h" /* LAST include file */
+
+static struct Curl_easy *easy;
+struct curl_hash *hostcache;
+
+static CURLcode unit_setup(void)
+{
+ int res = CURLE_OK;
+
+ global_init(CURL_GLOBAL_ALL);
+
+ easy = curl_easy_init();
+ if(!easy)
+ return CURLE_OUT_OF_MEMORY;
+
+ hostcache = Curl_global_host_cache_init();
+ if(!hostcache)
+ return CURLE_OUT_OF_MEMORY;
+
+ return res;
+}
+
+static void unit_stop(void)
+{
+ curl_easy_cleanup(easy);
+ curl_global_cleanup();
+}
+
+struct testcase {
+ /* host:port:address[,address]... */
+ const char *optval;
+
+ /* lowercase host and port to retrieve the addresses from hostcache */
+ const char *host;
+ int port;
+
+ /* 0 to 9 addresses expected from hostcache */
+ const char *address[10];
+};
+
+
+/* In builds without IPv6 support CURLOPT_RESOLVE should skip over those
+ addresses, so we have to do that as well. */
+static const char skip = 0;
+#ifdef ENABLE_IPV6
+#define IPV6ONLY(x) x
+#else
+#define IPV6ONLY(x) &skip
+#endif
+
+/* CURLOPT_RESOLVE address parsing tests */
+static const struct testcase tests[] = {
+ /* spaces aren't allowed, for now */
+ { "test.com:80:127.0.0.1, 127.0.0.2",
+ "test.com", 80, { NULL, }
+ },
+ { "TEST.com:80:,,127.0.0.1,,,127.0.0.2,,,,::1,,,",
+ "test.com", 80, { "127.0.0.1", "127.0.0.2", IPV6ONLY("::1"), }
+ },
+ { "test.com:80:::1,127.0.0.1",
+ "test.com", 80, { IPV6ONLY("::1"), "127.0.0.1", }
+ },
+ { "test.com:80:[::1],127.0.0.1",
+ "test.com", 80, { IPV6ONLY("::1"), "127.0.0.1", }
+ },
+ { "test.com:80:::1",
+ "test.com", 80, { IPV6ONLY("::1"), }
+ },
+ { "test.com:80:[::1]",
+ "test.com", 80, { IPV6ONLY("::1"), }
+ },
+ { "test.com:80:127.0.0.1",
+ "test.com", 80, { "127.0.0.1", }
+ },
+ { "test.com:80:,127.0.0.1",
+ "test.com", 80, { "127.0.0.1", }
+ },
+ { "test.com:80:127.0.0.1,",
+ "test.com", 80, { "127.0.0.1", }
+ },
+ { "test.com:0:127.0.0.1",
+ "test.com", 0, { "127.0.0.1", }
+ },
+};
+
+UNITTEST_START
+ int i;
+ int testnum = sizeof(tests) / sizeof(struct testcase);
+
+ for(i = 0; i < testnum; ++i, curl_easy_reset(easy)) {
+ int j;
+ int addressnum = sizeof tests[i].address / sizeof *tests[i].address;
+ struct Curl_addrinfo *addr;
+ struct Curl_dns_entry *dns;
+ struct curl_slist *list;
+ void *entry_id;
+ bool problem = false;
+
+ Curl_hostcache_clean(easy, hostcache);
+ easy->dns.hostcache = hostcache;
+ easy->dns.hostcachetype = HCACHE_GLOBAL;
+
+ list = curl_slist_append(NULL, tests[i].optval);
+ curl_easy_setopt(easy, CURLOPT_RESOLVE, list);
+
+ Curl_loadhostpairs(easy);
+
+ entry_id = (void *)aprintf("%s:%d", tests[i].host, tests[i].port);
+ dns = Curl_hash_pick(easy->dns.hostcache, entry_id, strlen(entry_id) + 1);
+ free(entry_id);
+ entry_id = NULL;
+
+ addr = dns ? dns->addr : NULL;
+
+ for(j = 0; j < addressnum; ++j) {
+ long port = 0;
+ char ipaddress[MAX_IPADR_LEN] = {0};
+
+ if(!addr && !tests[i].address[j])
+ break;
+
+ if(tests[i].address[j] == &skip)
+ continue;
+
+ if(addr && !Curl_getaddressinfo(addr->ai_addr,
+ ipaddress, &port)) {
+ fprintf(stderr, "%s:%d tests[%d] failed. getaddressinfo failed.\n",
+ __FILE__, __LINE__, i);
+ problem = true;
+ break;
+ }
+
+ if(addr && !tests[i].address[j]) {
+ fprintf(stderr, "%s:%d tests[%d] failed. the retrieved addr "
+ "is %s but tests[%d].address[%d] is NULL.\n",
+ __FILE__, __LINE__, i, ipaddress, i, j);
+ problem = true;
+ break;
+ }
+
+ if(!addr && tests[i].address[j]) {
+ fprintf(stderr, "%s:%d tests[%d] failed. the retrieved addr "
+ "is NULL but tests[%d].address[%d] is %s.\n",
+ __FILE__, __LINE__, i, i, j, tests[i].address[j]);
+ problem = true;
+ break;
+ }
+
+ if(!curl_strequal(ipaddress, tests[i].address[j])) {
+ fprintf(stderr, "%s:%d tests[%d] failed. the retrieved addr "
+ "%s is not equal to tests[%d].address[%d] %s.\n",
+ __FILE__, __LINE__, i, ipaddress, i, j, tests[i].address[j]);
+ problem = true;
+ break;
+ }
+
+ if(port != tests[i].port) {
+ fprintf(stderr, "%s:%d tests[%d] failed. the retrieved port "
+ "for tests[%d].address[%d] is %ld but tests[%d].port is %d.\n",
+ __FILE__, __LINE__, i, i, j, port, i, tests[i].port);
+ problem = true;
+ break;
+ }
+
+ addr = addr->ai_next;
+ }
+
+ Curl_hostcache_clean(easy, easy->dns.hostcache);
+ curl_slist_free_all(list);
+
+ if(problem) {
+ unitfail++;
+ continue;
+ }
+ }
+UNITTEST_STOP