projectdiscovery/retryabledns

retryabledns seems to only use the last dns resolver.

yaabdala opened this issue · 3 comments

func (c *Client) Do(msg *dns.Msg) (*dns.Msg, error) {

In the Do function, the last resolver in the list of resolvers is selected

resolver := c.resolvers[index%uint32(len(c.resolvers))]

And the client attempts to resolve against this resolver up to the max retries. However there is no logic to cycle to the next resolver if the current one fails or returns NXDOMAIN.

Currently, behavior is that if the last DNS resolver fails, the client returns failure, the other resolvers are never used. You can see this in dnsx using a host file like

yahoo.com
007yahoo.com

with resolvers resolvers := []string{"8.8.8.8", "74.6.35.130"}

If you swap the order of the resolvers, it will fail. But using the yahoo NS server, it succeeds. I would expect if the google ns server fails, the yahoo one would be used.

Thanks

Can you provide a go snippet to reproduce this behavior?

The iteration logic is just above the line that you have mentioned:

index := atomic.AddUint32(&c.serversIndex, 1)
resolver := c.resolvers[index%uint32(len(c.resolvers))]

The instance internal counter c.serversIndex gets incremented atomically, and the modulo between it and the number of resolvers is used as an index of the resolvers slice. If you add a log.Println within the function to dump the resolver used, you'd see something like this:

for i := 0; i < c.options.MaxRetries; i++ {
	index := atomic.AddUint32(&c.serversIndex, 1)
	resolver := c.resolvers[index%uint32(len(c.resolvers))]
	log.Println(i, resolver)
	...

Output:

2023/01/07 20:31:42 0 1.0.0.1:53
2023/01/07 20:31:42 1 8.8.8.8:53
2023/01/07 20:31:42 2 8.8.4.4:53
2023/01/07 20:31:42 3 1.1.1.1:53
2023/01/07 20:31:42 4 1.0.0.1:53
2023/01/07 20:31:42 0 8.8.8.8:53
2023/01/07 20:31:42 1 8.8.4.4:53
2023/01/07 20:31:42 2 1.1.1.1:53
2023/01/07 20:31:42 3 1.0.0.1:53
2023/01/07 20:31:42 4 8.8.8.8:53
2023/01/07 20:31:42 0 8.8.4.4:53
2023/01/07 20:31:42 1 1.1.1.1:53
2023/01/07 20:31:42 2 1.0.0.1:53
2023/01/07 20:31:42 3 8.8.8.8:53
2023/01/07 20:31:42 4 8.8.4.4:53

This might be an issue with how dnsx implements retryabledns but you should be able to replicate with

func main() {
	dnsxOption := dnsx.DefaultOptions
	resolvers := []string{"8.8.8.8", "74.6.35.130"}
	dnsxOption.BaseResolvers = resolvers

	dnsClient, err := dnsx.New(dnsxOption)
	check(err)

	fmt.Println("Lookup: yahoo.com")
	result, err := dnsClient.Lookup("yahoo.com")
	check_soft(err)
	for idx, msg := range result {
		fmt.Printf("%d: %s\n", idx+1, msg)
	}

	fmt.Println("Lookup: 007yahoo.com")
	result, err = dnsClient.Lookup("007yahoo.com")
	check_soft(err)
	for idx, msg := range result {
		fmt.Printf("%d: %s\n", idx+1, msg)
	}
}

Result:

Lookup: yahoo.com
err: no ips found
Lookup: 007yahoo.com
1: 212.82.100.150
2: 98.136.103.23
3: 74.6.136.150

Now swap the dns resolver order resolvers := []string{"74.6.35.130", "8.8.8.8"}
Result:

Lookup: yahoo.com
1: 98.137.11.163
2: 98.137.11.164
3: 74.6.143.25
4: 74.6.231.20
5: 74.6.231.21
6: 74.6.143.26
Lookup: 007yahoo.com
err: no ips found

We can see that because 007yahoo.com is not propagated to upstream dns servers, it's only resolved by the yahoo nameserver. When supplying multiple dns servers, I expect that all of them would be checked when performing a lookup.

Probably this problem is the same that got fixed with projectdiscovery/dnsx#288 (merged in the dev branch):

$ go run .
Lookup: yahoo.com
1: 98.137.11.163
2: 74.6.231.20
3: 74.6.143.26
4: 74.6.143.25
5: 74.6.231.21
6: 98.137.11.164
Lookup: 007yahoo.com
1: 212.82.100.150
2: 74.6.136.150
3: 98.136.103.23