diff --git a/Makefile b/Makefile index 9fe5eea..084daa2 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD) compile: echo "Compiling for every OS and Platform" - GOOS=freebsd GOARCH=amd64 go build -o bin/$(NAME)_$(VERSION)_freebsd-amd64 -ldflags '-X "main.versionID=$(VERSION)"' - GOOS=linux GOARCH=amd64 go build -o bin/$(NAME)_$(VERSION)_linux-amd64 -ldflags '-X "main.versionID=$(VERSION)"' - GOOS=darwin GOARCH=amd64 go build -o bin/$(NAME)_$(VERSION)_darwin-amd64 -ldflags '-X "main.versionID=$(VERSION)"' - GOOS=windows GOARCH=amd64 go build -o bin/$(NAME)_$(VERSION)_windows-amd64 -ldflags '-X "main.versionID=$(VERSION)"' \ No newline at end of file + GOOS=freebsd GOARCH=amd64 go build -o ./bin/$(NAME)_$(VERSION)_freebsd-amd64 -ldflags '-X "main.versionID=$(VERSION)"' ./cmd/dotbomb + GOOS=linux GOARCH=amd64 go build -o ./bin/$(NAME)_$(VERSION)_linux-amd64 -ldflags '-X "main.versionID=$(VERSION)"' ./cmd/dotbomb + GOOS=darwin GOARCH=amd64 go build -o ./bin/$(NAME)_$(VERSION)_darwin-amd64 -ldflags '-X "main.versionID=$(VERSION)"' ./cmd/dotbomb + GOOS=windows GOARCH=amd64 go build -o ./bin/$(NAME)_$(VERSION)_windows-amd64 -ldflags '-X "main.versionID=$(VERSION)"' ./cmd/dotbomb \ No newline at end of file diff --git a/README.md b/README.md index cededf6..25582fa 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,12 @@ This is a DNS over TLS stress test tool +Support DNS and DoH by the way ## Command usage description ``` -v show version +-m mode: dns / dot / doh -c number of concurrency -t last Recv Packet Timeout -n number of query @@ -14,45 +16,69 @@ This is a DNS over TLS stress test tool -f stress domain list ``` -## Source Code +## Example + +* DoT ```bash -go run main.go -c 20 -n 2000 -r 8.8.8.8 -f ./domains.txt -2022/03/26 14:40:44 DoTBomb start stress... -2022/03/26 14:40:45 DNS Over TLS Server: 8.8.8.8:853 -Progress: 4000/4000 +$ dotbomb -m dot -c 20 -n 100 -r 8.8.8.8 -f domains.txt +2022/03/26 15:44:10 DoTBomb start stress... +2022/03/26 15:44:10 Mode: dot +2022/03/26 15:44:10 DNS Over TLS Server: 8.8.8.8:853 +Progress: 2000/2000 Status: Finish -Finish Time: 3.472923s -Avg Latency: 0.000869s +Finish Time: 2.172188s +Avg Latency: 0.001086s ========================================== -Send: 4000 -Recv: 4000 - Answer: 3917 - NoAnswer: 80 - Timeout: 3 +Send: 2000 +Recv: 2000 + Answer: 1980 + NoAnswer: 20 + Timeout: 0 + Other: 0 + Timeout: 0 Other: 0 ``` +* DNS -## Binary executable file +```bash +$ dotbomb -m dns -c 20 -n 50 -r 8.8.8.8 -f domains.txt +2022/03/26 15:44:44 DoTBomb start stress... +2022/03/26 15:44:44 Mode: dns +2022/03/26 15:44:44 DNS Server: 8.8.8.8:53 +Progress: 1000/1000 -### Linux / MacOS +Status: Finish +Finish Time: 1.569659s +Avg Latency: 0.001571s +========================================== +Send: 1000 +Recv: 1000 + Answer: 999 + NoAnswer: 0 + Timeout: 1 + Other: 0 +``` + +* DoH ```bash -./dotbomb_v1.1.1_darwin-amd64 -c 20 -n 100 -r 8.8.8.8 -f domains.txt -2022/03/26 14:39:49 DoTBomb start stress... -2022/03/26 14:39:49 DNS Over TLS Server: 8.8.8.8:853 -Progress: 2000/2000 +$ dotbomb -m doh -c 20 -n 300 -r 8.8.8.8 -f domains.txt +2022/03/26 15:45:10 DoTBomb start stress... +2022/03/26 15:45:10 Mode: doh +2022/03/26 15:45:10 DNS Over HTTPS Server: https://8.8.8.8:443/dns-query +Progress: 6000/6000 Status: Finish -Finish Time: 1.084065s -Avg Latency: 0.000542s +Finish Time: 18.190158s +Avg Latency: 0.003032s ========================================== -Send: 2000 -Recv: 2000 - Answer: 1980 - NoAnswer: 20 +Send: 6000 +Recv: 6000 + Answer: 5820 + NoAnswer: 180 Timeout: 0 Other: 0 -``` +``` \ No newline at end of file diff --git a/cmd/dotbomb/flag.go b/cmd/dotbomb/flag.go new file mode 100644 index 0000000..fc90add --- /dev/null +++ b/cmd/dotbomb/flag.go @@ -0,0 +1,69 @@ +package main + +import ( + "flag" + "fmt" + "os" +) + +var ( + versionID string = "%VERSION%" + version bool + mode string + timeout int + concurrency int + totalRequest int + requestIP string + requestPort string + domainFile string +) + +func init() { + flag.BoolVar(&version, "v", false, "number of concurrency") + flag.StringVar(&mode, "m", "dot", "dot / doh / dns") + flag.IntVar(&timeout, "t", 3, "Last Recv Packet Timeout") + flag.IntVar(&concurrency, "c", 1, "number of concurrency") + flag.IntVar(&totalRequest, "n", 1, "number of request") + flag.StringVar(&requestIP, "r", "", "request ip address") + flag.StringVar(&requestPort, "p", "", "request port") + flag.StringVar(&domainFile, "f", "", "domain list file") + + flag.Parse() + + if version { + fmt.Println(versionID) + os.Exit(0) + } + + if concurrency == 0 || totalRequest == 0 || requestIP == "" || mode == "" { + fmt.Println("Example: dotbomb -m dot -c 10 -n 100 -r 8.8.8.8 -p 853 -f domains.txt") + fmt.Println("-v [Version]") + fmt.Println("-m [Mode] Default: dot, Option: dot / doh / dns") + fmt.Println("-c [Concurrency] ") + fmt.Println("-t [Timeout] ") + fmt.Println("-n [request] ") + fmt.Println("-r ") + fmt.Println("-p ") + fmt.Println("-f ") + + os.Exit(0) + } + + switch mode { + case "dns", "dot", "doh": + default: + fmt.Println("-m [Mode] Default: dot, Option: dot / doh / dns") + os.Exit(0) + } + + if requestPort == "" { + switch mode { + case "dns": + requestPort = "53" + case "dot": + requestPort = "853" + case "doh": + requestPort = "443" + } + } +} diff --git a/main.go b/cmd/dotbomb/main.go similarity index 60% rename from main.go rename to cmd/dotbomb/main.go index 4da2486..0acbbd9 100644 --- a/main.go +++ b/cmd/dotbomb/main.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "flag" "fmt" "log" "math" @@ -14,51 +13,11 @@ import ( "github.com/cody0704/dotbomb/server" ) -var ( - versionID string = "%VERSION%" - version bool - timeout int - concurrency int - totalRequest int - requestIP string - requestPort string - domainFile string -) - -func init() { - flag.BoolVar(&version, "v", false, "number of concurrency") - flag.IntVar(&timeout, "t", 3, "RecvTimeout") - flag.IntVar(&concurrency, "c", 1, "number of concurrency") - flag.IntVar(&totalRequest, "n", 1, "number of request") - flag.StringVar(&requestIP, "r", "", "request ip address") - flag.StringVar(&requestPort, "p", "853", "request port") - flag.StringVar(&domainFile, "f", "", "domain list file") - - flag.Parse() - - if version { - fmt.Println(versionID) - os.Exit(0) - } - - if concurrency == 0 || totalRequest == 0 || requestIP == "" { - fmt.Println("Example: ./dotbomb -c 10 -n 100 -r 8.8.8.8 -f domains.txt") - fmt.Println("Example: ./dotbomb -c 10 -n 100 -r 8.8.8.8 -p 853 -f domains.txt") - fmt.Println("-c [Concurrency] ") - fmt.Println("-n [request] ") - fmt.Println("-r ") - fmt.Println("-p ") - - flag.Usage() - os.Exit(0) - } -} - func main() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) - var dotbomb = server.DoTBomb{ + var bomb = server.Bomb{ Concurrency: concurrency, TotalRequest: totalRequest, RequestIP: requestIP, @@ -77,10 +36,10 @@ func main() { if domain == "" { continue } - dotbomb.DomainArray = append(dotbomb.DomainArray, domain) + bomb.DomainArray = append(bomb.DomainArray, domain) } - if len(dotbomb.DomainArray) == 0 { + if len(bomb.DomainArray) == 0 { log.Fatal(domainFile, " does not have any domains") } @@ -90,8 +49,16 @@ func main() { file.Close() log.Println("DoTBomb start stress...") - - go dotbomb.Start() + log.Println("Mode:", mode) + + switch mode { + case "dns": + go bomb.DNS() + case "dot": + go bomb.DoT() + case "doh": + go bomb.DoH() + } select { case <-sigChan: diff --git a/server/dns.go b/server/dns.go new file mode 100644 index 0000000..27d03b5 --- /dev/null +++ b/server/dns.go @@ -0,0 +1,80 @@ +package server + +import ( + "fmt" + "log" + "os" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/cody0704/dotbomb/server/verify" + + rdns "github.com/folbricht/routedns" + "github.com/miekg/dns" +) + +func (b Bomb) DNS() { + server := b.RequestIP + ":" + b.RequestPort + if !verify.DNSServer(server) { + log.Println("Cannot connect to DNS Server:", server) + os.Exit(0) + return + } + log.Println("DNS Server:", server) + + t1 := time.Now() + + wg.Add(b.Concurrency) + var domainCount = len(b.DomainArray) + finish := b.TotalRequest * b.Concurrency + for count := 1; count <= b.Concurrency; count++ { + go func(count, finish int) { + // Build a query + q := new(dns.Msg) + + // Resolve the query + dnsClient, err := rdns.NewDNSClient("stress-dns-"+strconv.Itoa(count), server, "udp", rdns.DNSClientOptions{}) + if err != nil { + log.Println(err) + wg.Done() + return + } + + for i := 0; i < b.TotalRequest; i++ { + domain := b.DomainArray[i%domainCount] + "." + + q.SetQuestion(domain, dns.TypeA) + atomic.AddUint64(&Result.SendCount, 1) + fmt.Printf("Progress:\t%d/%d\r", Result.SendCount, finish) + resp, err := dnsClient.Resolve(q, rdns.ClientInfo{}) + if err != nil { + if strings.Contains(err.Error(), "timed out") { + atomic.AddUint64(&Result.TimeoutCount, 1) + } else { + atomic.AddUint64(&Result.OtherCount, 1) + } + continue + } + Result.LastTime = time.Since(t1) + + answers := resp.Answer + if len(answers) > 0 { + switch len(strings.Split(answers[0].String(), "\t")) { + case 5: + atomic.AddUint64(&Result.RecvAnsCount, 1) + default: + atomic.AddUint64(&Result.RecvNoAnsCount, 1) + } + } else { + atomic.AddUint64(&Result.RecvNoAnsCount, 1) + } + + } + wg.Done() + }(count, finish) + } + wg.Wait() + StatusChan <- 0 +} diff --git a/server/doh.go b/server/doh.go new file mode 100644 index 0000000..32e35c8 --- /dev/null +++ b/server/doh.go @@ -0,0 +1,85 @@ +package server + +import ( + "crypto/tls" + "fmt" + "log" + "os" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/cody0704/dotbomb/server/verify" + + rdns "github.com/folbricht/routedns" + "github.com/miekg/dns" +) + +func (b Bomb) DoH() { + server := "https://" + b.RequestIP + ":" + b.RequestPort + "/dns-query" + if !verify.DoHServer(server) { + log.Println("Cannot connect to DNS Over HTTPS Server:", server) + os.Exit(0) + return + } + log.Println("DNS Over HTTPS Server:", server) + + t1 := time.Now() + + config := tls.Config{ + InsecureSkipVerify: true, + } + + wg.Add(b.Concurrency) + var domainCount = len(b.DomainArray) + finish := b.TotalRequest * b.Concurrency + for count := 1; count <= b.Concurrency; count++ { + go func(count, finish int) { + // Build a query + q := new(dns.Msg) + + // Resolve the query + dohClient, err := rdns.NewDoHClient("stress-doh-"+strconv.Itoa(count), server, rdns.DoHClientOptions{TLSConfig: &config}) + if err != nil { + log.Println(err) + wg.Done() + return + } + + for i := 0; i < b.TotalRequest; i++ { + domain := b.DomainArray[i%domainCount] + "." + + q.SetQuestion(domain, dns.TypeA) + atomic.AddUint64(&Result.SendCount, 1) + fmt.Printf("Progress:\t%d/%d\r", Result.SendCount, finish) + resp, err := dohClient.Resolve(q, rdns.ClientInfo{}) + if err != nil { + if strings.Contains(err.Error(), "timed out") { + atomic.AddUint64(&Result.TimeoutCount, 1) + } else { + atomic.AddUint64(&Result.OtherCount, 1) + } + continue + } + Result.LastTime = time.Since(t1) + + answers := resp.Answer + if len(answers) > 0 { + switch len(strings.Split(answers[0].String(), "\t")) { + case 5: + atomic.AddUint64(&Result.RecvAnsCount, 1) + default: + atomic.AddUint64(&Result.RecvNoAnsCount, 1) + } + } else { + atomic.AddUint64(&Result.RecvNoAnsCount, 1) + } + + } + wg.Done() + }(count, finish) + } + wg.Wait() + StatusChan <- 0 +} diff --git a/server/server.go b/server/dot.go similarity index 75% rename from server/server.go rename to server/dot.go index 3d9d300..73988b2 100644 --- a/server/server.go +++ b/server/dot.go @@ -7,7 +7,6 @@ import ( "os" "strconv" "strings" - "sync" "sync/atomic" "time" @@ -17,32 +16,9 @@ import ( "github.com/miekg/dns" ) -type DoTBomb struct { - Concurrency int - TotalRequest int - RequestIP string - RequestPort string - DomainArray []string - LastTimeout time.Duration -} - -type StressReport struct { - SendCount uint64 - RecvAnsCount uint64 - RecvNoAnsCount uint64 - TimeoutCount uint64 - OtherCount uint64 - StopSockCount uint64 - LastTime time.Duration -} - -var Result StressReport -var StatusChan = make(chan int, 1) -var wg sync.WaitGroup - -func (b DoTBomb) Start() { +func (b Bomb) DoT() { server := b.RequestIP + ":" + b.RequestPort - if !verify.VerifyDoTServer(server) { + if !verify.DoTServer(server) { log.Println("Cannot connect to DNS Over TLS Server:", server) os.Exit(0) return @@ -64,7 +40,7 @@ func (b DoTBomb) Start() { q := new(dns.Msg) // Resolve the query - dotClient, err := rdns.NewDoTClient("stress-dns-"+strconv.Itoa(count), server, rdns.DoTClientOptions{TLSConfig: &config}) + dotClient, err := rdns.NewDoTClient("stress-dot-"+strconv.Itoa(count), server, rdns.DoTClientOptions{TLSConfig: &config}) if err != nil { log.Println(err) wg.Done() diff --git a/server/stress.go b/server/stress.go new file mode 100644 index 0000000..837c08c --- /dev/null +++ b/server/stress.go @@ -0,0 +1,29 @@ +package server + +import ( + "sync" + "time" +) + +type Bomb struct { + Concurrency int + TotalRequest int + RequestIP string + RequestPort string + DomainArray []string + LastTimeout time.Duration +} + +type StressReport struct { + SendCount uint64 + RecvAnsCount uint64 + RecvNoAnsCount uint64 + TimeoutCount uint64 + OtherCount uint64 + StopSockCount uint64 + LastTime time.Duration +} + +var Result StressReport +var StatusChan = make(chan int, 1) +var wg sync.WaitGroup diff --git a/server/verify/dns.go b/server/verify/dns.go new file mode 100644 index 0000000..c64f795 --- /dev/null +++ b/server/verify/dns.go @@ -0,0 +1,24 @@ +package verify + +import ( + rdns "github.com/folbricht/routedns" + "github.com/miekg/dns" +) + +func DNSServer(dnsServer string) bool { + // Resolve the query + r, err := rdns.NewDNSClient("test-dns", dnsServer, "udp", rdns.DNSClientOptions{}) + if err != nil { + return false + } + + // Build a query + q := new(dns.Msg) + q.SetQuestion("www.google.com.", dns.TypeA) + + if _, err = r.Resolve(q, rdns.ClientInfo{}); err != nil { + return false + } + + return true +} diff --git a/server/verify/doh.go b/server/verify/doh.go new file mode 100644 index 0000000..a106d70 --- /dev/null +++ b/server/verify/doh.go @@ -0,0 +1,30 @@ +package verify + +import ( + "crypto/tls" + + rdns "github.com/folbricht/routedns" + "github.com/miekg/dns" +) + +func DoHServer(dohServer string) bool { + config := tls.Config{ + InsecureSkipVerify: true, + } + + // Resolve the query + r, err := rdns.NewDoHClient("test-doh", dohServer, rdns.DoHClientOptions{TLSConfig: &config}) + if err != nil { + return false + } + + // Build a query + q := new(dns.Msg) + q.SetQuestion("www.google.com.", dns.TypeA) + + if _, err = r.Resolve(q, rdns.ClientInfo{}); err != nil { + return false + } + + return true +} diff --git a/server/verify/dot_verify.go b/server/verify/dot.go similarity index 91% rename from server/verify/dot_verify.go rename to server/verify/dot.go index 30dee86..bff4443 100644 --- a/server/verify/dot_verify.go +++ b/server/verify/dot.go @@ -7,7 +7,7 @@ import ( "github.com/miekg/dns" ) -func VerifyDoTServer(dotServer string) bool { +func DoTServer(dotServer string) bool { config := tls.Config{ InsecureSkipVerify: true, }