├── LICENSE ├── README.md ├── bin └── httpDns ├── etc ├── conf.yml └── logger.xml ├── log └── empty └── src ├── logic ├── conf.go ├── dns.go ├── handlers.go ├── initial.go ├── redis.go └── util.go ├── makefile └── server.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 zheng-ji.info 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## goHttpDns 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/zheng-ji/goHttpDns)](https://goreportcard.com/report/github.com/zheng-ji/goHttpDns) 4 | 5 | A HttpDns Server Written by Go, In order to avoid Dns hijacking and cache resolve answer 6 | 7 | 一个用 Go 写的 HttpDns 服务, 为了抵抗运营商邪恶的 DNS 劫持污染,并带有缓存功能 。 8 | 9 | ### How To Compile 10 | 11 | ``` 12 | cd $GOPATH; 13 | git clone http://github.com/zheng-ji/goHttpDns; 14 | cd src; 15 | make 16 | ``` 17 | 18 | ### How To Configure 19 | 20 | ``` 21 | # redis connect config 22 | redis: 23 | host: 127.0.0.1:6379 24 | db: 0 25 | 26 | # seelog config 27 | log_config: ../etc/logger.xml 28 | 29 | # ip & port & answer cache TTL 30 | listen: 0.0.0.0 31 | port: 9999 32 | ttl: 100 33 | 34 | # DnsServer lists 35 | dnsservers: 36 | - 202.96.128.86 37 | - 202.96.128.166 38 | - 8.8.8.8 39 | - 8.8.4.4 40 | ``` 41 | 42 | ### How To Run 43 | 44 | After `make`, a binary executable file called `httpDns` will be generated under the `bin` directory 45 | 46 | ``` 47 | $ ./httpDns --help 48 | Usage of ./httpDns: 49 | -c="../etc/conf.yml": conf file,default is ../etc/conf.yml 50 | 51 | ./httpDns -c="your_conf_yaml_path" 52 | ``` 53 | 54 | You can also use `supervisor` to start your sever 55 | 56 | ### How To Use 57 | 58 | ``` 59 | $ curl http://127.0.0.1:9999/d?url=http://zheng-ji.info 60 | 61 | Resp: 62 | { 63 | "c":0, 64 | "targetip":"http://106.185.48.24", 65 | "host":"zheng-ji.info", 66 | "msg":"" 67 | } 68 | ``` 69 | 70 | ### Dependence Third Part Lib 71 | 72 | Thanks to: 73 | 74 | * [launchpad/goyaml](https://launchpad.net/goyaml) 75 | * [cihub/seelog](github.com/cihub/seelog) 76 | * [miekg/dns](github.com/miekg/dns) 77 | * [redisgo/redis](github.com/garyburd/redigo/redis") 78 | * [hoisie/web](github.com/hoisie/web) 79 | 80 | You need to `go get` the list above 81 | 82 | 83 | License 84 | ------- 85 | 86 | Copyright (c) 2015 by [zheng-ji](zheng-ji.info) released under a MIT style license. 87 | -------------------------------------------------------------------------------- /bin/httpDns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zheng-ji/goHttpDns/725be4869a09c09251ffed34e9b224da8ad108c3/bin/httpDns -------------------------------------------------------------------------------- /etc/conf.yml: -------------------------------------------------------------------------------- 1 | # redis 连接配置 2 | redis: 3 | host: 127.0.0.1:6379 4 | db: 0 5 | 6 | # 日志(出错信息,调试信息)配置路径 7 | log_config: ../etc/logger.xml 8 | 9 | # 监听的IP,监听的端口, cache TTL 10 | listen: 0.0.0.0 11 | port: 9999 12 | ttl: 100 13 | 14 | dnsservers: 15 | - 202.96.128.86 16 | - 202.96.128.166 17 | - 8.8.8.8 18 | - 8.8.4.4 19 | -------------------------------------------------------------------------------- /etc/logger.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /log/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zheng-ji/goHttpDns/725be4869a09c09251ffed34e9b224da8ad108c3/log/empty -------------------------------------------------------------------------------- /src/logic/conf.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: zheng-ji.info 3 | */ 4 | 5 | package logic 6 | 7 | import ( 8 | "errors" 9 | goyaml "gopkg.in/yaml.v3" 10 | "io/ioutil" 11 | ) 12 | 13 | // AppConfig Type 14 | type AppConfig struct { 15 | Redis RedisConf `yaml:"redis"` 16 | Logconf string `yaml:"log_config"` 17 | Listen string `yaml:"listen"` 18 | Port string `yaml:"port"` 19 | TTL int `yaml:"ttl"` 20 | Dnsservers []string `yaml:"dnsservers"` 21 | } 22 | 23 | // RedisConf Type 24 | type RedisConf struct { 25 | Host string `yaml:"host"` 26 | Db string `yaml:"db"` 27 | } 28 | 29 | func (rc *RedisConf) isValid() bool { 30 | return len(rc.Host) > 0 && len(rc.Db) > 0 31 | } 32 | 33 | var appConfig AppConfig 34 | 35 | func (ac *AppConfig) isValid() bool { 36 | return ac.Redis.isValid() && 37 | len(ac.Listen) > 0 && 38 | len(ac.Port) > 0 39 | } 40 | 41 | func parseConfigFile(filepath string) error { 42 | if config, err := ioutil.ReadFile(filepath); err == nil { 43 | if err = goyaml.Unmarshal(config, &appConfig); err != nil { 44 | return err 45 | } 46 | if appConfig.TTL == 0 { 47 | appConfig.TTL = 10 * 60 48 | } 49 | if !appConfig.isValid() { 50 | return errors.New("Invalid configuration") 51 | } 52 | } else { 53 | return err 54 | } 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /src/logic/dns.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: zheng-ji.info 3 | */ 4 | 5 | package logic 6 | 7 | import ( 8 | "fmt" 9 | "github.com/cihub/seelog" 10 | "github.com/miekg/dns" 11 | "net" 12 | "net/url" 13 | ) 14 | 15 | // DnsDecoder resolve url's dns 16 | func DnsDecoder(urlStr string) (*string, *string, error) { 17 | u, err := url.Parse(urlStr) 18 | if err != nil { 19 | return nil, nil, err 20 | } 21 | hostTmp := u.Host 22 | IP := Dns(u.Host) 23 | if IP != nil { 24 | u.Host = IP.String() 25 | urlStr = u.String() 26 | return &urlStr, &hostTmp, nil 27 | } 28 | return nil, nil, fmt.Errorf("dnsDecoder fail") 29 | } 30 | 31 | // Dns get DNS record 32 | func Dns(host string) *net.IP { 33 | for _, dnsServer := range appConfig.Dnsservers { 34 | IP := dnss(host, dnsServer+":53") 35 | if IP != nil { 36 | return IP 37 | } 38 | } 39 | return nil 40 | } 41 | 42 | /** 43 | * CNAME -> A 44 | */ 45 | func dnss(host, dnsServer string) *net.IP { 46 | addrs, err := Lookup("CNAME", host, dnsServer) 47 | if err != nil { 48 | seelog.Errorf("dns cname fail with the host[%s]. error: [%s]", host, err.Error()) 49 | return nil 50 | } 51 | 52 | for { 53 | if len(addrs.Answer) == 0 { 54 | break 55 | } 56 | host = addrs.Answer[0].(*dns.CNAME).Target 57 | addrs, err = Lookup("CNAME", host, dnsServer) 58 | if err != nil { 59 | seelog.Errorf("dns cname fail with the host[%s]. error: [%s]", host, err.Error()) 60 | return nil 61 | } 62 | } 63 | addrs, err = Lookup("A", host, dnsServer) 64 | if err != nil { 65 | seelog.Errorf("dns a fail with the host[%s]. error: [%s]", host, err.Error()) 66 | return nil 67 | } 68 | for _, a := range addrs.Answer { 69 | if a.(*dns.A).A != nil { 70 | return &a.(*dns.A).A 71 | } 72 | } 73 | return nil 74 | } 75 | 76 | // Lookup search recordy by dns server 77 | func Lookup(ctype, host, dnsServer string) (*dns.Msg, error) { 78 | 79 | itype, ok := dns.StringToType[ctype] 80 | if !ok { 81 | return nil, fmt.Errorf("Invalid type %s", ctype) 82 | } 83 | 84 | host = dns.Fqdn(host) 85 | client := &dns.Client{} 86 | msg := &dns.Msg{} 87 | msg.SetQuestion(host, itype) 88 | response := &dns.Msg{} 89 | 90 | response, err := lookup(msg, client, dnsServer, false) 91 | if err != nil { 92 | return response, err 93 | } 94 | 95 | return response, nil 96 | } 97 | 98 | func lookup(msg *dns.Msg, client *dns.Client, server string, edns bool) (*dns.Msg, error) { 99 | if edns { 100 | opt := &dns.OPT{ 101 | Hdr: dns.RR_Header{ 102 | Name: ".", 103 | Rrtype: dns.TypeOPT, 104 | }, 105 | } 106 | opt.SetUDPSize(dns.DefaultMsgSize) 107 | msg.Extra = append(msg.Extra, opt) 108 | } 109 | response, _, err := client.Exchange(msg, server) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | if msg.Id != response.Id { 115 | return nil, fmt.Errorf("DNS ID mismatch, request: %d, response: %d", msg.Id, response.Id) 116 | } 117 | 118 | if response.MsgHdr.Truncated { 119 | if client.Net == "tcp" { 120 | return nil, fmt.Errorf("Got truncated message on tcp") 121 | } 122 | if edns { 123 | client.Net = "tcp" 124 | } 125 | 126 | return lookup(msg, client, server, !edns) 127 | } 128 | return response, nil 129 | } 130 | -------------------------------------------------------------------------------- /src/logic/handlers.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: zheng-ji.info 3 | */ 4 | 5 | package logic 6 | 7 | import ( 8 | "fmt" 9 | "github.com/cihub/seelog" 10 | "github.com/hoisie/web" 11 | ) 12 | 13 | // Resp Type 14 | type Resp struct { 15 | Code int `json:"c"` 16 | TargetIP string `json:"targetip,omitempty"` 17 | Host string `json:"host, omitempty"` 18 | Msg string `json:"msg, omitempty"` 19 | } 20 | 21 | const ( 22 | SUCC = 0 23 | FAILED = -1 24 | HTTP = "http://" 25 | ) 26 | 27 | // PingHandler Func 28 | func PingHandler(ctx *web.Context) string { 29 | ret := "ok" 30 | return ret 31 | } 32 | 33 | // ResolveHandler Func 34 | func ResolveHandler(ctx *web.Context) string { 35 | 36 | url := ctx.Params["url"] 37 | targetIPstr, hostStr, err := getResultFromCache(url) 38 | if err == nil { 39 | resp := Resp{ 40 | Code: SUCC, 41 | TargetIP: targetIPstr, 42 | Host: hostStr, 43 | } 44 | return resp.jsonString() 45 | } 46 | 47 | targetIP, host, err := DnsDecoder(url) 48 | if nil != err { 49 | resp := Resp{ 50 | Code: FAILED, 51 | Msg: fmt.Sprintf("%s", err), 52 | } 53 | seelog.Errorf("[ResolveHandler] error: %v", err) 54 | return resp.jsonString() 55 | } else { 56 | resp := Resp{ 57 | Code: SUCC, 58 | TargetIP: *targetIP, 59 | Host: *host, 60 | } 61 | cacheResp(url, *host, *targetIP) 62 | seelog.Infof("[ResolveHandler] host:%s targetIp:%s", *host, *targetIP) 63 | return resp.jsonString() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/logic/initial.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: zheng-ji.info 3 | */ 4 | 5 | package logic 6 | 7 | import ( 8 | "fmt" 9 | "github.com/cihub/seelog" 10 | "github.com/garyburd/redigo/redis" 11 | "github.com/hoisie/web" 12 | "runtime" 13 | ) 14 | 15 | var ( 16 | redisPool *redis.Pool 17 | ) 18 | 19 | // Initializer Func 20 | func Initializer(conf string) bool { 21 | 22 | err := parseConfigFile(conf) 23 | if nil != err { 24 | fmt.Println("%v\n", err) 25 | return false 26 | } 27 | runtime.GOMAXPROCS(runtime.NumCPU()) 28 | 29 | //seelog 配置 30 | logger, err := seelog.LoggerFromConfigAsFile(appConfig.Logconf) 31 | if nil != err { 32 | fmt.Println("%v\n", err) 33 | return false 34 | } 35 | seelog.UseLogger(logger) 36 | 37 | //redis 链接 38 | redisPool = NewRedisPool(appConfig.Redis.Host, appConfig.Redis.Db) 39 | return true 40 | } 41 | 42 | // Loop Func Register web interface 43 | func Loop() { 44 | 45 | // 注册web接口 46 | web.Get("/ping", PingHandler) 47 | web.Get("/d", ResolveHandler) 48 | addr := fmt.Sprintf("%s:%s", appConfig.Listen, appConfig.Port) 49 | web.Run(addr) 50 | } 51 | -------------------------------------------------------------------------------- /src/logic/redis.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: zheng-ji.info 3 | */ 4 | 5 | package logic 6 | 7 | import ( 8 | "github.com/cihub/seelog" 9 | "github.com/garyburd/redigo/redis" 10 | "time" 11 | ) 12 | 13 | func NewRedisPool(host string, db string) *redis.Pool { 14 | redisPool = &redis.Pool{ 15 | MaxIdle: 3, 16 | IdleTimeout: 1 * time.Hour, 17 | Dial: func() (redis.Conn, error) { 18 | c, err := redis.Dial("tcp", host) 19 | if nil != err { 20 | seelog.Errorf("[Redis] Dial erro: %v", err) 21 | return nil, err 22 | } 23 | if _, err := c.Do("SELECT", db); nil != err { 24 | c.Close() 25 | seelog.Errorf("[Redis] Select DB error: %v", err) 26 | return nil, err 27 | } 28 | return c, err 29 | }, 30 | TestOnBorrow: func(c redis.Conn, t time.Time) error { 31 | _, err := c.Do("PING") 32 | return err 33 | }, 34 | } 35 | return redisPool 36 | } 37 | -------------------------------------------------------------------------------- /src/logic/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: zheng-ji.info 3 | */ 4 | 5 | package logic 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "github.com/cihub/seelog" 11 | "github.com/garyburd/redigo/redis" 12 | ) 13 | 14 | func substr(s string, from, to int) string { 15 | bytes := []byte(s) 16 | return string(bytes[from:to]) 17 | } 18 | 19 | func (resp *Resp) jsonString() string { 20 | b, _ := json.Marshal(resp) 21 | return string(b) 22 | } 23 | 24 | func cacheResp(url, host, targetIP string) { 25 | var err error 26 | conn := redisPool.Get() 27 | if conn == nil { 28 | return 29 | } 30 | defer conn.Close() 31 | 32 | var key string 33 | 34 | key = fmt.Sprintf("%s_host", url) 35 | _, err = conn.Do("SETEX", key, appConfig.TTL, host) 36 | if err != nil { 37 | seelog.Errorf("[Redis][SETEX] error: %v", err) 38 | } 39 | 40 | key = fmt.Sprintf("%s_ip", url) 41 | _, err = conn.Do("SETEX", key, appConfig.TTL, targetIP) 42 | if err != nil { 43 | seelog.Errorf("[Redis][SETEX] error: %v", err) 44 | } 45 | } 46 | 47 | func getResultFromCache(url string) (targetIP string, host string, err error) { 48 | conn := redisPool.Get() 49 | if conn == nil { 50 | return 51 | } 52 | defer conn.Close() 53 | 54 | var key string 55 | 56 | key = fmt.Sprintf("%s_host", url) 57 | host, err = redis.String(conn.Do("GET", key)) 58 | key = fmt.Sprintf("%s_ip", url) 59 | targetIP, err = redis.String(conn.Do("GET", key)) 60 | 61 | if err != nil { 62 | seelog.Errorf("[Redis][GET] error: %v", err) 63 | } 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | all: compile 2 | 3 | compile: 4 | go build . && mv src ../bin/httpDns; 5 | 6 | clean: 7 | cd ../bin; go clean; cd logic; go clean; 8 | 9 | -------------------------------------------------------------------------------- /src/server.go: -------------------------------------------------------------------------------- 1 | // Author: zheng-ji.info 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "goHttpDns/src/logic" 8 | ) 9 | 10 | var ( 11 | configFile = flag.String("c", "../etc/conf.yml", "配置文件路径,默认etc/conf.yml") 12 | ) 13 | 14 | func main() { 15 | 16 | flag.Parse() 17 | 18 | if !logic.Initializer(*configFile) { 19 | return 20 | } 21 | logic.Loop() 22 | } 23 | --------------------------------------------------------------------------------