When you are trying to reach localhost, your source address is 127.0.0.1, as is your destination address. So, packet looks something like this:
| SRC | DST |
| 127.0.0.1 | 127.0.0.1 |
Since packets that are locally generated first traverse OUTPUT chain, you modify the packet with DNAT rule:
iptables -t nat -A OUTPUT \
-d 127.0.0.1 \
-p tcp --dport 80 \
-j DNAT \
--to 172.17.42.1:80
After OUTPUT chain, packet looks like this:
| SRC | DST |
| 127.0.0.1 | 172.17.42.1 |
So, the first error you see is that even if this packet gets routed outside the source device, destination will not know how to correctly return it. Thus, you also need to add additional SNAT rule:
iptables -t nat -A POSTROUTING \
-d 172.17.42.1 \
-p tcp --dport 80 \
-j SNAT \
--to-source <your_ip_addr>
Now, packet will look pretty much correct to exit on the network (source address will be the public address of this device and not localhost address).
But, this won't solve your pain just yet.
Routing decisions for packets generated on a local machine are taken in two places.
- before the OUTPUT chain
- after the OUTPUT chain and before POSTROUTING
Decision for this packet will be made in the second stage, before the source IP is rewritten in POSTROUTING chain...
So, kernel security mechanism will drop the packet because by default kernel refuses to route packets with src of 127.0.0.1. That means that even mangle coupled with fwmark wouldn't help us here. What's needed for this to work is to enable route_localnet. That's the per-iface variable that enables the use of 127/8 for local routing purposes (default is 0 - aka disabled).
sysctl -w net.ipv4.conf.all.route_localnet=1
You can safely change 'all' for the name of the outgoing interface in your case.
Now, packets will get routed by the previous rules in the table, and since we had the POSTROUTING SNAT, we will fix the source ip of 127.0.0.1 and rewrite it.
Hope this helps.
PS. sorry before 2-3 edits, it's 5AM here and I'm still awake :)