outline-apps icon indicating copy to clipboard operation
outline-apps copied to clipboard

Allow custom DNS resolver in client

Open mrphs opened this issue 5 years ago • 43 comments

I've raised this problem a long while ago privately but considering it hasn't changed, I'm assuming it has been forgotten, hence opening this ticket.

Outline shouldn't hard-code DNS resolver in the client. Especially when using OpenDNS. Ideally, this should be an option that is set by the outline manager. Otherwise outline should route the DNS through outline-server. This has a few reasons:

  • OpenDNS is known to interfere with traffic and arbitrary filter, block or drop some requests. They always block Tor, sometimes they block Signal. And that's what a lot journalists need these days to communicate with each other and their sources. That is, if you want to continue to brand outline as a tool for safer journalism.

  • I use outline to help people bypass censorship in various repressive countries. Bypassing one censor to hit another doesn't seem like an ideal situation.

  • This interferes with the principles of open and decentralized web, which I believe are the underlying principles of having any services run self hosted.

  • System administrators should have control over where their users' traffic is routing through.

  • This has a great impact on the privacy of the users.

  • This leads to people following nonstandard workarounds and potentially shooting themselves in the foot if they don't know what they're doing.

mrphs avatar Apr 04 '19 16:04 mrphs

I totally agree with this request, Here in I.R.Iran i can't use Outline on my Android because government censored hard-coded DNS Servers inside outline client, I don't know how they do that but months ago DNS stopped working for mobile phones which run Outline. (means if I turn on Outline, apps which use DNS to resolve their server's IP address will stop working, but some apps like Telegram works because they have IP address of their servers hard-coded inside them)

By the way I'm currently using Simple DNSCrypt + Shadowsocks client on my Laptop and it works perfectly.

Kolahzary avatar Apr 11 '19 09:04 Kolahzary

Thank you for your feedback. We are open to considering this feature request. Since the client could easily override the DNS server set by the server admin (many apps use custom DNS resolvers), we are thinking of making a configurable option in the client. Perhaps the admin could set the default resolver in the invite.

alalamav avatar Apr 11 '19 15:04 alalamav

@alalamav Thanks for the response. I'd argue that those "many apps" you mentioned aren't probably routing the entire traffic of the device.

To be perfectly clear... I think it's ideal if it's configurable in "outline manager" and optionally possible to override by "outline client" if the users wishes to. Otherwise, considering most end users might not even know what DNS is, and that they already have trusted admin with their traffic, it might make sense to let the admin decide where the DNS requests should go.

mrphs avatar Apr 11 '19 16:04 mrphs

@mrphs, your proposal sounds reasonable to me.

The point I was trying to make is that there is no guarantee that a given app will use the system resolver set by Outline. They can always make DNS requests to the resolver of their choice and indeed many apps do.

alalamav avatar Apr 11 '19 16:04 alalamav

Hi, this has become quite urgent as of today. I'm operating four servers in three different countries, and OpenDNS has stopped responding to all of them.

mvonweis avatar Apr 24 '19 10:04 mvonweis

@alalamav

  1. An app not using system resolver is beyond the control of the user without port 53 traffic forwarding, so it could be ignored for now.
  2. The Outline client should always rely on outline server to provide a list of DNS servers to be configured for client. The server can have a default list like the opendns servers or 9.9.9.9 that could be easily changed via outline manager. For non-technical users setting custom DNS servers via client is not something they should have to know. Technical users will always know how to override it.
  3. Truly stealth option would be to embed a DNS-over-https(DoH) client with a localhost resolver on client side querying a DoH server on outline server side that uses an upstream DNS server configurable via outline manager. e.g. https://github.com/m13253/dns-over-https

modib avatar Jun 06 '19 03:06 modib

I don't think Shadowsocks has a mechanism for setting the DNS servers, as it seems to be only a transparent proxy. The client needs to somehow understand which DNS servers to use. The Outline developers have made the simplest possible choice: the DNS servers are hardcoded into the Outline client. If these servers don't work for you, you're out of luck.

The simplest solution I can see is to make the DNS IP addresses changeable through extra URL arguments such as "?dns1=9.9.9.9&dns2=1.1.1.1". This looks sensible to me and is also backwards-compatible.

There could also be a UI element or a json/xml/plist file for users to manually change the DNS servers on the client side. You could also add to the server Docker image a simple httpd daemon that returns a dnsconfig.json file that the client can parse. And if I've understood SS properly (possibly not) you could also set up dnsmasq on the server Docker image, and hardcode the client to use this instead.

This is mostly a problem on the Mac where overriding Outline's default DNS servers is a tough exercise. I'd rather fix the problem properly than write scripts to work around it.

mvonweis avatar Jun 06 '19 08:06 mvonweis

Just make a script called “routedns.sh” in your root directory using sudo nano /root/routedns.sh And paste in the following

#!/bin/bash

/sbin/iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to “outline-server-external-ipv4”:53;
/sbin/iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to “outline-server-external-ipv4”:53;
/sbin/iptables -t nat -A POSTROUTING -j MASQUERADE;
/sbin/ip6tables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to [“outline-server-external-IPv6”]:53;
/sbin/ip6tables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to [“outline-server-external-IPv6”]:53;
/sbin/ip6tables -t nat -A POSTROUTING -j MASQUERADE

exit 0

And then type sudo chmod +x /root/routedns.sh

Then type sudo nano /etc/rc.local And paste in this

#!/bin/bash

bash /root/routedns.sh

exit 0

Finally, type sudo chmod +x /etc/rc.local And enable rc-local by typing sudo systemctl enable rc-local

This will route all dns traffic to the outline server. I tested this on a DigitalOcean Ubuntu 18.04 droplet. This might not work on other vps providers and distros.

Omoeba avatar Jun 10 '19 04:06 Omoeba

@mvonweis The README on outline-server says

This repository has all the code needed to create and manage Outline servers on DigitalOcean. An Outline server runs instances of Shadowsocks proxies and provides an API used by the Outline Manager application.

Outline server install script installs 2 containers - the API for outline manager and the shadowsocks server.

The outline manager API can be extended to also allow defining a default set of nameservers that could be served to all the outline clients.

@Omoeba Thanks for the shell script but I don't think it fits neatly in the way outline-server, outline-manager and outline-client has been designed. Plus the script is not portable to windows and mac.

modib avatar Jun 13 '19 19:06 modib

@modib what do you mean by not portable?

Omoeba avatar Jun 13 '19 20:06 Omoeba

@modib Correct, but this requires modifying three components instead of one. The Manager app is so limited that out only works for trivial setups.

@omoeba The iptables script only works on Linux. Porting it to Mac or Windows requires rewriting the whole script.

mvonweis avatar Jun 14 '19 00:06 mvonweis

The script is meant to be run on the server, not the client.

Omoeba avatar Jun 14 '19 04:06 Omoeba

Just make a script called “routedns.sh” in your root directory using sudo nano /root/routedns.sh And paste in the following

#!/bin/bash

/sbin/iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to “outline-server-external-ipv4”:53;
/sbin/iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to “outline-server-external-ipv4”:53;
/sbin/iptables -t nat -A POSTROUTING -j MASQUERADE;
/sbin/ip6tables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to [“outline-server-external-IPv6”]:53;
/sbin/ip6tables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to [“outline-server-external-IPv6”]:53;
/sbin/ip6tables -t nat -A POSTROUTING -j MASQUERADE

exit 0

And then type sudo chmod +x /root/routedns.sh

Then type sudo nano /etc/rc.local And paste in this

#!/bin/bash

bash /root/routedns.sh

exit 0

Finally, type sudo chmod +x /etc/rc.local And enable rc-local by typing sudo systemctl enable rc-local

This will route all dns traffic to the outline server. I tested this on a DigitalOcean Ubuntu 18.04 droplet. This might not work on other vps providers and distros.

@Omoeba I'm sorry but I'm not good at iptables... so could you write some example codes with example ip for me?

asteriskyg avatar Jul 01 '19 01:07 asteriskyg

@ASTERISK-Git this will route all dns to cloudflare’s DNS at https://1.1.1.1/dns/

#!/bin/bash

/sbin/iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to 1.1.1.1:53;
/sbin/iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to 1.1.1.1:53;
/sbin/iptables -t nat -A POSTROUTING -j MASQUERADE;
/sbin/ip6tables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to [2606:4700:4700::1111]:53;
/sbin/ip6tables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to [2606:4700:4700::1111]:53;
/sbin/ip6tables -t nat -A POSTROUTING -j MASQUERADE

exit 0

Omoeba avatar Jul 01 '19 02:07 Omoeba

I hear the need to specify the DNS resolver to be used. That should be specified by the managers, as general users don't know how to do that.

I like the idea of the server running its own resolver. Then the client could be changed to try :53 first. Or perhaps a special address like dns.local, since Shadowsocks allows for domain addresses.

What should this server resolver look like? I see at least two options:

  • A full recursive resolver, so you don't actually depend on any third party resolver and no extra configuration is needed. This may hurt performance or require extra memory.
  • A stub resolver that talks to a third-party resolver. This requires the manager to specify the resolver somehow. Or perhaps we use the system resolver of the host machine.

Thoughts?

cc: @bemasc

fortuna avatar Sep 17 '20 19:09 fortuna

Clarifying some implementation options.

DNS Wiring

A) Run a stub resolver on the Client's localhost that forwards the queries to dns.local (or some other pre-defined domain) via a Shadowsocks proxy connection. The server would intercept dns.local and forward to the designated resolver. Configure the VPN to use the localhost address as the DNS resolver. B) Configure the VPN to use as the primary DNS resolver. Change server to resolve DNS on :53 using the designated resolver. C) Fetch some DNS config with the address of the designated resolver from the server on connection and configure the VPN with that. D) Hardcode the designated DNS resolver on the invite. Can't be changed later.

