Opened 12 years ago

Last modified 20 months ago

#393 assigned defect (None)

hidden services resolve hosts only once

Reported by: weasel Owned by: rransom
Priority: Low Milestone: Tor: unspecified
Component: Core Tor/Tor Version:
Severity: Normal Keywords: dns, tor-hs
Cc: weasel, nickm, Sebastian, saint, teor Actual Points:
Parent ID: Points: 5
Reviewer: Sponsor:

Description (last modified by nickm)

I have a hidden service, configured with
HiddenServicePort 6666

Now is a rotation that changes quite frequently and for that
reason the TTL of the A records it returns is only 60.

The problem is that Tor resolves the hostname only once, when it configures
the hidden service, not everytime a user establishes a new connection. This
causes problems since by the time a client request comes the information Tor
has often is long obsolete.

Please resolve it on connects, and don't cache it in a broken way (caching it
for TTL is fine).


[Automatically added by flyspray2trac: Operating System: All]

Child Tickets

Change History (22)

comment:1 Changed 12 years ago by mwenge

Dirty patch below resolves address every time it is requested. This could serve until a cleaner solution comes along.
This may even spoil the anonymity of the hidden service for all I know.

Index: src/or/rendservice.c
--- src/or/rendservice.c	(revision 9613)
+++ src/or/rendservice.c	(working copy)
@@ -20,7 +20,7 @@
 typedef struct rend_service_port_config_t {
   uint16_t virtual_port;
   uint16_t real_port;
-  uint32_t real_addr;
+  char *real_addr;
 } rend_service_port_config_t;
 /** Try to maintain this many intro points per service if possible. */
@@ -113,7 +113,6 @@
   int i;
   rend_service_port_config_t *p;
-  struct in_addr addr;
   if (!service->intro_prefer_nodes)
     service->intro_prefer_nodes = tor_strdup("");
