shadowsocks / shadowsocks-org

www.shadowsocks.org
MIT License
882 stars 541 forks source link

Protocol: TCP responses should include remotely resolved target address like UDP #52

Closed riobard closed 7 years ago

riobard commented 7 years ago

In the current protocol, TCP responses from ss-remote do not contain the actual IP address of the target server.

It would be nice to include the actual IP address of the target server relayed back to ss-local so ss-local can provide functionality similar to ChinaDNS, making it easier for people to direct traffic automatically without using the unreliable gfwlist.

A typical use case:

  1. Client asks ss-local to connect to twitter.com.
  2. ss-local tries to directly connect to twitter.com using a locally resolved IP address. Because ss-local is located within GFW, the resolved IP is polluted and fake.
  3. ss-local also tries to connect to twitter.com via ss-remote, which is not affected by GFW, and resolves the actual IP of twitter.com.
  4. ss-local compares the address locally resolved and the address relayed from ss-remote, and see that both IPs are outside China IP range.
  5. ss-local prefers the connection via ss-remote because the it knows the locally resolved address is fake.
madeye commented 7 years ago

ss-local compares the address locally resolved and the address relayed from ss-remote, and see that both IPs are outside China IP range.

I don't think it would work today. AFAIK, fake DNS results could be also outside China IP range.

riobard commented 7 years ago

@madeye The strategy of ChinaDNS is:

  1. resolve both locally and remotely
  2. if both localIP and remoteIP are within China IP range, connect directly
  3. if both localIP and remoteIP are outside China IP range, connect via proxy

This is what usually called "a split tunnel".

riobard commented 7 years ago

The other remaining cases are more interesting:

  1. localIP in China but remoteIP abroad: it's usually because of GeoDNS or CDN. It's usually better to connect directly for better speed due to locality.
  2. if localIP abroad but remoteIP in China: it's likely an error.
madeye commented 7 years ago

If so, why not resolve the remote IP locally with a tunnel to the remote DNS?

Shadowsocks-android did this by setting up a ss-tunnel to remote DNS.

riobard commented 7 years ago

That's how I currently use it. However the setup is quite complex: in addition to ss-tunnel, you also need ChinaDNS to filter IP addresses, and because ChinaDNS is not very performant and does not cache results, you'll also need dnsmasq in front of it. Something like this:

dnsmasq --> ChinaDNS --> ss-tunnel --> ss-remote

Not to mention in some environment UDP packets are not allowed. This is why shadowsocksr supports UDP-over-TCP if I understand it correctly.

With this change in protocol, we essentially gain DNS-over-TCP for free, and there's no need for UDP-based ss-tunnel, dnsmasq, or ChinaDNS at all. Deployment can be vastly simplified.

madeye commented 7 years ago

Then the problem becomes

  1. If ss-local is aware of the domain name, we can always ensure remote DNS resolving and accurate policy route based on GFWList.
  2. If ss-local only knows the IP, it still cannot identify fake IP with this proposal.

For better local DNS resolving, I suggest using dnscrypt-proxy in your environment. By connecting to a safe DNS directly, no need to worry about fake IPs or GEO-IP CDN related problems.

riobard commented 7 years ago

Yes, this is only possible where ss-local can get domain name. Luckily this is the usual case.

The problem of GFWlist is that it does not scale well with the number of blocked domains. That's why many users prefer the split tunnel approach to automatically direct traffic to proxy.

I don't think dnscrypt-proxy solves the CDN problem. Remotely resolved IP addresses will point to a foreign CDN when there's a China CDN to use. You still need ChinaDNS to decide which IP addresses to return to clients.

madeye commented 7 years ago

I don't think dnscrypt-proxy solves the CDN problem. Remotely resolved IP addresses will point to a foreign CDN when there's a China CDN to use. You still need ChinaDNS to decide which IP addresses to return to clients.

Hmm, I'm not suggesting dnscrypt-proxy with ss-tunnel. Actually dnscrypt-proxy can connect to OpenDNS directly, so it's still a local DNS resolving.

Also find some assumptions look not correct:

localIP in China but remoteIP abroad: it's usually because of GeoDNS or CDN. It's usually better to connect directly for better speed due to locality.

