#include "multihandle.h"
#include "rand.h"
#include "share.h"
+#include "strdup.h"
#include "version_win32.h"
/* The last 3 #include files should be in this order */
}
#endif /* USE_WINSOCK */
+/*
+ * Curl_parse_interface()
+ *
+ * This is used to parse interface argument in the following formats.
+ * In all the examples, `host` can be an IP address or a hostname.
+ *
+ * <iface_or_host> - can be either an interface name or a host.
+ * if!<iface> - interface name.
+ * host!<host> - host name.
+ * ifhost!<iface>!<host> - interface name and host name.
+ *
+ * Parameters:
+ *
+ * input [in] - input string.
+ * len [in] - length of the input string.
+ * dev [in/out] - address where a pointer to newly allocated memory
+ * holding the interface-or-host will be stored upon
+ * completion.
+ * iface [in/out] - address where a pointer to newly allocated memory
+ * holding the interface will be stored upon completion.
+ * host [in/out] - address where a pointer to newly allocated memory
+ * holding the host will be stored upon completion.
+ *
+ * Returns CURLE_OK on success.
+ */
+CURLcode Curl_parse_interface(const char *input, size_t len,
+ char **dev, char **iface, char **host)
+{
+ static const char if_prefix[] = "if!";
+ static const char host_prefix[] = "host!";
+ static const char if_host_prefix[] = "ifhost!";
+
+ DEBUGASSERT(dev);
+ DEBUGASSERT(iface);
+ DEBUGASSERT(host);
+
+ if(strncmp(if_prefix, input, strlen(if_prefix)) == 0) {
+ input += strlen(if_prefix);
+ if(!*input)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *iface = Curl_memdup0(input, len - strlen(if_prefix));
+ return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+ }
+ if(strncmp(host_prefix, input, strlen(host_prefix)) == 0) {
+ input += strlen(host_prefix);
+ if(!*input)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *host = Curl_memdup0(input, len - strlen(host_prefix));
+ return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+ }
+ if(strncmp(if_host_prefix, input, strlen(if_host_prefix)) == 0) {
+ const char *host_part;
+ input += strlen(if_host_prefix);
+ len -= strlen(if_host_prefix);
+ host_part = memchr(input, '!', len);
+ if(!host_part || !*(host_part + 1))
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *iface = Curl_memdup0(input, host_part - input);
+ if(!*iface)
+ return CURLE_OUT_OF_MEMORY;
+ ++host_part;
+ *host = Curl_memdup0(host_part, len - (host_part - input));
+ if(!*host) {
+ free(*iface);
+ *iface = NULL;
+ return CURLE_OUT_OF_MEMORY;
+ }
+ return CURLE_OK;
+ }
+
+ if(!*input)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ *dev = Curl_memdup0(input, len);
+ return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+}
+
#ifndef CURL_DISABLE_BINDLOCAL
static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
curl_socket_t sockfd, int af, unsigned int scope)
/* how many port numbers to try to bind to, increasing one at a time */
int portnum = data->set.localportrange;
const char *dev = data->set.str[STRING_DEVICE];
+ const char *iface_input = data->set.str[STRING_INTERFACE];
+ const char *host_input = data->set.str[STRING_BINDHOST];
+ const char *iface = iface_input ? iface_input : dev;
+ const char *host = host_input ? host_input : dev;
int error;
#ifdef IP_BIND_ADDRESS_NO_PORT
int on = 1;
/*************************************************************
* Select device to bind socket to
*************************************************************/
- if(!dev && !port)
+ if(!iface && !host && !port)
/* no local kind of binding was requested */
return CURLE_OK;
memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
- if(dev && (strlen(dev)<255) ) {
+ if(iface && (strlen(iface)<255) ) {
char myhost[256] = "";
int done = 0; /* -1 for error, 1 for address found */
- bool is_interface = FALSE;
- bool is_host = FALSE;
- static const char *if_prefix = "if!";
- static const char *host_prefix = "host!";
-
- if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) {
- dev += strlen(if_prefix);
- is_interface = TRUE;
- }
- else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) {
- dev += strlen(host_prefix);
- is_host = TRUE;
- }
+ if2ip_result_t if2ip_result = IF2IP_NOT_FOUND;
/* interface */
- if(!is_host) {
#ifdef SO_BINDTODEVICE
- /*
- * This binds the local socket to a particular interface. This will
- * force even requests to other local interfaces to go out the external
- * interface. Only bind to the interface when specified as interface,
- * not just as a hostname or ip address.
- *
- * The interface might be a VRF, eg: vrf-blue, which means it cannot be
- * converted to an IP address and would fail Curl_if2ip. Simply try to
- * use it straight away.
- */
- if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
- dev, (curl_socklen_t)strlen(dev) + 1) == 0) {
- /* This is often "errno 1, error: Operation not permitted" if you're
- * not running as root or another suitable privileged user. If it
- * succeeds it means the parameter was a valid interface and not an IP
- * address. Return immediately.
- */
- infof(data, "socket successfully bound to interface '%s'", dev);
+ /*
+ * This binds the local socket to a particular interface. This will
+ * force even requests to other local interfaces to go out the external
+ * interface. Only bind to the interface when specified as interface,
+ * not just as a hostname or ip address.
+ *
+ * The interface might be a VRF, eg: vrf-blue, which means it cannot be
+ * converted to an IP address and would fail Curl_if2ip. Simply try to
+ * use it straight away.
+ */
+ if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
+ iface, (curl_socklen_t)strlen(iface) + 1) == 0) {
+ /* This is often "errno 1, error: Operation not permitted" if you're
+ * not running as root or another suitable privileged user. If it
+ * succeeds it means the parameter was a valid interface and not an IP
+ * address. Return immediately.
+ */
+ if(!host_input) {
+ infof(data, "socket successfully bound to interface '%s'", iface);
return CURLE_OK;
}
+ }
#endif
-
- switch(Curl_if2ip(af,
+ if(!host_input) {
+ /* Discover IP from input device, then bind to it */
+ if2ip_result = Curl_if2ip(af,
#ifdef USE_IPV6
- scope, conn->scope_id,
-#endif
- dev, myhost, sizeof(myhost))) {
- case IF2IP_NOT_FOUND:
- if(is_interface) {
- /* Do not fall back to treating it as a host name */
- failf(data, "Couldn't bind to interface '%s'", dev);
- return CURLE_INTERFACE_FAILED;
- }
- break;
- case IF2IP_AF_NOT_SUPPORTED:
- /* Signal the caller to try another address family if available */
- return CURLE_UNSUPPORTED_PROTOCOL;
- case IF2IP_FOUND:
- is_interface = TRUE;
- /*
- * We now have the numerical IP address in the 'myhost' buffer
- */
- infof(data, "Local Interface %s is ip %s using address family %i",
- dev, myhost, af);
- done = 1;
- break;
- }
+ scope, conn->scope_id,
+#endif
+ iface, myhost, sizeof(myhost));
+ }
+ switch(if2ip_result) {
+ case IF2IP_NOT_FOUND:
+ if(iface_input && !host_input) {
+ /* Do not fall back to treating it as a host name */
+ failf(data, "Couldn't bind to interface '%s'", iface);
+ return CURLE_INTERFACE_FAILED;
+ }
+ break;
+ case IF2IP_AF_NOT_SUPPORTED:
+ /* Signal the caller to try another address family if available */
+ return CURLE_UNSUPPORTED_PROTOCOL;
+ case IF2IP_FOUND:
+ /*
+ * We now have the numerical IP address in the 'myhost' buffer
+ */
+ host = myhost;
+ infof(data, "Local Interface %s is ip %s using address family %i",
+ iface, host, af);
+ done = 1;
+ break;
}
- if(!is_interface) {
+ if(!iface_input || host_input) {
/*
* This was not an interface, resolve the name as a host name
* or IP number
conn->ip_version = CURL_IPRESOLVE_V6;
#endif
- rc = Curl_resolv(data, dev, 80, FALSE, &h);
+ rc = Curl_resolv(data, host, 80, FALSE, &h);
if(rc == CURLRESOLV_PENDING)
(void)Curl_resolver_wait_resolv(data, &h);
conn->ip_version = ipver;
/* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
Curl_printable_address(h->addr, myhost, sizeof(myhost));
infof(data, "Name '%s' family %i resolved to '%s' family %i",
- dev, af, myhost, h->addr->ai_family);
+ host, af, myhost, h->addr->ai_family);
Curl_resolv_unlock(data, h);
if(af != h->addr->ai_family) {
/* bad IP version combo, signal the caller to try another address
the error buffer, so the user receives this error message instead of a
generic resolve error. */
data->state.errorbuf = FALSE;
- failf(data, "Couldn't bind to '%s'", dev);
+ failf(data, "Couldn't bind to '%s'", host);
return CURLE_INTERFACE_FAILED;
}
}
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 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.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.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curlcheck.h"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN6_H
+#include <netinet/in6.h>
+#endif
+
+#include <curl/curl.h>
+
+#include "cf-socket.h"
+
+#include "memdebug.h" /* LAST include file */
+
+static CURLcode unit_setup(void)
+{
+ CURLcode res = CURLE_OK;
+ global_init(CURL_GLOBAL_ALL);
+ return res;
+}
+
+static void unit_stop(void)
+{
+ curl_global_cleanup();
+}
+
+static void test_parse(
+ const char *input,
+ const char *exp_dev,
+ const char *exp_iface,
+ const char *exp_host,
+ CURLcode exp_rc)
+{
+ char *dev = NULL;
+ char *iface = NULL;
+ char *host = NULL;
+ CURLcode rc = Curl_parse_interface(
+ input, strlen(input), &dev, &iface, &host);
+ fail_unless(rc == exp_rc, "Curl_parse_interface() failed");
+
+ fail_unless(!!exp_dev == !!dev, "dev expectation failed.");
+ fail_unless(!!exp_iface == !!iface, "iface expectation failed");
+ fail_unless(!!exp_host == !!host, "host expectation failed");
+
+ if(!unitfail) {
+ fail_unless(!exp_dev || strcmp(dev, exp_dev) == 0,
+ "dev should be equal to exp_dev");
+ fail_unless(!exp_iface || strcmp(iface, exp_iface) == 0,
+ "iface should be equal to exp_iface");
+ fail_unless(!exp_host || strcmp(host, exp_host) == 0,
+ "host should be equal to exp_host");
+ }
+
+ free(dev);
+ free(iface);
+ free(host);
+}
+
+UNITTEST_START
+{
+ test_parse("dev", "dev", NULL, NULL, CURLE_OK);
+ test_parse("if!eth0", NULL, "eth0", NULL, CURLE_OK);
+ test_parse("host!myname", NULL, NULL, "myname", CURLE_OK);
+ test_parse("ifhost!eth0!myname", NULL, "eth0", "myname", CURLE_OK);
+ test_parse("", NULL, NULL, NULL, CURLE_BAD_FUNCTION_ARGUMENT);
+ test_parse("!", "!", NULL, NULL, CURLE_OK);
+ test_parse("if!", NULL, NULL, NULL, CURLE_BAD_FUNCTION_ARGUMENT);
+ test_parse("if!eth0!blubb", NULL, "eth0!blubb", NULL, CURLE_OK);
+ test_parse("host!", NULL, NULL, NULL, CURLE_BAD_FUNCTION_ARGUMENT);
+ test_parse("ifhost!", NULL, NULL, NULL, CURLE_BAD_FUNCTION_ARGUMENT);
+ test_parse("ifhost!eth0", NULL, NULL, NULL, CURLE_BAD_FUNCTION_ARGUMENT);
+ test_parse("ifhost!eth0!", NULL, NULL, NULL, CURLE_BAD_FUNCTION_ARGUMENT);
+}
+UNITTEST_STOP