[tor-commits] [tor/master] Add support for HTTP Connect tunnels

nickm at torproject.org nickm at torproject.org
Tue Sep 5 18:34:39 UTC 2017


commit 4b30ae158175a3a1cbbed4bb418e8ad9be1ba35e
Author: Nick Mathewson <nickm at torproject.org>
Date:   Sun Aug 20 11:59:58 2017 -0400

    Add support for HTTP Connect tunnels
---
 src/or/config.c          |  15 ++++++-
 src/or/connection.c      |   8 +++-
 src/or/connection_edge.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/or/networkstatus.c   |   3 +-
 src/or/or.h              |  18 ++++++--
 src/or/reasons.c         |  52 +++++++++++++++++++++++
 src/or/reasons.h         |   1 +
 src/or/relay.c           |   3 +-
 8 files changed, 197 insertions(+), 8 deletions(-)

diff --git a/src/or/config.c b/src/or/config.c
index 9b6bf40eb..63122344a 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -372,6 +372,7 @@ static config_var_t option_vars_[] = {
   V(HTTPProxyAuthenticator,      STRING,   NULL),
   V(HTTPSProxy,                  STRING,   NULL),
   V(HTTPSProxyAuthenticator,     STRING,   NULL),
+  VPORT(HTTPTunnelPort),
   V(IPv6Exit,                    BOOL,     "0"),
   VAR("ServerTransportPlugin",   LINELIST, ServerTransportPlugin,  NULL),
   V(ServerTransportListenAddr,   LINELIST, NULL),
@@ -2910,7 +2911,8 @@ options_validate_single_onion(or_options_t *options, char **msg)
   const int client_port_set = (options->SocksPort_set ||
                                options->TransPort_set ||
                                options->NATDPort_set ||
-                               options->DNSPort_set);
+                               options->DNSPort_set ||
+                               options->HTTPTunnelPort_set);
   if (rend_service_non_anonymous_mode_enabled(options) && client_port_set &&
       !options->Tor2webMode) {
     REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as "
@@ -6976,6 +6978,15 @@ parse_ports(or_options_t *options, int validate_only,
     *msg = tor_strdup("Invalid NatdPort configuration");
     goto err;
   }
+  if (parse_port_config(ports,
+                        options->HTTPTunnelPort_lines,
+                        "HTTP Tunnel", CONN_TYPE_AP_HTTP_CONNECT_LISTENER,
+                        "127.0.0.1", 0,
+                        ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL)
+                         | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) {
+    *msg = tor_strdup("Invalid HTTPTunnelPort configuration");
+    goto err;
+  }
   {
     unsigned control_port_flags = CL_PORT_NO_STREAM_OPTIONS |
       CL_PORT_WARN_NONLOCAL;
@@ -7053,6 +7064,8 @@ parse_ports(or_options_t *options, int validate_only,
     !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1);
   options->NATDPort_set =
     !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1);
+  options->HTTPTunnelPort_set =
+    !! count_real_listeners(ports, CONN_TYPE_AP_HTTP_CONNECT_LISTENER, 1);
   /* Use options->ControlSocket to test if a control socket is set */
   options->ControlPort_set =
     !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0);
diff --git a/src/or/connection.c b/src/or/connection.c
index 5c65e886c..b760fe872 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -158,7 +158,8 @@ static smartlist_t *outgoing_addrs = NULL;
     case CONN_TYPE_CONTROL_LISTENER: \
     case CONN_TYPE_AP_TRANS_LISTENER: \
     case CONN_TYPE_AP_NATD_LISTENER: \
-    case CONN_TYPE_AP_DNS_LISTENER
+    case CONN_TYPE_AP_DNS_LISTENER: \
+    case CONN_TYPE_AP_HTTP_CONNECT_LISTENER
 
 /**************************************************************/
 
@@ -185,6 +186,7 @@ conn_type_to_string(int type)
     case CONN_TYPE_CONTROL: return "Control";
     case CONN_TYPE_EXT_OR: return "Extended OR";
     case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener";
