You can accomplish this by creating a wireless network on your Mac using Internet Sharing, degrading that interface using firewall rules, and connecting your iPhone to that network. This will actually work to debug any device that connects to a wireless network.
Using this technique, you can simulate extremely meager, lossy, or latent networks.
I use this technique instead of Apple's official Network Link Conditioner for a couple reasons:
- Throttling can be applied only to specific connected devices, rather than affecting your development machine's network connection.
- It can be scripted to simulate rapidly changing or "bursty" networks.
- Unlike the Network Link Conditioner built into iOS, you can change the settings while your application remains in the foreground.
This uses ipfw's dummeynet
feature. ipfw is technically deprecated in modern versions of OS X, but it still works fine. Unfortunately, pf (the replacement) doesn't yet support arbitrary packet delays. I'll update this answer if something changes.
Creating an awful Wi-Fi network
- Plug into Ethernet if you aren't already.
- Enable Internet Sharing in the Sharing pane of System Preferences. Choose to "Share your connection from: Ethernet" and check "Wi-Fi".
- Get your phone connected to the network you just created and make sure you can browse the web.
Tell Mac OS's built-in firewall (ipfw) to ensure packets that have latency applied (pass through the "dummynet" in ipfw parlance) are still routed through the normal rules. This allows Internet Sharing to continue working:
phil@Nebula ~$ sudo sysctl -w net.inet.ip.fw.one_pass=0
net.inet.ip.fw.one_pass: 0 -> 0
Configure the low-quality pipe through which your iPhone's traffic will pass (14Kb/s throughput with 1% packet loss):
phil@Nebula ~$ sudo ipfw pipe 1 config bw 14KB/s
phil@Nebula ~$ sudo ipfw pipe 1 config plr 0.01
The next step varies depending on whether you're on Mountain Lion or below, or Mavericks.
For Mountain Lion (10.8) or below:
Route packets into the pipe, but only for traffic over your AirPort interface:
phil@Nebula ~$ sudo ipfw add 10 pipe 1 ip from any to any via en1
00010 pipe 1 ip from any to any via en1
Important: If you're using an Air or new MacBook Pro without a physical ethernet port, your AirPort interface will likely be called en0
. Replace en1
with en0
above if that's the case.
For Mavericks (10.9):
Check the output of ifconfig
and look for the bridge interface created by Internet Sharing:
bridge0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=63<RXCSUM,TXCSUM,TSO4,TSO6>
ether xx:xx:xx:xx:xx:xx
Configuration:
id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
ipfilter disabled flags 0x2
member: en4 flags=3<LEARNING,DISCOVER>
ifmaxaddr 0 port 6 priority 0 path cost 0
nd6 options=1<PERFORMNUD>
media: <unknown type>
status: inactive
bridge100: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=3<RXCSUM,TXCSUM>
ether xx:xx:xx:xx:xx:xx
inet 192.168.2.1 netmask 0xffffff00 broadcast 192.168.2.255
Configuration:
id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
ipfilter disabled flags 0x2
member: en1 flags=3<LEARNING,DISCOVER>
ifmaxaddr 0 port 5 priority 0 path cost 0
media: autoselect
status: active
You want the bridge interface that has an IP address; in most cases, it will be bridge100
.
Route packets into the pipe, but only for traffic over the bridge interface:
phil@Nebula ~$ sudo ipfw add 10 pipe 1 ip from any to any via bridge100
00010 pipe 1 ip from any to any via bridge100
Change bridge100
if it has a different name on your system.
Simulating a changing network
You can change the values 14KB/s
and 0.01
in step 5 above to simulate different types of networks. You can also specify config delay 1000
to introduce a 1000ms delay. See the manpage for more options.
You can continue to reconfigure the pipe after adding the rule for it. For instance, to simulate nearing the edge of cellular coverage, issue this command while your app is running and connected (95% packet loss):
phil@Nebula ~$ sudo ipfw pipe 1 config plr 0.95
There is no need to run sudo ipfw add 10 …
again after reconfiguring the pipe. You can script these changes to simulate an extremely dynamic network environment.
Cleaning up
You can issue sudo ipfw delete 10
to put everything back to normal, or just reboot.