Using Iodine DNS Tunneling on OS X Mavericks

For a long time I have had a T-Mobile unlimited data plan which allowed tethering my laptop to my Android Moto G LTE phone which runs Android KitKat 4.4.3. After switching plans tethering is now blocked by an “up-sell” screen when I connect to my hotspot. I don’t really mind paying for tethering, so I called up T-Mobile only to be told my particular day-by-day “unlimited” data plan doesn’t even have a tethering add-on even though I’m willing to pay a little extra for that. Well then. Even the “un-carrier” is still a carrier.

So — what to do?

Well back in the old days, T-Mobile used to block tethering just by inspecting the browser’s User Agent string and would redirect users to an up-sell tethering page if a mobile browser wasn’t detected. That might stop most users, but with User-Agent switching plugins readily available for all the major browsers, this used to be an easy work around.

After a lot of googling, nobody really seems to know definitively how T-Mobile is detecting tethering, and thus there are lots of proposed work-arounds. Many suspect that T-Mobile is inspecting packets instead of the payload of the packets to determine where those packets originated, and blocking tethered packets.

Perhaps it has something to do with how KitKat sets up separate routing tables for tethered data, thus allowing the carrier to differentiate between tethered and non-tethered data. By rooting your phone you can setup different routing tables and that might work, but I don’t want to root my phone (yet).

Perhaps the carrier is inspecting packets’ TTL values. Since packets from the tethered computer have a different TTL value than packets from the phone, the carrier could discriminate which packets to block in this way. By changing OS X’s TCP TTL value, perhaps we can slip them around the roadblock. This didn’t work for me.

Some people reported that by doing some technical machinations to set the “tether_dun_required” flag on the Android phone from 1 to 0. Perhaps this worked for a while, or perhaps on some phones, but it doesn’t work for me on the Moto G.

UPDATE: Actually, if you are on T-Mobile and tethering is blocked, there is something quite easy you can try to get it working which did work for me: Basically you need to update the T-Mobile APN (“Access Point Name”) configuration. To do so on your Android: Settings -> More… -> Mobile Networks -> Access Point Names. Then, click “T-Mobile US LTE” (it might have a different name, but the URL you see underneath it should be “”). Tap “APN type” and to that setting, append this:


Then save the settings. Tethering works again. My understanding of this this “dun” APN type is that it stands for “Dial Up Networking”. Essentially, when your phone requires network access, it needs to connect to the data network do any variety of things, say use the internet or send an MMS. When your phone’s tethering hotspot is enabled, it is requesting to use this “dun” APN type. Android then connects using the settings for an APN in your list that has the “dun” type. Apparently, the APN seems to allow tethering, whereas the other “dun” APN that existed on my phone, does not. That seems a little fragile and one day T-Mobile may get wise. But, for now that works.

So — what about this whole Iodine DNS tunneling thing, then?

Well, we still have airport hotspots to tunnel through, now don’t we?

DNS Tunneling basically means that if your computer can send and receive valid DNS responses, we can hide our network traffic inside the DNS packets. This means we need to run a server process (iodine) on a remote machine with port 53 open to receive and deal with these packed DNS packets, configure DNS entries to point to that server in a particular way, and then run a local client on your OS X machine. Once you’ve got that client running, you can basically access the remote machine, but then you have to route your computer’s traffic through it. This can be done in different ways. If you’re just browsing the web, probably the easiest way is via an SSH SOCKS proxy. Or, you could fiddle with your routing tables to send all your traffic over the tunnel.

Let’s begin.

The DNS Entries

I use Amazon’s Route 53 service which makes it easy to manipulate DNS records for any domain you control. The basic process is that you need to setup an “NS” record for a subdomain of a domain you control. It can be a little confusing:

  • Let’s say you control
  • Choose a subdomain you want to use for setup, it can be anything, say: (t for tunnel! keep it short)
  • We’re going to run the iodined process on a server at IP Address A.B.C.D.
  • This iodined process on A.B.C.D is going to act as the canonical Nameserver for the subdomain

This iodined server process binds to port 53, just a like a DNS server. Our iodine client process that we’ll run on our laptop is going to take our computer’s traffic and wrap it up into DNS requests for Any upstream DNS server from our client is going to say “Oh, hey, you should query A.B.C.D for the IP of — send those packets over there”.

Iodined will then then take our DNS requests with the wrapped-up traffic, dump it onto the server’s network, get a response and then “answer” the DNS request with a wrapped-up response. The packets look like normal DNS traffic, so in theory they should be able to be passed around the internet “per usual”, except that these DNS packets contain extra data, namely the traffic to and from your computer.

Because a lot of captive portals (like a cell carrier that blocks tethering, or an airport hotspot) allow DNS traffic to the outside world (if not HTTP or other traffic), as long as we can reach/query our iodined DNS server and receive responses from it, we’re in business.

The trick is, we need to tell the world “Hey — if you want to know the IP address of, look for it here, at DNS server A.B.C.D”.

So, basically, let’s call our nameserver It points to A.B.C.D in our DNS setup as an A record: A record => A.B.C.D

Now, we need to assign that new shiny as a nameserver for this is a “nameserver” (NS) record: NS =>

That’s it — anytime any client wants to know the IP address of “”, it’s going to ask “” which runs our iodined process.

The Server

I use a small Amazon AWS EC2 instance running Ubuntu. You need to make sure that the security group assigned to the instance allows incoming traffic on port 53 (the standard port for DNS processes).