+    case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return "HTTP tunnel listener";
     default:
       log_warn(LD_BUG, "unknown connection type %d", type);
       tor_snprintf(buf, sizeof(buf), "unknown [%d]", type);
@@ -1702,6 +1704,8 @@ connection_init_accepted_conn(connection_t *conn,
           TO_ENTRY_CONN(conn)->is_transparent_ap = 1;
           conn->state = AP_CONN_STATE_NATD_WAIT;
           break;
+        case CONN_TYPE_AP_HTTP_CONNECT_LISTENER:
+          conn->state = AP_CONN_STATE_HTTP_CONNECT_WAIT;
       }
       break;
     case CONN_TYPE_DIR:
@@ -3394,6 +3398,7 @@ connection_handle_read_impl(connection_t *conn)
     case CONN_TYPE_AP_LISTENER:
     case CONN_TYPE_AP_TRANS_LISTENER:
     case CONN_TYPE_AP_NATD_LISTENER:
+    case CONN_TYPE_AP_HTTP_CONNECT_LISTENER:
       return connection_handle_listener_read(conn, CONN_TYPE_AP);
     case CONN_TYPE_DIR_LISTENER:
       return connection_handle_listener_read(conn, CONN_TYPE_DIR);
@@ -4286,6 +4291,7 @@ connection_is_listener(connection_t *conn)
       conn->type == CONN_TYPE_AP_TRANS_LISTENER ||
       conn->type == CONN_TYPE_AP_DNS_LISTENER ||
       conn->type == CONN_TYPE_AP_NATD_LISTENER ||
+      conn->type == CONN_TYPE_AP_HTTP_CONNECT_LISTENER ||
       conn->type == CONN_TYPE_DIR_LISTENER ||
       conn->type == CONN_TYPE_CONTROL_LISTENER)
     return 1;
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index 12ddc7e82..98522218b 100644
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@ -127,6 +127,7 @@
 
 static int connection_ap_handshake_process_socks(entry_connection_t *conn);
 static int connection_ap_process_natd(entry_connection_t *conn);
+static int connection_ap_process_http_connect(entry_connection_t *conn);
 static int connection_exit_connect_dir(edge_connection_t *exitconn);
 static int consider_plaintext_ports(entry_connection_t *conn, uint16_t port);
 static int connection_ap_supports_optimistic_data(const entry_connection_t *);
@@ -237,6 +238,11 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial)
         return -1;
       }
       return 0;
+    case AP_CONN_STATE_HTTP_CONNECT_WAIT:
+      if (connection_ap_process_http_connect(EDGE_TO_ENTRY_CONN(conn)) < 0) {
+        return -1;
+      }
+      return 0;
     case AP_CONN_STATE_OPEN:
     case EXIT_CONN_STATE_OPEN:
       if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) {
@@ -486,6 +492,7 @@ connection_edge_finished_flushing(edge_connection_t *conn)
     case AP_CONN_STATE_CONNECT_WAIT:
     case AP_CONN_STATE_CONTROLLER_WAIT:
     case AP_CONN_STATE_RESOLVE_WAIT:
+    case AP_CONN_STATE_HTTP_CONNECT_WAIT:
       return 0;
     default:
       log_warn(LD_BUG, "Called in unexpected state %d.",conn->base_.state);
@@ -2349,6 +2356,95 @@ connection_ap_process_natd(entry_connection_t *conn)
   return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
 }
 
