OS X: How to Setup NAT on Lion and Mountain Lion

It appears Apple changed the way NAT works, starting in Lion and this upset a lot of people. Apparently, nat would just stop working for some folks after they upgraded to Lion from Snow Leopard.

Finding really in-depth information about Apple’s NAT implementation is surprisingly hard. While attempting to figure out how to setup a virtual network for two MacBooks connected to one another over Ethernet, I found a few helpful resources, but none helped me understand exactly how OS X does natd within InternetSharing and no matter whose blog or tutorial I followed, I just couldn’t get /usr/sbin/natd to forward my packets. This is how my setup first looked…

I experimented with VLANs, bridges, routing tables, Parallels NAT, Apple NAT, InternetSharing, ipfw, config files and other stuff I’ve forgotten. I typed in netstat -nr|more and ifconfig more times than I can count. One thing I didn’t get into was pf, though. It looked a little too complex.

First, the reason(s) why I didn’t want to use Apple’s InternetSharing app even though while using it, the VMs on both MacBooks could ping one another and the Internet. I’m a control freak. I want to manage my own everything. My own DNS, my own DHCP server and I want to pick my own damn subnet. Apple says that you can change the subnet by modifying SharingNumberStart in the nat defaults file but this won’t work if you already have any device assigned to an IP in that subnet. To avoid conflicts, InternetSharing takes it upon itself to modify the subnet by one octet (172.20.0.1 -> 172.20.1.1) which is the opposite of what I want. I was also unable to find any documentation on how to modify the subnet mask, so I don’t even think that’s possible with InternetSharing.

My network requirements aren’t that common, admittedly. It’s not often that someone will want to hook up two MacBook Pros and run a lil virtual network between them using only an Ethernet cable but, eh, I do. Btw, did you know that you don’t need a crossover cable for MacBooks to talk to one another directly over gigabit Ethernet? Me either. Anyway, I didn’t want to buy a gigabit router because this is a very short term project. And I thought I’d enjoy the challenge of figuring this out.

I knew my nat configs were mostly right, I was just missing something with the routes. I finally found the last piece using route monitor and watching what InternetSharing changed when it was turned on and all client VMs could ping each other and the Internet(s).

Check out the routing table InternetSharing created:

192.168.1.1 is my Wireless connection, 192.168.2.1 is Apple’s NAT subnet and the other nets were created by Parallels. Most network admins I talked to immediately thought the multiple gateways were causing an issue but this is the setup that actually worked, it just wasn’t customizable enough. By the way, BSD has one of the few tcp/ip stacks that allow you to have multiple gateways (so I read.)

So after about a week of prodding, I figured out that this is what Apple basically does within InternetSharing:

gwdev=en1
ifconfig bridge0 create
ifconfig bridge0 up
ifconfig bridge0 addm en0
ifconfig bridge0 172.20.0.1
route add default -interface bridge0 -ifscope bridge0 -cloning
sysctl -w net.inet.ip.forwarding=1
/sbin/ipfw add 100 divert natd ip from any to any via $gwdev
/usr/sbin/natd -interface $gwdev -use_sockets -same_ports -unregistered_only -dynamic -clamp_mss -enable_natportmap -natportmap_interface en0


That and a bunch of error handling and things related to DHCP and DNS. It also checks to see if the Internet connection exists and deletes the nat/bridge if it doesn’t and recreates it when the connection is back up.

Here’s a visual of my network after running that script:


I need to start working on my project so further error handling and automation are going to be dealt with later.

Below is the code I’ve started using on my machine. My guest OS network runs a Windows domain and has its own DNS and DHCP servers so those are not addressed in this script. They are really important services, though so if you don’t have them setup, you’ll want to do that.

Oh, it also doesn’t make any changes to the firewall except to add a forwarding rule. You’ll want to setup your firewall, too. Annnd this script doesn’t use a specific network interface. I make it detect the current gateway then use that interface. If it can’t find a gateway, it uses en1. I should probably make it just die but whatever.

#!/bin/bash

# Get the interface name for the gateway
gwdev=<code>netstat -nr | grep default | awk '{ print $6 }' | head -1

# If none are found, set the gateway to en1 (generally Wifi on OS X)
if [ -z "$gwdev" ]; then
gwdev=en1
fi

