socks: support unix sockets for socks proxy

Usage:
  curl -x "socks5h://localhost/run/tor/socks" "https://example.com"

Updated runtests.pl to run a socksd server listening on unix socket

Added tests test1467 test1468

Added documentation for proxy command line option and socks proxy
options

Closes #8668
This commit is contained in:
Balakrishnan Balasubramanian 2022-05-19 15:33:22 +02:00 committed by Daniel Stenberg
parent ee52bead4d
commit dfa84a0450
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
12 changed files with 240 additions and 23 deletions

View File

@ -14,6 +14,9 @@ specified or http:// will be treated as HTTP proxy. Use socks4://, socks4a://,
socks5:// or socks5h:// to request a specific SOCKS version to be used.
(Added in 7.21.7)
Unix domain sockets are supported for socks proxy. Set localhost for the host
part. e.g. socks5h://localhost/path/to/socket.sock
HTTPS proxy support via https:// protocol prefix was added in 7.52.0 for
OpenSSL, GnuTLS and NSS.

View File

@ -10,6 +10,9 @@ Use the specified SOCKS4 proxy. If the port number is not specified, it is
assumed at port 1080. Using this socket type make curl resolve the host name
and passing the address on to the proxy.
To specify proxy on a unix domain socket, use localhost for host, e.g.
socks4://localhost/path/to/socket.sock
This option overrides any previous use of --proxy, as they are mutually
exclusive.

View File

@ -9,6 +9,9 @@ See-also: socks4 socks5 socks5-hostname
Use the specified SOCKS4a proxy. If the port number is not specified, it is
assumed at port 1080. This asks the proxy to resolve the host name.
To specify proxy on a unix domain socket, use localhost for host, e.g.
socks4a://localhost/path/to/socket.sock
This option overrides any previous use of --proxy, as they are mutually
exclusive.

View File

@ -9,6 +9,9 @@ See-also: socks5 socks4a
Use the specified SOCKS5 proxy (and let the proxy resolve the host name). If
the port number is not specified, it is assumed at port 1080.
To specify proxy on a unix domain socket, use localhost for host, e.g.
socks5h://localhost/path/to/socket.sock
This option overrides any previous use of --proxy, as they are mutually
exclusive.

View File

@ -9,6 +9,9 @@ See-also: socks5-hostname socks4a
Use the specified SOCKS5 proxy - but resolve the host name locally. If the
port number is not specified, it is assumed at port 1080.
To specify proxy on a unix domain socket, use localhost for host, e.g.
socks5://localhost/path/to/socket.sock
This option overrides any previous use of --proxy, as they are mutually
exclusive.

View File

