Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SOCKS5 Proxy support for TCP #1676

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/iperf.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,14 @@ struct iperf_test
int timestamps; /* --timestamps */
char *timestamp_format;

char *socks5_host; /* --socks5 option */
uint16_t socks5_port; /* --socks5 option optional value */
char *socks5_username; /* --socks5 option optional value */
char *socks5_password; /* --socks5 option optional value */
char socks5_bind_atyp; /* from socks5 CONNECT response ATYP */
char *socks5_bind_host; /* from socks5 CONNECT response BIND.ADDR*/
uint16_t socks5_bind_port; /* from socks5 CONNECT response BIND.PORT */

char *json_output_string; /* rendered JSON output if json_output is set */
/* Select related parameters */
int max_fd;
Expand Down
250 changes: 248 additions & 2 deletions src/iperf_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ usage()
void
usage_long(FILE *f)
{
fprintf(f, usage_longstr, DEFAULT_NO_MSG_RCVD_TIMEOUT, UDP_RATE / (1024*1024), DEFAULT_PACING_TIMER, DURATION, DEFAULT_TCP_BLKSIZE / 1024, DEFAULT_UDP_BLKSIZE);
fprintf(f, usage_longstr, DEFAULT_NO_MSG_RCVD_TIMEOUT, UDP_RATE / (1024*1024), DEFAULT_PACING_TIMER, DURATION, DEFAULT_TCP_BLKSIZE / 1024, DEFAULT_UDP_BLKSIZE, SOCKS5_DEFAULT_PORT);
}


Expand Down Expand Up @@ -1100,6 +1100,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
{"version6", no_argument, NULL, '6'},
{"tos", required_argument, NULL, 'S'},
{"dscp", required_argument, NULL, OPT_DSCP},
{"socks5", required_argument, NULL, OPT_SOCKS5},
{"extra-data", required_argument, NULL, OPT_EXTRA_DATA},
#if defined(HAVE_FLOWLABEL)
{"flowlabel", required_argument, NULL, 'L'},
Expand Down Expand Up @@ -1157,7 +1158,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
char* comma;
#endif /* HAVE_CPU_AFFINITY */
char* slash;
char *p, *p1;
char *p, *p1, *p2;
struct xbind_entry *xbe;
double farg;
int rcv_timeout_in = 0;
Expand Down Expand Up @@ -1433,6 +1434,47 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
}
client_flag = 1;
break;
case OPT_SOCKS5: // Format: "[username:password@]<host addr/fqdn>[:port]"
if (strlen(optarg) <= 0) {
i_errno = IESOCKS5HOST;
return -1;
}
p1 = strtok(optarg, "@"); // p1 -> user:password
if (p1 == NULL) {
i_errno = IESOCKS5HOST;
return -1;
}
p = strtok(NULL, "@"); // p -> host[:port]
if (p == NULL) {
p = p1;
p1 = NULL;
}
p2 = strtok(p, ":"); // parse host[:port]
if (strlen(p2) <= 0) {
i_errno = IESOCKS5HOST;
return -1;
}
test->socks5_host = strdup(p2);
p2 = strtok(NULL, ":");
if (p2 && strlen(p2) > 0) {
test->socks5_port = atoi(p2);
}
if (p1) { // parse user:password
p2 = strtok(p1, ":");
if (strlen(p2) <= 0 || strlen(p2) > 255) {
i_errno = IESOCKS5HOST;
return -1;
}
test->socks5_username = strdup(p2);
p2 = strtok(NULL, ":");
if (!p2 || strlen(p2) <= 0 || strlen(p2) > 255) {
i_errno = IESOCKS5HOST;
return -1;
}
test->socks5_password = strdup(p2);
}
client_flag = 1;
break;
case OPT_EXTRA_DATA:
test->extra_data = strdup(optarg);
client_flag = 1;
Expand Down Expand Up @@ -1740,6 +1782,12 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
return -1;
}

// SOCKS5 Proxy is supported only for TCP
if(test->role == 'c' && test->socks5_host && test->protocol->id != Ptcp) {
i_errno = IESOCKS5RTCPONLY;
return -1;
}

