Skip to content

VirtualBox: switching to Host-only networking

There are many ways to provide network connectivity to VirtualBox guests. The most common ones, in short:

  • Network Address Translation (NAT): this is the default mode. VirtualBox will act as a DHCP server, providing guests with internal addresses and connectivity to the outside world. But no routing is provided and thus guests cannot be reached from the outside.
  • Bridged Networking: a virtual NIC is bridged to a physical NIC on the host host, guests have full network connectivity and can be reached from the outside world. However, an external DHCP and DNS service may be needed.
  • Internal networking: similar to bridged networking, but only the host and guests on the same host will be able to connect to the guest.
  • Host-only networking: a hybrid between bridged and internal networking. Guests can connect to each other, but no real NIC has to be present on the host. DHCP / DNS can be provided by VirtualBox or externally.
For a long time I've just used Bridged networking - it was easy to setup and worked like a charm. Of course, this incurred some administrative overhead: for every VM a DNS name had to be registered. At home, dnsmasq is running on the router and will be able to provide DHCP & DNS to the guests. With static names and IP addresses for the guests, a simple mapping scheme had to be implemented:

  • When a new VM gets created, modify its MAC address to correspond to a certain range and match the last octet to its (future) IP address. E.g. for a guest with the future IP address of 192.168.0.31, set its MAC address to 08:00:27:e2:81:31.
  • Add both entries to /etc/ethers and the IP address / hostname mapping to /etc/hosts.
This worked very well for a long time but was always dependent on the dnsmasq installation being around. When connected to a different network, the guests will not be able to rely on the DHCP & DNS setup. Also, the physical NIC the guest network is bridged to may not be online. Think of laptops, sometimes connecting via Wifi, sometimes via ethernet.

And so I decided to take a look at Host-only networking. First we have to create (and configure) the host-only interface:
$ vboxmanage hostonlyif create 
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Interface 'vboxnet0' was successfully created
$ vboxmanage hostonlyif ipconfig vboxnet0 --ip 192.168.56.1
Disable any VirtualBox DHCP servers:
$ vboxmanage list dhcpservers
[...]
$ vboxmanage dhcpserver remove --netname NetworkName
$ vboxmanage list hostonlyifs
Name:            vboxnet0
GUID:            607cb20b-9848-4313-b522-3ccd6cd01be9
DHCP:            Disabled
IPAddress:       192.168.56.1
NetworkMask:     255.255.255.0
IPV6Address:     fe80:0000:0000:0000:0800:27ff:fe00:0000
IPV6NetworkMaskPrefixLength: 64
HardwareAddress: 0a:00:27:00:00:00
MediumType:      Ethernet
Status:          Up
VBoxNetworkName: HostInterfaceNetworking-vboxnet0
With the virtual NIC in place, we have to configure the guests:
$ vboxmanage showvminfo vm1 | grep NIC\ 1
NIC 1:           MAC: 080027e28131, Attachment: Bridged Interface 'wlan0', ...

$ vboxmanage modifyvm vm1 --nic1 hostonly --hostonlyadapter1 vboxnet0
$ vboxmanage showvminfo vm1 | grep NIC\ 1
NIC 1:           MAC: 080027e28131, Attachment: Host-only Interface 'vboxnet0', ...
Although we could use the internal DHCP server from VirtualBox, we would not be able to provide our elaborate mapping scheme. Let's setup a small, local dnsmasq installation:
$ sudo apt-get install dnsmasq-base
$ tail -n2 /etc/{ethers,hosts}
==> /etc/ethers <==
08:00:27:e2:81:30       vm0
08:00:27:e2:81:31       vm1

==> /etc/hosts <==
192.168.56.30  vm0
192.168.56.31  vm1