As root:

apt-get install iodine

We need to then actually run an iodined process. In doing so we need to tell iodine what subnet we are going to use for our little private tunnel. Your computer is going to create a virtual tunnel device that will use this same subnet. So — it’s very important to use a subnet that is not being used by the server OR your computer. Amazon EC2 uses portions of the private subnet for internal addressing, and it uses portions of the subnet for internal services like it’s own DNS system. Most home routers use or sometimes This worked for me:

iodined -f -c -P secret

replace “secret” with a passphrase that the client will also supply. We don’t want to route traffic for just anybody. (Make sure you are running “iodined” with a “d” at the end! The program “iodine” (no “d”) is for the client…)


Obviously you need the iodine program installed. The easiest way is to install homebrew and then “brew install” it:

brew install iodine

On OS X Mavericks, you are going to have to do this as well (to get the tuntap tunnel working correctly):

sudo cp -pR $(brew --prefix tuntap)/Library/Extensions/tap.kext /Library/Extensions/
sudo cp -pR $(brew --prefix tuntap)/Library/Extensions/tun.kext /Library/Extensions/
sudo chown -R root:wheel /Library/Extensions/tap.kext
sudo chown -R root:wheel /Library/Extensions/tun.kext
sudo touch /Library/Extensions/
sudo cp -pR $(brew --prefix tuntap)/tap /Library/StartupItems/
sudo chown -R root:wheel /Library/StartupItems/tap
sudo cp -pR $(brew --prefix tuntap)/tun /Library/StartupItems/
sudo chown -R root:wheel /Library/StartupItems/tun
sudo kextload -b foo.tun
sudo kextload -b foo.tap

Then, we should be able to run the iodine client on our localhost:

sudo iodine -f -P secret

Note that “iodine” might not be in your PATH. If it’s not, you can call it directly from where homebrew installs programs:

sudo /usr/local/Cellar/iodine/0.7.0/sbin/iodine -f -P secret

Note: your version might not be “0.7.0” — adjust as needed.

You should now be able to “ping” the remote server through the tunnel:


If your local iodine process complains that you are getting too many “SERVFAIL” responses, you can start the command with a small interval, but note that the smaller the interval, the more DNS traffic you’ll be creating:

sudo iodine -f -P secret -I1

Routing Traffic

Now that you can ping, you can also SSH into the remote machine. If all you need is SSH, hey, you’re good to go:

ssh user@

But most of us want to at least browse the web. The easiest way is setup a SOCKS proxy via SSH, then tell your browsers to use that proxy to route all HTTP traffic. Another way is fiddle with our routes to send all traffic over the tunnel.


To setup a SOCKS Proxy over SSH:

ssh -N user@ -D 1080

This binds the proxy to localhost:1080. Any HTTP requests we make to localhost:1080 will be forwarded out to the remote machine. Tell OS X browsers to use the proxy:

  • Go to Settings -> Network -> Advanced -> Proxies.
  • Select “SOCKS Proxy”.
  • Set the proxy to localhost:1080
  • Click the “OK” button
  • Click the “Apply” button on the main network settings pane

Open a browser and your traffic should be routed over the SSH proxy.

Ok, that’s awesome, but what if we want Mail, DropBox and other non-HTTP traffic to be sent over the tunnel as well? Setup some routes. Oh, and you need to setup NAT on the remote server and alter the iptable rules as well. A little more of a headache, but doable.

Routing and NAT

This script is a great way to automatically start up iodine on your laptop and it also sets up the routes (and tears them down later) for routing all traffic through the iodine tunnel:

Once you grab that, you need to alter some of the variables. In our example with a homebrew iodine and the given subnet, change the variables at the top of the script to the following:

#### EDIT HERE ####

# Path to your iodine executable

# Your top domain

# You may choose to store the password in this script or enter it every time

# You might need to change this if you use linux, or already have
# tunnels running.  In linux iodine uses dnsX and fbsd/osX use tunX
# X represents how many tunnel interfaces exist, starting at 0

# The IP your iodined server uses inside the tunnel
# The man page calls this tunnel_ip

#### STOP EDITING ####

make sure that script is executable:

chmod a+x

Then run it as root:

sudo ./

And then try to use your browser… and it fails. Why? Because you need make sure your remote server is setup to actually forward the packets to the outside world via NAT.

NAT on the Server

A great writeup about this process already exists, please see the section called “Configuring NAT and IP masquerading”.

And that’s it — DNS tunneling for captive portals on OS X Mavericks.

Speed / Connectivity

So this method gets us around the captive portal, but the connection is not all that fast. And, sometimes even though we’re tunneled, some portals still are not completely defeated. Often, you’ll need to restart your tunnel, or perhaps networking if you see a dropped tunnel or connection.

The phone’s speed test results over 4G LTE:

Speed Test on a 4G LTE phone

Moments later, using tunneling, the tethered/tunneled computer’s results:

Speed Test on a DNS Tunneled Computer

3 thoughts on “Using Iodine DNS Tunneling on OS X Mavericks

  1. Majin

    Great post…..
    But please can you implement the same methods on windows ???
    Again thank you for this post.

  2. cj0

    Mac OS X instructions are outdated:
    $ brew install iodine
    fails, better is:
    $ brew install homebrew/boneyard/iodine

Leave a Reply

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