In A-C the Client needs to check if the resolver supports DNS resolution. For backward compatibility, we can check that in the connectivity test and fallback to the existing behavior on failure. Similar to how we fallback DNS to TCP if UDP is blocked.

(D) is probably the easiest, but cannot be changed. (C) is probably a good compromise of simplicity and flexibility A-B is compatible with third-party Shadowsocks clients that allow specifying the IP of the resolver.

Server designated resolver

There are a few options for the designated resolver: A) Run own recursive resolver B) Use the host's system resolver C) Use a third-party resolver, specified somehow by the admin. This is similar to (B), since the admin can change the system resolver.

fortuna avatar Sep 17 '20 19:09 fortuna

One interesting consideration here is that, if the VPN advertises the IP of a good third-party resolver, Android DoT and Chrome DoH might be able to upgrade to encrypted DNS. This improves the user's privacy with respect to the proxy server, and also has nontrivial performance and reliability implications (perhaps good, perhaps bad).

I think the best balance of simplicity and flexibility might be to place the proxy's recommended DNS server in a shadowsocks online-config JSON blob, and avoid changing the behavior of ss:// access keys (which would continue to use client-hardcoded resolvers).

One tricky thing here is that Chrome DoH needs to see the third-party IP as the configured DNS server, so we would need to reconfigure the VPN interface after receiving the latest shadowsocks config.

