wiki:doc/TorDNSExitList

Version 5 (modified by trac, 9 years ago) (diff)

--

Introduction

It is useful for a variety of reasons to determine if a connection is coming from a Tor node. Early attempts to determine if a given IP address was a Tor exit used the directory to match IP addresses and exit policies. This approach had a number of drawbacks, including false negatives when a Tor router exits traffic from a different IP address than its OR port listens on. The Tor DNS-based Exit List was designed to overcome these problems and provide a simple interface for answering the question: is this a Tor exit?

Implementation

An implementation of the Tor DNS Exit List has been completed at exitlist.torproject.org. DNS queries are answered via this host in the manner described in the design document. The exit nodes are tested regularly to avoid the false negatives when inspecting the directory entries alone.

Examples

The following examples describe how to utilize this service in various ways. Please add to this list if you have implemented hooks for a language or framework not provided below.

PHP Pear Net_DNS

You will need the Pear Net_DNS module for this to work properly:

include("Net/DNS.php");

// torel_check ($ip, $port, $destip) queries the Tor DNS Exit List server.
//   The result of the query is one of the following:
//   -1 : DNS lookup failed to get a response, or other error occurred.
//    0 : $ip does not appear to be a Tor exit.
//    1 : $ip is a known Tor exit for the provided destination IP / port.

function revaddr ($ip) {
  list($a, $b, $c, $d) = split("[.]", $ip);
  return("${d}.${c}.${b}.${a}");
}

function torel_qh ($ip, $port, $destip) {
  $rsrcip = revaddr ($ip);
  $rdstip = revaddr ($destip);
  return("${rsrcip}.${port}.${rdstip}.ip-port.exitlist.torproject.org");
}

function torel_check ($ip, $port, $destip) {
  $ndr = new Net_DNS_Resolver(); 
  $qh = torel_qh($ip, $port, $destip);

  // uncomment these two lines to query the server directly...
  //$ns = "exitlist-ns.torproject.org";
  //$ndr->nameservers( array($ns) );

  // tune DNS params accordingly.  this is just my preference.
  $ndr->retrans = 2;
  $ndr->retry = 3;
  $ndr->usevc = 0;

  // perform DNS query
  if (! $pkt = $ndr->search($qh)) {
    if (strcmp($ndr->errorstring, "NXDOMAIN") == 0) {
      // response but no answer.  does not appear to be Tor exit.
      return (0);
    }
    // search failed: no response or other problem...
    return(-1);
  }
  if (! isset($pkt->answer[0])) {
    // response but no answer section.  does not appear to be Tor exit.
    // (this should only happen when authority sections are provided without answer)
    return(0);
  }
  // is Tor exit
  return(1);
}

// get client request parameters from Apache or equiv server:
$ip = $myip = $myport = 0;
if (isset ($_SERVER["REMOTE_ADDR"])) { $ip = $_SERVER["REMOTE_ADDR"]; }
if (isset ($_SERVER["SERVER_ADDR"])) { $myip = $_SERVER["SERVER_ADDR"]; }
if (isset ($_SERVER["SERVER_PORT"])) { $myport = $_SERVER["SERVER_PORT"]; }

$istor = torel_check($ip, $myport, $myip);

// use $istor as needed for altering page behavior:
if ($istor < 0) {
  // DNS query failed to get an answer
}
elseif ($istor) {
  // Endpoint is a known Tor exit
}
else {
  // Endpoint does not appear to be a Tor exit
}

An example implementation of this interface is available at peertech.org/torel/.

Perl Net::DNS

You will need the Net::DNS module and its dependencies for this to work properly.

#!/usr/local/bin/perl -w
$|=1;

# torel_check ($ip, $port, $destip) queries the Tor DNS Exit List server.
#   The result of the query is one of the following:
#   -1 : DNS lookup failed to get a response, or other error occurred.
#    0 : $ip does not appear to be a Tor exit.
#    1 : $ip is a known Tor exit for the provided destination IP / port.

use Net::DNS::Resolver;
use Getopt::Long;

# some helper methods
sub revaddr {
  my ($addr) = @_;
  my ($a, $b, $c, $d) = split('[.]', $addr);
  return("${d}.${c}.${b}.${a}");
}

sub torel_qh {
  my ($ip, $port, $dest) = @_;
  my $rsrcip = revaddr($ip);
  my $rdstip = revaddr($dest);
  return("${rsrcip}.${port}.${rdstip}.ip-port.exitlist.torproject.org");
}

sub torel_check {
  my ($ip, $port, $dest) = @_;
  my $res = Net::DNS::Resolver->new;
  my $qh = torel_qh($ip, $port, $dest);
  my $pkt;

  # uncomment this line to query the server directly...
  #$res->nameservers("exitlist-ns.torproject.org");

  # tune DNS params accordingly.  this is just my preference.
  $res->retrans(2);
  $res->retry(3);
  $res->usevc(0);

  # perform DNS query
  if (! ($pkt = $res->search($qh)) ) {
    if ($res->errorstring eq "NXDOMAIN") {
      # response but no answer.  does not appear to be Tor exit.
      return(0);
    }
    # search failed: no response or other problem...
    return(-1);
  }
  if (! defined (($pkt->answer)[0])) {
    # response but no answer section.  does not appear to be Tor exit.
    # (this should only happen when authority sections are provided without answer)
    return(0);
  }
  # is Tor exit
  return(1);
}

# defaults, get options...
my $ip = "82.227.101.236";
my $port = 80;
my $dest = "4.3.2.1";
my $pstatus = GetOptions (
 "ip=s" => \$ip,
 "dest=s" => \$dest,
 "port=i" => \$port);

# perform the lookup...
print "Querying Tor DNS Exit List for IP $ip to destination $dest at port $port ... ";
my $result = torel_check($ip, $port, $dest);
print "Done.\n";

if ($result < 0) {
  print "DNS query failed due to timeout or other problem.\n";
}
elsif ($result) {
  print "$ip is a known Tor exit to $dest at port $port.\n";
  exit(1);
}
else {
  print "$ip does not appear to be a Tor exit.\n";
}
exit (0);

Invocation:

# using defaults
torelcheck.pl
Querying Tor DNS Exit List for IP 82.227.101.236 to destination 4.3.2.1 at port 80 ... Done.
82.227.101.236 is a known Tor exit to 4.3.2.1 at port 80.

# using explicit check
torelcheck.pl --ip 71.111.92.174 --port 80 --dest 66.135.40.74
Querying Tor DNS Exit List for IP 71.111.92.174 to destination 66.135.40.74 at port 80 ... Done.
71.111.92.174 does not appear to be a Tor exit.

Dig command line

The DNS query tool "dig" can also be used to make manual queries as described on the torel page. Remember to reverse the IP address octets in the query sent:

dig 236.101.227.82.80.74.40.135.66.ip-port.exitlist.torproject.org

In the response below, the 127.0.0.2 address in the answer section means this endpoint, 82.227.101.236, is indeed a Tor exit to port 80:

; <<>> DiG 9.3.2 <<>> 236.101.227.82.80.74.40.135.66.ip-port.exitlist.torproject.org
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56277
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;236.101.227.82.80.74.40.135.66.ip-port.exitlist.torproject.org.        IN A

;; ANSWER SECTION:
236.101.227.82.80.74.40.135.66.ip-port.exitlist.torproject.org. 579 IN A 127.0.0.2

;; Query time: 31 msec
;; SERVER: 4.2.2.2#53(4.2.2.2)
;; WHEN: Sun Jun 17 21:57:12 2007
;; MSG SIZE  rcvd: 96