if (blksize == 0) {
if (test->protocol->id == Pudp)
blksize = 0; /* try to dynamically determine from MSS */
Expand Down Expand Up @@ -2943,6 +2991,12 @@ iperf_defaults(struct iperf_test *testp)
testp->stats_interval = testp->reporter_interval = 1;
testp->num_streams = 1;

testp->socks5_host = NULL;
testp->socks5_port = SOCKS5_DEFAULT_PORT;
testp->socks5_username = NULL;
testp->socks5_password = NULL;
testp->socks5_bind_host = NULL;

testp->settings->domain = AF_UNSPEC;
testp->settings->unit_format = 'a';
testp->settings->socket_bufsize = 0; /* use autotuning */
Expand Down Expand Up @@ -3100,6 +3154,14 @@ iperf_free_test(struct iperf_test *test)
free(test->remote_congestion_used);
if (test->timestamp_format)
free(test->timestamp_format);
if (test->socks5_host)
free(test->socks5_host);
if (test->socks5_username)
free(test->socks5_username);
if (test->socks5_password)
free(test->socks5_password);
if (test->socks5_bind_host)
free(test->socks5_bind_host);
if (test->omit_timer != NULL)
tmr_cancel(test->omit_timer);
if (test->timer != NULL)
Expand Down Expand Up @@ -3289,6 +3351,23 @@ iperf_reset_test(struct iperf_test *test)
free(test->extra_data);
test->extra_data = NULL;
}
if (test->socks5_host) {
free(test->socks5_host);
test->socks5_host = NULL;
}
test->socks5_port = SOCKS5_DEFAULT_PORT;
if (test->socks5_username) {
free(test->socks5_username);
test->socks5_username = NULL;
}
if (test->socks5_password) {
free(test->socks5_password);
test->socks5_password = NULL;
}
if (test->socks5_bind_host) {
free(test->socks5_bind_host);
test->socks5_bind_host = NULL;
}

/* Free output line buffers, if any (on the server only) */
struct iperf_textline *t;
Expand Down Expand Up @@ -4614,6 +4693,173 @@ iperf_add_stream(struct iperf_test *test, struct iperf_stream *sp)
}
}

/**************************************************************************/

/* iperf_socks5_handshake
*
* Handshake with a SOCKS5 Proxy per RFC1928, RFC1929
*/
int
iperf_socks5_handshake(struct iperf_test *test, int s) {
char req[1024];
char res[1024];
char selected_mthod;
char *p, *p1;
size_t len;
int ret;
uint16_t net_order_short;

// Send method selection request [RFC1928]
p = req;
*p++ = 5; // VERSION
if (test->socks5_username) // Number of METHODs supported
*p++ = 2;
else
*p++ = 1;
*p++ = 0; // NO AUTHENTICATION REQUIRED
if (test->socks5_username) *p++ = 2; // USERNAME/PASSWORD
if (Nwrite(s, req, p - req, Ptcp) < 0) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Writing SOCKS5 auth methods message failed\n");
return -1;
}

// Receive selected method
if (Nread(s, res, 2, Ptcp) != 2) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Reading selected SOCKS5 method message failed\n");
return -1;
}

selected_mthod = res[1];
if (res[0] != 5 || (selected_mthod != 0 && selected_mthod != 2)) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Ilegal SOCKS5 method selection response: version=%d, auth method=%d\n", res[0], selected_mthod);
return -1;
}
if (test->debug) {
iperf_printf(test, "SOCKS5 server selected authentication method %d\n", selected_mthod);
}

// Send Username/Password request and receive the auth response [RFC1929]
if (selected_mthod == 2) {
p = req;
*p++ = 1; // VERSION
len = strlen(test->socks5_username);
*p++ = len;
memcpy(p, test->socks5_username, len); // USERNAME
p += len;
len = strlen(test->socks5_password);
*p++ = len;
memcpy(p, test->socks5_password, len); // PASSWORD
p += len;

if (Nwrite(s, req, p - req, Ptcp) < 0) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Writing SOCKS5 Username/Password request message failed\n");
return -1;
}

if ((ret = Nread(s, res, 2, Ptcp)) != 2) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Reading SOCKS5 Username/Password response failed; Returned %d\n", ret);
return -1;
}
if (res[1] != 0) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "SOCKS5 Username/Password failed with error %d\n", res[1]);
return -1;
}
}

// Send CONNECT request [RFC1928]
p = req;
*p++ = 5; // VERSION
*p++ = 1; // CMD = CONNECT
*p++ = 0; // RESERVED
*p++ = 3; // ATYPE = DOMAINNAME:
len = strlen(test->server_hostname);
if (len > 255) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "iperf3 host option length is limited to 255 chars when SOCKS5 is used\n");
return -1;
}
*p++ = len;
memcpy(p, test->server_hostname, len); // ADDR
p += len;
net_order_short = htons(test->server_port);
p1 = (char *)&net_order_short;
*p++ = *p1++; // PORT
*p++ = *p1;
if (Nwrite(s, req, p - req, Ptcp) < 0) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Writing SOCKS5 CONNECT message failed\n");
return -1;
}