bemasc avatar Sep 17 '20 21:09 bemasc

@fortuna is there any workaround that can give us some control over the resolver until a solution is finished? The only option I can think of atm is adding an upstream firewall and using NAT to force UDP dns traffic to a server of my choosing..

hilt86 avatar Apr 08 '21 07:04 hilt86

@ASTERISK-Git this will route all dns to cloudflare’s DNS at https://1.1.1.1/dns/

#!/bin/bash

/sbin/iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to 1.1.1.1:53;
/sbin/iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to 1.1.1.1:53;
/sbin/iptables -t nat -A POSTROUTING -j MASQUERADE;
/sbin/ip6tables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to [2606:4700:4700::1111]:53;
/sbin/ip6tables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to [2606:4700:4700::1111]:53;
/sbin/ip6tables -t nat -A POSTROUTING -j MASQUERADE

exit 0

Hi @Omoeba, I think iptables -t nat -A POSTROUTING -j MASQUERADE might be not necessary in my case?

I checked the codes here, the outline appears to use OpenDNS, Cloudflare, and Quad9.

// OpenDNS, Cloudflare, and Quad9 DNS resolvers' IP addresses. 
private static final String[] DNS_RESOLVER_IP_ADDRESSES = {
      "208.67.222.222", "208.67.220.220", "1.1.1.1", "9.9.9.9"};

So I just manipulated with those DNS by:

#! /bin/bash

# outline will try to use TCP if UDP is not available for DNS.
iptables -t nat -A OUTPUT -d 208.67.222.222/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 208.67.220.220/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 1.1.1.1/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 9.9.9.9/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53

iptables -t nat -A OUTPUT -d 208.67.222.222/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 208.67.220.220/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 1.1.1.1/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 9.9.9.9/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53

