I have virtual machines in VMware Fusion which need to find one-another by hostname, such as Puppet clients talking to a Puppet master. The VMware Fusion DNS forwarder will resolve names in my Mac’s /etc/hosts file for my VMs, but the lookup returns an extra CName record, and then multiple NXDOMAIN responses because additional unnecessary lookups are performed.
[root@vm etc]# host -v web1.fetch. Trying "web1.fetch" ;; ->>HEADER<
[root@vm etc]# ping -c1 web1.fetch. PING web1.fetch (172.16.74.31) 56(84) bytes of data. 64 bytes from 172.16.74.31: icmp_seq=1 ttl=64 time=0.025 ms --- web1.fetch ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 2ms rtt min/avg/max/mdev = 0.025/0.025/0.025/0.000 ms
While the above technically works; I can ping web1 by name, it bugs me. Why the extra CName, even when I lookup a fully qualified hostname with a trailing period? Why the extra lookups for the same name, which fail (NXDOMAIN) even when my VM’s resolver is not configured with any search domains? If anyone knows the answers to the above, please enlighten me.
A proper DNS server which both my Mac and VMs can use seems like a good idea – here are my requirements:
- I can resolve my local DNS from both my Mac and my VMs, without having to rely on a particular VM being started. This means the solution should run on my Mac.
- The DNS server is only consulted by my Mac, when looking up hostnames in my private domain / DNS zone, otherwise I want the normal name resolution to be used. This allows me to leverage DHCP-provided DNS servers to resolve hostnames on networks I visit.
- What ever I run should be light weight – I don’t want to have to remember to stop and start something when I am working with my VMs, nor run Bind, when I only need this local DNS 30-40% of the time.
I chose Dnsmasq because it is lightweight, designed for small networks, and gives me the option to provide more customizable DHCP, and network boot, in private Fusion networks in the future. I will use the OSX DNS resolver’s /etc/resolver/* syntax, to point a private DNS domain at Dnsmasq, while leaving DNS lookups for everything else untouched. I can add private DNS entries to the /etc/hosts file on my Mac (which Dnsmasq reads by default), or put Dnsmasq DNS entries in a separate file.
The only down-side is that I have to configure a static DNS server on my VMs, but my VMs use static IP addresses anyway. If I wanted DHCP to configure VMs to use Dnsmasq instead of Fusion’s DNS forwarder, I could either hack the Fusion DHCP server, or disable the Fusion DHCP service and use Dnsmasq to provide DHCP on Fusion’s private subnets.
I used Homebrew – The missing package manager for OS x to install Dnsmasq. Homebrew installs software into it’s own directory, helping to avoid a package’s installation spewing files throughout your Mac.
Install Dhsmasq using the brew install dnsmasq command.
After Dnsmasq is installed, Homebrew presents you with some commands to run in order to add dnsmasq to the OSX launched, agent and daemon manager – in other words, to have OSX start Dnsmasq at boot. IF you miss these commands, you can re-display this information by running brew info dnsmasq. You should use the commands which Homebrew tells you to run at your time of installation, but here is what was displayed to me:
To configure dnsmasq, copy the example configuration to /usr/local/etc/dnsmasq.conf and edit to taste. cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf To have launchd start dnsmasq at startup: sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons Then to load dnsmasq now: sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
After running the commands Homebrew tells you to, you will have a running Dnsmasq server – you can verify it is running and query it:
ifetch@mac:~$ host google.com localhost Using domain server: Name: localhost Address: ::1#53 Aliases: google.com has address 18.104.22.168 google.com has address 22.214.171.124 ..... and so on .....
Choosing a Private Domain
I use a top-level domain of “fetch” which means my hosts will be puppet.fetch, web1.fetch, Etc. Obviously .fetch is not a real top-level domain (TLD), but is alright for local use, only on my Mac. Note that if I were using this domain on a private network, beyond only my Mac, I would more closely follow the guidelines below.
The best practice when picking a private domain, is to use a sub-domain of a domain which you already have registered (like lab.MyCompany.com), or to use a domain under one of the reserved top-level domains in RFC 2606.
- Choose an internal domain name which will not be used on the Internet. In theory any top-level domain, like .fetch, could be used on the Internet in the future. The safest bet is to use a sub-domain under a domain which you have registered; control of.
- RFC 2606 lists the top-level domains .test, .example, .invalid, and .localhost as reserved for testing. The domains example.com, example.net, and example.org are also reserved, if you really lack originality and want to use one of those.
Using the above guidelines, any of these would be valid domains to put my hosts into:
- lab.IvanFetch.com – I own and control IvanFetch.com, so this is valid
- test – using the top-level domain of test, so my hosts would be puppet.test, web1.test, Etc.
Edit /usr/local/etc/dnsmasq.conf as root – below are some minimal options to add.
The local= option tells Dnsmasq to not send queries for this domain to any upstream DNS servers. This option is not strictly necessary because Dnsmasq will serve any entries in my Mac’s /etc/hosts file, but to play it safe, we’ll avoid the potential of querying the Internet for my .fetch domain.
I don’t want Dnsmasq to listen on all of my Mac’s network interfaces. This can be controlled by either specifying an explicit list of interfaces to listen on, or specifying a list of interfaces to explicitly not be listen on. IF you often change your VMware Fusion network configuration, or add/remove networks, it may be more desirable to tell Dnsmasq which network interfaces you do not want it to listen on. I told Dnsmasq to not listen on my ethernet, wireless, and VPN interfaces using multiple occurrences of the except-interface= option.
Here is the configuration I’ve added to my dnsmasq.conf file:
# Queries for .fetch should never be sent to upstream nameservers. local=/fetch/ # Listen on all interfaces except wired, wireless, and our VPN. except-interface=en0 except-interface=en1 except-interface=jnc0 except-interface=utun0
Other Interesting Options
Here are some other interesting Dnsmasq options you may want to look more into. Each of the bullets below lists multiple options, as they tend to be used in combination with one-another.
- to expand short; non-fully-qualified hostnames in /etc/hosts, into fully-qualified hostnames
- no-hosts and addn-hosts which allow you to not use /etc/hosts, and use a different file to supply local hostnames
- address to map an entire domain to a single IP address, similar to defining a wild-card DNS
Create Local DNS Entries
By default Dnsmasq will serve entries from the Mac’s /etc/hosts file. To store local DNS entries in a separate file, add these two options to the dnsmasq.conf configuration file:
# Do not read entries from /etc/hosts, and use a separate file instead. no-hosts addn-hosts=/etc/hosts-fetch
Put your private DNS domain entries in /etc/hosts, or which ever file you chose to use.
172.16.74.31 web1.fetch 172.16.74.32 puppet.fetch puppetmaster.fetch
Restart Dnsmasq, now that you have edited it’s configuration file.
The launched OSX service will restart Dnsmasq if it is stopped. Therefore, if you need to restart Dnsmasq after editing it’s configuration file, you can just kill Dnsmasq using sudo pkill dnsmasq and let launched restart it.
Alternatively, run sudo launchctl stop homebrew.mxcl.dnsmasq – this will stop Dnsmasq, and launched will promptly restart it. IF you forget the homebrew.mxcl.dnsmasq identifier, you can find it by running something like sudo launchctl list |grep -i dns
Stopping Dnsmasq All Together
IF you do not want Dnsmasq to be running for some reason, you can stop it with sudo launchctl unload /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist. You can then start it again with sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist. IF you unload the Dnsmasq service, then reboot your Mac, Dnsmasq will start at boot.
Point The OSX Resolver at Dnsmasq
The OSX DNS resolver will only consult Dnsmasq when a hostname in the .fetch domain needs to be looked up.
Create the /etc/resolver directory, and then create a resolver file named after your domain – in my case, fetch – pointing to Dnsmasq on 127.0.0.1.
sudo mkdir /etc/resolver sudo vi /etc/resolver/fetch
The contents of /etc/resolver/fetch should be:
' nameserver 127.0.0.1
The Mac will use Dnsmasq when ever I attempt to access a hostname within my private DNS domain, such as when using ping or a web browser. Using the nslookup or host command to lookup such a hostname will not work, because those commands are querying upstream DNS servers directly.
Point VMs at Dnsmasq
Configure the DNS resolver in virtual machines, to point to the IP address of the Mac in which ever network the VM is using. This is the .1 IP address in the subnet – for example, if a VM has an IP address of 172.16.74.30, configure the VM to use 172.16.74.1 for DNS. For a VM running Linux, edit /etc/resolv.conf to look like:
domain fetch nameserver 172.16.74.1 search fetch
My Linux VMs happen to have a second NIC, in the host-only Fusion network, which I use to SSH to the VM from my Mac. This has an added benefit of giving me a fixed static IP address to point to for DNS, even if I have changed the VM’s first NIC between NAT and bridged Fusion networks. The two NIC approach may not be desirable for others though, and you may find that switching your VM’s NIC to another Fusion network, breaks the VM’s ability to talk to your internal DNS server.