Browse Source

Merge branch 'bug11485_026_v2_squashed'

Nick Mathewson 9 years ago
parent
commit
a3de2dfde6
5 changed files with 329 additions and 81 deletions
  1. 3 0
      changes/ticket11485
  2. 132 62
      src/or/connection.c
  3. 7 0
      src/or/connection.h
  4. 29 7
      src/or/connection_edge.c
  5. 158 12
      src/or/rendservice.c

+ 3 - 0
changes/ticket11485

@@ -0,0 +1,3 @@
+  o Features (hidden services):
+    - Support mapping hidden service virtual ports to AF_UNIX sockets on
+      suitable platforms.  Resolves ticket #11485.

+ 132 - 62
src/or/connection.c

@@ -56,6 +56,11 @@
 #include <pwd.h>
 #endif
 
+#ifdef HAVE_SYS_UN_H
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif
+
 static connection_t *connection_listener_new(
                                const struct sockaddr *listensockaddr,
                                socklen_t listensocklen, int type,
@@ -1585,37 +1590,31 @@ connection_init_accepted_conn(connection_t *conn,
   return 0;
 }
 
-/** Take conn, make a nonblocking socket; try to connect to
- * addr:port (they arrive in *host order*). If fail, return -1 and if
- * applicable put your best guess about errno into *<b>socket_error</b>.
- * Else assign s to conn-\>s: if connected return 1, if EAGAIN return 0.
- *
- * address is used to make the logs useful.
- *
- * On success, add conn to the list of polled connections.
- */
-int
-connection_connect(connection_t *conn, const char *address,
-                   const tor_addr_t *addr, uint16_t port, int *socket_error)
+
+static int
+connection_connect_sockaddr(connection_t *conn,
+                            const struct sockaddr *sa,
+                            socklen_t sa_len,
+                            const struct sockaddr *bindaddr,
+                            socklen_t bindaddr_len,
+                            int *socket_error)
 {
   tor_socket_t s;
   int inprogress = 0;
-  struct sockaddr_storage addrbuf;
-  struct sockaddr *dest_addr;
-  int dest_addr_len;
   const or_options_t *options = get_options();
   int protocol_family;
 
+  tor_assert(conn);
+  tor_assert(sa);
+  tor_assert(socket_error);
+
   if (get_n_open_sockets() >= get_options()->ConnLimit_-1) {
     warn_too_many_conns();
     *socket_error = SOCK_ERRNO(ENOBUFS);
     return -1;
   }
 
-  if (tor_addr_family(addr) == AF_INET6)
-    protocol_family = PF_INET6;
-  else
-    protocol_family = PF_INET;
+  protocol_family = sa->sa_family;
 
   if (get_options()->DisableNetwork) {
     /* We should never even try to connect anyplace if DisableNetwork is set.
@@ -1628,7 +1627,7 @@ connection_connect(connection_t *conn, const char *address,
     return -1;
   }
 
-  s = tor_open_socket_nonblocking(protocol_family,SOCK_STREAM,IPPROTO_TCP);
+  s = tor_open_socket_nonblocking(protocol_family, SOCK_STREAM, 0);
   if (! SOCKET_OK(s)) {
     *socket_error = tor_socket_errno(-1);
     log_warn(LD_NET,"Error creating network socket: %s",
@@ -1641,6 +1640,74 @@ connection_connect(connection_t *conn, const char *address,
              tor_socket_strerror(errno));
   }
 
+  if (bindaddr && bind(s, bindaddr, bindaddr_len) < 0) {
+    *socket_error = tor_socket_errno(s);
+    log_warn(LD_NET,"Error binding network socket: %s",
+             tor_socket_strerror(*socket_error));
+    tor_close_socket(s);
+    return -1;
+  }
+
+  tor_assert(options);
+  if (options->ConstrainedSockets)
+    set_constrained_socket_buffers(s, (int)options->ConstrainedSockSize);
+
+  if (connect(s, sa, sa_len) < 0) {
+    int e = tor_socket_errno(s);
+    if (!ERRNO_IS_CONN_EINPROGRESS(e)) {
+      /* yuck. kill it. */
+      *socket_error = e;
+      log_info(LD_NET,
+               "connect() to socket failed: %s",
+               tor_socket_strerror(e));
+      tor_close_socket(s);
+      return -1;
+    } else {
+      inprogress = 1;
+    }
+  }
+
+  /* it succeeded. we're connected. */
+  log_fn(inprogress ? LOG_DEBUG : LOG_INFO, LD_NET,
+         "Connection to socket %s (sock "TOR_SOCKET_T_FORMAT").",
+         inprogress ? "in progress" : "established", s);
+  conn->s = s;
+  if (connection_add_connecting(conn) < 0) {
+    /* no space, forget it */
+    *socket_error = SOCK_ERRNO(ENOBUFS);
+    return -1;
+  }
+  return inprogress ? 0 : 1;
+
+}
+
+
+/** Take conn, make a nonblocking socket; try to connect to
+ * addr:port (they arrive in *host order*). If fail, return -1 and if
+ * applicable put your best guess about errno into *<b>socket_error</b>.
+ * Else assign s to conn-\>s: if connected return 1, if EAGAIN return 0.
+ *
+ * address is used to make the logs useful.
+ *
+ * On success, add conn to the list of polled connections.
+ */
+int
+connection_connect(connection_t *conn, const char *address,
+                   const tor_addr_t *addr, uint16_t port, int *socket_error)
+{
+  struct sockaddr_storage addrbuf;
+  struct sockaddr_storage bind_addr_ss;
+  struct sockaddr *bind_addr = NULL;
+  struct sockaddr *dest_addr;
+  int dest_addr_len, bind_addr_len = 0;
+  const or_options_t *options = get_options();
+  int protocol_family;
+
+  if (tor_addr_family(addr) == AF_INET6)
+    protocol_family = PF_INET6;
+  else
+    protocol_family = PF_INET;
+
   if (!tor_addr_is_loopback(addr)) {
     const tor_addr_t *ext_addr = NULL;
     if (protocol_family == AF_INET &&
@@ -1650,33 +1717,21 @@ connection_connect(connection_t *conn, const char *address,
              !tor_addr_is_null(&options->OutboundBindAddressIPv6_))
       ext_addr = &options->OutboundBindAddressIPv6_;
     if (ext_addr) {
-      struct sockaddr_storage ext_addr_sa;
       socklen_t ext_addr_len = 0;
-      memset(&ext_addr_sa, 0, sizeof(ext_addr_sa));
-      ext_addr_len = tor_addr_to_sockaddr(ext_addr, 0,
-                                          (struct sockaddr *) &ext_addr_sa,
-                                          sizeof(ext_addr_sa));
+      memset(&bind_addr_ss, 0, sizeof(bind_addr_ss));
+      bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0,
+                                           (struct sockaddr *) &bind_addr_ss,
+                                           sizeof(bind_addr_ss));
       if (ext_addr_len == 0) {
         log_warn(LD_NET,
                  "Error converting OutboundBindAddress %s into sockaddr. "
                  "Ignoring.", fmt_and_decorate_addr(ext_addr));
       } else {
-        if (bind(s, (struct sockaddr *) &ext_addr_sa, ext_addr_len) < 0) {
-          *socket_error = tor_socket_errno(s);
-          log_warn(LD_NET,"Error binding network socket to %s: %s",
-                   fmt_and_decorate_addr(ext_addr),
-                   tor_socket_strerror(*socket_error));
-          tor_close_socket(s);
-          return -1;
-        }
+        bind_addr = (struct sockaddr *)&bind_addr_ss;
       }
     }
   }
 
-  tor_assert(options);
-  if (options->ConstrainedSockets)
-    set_constrained_socket_buffers(s, (int)options->ConstrainedSockSize);
-
   memset(&addrbuf,0,sizeof(addrbuf));
   dest_addr = (struct sockaddr*) &addrbuf;
   dest_addr_len = tor_addr_to_sockaddr(addr, port, dest_addr, sizeof(addrbuf));
@@ -1685,36 +1740,51 @@ connection_connect(connection_t *conn, const char *address,
   log_debug(LD_NET, "Connecting to %s:%u.",
             escaped_safe_str_client(address), port);
 
-  if (connect(s, dest_addr, (socklen_t)dest_addr_len) < 0) {
-    int e = tor_socket_errno(s);
-    if (!ERRNO_IS_CONN_EINPROGRESS(e)) {
-      /* yuck. kill it. */
-      *socket_error = e;
-      log_info(LD_NET,
-               "connect() to %s:%u failed: %s",
-               escaped_safe_str_client(address),
-               port, tor_socket_strerror(e));
-      tor_close_socket(s);
-      return -1;
-    } else {
-      inprogress = 1;
-    }
-  }
+  return connection_connect_sockaddr(conn, dest_addr, dest_addr_len,
+                                     bind_addr, bind_addr_len, socket_error);
+}
 
-  /* it succeeded. we're connected. */
-  log_fn(inprogress?LOG_DEBUG:LOG_INFO, LD_NET,
-         "Connection to %s:%u %s (sock "TOR_SOCKET_T_FORMAT").",
-         escaped_safe_str_client(address),
-         port, inprogress?"in progress":"established", s);
-  conn->s = s;
-  if (connection_add_connecting(conn) < 0) {
-    /* no space, forget it */
-    *socket_error = SOCK_ERRNO(ENOBUFS);
+#ifdef HAVE_SYS_UN_H
+
+/** Take conn, make a nonblocking socket; try to connect to
+ * an AF_UNIX socket at socket_path. If fail, return -1 and if applicable
+ * put your best guess about errno into *<b>socket_error</b>. Else assign s
+ * to conn-\>s: if connected return 1, if EAGAIN return 0.
+ *
+ * On success, add conn to the list of polled connections.
+ */
+int
+connection_connect_unix(connection_t *conn, const char *socket_path,
+                        int *socket_error)
+{
+  struct sockaddr_un dest_addr;
+
+  tor_assert(socket_path);
+
+  /* Check that we'll be able to fit it into dest_addr later */
+  if (strlen(socket_path) + 1 > sizeof(dest_addr.sun_path)) {
+    log_warn(LD_NET,
+             "Path %s is too long for an AF_UNIX socket\n",
+             escaped_safe_str_client(socket_path));
+    *socket_error = SOCK_ERRNO(ENAMETOOLONG);
     return -1;
   }
-  return inprogress ? 0 : 1;
+
+  memset(&dest_addr, 0, sizeof(dest_addr));
+  dest_addr.sun_family = AF_UNIX;
+  strlcpy(dest_addr.sun_path, socket_path, sizeof(dest_addr.sun_path));
+
+  log_debug(LD_NET,
+            "Connecting to AF_UNIX socket at %s.",
+            escaped_safe_str_client(socket_path));
+
+  return connection_connect_sockaddr(conn,
+                       (struct sockaddr *)&dest_addr, sizeof(dest_addr),
+                       NULL, 0, socket_error);
 }
 
+#endif /* defined(HAVE_SYS_UN_H) */
+
 /** Convert state number to string representation for logging purposes.
  */
 static const char *

+ 7 - 0
src/or/connection.h

@@ -89,6 +89,13 @@ int connection_connect(connection_t *conn, const char *address,
                        const tor_addr_t *addr,
                        uint16_t port, int *socket_error);
 
+#ifdef HAVE_SYS_UN_H
+
+int connection_connect_unix(connection_t *conn, const char *socket_path,
+                            int *socket_error);
+
+#endif /* defined(HAVE_SYS_UN_H) */
+
 /** Maximum size of information that we can fit into SOCKS5 username
     or password fields. */
 #define MAX_SOCKS5_AUTH_FIELD_SIZE 255

+ 29 - 7
src/or/connection_edge.c

@@ -2940,7 +2940,7 @@ connection_exit_connect(edge_connection_t *edge_conn)
   const tor_addr_t *addr;
   uint16_t port;
   connection_t *conn = TO_CONN(edge_conn);
-  int socket_error = 0;
+  int socket_error = 0, result;
 
   if ( (!connection_edge_is_rendezvous_stream(edge_conn) &&
         router_compare_to_my_exit_policy(&edge_conn->base_.addr,
@@ -2955,14 +2955,36 @@ connection_exit_connect(edge_connection_t *edge_conn)
     return;
   }
 
-  addr = &conn->addr;
-  port = conn->port;
+#ifdef HAVE_SYS_UN_H
+  if (conn->socket_family != AF_UNIX) {
+#else
+  {
+#endif /* defined(HAVE_SYS_UN_H) */
+    addr = &conn->addr;
+    port = conn->port;
+
+    if (tor_addr_family(addr) == AF_INET6)
+      conn->socket_family = AF_INET6;
+
+    log_debug(LD_EXIT, "about to try connecting");
+    result = connection_connect(conn, conn->address,
+                                addr, port, &socket_error);
+#ifdef HAVE_SYS_UN_H
+  } else {
+    /*
+     * In the AF_UNIX case, we expect to have already had conn->port = 1,
+     * tor_addr_make_unspec(conn->addr) (cf. the way we mark in the incoming
+     * case in connection_handle_listener_read()), and conn->address should
+     * have the socket path to connect to.
+     */
+    tor_assert(conn->address && strlen(conn->address) > 0);
 
-  if (tor_addr_family(addr) == AF_INET6)
-    conn->socket_family = AF_INET6;
+    log_debug(LD_EXIT, "about to try connecting");
+    result = connection_connect_unix(conn, conn->address, &socket_error);
+#endif /* defined(HAVE_SYS_UN_H) */
+  }
 
-  log_debug(LD_EXIT,"about to try connecting");
-  switch (connection_connect(conn, conn->address, addr, port, &socket_error)) {
+  switch (result) {
     case -1: {
       int reason = errno_to_stream_end_reason(socket_error);
       connection_edge_end(edge_conn, reason);

+ 158 - 12
src/or/rendservice.c

@@ -66,9 +66,16 @@ static ssize_t rend_service_parse_intro_for_v3(
  * a real port on some IP.
  */
 typedef struct rend_service_port_config_t {
+  /* The incoming HS virtual port we're mapping */
   uint16_t virtual_port;
+  /* Is this an AF_UNIX port? */
+  unsigned int is_unix_addr:1;
+  /* The outgoing TCP port to use, if !is_unix_addr */
   uint16_t real_port;
+  /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
   tor_addr_t real_addr;
+  /* The socket path to connect to, if is_unix_addr */
+  char unix_addr[FLEXIBLE_ARRAY_MEMBER];
 } rend_service_port_config_t;
 
 /** Try to maintain this many intro points per service by default. */
@@ -279,16 +286,48 @@ rend_add_service(rend_service_t *service)
               service->directory);
     for (i = 0; i < smartlist_len(service->ports); ++i) {
       p = smartlist_get(service->ports, i);
-      log_debug(LD_REND,"Service maps port %d to %s",
-                p->virtual_port, fmt_addrport(&p->real_addr, p->real_port));
+      if (!(p->is_unix_addr)) {
+        log_debug(LD_REND,
+                  "Service maps port %d to %s",
+                  p->virtual_port,
+                  fmt_addrport(&p->real_addr, p->real_port));
+      } else {
+#ifdef HAVE_SYS_UN_H
+        log_debug(LD_REND,
+                  "Service maps port %d to socket at \"%s\"",
+                  p->virtual_port, p->unix_addr);
+#else
+        log_debug(LD_REND,
+                  "Service maps port %d to an AF_UNIX socket, but we "
+                  "have no AF_UNIX support on this platform.  This is "
+                  "probably a bug.",
+                  p->virtual_port);
+#endif /* defined(HAVE_SYS_UN_H) */
+      }
     }
   }
 }
 
+/** Return a new rend_service_port_config_t with its path set to
+ * <b>socket_path</b> or empty if <b>socket_path</b> is NULL */
+static rend_service_port_config_t *
+rend_service_port_config_new(const char *socket_path)
+{
+  if (!socket_path)
+    return tor_malloc_zero(sizeof(rend_service_port_config_t));
+
+  const size_t pathlen = strlen(socket_path) + 1;
+  rend_service_port_config_t *conf =
+    tor_malloc_zero(sizeof(rend_service_port_config_t) + pathlen);
+  memcpy(conf->unix_addr, socket_path, pathlen);
+  conf->is_unix_addr = 1;
+  return conf;
+}
+
 /** Parses a real-port to virtual-port mapping and returns a new
  * rend_service_port_config_t.
  *
- * The format is: VirtualPort (IP|RealPort|IP:RealPort)?
+ * The format is: VirtualPort (IP|RealPort|IP:RealPort|'socket':path)?
  *
  * IP defaults to 127.0.0.1; RealPort defaults to VirtualPort.
  */
@@ -302,6 +341,9 @@ parse_port_config(const char *string)
   tor_addr_t addr;
   const char *addrport;
   rend_service_port_config_t *result = NULL;
+  const char *socket_prefix = "socket:";
+  unsigned int is_unix_addr = 0;
+  char *socket_path = NULL;
 
   sl = smartlist_new();
   smartlist_split_string(sl, string, " ",
@@ -324,7 +366,26 @@ parse_port_config(const char *string)
     tor_addr_from_ipv4h(&addr, 0x7F000001u); /* 127.0.0.1 */
   } else {
     addrport = smartlist_get(sl,1);
-    if (strchr(addrport, ':') || strchr(addrport, '.')) {
+    /* If it starts with socket:, try to parse it as a socket path */
+    if (!strcmpstart(addrport, socket_prefix)) {
+      if (strlen(addrport + strlen(socket_prefix)) > 0) {
+#ifdef HAVE_SYS_UN_H
+        is_unix_addr = 1;
+        socket_path = tor_strdup(addrport + strlen(socket_prefix));
+#else
+        log_warn(LD_CONFIG,
+                 "Hidden service port configuration %s is for an AF_UNIX "
+                 "socket, but we have no support available on this platform",
+                 escaped(addrport));
+        goto err;
+#endif /* defined(HAVE_SYS_UN_H) */
+      } else {
+        log_warn(LD_CONFIG,
+                 "Empty socket path in hidden service port configuration.");
+        goto err;
+      }
+    } else if (strchr(addrport, ':') || strchr(addrport, '.')) {
+      /* else try it as an IP:port pair if it has a : or . in it */
       if (tor_addr_port_lookup(addrport, &addr, &p)<0) {
         log_warn(LD_CONFIG,"Unparseable address in hidden service port "
                  "configuration.");
@@ -343,13 +404,21 @@ parse_port_config(const char *string)
     }
   }
 
-  result = tor_malloc(sizeof(rend_service_port_config_t));
+  /* Allow room for unix_addr */
+  result = rend_service_port_config_new(socket_path);
   result->virtual_port = virtport;
-  result->real_port = realport;
-  tor_addr_copy(&result->real_addr, &addr);
+  result->is_unix_addr = is_unix_addr;
+  if (!is_unix_addr) {
+    result->real_port = realport;
+    tor_addr_copy(&result->real_addr, &addr);
+    result->unix_addr[0] = '\0';
+  }
+
  err:
   SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
   smartlist_free(sl);
+  if (socket_path) tor_free(socket_path);
+
   return result;
 }
 
@@ -3402,6 +3471,56 @@ rend_service_dump_stats(int severity)
   }
 }
 
+#ifdef HAVE_SYS_UN_H
+
+/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
+ * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
+ * else return -ENOSYS if AF_UNIX is not supported (see function in the
+ * #else statement below). */
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+  tor_assert(ports);
+  tor_assert(p);
+  tor_assert(p->is_unix_addr);
+
+  smartlist_add(ports, p);
+  return 0;
+}
+
+/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
+ * on success else return -ENOSYS if AF_UNIX is not supported (see function
+ * in the #else statement below). */
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+  tor_assert(conn);
+  tor_assert(p);
+  tor_assert(p->is_unix_addr);
+
+  conn->base_.socket_family = AF_UNIX;
+  tor_addr_make_unspec(&conn->base_.addr);
+  conn->base_.port = 1;
+  conn->base_.address = tor_strdup(p->unix_addr);
+  return 0;
+}
+
+#else /* defined(HAVE_SYS_UN_H) */
+
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+  return -ENOSYS;
+}
+
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+  return -ENOSYS;
+}
+
+#endif /* HAVE_SYS_UN_H */
+
 /** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for
  * 'circ', and look up the port and address based on conn-\>port.
  * Assign the actual conn-\>addr and conn-\>port. Return -2 on failure
@@ -3416,6 +3535,7 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
   char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
   smartlist_t *matching_ports;
   rend_service_port_config_t *chosen_port;
+  unsigned int warn_once = 0;
 
   tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
   tor_assert(circ->rend_data);
@@ -3433,19 +3553,45 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
   matching_ports = smartlist_new();
   SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p,
   {
-    if (conn->base_.port == p->virtual_port) {
+    if (conn->base_.port != p->virtual_port) {
+      continue;
+    }
+    if (!(p->is_unix_addr)) {
       smartlist_add(matching_ports, p);
+    } else {
+      if (add_unix_port(matching_ports, p)) {
+        if (!warn_once) {
+         /* Unix port not supported so warn only once. */
+          log_warn(LD_REND,
+              "Saw AF_UNIX virtual port mapping for port %d on service "
+              "%s, which is unsupported on this platform. Ignoring it.",
+              conn->base_.port, serviceid);
+        }
+        warn_once++;
+      }
     }
   });
   chosen_port = smartlist_choose(matching_ports);
   smartlist_free(matching_ports);
   if (chosen_port) {
-    tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr);
-    conn->base_.port = chosen_port->real_port;
+    if (!(chosen_port->is_unix_addr)) {
+      /* Get a non-AF_UNIX connection ready for connection_exit_connect() */
+      tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr);
+      conn->base_.port = chosen_port->real_port;
+    } else {
+      if (set_unix_port(conn, chosen_port)) {
+        /* Simply impossible to end up here else we were able to add a Unix
+         * port without AF_UNIX support... ? */
+        tor_assert(0);
+      }
+    }
     return 0;
   }
-  log_info(LD_REND, "No virtual port mapping exists for port %d on service %s",
-           conn->base_.port,serviceid);
+
+  log_info(LD_REND,
+           "No virtual port mapping exists for port %d on service %s",
+           conn->base_.port, serviceid);
+
   if (service->allow_unknown_ports)
     return -1;
   else