71

I want to forward requests from 192.168.99.100:80 to 127.0.0.1:8000. This is how I'd do it in linux using iptables:

iptables -t nat -A OUTPUT -p tcp --dport 80 -d 192.168.99.100 -j DNAT --to-destination 127.0.0.1:8000

How do I do the same thing in MacOS X? I tried out a combination of ipfw commands without much success:

ipfw add fwd 127.0.0.1,8000 tcp from any to 192.168.99.100 80

(Success for me is pointing a browser at http://192.168.99.100 and getting a response back from a development server that I have running on localhost:8000)

nafe
  • 1,241
  • 1
  • 10
  • 8

7 Answers7

43

So I found out a way to do this. I'm not sure if it's the preferred way but it works! At your favourite shell:

sudo ifconfig lo0 10.0.0.1 alias
sudo ipfw add fwd 127.0.0.1,9090 tcp from me to 10.0.0.1 dst-port 80

(The alias to lo0 seems to be the missing part)

If you'd like a (fake) domain to point to this new alias then make sure /etc/hosts contains the line:

10.0.0.1 www.your-domain.com
nafe
  • 1,241
  • 1
  • 10
  • 8
37

I was able to get this working using the ifconfig and pfctl commands on Mac 10.10.2. With the following approach I'm successfully mapping 127.0.0.1:3000 to mydomain.com locally on my machine.

In your command line enter the following two commands to forward connections to 127.0.0.1:3000 to 10.0.0.1:

sudo ifconfig lo0 10.0.0.1 alias
echo "rdr pass on lo0 inet proto tcp from any to 10.0.0.1 port 80 -> 127.0.0.1 port 3000" | sudo pfctl -ef -

Then edit your /etc/hosts or /private/etc/hosts file and add the following line to map your domain to 10.0.0.1.

10.0.0.1 mydomain.com

After you save your hosts file flush your local DNS:

sudo discoveryutil udnsflushcaches

Now open mydomain.com in a browser and you'll be seeing the server hosted on your localhost port (i.e. 127.0.0.1:3000). Basically this process maps an <ip>:<port> to a new <ip> so that you can then map a host that IP.

Dave Newton
  • 103
  • 2
Kevin Leary
  • 471
  • 4
  • 4
  • 1
    Just a tad hacky, but gets the job done. Good show. – Joey Carson Apr 24 '15 at 13:57
  • 6
    Thank you. How would you undo the alias and the forwarding? – Carlos Nuñez Sep 07 '15 at 19:22
  • 2
    Hi `sudo discoveryutil udnsflushcaches` gives me `command not found` but it still matches the entry correctly. But how can I map a second port? I tried to do steps 1+2 with id: `10.0.0.2` and another port, but it always takes only the last connection. So if I do last `10.0.0.1` & `port 3000` it forwards `3000`, if I do last `10.0.0.2` and port `4000` it forwards `4000`. In my `/private/etc/hosts` I wrote both entries with the (according) different id. – Andi Giga Feb 03 '16 at 10:18
  • 2
    Works, but I need to it again when I restart OSX.... Any thing to save this permanently? – Kasper Oct 05 '17 at 10:11
  • 3
    Please update with `sudo dscacheutil -flushcache` – northtree Aug 16 '18 at 05:15
  • This has been working well but seems to break in OSX 10.15 (Catalina). Any ideas about what broke and how to get it working again? – Tony Brasunas Nov 08 '19 at 23:30
  • If you are using https instead of http, remember to replace port 80 with port 443. – Sentient Apr 18 '21 at 21:57
7

I too had to do a similar thing recently, and in searching came upon this answer. Unfortunately, the answer of Nafe uses ipfw which is now deprecated and unavailable in OSX; and the answer of Kevin Leary is indeed a bit hackish. So I had to make something better (cleaner) and decided to share it here for posterity. This answer is largely based on the approach mentioned at this gist.

As OP mentions, pointing a browser at 192.168.99.100 should get a response from a server at localhost:8000. Adding an alias to ifconfig isn't really necessary, pfctl alone is sufficient: to achieve this the pf.conf file at /etc/pf.conf needs to be modified.

First we create (with sudo) a new anchor file (let's call it redirection) at: /etc/pf.anchors/redirection. This is basically a regular text file and contains the following line (just like in the answer of Kevin Leary): rdr pass on lo0 inet proto tcp from any to 192.168.99.100 port = 80 -> 127.0.0.1 port 8000. Once the new anchor file has been created, it needs to be referenced within the pf.conf file. Open the pf.conf file with sudo and add rdr-anchor "redirection" after the last rdr-anchor line (which is rdr-anchor "com.apple/*") and add load anchor "redirection" from "/etc/pf.anchors/redirection" at the end.

Ultimately, this is what the pf.conf file should look like:

scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
rdr-anchor "redirection"  #added for redirection/port forwarding
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"
load anchor "pow" from "/etc/pf.anchors/redirection"  #added for redirection/port forwarding

And that's almost it. Just restart pfctl by issuing sudo pfctl -d to disable it first and then sudo pfctl -fe /etc/pf.conf to start it again.

Now, if you need this happen automatically after every restart, another tiny bit of work needs to be done: the launch daemon for pfctl needs to be updated (the referenced gist mentions that pf is enabled automatically on boot, however this does not seem to be the case from looking at the code). Open (with sudo) System/Library/LaunchDaemons/com.apple.pfctl.plist and look for this:

<array>
          <string>pfctl</string>
          <string>-f</string>
          <string>/etc/pf.conf</string>
</array>

and add the line <string>-e</string> to ultimately make it like this:

<array>
         <string>pfctl</string>
         <string>-e</string>
         <string>-f</string>
         <string>/etc/pf.conf</string>
</array>

That should do it.

Caveat: Apple no longer allows to change the launch demon files just like that (not with sudo, nor chmod, nor anything else). The only way is to tinker with the System Integrity Protection settings: boot into recovery mode and launch terminal. Check the SIP status with csrutil status, it should generally be enabled. Disable it with csrutil disable and reboot in normal mode and then do the changes to the plist file as discussed above. Once done, go back to recovery mode and re-enable the protection (it's in place for good reason) by issuing csrutil enable.

Explanation: One can check by issuing the ifconfig command that 127.0.0.1 is already the (default) alias for localhost lo0 - this fact is being used to avoid having to add an extra alias for localhost and to simply use the default address in the pf.conf file.

UPDATE: Unfortunately, it seems as though loading the file at startup does not work. I am still trying to get help to have it sorted. Until then, running sudo pfctl -f /etc/pf.conf after starting up does the trick.

dakini
  • 325
  • 3
  • 13
  • 1
    Thanks for this, this worked for me in OS X Sierra. It is very important though that the redirection file ends with a newline. – kadrian Oct 28 '16 at 08:40
  • 1
    It doesn't seem safe to overwrite system files like `System/Library/LaunchDaemons/com.apple.pfctl.plist`; this probably wouldn't survive an OS update? – Tom Jun 12 '19 at 18:12
  • Yes, you're right, this sort of thing could easily get overwritten. Best to keep a back up of both the original (just in case) and the new one. "Safety" is relative, one would assume people on serverfault generally know what they're doing. But yeah, Apple added the SIP for good reason. – dakini Jan 23 '20 at 14:15
4

This worked well for me:

1

From 10.5 on, OS X comes with a new, application-oriented firewall instead of ipfw. But ipfw is still installed. If you have trouble with its syntax, check out graphical frontends like WaterRoof or Flying Buttress.

HTH, PEra

PEra
  • 2,825
  • 17
  • 14
1

order of rules is important, make sure there is no "deny all" before your allow rules, or something like that.

monomyth
  • 971
  • 1
  • 5
  • 9
  • Thanks for the advice but I don't think I have that particular problem (ipfw list only shows my rule and `65535 allow ip from any to any`). – nafe Jan 13 '10 at 22:13
1

Your command seems to be missing a rule number; try:

ipfw add 100 fwd 127.0.0.1,8000 tcp from any to 192.168.99.100 80

(if you aren't running as root, you'll have to prefix it with sudo). Another thing to check is that the firewall is enabled:

sysctl net.inet.ip.fw.enable

If it comes back with the value 0 (off), turn it on with:

sysctl -w sysctl net.inet.ip.fw.enable=1

... and then arrange for it to get reenabled when the computer reboots. The "proper" way to do this is probably to create a launchd item (Lingon makes this fairly easy). Or just use one of the GUI tools PEra mentioned, and let it take care of the details.

Gordon Davisson
  • 11,036
  • 3
  • 27
  • 33