# Create a bridge, add the Ethernet device
ifconfig bridge0 create
ifconfig bridge0 up
ifconfig bridge0 addm en0
# Give it an IP, route bridge0′s traffic to bridge0
ifconfig bridge0 172.20.0.1
route add default -interface bridge0 -ifscope bridge0 -cloning

# Enable IP forwarding, add a firewall rule to send all natd traffic to the real gateway
# Start natd with a whole bunch of options
sysctl -w net.inet.ip.forwarding=1
/sbin/ipfw add 100 divert natd ip from any to any via $gwdev
/usr/sbin/natd -interface $gwdev -use_sockets -same_ports -unregistered_only -dynamic -clamp_mss -enable_natportmap -natportmap_interface en0


And to stop...

#!/bin/bash
# All these steps look excessive but address
# network instability issues created by not doing them

gwdev=<code>netstat -nr | grep default | awk '{ print $6 }' | head -1

if [ -z "$gwdev" ]; then
gwdev=en1
fi

killall natd
/sbin/ipfw delete 100 divert natd ip from any to any via $gwdev
sysctl -w net.inet.ip.forwarding=0
ifconfig $gwdev down
ifconfig en0 down
route delete default -interface bridge0 -ifscope bridge0
ifconfig bridge0 172.20.0.1 delete
ifconfig bridge0 deletem en0
ifconfig bridge0 destroy
ifconfig en0 up
ifconfig $gwdev up

One big important thing for laptop users is that natd becomes unstable after the laptop wakes from sleep. To address that, I created the scripts above, installed sleepwatcher, and set sleepwatcher to run /sbin/stopnat on sleep and /sbin/startnat it on wake. This makes natd run 24/7 so make sure that's what you want. I also need to put it in the startup but haven't yet.

Here's that setup, real quick:
sudo port install sleepwatcher
/usr/local/sbin/sleepwatcher -d --wakeup /sbin/startnat --sleep /sbin/stopnat

It's likely I'll keep develop this more over time, but for now, my clusters need my attention. I know it's a bit of a pain just copy/pasting so give me about a month or eight and I'll package up the files nice and put em up here.

Update: If you're looking to do this without NAT, check out Thomas' post about bridging ethernet interfaces in OS X.

References: OS X Advanced Server Guide, Mac OS X for Unix Geeks, a blog post by akutz and every support forum for OSX, FreeBSD and Parallels ever.

Posted in Networking, OS X & iDevices
11 comments on “OS X: How to Setup NAT on Lion and Mountain Lion
  1. akutz says:

    Love the use of Balsamiq. My favorite wireframe software hands down. Great article too.

    • Hey akutz! That was actually my first use of Balsamiq… I love it. I hope they expand to cover more areas (like networking) at some point.

      Thank you, btw, for the awesome tutorial. It was one of the first and most useful resources I found.

  2. akutz says:

    You're very welcome. Yes, Balsamiq desperately needs more stencils.

  3. Mike Ross says:

    Thank you very much for this article. It helped me solve a very complex problem with virtualbox and vagrant. Thank you again.

  4. Francis says:

    Chrissy, thanks! I hope you'll write an update replacing ipfw by pfctl…

  5. Douglas says:

    Hi,
    Any hints for Mountain Lion on how to use pf with this?

    Regards,
    Doug

  6. Romeyn says:

    Hi…you seem to be the closest to what I'm trying to accomplish but can't quite figure out. I want a bridge en0 and en1 on a Mac Mini. The Mini will be plugged into ethernet (en0) and broadcasting a SSID for WiFi users. But I DO NOT WANT NAT! WiFi users' traffic should get sent straight to the wired LAN port, served IP addresses by the same network serving the wired LAN port. In short, I want WiFi clients to be served as if they WERE on the wired network.

    Or am I delusional in thinking I should be able to do that?

  7. Dan Gericke says:

    Very helpful! Thanks so much.

  8. David says:

    You are massively overcomplicating this creating a bridge. And you miss the command to enable ipfw. This is all you need for NATing from en1 (public, where your internet is) to en0 (private, where your clients are):

    (all as root)

    sysctl -w net.inet.ip.forwarding=1
    sysctl -w net.inet.ip.fw.enable=1
    /sbin/ipfw add divert natd ip from any to any via en1
    natd -interface en1 -dynamic

    The natportmap stuff is optional only if you need it.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">