dapphp / TorUtils

PHP classes for interacting with Tor over the control protocol, querying directory authorities and servers, and DNS exit lists.
BSD 3-Clause "New" or "Revised" License
69 stars 8 forks source link

dns server #5

Open melaxon opened 4 years ago

melaxon commented 4 years ago
try {
    $isTor = TorDNSEL::IpPort(
        $_SERVER['SERVER_ADDR'],
        $_SERVER['SERVER_PORT'],
        $_SERVER['REMOTE_ADDR']
    );
    var_dump($isTor);
 } catch (\Exception $ex) {
     echo $ex->getMessage() . "\n";
 }

This will always result in error:

Warning: fsockopen(): unable to connect to udp://exitlist.torproject.org:53 (php_network_getaddresses: getaddrinfo failed: Name or service not known) in /var/www/html/project/vendor/dapphp/torutils/src/TorDNSEL.php on line 183
Failed to send DNS request. Error 0: php_network_getaddresses: getaddrinfo failed: Name or service not known 

It seems like DNS server exitlist.torproject.org has changed. Will you please advice how to fix this

dapphp commented 4 years ago

Hi Thanks for opening this issue! It looks like I missed https://lists.torproject.org/pipermail/tor-project/2020-March/002759.html

TLDR; the TorDNSEL service is retired and replaced with a simpler and better service.

I will release a new version this evening or tomorrow morning with the fix for this.

dapphp commented 4 years ago

The last commit introduces a backwards compatible fix with the current interface so it will work.

In an upcoming release the interface will be simplified and add TXT support for getting relay fingerprints.

melaxon commented 4 years ago

Thanks for quick response! Another issue one may face when the server works under Cloudflare: $_SERVER shows sometimes (not always!) HTTP_X_FORWARDED_FOR in IPV6 format, while REMOTE_ADDR is Cloudflare's ip

dapphp commented 4 years ago

When I was working on it last night I was thinking about IPv6 too and noticing the "Tor bulk exit list" still does not contain any IPv6 addresses.

I'm going to add IPv6 support to my code for when the lists contain IPv6. This assumes the DNSBL will use the same 1-digit hex representation like other DNSBL services.

So right now you still cannot use the DNSBL for checking against IPv6 clients. I also publish my own exit lists (just derived from the "Exit" flag) so it is not based on observation like Onionoo but at least you might be able to get some basic IPv6 checking with that.

melaxon commented 4 years ago

About Cloudflare: they return real IP in the header parameter HTTP_CF_CONNECTING_IP, which may contain IPv6 as well. One man from Cloudflare community suggested a solution: to detect visitor country using IP geolocation. Indeed they also return HTTP_CF_IPCOUNTRY parameter that in case of Tor is always set to T1 independant on IPv4 or IPv6. I believe it is quite reliable. What do you think?

dapphp commented 4 years ago

If your site is using Cloudflare then you can simply rely on the HTTP_CF_IPCOUNTRY being T1 to check for Tor, otherwise, you can fall back to TorDNSEL or some other list. Given CF's history with detecting/blocking Tor and their wide range of skills there's no reason to think their matching would be any more or less accurate. The added benefit now is they work with IPv6 which the new Tor DNSEL still does not.

