restructure code

add tls, DoH support
remove black list, per domain config
change config format
change log library
nghttp2
fangdingjun 6 years ago
parent 667dc1ff49
commit 90d650c118

@ -1,54 +1,25 @@
# gdns
a dns proxy server write by go
gdns much like dnsmasq or chinadns, but it can run on windows.
Features
========
support different domains use different upstream dns servers
support tcp, udp, tls, DoH(dns over https/http2)
support contact to the upstream dns server by tcp or udp
support blacklist list to block the fake ip
Install
Usage
=======
```bash
# get the depended library
go get github.com/miekg/dns
go get github.com/vharitonsky/iniflags
git clone https://github.com/fangdingjun/gdns
cd gdns
go build
# generate a sample config file
./gdns -dumpflags > dns.ini
# edit the dns.ini and run, need root privileges to bind on port 53
sudo ./gdns -config dns.ini
# test it
dig @localhost twitter.com
go get github.com/fangdingjun/gdns
cp $GOPATH/src/github.com/fangdingjun/gdns/config_example.yaml config.yaml
vim config.yaml
$GOPATH/bin/gdns -c config.yaml -log_level DEBUG
```
Arguments
===========
use `gdns -h` to show the command line arguments.
all arguments can specialed in config file or in command line.
there is a sample file in the `config_sample/` directory.
Third-part library
==================
use
[dns](https://github.com/miekg/dns)
library to parse the dns message
use
[iniflags](https://github.com/vharitonsky/iniflags)
library to process the command line arguments and the config file

@ -1,118 +0,0 @@
package main
import (
"flag"
"github.com/miekg/dns"
"github.com/vharitonsky/iniflags"
"log"
)
var bind_addr string
var default_server ArgSrvs
var srv ArgSrvs
var logfile string
type ArgSrvs []string
var DefaultServer []*UpstreamServer
var blacklist_file string
var enable_cache = false
var region_file = ""
func (s *ArgSrvs) String() string {
//Sprintf("%s", s)
return ""
}
func (s *ArgSrvs) Set(s1 string) error {
*s = append(*s, s1)
return nil
}
func parse_flags() {
iniflags.Parse()
var err error
for _, s := range srv {
sv, err := parse_server(s)
if err != nil {
log.Print(err)
} else {
Servers = append(Servers, sv)
}
}
for _, dsvr := range default_server {
proto, addr, err := parse_addr(dsvr)
if err != nil {
log.Fatal(err)
}
var c *dns.Client
if proto == "udp" {
c = client_udp
} else {
c = client_tcp
}
upsrv := &UpstreamServer{
Addr: addr,
Proto: proto,
client: c,
}
DefaultServer = append(DefaultServer, upsrv)
}
if len(DefaultServer) == 0 {
log.Fatal("please special a -upstream")
}
a, err := load_domain(blacklist_file)
if err != nil {
log.Println(err)
} else {
Blacklist_ips = a
}
if hostfile == "" {
hostfile = GetHost()
}
if hostfile != "" {
record_hosts, err = ReadHosts(hostfile)
if err != nil {
log.Fatal(err)
}
}
if region_file != "" {
ip_region = parse_net(region_file)
}
}
func init() {
flag.Var(&srv, "server", `special the filter and the upstream server to use when match
format:
FILTER_FILE_NAME,PROTOCOL:SERVER_NAME:PORT
example:
filter1.json,udp:8.8.8.8:53
means the domains in the filter1.json will use the google dns server by udp
you can specail multiple filter and upstream server
`)
flag.StringVar(&bind_addr, "bind", ":53", "the address bind to")
flag.Var(&default_server, "upstream", "special the upstream server to use")
flag.StringVar(&logfile, "logfile", "", "the logfile, default stdout")
flag.StringVar(&blacklist_file, "blacklist", "", "the blacklist file")
flag.BoolVar(&debug, "debug", false, "output debug log, default false")
flag.StringVar(&hostfile, "hosts", "", "load special ip from hosts or /etc/hosts")
flag.BoolVar(&enable_cache, "enable_cache", false, "enable cache or not")
flag.StringVar(&region_file, "region_file", "", "local country region ip range file")
}

@ -1,19 +0,0 @@
package main
import (
"testing"
)
func TestArgs(t *testing.T) {
var a ArgSrvs
(&a).Set("aa")
if len(a) != 1 {
t.Fail()
}
if a[0] != "aa" {
t.Fail()
}
}

@ -1,529 +0,0 @@
{
"0.0.0.0": 1,
"1.1.1.1": 1,
"1.1.127.45": 1,
"1.1.67.51": 1,
"1.2.3.4": 1,
"1.209.208.200": 1,
"1.226.83.147": 1,
"1.234.21.83": 1,
"1.234.29.40": 1,
"1.234.39.14": 1,
"1.234.4.91": 1,
"1.234.70.80": 1,
"1.234.83.104": 1,
"1.244.115.172": 1,
"1.33.170.68": 1,
"1.33.188.62": 1,
"1.33.190.228": 1,
"1.33.190.70": 1,
"1.33.191.58": 1,
"2.1.1.2": 1,
"2.187.253.121": 1,
"2.228.123.7": 1,
"2.228.154.8": 1,
"4.17.143.131": 1,
"4.193.80.0": 1,
"4.21.70.9": 1,
"4.30.13.168": 1,
"4.30.187.9": 1,
"4.30.235.229": 1,
"4.31.139.146": 1,
"4.34.180.178": 1,
"4.35.100.20": 1,
"4.35.234.200": 1,
"4.36.66.178": 1,
"4.53.17.215": 1,
"4.59.79.206": 1,
"4.78.167.196": 1,
"4.79.129.122": 1,
"4.59.90.226": 1,
"4.59.90.212": 1,
"4.59.90.237": 1,
"4.59.90.221": 1,
"4.59.90.246": 1,
"4.59.90.232": 1,
"4.59.90.242": 1,
"4.59.90.241": 1,
"4.59.90.217": 1,
"4.59.90.227": 1,
"4.59.90.247": 1,
"4.59.90.231": 1,
"4.59.90.236": 1,
"4.59.90.251": 1,
"4.59.90.216": 1,
"4.59.90.222": 1,
"5.10.105.41": 1,
"5.10.68.187": 1,
"5.10.68.188": 1,
"5.10.69.29": 1,
"5.10.77.72": 1,
"5.100.152.24": 1,
"5.100.225.204": 1,
"5.100.228.206": 1,
"5.100.231.27": 1,
"5.100.248.208": 1,
"5.144.129.20": 1,
"5.35.251.108": 1,
"5.9.118.111": 1,
"5.9.120.140": 1,
"5.9.136.210": 1,
"5.9.242.232": 1,
"5.9.5.26": 1,
"5.9.65.105": 1,
"8.105.84.0": 1,
"8.34.161.150": 1,
"8.7.198.45": 1,
"12.87.133.0": 1,
"14.102.249.18": 1,
"16.63.155.0": 1,
"20.139.56.0": 1,
"23.23.14.192": 1,
"23.89.5.60": 1,
"24.51.184.0": 1,
"28.121.126.139": 1,
"28.13.216.0": 1,
"31.169.90.4": 1,
"31.170.8.8": 1,
"31.210.156.212": 1,
"31.22.4.60": 1,
"31.222.185.202": 1,
"31.25.191.134": 1,
"31.13.99.18": 1,
"34.254.247.151": 1,
"37.1.205.21": 1,
"37.1.207.129": 1,
"37.140.238.35": 1,
"37.187.134.150": 1,
"37.187.149.129": 1,
"37.187.251.35": 1,
"37.208.111.120": 1,
"37.252.122.184": 1,
"37.58.78.79": 1,
"37.59.25.95": 1,
"37.61.54.158": 1,
"37.99.194.148": 1,
"38.117.98.231": 1,
"41.79.20.9": 1,
"43.253.199.12": 1,
"46.137.219.7": 1,
"46.165.231.144": 1,
"46.20.126.252": 1,
"46.20.13.100": 1,
"46.229.175.95": 1,
"46.243.6.170": 1,
"46.30.212.198": 1,
"46.38.24.209": 1,
"46.82.174.68": 1,
"49.2.123.56": 1,
"49.212.153.128": 1,
"50.116.6.162": 1,
"50.18.183.233": 1,
"50.57.11.12": 1,
"50.63.202.13": 1,
"50.87.148.140": 1,
"50.87.169.77": 1,
"50.93.207.101": 1,
"50.97.134.91": 1,
"54.174.40.182": 1,
"54.187.136.30": 1,
"54.187.39.38": 1,
"54.191.193.138": 1,
"54.200.3.32": 1,
"54.206.98.127": 1,
"54.209.238.28": 1,
"54.209.87.186": 1,
"54.218.38.198": 1,
"54.229.147.183": 1,
"54.235.199.154": 1,
"54.244.22.77": 1,
"54.246.169.32": 1,
"54.246.202.250": 1,
"54.68.166.130": 1,
"54.76.135.1": 1,
"54.83.51.191": 1,
"54.86.21.64": 1,
"54.86.223.202": 1,
"54.88.252.91": 1,
"59.124.74.28": 1,
"59.24.3.173": 1,
"61.54.28.6": 1,
"62.138.115.35": 1,
"62.75.221.31": 1,
"62.92.17.213": 1,
"64.14.72.41": 1,
"64.150.184.98": 1,
"64.22.110.34": 1,
"64.33.88.161": 1,
"64.33.99.47": 1,
"64.34.161.142": 1,
"64.50.179.133": 1,
"64.66.163.251": 1,
"64.79.69.250": 1,
"64.79.84.141": 1,
"64.91.254.97": 1,
"65.104.202.252": 1,
"65.160.219.113": 1,
"65.183.39.139": 1,
"66.146.2.241": 1,
"66.187.204.50": 1,
"66.206.11.194": 1,
"66.39.61.161": 1,
"66.45.252.237": 1,
"66.55.151.148": 1,
"66.85.134.186": 1,
"66.96.147.160": 1,
"67.137.227.11": 1,
"67.225.220.248": 1,
"68.71.58.18": 1,
"69.16.196.113": 1,
"69.167.172.162": 1,
"69.171.13.49": 1,
"69.174.244.221": 1,
"69.175.75.202": 1,
"69.195.124.90": 1,
"69.30.23.10": 1,
"69.50.192.218": 1,
"69.61.60.122": 1,
"70.42.243.33": 1,
"72.14.205.104": 1,
"72.14.205.99": 1,
"72.167.32.10": 1,
"72.20.110.50": 1,
"72.29.94.240": 1,
"72.32.4.243": 1,
"72.47.228.79": 1,
"72.5.1.109": 1,
"72.52.244.56": 1,
"74.117.117.122": 1,
"74.117.57.138": 1,
"74.124.195.73": 1,
"74.125.127.102": 1,
"74.125.155.102": 1,
"74.125.204.121": 1,
"74.125.39.102": 1,
"74.125.39.113": 1,
"74.207.236.174": 1,
"74.208.125.184": 1,
"74.220.215.67": 1,
"74.82.166.166": 1,
"75.98.175.166": 1,
"76.164.217.116": 1,
"77.4.7.92": 1,
"78.108.178.26": 1,
"78.140.172.33": 1,
"78.16.49.15": 1,
"78.24.135.99": 1,
"79.127.127.68": 1,
"79.136.125.49": 1,
"79.98.34.60": 1,
"80.190.96.26": 1,
"80.241.209.19": 1,
"80.241.92.180": 1,
"80.245.171.70": 1,
"80.70.184.118": 1,
"80.72.41.146": 1,
"80.82.117.209": 1,
"80.82.201.154": 1,
"80.92.117.132": 1,
"82.145.47.117": 1,
"83.125.118.122": 1,
"83.222.124.187": 1,
"83.222.5.171": 1,
"84.124.59.165": 1,
"85.111.18.138": 1,
"85.190.0.110": 1,
"85.25.171.103": 1,
"85.92.134.229": 1,
"87.106.57.209": 1,
"87.230.46.50": 1,
"88.198.69.101": 1,
"88.214.195.67": 1,
"89.108.118.129": 1,
"89.111.181.74": 1,
"89.186.95.11": 1,
"89.30.125.204": 1,
"89.31.55.106": 1,
"90.156.201.42": 1,
"91.121.245.154": 1,
"91.186.28.41": 1,
"91.198.129.47": 1,
"91.217.73.22": 1,
"91.221.37.35": 1,
"91.223.175.25": 1,
"91.238.30.54": 1,
"91.239.201.16": 1,
"92.53.106.175": 1,
"92.53.96.9": 1,
"92.63.110.174": 1,
"93.115.240.148": 1,
"93.158.121.72": 1,
"93.187.205.2": 1,
"93.46.8.89": 1,
"93.93.187.49": 1,
"94.136.188.30": 1,
"94.141.31.140": 1,
"94.23.147.142": 1,
"94.23.156.11": 1,
"94.23.193.224": 1,
"94.23.199.144": 1,
"95.163.95.47": 1,
"95.211.150.70": 1,
"95.211.229.156": 1,
"95.211.58.97": 1,
"95.85.22.163": 1,
"96.126.97.15": 1,
"96.127.172.221": 1,
"96.30.51.148": 1,
"97.74.80.22": 1,
"98.129.229.202": 1,
"98.158.152.159": 1,
"98.158.178.141": 1,
"104.200.31.226": 1,
"104.28.1.22": 1,
"104.28.14.112": 1,
"104.28.20.14": 1,
"104.28.30.59": 1,
"106.186.120.157": 1,
"106.187.39.80": 1,
"107.6.34.101": 1,
"108.168.215.230": 1,
"108.168.250.3": 1,
"108.179.196.77": 1,
"108.179.250.106": 1,
"108.61.250.218": 1,
"109.123.115.205": 1,
"109.206.173.212": 1,
"109.234.159.38": 1,
"109.71.81.130": 1,
"110.173.154.142": 1,
"110.74.163.40": 1,
"112.175.60.31": 1,
"113.11.194.190": 1,
"113.160.102.90": 1,
"118.145.17.184": 1,
"118.219.253.245": 1,
"118.5.49.6": 1,
"119.18.62.130": 1,
"119.235.57.82": 1,
"119.245.217.155": 1,
"119.9.94.83": 1,
"120.196.0.5": 1,
"120.198.243.113": 1,
"120.198.243.114": 1,
"120.198.243.115": 1,
"120.198.243.116": 1,
"120.198.243.14": 1,
"120.198.243.15": 1,
"120.198.243.151": 1,
"120.198.243.48": 1,
"120.198.243.52": 1,
"120.198.243.53": 1,
"120.197.234.71": 1,
"120.89.93.248": 1,
"122.214.2.171": 1,
"122.218.101.190": 1,
"123.126.249.238": 1,
"123.30.175.29": 1,
"123.50.49.171": 1,
"125.230.148.48": 1,
"127.0.0.2": 1,
"128.121.126.139": 1,
"128.199.180.162": 1,
"133.192.181.66": 1,
"133.242.165.24": 1,
"133.42.48.3": 1,
"137.135.129.175": 1,
"141.101.118.102": 1,
"141.8.195.47": 1,
"141.8.195.78": 1,
"141.8.225.80": 1,
"142.4.5.109": 1,
"144.76.106.232": 1,
"144.76.127.114": 1,
"144.76.21.13": 1,
"145.253.183.23": 1,
"147.87.244.32": 1,
"155.92.182.118": 1,
"157.205.32.64": 1,
"157.7.143.209": 1,
"159.106.121.75": 1,
"159.24.3.173": 1,
"159.253.20.179": 1,
"159.50.88.77": 1,
"162.159.243.101": 1,
"162.243.137.163": 1,
"162.253.33.134": 1,
"164.109.96.232": 1,
"164.138.221.68": 1,
"168.156.168.21": 1,
"169.132.13.103": 1,
"171.17.130.53": 1,
"171.25.204.141": 1,
"173.192.219.59": 1,
"173.194.127.144": 1,
"173.201.216.6": 1,
"173.224.209.14": 1,
"173.236.228.108": 1,
"173.244.184.10": 1,
"173.255.194.174": 1,
"173.255.230.196": 1,
"174.142.113.142": 1,
"174.142.22.25": 1,
"176.10.37.81": 1,
"176.57.216.145": 1,
"178.18.82.216": 1,
"178.236.177.77": 1,
"178.32.111.136": 1,
"178.32.156.59": 1,
"178.32.247.82": 1,
"178.33.212.162": 1,
"178.49.132.135": 1,
"178.62.242.156": 1,
"178.62.75.99": 1,
"178.79.182.248": 1,
"180.153.225.168": 1,
"180.179.171.121": 1,
"180.87.182.227": 1,
"181.224.155.41": 1,
"183.111.141.95": 1,
"184.154.10.146": 1,
"184.169.132.244": 1,
"184.72.253.232": 1,
"185.25.150.45": 1,
"185.53.61.50": 1,
"188.132.250.186": 1,
"188.165.31.24": 1,
"188.226.207.251": 1,
"188.40.108.13": 1,
"188.5.4.96": 1,
"189.163.17.5": 1,
"192.104.44.6": 1,
"192.121.151.106": 1,
"192.67.198.6": 1,
"192.95.98.202": 1,
"193.105.145.158": 1,
"193.169.66.88": 1,
"193.203.48.18": 1,
"193.234.233.149": 1,
"193.238.151.98": 1,
"193.239.132.44": 1,
"193.48.96.218": 1,
"193.57.244.117": 1,
"193.91.26.132": 1,
"194.149.250.20": 1,
"194.185.115.1": 1,
"194.187.94.6": 1,
"194.67.144.70": 1,
"195.146.235.33": 1,
"195.149.210.211": 1,
"195.154.243.151": 1,
"195.191.149.103": 1,
"195.2.88.68": 1,
"195.211.72.200": 1,
"195.43.82.170": 1,
"195.49.201.30": 1,
"195.50.195.15": 1,
"195.74.38.62": 1,
"195.74.78.21": 1,
"195.77.241.242": 1,
"195.8.125.64": 1,
"197.4.4.12": 1,
"198.143.143.36": 1,
"198.57.205.133": 1,
"198.57.222.88": 1,
"198.58.124.68": 1,
"199.167.31.142": 1,
"199.21.68.222": 1,
"199.79.63.83": 1,
"200.229.206.115": 1,
"200.98.234.14": 1,
"201.77.211.143": 1,
"202.106.1.2": 1,
"202.181.7.85": 1,
"202.218.219.10": 1,
"202.6.96.25": 1,
"203.113.173.22": 1,
"203.133.238.172": 1,
"203.161.230.171": 1,
"203.199.57.81": 1,
"203.98.7.65": 1,
"206.108.51.91": 1,
"206.113.150.70": 1,
"207.12.88.98": 1,
"207.126.59.27": 1,
"207.140.149.247": 1,
"207.58.177.166": 1,
"208.109.138.55": 1,
"208.109.205.232": 1,
"208.112.102.122": 1,
"208.43.134.107": 1,
"208.43.33.194": 1,
"208.56.31.43": 1,
"208.73.211.164": 1,
"208.86.154.112": 1,
"208.93.0.150": 1,
"209.116.71.109": 1,
"209.126.106.182": 1,
"209.141.48.35": 1,
"209.145.54.50": 1,
"209.188.7.186": 1,
"209.204.148.22": 1,
"209.220.30.174": 1,
"209.235.224.25": 1,
"209.36.73.33": 1,
"209.43.1.130": 1,
"209.56.158.42": 1,
"209.62.154.94": 1,
"209.85.229.138": 1,
"210.175.255.154": 1,
"210.209.110.199": 1,
"210.230.192.183": 1,
"211.139.136.73": 1,
"211.139.204.59": 1,
"211.43.203.33": 1,
"211.5.133.18": 1,
"211.8.69.27": 1,
"211.94.66.147": 1,
"212.227.98.130": 1,
"212.45.52.219": 1,
"212.68.42.67": 1,
"212.77.104.29": 1,
"213.108.66.21": 1,
"213.133.111.102": 1,
"213.169.251.35": 1,
"213.174.158.108": 1,
"213.186.33.5": 1,
"213.19.161.141": 1,
"213.207.85.148": 1,
"213.238.166.227": 1,
"216.12.205.2": 1,
"216.139.213.144": 1,
"216.178.241.101": 1,
"216.198.246.103": 1,
"216.221.188.182": 1,
"216.234.179.13": 1,
"216.250.115.144": 1,
"216.38.0.92": 1,
"216.70.88.29": 1,
"216.92.58.37": 1,
"217.160.42.85": 1,
"217.172.183.9": 1,
"217.30.184.161": 1,
"218.44.251.212": 1,
"220.110.150.90": 1,
"220.247.224.8": 1,
"220.250.64.24": 1,
"221.213.49.149": 1,
"221.179.46.190": 1,
"221.179.46.194": 1,
"221.8.69.27": 1,
"222.122.56.219": 1,
"243.185.187.3": 1,
"243.185.187.30": 1,
"243.185.187.39": 1,
"249.129.46.48": 1,
"253.157.14.165": 1,
"255.255.255.255": 1
}

@ -1,96 +0,0 @@
package main
import (
//"fmt"
"errors"
"github.com/miekg/dns"
"net"
"time"
)
var ip_region []*net.IPNet
type res struct {
m *dns.Msg
err error
}
func _query(m *dns.Msg, s *UpstreamServer, c chan *res) {
res1 := make(chan *res)
go query_one(s, m, res1)
select {
case r := <-res1:
c <- r
case <-time.After(600 * time.Millisecond):
c <- &res{err: errors.New("timed out")}
}
}
func query(m *dns.Msg) *dns.Msg {
resch := make(chan *res, len(DefaultServer))
for _, s := range DefaultServer {
go _query(m, s, resch)
}
delayed := []*dns.Msg{}
slen := len(DefaultServer)
for i := 0; i < slen; i++ {
r := <-resch
r1 := *r
if r1.err != nil {
logger.Error("error %s\n", r1.err.Error())
continue
}
// drop the result with no error but has an empty result
if r1.m.Rcode == dns.RcodeSuccess &&
len(r1.m.Answer) == 0 {
continue
}
// drop blacklist
if in_blacklist(r1.m) {
continue
}
// check ip region
if answer_in_region(r1.m, ip_region) {
return r1.m
} else {
delayed = append(delayed, r1.m)
}
}
if len(delayed) == 0 {
logger.Error("empty delayed list")
return nil
}
// return first ok result
for _, m1 := range delayed {
if m1.Rcode == dns.RcodeSuccess {
return m1
}
}
// return NXDOMAIN result
for _, m1 := range delayed {
if m1.Rcode == dns.RcodeNameError {
return m1
}
}
// errror
return nil
}
func query_one(srv *UpstreamServer, m *dns.Msg, ch chan *res) {
m1, err := srv.query(m)
select {
case ch <- &res{m1, err}:
}
}

@ -0,0 +1,33 @@
package main
import (
"io/ioutil"
"github.com/go-yaml/yaml"
)
type conf struct {
UpstreamServers []string `yaml:"upstream_servers"`
BootstrapServers []string `yaml:"bootstrap_servers"`
Listen []listen `yaml:"listen"`
UpstreamTimeout int `yaml:"upstream_timeout"`
UpstreamInsecure bool `yaml:"upstream_insecure"`
}
type listen struct {
Addr string `yaml:"addr"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
}
func loadConfig(f string) (*conf, error) {
data, err := ioutil.ReadFile(f)
if err != nil {
return nil, err
}
var c conf
if err := yaml.Unmarshal(data, &c); err != nil {
return nil, err
}
return &c, nil
}

@ -0,0 +1,40 @@
# upstream servers
# support DoH(DNS over https/http2), tcp, udp and tls(dns over tls)
upstream_servers:
- https://cloudflare-dns.com/dns-query
- tcp://1.0.0.1:53
- udp://1.0.0.1:53
- tls://cloudflare-dns:853
# bootstrap dns server,
# this used to resolve the domain name used in Doh and TLS,
# not needed if you don't use DoH and tls
bootstrap_severs:
- tcp://1.0.0.1:53
- udp://1.0.0.1:53
# listen ports
listen:
-
addr: tcp://0.0.0.0:1053
cert:
key:
-
addr: udp://0.0.0.0:1053
cert:
key:
-
addr: https://0.0.0.0:1054/dns-query
cert: server.crt
key: server.key
-
addr: tls://0.0.0.1:1055
cert: server.crt
key: server.key
# timeout for upstream server, second
upstream_timeout: 3
# check upstream certificate or not
# if you use self signed certificate, set this to true
upstream_insecure: true

@ -1,3 +0,0 @@
{
"211.139.136.73":1
}

@ -1,8 +0,0 @@
bind = :53 # the address bind to
configUpdateInterval = 0
upstream = udp:114.114.114.114:53
server = domains.txt,udp:8.8.8.8:53
server = domains.txt,tcp:4.2.2.2:53
#logfile = error.log
blacklist = blacklist.txt
debug = true

@ -1,107 +0,0 @@
{
"xunlei.com":1,
"getlantern.org":1,
"greatfire.org":1,
"tietuku.com":1,
"dw.com":1,
"igfw.net":1,
"rfa.org":1,
"intercomcdn.com":1,
"gorillatoolkit.org":1,
"shadowsocks.org":1,
"openvpn.net":1,
"golang.org":1,
"reuters.com":1,
"lwgod.com":1,
"buzzfed.com":1,
"chinagfw.org":1,
"feedburner.com":1,
"bbc.com":1,
"fanqianghou.com":1,
"qiwen.lu":1,
"dropbox.com":1,
"laonanren.com":1,
"wikipedia.org":1,
"powerapple.com":1,
"ift.tt":1,
"bowenpress.com":1,
"assets-cdn.github.com":1,
"bx.tl":1,
"googlecode.com":1,
"gist.github.com":1,
"googleapis.com":1,
"sharethis.com":1,
"googletagservices.com":1,
"ablwang.com":1,
"githubusercontent.com":1,
"wordpress.com":1,
"linkedin.com":1,
"wsj.net":1,
"wsj.com":1,
"flickr.com":1,
"w3schools.com":1,
"hikinggfw.org":1,
"imgur.com":1,
"akamai.net":1,
"outbrain.com":1,
"amazonaws.com":1,
"wp.com":1,
"goo.gl":1,
"aboluowang.com":1,
"youmaker.com":1,
"gravatar.com":1,
"cmake.org":1,
"sourceforge.net":1,
"idv.tw":1,
"ntdtv.com":1,
"sourceforge.net":1,
"fbcdn.net":1,
"dw.de":1,
"imgur.com":1,
"bit.ly":1,
"fw.cm":1,
"cdninstagram.com":1,
"udn.com.tw":1,
"instagram.com":1,
"ltn.com.tw":1,
"wp.com":1,
"boxun.com":1,
"xys.org":1,
"allinfa.com":1,
"facebook.net":1,
"atgfw.org":1,
"pao-pao.net":1,
"torproject.org":1,
"nytimes.com":1,
"ifanqiang.com":1,
"googlevideo.com":1,
"freeweibo.com":1,
"ggpht.com":1,
"ytimg.com":1,
"renminbao.com":1,
"appspot.com":1,
"chinadigitaltimes.net":1,
"letscorp.net":1,
"t.co":1,
"dongtaiwang.com":1,
"kanzhongguo.com":1,
"youtube.com":1,
"haxx.se":1,
"feedly.com":1,
"peacehall.com":1,
"voachinese.com":1,
"voanews.com":1,
"secretchina.com":1,
"epochtimes.com":1,
"gstatic.com":1,
"facebook.com":1,
"blogblog.com":1,
"blogspot.com":1,
"googleusercontent.com":1,
"blogger.com":1,
"chromium.org":1,
"twimg.com":1,
"twitter.com":1,
"t66y.com":1,
"google.com":1
}

@ -1,82 +0,0 @@
command line options
=====================
###bind###
special the addres the dns server listen to
example:
`-bind 0.0.0.0:53`,
`-bind :53`,
`-bind 127.0.0.1:53`
###server###
special a filter file and the upstream dns server to use
format
**file_name**,**proto**:**addr**:**port**
**file_name** is the file name contains the domain list
**proto** is the upstream dns server protocol, `tcp` or `udp`
**addr** is the ip address of upstream dns server
**port** is the upstream dns server port
this options can special multipe times
example:
`-server domain1.json:udp:8.8.8.8:53`,
`-server domain1.json:tcp:4.2.2.2:53`,
`-server domin2.json:udp:49.32.34.44:5353`
see [example filter file](ex_config.md#filter-file)
###upstream###
special the default upstream dns server
format
**proto**:**addr**:**port**
**proto** is the upstream dns server protocol, `tcp` or `udp`
**addr** is the ip address of upstream dns server
**port** is the upstream dns server port
example:
`-upstream udp:10.10.1.1:53`
###logfile###
special the file name the log save to
example:
`-logfile /var/log/gdns.log`
###debug###
output the debug log or not, default false
this options is only used for debugging
###blacklist###
special the blacklist file
if the reply dns message contains the ip in the blacklist, the message will be dropped
example:
`-blacklist fakeip.json`
see [example black list](ex_config.md#blacklist-file)

@ -1,61 +0,0 @@
Configure example
=============
Configure file
======
Use `gdns -h > config.ini` generate a example configure file
a configure file like this:
```conf
bind = :53 # the address bind to
blacklist = # the blacklist file
configUpdateInterval = 0 # Update interval for re-reading config file set via -config flag. Zero disables config file re-reading.
debug = false # output debug log, default false
logfile = error.log # the logfile, default stdout
server = filter1.txt,udp:8.8.8.8:53 # special the filter and the upstream server to use when match
# format:
# FILTER_FILE_NAME,PROTOCOL:SERVER_NAME:PORT
# example:
# filter1.json,udp:8.8.8.8:53
# means the domains in the filter1.json will use the google dns server by udp
# you can specail multiple filter and upstream server
#
upstream = udp:114.114.114.114:53 # the default upstream server to use
```
comamnd
`gdns -config dns.ini`
use the dns.ini as a configure file
Filter file
===========
The filter file is a domains name list
command line
`--server domain1.json,udp:8.8.8.8:53`
means the domain name listed in domoin1.json will use 8.8.8.8 as the upstream server through udp
a filter file like this
```json
{
"twitter.com":1,
"facebook.com":1,
"google.com":1
}
```
you can special multiple filter file and upstream dns server
Blacklist file
=============
The blacklist file contains the ip that the message will be dropped when the ip dispeared in the upstream server reply
the blacklist file like this
```json
{
"113.123.21.43":1,
"31.53.23.12":1
}
```

@ -1,54 +0,0 @@
# gdns
a dns proxy server write by go
gdns much like dnsmasq or chinadns, but it can run on windows.
Features
========
support different domains use different upstream dns servers
support contact to the upstream dns server by tcp or udp
support blacklist list to block the fake ip
Install
=======
```bash
# get the depended library
go get github.com/miekg/dns
go get github.com/vharitonsky/iniflags
git clone https://github.com/fangdingjun/gdns
cd gdns
go build
# generate a sample config file
./gdns -dumpflags > dns.ini
# edit the dns.ini and run, need root privileges to bind on port 53
sudo ./gdns -config dns.ini
# test it
dig @localhost twitter.com
```
Arguments
===========
use `gdns -h` to show the command line arguments.
all arguments can specialed in config file or in command line.
there is a sample file in the `config_sample/` directory.
Third-part library
==================
use
[dns](https://github.com/miekg/dns)
library to parse the dns message
use
[iniflags](https://github.com/vharitonsky/iniflags)
library to process the command line arguments and the config file

File diff suppressed because it is too large Load Diff

@ -1,36 +0,0 @@
#!/usr/bin/env python
import math
import netaddr
import urllib2
url = "http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest"
data = urllib2.urlopen(url).read()
with open("delegated-apnic-lastest", "w") as fp:
fp.write(data)
results = []
with open("delegated-apnic-latest") as fp:
for line in fp:
# format
# apnic|CN|ipv4|1.0.0.0|256|2012232|allocated
l = line.strip()
if not l:
continue
if l[0] == '#':
continue
lns = l.split("|")
if len(lns) != 7:
continue
if lns[2] != 'ipv4':
continue
if lns[1] != 'CN':
continue
ip = lns[3]
mask = 32-int(math.log(int(lns[4]), 2))
cidr = "%s/%d" % (ip, mask)
results.append(cidr)
r = netaddr.cidr_merge(results)
with open("region_cn.txt", "w") as fp:
for a in r:
fp.write("%s,%s,%d\n" % (a.ip,a.netmask, a.prefixlen))

@ -1,132 +0,0 @@
package main
import (
"bufio"
"errors"
"fmt"
"github.com/miekg/dns"
"net"
"os"
"path/filepath"
"runtime"
"strings"
)
type HostRecord struct {
/* RR record */
rr dns.RR
/* type, dns.A or dns.AAAA */
t uint16
}
type Hosts map[string][]HostRecord
/*
get special type of record form Hosts
*/
func (h Hosts) Get(n string, t uint16) dns.RR {
n1 := dns.Fqdn(n)
if hr, ok := h[n1]; ok {
for _, v := range hr {
if v.t == t {
return v.rr
}
}
}
return nil
}
/*
read and parse the hosts file
*/
func ReadHosts(fn string) (Hosts, error) {
fp, err := os.Open(fn)
if err != nil {
return nil, err
}
defer fp.Close()
hts := Hosts{}
bf := bufio.NewReader(fp)
for {
var t uint16
bline, _, err := bf.ReadLine()
if err != nil {
break
}
sline := string(bline)
sline = strings.TrimSpace(sline)
/* empty line */
if sline == "" {
continue
}
/* comment */
if sline[0] == '#' {
continue
}
lns := strings.Fields(sline)
if len(lns) < 2 {
return nil, errors.New(fmt.Sprintf("invalid hosts line: %s", sline))
}
ip := net.ParseIP(lns[0])
if ip == nil {
return nil, errors.New(fmt.Sprintf("invalid ip: %s", lns[0]))
}
if strings.Index(lns[0], ".") != -1 {
t = dns.TypeA
} else {
t = dns.TypeAAAA
}
for _, dn := range lns[1:] {
dd := dns.Fqdn(strings.TrimSpace(dn))
/* ignore space */
if dd == "." {
continue
}
s := fmt.Sprintf("%s 36000 IN %s %s", dd,
dns.TypeToString[t], lns[0])
r, err := dns.NewRR(s)
if err != nil {
return nil, err
}
if _, ok := hts[dd]; ok {
hts[dd] = append(hts[dd], HostRecord{r, t})
} else {
hts[dd] = []HostRecord{HostRecord{r, t}}
}
}
}
return hts, nil
}
/*
return the path of hosts file
*/
func GetHost() string {
var p string
if runtime.GOOS == "windows" {
p = filepath.Join(os.Getenv("SYSTEMROOT"),
"system32/drivers/etc/hosts")
} else {
p = "/etc/hosts"
}
return filepath.Clean(p)
}

@ -1,40 +0,0 @@
package main
import (
"github.com/miekg/dns"
"testing"
//"fmt"
)
func TestReadHosts(t *testing.T) {
a, err := ReadHosts("testdata/hosts.txt")
if err != nil {
t.Error(err)
}
for k, v := range a {
for _, v1 := range v {
t.Logf("%s: %s\n", k, v1.rr.String())
}
}
r1 := a.Get("localhost", dns.TypeA)
if dnsa, ok := r1.(*dns.A); ok {
if dnsa.A.String() != "127.0.0.1" {
t.Errorf("get failed a\n")
}
} else {
t.Errorf("type not a\n")
}
r2 := a.Get("localhost", dns.TypeAAAA)
if dnsaa, ok := r2.(*dns.AAAA); ok {
if dnsaa.AAAA.String() != "::1" {
t.Errorf("get failed aaaa\n")
}
} else {
t.Errorf("type not aaaa\n")
}
}
func TestGetHost(t *testing.T) {
t.Logf("host: %s\n", GetHost())
}

@ -1,89 +0,0 @@
package main
import (
"bufio"
"fmt"
"github.com/miekg/dns"
"log"
"net"
"os"
"strings"
"unicode"
)
// parse ip range region file
// format
// ip, netmask, prefixlen
func parse_net(fn string) []*net.IPNet {
fp, err := os.Open(fn)
if err != nil {
log.Fatal(err)
return []*net.IPNet{}
}
defer fp.Close()
nets := []*net.IPNet{}
reader := bufio.NewReader(fp)
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
line = strings.Trim(line, "\r\n")
fnds := strings.FieldsFunc(line, func(c rune) bool {
if unicode.IsSpace(c) {
return true
}
if c == ',' {
return true
}
return false
})
if len(fnds) != 3 {
continue
}
if _, net1, err := net.ParseCIDR(
fmt.Sprintf("%s/%s", fnds[0], fnds[2])); err == nil {
nets = append(nets, net1)
} else {
log.Fatal(err)
}
}
return nets
}
// test ip in ip range region
func in_region(ip net.IP, nets []*net.IPNet) bool {
for _, n := range nets {
if n.Contains(ip) {
return true
}
}
return false
}
// test dns reply A or AAAA in special ip range region
func answer_in_region(m *dns.Msg, nets []*net.IPNet) bool {
// no region loaded, return true
if len(nets) == 0 {
return true
}
for _, rr := range m.Answer {
if a, ok := rr.(*dns.A); ok {
if in_region(a.A, nets) {
return true
}
}
if aaaa, ok := rr.(*dns.AAAA); ok {
if in_region(aaaa.AAAA, nets) {
return true
}
}
}
return false
}

@ -1,83 +0,0 @@
package main
import (
//"fmt"
"github.com/miekg/dns"
"testing"
"time"
//"os"
)
func TestParseNet(t *testing.T) {
nets := parse_net("region_cn.txt")
t.Logf("get %d networks\n", len(nets))
t.Logf("1st %s\n", nets[0].String())
}
func TestQuery(t *testing.T) {
ip_region = parse_net("region_cn.txt")
var c *dns.Client
for _, srv := range []string{
"tcp:114.114.114.114:53",
"udp:8.8.8.8:53",
"udp:192.168.41.1:53",
"udp:4.2.2.2:53",
} {
proto, addr, err := parse_addr(srv)
if err != nil {
t.Error(err)
}
if proto == "tcp" {
c = client_tcp
} else {
c = client_udp
}
upsrv := &UpstreamServer{
Addr: addr,
Proto: proto,
client: c,
}
DefaultServer = append(DefaultServer, upsrv)
}
blacklist_file = "blacklist.txt"
a, err := load_domain(blacklist_file)
if err != nil {
t.Log(err)
} else {
Blacklist_ips = a
}
logger = NewLogger("", true)
for _, dn := range []string{
"www.google.com",
"www.sina.com.cn",
"www.taobao.com",
"www.ifeng.com",
"twitter.com",
"www.facebook.com",
"plus.google.com",
"drive.google.com",
"dongtaiwang.com",
"www.ratafee.nl",
"cc.ratafee.nl",
"noddcade.xx.ffs.aafde",
"ndfddcade.xx.ffs.aafde",
"sddf32dsf.comd.ffdasdf.fdsd3eaaaaa",
"www.google.com.hk",
} {
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(dn), dns.TypeA)
t1 := time.Now()
m1 := query(m)
t2 := time.Now()
if m1 == nil {
t.Errorf("query %s failed", dn)
} else {
t.Logf("query time: %s\n", t2.Sub(t1))
t.Logf("result of %s\n", dn)
for _, a1 := range m1.Answer {
t.Logf("%s\n", a1)
}
//.Printf("\n")
}
}
}

@ -1,51 +0,0 @@
package main
import (
"log"
"os"
)
//var LogLevel int
type LogOut struct {
//out *os.File
debug bool
dbglog *log.Logger
errlog *log.Logger
infolog *log.Logger
}
func NewLogger(logfile string, debug bool) *LogOut {
var out *os.File
var err error
if logfile != "" {
out, err = os.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Println(err)
out = os.Stdout
}
} else {
out = os.Stdout
}
return &LogOut{
debug,
log.New(out, "DEBUG: ", log.LstdFlags),
log.New(out, "ERROR: ", log.LstdFlags),
log.New(out, "INFO: ", log.LstdFlags),
}
}
func (l *LogOut) Debug(format string, args ...interface{}) {
if l.debug {
l.dbglog.Printf(format, args...)
}
}
func (l *LogOut) Error(format string, args ...interface{}) {
l.errlog.Printf(format, args...)
}
func (l *LogOut) Info(format string, args ...interface{}) {
l.infolog.Printf(format, args...)
}

@ -0,0 +1,59 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/fangdingjun/go-log"
"github.com/fangdingjun/go-log/formatters"
"github.com/fangdingjun/go-log/writers"
)
func main() {
var configfile string
var logFileCount int
var logFileSize int64
var loglevel string
var logfile string
flag.StringVar(&logfile, "log_file", "", "log file, default stdout")
flag.IntVar(&logFileCount, "log_count", 10, "max count of log to keep")
flag.Int64Var(&logFileSize, "log_size", 10, "max log file size MB")
flag.StringVar(&loglevel, "log_level", "INFO",
"log level, values:\nOFF, FATAL, PANIC, ERROR, WARN, INFO, DEBUG")
flag.StringVar(&configfile, "c", "", "config file")
flag.Parse()
if logfile != "" {
log.Default.Out = &writers.FixedSizeFileWriter{
MaxCount: logFileCount,
Name: logfile,
MaxSize: logFileSize * 1024 * 1024,
}
}
if loglevel != "" {
lv, err := log.ParseLevel(loglevel)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
log.Default.Level = lv
}
log.Default.Formatter = &formatters.TextFormatter{
TimeFormat: "2006-01-02 15:04:05.000"}
cfg, err := loadConfig(configfile)
if err != nil {
log.Fatal(err)
}
if cfg.UpstreamTimeout == 0 {
cfg.UpstreamTimeout = 5
}
initDNSClient(cfg)
log.Debugf("%+v", cfg)
makeServers(cfg)
select {}
}

@ -1,8 +0,0 @@
site_name: gdns
docs_dir: docs
pages:
- Usage: 'index.md'
- Command line options: 'command_line.md'
- Config example: 'ex_config.md'
theme: readthedocs

@ -1,97 +0,0 @@
package main
import (
"encoding/json"
"errors"
. "fmt"
"github.com/miekg/dns"
"io/ioutil"
"log"
"net"
"strconv"
"strings"
)
func load_domain(f string) (Kv, error) {
var m1 Kv
c, err := ioutil.ReadFile(f)
if err != nil {
return nil, err
}
err = json.Unmarshal(c, &m1)
if err != nil {
return nil, err
}
return m1, nil
}
func parse_addr(s string) (string, string, error) {
s2 := strings.SplitN(s, ":", 2)
if len(s2) != 2 {
msg := Sprintf("error %s not well formatted", s)
err := errors.New(msg)
return "", "", err
}
if s2[0] != "tcp" && s2[0] != "udp" {
msg := Sprintf("invalid %s, only tcp or udp allowed", s2[0])
err := errors.New(msg)
return "", "", err
}
host, port, err := net.SplitHostPort(s2[1])
if err != nil {
return "", "", err
}
/* check host */
ip := net.ParseIP(host)
if ip == nil {
return "", "", errors.New(Sprintf("invalid host %s", host))
}
/* check port */
_, err = strconv.Atoi(port)
if err != nil {
return "", "", err
}
return s2[0], s2[1], nil
}
func parse_server(s string) (*UpstreamServer, error) {
s1 := strings.Split(s, ",")
if len(s1) != 2 {
msg := Sprintf("error %s not well formatted", s)
err := errors.New(msg)
return nil, err
}
proto, addr, err := parse_addr(s1[1])
if err != nil {
log.Fatal(err)
}
var c *dns.Client
if proto == "tcp" {
c = client_tcp
} else {
c = client_udp
}
d, err := load_domain(s1[0])
if err != nil {
log.Print(err)
}
var sv *UpstreamServer = &UpstreamServer{
Addr: addr,
domains: d,
client: c,
Proto: proto,
}
return sv, nil
}

@ -1,61 +0,0 @@
package main
import (
"testing"
)
func TestParseAddr(t *testing.T) {
var err error
var s1, s2 string
s1, s2, err = parse_addr("udp:123.2.3.4:321")
t.Logf("parse result %s, %s\n", s1, s2)
if err != nil {
t.Fail()
}
s1, s2, err = parse_addr("tcp:123.2.3.4:321")
t.Logf("parse result %s, %s\n", s1, s2)
if err != nil {
t.Fail()
}
_, _, err = parse_addr("1.2.3.4:333")
t.Log(err)
if err == nil {
t.Fail()
}
_, _, err = parse_addr("cc:1.2.3.4:33")
t.Log(err)
if err == nil {
t.Fail()
}
_, _, err = parse_addr("tcp:1.2.3.4:33:33")
t.Log(err)
if err == nil {
t.Fail()
}
}
func TestParseServer(t *testing.T) {
_, err := parse_server("aa.txt:tcp:1.2.3.4:32")
t.Log(err)
if err == nil {
t.Fail()
}
sv, err := parse_server("noexists.txt,tcp:1.2.3.4:32")
if err != nil {
t.Log(err)
t.Fail()
}
if sv.Addr != "1.2.3.4:32" {
t.Fail()
}
if sv.Proto != "tcp" {
t.Fail()
}
if sv.domains != nil {
t.Fail()
}
}

File diff suppressed because it is too large Load Diff

@ -1,231 +1,160 @@
/*
gdns is a dns proxy server write by go.
gdns much like dnsmasq or chinadns, but it can run on windows.
Features:
support different domains use different upstream dns servers
support contact to the upstream dns server by tcp or udp
support blacklist list to block the fake ip
Usage:
generate a config file and edit it
$ gdns -dumpflags > dns.ini
run it
$ sudo gdns -config dns.ini
*/
package main
import (
"fmt"
lru "github.com/hashicorp/golang-lru"
"github.com/miekg/dns"
"log"
"strings"
)
var client_udp *dns.Client = &dns.Client{}
var client_tcp *dns.Client = &dns.Client{Net: "tcp"}
var Servers []*UpstreamServer = nil
var logger *LogOut = nil
"crypto/tls"
"net"
"net/url"
"strconv"
var Blacklist_ips Kv = nil
"github.com/fangdingjun/go-log"
)
var debug bool = false
type server struct {
addr *url.URL
cert string
key string
upstreams []*url.URL
bootstrap []*url.URL
}
var dns_cache *lru.Cache
func (srv *server) serve() {
switch srv.addr.Scheme {
case "udp":
srv.serveUDP()
case "tcp":
srv.serveTCP()
case "tls":
srv.serveTLS()
case "https":
srv.serveHTTPS()
default:
log.Fatalf("unsupported type %s", srv.addr.Scheme)
}
}
var hostfile string = ""
var record_hosts Hosts = nil
func (srv *server) serveUDP() {
ip, port, _ := net.SplitHostPort(srv.addr.Host)
_ip := net.ParseIP(ip)
_port, _ := strconv.Atoi(port)
func in_blacklist(m *dns.Msg) bool {
if Blacklist_ips == nil {
return false
udpconn, err := net.ListenUDP("udp", &net.UDPAddr{IP: _ip, Port: _port})
if err != nil {
log.Fatalf("listen udp error", err)
}
if m == nil {
return false
}
defer udpconn.Close()
for _, rr := range m.Answer {
/* A */
if t, ok := rr.(*dns.A); ok {
ip := t.A.String()
if _, ok1 := Blacklist_ips[ip]; ok1 {
logger.Debug("%s is in blacklist\n", ip)
return true
buf := make([]byte, 4096)
for {
n, addr, err := udpconn.ReadFrom(buf)
if err != nil {
log.Debugln(err)
break
}
buf1 := make([]byte, n)
copy(buf1, buf[:n])
go srv.handleUDP(buf1, addr, udpconn)
}
}
/* AAAA */
if t, ok := rr.(*dns.AAAA); ok {
ip := t.AAAA.String()
if _, ok1 := Blacklist_ips[ip]; ok1 {
logger.Debug("%s is in blacklist\n", ip)
return true
func (srv *server) serveTCP() {
l, err := net.Listen("tcp", srv.addr.Host)
if err != nil {
log.Fatalln("listen tcp", err)
}
defer l.Close()
log.Debugf("listen tcp://%s", l.Addr().String())
for {
conn, err := l.Accept()
if err != nil {
log.Debugln(err)
break
}
go srv.handleTCP(conn)
}
return false
}
func handleRoot(w dns.ResponseWriter, r *dns.Msg) {
var err error
var res *dns.Msg
domain := r.Question[0].Name
/*
reply from hosts
*/
if record_hosts != nil {
rr := record_hosts.Get(domain, r.Question[0].Qtype)
if rr != nil {
msg := new(dns.Msg)
msg.SetReply(r)
msg.Answer = append(msg.Answer, rr)
w.WriteMsg(msg)
logger.Debug("%s query %s %s %s, reply from hosts\n",
w.RemoteAddr(),
domain,
dns.ClassToString[r.Question[0].Qclass],
dns.TypeToString[r.Question[0].Qtype],
)
return
}
}
key := fmt.Sprintf("%s_%s", domain, dns.TypeToString[r.Question[0].Qtype])
if enable_cache {
// reply from cache
if a, ok := dns_cache.Get(key); ok {
msg := new(dns.Msg)
msg.SetReply(r)
aa := strings.Split(a.(string), "|")
for _, a1 := range aa {
rr, _ := dns.NewRR(a1)
if rr != nil {
msg.Answer = append(msg.Answer, rr)
func (srv *server) serveTLS() {
cert, err := tls.LoadX509KeyPair(srv.cert, srv.key)
if err != nil {
log.Fatalln("load certificate failed", err)
}
l, err := tls.Listen("tcp", srv.addr.Host,
&tls.Config{
Certificates: []tls.Certificate{cert},
//NextProtos: []string{"h2"},
})
if err != nil {
log.Fatalln("listen tls", err)
}
w.WriteMsg(msg)
logger.Debug("%s query %s %s %s, reply from cache\n",
w.RemoteAddr(),
domain,
dns.ClassToString[r.Question[0].Qclass],
dns.TypeToString[r.Question[0].Qtype],
)
return
defer l.Close()
log.Debugf("listen tls://%s", l.Addr().String())
for {
conn, err := l.Accept()
if err != nil {
log.Debugln("tls accept", err)
break
}
go srv.handleTCP(conn)
}
}
// forward to upstream server
for i := 0; i < 2; i++ {
for _, sv := range Servers {
if sv.match(domain) {
res, err = sv.query(r)
func (srv *server) serveHTTPS() {
cert, err := tls.LoadX509KeyPair(srv.cert, srv.key)
if err != nil {
logger.Error("%s", err)
continue
}
logger.Debug("%s query %s %s %s, forward to %s:%s, %s\n",
w.RemoteAddr(),
domain,
dns.ClassToString[r.Question[0].Qclass],
dns.TypeToString[r.Question[0].Qtype],
sv.Proto, sv.Addr,
dns.RcodeToString[res.Rcode],
)
if res.Rcode == dns.RcodeSuccess &&
!in_blacklist(res) && len(res.Answer) > 0 {
if enable_cache {
// add to cache
v := []string{}
for _, as := range res.Answer {
v = append(v, as.String())
}
dns_cache.Add(key, strings.Join(v, "|"))
}
w.WriteMsg(res)
return
}
}
log.Fatalln("load certificate failed", err)
}
l, err := tls.Listen("tcp", srv.addr.Host,
&tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: []string{"h2"},
})
if err != nil {
log.Fatalln("listen https", err)
}
// fallback to default upstream server
for i := 0; i < 2; i++ {
logger.Debug("%s query %s %s %s, use default server\n",
w.RemoteAddr(),
domain,
dns.ClassToString[r.Question[0].Qclass],
dns.TypeToString[r.Question[0].Qtype],
)
res := query(r)
if res != nil {
//logger.Debug("get: %s", res)
if enable_cache && res.Rcode == dns.RcodeSuccess &&
len(res.Answer) > 0 {
// add to cache
v := []string{}
for _, as := range res.Answer {
v = append(v, as.String())
}
dns_cache.Add(key, strings.Join(v, "|"))
}
w.WriteMsg(res)
return
defer l.Close()
log.Debugf("listen https://%s", l.Addr().String())
for {
conn, err := l.Accept()
if err != nil {
log.Debugln("https accept", err)
break
}
go srv.handleHTTPSConn(conn)
}
dns.HandleFailed(w, r)
}
func main() {
parse_flags()
var err error
if enable_cache {
// create cache
dns_cache, err = lru.New(1000)
func makeServers(c *conf) {
upstreams := []*url.URL{}
bootstraps := []*url.URL{}
for _, a := range c.UpstreamServers {
u, err := url.Parse(a)
if err != nil {
log.Fatal(err)
}
upstreams = append(upstreams, u)
}
dns.HandleFunc(".", handleRoot)
logger = NewLogger(logfile, debug)
logger.Info("Listen on %s\n", bind_addr)
go func() {
/* listen tcp */
err := dns.ListenAndServe(bind_addr, "tcp", nil)
for _, a := range c.BootstrapServers {
u, err := url.Parse(a)
if err != nil {
log.Fatal(err)
}
}()
bootstraps = append(bootstraps, u)
}
/* listen udp */
err = dns.ListenAndServe(bind_addr, "udp", nil)
for _, l := range c.Listen {
u, err := url.Parse(l.Addr)
if err != nil {
log.Fatal(err)
}
srv := &server{
addr: u,
cert: l.Cert,
key: l.Key,
upstreams: upstreams,
bootstrap: bootstraps,
}
go srv.serve()
}
}

@ -0,0 +1,78 @@
package main
import (
"crypto/tls"
"io/ioutil"
"net"
"net/http"
"github.com/fangdingjun/go-log"
"github.com/fangdingjun/nghttp2-go"
"github.com/miekg/dns"
)
func (srv *server) handleHTTPSConn(c net.Conn) {
defer c.Close()
tlsconn := c.(*tls.Conn)
if err := tlsconn.Handshake(); err != nil {
log.Errorln("handshake", err)
return
}
state := tlsconn.ConnectionState()
if state.NegotiatedProtocol != "h2" {
log.Errorln("http2 is needed")
return
}
h2conn, err := nghttp2.Server(tlsconn, srv)
if err != nil {
log.Errorf("create http2 error: %s", err)
return
}
h2conn.Run()
}
func (srv *server) handleHTTP2Req(w http.ResponseWriter, r *http.Request) {
ctype := r.Header.Get("content-type")
if ctype != "application/dns-message" {
http.Error(w, "dns message is required", http.StatusBadRequest)
return
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Errorln("read request body", err)
w.WriteHeader(http.StatusBadRequest)
return
}
msg := new(dns.Msg)
if err := msg.Unpack(data); err != nil {
log.Errorln("parse dns message", err)
return
}
reply := false
for _, up := range srv.upstreams {
m, err := queryUpstream(msg, up)
if err == nil {
w.Header().Set("content-type", "application/dns-message")
w.WriteHeader(http.StatusOK)
d, _ := m.Pack()
w.Write(d)
reply = true
break
} else {
log.Errorf("https query upstream %s", err)
}
}
if !reply {
w.WriteHeader(http.StatusServiceUnavailable)
}
}
func (srv *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.RequestURI != srv.addr.Path {
w.WriteHeader(http.StatusNotFound)
return
}
srv.handleHTTP2Req(w, r)
}

@ -0,0 +1,35 @@
package main
import (
"net"
"github.com/fangdingjun/go-log"
"github.com/miekg/dns"
)
func (srv *server) handleTCP(c net.Conn) {
defer c.Close()
log.Debugln("tcp from", c.RemoteAddr())
conn := dns.Conn{Conn: c}
for {
msg, err := conn.ReadMsg()
if err != nil {
log.Debugln("tcp read message", err)
break
}
reply := false
for _, up := range srv.upstreams {
m, err := queryUpstream(msg, up)
if err == nil {
log.Debugln("got reply", m.String())
conn.WriteMsg(m)
reply = true
break
}
log.Debugln("query upstream", up.String(), err)
}
if !reply {
break
}
}
}

@ -1,31 +0,0 @@
package main
import (
"fmt"
"github.com/miekg/dns"
"testing"
)
func TestInblacklist(t *testing.T) {
logger = NewLogger("", false)
Blacklist_ips = Kv{"1.2.3.4": 1, "2.3.4.5": 1}
test_ips := map[string]bool{
"1.2.3.4": true,
"2.3.4.5": true,
"2.3.4.1": false,
"1.2.4.3": false,
}
for ip, r := range test_ips {
msg := new(dns.Msg)
s := fmt.Sprintf("example.com. IN A %s", ip)
rr, err := dns.NewRR(s)
if err != nil {
t.Error(err)
}
msg.Answer = append(msg.Answer, rr)
if in_blacklist(msg) != r {
t.Errorf("%s must match in %v result\n", ip, r)
}
}
}

@ -0,0 +1,26 @@
package main
import (
"net"
"github.com/fangdingjun/go-log"
"github.com/miekg/dns"
)
func (srv *server) handleUDP(buf []byte, addr net.Addr, conn *net.UDPConn) {
msg := new(dns.Msg)
if err := msg.Unpack(buf); err != nil {
log.Debugln("udp parse msg", err)
return
}
for _, up := range srv.upstreams {
m, err := queryUpstream(msg, up)
if err == nil {
d, _ := m.Pack()
conn.WriteTo(d, addr)
break
} else {
log.Debugln("udp query upstream err", err)
}
}
}

@ -1,8 +0,0 @@
127.0.0.1 localhost localhost.localdomain
192.168.1.1 gw
::1 localhost
192.243.112.217 www.ratafee.nl
2607:8700:103:9b22:: www.ratafee.nl
# 127.0.0.1 ccc.com

@ -1,36 +1,128 @@
package main
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"net/url"
"time"
"github.com/fangdingjun/go-log"
nghttp2 "github.com/fangdingjun/nghttp2-go"
"github.com/miekg/dns"
"strings"
)
type Kv map[string]int
var dnsClientTCP *dns.Client
var dnsClientHTTPS *dns.Client
var dnsClientUDP *dns.Client
var dnsClientTLS *dns.Client
type UpstreamServer struct {
domains Kv
Proto string
Addr string
client *dns.Client
func queryUpstream(msg *dns.Msg, upstream *url.URL) (*dns.Msg, error) {
switch upstream.Scheme {
case "tcp":
return queryUpstreamTCP(msg, upstream)
case "https":
return queryUpstreamHTTPS(msg, upstream)
case "udp":
return queryUpstreamUDP(msg, upstream)
case "tls":
return queryUpstreamTLS(msg, upstream)
default:
}
return nil, errors.New("unknown upstream type")
}
func (srv *UpstreamServer) match(d string) bool {
if srv.domains == nil {
return false
func queryUpstreamUDP(msg *dns.Msg, upstream *url.URL) (*dns.Msg, error) {
log.Debugln("query upstream", upstream.String())
m, _, err := dnsClientUDP.Exchange(msg, upstream.Host)
if err != nil {
log.Debugf("query udp error %s", err)
}
return m, err
}
s := strings.Split(strings.Trim(d, "."), ".")
for i := 0; i < len(s); i++ {
s1 := strings.Join(s[i:], ".")
if _, ok := srv.domains[s1]; ok {
return true
func queryUpstreamTCP(msg *dns.Msg, upstream *url.URL) (*dns.Msg, error) {
log.Debugln("query upstream", upstream.String())
m, _, err := dnsClientTCP.Exchange(msg, upstream.Host)
if err != nil {
log.Debugf("query tcp error %s", err)
}
return m, err
}
func queryUpstreamTLS(msg *dns.Msg, upstream *url.URL) (*dns.Msg, error) {
log.Debugln("query upstream", upstream.String())
m, _, err := dnsClientTLS.Exchange(msg, upstream.Host)
if err != nil {
log.Debugf("query tls error %s", err)
}
return m, err
}
return false
func queryUpstreamHTTPS(msg *dns.Msg, upstream *url.URL) (*dns.Msg, error) {
log.Debugln("query upstream", upstream.String())
m, _, err := dnsClientHTTPS.Exchange(msg, upstream.String())
if err != nil {
log.Debugf("query https error %s", err)
}
return m, err
}
func (srv *UpstreamServer) query(req *dns.Msg) (*dns.Msg, error) {
res, _, err := srv.client.Exchange(req, srv.Addr)
return res, err
func initDNSClient(c *conf) {
var resolver = new(net.Resolver)
if len(c.BootstrapServers) > 0 {
log.Debugf("init dns client, bootstrap servers %v", c.BootstrapServers)
resolver = &net.Resolver{
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
for _, a := range c.BootstrapServers {
u, _ := url.Parse(a)
conn, err := net.Dial(u.Scheme, u.Host)
if err == nil {
return conn, err
}
}
return nil, errors.New("dial failed")
},
}
}
dialer := &net.Dialer{
Resolver: resolver,
}
dnsClientTLS = &dns.Client{
Net: "tcp-tls",
Timeout: time.Duration(c.UpstreamTimeout) * time.Second,
Dialer: dialer,
TLSConfig: &tls.Config{
InsecureSkipVerify: c.UpstreamInsecure,
},
}
dnsClientUDP = &dns.Client{
Net: "udp",
Timeout: time.Duration(c.UpstreamTimeout) * time.Second,
}
dnsClientTCP = &dns.Client{
Net: "tcp",
Timeout: time.Duration(c.UpstreamTimeout) * time.Second,
}
dnsClientHTTPS = &dns.Client{
Net: "https",
Timeout: time.Duration(c.UpstreamTimeout) * time.Second,
HTTPClient: &http.Client{
Transport: &nghttp2.Transport{
DialTLS: func(network, addr string, cfg *tls.Config) (*tls.Conn, error) {
log.Debugln("dial to", network, addr)
conn, err := tls.DialWithDialer(dialer, network, addr, cfg)
return conn, err
},
TLSConfig: &tls.Config{
InsecureSkipVerify: c.UpstreamInsecure,
NextProtos: []string{"h2"},
},
},
},
}
}

@ -1,44 +0,0 @@
package main
import (
"testing"
)
func TestServerMathNil(t *testing.T) {
srv := UpstreamServer{} // initial with nil
domains := []string{"twitter.com", "google.com", "abc.com"}
for _, d := range domains {
if srv.match(d) {
t.Errorf("%s must match in false result\n", d)
}
}
}
func TestServerMatch(t *testing.T) {
d := Kv{"twitter.com": 1, "google.com": 1, "cn": 1}
srv := UpstreamServer{domains: d}
test_domains := map[string]bool{
"twitter.com": true,
"pbs.twitter.com": true,
"abc.pbs.twitter.com": true,
"efg.abc.pbs.twitter.com": true,
"google.com": true,
"plus.google.com": true,
"cc.plus.google.com": true,
"dd.cc.plus.google.com": true,
"twitter.abc.com": false,
"twitter.com.aa.com": false,
"google.com.cccc.com": false,
"google.com.aeddasdfc3.com": false,
"ip.cn": true,
}
for d, r := range test_domains {
if srv.match(d) != r {
t.Errorf("%s must match in %v result\n", d, r)
}
}
}
Loading…
Cancel
Save