gdns v0.0.1
commit
034712fda0
@ -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
|
||||
|
@ -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…
Reference in New Issue