Even though this code may no longer be too relevant for you (that's ok!) here is the newer interface I came up with in case you had any feedback. I don't like the static interface anymore but it keeps this really simple (for users) and keeps the existing code backwards compatible.

// The old deprecated method, works again and now ignores $ip, $port and only uses $remoteIp
public static TorDNSEL::IpPort($ip, $port, $remoteIp, $dnsServer = 'check-01.torproject.org');

/**
 * Query the Tor DNSEL service to check if $remoteAddr is a Tor exit relay.
 * 
 * @param $remoteAddr The remote IP address, (e.g. $_SERVER['REMOTE_ADDR'])
 * @param null $dnsServer The DNS resolver to query against (if null it will use check-01.torproject.or)
 *  Consider using a local, caching resolver for DNSEL queries!
 * @return bool true if the visitor's IP address is a Tor exit relay, false if it is not
 * @throws \Exception
 */
public static TorDNSEL::isTor($remoteAddr, $dnsServer = null);

/**
 * Query the Tor DNS Exit List service for a list of relay fingerprints that belong to the supplied IP address.
 * If $remoteAddr is not a Tor exit relay, an empty array is returned.
 * 
 * @param $remoteAddr The Tor exit relay IP address (IPv4 or IPv6) - Note: IPv6 is not currently supported by TorDNSEL
 * @param null $dnsServer The DNS resolver to query against (if null it will use check-01.torproject.or)
 *  Consider using a local, caching resolver for DNSEL queries!
 * @return array An array of Tor relay fingerprints, if the IP address is a Tor exit relay
 * @throws \Exception If there is a network error or the DNS query fails
 */
public static TorDNSEL::getFingerprints($remoteAddr, $dnsServer = null);

Example usage:

<?php

$isTor = false;
$fingerprints = [];
$remoteIP = $_SERVER['REMOTE_ADDR'];
$dnsServer = null; // null to query check-01.torproject.org, or set to your local, caching resolver

try {
    $isTor = TorDNSEL::isTor($remoteIP, $dnsServer);
    if ($isTor) {
        $fingerprints = TorDNSEL::getFingerprints($remoteIP, $dnsServer); // not necessary, for demonstration purposes.
    }
} catch (\Exception $ex) {
    error_log("TorDNSEL query failed: " . $ex->getMessage());
}

Thank you again!

melaxon commented 4 years ago

This Tor detecting became a real nightmare for me. I noticed that a quite sufficient part of TOR user IPs return a particular country code rather than T1. For example 2a0b:f4c1::8 returns DE. But its whois states in remarks that it is one of Tor exit node addresses. CF also provides an option called Pseudo IPv4. When it's on it sends corresponding IPv4 along with IPv6. But they are not in Tor exit list of course and their whois says it is reserved for future use or even is not available. E.g. 240.172.240.82

Will you please advice where and how these methods are implemented: TorDNSEL::isTor($remoteIP, $dnsServer) and TorDNSEL::getFingerprints($remoteIP, $dnsServer) ? Sorry to be so pain and for my carelessness if I missed something

dapphp commented 4 years ago

That is unfortunate. Maybe CF's Tor detection for IPv6 is also lacking.

As stated earlier, the new TorDNSEL service also doesn't support IPv6 so this class won't help yet.

Regarding the new method signatures I posted above, I haven't actually pushed those changes yet (which include IPv6 readiness but not support since the underlying service doesn't yet either.

Sadly, this may require even more code.

The Tor exit lists that I maintain and publish (updated every 10 minutes from live Tor directory consensus) do contain the IP you reference, 2a0b:f4c1::8, so perhaps a combination of DNSEL for IPv4 and list lookup for IPv6 is the interim solution (which sucks).

I will push the changes to master in a moment, and you may need to do something to download and cache these lists so you can do an IPv6 lookup.

dapphp commented 4 years ago

I have to run for a bit but the new code is pushed to master (referenced in above commit).

This TorUtils library doesn't have any code for dealing with my exit lists but in the mean time feel free to check out my Wordpress plugin https://wordpress.org/plugins/vigilantor/ which has code that downloads the lists and checks (works with IPv6).

We can discuss further if you have ideas and maybe it makes sense to add a new class to this code that can check against Onionoo lists, Tor DNSEL, my exit lists, and perhaps others' too.

melaxon commented 4 years ago

Check this: onionoo call described here can help (I hope) https://metrics.torproject.org/onionoo.html#summary

Let's say I'm interesting in this address 2a0b:f4c1::8, so I can curl to https://onionoo.torproject.org/summary?search=2a0b:f4c1::8 and receive json response containing both IPv6 I requested and corresponding IPv4. If I receive it I can conclude that this IP is from Tor network

I actually am not sure if this format https://onionoo.torproject.org/summary?search=... is documented and will be supported tomorrow

dapphp commented 4 years ago

The parameters described here are the documentation on how to use Onionoo. These have been stable for quite some time but I'm not sure this service is really suitable for real-time lookups.

For example you could shorten your search to https://onionoo.torproject.org/summary?search=2a0b:f4c1::8&fields=nickname&limit=1 (limit 1, only return 1 field). Right now I'm getting response times around 0.8 seconds looking up random IP's (some Tor some not).

Non-cached Tor DNSEL responses are at least 2.5x faster, and if you use a close & fast caching resolver they go down to virtually 0 once cached.

The mailing list post https://lists.torproject.org/pipermail/tor-project/2020-March/002759.html mentions some changes to how the Bulk Exit List is created (based on observations and not only "Exit" flags set on relays) so I'm not entirely sure what the source(s) of that list are anymore.

The fastest and more reliable way to run these checks is to download lists periodically and stoe them locally and perform lookups against these lists.As of last night I am having good luck combining https://check.torproject.org/torbulkexitlist and https://www2.openinternet.io/tor/tor-exit-list.txt and removing dupes and searching a sorted list with binary search (for speed).

melaxon commented 4 years ago

Yes, you are right! Cached file is simple and fast solution. I update it each hour, I guess it is quite sufficient for practical use. If the file is not updated for a while (e.g. onionoo service ceased to exist) then the script tries to fetch from onionoo link then if failed it calls TorDNSEL. Just wanted to notice that limit=1 (see your previous post) is not enough as such an address 2a03:e600:100::1 will match 2a03:e600:100::10, 2a03:e600:100::11, ... as well. Thanks a lot for your help!!!