gdns v0.0.1

dns
Dingjun 8 years ago
commit 034712fda0

5
.gitignore vendored

@ -0,0 +1,5 @@
*~
*.swp
*.json
*.txt
dns

@ -0,0 +1,12 @@
gdns
====
a dns forward proxy write by go
**Features**
- support listen tcp and udp protocol
- support configure different domains forward to different upstream servers
- support tcp or udp to communicate to upstream servers
- support ip blacklist, drop unwanted dns reply

142
cfg.go

@ -0,0 +1,142 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
)
type item map[string]int
func (i item) has(s string) bool {
if _, ok := i[s]; ok {
return true
}
return false
}
func (it item) match(s string) bool {
iis := strings.Split(s, ".")
for i := 0; i < len(iis); i++ {
ii := strings.Join(iis[i:], ".")
if _, ok := it[ii]; ok {
return true
}
}
return false
}
type addr struct {
network string
addr string
}
// Rule present a forward rule
type Rule struct {
DomainlistFile string `json:"domain_list_file"`
domains item
ServersString []string `json:"servers"`
servers []addr
}
type cfg struct {
Listen []string `json:"listen"`
listen []addr
ServersString []string `json:"default_servers"`
servers []addr
Timeout int `json:"timeout"`
BlacklistFiles []string `json:"blacklist_ips"`
blacklistIps item
Rules []Rule `json:"rules"`
}
func parseCfg(fn string) (*cfg, error) {
fp, err := os.Open(fn)
if err != nil {
return nil, err
}
defer fp.Close()
c := cfg{}
buf, err := ioutil.ReadAll(fp)
err = json.Unmarshal(buf, &c)
if err != nil {
return nil, err
}
var adr []addr
for _, a := range c.ServersString {
a1 := parseAddr(a)
if a1.network != "" {
adr = append(adr, a1)
}
}
c.servers = adr
var ll []addr
for _, a := range c.Listen {
a1 := parseAddr(a)
if a1.network != "" {
ll = append(ll, a1)
}
}
c.listen = ll
l1 := make(item)
for _, a := range c.BlacklistFiles {
parseFile(a, &l1)
}
c.blacklistIps = l1
for i, r := range c.Rules {
l2 := make(item)
parseFile(r.DomainlistFile, &l2)
c.Rules[i].domains = l2
var adr1 []addr
for _, a := range r.ServersString {
a1 := parseAddr(a)
if a1.network != "" {
adr1 = append(adr1, a1)
}
}
c.Rules[i].servers = adr1
}
return &c, nil
}
func parseAddr(addr1 string) addr {
a := strings.SplitN(addr1, ":", 2)
if len(a) != 2 {
fmt.Printf("addr error")
return addr{"", ""}
}
return addr{a[0], a[1]}
}
func parseFile(fn string, i *item) {
ii := *i
fp, err := os.Open(fn)
if err != nil {
fmt.Printf("open failed: %s\n", err)
return
}
defer fp.Close()
r := bufio.NewReader(fp)
for {
line, err := r.ReadString('\n')
l := strings.Trim(line, " \r\n\t")
if err != nil {
if l != "" {
ii[l] = 1
}
break
}
if l == "" {
continue
}
ii[l] = 1
}
}

@ -0,0 +1,16 @@
package main
import (
"fmt"
"testing"
)
func TestCfg(t *testing.T) {
c, err := parseCfg("config.json")
if err != nil {
t.Fatalf("%s\n", err)
}
fmt.Printf("%+v\n", c)
fmt.Printf("%v\n", c.Rules[0].domains.match("google.com"))
fmt.Printf("%v\n", c.Rules[0].domains.match("www.ip.cn"))
}

@ -0,0 +1,16 @@
{
"listen":["tcp:0.0.0.0:8053","udp::8053"],
"default_servers":["tcp:114.114.114.114:53","tcp:8.8.8.8:53"],
"timeout":1,
"blacklist_ips":["ip.txt"],
"rules":[
{
"domain_list_file":"domain1.txt",
"servers":["tcp:4.2.2.2:53"]
},
{
"domain_list_file":"domain2.txt",
"servers":["tcp:8.8.4.4:53"]
}
]
}

@ -0,0 +1,4 @@
ww.goole.com
ip.cn
taobao.com
a.cn

@ -0,0 +1,3 @@
a.com
b.org
c.net

@ -0,0 +1,2 @@
1.2.3.4
2.21.2.2

@ -0,0 +1,100 @@
package main
import (
"github.com/miekg/dns"
"log"
"strings"
"time"
)
type routers struct {
c *cfg
tcp *dns.Client
udp *dns.Client
}
func (r routers) checkBlacklist(m *dns.Msg) bool {
if m.Rcode != dns.RcodeSuccess {
// not success, not in blacklist
return false
}
for _, rr := range m.Answer {
var ip = ""
if t, ok := rr.(*dns.A); ok {
ip = t.A.String()
} else if t, ok := rr.(*dns.AAAA); ok {
ip = t.AAAA.String()
}
if ip != "" && r.c.blacklistIps.has(ip) {
log.Printf("%s is in blacklist.\n", ip)
return true
}
}
return false
}
func (r routers) query(m *dns.Msg, servers []addr) (*dns.Msg, error) {
var up *dns.Client
var lastErr error
for _, srv := range servers {
switch srv.network {
case "tcp":
up = r.tcp
case "udp":
up = r.udp
default:
up = r.udp
}
log.Printf("query %s use %s:%s\n", m.Question[0].Name, srv.network, srv.addr)
m, _, err := up.Exchange(m, srv.addr)
if err == nil && !r.checkBlacklist(m) {
return m, err
}
log.Println(err)
lastErr = err
}
// return last error
return nil, lastErr
}
// ServeDNS implements dns.Handler interface
func (r routers) ServeDNS(w dns.ResponseWriter, m *dns.Msg) {
domain := m.Question[0].Name
d := strings.Trim(domain, ".")
for _, rule := range r.c.Rules {
if rule.domains.match(d) {
m1, err := r.query(m, rule.servers)
if err == nil {
w.WriteMsg(m1)
return
} else {
log.Println(err)
}
}
}
// no match or failed, fallback to default
m1, err := r.query(m, r.c.servers)
if err != nil {
log.Println(err)
dns.HandleFailed(w, m)
} else {
w.WriteMsg(m1)
}
}
func initRouters(c *cfg) {
router := &routers{
c,
&dns.Client{Net: "tcp", Timeout: time.Duration(c.Timeout) * time.Second},
&dns.Client{Net: "udp", Timeout: time.Duration(c.Timeout) * time.Second},
}
dns.Handle(".", router)
}

@ -0,0 +1,31 @@
package main
import (
"flag"
"github.com/miekg/dns"
"log"
"os"
)
func initListeners(c *cfg) {
for _, a := range c.listen {
log.Printf("Listen on %s %s...\n", a.network, a.addr)
s := dns.Server{Addr: a.addr, Net: a.network}
go s.ListenAndServe()
}
}
func main() {
var configFile string
flag.StringVar(&configFile, "c", "", "config file")
flag.Parse()
config, err := parseCfg(configFile)
if err != nil {
log.Println(err)
os.Exit(-1)
}
initRouters(config)
initListeners(config)
select {}
}
Loading…
Cancel
Save