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.