├── .gitignore ├── go.mod ├── README.md ├── go.sum └── dnsfaster.go /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bp0lr/dnsfaster 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/miekg/dns v1.1.35 7 | github.com/spf13/pflag v1.0.5 8 | ) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## dnsfaster 2 | 3 | 4 | ### why? 5 | 6 | I have started working on an app to brute force subdomains using mutations, permutations and alterations. 7 | This will test millions of combinations again a list of DNS servers. I need a tool to keep my DNS servers list clean and fast. 8 | I didn't found anything to fit what I want at 100%, so I take dnsfaster and I change some things. 9 | 10 | 11 | ### what dnsfaster does 12 | 13 | This tool will test (many times) your DNS list again a test domain to validate responses. 14 | This is useful if you want to keep your DNS list under an error rate or speed limit. 15 | 16 | 17 | ### Install 18 | 19 | Install is quick and clean 20 | ``` 21 | go get github.com/bp0lr/dnsfaster 22 | ``` 23 | 24 | 25 | ### examples 26 | ``` 27 | dnsfaster --domain example.com --in "dnslist.txt" --out "resolvers.txt" --tests 1000 --workers 50 --filter-time 400 --filter-errors 50 --filter-rate 90 --save-dns 28 | ``` 29 | 30 | this will run dnsfaster again the dns servers in dnslist.txt, testing 1000 times on each server using 50 workers, and filter out the domains with response time average > 400ms, errors > 50 and sucess rate < 90%. 31 | Use --save-dns if you want just the dns ip saved in the result file. 32 | 33 | 34 | ### Why dnsfaster was not forked 35 | 36 | the original author has moved out his repo to gitlab. 37 | You can visit his version at 38 | ``` 39 | https://gitlab.com/jules.rigaudie/dnsfaster 40 | ``` 41 | thanks @jules.rigaudie for your work. :) 42 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= 2 | github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 3 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 4 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 6 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= 7 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 8 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 9 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 10 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 11 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 12 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 13 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 14 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 15 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= 17 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 18 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 19 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 20 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 21 | -------------------------------------------------------------------------------- /dnsfaster.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "time" 8 | "strings" 9 | "sync" 10 | "math/rand" 11 | 12 | "github.com/miekg/dns" 13 | flag "github.com/spf13/pflag" 14 | ) 15 | 16 | const workerExit = "~" 17 | const workerNotifyExit = "!~" 18 | const separator = " ----------------------------------------------------------------" 19 | 20 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 21 | 22 | type result struct { 23 | dns string 24 | rtt float64 25 | } 26 | 27 | type resultStats struct { 28 | dns string 29 | rtt float64 30 | succ int 31 | fail int 32 | filtered bool 33 | } 34 | 35 | type testInfo struct { 36 | domain string 37 | dns string 38 | rtt float64 39 | } 40 | 41 | var ( 42 | numWorkersArg int 43 | numTestsArg int 44 | timefilterArg int 45 | errorfilterArg int 46 | ratefilterArg int 47 | 48 | saveJustDNSArg bool 49 | 50 | inArg string 51 | outArg string 52 | testDomainArg string 53 | 54 | globalDNSList []string 55 | ) 56 | 57 | func randStringBytes(n int) string { 58 | b := make([]byte, n) 59 | for i := range b { 60 | b[i] = letterBytes[rand.Intn(len(letterBytes))] 61 | } 62 | return string(b) 63 | } 64 | 65 | func getDNSList(DNSServersFile string) ([]string, error) { 66 | file, err := os.Open(DNSServersFile) 67 | if err != nil { 68 | return nil, fmt.Errorf("[!!!] Can't open file: %s", DNSServersFile) 69 | } 70 | defer file.Close() 71 | 72 | var lines []string 73 | scanner := bufio.NewScanner(file) 74 | for scanner.Scan() { 75 | lines = append(lines, scanner.Text()) 76 | } 77 | 78 | return lines, nil 79 | } 80 | 81 | func printHeader() { 82 | fmt.Println(` 83 | _ __ _ 84 | | | / _| | | 85 | __| |_ __ ___| |_ __ _ ___| |_ ___ _ __ 86 | / _' | '_ \/ __| _/ _' / __| __/ _ \ '__| 87 | | (_| | | | \__ \ || (_| \__ \ || __/ | 88 | \__,_|_| |_|___/_| \__,_|___/\__\___|_| 89 | 90 | `) 91 | 92 | fmt.Println(separator) 93 | fmt.Printf("| %7d threads | domain : %30s |\n", numWorkersArg, testDomainArg) 94 | fmt.Printf("| %7d tests | in file : %30s |\n", numTestsArg, inArg) 95 | fmt.Println(separator) 96 | fmt.Println("| status | ip | avg milsec | Rate | Succ | Fail |") 97 | fmt.Println(separator) 98 | } 99 | 100 | func workerResolverChecker(dc chan *testInfo, receiver chan *testInfo, baseDomain string) { 101 | for { 102 | test, ok := <-dc 103 | if !ok || test.dns == workerExit { 104 | break 105 | } 106 | 107 | if test.dns == workerNotifyExit { 108 | receiver<-nil 109 | break 110 | } 111 | 112 | c := dns.Client{} 113 | m := dns.Msg{} 114 | 115 | m.SetQuestion(test.domain + ".", dns.TypeA) 116 | r, rtt, err := c.Exchange(&m, test.dns + ":53") 117 | 118 | // make sure the server responds and returns no entry 119 | if err == nil && r != nil && r.Rcode == dns.RcodeNameError { 120 | test.rtt = float64(rtt/time.Millisecond) 121 | } 122 | receiver<-test 123 | } 124 | } 125 | 126 | 127 | func receiverService(rcv chan *testInfo, done chan bool) { 128 | 129 | var w *bufio.Writer 130 | 131 | results := make(map[string]*resultStats) 132 | 133 | defer func() { done<-true }() // close the channel once done 134 | 135 | if(len(outArg) > 0){ 136 | 137 | os.Remove(outArg) 138 | file, err := os.OpenFile(outArg, os.O_WRONLY | os.O_CREATE, 0644) 139 | if err != nil { 140 | fmt.Println("[!] Can't open file to save: ", outArg) 141 | return 142 | } 143 | 144 | defer file.Close() 145 | 146 | w = bufio.NewWriter(file) 147 | } 148 | 149 | for { 150 | 151 | result, ok := <-rcv 152 | if !ok || result == nil{ 153 | break 154 | } 155 | 156 | _, prs := results[result.dns] 157 | if !prs { 158 | results[result.dns] = new(resultStats) 159 | results[result.dns].dns = result.dns 160 | } 161 | 162 | cur := results[result.dns] 163 | 164 | if result.rtt == -1 { 165 | cur.fail++ 166 | } else { 167 | cur.rtt += result.rtt 168 | cur.succ++ 169 | } 170 | 171 | if cur.succ + cur.fail == numTestsArg { 172 | if cur.rtt != 0 { 173 | cur.rtt = cur.rtt / float64(cur.succ) 174 | } 175 | succP := cur.succ*100/numTestsArg 176 | 177 | if timefilterArg > 0 && int(cur.rtt) > timefilterArg{ 178 | //fmt.Printf("%v filtered by time!: %v || %v || %v \n", cur.dns, timefilterArg, int(cur.rtt), cur.rtt) 179 | cur.filtered = true 180 | } 181 | 182 | if errorfilterArg > 0 && cur.fail > errorfilterArg{ 183 | //fmt.Printf("%v filtered by error!: %v || %v \n", cur.dns, errorfilterArg, cur.fail) 184 | cur.filtered = true 185 | } 186 | 187 | if ratefilterArg > 0 && succP < ratefilterArg{ 188 | //fmt.Printf("%v filtered by ratelimit!: %v || %v\n", cur.dns, ratefilterArg, succP) 189 | cur.filtered = true 190 | } 191 | 192 | if(cur.filtered){ 193 | fmt.Printf("| FILTER | %17s | %10v | %3d%% | %5d | %5d |\n", cur.dns, int(cur.rtt), succP, cur.succ, cur.fail) 194 | }else{ 195 | fmt.Printf("| OK | %17s | %10v | %3d%% | %5d | %5d |\n", cur.dns, int(cur.rtt), succP, cur.succ, cur.fail) 196 | } 197 | 198 | var s string 199 | if(saveJustDNSArg){ 200 | s = fmt.Sprintf("%s\n", cur.dns) 201 | } else{ 202 | s = fmt.Sprintf("%s,%v,%d,%d,%d\n", cur.dns, int(cur.rtt), succP, cur.succ, cur.fail) 203 | } 204 | 205 | if(len(outArg) > 0){ 206 | if(!cur.filtered) { 207 | if _, err := w.WriteString(s); err != nil { 208 | fmt.Println(err) 209 | os.Exit(1) 210 | } 211 | } 212 | } 213 | } 214 | } 215 | if(len(outArg) > 0){ 216 | if err := w.Flush(); err != nil { 217 | fmt.Println(err) 218 | os.Exit(1) 219 | } 220 | } 221 | 222 | fmt.Println(separator) 223 | } 224 | 225 | func distributorService(){ 226 | 227 | rand.Seed(time.Now().UnixNano()) 228 | 229 | printHeader() 230 | 231 | if(len(globalDNSList) < 1){ 232 | fmt.Printf("The DNS server list is empty, I can't go.\n") 233 | os.Exit(0) 234 | } 235 | 236 | // pregenerate test cases 237 | var domains []string 238 | for i := 0; i < numTestsArg; i++ { 239 | domains = append(domains, strings.Join([]string{randStringBytes(8), ".", testDomainArg}, "")) 240 | } 241 | 242 | dc := make(chan *testInfo, 1000) 243 | receiver := make(chan *testInfo, 250) 244 | 245 | rcvDone := make(chan bool) 246 | 247 | go receiverService(receiver, rcvDone) 248 | 249 | for i := 0; i < numWorkersArg; i++ { 250 | go workerResolverChecker(dc, receiver, testDomainArg) 251 | } 252 | 253 | for i := 0; i < numTestsArg; i++ { 254 | for _, dns := range globalDNSList { 255 | test := new(testInfo) 256 | test.dns = dns 257 | test.domain = domains[i] 258 | test.rtt = -1 259 | dc<-test 260 | } 261 | } 262 | 263 | for i := 0; i < numWorkersArg; i++ { 264 | test := new(testInfo) 265 | if i+1 == numWorkersArg { // last worker notifies receiver 266 | test.dns = workerNotifyExit 267 | } else { 268 | test.dns = workerExit 269 | } 270 | dc<-test 271 | } 272 | 273 | <-rcvDone 274 | } 275 | 276 | func checkDNSList(DNSServerList []string){ 277 | 278 | jobs := make(chan string) 279 | var wg sync.WaitGroup 280 | 281 | for i := 0; i < 10; i++ { 282 | wg.Add(1) 283 | go func() { 284 | for task := range jobs { 285 | v:=checkTruncation(task) 286 | if(!v){ 287 | globalDNSList = append(globalDNSList, task) 288 | } 289 | } 290 | wg.Done() 291 | }() 292 | } 293 | 294 | for _, line := range DNSServerList { 295 | jobs <- line 296 | } 297 | 298 | close(jobs) 299 | wg.Wait() 300 | } 301 | 302 | func checkTruncation(DNSServer string)bool{ 303 | 304 | var total int = 0 305 | 306 | for i := 0; i < 20; i++ { 307 | 308 | c := dns.Client{} 309 | m := dns.Msg{} 310 | 311 | m.SetQuestion("example.com" + ".", dns.TypeA) 312 | r, _, err := c.Exchange(&m, DNSServer + ":53") 313 | 314 | if(err != nil){ 315 | //fmt.Printf("err: %v\n", err) 316 | total++ 317 | continue 318 | } 319 | 320 | if (r != nil && r.Truncated) { 321 | //fmt.Printf("[truncate | %v]: %v\n", DNSServer, m.Question[0].String()) 322 | total++ 323 | } 324 | } 325 | 326 | if(total == 0){ 327 | //fmt.Printf("[%v] truncation OK\n", DNSServer) 328 | return false 329 | } 330 | 331 | //fmt.Printf("[%v] truncation FAIL\n", DNSServer) 332 | return true 333 | } 334 | 335 | func main() { 336 | 337 | flag.StringVar(&inArg, "in", "", "DNS servers list") 338 | flag.StringVar(&outArg, "out", "", "Output file to save the results to") 339 | flag.StringVar(&testDomainArg, "domain", "example.com", "Domain name to test against") 340 | 341 | flag.IntVar(&numWorkersArg, "workers", 10, "Number of workers") 342 | flag.IntVar(&numTestsArg, "tests", 10, "Number of test again each dns server") 343 | flag.IntVar(&timefilterArg, "filter-time", 0, "Filter results with average response time higher than") 344 | flag.IntVar(&errorfilterArg, "filter-errors", 0, "Filter results with error number higher than") 345 | flag.IntVar(&ratefilterArg, "filter-rate", 0, "Filter results with average success rate less than") 346 | 347 | flag.BoolVar(&saveJustDNSArg, "save-dns", true, "Save just the DNS hostname") 348 | 349 | flag.Parse(); 350 | 351 | if numWorkersArg < 1 || numWorkersArg > 251 { 352 | fmt.Fprintf(os.Stderr, "[!] Invalid number of workers: %d\n", numWorkersArg) 353 | return 354 | } 355 | 356 | if numTestsArg < 1 || numTestsArg > 5000{ 357 | fmt.Fprintf(os.Stderr, "[!] Invalid number of tests: %d\n", numTestsArg) 358 | return 359 | } 360 | 361 | var localDNSList []string 362 | localDNSList, _ = getDNSList(inArg) 363 | 364 | checkDNSList(localDNSList) 365 | distributorService() 366 | } 367 | --------------------------------------------------------------------------------