Unified Mac and Fusion VM hostnames with Dnsmasq
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 /etc/hosts file for my VMs, but the lookup returns an extra CName record, and then multiple NXDOMAIN responses because additional unnecessary lookups were performed.
[root@vm etc]# host -v web1.fetch.
Trying "web1.fetch"
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41003
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;web1.fetch. IN A
;; ANSWER SECTION:
web1.fetch. 5 IN CNAME web1.fetch.
web1.fetch. 5 IN A 172.16.74.31
Received 68 bytes from 192.168.78.2#53 in 21 ms
Trying "web1.fetch"
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6379
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;web1.fetch. IN AAAA
;; ANSWER SECTION:
web1.fetch. 5 IN CNAME web1.fetch.
web1.fetch. 5 IN A 172.16.74.31
Received 68 bytes from 192.168.78.2#53 in 18 ms
Trying "web1.fetch"
Received 103 bytes from 192.168.78.2#53 in 31 ms
Trying "web1.fetch.localdomain"
Trying "web1.fetch.fetch"
Host web1.fetch not found: 3(NXDOMAIN)
Received 109 bytes from 192.168.78.2#53 in 28 ms
I can sitll reach the host - it isn’t as though the DNS lookup doesn’t produce the right result in the end:
[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, why the extra CName in the result? Even though I looked up a fully qualified hostname with a trailing period, why the extra lookups of .fetch.fetch
and .local
?
A proper DNS server which both my Mac and VMs can use seems like a good idea:
- 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; 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’m visiting.
- 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 - 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 (meaning network boot) options to VMs. I will use the OSX DNS resolver’s /etc/resolver/* syntax, to point a private DNS domain at Dnsmasq, while leaving other DNS lookups untouched. I can still add private DNS entries to the /etc/hosts file on my Mac (which Dnsmasq reads and serves 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 they use static IP addresses anyway. If I wanted DHCP to configure VMs to use Dnsmasq instead of using 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.
###Install Dnsmasq I used Homebrew to install Dnsmasq. Homebrew installs software into it’s own directory, helping to avoid an 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 configure Dnsmasq to start 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 74.125.225.206 google.com has address 74.125.225.195 ….. 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 one 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.</li>
* [RFC 2606](http://tools.ietf.org/html/rfc2606) 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.
### Configuring Dnsmasq
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 by listing it here.
* 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 <strong>not</strong> 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.
* `expand-hosts` and `domain` - to expand short; non-fully-qualified hostnames in /etc/hosts, into fully-qualified hostnames
* `no-hosts` and `addn-hosts` - Use a file other than /etc/hosts to supply local entries to Dnsmasq.
* `address` - 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
Then 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
Restart Dnsmasq, now that you have edited it's configuration file. IF killed, the launched OSX service will restart Dnsmasq. 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 OS X 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.