From 200ac2d7eb00c197d86ca275e1c7dfaed852309e Mon Sep 17 00:00:00 2001 From: Sina Ghaderi Date: Fri, 21 Aug 2020 20:16:33 +0430 Subject: new dns --- Readme.md | 82 ++++++++++++++++++++++++++++ dns-server.go | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 Readme.md create mode 100644 dns-server.go diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..1885a54 --- /dev/null +++ b/Readme.md @@ -0,0 +1,82 @@ +# Nano DNS +Simple and tiny DNS server written in Golang for filtering domains and speed up name resolution functionality. +Core features: +forward and cache dns queries +block domain names with regex. + +### Installation +Prerequisites: [Golang](https://golang.org) + [Git](https://git-scm.com) +Installing for windows - linux - freebsd and macos. (linux is recommended) +Clone the code form Github or [Snix](https://slc.snix.ir) servers. +``` +# git clone https://slc.snix.ir/snix/nanodns.git # Snix +# git clone https://github.com/khokooli/nanodns.git # Github +# cd nanodns +# go get -v +# go build +# ./nanodns +2020/07/31 05:11:29 upstream dns: 1.1.1.1:53 connection udp +2020/07/31 05:11:29 dns listen on: 0.0.0.0:53 connection udp +2020/07/31 05:11:29 load banned domains from: noacc.txt + +``` + +### Cross Compiles +If you want to build this for other device architectures (like Mikrotik, Raspberry pi) follow this. +Build for Mikrotik RB 2011 UIAS-IN runing on [OpenWRT](https://openwrt.org): (Mipsbe CPU) +``` +# cd nanodns +# GOOS=linux GOARCH=mips GOMIPS=softfloat go build +# scp nanodns root@192.168.1.1:/root/ +``` +After all connect to your device and run the nanodns binary. +Building for [Raspberry pi](https://www.raspberrypi.org/): (ARM64 CPU's) +``` +cd nanodns +GOARCH=arm64 GOOS=linux go build +``` +### Usage and Options +``` +# ./nanodns -h +Usage of ./nanodns: + -fakeadd string + an ip to send back for filtered domains (default "127.0.0.1") + -filter string + filtered domains - regex is supported (default "noacc.txt") + -loaddr string + dns server listen address (default "0.0.0.0:53") + -loconn string + dns server connection type (default "udp") + -upaddr string + upsteam dns server to connect (default "1.1.1.1:53") + -upconn string + upsteam dns connection type (default "udp") +``` + +### Regex Domain Blocking +You can use regex for blocking domain names, create a file like `noacc.txt` in `nanodns` directory. +For blocking all `com` and `example.net` put the following in `noacc.txt` +[Regex Test Tool Online](https://regex101.com/) +``` +^([a-z0-9]+[.])*com.$ +example.net. +``` +Block all domains if contain following words: +`advertise`, `torrent` or `hack` +``` +^(torrent|hack|advertise).*$ +``` +Block any domain except `google.com` and `snix.ir` +``` +^((?!google[.]com[.]|snix[.]ir[.]).)*$ +``` +Notes about regex in [Golang](https://golang.org): +Please note that if your regex string contain escape character `'\'` char, put another backslash befor it. +Read more about [Escape Character](https://yourbasic.org/golang/regexp-cheat-sheet/). + ``` + ^\babc\b.*$ ---> wrong + ^\\babc\\b.*$ ---> it's ok +``` + +### Support and Social Media +So if you interested to learn [Golang](https://golang.org) follow my [Instagram Account](https://instagram.com/Gonoobies), Thanks. diff --git a/dns-server.go b/dns-server.go new file mode 100644 index 0000000..197a17b --- /dev/null +++ b/dns-server.go @@ -0,0 +1,172 @@ +package main + +import ( + "context" + "flag" + "io/ioutil" + "log" + "math/rand" + "net" + "os" + "regexp" + "strings" + "time" + + "github.com/miekg/dns" +) + +type dnsMistake struct{} + +func (*dnsMistake) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { + msg := dns.Msg{} + msg.SetReply(r) + switch r.Question[0].Qtype { + case dns.TypeA: + msg.Authoritative = true + domain := msg.Question[0].Name + address, ok := haveIT(domain) + if ok { + msg.Answer = append(msg.Answer, &dns.A{ + Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60}, + A: net.ParseIP(address), + }) + } + } + w.WriteMsg(&msg) +} + +var ( + upStrdns *string + upStrcon *string +) + +func flushCH(name string) { + for { + <-flush + log.Printf("dropping memory cache .... %v recored", len(dataCH)) + dataCH = make(map[string]string) + fileCheck(name) + + } + +} + +func askUpstr(s string) (string, bool) { + r := &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{ + Timeout: time.Millisecond * time.Duration(10000), + } + return d.DialContext(ctx, *upStrcon, *upStrdns) + }, + } + + ip, err := r.LookupHost(context.Background(), s) + if err != nil { + return "err", false + } + return ip[0], true + +} + +const regIPPORT string = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$" +const regTCPUDP string = "^(tcp|udp)$" +const ipONLY string = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" + +var dataCH = make(map[string]string) +var fakeAdd *string + +func timeCh() { + for { + time.Sleep(time.Second*time.Duration(rand.Intn(120)) + 90) + flush <- struct{}{} + } +} + +func main() { + rand.Seed(time.Now().UnixNano()) + upStrdns = flag.String("upaddr", "1.1.1.1:53", "upsteam dns server to connect ") + listenAddr := flag.String("loaddr", "0.0.0.0:53", "dns server listen address ") + upStrcon = flag.String("upconn", "udp", "upsteam dns connection type ") + listenCon := flag.String("loconn", "udp", "dns server connection type ") + fakeAdd = flag.String("fakeadd", "127.0.0.1", "an ip to send back for filtered domains ") + filter := flag.String("filter", "noacc.txt", "filtered domains - regex is supported") + + flag.Parse() + if match, _ := regexp.MatchString(regIPPORT, *upStrdns); !match { + flag.Usage() + os.Exit(1) + } + if match, _ := regexp.MatchString(ipONLY, *fakeAdd); !match { + flag.Usage() + os.Exit(1) + } + if match, _ := regexp.MatchString(regIPPORT, *listenAddr); !match { + flag.Usage() + os.Exit(1) + } + if match, _ := regexp.MatchString(regTCPUDP, *upStrcon); !match { + flag.Usage() + os.Exit(1) + } + if match, _ := regexp.MatchString(regTCPUDP, *listenCon); !match { + flag.Usage() + os.Exit(1) + } + + fileCheck(*filter) + go flushCH(*filter) + go timeCh() + go memCH() + srv := &dns.Server{Addr: *listenAddr, Net: *listenCon} + srv.Handler = &dnsMistake{} + log.Printf("upstream dns: %v connection %v", *upStrdns, *upStrcon) + log.Printf("dns listen on: %v connection %v", *listenAddr, *listenCon) + log.Printf("load banned domains from: %v", *filter) + if err := srv.ListenAndServe(); err != nil { + log.Fatalf("fatal: failed to set udp|tcp listener %s\n", err.Error()) + } +} + +func fileCheck(name string) { + content, err := os.OpenFile(name, os.O_RDONLY|os.O_CREATE, 0666) + if err != nil { + log.Fatal("fatal: can not access or create the file", err.Error()) + } + ready, err := ioutil.ReadAll(content) + if err != nil { + log.Fatal("fatal: unknow error", err.Error()) + } + stringArr := strings.Fields(string(ready)) + for _, v := range stringArr { + dataCH[v] = *fakeAdd + } + +} + +var flush = make(chan struct{}) + +func haveIT(domain string) (string, bool) { + for k, v := range dataCH { + if ok, _ := regexp.MatchString(k, domain); ok { + return v, true + } + } + + if addr, ok := askUpstr(domain); ok { + dataCH[domain] = addr + return addr, true + } + return "err", false +} + +func memCH() { + for { + time.Sleep(time.Millisecond * 100) + if len(dataCH) > 900000 { + flush <- struct{}{} + } + + } +} -- cgit v1.2.3