china-dns icon indicating copy to clipboard operation
china-dns copied to clipboard

A DNS proxy for netizens in China

#+TITLE: A DNS proxy preventing poisoning and CDN-friendly

I'm looking for statically compiled binary for every platform. If you're willing to contribute, please contact me at zhina dash dns at riaqn dot org

Available binary versions so far: | | Linux | MacOS | Windows | |--------+-------+-------+---------| | x86_64 | ✓ | ✓ | | | x86 | | n/a | | | ARMv7h | ✓ | n/a | | | ... | | | |

Please see the Release page for downloads.

I'm developing this project because the existing ChinaDNS occasionally behaves wierd, so I would rather write one by myself. I'm using Haskell because of:

  • high concurrency
  • static type safety
  • efficient development
  • Overview This program assumes:
  • access to the TCP port 53 on the foreign DNS is proxy-ed(ss-redir, VPN, etc.), or you will still be poisoned.
  • this proxy has to be the same host you use to proxy other traffic (http, etc.), otherwise we couldn't guarantee a CDN-optimal result.

The built-in resolution strategy:

  1. On receiving a request, check if the request is for a foreign domain(=--world_name=). yes: go to 2; no: go to 3;
  2. Forward the request to foreign DNS and wait for the result.
  3. Forward the request to Chinese and foreign DNS at the same time. Check if the response from Chinese DNS contains foreign IP(=--china_ip=). yes: go to 4; no: go to 5;
  4. wait for response from foreign DNS
  5. return Chinese result immediately.

Note: only TCP is used for foreign DNS.

If you have any thoughts about the resolution strategy, please feel free to share it on the issue page.

  • Configuration #+Begin_example Usage: zhina-dns [--host HOST] [--port PORT] [--zhina_host HOST] [--zhina_port PORT] [--world_host HOST] [--world_port PORT] [--zhina_timeout MICROSEC] [--world_timeout MICROSEC] [--log_level SPEC] --zhina_ip PATH --world_name PATH a DNS proxy for people in Zhina

    Available options: -h,--help Show this help text --host HOST hostname to bind (default: Nothing) --port PORT port number to bind (default: "5300") --zhina_host HOST upstream Chinese server host (default: "114.114.114.114") --zhina_port PORT upstream Chinese server port (default: "53") --world_host HOST upstream foreign server host (default: "8.8.8.8") --world_port PORT upstream foreign server port (default: "53") --zhina_timeout MICROSEC timeout for Chinese upstream, UDP and TCP combined (default: 1000000) --world_timeout MICROSEC timeout for foreign upstream, UDP and TCP combined (default: 5000000) --log_level SPEC spec for logging (default: [("",INFO)]) --zhina_ip PATH file containing Chinese IP ranges --world_name PATH file containing foreign domain names #+end_example

Please fork this repository for more fine-grained modifications; feel free to drop a pull request if you think your modifications may be also useful to others.

  • for modifications of upstream servers/local servers, see [[src/Main.hs]]
  • for modifications of the resolution strategy, see [[src/ZhinaDNS.hs]]
  • Running This program needs a list of Chinese IP ranges and a list of foreign domain names on startup. The formats of both are similar and is as follows: #+begin_example

    this is allowed

    this is also allowed

    #^ empty line (with or without spaces) is allowed #v ip range prefixed or suffixed with spaces is allowed 1.0.1.0/24 #v single ip address is also allowed 127.0.0.1 #+end_example

Both files are shipped with the project already: check out [[china.txt]] and [[world.txt]]. However, if you prefer generating these files yourself, please check out [[china.awk]] (for =china.txt=) and [[https://github.com/cokebar/gfwlist2dnsmasq]] (for =world.txt=).

And we are free to go: #+begin_src sh ./zhina-dns --zhina_ip china.txt --world_name world.txt #+end_src

Since every request to =zhina-dns= involves a request to the foreign DNS(if it's a foreign website), the latency may be quite high(as high as 300ms), it's thus strongly recommended to wrap a layer of =pdnsd= or =unbound= as DNS cache. I had some problems with =pdnsd=, so =unbound= maybe better. An example of =unbound.conf=:

#+begin_src yaml server: verbosity: 3 interface: 127.0.0.1 use-syslog: yes username: "unbound" directory: "/etc/unbound" do-not-query-localhost: no

forward-zone: name: "." forward-addr: 127.0.0.1@5300 #+end_src =do-not-query-localhost: no= overrides the restriction of =unbound= that local servers can't be used as upstream server.