In this case, the local IP could still be a fake IP... If we choose to connect both local IP and remote IP, then this proposal becomes cow.

riobard commented 7 years ago

Isn't OpenDNS IP blocked? I didn't know that you can still send packets to it. Will try later. But even so, I doubt if OpenDNS can correctly handle the complicated network situations in China (like Telecom/Unicom/China Mobile IP ranges and stuff). You'd still need to use things like 114 DNS.

hellofwy commented 7 years ago

If i'm correct: If the domain's authoritative DNS server doesn't support edns-client-ip extension, it'll only see recursive server's IP from OpenDNS. So location based CDN optimization will fail.

madeye commented 7 years ago

@hellofwy Correct. But as we're talking about GEO-IP based CDN, we should not worry about it.

hellofwy commented 7 years ago

But I think if a website do HTTP redirection base on the client IP, the redirection may not be expected in some cases, because website's authoritative DNS server may not support edns-client-ip extension.

I don't support to alert the protocol though. Maybe can use different cipher names to support different protocols.

madeye commented 7 years ago

@riobard BTW, do you have time to write a simple go-dns server that supports memory cache, ACL, socks5 proxy and edns-client-subnet? If so, we'd like to integrate it into shadowsocks-android.

I did a survey before and found the best way is based on this library https://github.com/miekg/dns. However, I'm not very familiar with golang.

riobard commented 7 years ago

@madeye If I understand you correctly, dnscrypt-proxy talking to OpenDNS with edns-client-ip extension

  1. Allow clients in China to resolve foreign IP correctly without pollution, and
  2. OpenDNS will also correctly handle the China Telecom/Unicom/Mobile network fragmentation issues for China CDN, and
  3. OpenDNS IP is not blocked by GFW.

I find 1 is OK, but 2 and 3 are questionable.

As for the go-dns server: I've checked the dns lib you mentioned above and was thinking about porting ChinaDNS to Go before. What was the requirement for shadowsocks-android? I didn't even know that it's possible to run Go app on Android πŸ‘

madeye commented 7 years ago

And OpenDNS IP is not blocked by GFW?

Nope, never blocked. http://ping.chinaz.com/208.67.222.222.

Also, we have many anycast DNS servers support TLS DNS (dnscrypt): https://github.com/jedisct1/dnscrypt-proxy/blob/master/dnscrypt-resolvers.csv

dnscrypt-proxy talking to OpenDNS with edns-client-ip extension should allow clients in China to resolve foreign IP correctly without pollution and OpenDNS will also correctly handle the China Telecom/Unicom/Mobile network fragmentation issues for China CDN?

This should also work, even with all the foreign DNS servers blocked.

What was the requirement for shadowsocks-android?

No specific requirements, but the feature wish list above is necessary, in order to replace PDNSD in shadowsocks-android. The most wanted feature is to support socks5 proxy, which helps to replace ss-tunnel.

I didn't even know that it's possible to run Go app on Android

Actually, golang works quite well on Android. πŸ˜„

riobard commented 7 years ago

Just tried to directly resolve via OpenDNS on port 53 and 5353. Indeed, they are not blocked, but DNS requests to port 53 are polluted (as expected).

Come to think about it, I realized that unfortunately we still need a split DNS solution because we also need to make sure that ss-remote can access nearby CDN. For example if ss-remote is deployed in Japan, you want a CDN edge in Japan. Locally resolving via OpenDNS might give you US IP.

Ideally we need a solution that works consistently in most situations. Can you explain a little bit how shadowsocks-android works? I don't have android devices to test.

madeye commented 7 years ago

shadowsocks-android works as a transparent proxy, which requires local DNS resolving. So,

  1. We set up a "split" local DNS resolver with PDNSD. All the domain names in the GFWList are resolved with a remote DNS using TCP through ss-tunnel and others resolved by a local DNS (114DNS) using UDP directly.
  2. Then we got IPs in ss-local and forward IPs not in China IP block to remote server.
riobard commented 7 years ago

You're using both GFWlist and ss-tunnel in TCP mode? That's like the worst of both worlds 😱