// Read CONNECT response [RFC1928]
if ((ret = Nread(s, res, 4, Ptcp)) != 4) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Reading SOCKS5 CONNECT response failed; Returned %d\n", ret);
return -1;
}

if (res[0] != 5 || res[1] != 0 || res[2] != 0) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "SOCKS5 CONNECT failed with error %d\n", res[1]);
return -1;
}

// Get BND.ADDR length
test->socks5_bind_atyp = res[3]; // ATYP
switch (test->socks5_bind_atyp) {
case 1: // IP V4 address
len = 4;
break;
case 3: // DOMAINNAME:
if ((ret = read(s, res, 1)) != 1) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Failed to read SOCKS5 CONNECT response BND.ADDR length; Returned %d\n", ret);
return -1;
}
len = (unsigned char)res[0];
break;
case 4: // IP V6 address
len = 16;
break;
default:
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Illegal SOCKS5 CONNECT response ATYP %d\n", res[3]);
return -1;
}
// Read BND.ADDR
if ((ret = Nread(s, res, len, Ptcp)) != len) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Failed to read SOCKS5 detailes BND.ADDR; Returned %d\n", ret);
return -1;
}
res[len] = '\0';
test->socks5_bind_host = strdup(res);
// Read BND.PORT
if ((ret = Nread(s, res, 2, Ptcp)) != 2) {
i_errno = IESOCKS5HANDSHAKE;
iperf_err(test, "Failed to read SOCKS5 detailes BND.PORT; Returned %d\n", ret);
return -1;
}
p1 = (char *)&net_order_short;
*p1++ = res[0];
*p1 = res[1];
test->socks5_bind_port = ntohs(net_order_short);
if (test->debug) {
iperf_printf(test, "SOCKS5 server BIND ADDR type=%d, PORT=%d\n", test->socks5_bind_atyp, test->socks5_bind_port);
}

return 0;
}

/**************************************************************************/


/* This pair of routines gets inserted into the snd/rcv function pointers
** when there's a -F flag. They handle the file stuff and call the real
** snd/rcv functions, which have been saved in snd2/rcv2.
Expand Down
13 changes: 12 additions & 1 deletion src/iperf_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t;
#define DEFAULT_PACING_TIMER 1000
#define DEFAULT_NO_MSG_RCVD_TIMEOUT 120000
#define MIN_NO_MSG_RCVD_TIMEOUT 100
#define SOCKS5_DEFAULT_PORT 1080

#define WARN_STR_LEN 128

Expand Down Expand Up @@ -100,6 +101,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t;
#define OPT_RCV_TIMEOUT 27
#define OPT_JSON_STREAM 28
#define OPT_SND_TIMEOUT 29
#define OPT_SOCKS5 30

/* states */
#define TEST_START 1
Expand Down Expand Up @@ -308,6 +310,12 @@ void iperf_free_stream(struct iperf_stream * sp);
*/
int iperf_common_sockopts(struct iperf_test *, int s);

/**
* iperf_socks5_handshake - handshake with a SOCKS5 Proxy per RFC1928, RFC1929
*
*/
int iperf_socks5_handshake(struct iperf_test *test, int s);

int has_tcpinfo(void);
int has_tcpinfo_retransmits(void);
void save_tcpinfo(struct iperf_stream *sp, struct iperf_interval_results *irp);
Expand Down Expand Up @@ -419,6 +427,8 @@ enum {
IESNDTIMEOUT = 33, // Illegal message send timeout
IEUDPFILETRANSFER = 34, // Cannot transfer file using UDP
IESERVERAUTHUSERS = 35, // Cannot access authorized users file
IESOCKS5HOST = 36, // Illegal SOCKS5 host / creadentials
IESOCKS5RTCPONLY = 37, // SOCKS5 Proxy is supported only for TCP
/* Test errors */
IENEWTEST = 100, // Unable to create a new test (check perror)
IEINITTEST = 101, // Test initialization failed (check perror)
Expand Down Expand Up @@ -473,7 +483,8 @@ enum {
IEPTHREADCANCEL=151, // Unable to cancel thread (check perror)
IEPTHREADJOIN=152, // Unable to join thread (check perror)
IEPTHREADATTRINIT=153, // Unable to initialize thread attribute (check perror)
IEPTHREADATTRDESTROY=154, // Unable to destroy thread attribute (check perror)
IEPTHREADATTRDESTROY=154, // Unable to destroy thread attribute (check perror)
IESOCKS5HANDSHAKE = 155, // SOCKS5 Handshake with the server failed
/* Stream errors */
IECREATESTREAM = 200, // Unable to create a new stream (check herror/perror)
IEINITSTREAM = 201, // Unable to initialize stream (check herror/perror)
Expand Down
Loading
Loading