+/** Called on an HTTP CONNECT entry connection when some bytes have arrived,
+ * but we have not yet received a full HTTP CONNECT request.  Try to parse an
+ * HTTP CONNECT request from the connection's inbuf.  On success, set up the
+ * connection's socks_request field and try to attach the connection.  On
+ * failure, send an HTTP reply, and mark the connection.
+ */
+static int
+connection_ap_process_http_connect(entry_connection_t *conn)
+{
+  if (BUG(ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_HTTP_CONNECT_WAIT))
+    return -1;
+
+  char *headers = NULL, *body = NULL;
+  char *command = NULL, *addrport = NULL;
+  char *addr = NULL;
+  size_t bodylen = 0;
+
+  const char *errmsg = NULL;
+  int rv = 0;
+
+  const int http_status =
+    fetch_from_buf_http(ENTRY_TO_CONN(conn)->inbuf, &headers, 8192,
+                        &body, &bodylen, 1024, 0);
+  if (http_status < 0) {
+    /* Bad http status */
+    errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
+    goto err;
+  } else if (http_status == 0) {
+    /* no HTTP request yet. */
+    goto done;
+  }
+
+  const int cmd_status = parse_http_command(headers, &command, &addrport);
+  if (cmd_status < 0) {
+    errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
+    goto err;
+  }
+  tor_assert(command);
+  tor_assert(addrport);
+  if (strcasecmp(command, "connect")) {
+    errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
+    goto err;
+  }
+
+  tor_assert(conn->socks_request);
+  socks_request_t *socks = conn->socks_request;
+  uint16_t port;
+  if (tor_addr_port_split(LOG_WARN, addrport, &addr, &port) < 0) {
+    errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
+    goto err;
+  }
+  if (strlen(addr) >= MAX_SOCKS_ADDR_LEN) {
+    errmsg = "HTTP/1.0 414 Request-URI Too Long\r\n\r\n";
+    goto err;
+  }
+
+  /* XXXX Look at headers */
+
+  socks->command = SOCKS_COMMAND_CONNECT;
+  socks->listener_type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER;
+  strlcpy(socks->address, addr, sizeof(socks->address));
+  socks->port = port;
+
+  control_event_stream_status(conn, STREAM_EVENT_NEW, 0);
+
+  rv = connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL);
+
+  // XXXX send a "100 Continue" message?
+
+  goto done;
+
+ err:
+  if (BUG(errmsg == NULL))
+    errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
+  log_warn(LD_EDGE, "Saying %s", escaped(errmsg));
+  connection_write_to_buf(errmsg, strlen(errmsg), ENTRY_TO_CONN(conn));
+  connection_mark_unattached_ap(conn,
+                                END_STREAM_REASON_HTTPPROTOCOL|
+                                END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
+
+ done:
+  tor_free(headers);
+  tor_free(body);
+  tor_free(command);
+  tor_free(addrport);
+  tor_free(addr);
+  return rv;
+}
+
 /** Iterate over the two bytes of stream_id until we get one that is not
  * already in use; return it. Return 0 if can't get a unique stream_id.
  */
@@ -2972,7 +3068,14 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply,
     conn->socks_request->has_finished = 1;
     return;
   }
-  if (conn->socks_request->socks_version == 4) {
+  if (conn->socks_request->listener_type ==
+       CONN_TYPE_AP_HTTP_CONNECT_LISTENER) {
+    const char *response = end_reason_to_http_connect_response_line(endreason);
+    if (!response) {
+      response = "HTTP/1.0 400 Bad Request\r\n\r\n";
+    }
+    connection_write_to_buf(response, strlen(response), ENTRY_TO_CONN(conn));
+  } else if (conn->socks_request->socks_version == 4) {
     memset(buf,0,SOCKS4_NETWORK_LEN);
     buf[1] = (status==SOCKS5_SUCCEEDED ? SOCKS4_GRANTED : SOCKS4_REJECT);
     /* leave version, destport, destip zero */
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 69bff55cf..e6550beb8 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -1683,7 +1683,8 @@ any_client_port_set(const or_options_t *options)
           options->TransPort_set ||
           options->NATDPort_set ||
           options->ControlPort_set ||
-          options->DNSPort_set);
+          options->DNSPort_set ||
+          options->HTTPTunnelPort_set);
 }
 
 /**
diff --git a/src/or/or.h b/src/or/or.h
index ff11c7279..98fb677b7 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -226,8 +226,10 @@ typedef enum {
 #define CONN_TYPE_EXT_OR 16
 /** Type for sockets listening for Extended ORPort connections. */
 #define CONN_TYPE_EXT_OR_LISTENER 17
+/** Type for sockets listening for HTTP CONNECT tunnel connections. */
+#define CONN_TYPE_AP_HTTP_CONNECT_LISTENER 18
 
