diff --git a/ChangeLog b/ChangeLog
index 83b6613d7931ed60889ed702db0d18f758492c70..dcf455c250d2cb7e056ffef8ae721e8fcdc5765e 100644
--- a/ChangeLog
+++ b/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
diff --git a/doc/TODO b/doc/TODO
index 0bcda243bb15dbe2fd99a29c89b05e5de1753f16..82fa9fdb9f55c473b24777e20d30c2ec0b110982 100644
--- a/doc/TODO
+++ b/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
diff --git a/src/or/Makefile.am b/src/or/Makefile.am
index 744ea35b6764978ff322da73daeb6ba2f1ccc4c4..2b35ef75c7d858e7e4ee99871f9f4845110606c5 100644
--- a/src/or/Makefile.am
+++ b/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 \
diff --git a/src/or/config.c b/src/or/config.c
index 05e32d71cf22a166dc16ad33601b497dfd72cceb..8cfd5087fc68fd34f1db323dd0cb5d25a125b65d 100644
--- a/src/or/config.c
+++ b/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.");
 
diff --git a/src/or/connection.c b/src/or/connection.c
index 5ca27447781d675a79e453b912504d6879a2c180..daafc7d7c09b16d91514ceab17c6bf91de59eea4 100644
--- a/src/or/connection.c
+++ b/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:
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index a910013d254c5853ec4274dbef5994172f75ec8c..e2b4f1b021abad294bd060ab44cf2f0a2dfb8ef7 100644
--- a/src/or/connection_edge.c
+++ b/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");
diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c
new file mode 100644
index 0000000000000000000000000000000000000000..489dda73e81f07e059480664c1a3764a1caf9927
--- /dev/null
+++ b/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;
+}
+
diff --git a/src/or/or.h b/src/or/or.h
index 51c75eddebee1fc9ca397817d716f955306d710f..a61e5efc78e576c0a08ffcaded88615412145c34 100644
--- a/src/or/or.h
+++ b/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);