Without `iptables -t nat -A POSTROUTING -j MASQUERADE`` it works fine. I can see tcpdump log in the system.

Without (before) iptables rules, I see the outline is using the shadowsocks-rust server to make the connection to the DNS server (1.1.1.1) directly. tcpdump is not getting anything.

After ipbtales (now), tcpdump is catching the log which should means the host server is making the connection without the shadowsocsks-rust server involve.

My environment: Ubuntu 18.04.5 shadowsocks-rust server (shouldn't matter?)

RebelliousWhiz avatar Jun 10 '21 04:06 RebelliousWhiz

By the way, I think both shadowsocks-libev and shadowsocks-rust servers have the ability to define DNS server for traffic. It really doesn't make sense why Outline can't... 😅

RebelliousWhiz avatar Jun 10 '21 04:06 RebelliousWhiz

@ASTERISK-Git this will route all dns to cloudflare’s DNS at https://1.1.1.1/dns/

#!/bin/bash

/sbin/iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to 1.1.1.1:53;
/sbin/iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to 1.1.1.1:53;
/sbin/iptables -t nat -A POSTROUTING -j MASQUERADE;
/sbin/ip6tables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to [2606:4700:4700::1111]:53;
/sbin/ip6tables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to [2606:4700:4700::1111]:53;
/sbin/ip6tables -t nat -A POSTROUTING -j MASQUERADE

exit 0

Hi @Omoeba, I think iptables -t nat -A POSTROUTING -j MASQUERADE might be not necessary in my case?

I checked the codes here, the outline appears to use OpenDNS, Cloudflare, and Quad9.

// OpenDNS, Cloudflare, and Quad9 DNS resolvers' IP addresses. 
private static final String[] DNS_RESOLVER_IP_ADDRESSES = {
      "208.67.222.222", "208.67.220.220", "1.1.1.1", "9.9.9.9"};

So I just manipulated with those DNS by:

#! /bin/bash

# outline will try to use TCP if UDP is not available for DNS.
iptables -t nat -A OUTPUT -d 208.67.222.222/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 208.67.220.220/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 1.1.1.1/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 9.9.9.9/32 -p tcp --dport 53 -j DNAT --to-destination 8.8.8.8:53

iptables -t nat -A OUTPUT -d 208.67.222.222/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 208.67.220.220/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 1.1.1.1/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53
iptables -t nat -A OUTPUT -d 9.9.9.9/32 -p udp --dport 53 -j DNAT --to-destination 8.8.8.8:53

Without `iptables -t nat -A POSTROUTING -j MASQUERADE`` it works fine. I can see tcpdump log in the system.

Without (before) iptables rules, I see the outline is using the shadowsocks-rust server to make the connection to the DNS server (1.1.1.1) directly. tcpdump is not getting anything.

After ipbtales (now), tcpdump is catching the log which should means the host server is making the connection without the shadowsocsks-rust server involve.

My environment: Ubuntu 18.04.5 shadowsocks-rust server (shouldn't matter?)

Yeah the masquerade rules are completely unnecessary. I don't recommend using rc.local either since it's deprecated so use a @reboot cron job or even better, a simple systemd service ordered after the host's networking (network-online.target in most cases). To be completely honest, I'll probably disagree with the vast majority of recommendations I made 2 years ago so take everything here with a healthy dose of skepticism and make sure to do plenty of testing.

Omoeba avatar Jun 10 '21 06:06 Omoeba

Thanks for the reply Omoeba! And yes, I am using a systemd .service file to manage this script. I am doing tons of testing here to make sure everything works fine...

RebelliousWhiz avatar Jun 10 '21 08:06 RebelliousWhiz

Sounds good! So DNS resolvers are still hardcoded in the client 2 years later?

Omoeba avatar Jun 10 '21 08:06 Omoeba

Yelp.... 🙄

RebelliousWhiz avatar Jun 10 '21 08:06 RebelliousWhiz

That's unfortunate.

Omoeba avatar Jun 10 '21 08:06 Omoeba

Since this came up in a net4people issue, I wanted to say that I think we'd be open to collaborating with a community member on a contribution to solve this.

Regarding @bemasc's suggestion, we are currently working on online config and so that may become possible in the next few months.

My guess (but @fortuna would have to weigh in) is that we'd want to start with the simplest approach first (such as using a forwarder on the server) before moving on to more advanced options (advertising a server-specified DNS server to the client and re-configuring the VPN connection).

cjhenck avatar Aug 06 '21 00:08 cjhenck

Just looking for a way to configure DoH or DoT 3rd party dns resolver on the outline server.

nhutwelker avatar Sep 05 '21 23:09 nhutwelker

+1 to set custom DNS or even local DNS instead of routing it through server

nirkons avatar Dec 05 '21 08:12 nirkons

I'm using laravel valet which uses dnsmasq, I can't access my projects when outline is connected. Please add an option to disable outline DNS resolver entirely

Zzombiee2361 avatar Jan 07 '22 14:01 Zzombiee2361

After some fiddling, I noticed that shadowsocks has a Rust version that does support local-dns features, but it needed quite a bit of fiddling to try, since you need both the server and the client to use that Rust version if I'm not mistaken. And I'm not even sure if you can use local resolvers with it, but at least you can specify custom DNS servers.

At least there is hope!

For the time being, my workarounds was:

  1. Use a custom DNS on the server hosting outline itself.
  2. OR.. use a custom DNS server while bringing up the docker container of outline, which isn't easy if you're using the bash script directly. But it's possible if you hack it. Check out the --dns argument in Docker docs.

shadyvb avatar Jan 11 '22 10:01 shadyvb