From 1b07a2d7bf8cc868e2d904191e8c30b48b7ac216 Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Tue, 20 Aug 2019 16:18:24 +0800 Subject: [PATCH 01/10] doc: Add TCPProxy option TCPProxy is used for proxying all outgoing OR connections through some proxy with the protocol specified in the option. --- doc/tor.1.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 06bf022b15..5bcb6ec2bd 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -647,6 +647,22 @@ GENERAL OPTIONS in accordance to RFC 1929. Both username and password must be between 1 and 255 characters. +[[TCPProxy]] **TCPProxy** __protocol__ __host__:__port__:: + Tor will use the given protocol to make all its OR (SSL) connections through + a TCP proxy on host:port, rather than connecting directly to servers. You may + want to set **FascistFirewall** to restrict the set of ports you might try to + connect to, if your proxy only allows connecting to certain ports. There is no + equivalent option for directory connections, because all Tor client versions + that support this option download directory documents via OR connections. + ++ + The only protocol supported right now 'haproxy'. This option is only for + clients. (Default: none) + ++ + The HAProxy version 1 proxy protocol is described in detail at + https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + ++ + Both source IP address and source port will be set to zero. + [[UnixSocksGroupWritable]] **UnixSocksGroupWritable** **0**|**1**:: If this option is set to 0, don't allow the filesystem group to read and write unix sockets (e.g. SocksPort unix:). If the option is set to 1, make From 5a6a6ed33c400c93387f388e3b8fb109d7047f2f Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Wed, 21 Aug 2019 14:56:32 +0800 Subject: [PATCH 02/10] config: Add TCPProxy option for haproxy protocol Read the TCPProxy option and put in or_options_t. --- src/app/config/config.c | 89 +++++++++++++++++++++++++++++++--- src/app/config/config.h | 2 + src/app/config/or_options_st.h | 11 +++++ src/test/test_options.c | 9 ++-- 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/src/app/config/config.c b/src/app/config/config.c index deda2448b6..c67f547cf8 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -522,6 +522,7 @@ static const config_var_t option_vars_[] = { V(Socks5Proxy, STRING, NULL), V(Socks5ProxyUsername, STRING, NULL), V(Socks5ProxyPassword, STRING, NULL), + V(TCPProxy, STRING, NULL), VAR("KeyDirectory", FILENAME, KeyDirectory_option, NULL), V(KeyDirectoryGroupReadable, BOOL, "0"), VAR_D("HSLayer2Nodes", ROUTERSET, HSLayer2Nodes, NULL), @@ -4150,19 +4151,28 @@ options_validate(or_options_t *old_options, or_options_t *options, } } + if (options->TCPProxy) { + int res = parse_tcp_proxy_line(options->TCPProxy, options, msg); + if (res < 0) { + return res; + } + } + /* Check if more than one exclusive proxy type has been enabled. */ if (!!options->Socks4Proxy + !!options->Socks5Proxy + - !!options->HTTPSProxy > 1) + !!options->HTTPSProxy + !!options->TCPProxy > 1) REJECT("You have configured more than one proxy type. " - "(Socks4Proxy|Socks5Proxy|HTTPSProxy)"); + "(Socks4Proxy|Socks5Proxy|HTTPSProxy|TCPProxy)"); /* Check if the proxies will give surprising behavior. */ if (options->HTTPProxy && !(options->Socks4Proxy || options->Socks5Proxy || - options->HTTPSProxy)) { - log_warn(LD_CONFIG, "HTTPProxy configured, but no SOCKS proxy or " - "HTTPS proxy configured. Watch out: this configuration will " - "proxy unencrypted directory connections only."); + options->HTTPSProxy || + options->TCPProxy)) { + log_warn(LD_CONFIG, "HTTPProxy configured, but no SOCKS proxy, " + "HTTPS proxy, or any other TCP proxy configured. Watch out: " + "this configuration will proxy unencrypted directory " + "connections only."); } if (options->Socks5ProxyUsername) { @@ -5962,6 +5972,68 @@ parse_bridge_line(const char *line) return bridge_line; } +/** Parse the contents of a TCPProxy line from line and put it + * in options. Return 0 if the line is well-formed, and -1 if it + * isn't. + * + * This will mutate only options->TCPProxyProtocol, options->TCPProxyAddr, + * and options->TCPProxyPort. + * + * On error, tor_strdup an error explanation into *msg. + */ +STATIC int +parse_tcp_proxy_line(const char *line, or_options_t *options, char **msg) +{ + int ret = 0; + tor_assert(line); + tor_assert(options); + tor_assert(msg); + + smartlist_t *sl = smartlist_new(); + /* Split between the protocol and the address/port. */ + smartlist_split_string(sl, line, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2); + + /* The address/port is not specified. */ + if (smartlist_len(sl) < 2) { + *msg = tor_strdup("TCPProxy has no address/port. Please fix."); + goto err; + } + + char *protocol_string = smartlist_get(sl, 0); + char *addrport_string = smartlist_get(sl, 1); + + /* The only currently supported protocol is 'haproxy'. */ + if (strcasecmp(protocol_string, "haproxy")) { + *msg = tor_strdup("TCPProxy protocol is not supported. Currently " + "the only supported protocol is 'haproxy'. " + "Please fix."); + goto err; + } else { + /* Otherwise, set the correct protocol. */ + options->TCPProxyProtocol = TCP_PROXY_PROTOCOL_HAPROXY; + } + + /* Parse the address/port. */ + if (tor_addr_port_lookup(addrport_string, &options->TCPProxyAddr, + &options->TCPProxyPort) < 0) { + *msg = tor_strdup("TCPProxy address/port failed to parse or resolve. " + "Please fix."); + goto err; + } + + /* Success. */ + ret = 0; + goto end; + + err: + ret = -1; + end: + SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); + smartlist_free(sl); + return ret; +} + /** Read the contents of a ClientTransportPlugin or ServerTransportPlugin * line from line, depending on the value of server. Return 0 * if the line is well-formed, and -1 if it isn't. @@ -6110,9 +6182,10 @@ parse_transport_line(const or_options_t *options, /* ClientTransportPlugins connecting through a proxy is managed only. */ if (!server && (options->Socks4Proxy || options->Socks5Proxy || - options->HTTPSProxy)) { + options->HTTPSProxy || options->TCPProxy)) { log_warn(LD_CONFIG, "You have configured an external proxy with another " - "proxy type. (Socks4Proxy|Socks5Proxy|HTTPSProxy)"); + "proxy type. (Socks4Proxy|Socks5Proxy|HTTPSProxy|" + "TCPProxy)"); goto err; } diff --git a/src/app/config/config.h b/src/app/config/config.h index 44f09e5ee9..aa2b052c01 100644 --- a/src/app/config/config.h +++ b/src/app/config/config.h @@ -266,6 +266,8 @@ STATIC int options_validate(or_options_t *old_options, STATIC int parse_transport_line(const or_options_t *options, const char *line, int validate_only, int server); +STATIC int parse_tcp_proxy_line(const char *line, or_options_t *options, + char **msg); STATIC int consider_adding_dir_servers(const or_options_t *options, const or_options_t *old_options); STATIC void add_default_trusted_dir_authorities(dirinfo_type_t type); diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index ca2d5de2f2..96702d61c8 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -26,6 +26,12 @@ typedef enum {OUTBOUND_ADDR_EXIT, OUTBOUND_ADDR_OR, OUTBOUND_ADDR_EXIT_AND_OR, OUTBOUND_ADDR_MAX} outbound_addr_t; +/** Which protocol to use for TCPProxy. */ +typedef enum { + /** Use the HAProxy proxy protocol. */ + TCP_PROXY_PROTOCOL_HAPROXY +} tcp_proxy_protocol_t; + /** Configuration options for a Tor process. */ struct or_options_t { uint32_t magic_; @@ -423,6 +429,11 @@ struct or_options_t { char *Socks5ProxyUsername; /**< Username for SOCKS5 authentication, if any */ char *Socks5ProxyPassword; /**< Password for SOCKS5 authentication, if any */ + char *TCPProxy; /**< protocol and hostname:port to use as a proxy, if any. */ + tcp_proxy_protocol_t TCPProxyProtocol; /**< Derived from TCPProxy. */ + tor_addr_t TCPProxyAddr; /**< Derived from TCPProxy. */ + uint16_t TCPProxyPort; /**< Derived from TCPProxy. */ + /** List of configuration lines for replacement directory authorities. * If you just want to replace one class of authority at a time, * use the "Alternate*Authority" options below instead. */ diff --git a/src/test/test_options.c b/src/test/test_options.c index 69407a999b..394aff45b9 100644 --- a/src/test/test_options.c +++ b/src/test/test_options.c @@ -2953,7 +2953,7 @@ test_options_validate__proxy(void *ignored) ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); tt_str_op(msg, OP_EQ, "You have configured more than one proxy type. " - "(Socks4Proxy|Socks5Proxy|HTTPSProxy)"); + "(Socks4Proxy|Socks5Proxy|HTTPSProxy|TCPProxy)"); tor_free(msg); free_options_test_data(tdata); @@ -2963,9 +2963,10 @@ test_options_validate__proxy(void *ignored) mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, 0); - expect_log_msg("HTTPProxy configured, but no SOCKS " - "proxy or HTTPS proxy configured. Watch out: this configuration " - "will proxy unencrypted directory connections only.\n"); + expect_log_msg("HTTPProxy configured, but no SOCKS proxy, " + "HTTPS proxy, or any other TCP proxy configured. Watch out: " + "this configuration will proxy unencrypted directory " + "connections only.\n"); tor_free(msg); free_options_test_data(tdata); From 9dd04396ba66602e89df52fd8bc1cbad1201083b Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Thu, 22 Aug 2019 10:25:04 +0800 Subject: [PATCH 03/10] test: Add TCPProxy option for haproxy protocol --- src/test/test_config.c | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/test/test_config.c b/src/test/test_config.c index cbb84e4dcf..a9094c79b8 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -672,6 +672,52 @@ transport_is_needed_mock(const char *transport_name) return transport_is_needed_mock_return; } +static void +test_config_parse_tcp_proxy_line(void *arg) +{ + (void)arg; + + int ret; + char *msg = NULL; + or_options_t *options = get_options_mutable(); + + /* Bad TCPProxy line - too short. */ + ret = parse_tcp_proxy_line("haproxy", options, &msg); + /* Return error. */ + tt_int_op(ret, OP_EQ, -1); + /* Correct error message. */ + tt_str_op(msg, OP_EQ, "TCPProxy has no address/port. Please fix."); + /* Free error message. */ + tor_free(msg); + + /* Bad TCPProxy line - unsupported protocol. */ + ret = parse_tcp_proxy_line("unsupported 95.216.163.36:443", options, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TCPProxy protocol is not supported. Currently the " + "only supported protocol is 'haproxy'. Please fix."); + tor_free(msg); + + /* Bad TCPProxy line - unparsable address/port. */ + ret = parse_tcp_proxy_line("haproxy 95.216.163.36/443", options, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TCPProxy address/port failed to parse or resolve. " + "Please fix."); + tor_free(msg); + + /* Good TCPProxy line - ipv4. */ + ret = parse_tcp_proxy_line("haproxy 95.216.163.36:443", options, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_ptr_op(msg, OP_EQ, NULL); + tt_int_op(options->TCPProxyProtocol, OP_EQ, TCP_PROXY_PROTOCOL_HAPROXY); + /* Correct the address. */ + tt_assert(tor_addr_eq_ipv4h(&options->TCPProxyAddr, 0x5fd8a324)); + tt_int_op(options->TCPProxyPort, OP_EQ, 443); + tor_free(msg); + + done: + ; +} + /** * Test parsing for the ClientTransportPlugin and ServerTransportPlugin config * options. @@ -6097,6 +6143,7 @@ struct testcase_t config_tests[] = { CONFIG_TEST(parse_bridge_line, 0), CONFIG_TEST(parse_transport_options_line, 0), CONFIG_TEST(parse_transport_plugin_line, TT_FORK), + CONFIG_TEST(parse_tcp_proxy_line, TT_FORK), CONFIG_TEST(check_or_create_data_subdir, TT_FORK), CONFIG_TEST(write_to_data_subdir, TT_FORK), CONFIG_TEST(fix_my_family, 0), From 101bdeb02d78aab7d7b4b13e8620203ce6e44b4c Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Thu, 22 Aug 2019 11:53:59 +0800 Subject: [PATCH 04/10] doc: TCPProxy is not supported in TOR_PT_PROXY Because we need to construct the URI using the TCPProxy configuration but we don't have a standard URI scheme for haproxy yet, we decided to not support TCPProxy in TOR_PT_PROXY now. There is no problem with HTTPSProxy, Socks4Proxy, or Socks5Proxy because they all have standard URI schemes. --- src/feature/client/transports.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c index 97bfc8ae30..de383c9836 100644 --- a/src/feature/client/transports.c +++ b/src/feature/client/transports.c @@ -733,6 +733,9 @@ get_pt_proxy_uri(void) const or_options_t *options = get_options(); char *uri = NULL; + /* XXX: Currently TCPProxy is not supported in TOR_PT_PROXY because + * there isn't a standard URI scheme for some proxy protocols, such as + * haproxy. */ if (options->Socks4Proxy || options->Socks5Proxy || options->HTTPSProxy) { char addr[TOR_ADDR_BUF_LEN+1]; From 52e59640f9ae266d25a7727869c84e506c96a1c8 Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Fri, 23 Aug 2019 10:35:50 +0800 Subject: [PATCH 05/10] circuit: Refactor connection_proxy_connect Since connection_proxy_connect is too long now, it's better to create new functions (connection_https_proxy_connect, connection_socks4_proxy_connect, and connection_socks5_proxy_connect) to make connection_proxy_connect shorter. --- src/core/mainloop/connection.c | 315 +++++++++++++++++++-------------- 1 file changed, 181 insertions(+), 134 deletions(-) diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 6094f33e4d..c135bf3959 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -2326,10 +2326,173 @@ conn_get_proxy_type(const connection_t *conn) username NUL: */ #define SOCKS4_STANDARD_BUFFER_SIZE (1 + 1 + 2 + 4 + 1) -/** Write a proxy request of type (socks4, socks5, https) to conn - * for conn->addr:conn->port, authenticating with the auth details given - * in the configuration (if available). SOCKS 5 and HTTP CONNECT proxies - * support authentication. +/** Write a proxy request of https to conn for conn->addr:conn->port, + * authenticating with the auth details given in the configuration + * (if available). + * + * Returns -1 if conn->addr is incompatible with the proxy protocol, and + * 0 otherwise. + */ +static int +connection_https_proxy_connect(connection_t *conn) +{ + tor_assert(conn); + + const or_options_t *options = get_options(); + char buf[1024]; + char *base64_authenticator = NULL; + const char *authenticator = options->HTTPSProxyAuthenticator; + + /* Send HTTP CONNECT and authentication (if available) in + * one request */ + + if (authenticator) { + base64_authenticator = alloc_http_authenticator(authenticator); + if (!base64_authenticator) + log_warn(LD_OR, "Encoding https authenticator failed"); + } + + if (base64_authenticator) { + const char *addrport = fmt_addrport(&conn->addr, conn->port); + tor_snprintf(buf, sizeof(buf), "CONNECT %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Proxy-Authorization: Basic %s\r\n\r\n", + addrport, + addrport, + base64_authenticator); + tor_free(base64_authenticator); + } else { + tor_snprintf(buf, sizeof(buf), "CONNECT %s HTTP/1.0\r\n\r\n", + fmt_addrport(&conn->addr, conn->port)); + } + + connection_buf_add(buf, strlen(buf), conn); + conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK; + + return 0; +} + +/** Write a proxy request of socks4 to conn for conn->addr:conn->port. + * + * Returns -1 if conn->addr is incompatible with the proxy protocol, and + * 0 otherwise. + */ +static int +connection_socks4_proxy_connect(connection_t *conn) +{ + tor_assert(conn); + + unsigned char *buf; + uint16_t portn; + uint32_t ip4addr; + size_t buf_size = 0; + char *socks_args_string = NULL; + + /* Send a SOCKS4 connect request */ + + if (tor_addr_family(&conn->addr) != AF_INET) { + log_warn(LD_NET, "SOCKS4 client is incompatible with IPv6"); + return -1; + } + + { /* If we are here because we are trying to connect to a + pluggable transport proxy, check if we have any SOCKS + arguments to transmit. If we do, compress all arguments to + a single string in 'socks_args_string': */ + + if (conn_get_proxy_type(conn) == PROXY_PLUGGABLE) { + socks_args_string = + pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port); + if (socks_args_string) + log_debug(LD_NET, "Sending out '%s' as our SOCKS argument string.", + socks_args_string); + } + } + + { /* Figure out the buffer size we need for the SOCKS message: */ + + buf_size = SOCKS4_STANDARD_BUFFER_SIZE; + + /* If we have a SOCKS argument string, consider its size when + calculating the buffer size: */ + if (socks_args_string) + buf_size += strlen(socks_args_string); + } + + buf = tor_malloc_zero(buf_size); + + ip4addr = tor_addr_to_ipv4n(&conn->addr); + portn = htons(conn->port); + + buf[0] = 4; /* version */ + buf[1] = SOCKS_COMMAND_CONNECT; /* command */ + memcpy(buf + 2, &portn, 2); /* port */ + memcpy(buf + 4, &ip4addr, 4); /* addr */ + + /* Next packet field is the userid. If we have pluggable + transport SOCKS arguments, we have to embed them + there. Otherwise, we use an empty userid. */ + if (socks_args_string) { /* place the SOCKS args string: */ + tor_assert(strlen(socks_args_string) > 0); + tor_assert(buf_size >= + SOCKS4_STANDARD_BUFFER_SIZE + strlen(socks_args_string)); + strlcpy((char *)buf + 8, socks_args_string, buf_size - 8); + tor_free(socks_args_string); + } else { + buf[8] = 0; /* no userid */ + } + + connection_buf_add((char *)buf, buf_size, conn); + tor_free(buf); + + conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; + return 0; +} + +/** Write a proxy request of socks5 to conn for conn->addr:conn->port, + * authenticating with the auth details given in the configuration + * (if available). + * + * Returns -1 if conn->addr is incompatible with the proxy protocol, and + * 0 otherwise. + */ +static int +connection_socks5_proxy_connect(connection_t *conn) +{ + tor_assert(conn); + + const or_options_t *options = get_options(); + unsigned char buf[4]; /* fields: vers, num methods, method list */ + + /* Send a SOCKS5 greeting (connect request must wait) */ + + buf[0] = 5; /* version */ + + /* We have to use SOCKS5 authentication, if we have a + Socks5ProxyUsername or if we want to pass arguments to our + pluggable transport proxy: */ + if ((options->Socks5ProxyUsername) || + (conn_get_proxy_type(conn) == PROXY_PLUGGABLE && + (get_socks_args_by_bridge_addrport(&conn->addr, conn->port)))) { + /* number of auth methods */ + buf[1] = 2; + buf[2] = 0x00; /* no authentication */ + buf[3] = 0x02; /* rfc1929 Username/Passwd auth */ + conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929; + } else { + buf[1] = 1; + buf[2] = 0x00; /* no authentication */ + conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE; + } + + connection_buf_add((char *)buf, 2 + buf[1], conn); + return 0; +} + +/** Write a proxy request of type (socks4, socks5, https, haproxy) + * to conn for conn->addr:conn->port, authenticating with the auth details + * given in the configuration (if available). SOCKS 5 and HTTP CONNECT + * proxies support authentication. * * Returns -1 if conn->addr is incompatible with the proxy protocol, and * 0 otherwise. @@ -2339,152 +2502,36 @@ conn_get_proxy_type(const connection_t *conn) int connection_proxy_connect(connection_t *conn, int type) { - const or_options_t *options; + int ret = 0; tor_assert(conn); - options = get_options(); - switch (type) { - case PROXY_CONNECT: { - char buf[1024]; - char *base64_authenticator=NULL; - const char *authenticator = options->HTTPSProxyAuthenticator; - - /* Send HTTP CONNECT and authentication (if available) in - * one request */ - - if (authenticator) { - base64_authenticator = alloc_http_authenticator(authenticator); - if (!base64_authenticator) - log_warn(LD_OR, "Encoding https authenticator failed"); - } - - if (base64_authenticator) { - const char *addrport = fmt_addrport(&conn->addr, conn->port); - tor_snprintf(buf, sizeof(buf), "CONNECT %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Proxy-Authorization: Basic %s\r\n\r\n", - addrport, - addrport, - base64_authenticator); - tor_free(base64_authenticator); - } else { - tor_snprintf(buf, sizeof(buf), "CONNECT %s HTTP/1.0\r\n\r\n", - fmt_addrport(&conn->addr, conn->port)); - } - - connection_buf_add(buf, strlen(buf), conn); - conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK; + case PROXY_CONNECT: + ret = connection_https_proxy_connect(conn); break; - } - case PROXY_SOCKS4: { - unsigned char *buf; - uint16_t portn; - uint32_t ip4addr; - size_t buf_size = 0; - char *socks_args_string = NULL; - - /* Send a SOCKS4 connect request */ - - if (tor_addr_family(&conn->addr) != AF_INET) { - log_warn(LD_NET, "SOCKS4 client is incompatible with IPv6"); - return -1; - } - - { /* If we are here because we are trying to connect to a - pluggable transport proxy, check if we have any SOCKS - arguments to transmit. If we do, compress all arguments to - a single string in 'socks_args_string': */ - - if (conn_get_proxy_type(conn) == PROXY_PLUGGABLE) { - socks_args_string = - pt_get_socks_args_for_proxy_addrport(&conn->addr, conn->port); - if (socks_args_string) - log_debug(LD_NET, "Sending out '%s' as our SOCKS argument string.", - socks_args_string); - } - } - - { /* Figure out the buffer size we need for the SOCKS message: */ - - buf_size = SOCKS4_STANDARD_BUFFER_SIZE; - - /* If we have a SOCKS argument string, consider its size when - calculating the buffer size: */ - if (socks_args_string) - buf_size += strlen(socks_args_string); - } - - buf = tor_malloc_zero(buf_size); - - ip4addr = tor_addr_to_ipv4n(&conn->addr); - portn = htons(conn->port); - - buf[0] = 4; /* version */ - buf[1] = SOCKS_COMMAND_CONNECT; /* command */ - memcpy(buf + 2, &portn, 2); /* port */ - memcpy(buf + 4, &ip4addr, 4); /* addr */ - - /* Next packet field is the userid. If we have pluggable - transport SOCKS arguments, we have to embed them - there. Otherwise, we use an empty userid. */ - if (socks_args_string) { /* place the SOCKS args string: */ - tor_assert(strlen(socks_args_string) > 0); - tor_assert(buf_size >= - SOCKS4_STANDARD_BUFFER_SIZE + strlen(socks_args_string)); - strlcpy((char *)buf + 8, socks_args_string, buf_size - 8); - tor_free(socks_args_string); - } else { - buf[8] = 0; /* no userid */ - } - - connection_buf_add((char *)buf, buf_size, conn); - tor_free(buf); - - conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; + case PROXY_SOCKS4: + ret = connection_socks4_proxy_connect(conn); break; - } - case PROXY_SOCKS5: { - unsigned char buf[4]; /* fields: vers, num methods, method list */ - - /* Send a SOCKS5 greeting (connect request must wait) */ - - buf[0] = 5; /* version */ - - /* We have to use SOCKS5 authentication, if we have a - Socks5ProxyUsername or if we want to pass arguments to our - pluggable transport proxy: */ - if ((options->Socks5ProxyUsername) || - (conn_get_proxy_type(conn) == PROXY_PLUGGABLE && - (get_socks_args_by_bridge_addrport(&conn->addr, conn->port)))) { - /* number of auth methods */ - buf[1] = 2; - buf[2] = 0x00; /* no authentication */ - buf[3] = 0x02; /* rfc1929 Username/Passwd auth */ - conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929; - } else { - buf[1] = 1; - buf[2] = 0x00; /* no authentication */ - conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE; - } - - connection_buf_add((char *)buf, 2 + buf[1], conn); + case PROXY_SOCKS5: + ret = connection_socks5_proxy_connect(conn); break; - } default: log_err(LD_BUG, "Invalid proxy protocol, %d", type); tor_fragile_assert(); - return -1; + ret = -1; + break; } - log_debug(LD_NET, "set state %s", - connection_proxy_state_to_string(conn->proxy_state)); + if (ret == 0) { + log_debug(LD_NET, "set state %s", + connection_proxy_state_to_string(conn->proxy_state)); + } - return 0; + return ret; } /** Read conn's inbuf. If the http response from the proxy is all From 119004e87d6303de5e90e7dfad87dd89bae6bd5f Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Fri, 23 Aug 2019 11:44:49 +0800 Subject: [PATCH 06/10] circuit: Implement haproxy --- src/core/include.am | 2 + src/core/mainloop/connection.c | 48 +++++++++++++++++++++- src/core/mainloop/connection.h | 4 +- src/core/or/connection_or.c | 15 ++++++- src/core/or/or.h | 5 ++- src/core/or/or_connection_st.h | 2 +- src/core/proto/.may_include | 6 ++- src/core/proto/proto_haproxy.c | 45 ++++++++++++++++++++ src/core/proto/proto_haproxy.h | 12 ++++++ src/feature/control/btrack_orconn_cevent.c | 1 + 10 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 src/core/proto/proto_haproxy.c create mode 100644 src/core/proto/proto_haproxy.h diff --git a/src/core/include.am b/src/core/include.am index 9b4b251c81..8f455c1773 100644 --- a/src/core/include.am +++ b/src/core/include.am @@ -63,6 +63,7 @@ LIBTOR_APP_A_SOURCES = \ src/core/proto/proto_cell.c \ src/core/proto/proto_control0.c \ src/core/proto/proto_ext_or.c \ + src/core/proto/proto_haproxy.c \ src/core/proto/proto_http.c \ src/core/proto/proto_socks.c \ src/feature/api/tor_api.c \ @@ -295,6 +296,7 @@ noinst_HEADERS += \ src/core/proto/proto_cell.h \ src/core/proto/proto_control0.h \ src/core/proto/proto_ext_or.h \ + src/core/proto/proto_haproxy.h \ src/core/proto/proto_http.h \ src/core/proto/proto_socks.h \ src/feature/api/tor_api_internal.h \ diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index c135bf3959..de91b93cb6 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -83,6 +83,7 @@ #include "core/or/reasons.h" #include "core/or/relay.h" #include "core/or/crypt_path.h" +#include "core/proto/proto_haproxy.h" #include "core/proto/proto_http.h" #include "core/proto/proto_socks.h" #include "feature/client/dnsserv.h" @@ -2317,7 +2318,11 @@ conn_get_proxy_type(const connection_t *conn) return PROXY_SOCKS4; else if (options->Socks5Proxy) return PROXY_SOCKS5; - else + else if (options->TCPProxy) { + /* The only supported protocol in TCPProxy is haproxy. */ + tor_assert(options->TCPProxyProtocol == TCP_PROXY_PROTOCOL_HAPROXY); + return PROXY_HAPROXY; + } else return PROXY_NONE; } @@ -2489,6 +2494,35 @@ connection_socks5_proxy_connect(connection_t *conn) return 0; } +/** Write a proxy request of haproxy to conn for conn->addr:conn->port. + * + * Returns -1 if conn->addr is incompatible with the proxy protocol, and + * 0 otherwise. + */ +static int +connection_haproxy_proxy_connect(connection_t *conn) +{ + int ret = 0; + tor_addr_port_t *addr_port = tor_addr_port_new(&conn->addr, conn->port); + char *buf = haproxy_format_proxy_header_line(addr_port); + + if (buf == NULL) { + ret = -1; + goto done; + } + + connection_buf_add(buf, strlen(buf), conn); + /* In haproxy, we don't have to wait for the response, but we wait for ack. + * So we can set the state to be PROXY_HAPROXY_WAIT_FOR_FLUSH. */ + conn->proxy_state = PROXY_HAPROXY_WAIT_FOR_FLUSH; + + ret = 0; + done: + tor_free(buf); + tor_free(addr_port); + return ret; +} + /** Write a proxy request of type (socks4, socks5, https, haproxy) * to conn for conn->addr:conn->port, authenticating with the auth details * given in the configuration (if available). SOCKS 5 and HTTP CONNECT @@ -2519,6 +2553,10 @@ connection_proxy_connect(connection_t *conn, int type) ret = connection_socks5_proxy_connect(conn); break; + case PROXY_HAPROXY: + ret = connection_haproxy_proxy_connect(conn); + break; + default: log_err(LD_BUG, "Invalid proxy protocol, %d", type); tor_fragile_assert(); @@ -5498,6 +5536,13 @@ get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, *port = options->Socks5ProxyPort; *proxy_type = PROXY_SOCKS5; return 0; + } else if (options->TCPProxy) { + tor_addr_copy(addr, &options->TCPProxyAddr); + *port = options->TCPProxyPort; + /* The only supported protocol in TCPProxy is haproxy. */ + tor_assert(options->TCPProxyProtocol == TCP_PROXY_PROTOCOL_HAPROXY); + *proxy_type = PROXY_HAPROXY; + return 0; } tor_addr_make_unspec(addr); @@ -5535,6 +5580,7 @@ proxy_type_to_string(int proxy_type) case PROXY_CONNECT: return "HTTP"; case PROXY_SOCKS4: return "SOCKS4"; case PROXY_SOCKS5: return "SOCKS5"; + case PROXY_HAPROXY: return "HAPROXY"; case PROXY_PLUGGABLE: return "pluggable transports SOCKS"; case PROXY_NONE: return "NULL"; default: tor_assert(0); diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index c93f1ef8e8..2ebb053cca 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -75,8 +75,10 @@ struct buf_t; #define PROXY_SOCKS5_WANT_AUTH_RFC1929_OK 6 /* We use a SOCKS5 proxy and we just sent our CONNECT command. */ #define PROXY_SOCKS5_WANT_CONNECT_OK 7 +/* We use an HAPROXY proxy and we just sent the proxy header. */ +#define PROXY_HAPROXY_WAIT_FOR_FLUSH 8 /* We use a proxy and we CONNECTed successfully!. */ -#define PROXY_CONNECTED 8 +#define PROXY_CONNECTED 9 /** State for any listener connection. */ #define LISTENER_STATE_READY 0 diff --git a/src/core/or/connection_or.c b/src/core/or/connection_or.c index 4c93351e31..d9db17ef77 100644 --- a/src/core/or/connection_or.c +++ b/src/core/or/connection_or.c @@ -726,6 +726,18 @@ connection_or_finished_flushing(or_connection_t *conn) switch (conn->base_.state) { case OR_CONN_STATE_PROXY_HANDSHAKING: + /* PROXY_HAPROXY gets connected by receiving an ack. */ + if (conn->proxy_type == PROXY_HAPROXY) { + tor_assert(TO_CONN(conn)->proxy_state == PROXY_HAPROXY_WAIT_FOR_FLUSH); + TO_CONN(conn)->proxy_state = PROXY_CONNECTED; + + if (connection_tls_start_handshake(conn, 0) < 0) { + /* TLS handshaking error of some kind. */ + connection_or_close_for_error(conn, 0); + return -1; + } + break; + } case OR_CONN_STATE_OPEN: case OR_CONN_STATE_OR_HANDSHAKING_V2: case OR_CONN_STATE_OR_HANDSHAKING_V3: @@ -765,8 +777,9 @@ connection_or_finished_connecting(or_connection_t *or_conn) return -1; } - connection_start_reading(conn); connection_or_change_state(or_conn, OR_CONN_STATE_PROXY_HANDSHAKING); + connection_start_reading(conn); + return 0; } diff --git a/src/core/or/or.h b/src/core/or/or.h index 990cfacbc0..9a7a9cc4b7 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -168,12 +168,13 @@ struct curve25519_public_key_t; #define PROXY_CONNECT 1 #define PROXY_SOCKS4 2 #define PROXY_SOCKS5 3 -/* !!!! If there is ever a PROXY_* type over 3, we must grow the proxy_type +#define PROXY_HAPROXY 4 +/* !!!! If there is ever a PROXY_* type over 7, we must grow the proxy_type * field in or_connection_t */ /* Pluggable transport proxy type. Don't use this in or_connection_t, * instead use the actual underlying proxy type (see above). */ -#define PROXY_PLUGGABLE 4 +#define PROXY_PLUGGABLE 5 /** How many circuits do we want simultaneously in-progress to handle * a given stream? */ diff --git a/src/core/or/or_connection_st.h b/src/core/or/or_connection_st.h index 051fcd00d3..ae94b42cd9 100644 --- a/src/core/or/or_connection_st.h +++ b/src/core/or/or_connection_st.h @@ -58,7 +58,7 @@ struct or_connection_t { /** True iff this is an outgoing connection. */ unsigned int is_outgoing:1; - unsigned int proxy_type:2; /**< One of PROXY_NONE...PROXY_SOCKS5 */ + unsigned int proxy_type:3; /**< One of PROXY_NONE...PROXY_HAPROXY */ unsigned int wide_circ_ids:1; /** True iff this connection has had its bootstrap failure logged with * control_event_bootstrap_problem. */ diff --git a/src/core/proto/.may_include b/src/core/proto/.may_include index c1647a5cf9..a66c3f83a6 100644 --- a/src/core/proto/.may_include +++ b/src/core/proto/.may_include @@ -4,7 +4,11 @@ orconfig.h lib/crypt_ops/*.h lib/buf/*.h +lib/malloc/*.h +lib/string/*.h + +lib/net/address.h trunnel/*.h -core/proto/*.h \ No newline at end of file +core/proto/*.h diff --git a/src/core/proto/proto_haproxy.c b/src/core/proto/proto_haproxy.c new file mode 100644 index 0000000000..856f2ab152 --- /dev/null +++ b/src/core/proto/proto_haproxy.c @@ -0,0 +1,45 @@ +/* Copyright (c) 2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define PROTO_HAPROXY_PRIVATE +#include "lib/malloc/malloc.h" +#include "lib/net/address.h" +#include "lib/string/printf.h" +#include "core/proto/proto_haproxy.h" + +/** Return a newly allocated PROXY header null-terminated string. Returns NULL + * if addr_port->addr is incompatible with the proxy protocol. + */ +char * +haproxy_format_proxy_header_line(const tor_addr_port_t *addr_port) +{ + tor_assert(addr_port); + + sa_family_t family = tor_addr_family(&addr_port->addr); + const char *family_string = NULL; + const char *src_addr_string = NULL; + + switch (family) { + case AF_INET: + family_string = "TCP4"; + src_addr_string = "0.0.0.0"; + break; + case AF_INET6: + family_string = "TCP6"; + src_addr_string = "::"; + break; + default: + /* Unknown family. */ + return NULL; + } + + char *buf; + char addrbuf[TOR_ADDR_BUF_LEN]; + + tor_addr_to_str(addrbuf, &addr_port->addr, sizeof(addrbuf), 0); + + tor_asprintf(&buf, "PROXY %s %s %s 0 %d\r\n", family_string, src_addr_string, + addrbuf, addr_port->port); + + return buf; +} diff --git a/src/core/proto/proto_haproxy.h b/src/core/proto/proto_haproxy.h new file mode 100644 index 0000000000..fd4240f5dd --- /dev/null +++ b/src/core/proto/proto_haproxy.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_HAPROXY_H +#define TOR_PROTO_HAPROXY_H + +struct tor_addr_port_t; + +char *haproxy_format_proxy_header_line( + const struct tor_addr_port_t *addr_port); + +#endif /* !defined(TOR_PROTO_HAPROXY_H) */ diff --git a/src/feature/control/btrack_orconn_cevent.c b/src/feature/control/btrack_orconn_cevent.c index 535aa8f614..d4ba7f79a1 100644 --- a/src/feature/control/btrack_orconn_cevent.c +++ b/src/feature/control/btrack_orconn_cevent.c @@ -45,6 +45,7 @@ using_proxy(const bt_orconn_t *bto) case PROXY_CONNECT: case PROXY_SOCKS4: case PROXY_SOCKS5: + case PROXY_HAPROXY: return true; default: return false; From 41b9dca07bb7dea4758cf97f9bbff7a52b09ebf4 Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Tue, 10 Sep 2019 15:32:08 +0800 Subject: [PATCH 07/10] test: Implement haproxy --- src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_proto_haproxy.c | 66 +++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 src/test/test_proto_haproxy.c diff --git a/src/test/include.am b/src/test/include.am index d8e25dea9f..b7f3d7ece1 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -182,6 +182,7 @@ src_test_test_SOURCES += \ src/test/test_process_descs.c \ src/test/test_prob_distr.c \ src/test/test_procmon.c \ + src/test/test_proto_haproxy.c \ src/test/test_proto_http.c \ src/test/test_proto_misc.c \ src/test/test_protover.c \ diff --git a/src/test/test.c b/src/test/test.c index 6dbec26fa8..11a00606f3 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -899,6 +899,7 @@ struct testgroup_t testgroups[] = { { "prob_distr/", prob_distr_tests }, { "procmon/", procmon_tests }, { "process/", process_tests }, + { "proto/haproxy/", proto_haproxy_tests }, { "proto/http/", proto_http_tests }, { "proto/misc/", proto_misc_tests }, { "protover/", protover_tests }, diff --git a/src/test/test.h b/src/test/test.h index 76c4c0ec75..fdae38a105 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -254,6 +254,7 @@ extern struct testcase_t slow_stochastic_prob_distr_tests[]; extern struct testcase_t procmon_tests[]; extern struct testcase_t process_tests[]; extern struct testcase_t process_descs_tests[]; +extern struct testcase_t proto_haproxy_tests[]; extern struct testcase_t proto_http_tests[]; extern struct testcase_t proto_misc_tests[]; extern struct testcase_t protover_tests[]; diff --git a/src/test/test_proto_haproxy.c b/src/test/test_proto_haproxy.c new file mode 100644 index 0000000000..653bf67e23 --- /dev/null +++ b/src/test/test_proto_haproxy.c @@ -0,0 +1,66 @@ +/* Copyright (c) 2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_proto_haproxy.c + * \brief Tests for our HAProxy protocol parser code + */ + +#define PROTO_HAPROXY_PRIVATE + +#include "test/test.h" +#include "core/proto/proto_haproxy.h" +#include "test/log_test_helpers.h" + +static void +test_format_proxy_header_line(void *arg) +{ + tor_addr_t addr; + tor_addr_port_t *addr_port = NULL; + char *output = NULL; + + (void) arg; + + /* IPv4 address. */ + tor_addr_parse(&addr, "192.168.1.2"); + addr_port = tor_addr_port_new(&addr, 8000); + output = haproxy_format_proxy_header_line(addr_port); + + tt_str_op(output, OP_EQ, "PROXY TCP4 0.0.0.0 192.168.1.2 0 8000\r\n"); + + tor_free(addr_port); + tor_free(output); + + /* IPv6 address. */ + tor_addr_parse(&addr, "123:45:6789::5005:11"); + addr_port = tor_addr_port_new(&addr, 8000); + output = haproxy_format_proxy_header_line(addr_port); + + tt_str_op(output, OP_EQ, "PROXY TCP6 :: 123:45:6789::5005:11 0 8000\r\n"); + + tor_free(addr_port); + tor_free(output); + + /* UNIX socket address. */ + memset(&addr, 0, sizeof(addr)); + addr.family = AF_UNIX; + addr_port = tor_addr_port_new(&addr, 8000); + output = haproxy_format_proxy_header_line(addr_port); + + /* If it's not an IPv4 or IPv6 address, haproxy_format_proxy_header_line + * must return NULL. */ + tt_ptr_op(output, OP_EQ, NULL); + + tor_free(addr_port); + tor_free(output); + + done: + tor_free(addr_port); + tor_free(output); +} + +struct testcase_t proto_haproxy_tests[] = { + { "format_proxy_header_line", test_format_proxy_header_line, 0, NULL, NULL }, + + END_OF_TESTCASES +}; From de58a49a2db20638823a77a1fb4c7f913f4f2169 Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Mon, 21 Oct 2019 18:00:02 +0800 Subject: [PATCH 08/10] test: HAPRoxy protocol --- src/core/or/connection_or.c | 11 +---- src/core/or/connection_or.h | 7 ++++ src/test/test_connection.c | 77 ++++++++++++++++++++++++++++++++++- src/test/test_connection.h | 1 + src/test/test_helpers.c | 81 +++++++++++++++++++++++++++++++++++++ src/test/test_helpers.h | 3 ++ 6 files changed, 169 insertions(+), 11 deletions(-) diff --git a/src/core/or/connection_or.c b/src/core/or/connection_or.c index d9db17ef77..31b8210888 100644 --- a/src/core/or/connection_or.c +++ b/src/core/or/connection_or.c @@ -95,13 +95,6 @@ static unsigned int connection_or_is_bad_for_new_circs(or_connection_t *or_conn); static void connection_or_mark_bad_for_new_circs(or_connection_t *or_conn); -/* - * Call this when changing connection state, so notifications to the owning - * channel can be handled. - */ - -static void connection_or_change_state(or_connection_t *conn, uint8_t state); - static void connection_or_check_canonicity(or_connection_t *conn, int started_here); @@ -457,8 +450,8 @@ connection_or_state_publish(const or_connection_t *conn, uint8_t state) * be notified. */ -static void -connection_or_change_state(or_connection_t *conn, uint8_t state) +MOCK_IMPL(STATIC void, +connection_or_change_state,(or_connection_t *conn, uint8_t state)) { tor_assert(conn); diff --git a/src/core/or/connection_or.h b/src/core/or/connection_or.h index 272f536b83..9d414254a6 100644 --- a/src/core/or/connection_or.h +++ b/src/core/or/connection_or.h @@ -134,6 +134,13 @@ void connection_or_group_set_badness_(smartlist_t *group, int force); #ifdef CONNECTION_OR_PRIVATE STATIC int should_connect_to_relay(const or_connection_t *or_conn); STATIC void note_or_connect_failed(const or_connection_t *or_conn); + +/* + * Call this when changing connection state, so notifications to the owning + * channel can be handled. + */ +MOCK_DECL(STATIC void,connection_or_change_state, + (or_connection_t *conn, uint8_t state)); #endif #ifdef TOR_UNIT_TESTS diff --git a/src/test/test_connection.c b/src/test/test_connection.c index ebe7c6d36f..36543b3af5 100644 --- a/src/test/test_connection.c +++ b/src/test/test_connection.c @@ -10,6 +10,7 @@ #include "core/or/or.h" #include "test/test.h" +#include "app/config/or_options_st.h" #include "core/mainloop/connection.h" #include "core/or/connection_edge.h" #include "feature/hs/hs_common.h" @@ -312,6 +313,31 @@ test_conn_download_status_teardown(const struct testcase_t *tc, void *arg) return rv; } +static void * +test_conn_proxy_connect_setup(const struct testcase_t *tc) +{ + tcp_proxy_protocol_t proxy_type = (tcp_proxy_protocol_t)tc->setup_data; + switch (proxy_type) { + case TCP_PROXY_PROTOCOL_HAPROXY: + return test_conn_get_proxy_or_connection(PROXY_HAPROXY); + default: + return NULL; + } +} + +static int +test_conn_proxy_connect_teardown(const struct testcase_t *tc, void *arg) +{ + (void)tc; + or_connection_t *conn = arg; + + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + done: + return 1; +} + /* Like connection_ap_make_link(), but does much less */ static connection_t * test_conn_get_linked_connection(connection_t *l_conn, uint8_t state) @@ -360,6 +386,10 @@ static struct testcase_setup_t test_conn_download_status_st = { test_conn_download_status_setup, test_conn_download_status_teardown }; +static struct testcase_setup_t test_conn_proxy_connect_st = { + test_conn_proxy_connect_setup, test_conn_proxy_connect_teardown +}; + static void test_conn_get_basic(void *arg) { @@ -788,6 +818,45 @@ test_conn_download_status(void *arg) /* the teardown function removes all the connections in the global list*/; } +static int handshake_start_called = 0; + +static int +handshake_start(or_connection_t *conn, int receiving) +{ + (void)receiving; + + tor_assert(conn); + + handshake_start_called = 1; + return 0; +} + +static void +test_conn_haproxy_proxy_connect(void *arg) +{ + size_t sz; + char *buf = NULL; + or_connection_t *conn = arg; + + MOCK(connection_or_change_state, mock_connection_or_change_state); + MOCK(connection_tls_start_handshake, handshake_start); + + tt_int_op(conn->base_.proxy_state, OP_EQ, PROXY_HAPROXY_WAIT_FOR_FLUSH); + + buf = buf_get_contents(conn->base_.outbuf, &sz); + tt_str_op(buf, OP_EQ, "PROXY TCP4 0.0.0.0 127.0.0.1 0 12345\r\n"); + + connection_or_finished_flushing(conn); + + tt_int_op(conn->base_.proxy_state, OP_EQ, PROXY_CONNECTED); + tt_int_op(handshake_start_called, OP_EQ, 1); + + done: + UNMOCK(connection_or_change_state); + UNMOCK(connection_tls_start_handshake); + tor_free(buf); +} + static node_t test_node; static node_t * @@ -892,10 +961,14 @@ struct testcase_t connection_tests[] = { CONNECTION_TESTCASE(get_basic, TT_FORK, test_conn_get_basic_st), CONNECTION_TESTCASE(get_rend, TT_FORK, test_conn_get_rend_st), CONNECTION_TESTCASE(get_rsrc, TT_FORK, test_conn_get_rsrc_st), - CONNECTION_TESTCASE_ARG(download_status, TT_FORK, + + CONNECTION_TESTCASE_ARG(download_status, TT_FORK, test_conn_download_status_st, FLAV_MICRODESC), - CONNECTION_TESTCASE_ARG(download_status, TT_FORK, + CONNECTION_TESTCASE_ARG(download_status, TT_FORK, test_conn_download_status_st, FLAV_NS), + CONNECTION_TESTCASE_ARG(haproxy_proxy_connect, TT_FORK, + test_conn_proxy_connect_st, + TCP_PROXY_PROTOCOL_HAPROXY), //CONNECTION_TESTCASE(func_suffix, TT_FORK, setup_func_pair), { "failed_orconn_tracker", test_failed_orconn_tracker, TT_FORK, NULL, NULL }, END_OF_TESTCASES diff --git a/src/test/test_connection.h b/src/test/test_connection.h index 40121e6d38..9efe31ebc6 100644 --- a/src/test/test_connection.h +++ b/src/test/test_connection.h @@ -7,6 +7,7 @@ /** Some constants used by test_connection and helpers */ #define TEST_CONN_FAMILY (AF_INET) #define TEST_CONN_ADDRESS "127.0.0.1" +#define TEST_CONN_ADDRESS_2 "127.0.0.2" #define TEST_CONN_PORT (12345) #define TEST_CONN_ADDRESS_PORT "127.0.0.1:12345" #define TEST_CONN_FD_INIT 50 diff --git a/src/test/test_helpers.c b/src/test/test_helpers.c index 8eb3c2c928..f972aca5ba 100644 --- a/src/test/test_helpers.c +++ b/src/test/test_helpers.c @@ -9,6 +9,7 @@ #define ROUTERLIST_PRIVATE #define CONFIG_PRIVATE #define CONNECTION_PRIVATE +#define CONNECTION_OR_PRIVATE #define MAINLOOP_PRIVATE #include "orconfig.h" @@ -19,6 +20,7 @@ #include "lib/confmgt/confparse.h" #include "app/main/subsysmgr.h" #include "core/mainloop/connection.h" +#include "core/or/connection_or.h" #include "lib/crypt_ops/crypto_rand.h" #include "core/mainloop/mainloop.h" #include "feature/nodelist/nodelist.h" @@ -33,6 +35,7 @@ #include "core/or/cell_st.h" #include "core/or/connection_st.h" +#include "core/or/or_connection_st.h" #include "feature/nodelist/node_st.h" #include "core/or/origin_circuit_st.h" #include "feature/nodelist/routerlist_st.h" @@ -194,6 +197,14 @@ fake_close_socket(tor_socket_t sock) return 0; } +/* Helper for test_conn_get_proxy_or_connection() */ +void +mock_connection_or_change_state(or_connection_t *conn, uint8_t state) +{ + tor_assert(conn); + conn->base_.state = state; +} + static int mock_connection_connect_sockaddr_called = 0; static int fake_socket_number = TEST_CONN_FD_INIT; @@ -228,6 +239,76 @@ mock_connection_connect_sockaddr(connection_t *conn, return 1; } +or_connection_t * +test_conn_get_proxy_or_connection(unsigned int proxy_type) +{ + or_connection_t *conn = NULL; + tor_addr_t dst_addr; + tor_addr_t proxy_addr; + int socket_err = 0; + int in_progress = 0; + + MOCK(connection_connect_sockaddr, + mock_connection_connect_sockaddr); + MOCK(connection_write_to_buf_impl_, + connection_write_to_buf_mock); + MOCK(connection_or_change_state, + mock_connection_or_change_state); + MOCK(tor_close_socket, fake_close_socket); + + tor_init_connection_lists(); + + conn = or_connection_new(CONN_TYPE_OR, TEST_CONN_FAMILY); + tt_assert(conn); + + /* Set up a destination address. */ + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, + &dst_addr); + tt_assert(!tor_addr_is_null(&dst_addr)); + + conn->proxy_type = proxy_type; + conn->base_.proxy_state = PROXY_INFANT; + + tor_addr_copy_tight(&conn->base_.addr, &dst_addr); + conn->base_.address = tor_addr_to_str_dup(&dst_addr); + conn->base_.port = TEST_CONN_PORT; + + /* Set up a proxy address. */ + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS_2, TEST_CONN_FAMILY, + &proxy_addr); + tt_assert(!tor_addr_is_null(&proxy_addr)); + + conn->base_.state = OR_CONN_STATE_CONNECTING; + + mock_connection_connect_sockaddr_called = 0; + in_progress = connection_connect(TO_CONN(conn), TEST_CONN_ADDRESS_PORT, + &proxy_addr, TEST_CONN_PORT, &socket_err); + tt_int_op(mock_connection_connect_sockaddr_called, OP_EQ, 1); + tt_assert(!socket_err); + tt_assert(in_progress == 0 || in_progress == 1); + + assert_connection_ok(TO_CONN(conn), time(NULL)); + + in_progress = connection_or_finished_connecting(conn); + tt_int_op(in_progress, OP_EQ, 0); + + assert_connection_ok(TO_CONN(conn), time(NULL)); + + UNMOCK(connection_connect_sockaddr); + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(connection_or_change_state); + UNMOCK(tor_close_socket); + return conn; + + /* On failure */ + done: + UNMOCK(connection_connect_sockaddr); + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(connection_or_change_state); + UNMOCK(tor_close_socket); + return NULL; +} + /** Create and return a new connection/stream */ connection_t * test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose) diff --git a/src/test/test_helpers.h b/src/test/test_helpers.h index d82072bb34..d5a5417983 100644 --- a/src/test/test_helpers.h +++ b/src/test/test_helpers.h @@ -26,6 +26,9 @@ char *buf_get_contents(buf_t *buf, size_t *sz_out); int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name, uint16_t family, tor_addr_t *out); +void mock_connection_or_change_state(or_connection_t *conn, uint8_t state); + +or_connection_t *test_conn_get_proxy_or_connection(unsigned int proxy_type); connection_t *test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose); or_options_t *helper_parse_options(const char *conf); From 4264717ca377464b5304df0e3a322afaef72d812 Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Tue, 22 Oct 2019 01:55:23 +0800 Subject: [PATCH 09/10] test: HTTP CONNECT protocol --- src/test/test_connection.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/test/test_connection.c b/src/test/test_connection.c index 36543b3af5..34ef3fdf18 100644 --- a/src/test/test_connection.c +++ b/src/test/test_connection.c @@ -316,13 +316,7 @@ test_conn_download_status_teardown(const struct testcase_t *tc, void *arg) static void * test_conn_proxy_connect_setup(const struct testcase_t *tc) { - tcp_proxy_protocol_t proxy_type = (tcp_proxy_protocol_t)tc->setup_data; - switch (proxy_type) { - case TCP_PROXY_PROTOCOL_HAPROXY: - return test_conn_get_proxy_or_connection(PROXY_HAPROXY); - default: - return NULL; - } + return test_conn_get_proxy_or_connection(*(unsigned int *)tc->setup_data); } static int @@ -818,6 +812,25 @@ test_conn_download_status(void *arg) /* the teardown function removes all the connections in the global list*/; } +static void +test_conn_https_proxy_connect(void *arg) +{ + size_t sz; + char *buf = NULL; + or_connection_t *conn = arg; + + MOCK(connection_or_change_state, mock_connection_or_change_state); + + tt_int_op(conn->base_.proxy_state, OP_EQ, PROXY_HTTPS_WANT_CONNECT_OK); + + buf = buf_get_contents(conn->base_.outbuf, &sz); + tt_str_op(buf, OP_EQ, "CONNECT 127.0.0.1:12345 HTTP/1.0\r\n\r\n"); + + done: + UNMOCK(connection_or_change_state); + tor_free(buf); +} + static int handshake_start_called = 0; static int @@ -957,6 +970,9 @@ test_failed_orconn_tracker(void *arg) #define CONNECTION_TESTCASE_ARG(name, fork, setup, arg) \ { #name "_" #arg, test_conn_##name, fork, &setup, (void *)arg } +static const unsigned int PROXY_CONNECT_ARG = PROXY_CONNECT; +static const unsigned int PROXY_HAPROXY_ARG = PROXY_HAPROXY; + struct testcase_t connection_tests[] = { CONNECTION_TESTCASE(get_basic, TT_FORK, test_conn_get_basic_st), CONNECTION_TESTCASE(get_rend, TT_FORK, test_conn_get_rend_st), @@ -966,9 +982,12 @@ struct testcase_t connection_tests[] = { test_conn_download_status_st, FLAV_MICRODESC), CONNECTION_TESTCASE_ARG(download_status, TT_FORK, test_conn_download_status_st, FLAV_NS), + + CONNECTION_TESTCASE_ARG(https_proxy_connect, TT_FORK, + test_conn_proxy_connect_st, &PROXY_CONNECT_ARG), CONNECTION_TESTCASE_ARG(haproxy_proxy_connect, TT_FORK, - test_conn_proxy_connect_st, - TCP_PROXY_PROTOCOL_HAPROXY), + test_conn_proxy_connect_st, &PROXY_HAPROXY_ARG), + //CONNECTION_TESTCASE(func_suffix, TT_FORK, setup_func_pair), { "failed_orconn_tracker", test_failed_orconn_tracker, TT_FORK, NULL, NULL }, END_OF_TESTCASES From 14d781fff6bd88c4e0cd5b741629c3e79c8612d9 Mon Sep 17 00:00:00 2001 From: Suphanat Chunhapanya Date: Mon, 2 Dec 2019 15:24:44 +0800 Subject: [PATCH 10/10] circuit: Add change file --- changes/ticket31518 | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changes/ticket31518 diff --git a/changes/ticket31518 b/changes/ticket31518 new file mode 100644 index 0000000000..8deac56b98 --- /dev/null +++ b/changes/ticket31518 @@ -0,0 +1,6 @@ + o Major features (proxy): + - In addition to HTTP CONNECT, SOCKS4, and SOCKS5, Tor can make all OR + connections through the HAProxy server. A new torrc option was added to + specify the address/port of the server: TCPProxy + :. Currently the only supported protocol in the option is + haproxy. Close ticket 31518. Patch done by Suphanat Chunhapanya (haxxpop).