Monday, November 5, 2012

OSX, VirtualBox, NAT and static DHCP



Update Oct, 2014: the guide was slightly modified to support new OSX 10.10 Yosemite. The changes are minor, so I believe it still works for the previous versions, but I didn’t check it.

There's an issue that VirtualBox doesn't allow to configure VMs' static IP-addresses by, for example, their MAC-addresses.

In general the solution is quite simple:
  1. run DHCP server on the host on the VM-ethernet interface (vboxnet0);
  2. allow access from VMs to the external network via NAT.
Step by step solution
en0 - ethernet interface
en1 - WiFi interface
192.168.56.0/24 - internal virtual network for VMs
  1. in VirtualBox disable DHCP-server;
  2. for each VM set network adapter to Host-only;
  3. install (I used brew) dnsmasq;
  4. in dnsmasq conf-file (/usr/local/etc/dnsmasq.conf) configure DHCP-settings;
    • set the property dhcp-leasefile=/usr/local/etc/dnsmasq.leases;
    • interface listen to (interface=vboxnet0);
    • IP-range for dynamic IP-addresses (dhcp-range=...);
    • static IPs for particular MAC-addresses(dhcp-host=...);
  5. configure dnsmasq to start as daemon;
  6. enable port forwarding
    • sudo sysctl -w net.inet.ip.forwarding=1
  7. in /etc/pf.conf add line after nat-anchor "com.apple/*"
    • nat on { en0 en1 } from 192.168.56.0/24 to any -> { (en0) (en1) }
  8. load rules into pf
    • pfctl -F all -f /etc/pf.conf
  9. enable pf with command
    • pfctl -e

Some additional hints

IP-forwarting can be enabled permanently. For this add into /etc/sysctl.conf (create this file if it doesn't exist):
  • net.inet.ip.forwarding=1
To enable pf on boot open file /System/Library/LaunchDaemons/com.apple.pfctl.plist and add -e to ProgramArguments:
        <key>ProgramArguments</key>
        <array>
                <string>pfctl</string>
                <string>-f</string>
                <string>/etc/pf.conf</string>
                <string>-e</string>
        </array>

To run dnsmasq as daemon create file /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist with this content:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>homebrew.mxcl.dnsmasq</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/sbin/dnsmasq</string>
      <string>--keep-in-foreground</string>
    </array>
    <key>KeepAlive</key>
    <true/>
  </dict>
</plist>

Then register it:
  • sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist

The virtual network interface vboxnet0 is created only when you run one of the VMs. To create it on boot I wrote a script and place it in ~/.scripts/vboxnet0.sh:
#!/bin/bash

VBoxManage list hostonlyifs > /dev/null
VBoxManage hostonlyif ipconfig vboxnet0 --ip 192.168.56.1 > /dev/null

Don't forget to execute chmod a+x ~/.scripts/vboxnet0.sh

Then I've created ~/Library/LaunchAgents/virtualbox.vboxnet0.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>virtualbox.vboxnet0</string>
    <key>ProgramArguments</key>
    <array>
      <string>/Users/Oleg/.scripts/vboxnet0.sh</string>
    </array>
    <key>RunAtLoad</key>
        <true/>
  </dict>
</plist>
*Don't forget to change the username to yours :-)

And registered it:
  • launchctl load ~/Library/LaunchAgents/virtualbox.vboxnet0.plist
Thus dnsmasq doesn't crash with the message that there's no interface vboxnet0.

10 comments:

  1. Do you mean "nat on { en0 vboxnet0 } from 192.168.56.0/24 to any -> { (en0) (vboxnet0) }" instead of "nat on { en0 en1 } from 192.168.56.0/24 to any -> { (en0) (en1) }" ?

    ReplyDelete
  2. In my case it works exactly as I wrote in the post. I can't provide details now, because I did the investigation almost 3 years ago :-)
    The only thing I can say is that "man pfconf" contains this example:
    # NAT
    # Translate outgoing packets' source addresses (any protocol).
    # In this case, any address but the gateway's external address is mapped.
    nat on $ext_if inet from ! ($ext_if) to any -> ($ext_if)

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Oops... I looked into the config I use and found a mistake:
      In the post I used instead of correct tag.

      I fixed it in the post.

      Hope it will work for you.

      Delete
  4. Could you please specify where the mistake was? =) Should I additionally install DHCP server on OSX. It seems that my VM guest does not get any IP address from OS X

    ReplyDelete
    Replies
    1. I have just checked with internal VirtualBox DHCP server, all works, except that fact VirtualBox does not send information about default gateway in DHCP leases to VM guests. So the question is start DHCP server on OSX or have it as a separated VM =)

      Delete
    2. OK, I got the point. dnsmasq - it is a DHCP server. But it won't work. It is started and I see it in processes list, but something with config or firewall maybe

      Delete
    3. The comment didn't show the tag :-). It was [/true] instead of [true/] in the config.
      Check NAT config for pf and the net.inet.ip.forwarding=1 property. I found that sometimes OSX rewrites pf.conf with the default configuration. If this case all changes should be applied again.

      Delete
    4. It works now.
      I fixed my mistake for dhcp-range parameter in dnsmasq.conf. It has values from different subnetwork than server specified in vboxnet0.sh.

      Also I have added dhcp-option=option:router,192.168.56.1 in dnsmasq.conf, without this parameter my VMs did not get gateway :)

      Thanks. Your post is #1 in Google search according this topic :)

      Delete