Resolve SystemStackError issue when resolving IP address in Ruby
In Ruby, Resolv is the default DNS resolution implementation. It can be used to resolve IP address of a hostname. To use it, one just needs to require 'resolv' in the code. But sometimes, a user would want to check /etc/hosts first or some other mechanisms to resolve an IP address. In this case, one can require 'resolv-replace' and then replace the default DNS resolvers with customized DNS resolvers.
For example, using resolv-replace, one would write
require 'resolv-replace' Resolv::DefaultResolver.replace_resolvers([Resolv::Hosts.new, Resolv::DNS.new]) Resolv.getaddress 'some-site.com'
In this case, it would first go to the /etc/hosts and look up some-site.com, if its IP is found there, that one will be returned. Otherwise, the real DNS server will be searched for, those DNS servers are usually defined in /etc/resolv.conf. The file looks like
One can also replace other DNS resolver implementation as well. This resolv-replace has become very popular in Ruby and Rails applications. But this would cause some problem in some cases if something is wrongly configured in /etc/resolv.conf. One of the issues would be "SystemStackError: stack level too deep". Why would the stack overflows? There must be some recursion happens. The backtrace would look like
SystemStackError: stack level too deep from /usr/lib/ruby/1.8/resolv.rb:1336:in `put_labels' from /usr/lib/ruby/1.8/resolv.rb:1330:in `each_index' from /usr/lib/ruby/1.8/resolv.rb:1330:in `put_labels' from /usr/lib/ruby/1.8/resolv.rb:1326:in `put_name' from /usr/lib/ruby/1.8/resolv.rb:1272:in `encode' from /usr/lib/ruby/1.8/resolv.rb:1270:in `each' from /usr/lib/ruby/1.8/resolv.rb:1270:in `encode' from /usr/lib/ruby/1.8/resolv.rb:1290:in `initialize' from /usr/lib/ruby/1.8/resolv.rb:1256:in `new' from /usr/lib/ruby/1.8/resolv.rb:1256:in `encode' from /usr/lib/ruby/1.8/resolv.rb:686:in `sender' from /usr/lib/ruby/1.8/resolv.rb:487:in `each_resource' from /usr/lib/ruby/1.8/resolv.rb:971:in `resolv' from /usr/lib/ruby/1.8/resolv.rb:969:in `each' from /usr/lib/ruby/1.8/resolv.rb:969:in `resolv' from /usr/lib/ruby/1.8/resolv.rb:968:in `each' ... 3200 levels... from /usr/lib/ruby/1.8/resolv.rb:971:in `resolv' from /usr/lib/ruby/1.8/resolv.rb:969:in `each' from /usr/lib/ruby/1.8/resolv.rb:969:in `resolv' from /usr/lib/ruby/1.8/resolv.rb:968:in `each' from /usr/lib/ruby/1.8/resolv.rb:968:in `resolv' from /usr/lib/ruby/1.8/resolv.rb:966:in `each' from /usr/lib/ruby/1.8/resolv.rb:966:in `resolv' from /usr/lib/ruby/1.8/resolv.rb:481:in `each_resource' from /usr/lib/ruby/1.8/resolv.rb:386:in `each_address' from /usr/lib/ruby/1.8/resolv.rb:115:in `each_address' from /usr/lib/ruby/1.8/resolv.rb:114:in `each' from /usr/lib/ruby/1.8/resolv.rb:114:in `each_address' from /usr/lib/ruby/1.8/resolv.rb:92:in `getaddress' from /usr/lib/ruby/1.8/resolv.rb:43:in `getaddress'
From the backtrace and the source code of resolv-replace.rb, we would find that the Resolv tries to resolve the IP address, it would loop through all the nameservers defined in the /etc/resolv.conf. However, if the nameserver is not an IP address but a hostname as well. Then this hostname needs to be resolved first, so to resolve this hostname, it would search for the /etc/resolv.conf again and do this again and again until the SystemStackError occurs and the application is down.
See below how resolv-replace.rb is implemented. When contacting the DNS server, it would call Resol.getaddress again.
To resolve this issue, need to check the /etc/resolv.conf and ensure that the nameserver is an IP address instead of a hostname.