mirror of
https://github.com/curl/curl.git
synced 2025-09-12 23:22:42 +03:00
socket: support binding to interface *AND* IP
Introduce new notation for CURLOPT_INTERFACE / --interface: ifhost!<interface>!<host> Binding to an interface doesn't set the address, and an interface can have multiple addresses. When binding to an address (without interface), the kernel is free to choose the route, and it can route through any device that can access the target address, not necessarily the one with the chosen address. Moreover, it is possible for different interfaces to have the same IP address, on which case we need to provide a way to be more specific. Factor out the parsing part of interface option, and add unit tests: 1663. Closes #13719
This commit is contained in:
parent
23fe1a52dc
commit
3060557af7
4
.github/workflows/proselint.yml
vendored
4
.github/workflows/proselint.yml
vendored
|
@ -51,7 +51,7 @@ jobs:
|
|||
JSON
|
||||
|
||||
- name: check prose
|
||||
run: a=`git ls-files '*.md' | grep -Ev '(docs/CHECKSRC.md|docs/DISTROS.md)'` && proselint $a README
|
||||
run: git ls-files '*.md' | grep -Ev 'CHECKSRC.md|DISTROS.md|CURLOPT_INTERFACE.md' | xargs proselint README
|
||||
|
||||
# This is for CHECKSRC and files with aggressive exclamation mark needs
|
||||
- name: create second proselint config
|
||||
|
@ -68,4 +68,4 @@ jobs:
|
|||
JSON
|
||||
|
||||
- name: check special prose
|
||||
run: a=docs/CHECKSRC.md && proselint $a
|
||||
run: proselint docs/CHECKSRC.md docs/libcurl/opts/CURLOPT_INTERFACE.md
|
||||
|
|
|
@ -28,15 +28,16 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_INTERFACE, char *interface);
|
|||
|
||||
Pass a char pointer as parameter. This sets the *interface* name to use as
|
||||
outgoing network interface. The name can be an interface name, an IP address,
|
||||
or a hostname.
|
||||
or a hostname. If you prefer one of these, you can use the following special
|
||||
prefixes:
|
||||
|
||||
If the parameter starts with "if!" then it is treated only as an interface
|
||||
name. If the parameter starts with "host!" it is treated as either an IP
|
||||
address or a hostname.
|
||||
* `if!<name>` - Interface name
|
||||
* `host!<name>` - IP address or hostname
|
||||
* `ifhost!<interface>!<host>` - Interface name and IP address or hostname
|
||||
|
||||
If "if!" is specified but the parameter does not match an existing interface,
|
||||
*CURLE_INTERFACE_FAILED* is returned from the libcurl function used to perform
|
||||
the transfer.
|
||||
If `if!` or `ifhost!` is specified but the parameter does not match an existing
|
||||
interface, *CURLE_INTERFACE_FAILED* is returned from the libcurl function used
|
||||
to perform the transfer.
|
||||
|
||||
libcurl does not support using network interface names for this option on
|
||||
Windows.
|
||||
|
@ -74,7 +75,9 @@ int main(void)
|
|||
|
||||
# AVAILABILITY
|
||||
|
||||
The "if!" and "host!" syntax was added in 7.24.0.
|
||||
The `if!` and `host!` syntax was added in 7.24.0.
|
||||
|
||||
The `ifhost!` syntax was added in 8.9.0.
|
||||
|
||||
# RETURN VALUE
|
||||
|
||||
|
|
202
lib/cf-socket.c
202
lib/cf-socket.c
|
@ -78,6 +78,7 @@
|
|||
#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 */
|
||||
|
@ -435,6 +436,82 @@ void Curl_sndbuf_init(curl_socket_t sockfd)
|
|||
}
|
||||
#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)
|
||||
|
@ -453,6 +530,10 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
/* 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;
|
||||
|
@ -464,81 +545,72 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
/*************************************************************
|
||||
* 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,
|
||||
#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;
|
||||
}
|
||||
}
|
||||
if(!is_interface) {
|
||||
#endif
|
||||
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
|
||||
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(!iface_input || host_input) {
|
||||
/*
|
||||
* This was not an interface, resolve the name as a host name
|
||||
* or IP number
|
||||
|
@ -557,7 +629,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
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;
|
||||
|
@ -566,7 +638,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
/* 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
|
||||
|
@ -628,7 +700,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,11 @@ struct Curl_sockaddr_ex {
|
|||
};
|
||||
#define sa_addr _sa_ex_u.addr
|
||||
|
||||
/*
|
||||
* Parse interface option, and return the interface name and the host part.
|
||||
*/
|
||||
CURLcode Curl_parse_interface(const char *input, size_t len,
|
||||
char **dev, char **iface, char **host);
|
||||
|
||||
/*
|
||||
* Create a socket based on info from 'conn' and 'ai'.
|
||||
|
|
42
lib/setopt.c
42
lib/setopt.c
|
@ -139,6 +139,42 @@ static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp)
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static CURLcode setstropt_interface(
|
||||
char *option, char **devp, char **ifacep, char **hostp)
|
||||
{
|
||||
char *dev = NULL;
|
||||
char *iface = NULL;
|
||||
char *host = NULL;
|
||||
size_t len;
|
||||
CURLcode result;
|
||||
|
||||
DEBUGASSERT(devp);
|
||||
DEBUGASSERT(ifacep);
|
||||
DEBUGASSERT(hostp);
|
||||
|
||||
/* Parse the interface details */
|
||||
if(!option || !*option)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
len = strlen(option);
|
||||
if(len > 255)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
|
||||
result = Curl_parse_interface(option, len, &dev, &iface, &host);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
free(*devp);
|
||||
*devp = dev;
|
||||
|
||||
free(*ifacep);
|
||||
*ifacep = iface;
|
||||
|
||||
free(*hostp);
|
||||
*hostp = host;
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
#define C_SSLVERSION_VALUE(x) (x & 0xffff)
|
||||
#define C_SSLVERSION_MAX_VALUE(x) (x & 0xffff0000)
|
||||
|
||||
|
@ -1881,8 +1917,10 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
|
|||
* Set what interface or address/hostname to bind the socket to when
|
||||
* performing an operation and thus what from-IP your connection will use.
|
||||
*/
|
||||
result = Curl_setstropt(&data->set.str[STRING_DEVICE],
|
||||
va_arg(param, char *));
|
||||
result = setstropt_interface(va_arg(param, char *),
|
||||
&data->set.str[STRING_DEVICE],
|
||||
&data->set.str[STRING_INTERFACE],
|
||||
&data->set.str[STRING_BINDHOST]);
|
||||
break;
|
||||
#ifndef CURL_DISABLE_BINDLOCAL
|
||||
case CURLOPT_LOCALPORT:
|
||||
|
|
|
@ -1458,6 +1458,8 @@ enum dupstring {
|
|||
STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */
|
||||
STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL doesn't specify */
|
||||
STRING_DEVICE, /* local network interface/address to use */
|
||||
STRING_INTERFACE, /* local network interface to use */
|
||||
STRING_BINDHOST, /* local address to use */
|
||||
STRING_ENCODING, /* Accept-Encoding string */
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
STRING_FTP_ACCOUNT, /* ftp account data */
|
||||
|
|
|
@ -211,7 +211,7 @@ test1620 test1621 \
|
|||
test1630 test1631 test1632 test1633 test1634 test1635 \
|
||||
\
|
||||
test1650 test1651 test1652 test1653 test1654 test1655 \
|
||||
test1660 test1661 test1662 \
|
||||
test1660 test1661 test1662 test1663 \
|
||||
\
|
||||
test1670 test1671 \
|
||||
\
|
||||
|
|
23
tests/data/test1663
Normal file
23
tests/data/test1663
Normal file
|
@ -0,0 +1,23 @@
|
|||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
unittest
|
||||
interface
|
||||
bind
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
#
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
none
|
||||
</server>
|
||||
<features>
|
||||
unittest
|
||||
</features>
|
||||
<name>
|
||||
unit tests for interface option parsing
|
||||
</name>
|
||||
</client>
|
||||
</testcase>
|
|
@ -37,7 +37,7 @@ UNITPROGS = unit1300 unit1302 unit1303 unit1304 unit1305 unit1307 \
|
|||
unit1608 unit1609 unit1610 unit1611 unit1612 unit1614 unit1615 unit1616 \
|
||||
unit1620 unit1621 \
|
||||
unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 \
|
||||
unit1660 unit1661 \
|
||||
unit1660 unit1661 unit1663 \
|
||||
unit2600 unit2601 unit2602 unit2603 unit2604 \
|
||||
unit3200 \
|
||||
unit3205
|
||||
|
@ -126,6 +126,8 @@ unit1660_SOURCES = unit1660.c $(UNITFILES)
|
|||
|
||||
unit1661_SOURCES = unit1661.c $(UNITFILES)
|
||||
|
||||
unit1663_SOURCES = unit1663.c $(UNITFILES)
|
||||
|
||||
unit2600_SOURCES = unit2600.c $(UNITFILES)
|
||||
|
||||
unit2601_SOURCES = unit2601.c $(UNITFILES)
|
||||
|
|
98
tests/unit/unit1663.c
Normal file
98
tests/unit/unit1663.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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
|
Loading…
Reference in New Issue
Block a user