@ -5,7 +5,7 @@
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
.\" * Copyright (C) 1998 - 2022, 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
@ -73,6 +73,9 @@ use of a proxy, even if there is an environment variable set for it.
A proxy host string can also include protocol scheme (http://) and embedded
user + password.
Unix domain sockets are supported for socks proxies since 7.84.0. Set
localhost for the host part. e.g. socks5h://localhost/path/to/socket.sock
The application does not have to keep the string around after setting this
option.
.SH "Environment variables"

View File

@ -147,6 +147,10 @@ static void conn_free(struct connectdata *conn);
# error READBUFFER_SIZE is too small
#endif
#ifdef USE_UNIX_SOCKETS
#define UNIX_SOCKET_PREFIX "localhost"
#endif
/*
* get_protocol_family()
*
@ -2407,13 +2411,18 @@ static CURLcode parse_proxy(struct Curl_easy *data,
int port = -1;
char *proxyuser = NULL;
char *proxypasswd = NULL;
char *host;
char *host = NULL;
bool sockstype;
CURLUcode uc;
struct proxy_info *proxyinfo;
CURLU *uhp = curl_url();
CURLcode result = CURLE_OK;
char *scheme = NULL;
#ifdef USE_UNIX_SOCKETS
char *path = NULL;
bool is_unix_proxy = FALSE;
#endif
if(!uhp) {
result = CURLE_OUT_OF_MEMORY;
@ -2538,21 +2547,54 @@ static CURLcode parse_proxy(struct Curl_easy *data,
result = CURLE_OUT_OF_MEMORY;
goto error;
}
Curl_safefree(proxyinfo->host.rawalloc);
proxyinfo->host.rawalloc = host;
if(host[0] == '[') {
/* this is a numerical IPv6, strip off the brackets */
size_t len = strlen(host);
host[len-1] = 0; /* clear the trailing bracket */
host++;
zonefrom_url(uhp, data, conn);
#ifdef USE_UNIX_SOCKETS
if(sockstype && strcasecompare(UNIX_SOCKET_PREFIX, host)) {
uc = curl_url_get(uhp, CURLUPART_PATH, &path, CURLU_URLDECODE);
if(uc) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
/* path will be "/", if no path was was found */
if(strcmp("/", path)) {
is_unix_proxy = TRUE;
free(host);
host = aprintf(UNIX_SOCKET_PREFIX"%s", path);
if(!host) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
Curl_safefree(proxyinfo->host.rawalloc);
proxyinfo->host.rawalloc = host;
proxyinfo->host.name = host;
host = NULL;
}
}
proxyinfo->host.name = host;
if(!is_unix_proxy) {
#endif
Curl_safefree(proxyinfo->host.rawalloc);
proxyinfo->host.rawalloc = host;
if(host[0] == '[') {
/* this is a numerical IPv6, strip off the brackets */
size_t len = strlen(host);
host[len-1] = 0; /* clear the trailing bracket */
host++;
zonefrom_url(uhp, data, conn);
}
proxyinfo->host.name = host;
host = NULL;
#ifdef USE_UNIX_SOCKETS
}
#endif
error:
free(proxyuser);
free(proxypasswd);
free(host);
free(scheme);
#ifdef USE_UNIX_SOCKETS
free(path);
#endif
curl_url_cleanup(uhp);
return result;
}
@ -3384,25 +3426,35 @@ static CURLcode resolve_server(struct Curl_easy *data,
struct Curl_dns_entry *hostaddr = NULL;
#ifdef USE_UNIX_SOCKETS
if(conn->unix_domain_socket) {
char *unix_path = NULL;
if(conn->unix_domain_socket)
unix_path = conn->unix_domain_socket;
#ifndef CURL_DISABLE_PROXY
else if(conn->socks_proxy.host.name
&& !strncmp(UNIX_SOCKET_PREFIX"/",
conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX)))
unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1;
#endif
if(unix_path) {
/* Unix domain sockets are local. The host gets ignored, just use the
* specified domain socket address. Do not cache "DNS entries". There is
* no DNS involved and we already have the filesystem path available */
const char *path = conn->unix_domain_socket;
hostaddr = calloc(1, sizeof(struct Curl_dns_entry));
if(!hostaddr)
result = CURLE_OUT_OF_MEMORY;
else {
bool longpath = FALSE;
hostaddr->addr = Curl_unix2addr(path, &longpath,
hostaddr->addr = Curl_unix2addr(unix_path, &longpath,
conn->bits.abstract_unix_socket);
if(hostaddr->addr)
hostaddr->inuse++;
else {
/* Long paths are not supported for now */
if(longpath) {
failf(data, "Unix socket path too long: '%s'", path);
failf(data, "Unix socket path too long: '%s'", unix_path);
result = CURLE_COULDNT_RESOLVE_HOST;
}
else

View File

@ -128,6 +128,7 @@ Available substitute variables include:
- `%HTTPTLS6PORT` - IPv6 port number of the HTTP TLS server
- `%HTTPTLSPORT` - Port number of the HTTP TLS server
- `%HTTPUNIXPATH` - Path to the Unix socket of the HTTP server
- `%SOCKSUNIXPATH` - Absolute Path to the Unix socket of the SOCKS server
- `%IMAP6PORT` - IPv6 port number of the IMAP server
- `%IMAPPORT` - Port number of the IMAP server
- `%MQTTPORT` - Port number of the MQTT server

View File

@ -185,7 +185,7 @@ test1432 test1433 test1434 test1435 test1436 test1437 test1438 test1439 \
test1440 test1441 test1442 test1443 test1444 test1445 test1446 test1447 \
test1448 test1449 test1450 test1451 test1452 test1453 test1454 test1455 \
test1456 test1457 test1458 test1459 test1460 test1461 test1462 test1463 \
test1464 test1465 test1466 \
test1464 test1465 test1466 test1467 test1468 \
\
test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \

59
tests/data/test1467 Normal file
View File

@ -0,0 +1,59 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
SOCKS5
</keywords>
</info>
#
# Server-side
<reply>
<data>
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Accept-Ranges: bytes
Content-Length: 6
Connection: close
Content-Type: text/html
Funny-head: yesyes
-foo-
</data>
</reply>
#
# Client-side
<client>
<features>
proxy
unix-sockets
</features>
<server>
http
socks5unix
</server>
<name>
HTTP GET via SOCKS5 proxy via unix sockets
</name>
<command>
--socks5 localhost%SOCKSUNIXPATH http://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
</verify>
</testcase>

63
tests/data/test1468 Normal file
View File

@ -0,0 +1,63 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
SOCKS5
SOCKS5h
</keywords>
</info>
#
# Server-side
<reply>
<data>
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Accept-Ranges: bytes
Content-Length: 6
Connection: close
Content-Type: text/html
Funny-head: yesyes
-foo-
</data>
</reply>
#
# Client-side
<client>
<features>
proxy
unix-sockets
</features>
<server>
http
socks5unix
</server>
<name>
HTTP GET with host name using SOCKS5h via unix sockets
</name>
<command>
http://this.is.a.host.name:%HTTPPORT/%TESTNUMBER --proxy socks5h://localhost%SOCKSUNIXPATH
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
GET /%TESTNUMBER HTTP/1.1
Host: this.is.a.host.name:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
<socks>
atyp 3 => this.is.a.host.name
</socks>
</verify>
</testcase>

View File

@ -162,6 +162,7 @@ my $SMBPORT=$noport; # SMB server port
my $SMBSPORT=$noport; # SMBS server port
my $TELNETPORT=$noport; # TELNET server port with negotiation
my $HTTPUNIXPATH; # HTTP server Unix domain socket path
my $SOCKSUNIXPATH; # socks server Unix domain socket path
my $use_external_proxy = 0;
my $proxy_address;
@ -1435,6 +1436,7 @@ my %protofunc = ('http' => \&verifyhttp,
'tftp' => \&verifyftp,
'ssh' => \&verifyssh,
'socks' => \&verifysocks,
'socks5unix' => \&verifysocks,
'gopher' => \&verifyhttp,
'httptls' => \&verifyhttptls,
'dict' => \&verifyftp,
@ -2379,7 +2381,7 @@ sub runmqttserver {
# Start the socks server
#
sub runsocksserver {
my ($id, $verbose, $ipv6) = @_;
my ($id, $verbose, $ipv6, $is_unix) = @_;
my $ip=$HOSTIP;
my $proto = 'socks';
my $ipvnum = 4;
@ -2411,12 +2413,21 @@ sub runsocksserver {
$logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum);
# start our socks server, get commands from the FTP cmd file
my $cmd="server/socksd".exe_ext('SRV').
" --port 0 ".
" --pidfile $pidfile".
" --portfile $portfile".
" --backend $HOSTIP".
" --config $FTPDCMD";
my $cmd="";
if($is_unix) {
$cmd="server/socksd".exe_ext('SRV').
" --pidfile $pidfile".
" --unix-socket $SOCKSUNIXPATH".
" --backend $HOSTIP".
" --config $FTPDCMD";
} else {
$cmd="server/socksd".exe_ext('SRV').
" --port 0 ".
" --pidfile $pidfile".
" --portfile $portfile".
" --backend $HOSTIP".
" --config $FTPDCMD";
}
my ($sockspid, $pid2) = startnew($cmd, $pidfile, 30, 0);
if($sockspid <= 0 || !pidexists($sockspid)) {
@ -3337,6 +3348,7 @@ sub checksystem {
logmsg "* Unix socket paths:\n";
if($http_unix) {
logmsg sprintf("* HTTP-Unix:%s\n", $HTTPUNIXPATH);
logmsg sprintf("* Socks-Unix:%s\n", $SOCKSUNIXPATH);
}
}
}
@ -3397,6 +3409,7 @@ sub subVariables {
# server Unix domain socket paths
$$thing =~ s/${prefix}HTTPUNIXPATH/$HTTPUNIXPATH/g;
$$thing =~ s/${prefix}SOCKSUNIXPATH/$SOCKSUNIXPATH/g;
# client IP addresses
$$thing =~ s/${prefix}CLIENT6IP/$CLIENT6IP/g;
@ -5273,6 +5286,16 @@ sub startservers {
$run{'socks'}="$pid $pid2";
}
}
elsif($what eq "socks5unix") {
if(!$run{'socks5unix'}) {
($pid, $pid2) = runsocksserver("2", $verbose, "", "unix");
if($pid <= 0) {
return "failed starting socks5unix server";
}
printf ("* pid socks5unix => %d %d\n", $pid, $pid2) if($verbose);
$run{'socks5unix'}="$pid $pid2";
}
}
elsif($what eq "mqtt" ) {
if(!$run{'mqtt'}) {
($pid, $pid2) = runmqttserver("", $verbose);
@ -5867,6 +5890,7 @@ if ($gdbthis) {
}
$HTTPUNIXPATH = "http$$.sock"; # HTTP server Unix domain socket path
$SOCKSUNIXPATH = $pwd."/socks$$.sock"; # HTTP server Unix domain socket path, absolute path
#######################################################################
# clear and create logging directory: