Browse Source

r12942@catbus: nickm | 2007-05-24 16:31:22 -0400
Well, that was easier than I thought it would be. Tor is now a DNS proxy as well as a socks proxy. Probably some bugs remain, but since it A) has managed to resolve one address for me successfully, and B) will not affect anybody who leaves DNSPort unset, it feel like a good time to commit.


svn:r10317

Nick Mathewson 17 years ago
parent
commit
703bf19620
8 changed files with 266 additions and 13 deletions
  1. 6 0
      ChangeLog
  2. 7 1
      doc/TODO
  3. 2 2
      src/or/Makefile.am
  4. 8 0
      src/or/config.c
  5. 36 7
      src/or/connection.c
  6. 6 2
      src/or/connection_edge.c
  7. 177 0
      src/or/dnsserv.c
  8. 24 1
      src/or/or.h

+ 6 - 0
ChangeLog

@@ -22,6 +22,12 @@ Changes in version 0.2.0.1-alpha - 2007-??-??
       logging information that would be very useful to an attacker.
     - Start work implementing proposal 103: Add a standalone tool to
       generate key certificates.
+    - Initial implementation of a client-side DNS proxy feature to replace
+      the need for dns-proxy-tor:  Just set "DNSPort 9999", and Tor will
+      now listen for DNS requests on port 9999, use the Tor network to
+      resolve them anonymously, and send the reply back like a regular DNS
+      server.  The code is still buggy, undocumented, and only implements
+      a subset of DNS.
 
   o Security fixes:
     - Directory authorities now call routers stable if they have an

+ 7 - 1
doc/TODO

@@ -195,7 +195,13 @@ Things we'd like to do in 0.2.0.x:
   - Features:
     - Traffic priorities (by Jun 1)
       - Ability to prioritize own traffic over relayed traffic.
-    - Implement a DNS proxy
+    o Implement a DNS proxy
+      o Make a listener type.
+      o Hook into connection_edge logic.
+      o Hook into evdns_server_* logic
+      - Actually send back a useful answer.
+      - Make i
+      - Document.
     - Add a way to request DNS resolves from the controller.
     - A better UI for authority ops.
       - Follow weasel's proposal, crossed with mixminion dir config format

+ 2 - 2
src/or/Makefile.am

@@ -7,7 +7,7 @@ bin_PROGRAMS = tor
 tor_SOURCES = buffers.c circuitbuild.c circuitlist.c \
 	circuituse.c command.c config.c \
 	connection.c connection_edge.c connection_or.c control.c \
-	cpuworker.c directory.c dirserv.c dns.c hibernate.c main.c \
+	cpuworker.c directory.c dirserv.c dns.c dnsserv.c hibernate.c main.c \
 	onion.c policies.c relay.c rendcommon.c rendclient.c rendmid.c \
 	rendservice.c rephist.c router.c routerlist.c routerparse.c \
 	eventdns.c \
@@ -23,7 +23,7 @@ tor_LDADD = ../common/libor.a ../common/libor-crypto.a \
 test_SOURCES = buffers.c circuitbuild.c circuitlist.c \
 	circuituse.c command.c config.c \
 	connection.c connection_edge.c connection_or.c control.c \
-	cpuworker.c directory.c dirserv.c dns.c hibernate.c main.c \
+	cpuworker.c directory.c dirserv.c dns.c dnsserv.c hibernate.c main.c \
 	onion.c policies.c relay.c rendcommon.c rendclient.c rendmid.c \
 	rendservice.c rephist.c router.c routerlist.c routerparse.c \
 	eventdns.c \

+ 8 - 0
src/or/config.c

@@ -155,6 +155,8 @@ static config_var_t _option_vars[] = {
   VAR("DirPort",             UINT,     DirPort,              "0"),
   OBSOLETE("DirPostPeriod"),
   VAR("DirServer",           LINELIST, DirServers,           NULL),
+  VAR("DNSPort",             UINT,     DNSPort,              "0"),
+  VAR("DNSListenAddress",    LINELIST, DNSListenAddress,     NULL),
   VAR("DownloadExtraInfo",   BOOL,     DownloadExtraInfo,    "0"),
   VAR("EnforceDistinctSubnets", BOOL,  EnforceDistinctSubnets,"1"),
   VAR("EntryNodes",          STRING,   EntryNodes,           NULL),
@@ -2415,6 +2417,9 @@ options_validate(or_options_t *old_options, or_options_t *options,
   if (options->DirPort == 0 && options->DirListenAddress != NULL)
     REJECT("DirPort must be defined if DirListenAddress is defined.");
 
+  if (options->DNSPort == 0 && options->DNSListenAddress != NULL)
+    REJECT("DirPort must be defined if DirListenAddress is defined.");
+
   if (options->ControlPort == 0 && options->ControlListenAddress != NULL)
     REJECT("ControlPort must be defined if ControlListenAddress is defined.");
 
@@ -2523,6 +2528,9 @@ options_validate(or_options_t *old_options, or_options_t *options,
   if (options->SocksPort < 0 || options->SocksPort > 65535)
     REJECT("SocksPort option out of bounds.");
 
+  if (options->DNSPort < 0 || options->DNSPort > 65535)
+    REJECT("DNSPort option out of bounds.");
+
   if (options->TransPort < 0 || options->TransPort > 65535)
     REJECT("TransPort option out of bounds.");
 

+ 36 - 7
src/or/connection.c

@@ -48,6 +48,7 @@ conn_type_to_string(int type)
     case CONN_TYPE_AP_TRANS_LISTENER:
       return "Transparent pf/netfilter listener";
     case CONN_TYPE_AP_NATD_LISTENER: return "Transparent natd listener";
+    case CONN_TYPE_AP_DNS_LISTENER: return "DNS listener";
     case CONN_TYPE_AP: return "Socks";
     case CONN_TYPE_DIR_LISTENER: return "Directory listener";
     case CONN_TYPE_DIR: return "Directory";
@@ -74,6 +75,7 @@ conn_state_to_string(int type, int state)
     case CONN_TYPE_AP_LISTENER:
     case CONN_TYPE_AP_TRANS_LISTENER:
     case CONN_TYPE_AP_NATD_LISTENER:
+    case CONN_TYPE_AP_DNS_LISTENER:
     case CONN_TYPE_DIR_LISTENER:
     case CONN_TYPE_CONTROL_LISTENER:
       if (state == LISTENER_STATE_READY)
@@ -240,6 +242,9 @@ connection_unregister_events(connection_t *conn)
       log_warn(LD_BUG, "Error removing write event for %d", conn->s);
     tor_free(conn->write_event);
   }
+  if (conn->dns_server_port) {
+    dnsserv_close_listener(conn);
+  }
 }
 
 /** Deallocate memory used by <b>conn</b>. Deallocate its buffers if
@@ -491,6 +496,12 @@ connection_about_to_close_connection(connection_t *conn)
                  " set end_reason.",
                  conn->marked_for_close_file, conn->marked_for_close);
       }
+      if (edge_conn->dns_server_request) {
+        log_warn(LD_BUG,"Closing stream (marked at %s:%d) without having"
+                 " replied to DNS request.",
+                 conn->marked_for_close_file, conn->marked_for_close);
+        dnsserv_reject_request(edge_conn);
+      }
       control_event_stream_status(edge_conn, STREAM_EVENT_CLOSED,
                                   edge_conn->end_reason);
       circ = circuit_get_by_edge_conn(edge_conn);
@@ -632,6 +643,7 @@ connection_create_listener(const char *listenaddress, uint16_t listenport,
 #ifndef MS_WINDOWS
   int one=1;
 #endif
+  int is_tcp = (type != CONN_TYPE_AP_DNS_LISTENER);
 
   memset(&listenaddr,0,sizeof(struct sockaddr_in));
   if (parse_addr_port(LOG_WARN, listenaddress, &address, &addr, &usePort)<0) {
@@ -658,7 +670,9 @@ connection_create_listener(const char *listenaddress, uint16_t listenport,
     return NULL;
   }
 
-  s = tor_open_socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
+  s = tor_open_socket(PF_INET,
+                      is_tcp ? SOCK_STREAM : SOCK_DGRAM,
+                      is_tcp ? IPPROTO_TCP: IPPROTO_UDP);
   if (s < 0) {
     log_warn(LD_NET,"Socket creation failed.");
     goto err;
@@ -683,11 +697,13 @@ connection_create_listener(const char *listenaddress, uint16_t listenport,
     goto err;
   }
 
-  if (listen(s,SOMAXCONN) < 0) {
-    log_warn(LD_NET, "Could not listen on %s:%u: %s", address, usePort,
-             tor_socket_strerror(tor_socket_errno(s)));
-    tor_close_socket(s);
-    goto err;
+  if (is_tcp) {
+    if (listen(s,SOMAXCONN) < 0) {
+      log_warn(LD_NET, "Could not listen on %s:%u: %s", address, usePort,
+               tor_socket_strerror(tor_socket_errno(s)));
+      tor_close_socket(s);
+      goto err;
+    }
   }
 
   set_socket_nonblocking(s);
@@ -708,7 +724,12 @@ connection_create_listener(const char *listenaddress, uint16_t listenport,
             conn_type_to_string(type), usePort);
 
   conn->state = LISTENER_STATE_READY;
-  connection_start_reading(conn);
+  if (is_tcp) {
+    connection_start_reading(conn);
+  } else {
+    tor_assert(type == CONN_TYPE_AP_DNS_LISTENER);
+    dnsserv_configure_listener(conn);
+  }
 
   return conn;
 
@@ -1125,6 +1146,10 @@ retry_all_listeners(int force, smartlist_t *replaced_conns,
                       options->NatdPort, "127.0.0.1", force,
                       replaced_conns, new_conns, 0)<0)
     return -1;
+  if (retry_listeners(CONN_TYPE_AP_DNS_LISTENER, options->DNSListenAddress,
+                      options->DNSPort, "127.0.0.1", force,
+                      replaced_conns, new_conns, 0)<0)
+    return -1;
   if (retry_listeners(CONN_TYPE_CONTROL_LISTENER,
                       options->ControlListenAddress,
                       options->ControlPort, "127.0.0.1", force,
@@ -1535,6 +1560,10 @@ connection_handle_read(connection_t *conn)
       return connection_handle_listener_read(conn, CONN_TYPE_DIR);
     case CONN_TYPE_CONTROL_LISTENER:
       return connection_handle_listener_read(conn, CONN_TYPE_CONTROL);
+    case CONN_TYPE_AP_DNS_LISTENER:
+      /* This should never happen; eventdns.c handles the reads here. */
+      tor_fragile_assert();
+      return 0;
   }
 
 loop_again:

+ 6 - 2
src/or/connection_edge.c

@@ -30,7 +30,6 @@ static smartlist_t *redirect_exit_list = NULL;
 static int connection_ap_handshake_process_socks(edge_connection_t *conn);
 static int connection_ap_process_natd(edge_connection_t *conn);
 static int connection_exit_connect_dir(edge_connection_t *exitconn);
-static int hostname_is_noconnect_address(const char *address);
 
 /** An AP stream has failed/finished. If it hasn't already sent back
  * a socks reply, send one now (based on endreason). Also set
@@ -1920,6 +1919,11 @@ connection_ap_handshake_socks_resolved(edge_connection_t *conn,
   char buf[384];
   size_t replylen;
 
+  if (conn->dns_server_request) {
+    dnsserv_resolved(conn, answer_type, answer_len, answer, ttl);
+    return;
+  }
+
   if (ttl >= 0) {
     if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4) {
       uint32_t a = ntohl(get_uint32(answer));
@@ -2551,7 +2555,7 @@ failed:
 
 /** Check if the address is of the form "y.noconnect"
  */
-static int
+int
 hostname_is_noconnect_address(const char *address)
 {
   return ! strcasecmpend(address, ".noconnect");

+ 177 - 0
src/or/dnsserv.c

@@ -0,0 +1,177 @@
+/* Copyright 2007 Roger Dingledine, Nick Mathewson */
+/* See LICENSE for licensing information */
+/* $Id$ */
+const char dnsserv_c_id[] =
+  "$Id$";
+
+/**
+ * \file dnservs.c
+ * \brief Implements client-side DNS proxy server code.
+ **/
+
+#include "or.h"
+#include "eventdns.h"
+
+static void
+evdns_server_callback(struct evdns_server_request *req, void *_data)
+{
+  edge_connection_t *conn;
+  int i = 0;
+  struct evdns_server_question *q = NULL;
+  struct sockaddr_storage addr;
+  struct sockaddr *sa;
+  int addrlen;
+  uint32_t ipaddr;
+  int err = DNS_ERR_NONE;
+
+  tor_assert(req);
+  tor_assert(_data == NULL);
+  log_info(LD_APP, "Got a new DNS request!");
+
+  if ((addrlen = evdns_server_request_get_requesting_addr(req,
+                                (struct sockaddr*)&addr, sizeof(addr))) < 0) {
+    log_warn(LD_APP, "Couldn't get requesting address.");
+    evdns_server_request_respond(req, DNS_ERR_SERVERFAILED);
+    return;
+  }
+  sa = (struct sockaddr*) &addr;
+  if (sa->sa_family != AF_INET) {
+    /* XXXX020 Handle IPV6 */
+    log_warn(LD_APP, "Requesting address wasn't ipv4.");
+    evdns_server_request_respond(req, DNS_ERR_SERVERFAILED);
+    return;
+  } else {
+    struct sockaddr_in *sin = (struct sockaddr_in*)&addr;
+    ipaddr = ntohl(sin->sin_addr.s_addr);
+  }
+  if (!socks_policy_permits_address(ipaddr)) {
+    log_warn(LD_APP, "Rejecting DNS request from disallowed IP.");
+    evdns_server_request_respond(req, DNS_ERR_REFUSED);
+    return;
+  }
+  if (req->nquestions == 0) {
+    log_info(LD_APP, "No questions in DNS request; sending back nil reply.");
+    evdns_server_request_respond(req, 0);
+    return;
+  }
+  if (req->nquestions > 1) {
+    log_info(LD_APP, "Got a DNS request with more than one question; I only "
+             "handle one question at a time for now.  Skipping the extras.");
+  }
+  for (i = 0; i < req->nquestions; ++i) {
+    if (req->questions[i]->class != EVDNS_CLASS_INET)
+      continue;
+    switch (req->questions[i]->type) {
+      case EVDNS_TYPE_A:
+      case EVDNS_TYPE_PTR:
+        q = req->questions[i];
+      default:
+        break;
+      }
+  }
+  if (!q) {
+    log_info(LD_APP, "None of the questions we got were ones we're willing "
+             "to support. Sending error.");
+    evdns_server_request_respond(req, DNS_ERR_NOTIMPL);
+    return;
+  }
+  if (q->type == EVDNS_TYPE_A) {
+    if (hostname_is_noconnect_address(q->name)) {
+      err = DNS_ERR_REFUSED;
+    }
+  } else {
+    tor_assert(q->type == EVDNS_TYPE_PTR);
+  }
+  if (err == DNS_ERR_NONE && strlen(q->name) > MAX_SOCKS_ADDR_LEN-1)
+    err = DNS_ERR_FORMAT;
+
+  if (err != DNS_ERR_NONE) {
+    evdns_server_request_respond(req, err);
+    return;
+  }
+
+  /* XXXX020 Handle .onion and .exit. */
+  /* XXXX020 Send a stream event to the controller. */
+
+  conn = TO_EDGE_CONN(connection_new(CONN_TYPE_AP));
+  if (q->type == EVDNS_TYPE_A)
+    conn->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  else
+    conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR;
+
+  strlcpy(conn->socks_request->address, q->name,
+          sizeof(conn->socks_request->address));
+
+  conn->dns_server_request = req;
+
+  /* XXXX need to set state ?? */
+
+  log_info(LD_APP, "Passing request for %s to rewrite_and_attach.", q->name);
+  connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL);
+  /* Now the connection is marked if it was bad. */
+
+  log_info(LD_APP, "Passed request for %s to rewrite_and_attach.", q->name);
+}
+
+void
+dnsserv_reject_request(edge_connection_t *conn)
+{
+  evdns_server_request_respond(conn->dns_server_request, DNS_ERR_SERVERFAILED);
+  conn->dns_server_request = NULL;
+}
+
+void
+dnsserv_resolved(edge_connection_t *conn,
+                 int answer_type,
+                 size_t answer_len,
+                 const char *answer,
+                 int ttl)
+{
+  struct evdns_server_request *req = conn->dns_server_request;
+  int err = DNS_ERR_NONE;
+  if (!req)
+    return;
+
+  /* XXXX Re-do. */
+  if (ttl < 60)
+    ttl = 60;
+
+  if (answer_type == RESOLVED_TYPE_IPV6) {
+    log_info(LD_APP, "Got an IPv6 answer; that's not implemented.");
+    err = DNS_ERR_NOTIMPL;
+  } else if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4 &&
+             conn->socks_request->command == SOCKS_COMMAND_RESOLVE) {
+    evdns_server_request_add_a_reply(req,
+                                     conn->socks_request->address,
+                                     1, (char*)answer, ttl);
+  } else if (answer_type == RESOLVED_TYPE_HOSTNAME &&
+             conn->socks_request->command == SOCKS_COMMAND_RESOLVE_PTR) {
+    char *ans = tor_strndup(answer, answer_len);
+    evdns_server_request_add_ptr_reply(req, NULL,
+                                       conn->socks_request->address,
+                                       (char*)answer, ttl);
+    tor_free(ans);
+  } else {
+    err = DNS_ERR_SERVERFAILED;
+  }
+
+  evdns_server_request_respond(req, err);
+  conn->dns_server_request = NULL;
+}
+
+void
+dnsserv_configure_listener(connection_t *conn)
+{
+  tor_assert(conn);
+  tor_assert(conn->s);
+
+  evdns_add_server_port(conn->s, 0, evdns_server_callback, NULL);
+}
+
+void
+dnsserv_close_listener(connection_t *conn)
+{
+  evdns_close_server_port(conn->dns_server_port);
+  conn->dns_server_port = NULL;
+}
+

+ 24 - 1
src/or/or.h

@@ -237,7 +237,9 @@ typedef enum {
 /** Type for sockets listening for transparent connections redirected by
  * natd. */
 #define CONN_TYPE_AP_NATD_LISTENER 14
-#define _CONN_TYPE_MAX 14
+/** Type for sockets listening for DNS requests. */
+#define CONN_TYPE_AP_DNS_LISTENER 15
+#define _CONN_TYPE_MAX 15
 
 #define CONN_IS_EDGE(x) \
   ((x)->type == CONN_TYPE_EXIT || (x)->type == CONN_TYPE_AP)
@@ -813,6 +815,9 @@ typedef struct connection_t {
    * read_event should be made active with libevent. */
   unsigned int active_on_link:1;
 
+  /* XXXX020 move this into a subtype!!! */
+  struct evdns_server_port *dns_server_port;
+
 } connection_t;
 
 /** Subtype of connection_t for an "OR connection" -- that is, one that speaks
@@ -905,6 +910,9 @@ typedef struct edge_connection_t {
    * already retried several times. */
   uint8_t num_socks_retries;
 
+  /** DOCDOC */
+  struct evdns_server_request *dns_server_request;
+
 } edge_connection_t;
 
 /** Subtype of connection_t for an "directory connection" -- that is, an HTTP
@@ -1728,6 +1736,8 @@ typedef struct {
   config_line_t *TransListenAddress;
   /** Addresses to bind for listening for transparent natd connections */
   config_line_t *NatdListenAddress;