-#define CONN_TYPE_MAX_ 17
+#define CONN_TYPE_MAX_ 19
 /* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in
  * connection_t. */
 
@@ -348,7 +350,9 @@ typedef enum {
 /** State for a transparent natd connection: waiting for original
  * destination. */
 #define AP_CONN_STATE_NATD_WAIT 12
-#define AP_CONN_STATE_MAX_ 12
+/** State for an HTTP tunnel: waiting for an HTTP CONNECT command. */
+#define AP_CONN_STATE_HTTP_CONNECT_WAIT 13
+#define AP_CONN_STATE_MAX_ 13
 
 /** True iff the AP_CONN_STATE_* value <b>s</b> means that the corresponding
  * edge connection is not attached to any circuit. */
@@ -645,6 +649,10 @@ typedef enum {
 /** The target address is in a private network (like 127.0.0.1 or 10.0.0.1);
  * you don't want to do that over a randomly chosen exit */
 #define END_STREAM_REASON_PRIVATE_ADDR 262
+/** This is an HTTP tunnel connection and the client used or misused HTTP in a
+ * way we can't handle.
+ */
+#define END_STREAM_REASON_HTTPPROTOCOL 263
 
 /** Bitwise-and this value with endreason to mask out all flags. */
 #define END_STREAM_REASON_MASK 511
@@ -3693,6 +3701,8 @@ typedef struct {
   } TransProxyType_parsed;
   config_line_t *NATDPort_lines; /**< Ports to listen on for transparent natd
                             * connections. */
+  /** Ports to listen on for HTTP Tunnel connections. */
+  config_line_t *HTTPTunnelPort_lines;
   config_line_t *ControlPort_lines; /**< Ports to listen on for control
                                * connections. */
   config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on
@@ -3719,7 +3729,8 @@ typedef struct {
    * configured in one of the _lines options above.
    * For client ports, also true if there is a unix socket configured.
    * If you are checking for client ports, you may want to use:
-   *   SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set
+   *   SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set ||
+   *   HTTPTunnelPort_set
    * rather than SocksPort_set.
    *
    * @{
@@ -3732,6 +3743,7 @@ typedef struct {
   unsigned int DirPort_set : 1;
   unsigned int DNSPort_set : 1;
   unsigned int ExtORPort_set : 1;
+  unsigned int HTTPTunnelPort_set : 1;
   /**@}*/
 
   int AssumeReachable; /**< Whether to publish our descriptor regardless. */
diff --git a/src/or/reasons.c b/src/or/reasons.c
index e6c325f1b..717cf57c2 100644
--- a/src/or/reasons.c
+++ b/src/or/reasons.c
@@ -45,6 +45,8 @@ stream_end_reason_to_control_string(int reason)
     case END_STREAM_REASON_CANT_ATTACH: return "CANT_ATTACH";
     case END_STREAM_REASON_NET_UNREACHABLE: return "NET_UNREACHABLE";
     case END_STREAM_REASON_SOCKSPROTOCOL: return "SOCKS_PROTOCOL";
+    // XXXX Controlspec
+    case END_STREAM_REASON_HTTPPROTOCOL: return "HTTP_PROTOCOL";
 
     case END_STREAM_REASON_PRIVATE_ADDR: return "PRIVATE_ADDR";
 
@@ -138,6 +140,11 @@ stream_end_reason_to_socks5_response(int reason)
       return SOCKS5_NET_UNREACHABLE;
     case END_STREAM_REASON_SOCKSPROTOCOL:
       return SOCKS5_GENERAL_ERROR;
+    case END_STREAM_REASON_HTTPPROTOCOL:
+      // LCOV_EXCL_START
+      tor_assert_nonfatal_unreached();
+      return SOCKS5_GENERAL_ERROR;
+      // LCOV_EXCL_STOP
     case END_STREAM_REASON_PRIVATE_ADDR:
       return SOCKS5_GENERAL_ERROR;
 
@@ -442,3 +449,48 @@ bandwidth_weight_rule_to_string(bandwidth_weight_rule_t rule)
   }
 }
 
+/** Given a RELAY_END reason value, convert it to an HTTP response to be
+ * send over an HTTP tunnel connection. */
+const char *
+end_reason_to_http_connect_response_line(int endreason)
+{
+  endreason &= END_STREAM_REASON_MASK;
+  /* XXXX these are probably all wrong. Should they all be 502? */
+  switch (endreason) {
+    case 0:
+      return "HTTP/1.0 200 OK\r\n\r\n";
+    case END_STREAM_REASON_MISC:
+      return "HTTP/1.0 500 Internal Server Error\r\n\r\n";
+    case END_STREAM_REASON_RESOLVEFAILED:
+      return "HTTP/1.0 404 Not Found (resolve failed)\r\n\r\n";
+    case END_STREAM_REASON_NOROUTE:
+      return "HTTP/1.0 404 Not Found (no route)\r\n\r\n";
+    case END_STREAM_REASON_CONNECTREFUSED:
+      return "HTTP/1.0 403 Forbidden (connection refused)\r\n\r\n";
+    case END_STREAM_REASON_EXITPOLICY:
+      return "HTTP/1.0 403 Forbidden (exit policy)\r\n\r\n";
+    case END_STREAM_REASON_DESTROY:
+      return "HTTP/1.0 502 Bad Gateway (destroy cell received)\r\n\r\n";
+    case END_STREAM_REASON_DONE:
+      return "HTTP/1.0 502 Bad Gateway (unexpected close)\r\n\r\n";
+    case END_STREAM_REASON_TIMEOUT:
+      return "HTTP/1.0 504 Gateway Timeout\r\n\r\n";
+    case END_STREAM_REASON_HIBERNATING:
+      return "HTTP/1.0 502 Bad Gateway (hibernating server)\r\n\r\n";
+    case END_STREAM_REASON_INTERNAL:
+      return "HTTP/1.0 502 Bad Gateway (internal error)\r\n\r\n";
+    case END_STREAM_REASON_RESOURCELIMIT:
+      return "HTTP/1.0 502 Bad Gateway (resource limit)\r\n\r\n";
+    case END_STREAM_REASON_CONNRESET:
+      return "HTTP/1.0 403 Forbidden (connection reset)\r\n\r\n";
+    case END_STREAM_REASON_TORPROTOCOL:
+      return "HTTP/1.0 502 Bad Gateway (tor protocol violation)\r\n\r\n";
+    case END_STREAM_REASON_ENTRYPOLICY:
+      return "HTTP/1.0 403 Forbidden (entry policy violation)\r\n\r\n";
+    case END_STREAM_REASON_NOTDIRECTORY: /* Fall Through */
+    default:
+      tor_assert_nonfatal_unreached();
+      return "HTTP/1.0 500 Internal Server Error (weird end reason)\r\n\r\n";
+  }
+}
+
diff --git a/src/or/reasons.h b/src/or/reasons.h
index 1cadf4e89..f98db55bd 100644
--- a/src/or/reasons.h
+++ b/src/or/reasons.h
@@ -26,6 +26,7 @@ const char *socks4_response_code_to_string(uint8_t code);
 const char *socks5_response_code_to_string(uint8_t code);
 
 const char *bandwidth_weight_rule_to_string(enum bandwidth_weight_rule_t rule);
+const char *end_reason_to_http_connect_response_line(int endreason);
 
 #endif
 
diff --git a/src/or/relay.c b/src/or/relay.c
index 18ccc65b8..87ab3b793 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -1467,8 +1467,9 @@ connection_edge_process_relay_cell_not_open(
     circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ));
     /* don't send a socks reply to transparent conns */
     tor_assert(entry_conn->socks_request != NULL);
-    if (!entry_conn->socks_request->has_finished)
+    if (!entry_conn->socks_request->has_finished) {
       connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0);
+    }
 
     /* Was it a linked dir conn? If so, a dir request just started to
      * fetch something; this could be a bootstrap status milestone. */





More information about the tor-commits mailing list