aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSina Ghaderi <khokooli@gmail.com>2020-08-21 20:16:33 +0430
committerSina Ghaderi <khokooli@gmail.com>2020-08-21 20:16:33 +0430
commit200ac2d7eb00c197d86ca275e1c7dfaed852309e (patch)
tree17e32f0f399be8b51d14b6ea3d81fd2e88cbb2e2
new dns
-rw-r--r--Readme.md82
-rw-r--r--dns-server.go172
2 files changed, 254 insertions, 0 deletions
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 <ipaddr> (default "127.0.0.1")
+ -filter string
+ filtered domains <filename> - regex is supported (default "noacc.txt")
+ -loaddr string
+ dns server listen address <ipaddr:port> (default "0.0.0.0:53")
+ -loconn string
+ dns server connection type <udp|tcp> (default "udp")
+ -upaddr string
+ upsteam dns server to connect <ipaddr:port> (default "1.1.1.1:53")
+ -upconn string
+ upsteam dns connection type <udp|tcp> (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 <ipaddr:port>")
+ listenAddr := flag.String("loaddr", "0.0.0.0:53", "dns server listen address <ipaddr:port>")
+ upStrcon = flag.String("upconn", "udp", "upsteam dns connection type <udp|tcp>")
+ listenCon := flag.String("loconn", "udp", "dns server connection type <udp|tcp>")
+ fakeAdd = flag.String("fakeadd", "127.0.0.1", "an ip to send back for filtered domains <ipaddr>")
+ filter := flag.String("filter", "noacc.txt", "filtered domains <filename> - 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{}{}
+ }
+
+ }
+}

Snix LLC Git Repository Holder Copyright(C) 2022 All Rights Reserved Email To Snix.IR