+  /** Addresses to bind for listening for SOCKS connections. */
+  config_line_t *DNSListenAddress;
   /** Addresses to bind for listening for OR connections. */
   config_line_t *ORListenAddress;
   /** Addresses to bind for listening for directory connections. */
@@ -1752,6 +1762,7 @@ typedef struct {
   int NatdPort; /**< Port to listen on for transparent natd connections. */
   int ControlPort; /**< Port to listen on for control connections. */
   int DirPort; /**< Port to listen on for directory connections. */
+  int DNSPort; /**< Port to listen on for DNS requests. */
   int AssumeReachable; /**< Whether to publish our descriptor regardless. */
   int AuthoritativeDir; /**< Boolean: is this an authoritative directory? */
   int V1AuthoritativeDir; /**< Boolean: is this an authoritative directory
@@ -2403,6 +2414,7 @@ void addressmap_get_mappings(smartlist_t *sl, time_t min_expires,
 int connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
                                                origin_circuit_t *circ,
                                                crypt_path_t *cpath);
+int hostname_is_noconnect_address(const char *address);
 
 void set_exit_redirects(smartlist_t *lst);
 typedef enum hostname_type_t {
@@ -2655,6 +2667,17 @@ void dns_launch_correctness_checks(void);
 int dns_seems_to_be_broken(void);
 void dns_reset_correctness_checks(void);
 
+/********************************* dnsserv.c ************************/
+
+void dnsserv_configure_listener(connection_t *conn);
+void dnsserv_close_listener(connection_t *conn);
+void dnsserv_resolved(edge_connection_t *conn,
+                      int answer_type,
+                      size_t answer_len,
+                      const char *answer,
+                      int ttl);
+void dnsserv_reject_request(edge_connection_t *conn);
+
 /********************************* hibernate.c **********************/
 
 int accounting_parse_options(or_options_t *options, int validate_only);