$ grep ^[a-z] dnsmasq.conf 
dhcp-authoritative
read-ethers
dhcp-leasefile=/var/run/dnsmasq/dhcp.leases
dhcp-range=192.168.56.30,192.168.56.100,255.255.255.0,12h
port=53
domain=vbox
expand-hosts
Note: we're not using dnsmasq as a DNS server on our host. Our virtual machines only need to be reachable from localhost anyway and we'll just use /etc/hosts. However, we cannot disable the DNS function in dnsmasq (by setting port=0) because then dnsmasq won't send DHCP offers for the matching MAC address. I was about to use port=2053 to allow dnsmasq to run as a non-root user, but of course dnsmasq still needs to bind to port 67 to act as a DHCP server. Also, with port set to any other port than 53, guests would not be able to refer to other guests by its name, because resolv.conf doesn't understand port numbers:
vm1$ dig vm0 -p 2053 @192.168.56.1 | grep ^[a-z]
vm0.                  0       IN      A       192.168.56.30
Almost there. We can now startup the VM and it should get its 192.168.56.30 assigned via DHCP. We should be able to connect to the guest, but we don't seem to be able to reach any other destination except the local network from insie the guest. For that, we have to enable IP forwarding in the host.

Linux host

# iptables -A FORWARD -i vboxnet0 -s 192.168.56.0/24 -m conntrack --ctstate NEW -j ACCEPT
# iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# iptables -A POSTROUTING -t nat -j MASQUERADE
# sysctl -qw net.ipv4.ip_forward=1
Now we should be able to connect to the guest with a static DNS name or IP address and we should be able to connect to the outside world from within the guest.

MacOS X host

On MacOS X, the magic commands would be:
# sysctl net.inet.ip.forwarding=1 net.inet.ip.fw.enable=1
net.inet.ip.forwarding: 0 -> 1
net.inet.ip.fw.enable: 1 -> 1

# grep ^net /etc/sysctl.conf
net.inet.ip.forwarding=1
net.inet.ip.fw.enable=1
Enable NAT through pf.conf(5):
# grep -B1 nat\  /etc/pf.conf 
rdr-anchor "com.apple/*"
nat on en1 from 192.168.56.0/24 to any -> (en1)

# pfctl -f /etc/pf.conf
# pfctl -e
Note: the nat entry must follow the rdr-anchor entry, it cannot be just appended to the end of the file.

Homebrew: GitHub API rate limit exceeded

I'm a big MacPorts fanboy but since Homebrew is all the craze for a few years now, I tried to give it another look.
$ mkdir homebrew && homebrew
$ curl -L https://github.com/Homebrew/homebrew/tarball/master | \
         tar --strip 1 -xzvf -
$ sudo mv homebrew /opt/homebrew && sudo chown -R root:wheel /opt/homebrew
$ sudo brew update
Initialized empty Git repository in /opt/homebrew
[...]
OK, so far - so good. Let's search for some packages, shall we?
$ brew search foo
[...]

$ brew search bar
[...]

$ brew search ssh
autossh      git-ssh      libssh       mpssh        pssh         ssh-copy-id  sshrc        sshuttle     tmux-cssh    zssh
csshx        gssh         libssh2      mussh        rssh         sshguard     sshtrix      stormssh     vassh
homebrew/fuse/sshfs                 homebrew/php/php54-ssh2             homebrew/php/php56-ssh2             Caskroom/cask/ssh-tunnel-manager
homebrew/php/php53-ssh2             homebrew/php/php55-ssh2             Caskroom/cask/bassshapes            Caskroom/cask/sshfs
Error: GitHub API rate limit exceeded for 172.16.30.4 (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
Try again in 59 minutes 32 seconds, or create an personal access token:
  https://github.com/settings/tokens
and then set it as HOMEBREW_GITHUB_API_TOKEN
Wait, wat? brew is asking the remote repo if a package is available? I've just run brew update:
   update   - Fetch the newest version of Homebrew and all formulae 
              from GitHub using git(1).
But indeed, the search command will perform an online search:
   search  - [...] The search for text is extended online to some popular taps.
Fortunately one can set HOMEBREW_NO_GITHUB_API=1 to stop this madness.