@@ -129,16 +128,15 @@
     log_debug(LD_REND,"Configuring service with directory "%s"",
     for (i = 0; i < smartlist_len(service->ports); ++i) {
-      char addrbuf[INET_NTOA_BUF_LEN];
       p = smartlist_get(service->ports, i);
-      addr.s_addr = htonl(p->real_addr);
-      tor_inet_ntoa(&addr, addrbuf, sizeof(addrbuf));
-      log_debug(LD_REND,"Service maps port %d to %s:%d",
-                p->virtual_port, addrbuf, p->real_port);
+      log_debug(LD_REND,"Service maps port %d to %s",
+                p->virtual_port, p->real_addr);
 /** Parses a real-port to virtual-port mapping and returns a new
  * rend_service_port_config_t.
@@ -175,7 +173,7 @@
   if (smartlist_len(sl) == 1) {
     /* No addr:port part; use default. */
     realport = virtport;
-    addr = 0x7F000001u; /* */
+    addrport = tor_strdup(""); /* */
   } else {
     addrport = smartlist_get(sl,1);
     if (strchr(addrport, ':') || strchr(addrport, '.')) {
@@ -190,14 +188,14 @@
       realport = atoi(addrport);
       if (realport < 1 || realport > 65535)
         goto err;
-      addr = 0x7F000001u; /* Default to */
+      addrport = tor_strdup(""); /* */
   result = tor_malloc(sizeof(rend_service_port_config_t));
   result->virtual_port = virtport;
   result->real_port = realport;
-  result->real_addr = addr;
+  result->real_addr = tor_strdup(addrport);
   SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
@@ -1157,6 +1155,8 @@
   int i;
   rend_service_port_config_t *p;
   char serviceid[REND_SERVICE_ID_LEN+1];
+  uint32_t addr;
+  uint16_t port;
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
   log_debug(LD_REND,"beginning to hunt for addr/port");
@@ -1172,7 +1172,12 @@
   for (i = 0; i < smartlist_len(service->ports); ++i) {
     p = smartlist_get(service->ports, i);
     if (conn->_base.port == p->virtual_port) {
-      conn->_base.addr = p->real_addr;
+      addr = 0x7F000001u; /* Default to */
+      if (parse_addr_port(LOG_WARN, p->real_addr, NULL, &addr, &port)<0) {
+        return -1;
+      }
+      conn->_base.addr = addr;
       conn->_base.port = p->real_port;
       return 0;
Last edited 4 years ago by arma (previous) (diff)

comment:2 Changed 12 years ago by nickm

There's a performance issue with the patch, and a couple of anonymity issues.

The performance issue is that parse_addr_port calls tor_lookup_hostname, which is blocking. If the nameserver is
slow, then the entire Tor process (not just the hidden service connection in question) blocks while the lookup is

There's an anonymity issue here in that a clever attacker could pretty easily use it to tell who else is connecting to
the same hidden service, by opening connections to cause gaps in their traffic. (This exists to a lesser extent with
bandwidth interference, but I believe the attack here is even easier.)

Another anonymity issue is that the nameserver learns every time somebody connects to the hidden service. Under some
circumstances, this could let the nameserver ID the hidden service.

I think I'd prefer an approach where instead of doing the resolve _late_, the hidden service could be configured
with a list of addresses. It could get these via DNS, or via the config file.

comment:3 Changed 12 years ago by weasel

Tor getting them all from DNS and then updating them every TTL seconds would be
fine with me. Since most records out there have long TTLs that would be just fine
for those, and since my hostname has short TTLs that is good for me too. It's
probably way overkill in my case, because I have less than 30 connect per day,
but it's something I could live with.

(oh, also the patch appears to leak memory)

comment:4 Changed 12 years ago by mwenge

You're right about the leak, sorry. Wouldn't observing the TTLs still endanger the service's anonymity though? Also,
if tor takes the 4 addresses currently reported as A records, wouldn't it take a while to give up on one before
trying the next? Hidden Service clients do take a good while before giving up on an address.

comment:5 Changed 12 years ago by nickm

Sorry, the idea was for the hidden service to round-robin between its addresses. The client never knows how
many addresses the hidden server is using internally.

To implement this, you'd probably want to start with configuration support sans DNS: change real_addr and real_port
in struct rend_service_port_config_t so that they are instead a list of addr:port pairs; make it possible to set
these from the config file; and have rend_service_set_connection_addr_port pick one at random.

Next you'd want to expand tor_lookup_hostname so it can return multiple values if the underlying resolver supports it
and if the caller wants it.

Next you'd want to make this information propagate to parse_port_config somehow.

comment:6 Changed 12 years ago by arma

I don't mind the anonymity worry from an attacking nameserver. If
it wants to learn when somebody is hitting the hidden service, it
should give you an IP address it controls, and watch for the hit.

Robert has a good point that if we have a round-robin set and half
are down, then hidden services are going to suck even more; we
would want to remember which seem down and which don't, etc, and
that still doesn't entirely solve the problem.

This will get complex quickly. We should delay it til 0.2.0.x.

comment:7 Changed 12 years ago by weasel

You don't have to solve all the things in one day. Tracking upness of hosts in a rotation
is really not required at this point. Just picking a random IP out of the many in DNS at
the point of connect is all that's needed to not suck.

comment:8 Changed 12 years ago by nickm

It looks like multiple targets is easy, but mucking with the DNS code will be more involved than I'm
comfortable doing at this stage of the 0.1.2.x series (dns and config are "subtle and swift to anger,"
and I have a poor track record of messing with them successfully on my first attempt). Peter,
could you let me know if this patch makes stuff better for you? It lets you give multiple
HiddenServicePort directives with the same virtual port, and chooses randomly among them when you get
a connection.

=== src/or/rendservice.c
--- src/or/rendservice.c        (revision 12009)
+++ src/or/rendservice.c        (local)
@@ -1154,9 +1154,9 @@
                                       origin_circuit_t *circ)
   rend_service_t *service;
-  int i;
-  rend_service_port_config_t *p;
   char serviceid[REND_SERVICE_ID_LEN+1];
+  smartlist_t *matching_ports;
+  rend_service_port_config_t *chosen_port;
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
   log_debug(LD_REND,"beginning to hunt for addr/port");
@@ -1169,13 +1169,19 @@
              serviceid, circ->_base.n_circ_id);
     return -1;
-  for (i = 0; i < smartlist_len(service->ports); ++i) {
-    p = smartlist_get(service->ports, i);
+  matching_ports = smartlist_create();
+  SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p,
+  {
     if (conn->_base.port == p->virtual_port) {
-      conn->_base.addr = p->real_addr;
-      conn->_base.port = p->real_port;
-      return 0;
+      smartlist_add(matching_ports, p);
+  });
+  chosen_port = smartlist_choose(matching_ports);
+  smartlist_free(matching_ports);
+  if (chosen_port) {
+    conn->_base.addr = chosen_port->real_addr;
+    conn->_base.port = chosen_port->real_port;
+    return 0;
   log_info(LD_REND, "No virtual port mapping exists for port %d on service %s",
Last edited 4 years ago by arma (previous) (diff)

comment:9 Changed 12 years ago by nickm

Peter says that the above patch isn't good enough to meet his needs, so I'll instead mark this whole thing
as "post-0.1.2.x" and try to get to it soon in the 0.2.0.x series.

comment:10 Changed 12 years ago by nickm

I merged in the partial fix above in as r10398; it will appear in

We still need to actually hook this into DNS.

comment:11 Changed 11 years ago by nickm

see also bug 449.

comment:12 Changed 10 years ago by nickm

The "right" solution is probably some kind of hack in connection_exit_begin_conn() in the
circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED block. Right now, rend_service_set_connection_addr_port() just
replaces the addr and port fields of the connection, and returns -1 on error. It could become like dns_resolve,
and return a tristate of "I set the addr and port", "There's an error", and "I'm resolving the addr and port." The
rest of that block could move into a different function.

Doing resolves at connect-time rather than at runtime should probably be configurable, though.

Also, we should think about attacks where an adversary probes our DNS cache to see whether we're the ones who keep

comment:13 Changed 8 years ago by nickm

Description: modified (diff)
Keywords: dns hidden service added
Milestone: post 0.2.1.xTor: unspecified
Status: assignedaccepted

comment:14 Changed 8 years ago by arma

Component: Tor ClientTor hidden services

comment:15 Changed 8 years ago by rransom

Owner: changed from nickm to rransom
Status: acceptedassigned

comment:16 Changed 6 years ago by nickm

Keywords: tor-hs added

comment:17 Changed 6 years ago by nickm

Component: Tor Hidden ServicesTor

comment:18 Changed 4 years ago by saint

Cc: saint added

comment:19 Changed 4 years ago by teor

Cc: teor added

comment:20 Changed 20 months ago by dgoulet

Keywords: hidden service removed
Severity: Normal

comment:21 Changed 20 months ago by nickm

Points: 5

comment:22 Changed 20 months ago by nickm

Updating. To do the right solution here, we'd need to make it so that we use our existing evdns backend for asynchronous hostname lookups whenever we're also running about a hidden service. Then we could launch a lookup for the hidden service whenever we connected there.

On the other hand, we might want to make this feature RSOS-only, since doing hostname lookups in response to user requests for a hidden service seems like a good way to risk leaking.

Note: See TracTickets for help on using tickets.