Technical Support manager at @heroku. Part of the @madrockclimbing climbing team.

Modify Your Ruby App's DNS Lookup

We recently had a Heroku support ticket where a user needed to modify their DNS lookup inside their app:

We’re connecting to an external API that requires a custom DNS entry. Locally, we modify our /etc/hosts file. How can we do this inside our app?

Modifying your /etc/hosts file is a common process for tweaking your local DNS lookup. Your machine will first check this file for any custom entries before proceeding to find the host address on your remote DNS server (usually provided by your ISP).

However, while modifying the /etc/hosts file on a remote server will work as well, this decreases the ability for your app to remain portable. In addition, this may create confusion for other team members that aren’t aware that you’ve modifed this file. Instead, let’s make the change inside our app using the Ruby standard library.

resolv.rb is a pure-Ruby DNS implementation, and it’s actually much better performance-wise when app concurrency grows. The resolv-replace.rb part replaces the default Ruby DNS resolution (e.g. system DNS) with resolv.rb.

To implement, we first require “resolv-replace”, then create a Resolv::Hosts resolver and a regular DNS resolver. That way, the app will will check the hosts first, then fallback to real DNS.

Let’s start by adding our entry to a custom_hosts file within our app:

$ echo 127.0.0.1 my.customserver.com > custom_hosts

You can now test this out in an irb session:

require 'resolv-replace'

hosts_resolver = Resolv::Hosts.new('custom_hosts')
dns_resolver = Resolv::DNS.new

Resolv::DefaultResolver.replace_resolvers([hosts_resolver, dns_resolver])

require "net/http"
require "uri"

You can now test the URL’s and ensure the DNS lookup behaves as necessary:

> Net::HTTP.get_response(URI.parse("http://google.com/"))
=> #<Net::HTTPFound 302 Found readbody=true>
> Net::HTTP.get_response(URI.parse("http://my.customserver.com/"))
Errno::ECONNREFUSED: Failed to open TCP connection to my.customserver.com:80 (Connection refused - connect(2) for "127.0.0.1" port 80)

As you can see above, the DNS for my.customserver.com resolves correctly as 127.0.0.1.

We now have custom DNS information within our app, instead of needing to modify your system’s /etc/hosts file. This allows you to keep this vital information contained within your app and under source control.

NOTE: using resolv.rb can cause some unwanted side effects like this one. Please make sure to test your implementation thoroughly!

© 2017 Jon McCartie