Wouldn't it be easier to use the setup described in https://github.com/shadowsocks/shadowsocks-org/issues/52#issuecomment-281561841 ?

madeye commented 7 years ago

They are both necessary for shadowsocks-android. We need to convert UDP DNS queries to TCP queries in PDNSD, in case the server doesn't support UDP forwarding.

riobard commented 7 years ago

So basically you need a customized DNS solution that can

  1. Talk to 114 DNS using UDP;
  2. Talk to Google DNS via ss-remote using UDP and TCP if UDP forwarding is not enabled on ss-remote;
  3. Filter polluted IP;
  4. Cache results.

Is that correct?

madeye commented 7 years ago

Right. In addition, with socks5 proxy support, we can also remove ss-tunnel.

riobard commented 7 years ago

How? You still need ss-tunnel to forward UDP packets. Shadowsocks itself does not implement SOCKS5 UDP ASSOCIATE command AFAIK.

madeye commented 7 years ago

All the shadowsocks clients have already supported socks5 UDP associate...

maddie commented 7 years ago

@riobard @madeye Regarding the customized DNS solution you guys mentioned, I'm using https://github.com/holyshawn/overture in my home and company to filter DNS results. Would that be any help? It's written in Go and supports edns-client-subnet

riobard commented 7 years ago

@madeye I didn't know that! πŸ˜‚ Do you know any use cases? I've always wanted to try something like that but so far haven't found an app that works with SOCKS5 UDP.

madeye commented 7 years ago

@maddie Wow, it looks really cool. After adding socks5 proxy, we can directly replace PDNSD with it.

madeye commented 7 years ago

The best app is the socksify in Dante. You can even do socksify dig google.com with shadowsocks.

riobard commented 7 years ago

I see. So ss-local in libev port also has complete SOCKS5 support (including UDP ASSOCIATE), right? We might need to have a feature comparison table for common clients as mentioned somewhere in another issue…

riobard commented 7 years ago

@maddie Did you use the EDNSClientSubnet option?

maddie commented 7 years ago

@riobard I tried but it didn't seems to work reliably. I'm not sure if it's the implementation or the server I'm using (DNSPod IIRC) isn't working correctly. I haven't tried the option with Google/OpenDNS though.

I'm not a very seasoned Go programmer (or in general programmer ;]) but overture's code doesn't seem to be very idiomatic and definitely needs some work.

@madeye It should be pretty trivial to add SOCKS5 proxy in Go

hellofwy commented 7 years ago

Can the DNS resolver uses Shandowsocks protocol directly, which seems don't have to set up a SOCKS5 UDP association first?

Though extra SOCKS5 proxy support is also helpful.

riobard commented 7 years ago

@maddie Yeah I glanced thru overture's code base and the style is kinda Python-ish, probably due to the developer's Python background.

@madeye Overture's features almost fit the bill if you don't mind ss-remote not getting the optimal result due to local resolution. I'd still prefer a split DNS solution where ss-local resolves domestic addresses and ss-remote resolves foreign addresses for maximum accuracy.

riobard commented 7 years ago

@hellofwy It is currently possible if you use ss-tunnel.

madeye commented 7 years ago

@riobard

So ss-local in libev port also has complete SOCKS5 support (including UDP ASSOCIATE), right?

Right. AFAIK, python, golang, qt, libev and even rust ports support this.

ss-remote not getting the optimal result due to local resolution.

I think we can fix it by adding SOCKS5 proxy feature to overture, enforcing any remote resolving through ss-local. We can also apply ChinaDNS feature as well.

riobard commented 7 years ago

@madeye I'm pretty sure go-shadowsocks does not support UDP ASSOCIATE.

madeye commented 7 years ago

@riobard Hmm, it looks interesting. Then what's this release for? https://github.com/shadowsocks/shadowsocks-go/releases/tag/1.2.0.

riobard commented 7 years ago

@madeye That one added UDP relay mode to the server. The client is still only capable of TCP.

riobard commented 7 years ago

I've been thinking about this proposal this week. Now I think it's better to solve the DNS problem separately. The benefit this proposal brings is not worth the trouble of changing the existing protocol.

Closing for now.