1

I have a service running on "localhost" address on a certain X port. Unfortunately, I can't change the address to "0.0.0.0" to expose that service.

Can I use iptables to internally redirect request from external to loopback / localhost interface? The redirection would be 0.0.0.0:X -> localhost:X

Thank you

1
  • 1
    Yes, you can do this.
    – binarym
    Feb 28, 2020 at 8:54

2 Answers 2

5

As OP didn't mention the presence of a firewall, and for simplicity, I will assume no prior iptables settings exist: any traffic is allowed.

DNAT can be used in nat/PREROUTING to change the destination IP to 127.0.0.1, like this (example to redirect UDP port 5555):

# iptables -t nat -A PREROUTING -p udp --dport 5555 -j DNAT --to-destination 127.0.0.1

But while this would have been enough for any DNAT destination to any local IP address of the system outside of the 127.0.0.0/8 range. That's not enough for the specific case of changing the destination to an IP address within 127.0.0.0/8.

One has to toggle a special setting for this to work properly. Assuming the system's network interface is called eth0 this command will do it:

# sysctl -w net.ipv4.conf.eth0.route_localnet=1

See later for detailed explanations.

Optionally, especially if the application listening on 127.0.0.1 doesn't want a source IP outside of 127.0.0.0/8, you can also alter the source IP in the nat/INPUT chain with SNAT. NATing both destination and source severely limits the number of concurrent connections, so you should do this only if needed, and you won't get the actual source in logs anymore:

# iptables -t nat -A INPUT -d 127.0.0.1 -m state --ctstate DNAT -j SNAT --to-source 127.0.0.1

Detailed explanation about the use of route_localnet=1

To illustrate things a bit more thoroughly below, I will assume that: host's network interface is eth0, its IP address set on this interface is 192.0.2.100/24 and is receiving traffic from a client 192.0.2.200 and route_localnet has still not been enabled.

One has to take a look at Packet flow in Netfilter and General Networking (click to get full size):

Packet flow in Netfilter and General Networking

to check in what order things happen:

  • a client sends a packet from 192.0.2.200 to 192.0.2.100 (UDP destination port 5555)

  • nat/PREROUTING happens

    The destination is changed to 127.0.0.1 (and the corresponding conntrack entry will later be used to revert this change for the replies)

  • the routing stack is handled a packet arriving on eth0 with source 192.0.2.200 and destination 127.0.0.1.

  • the routing stack considers this packet invalid because 127.0.0.1 should never be seen outside of the lo loopback interface. Packet is dropped.

    It can be checked with ip route get:

    # ip route get 127.0.0.1 from 192.0.2.200 iif eth0
    RTNETLINK answers: Invalid argument
    

    Beside if one were to activate kernel's martian logs (sysctl -w net.ipv4.conf.eth0.log_martians=1) , this would be logged for each packet received when the DNAT iptables rule above is working:

    [728538.240893] IPv4: martian destination 127.0.0.1 from 192.0.2.200, dev eth0
    

The kernel has a specific toggle to relax this behaviour in the rare cases where it's needed to do so, like this specific case, where we know the reason of the apparition of 127.0.0.1 in the packet is the DNAT rule: route_localnet:

route_localnet - BOOLEAN
  Do not consider loopback addresses as martian source or destination
  while routing. This enables the use of 127/8 for local routing purposes.
  default FALSE

On eth0:

# sysctl -w net.ipv4.conf.eth0.route_localnet=1

And now the same previous ip route get command gives:

# ip route get 127.0.0.1 from 192.0.2.200 iif eth0
local 127.0.0.1 from 192.0.2.200 dev lo 
    cache <local> iif eth0 

Not dropped anymore.

This will also relax the same restriction the other way around: reply packets will, as the very first step, traverse the routing decision, before being un-DNATed by netfilter, so would also be dropped. Route before and after route_localnet is set to 1:

before:

# ip route get 192.0.2.200 from 127.0.0.1
RTNETLINK answers: Invalid argument

after:

# ip route get 192.0.2.200 from 127.0.0.1
192.0.2.200 from 127.0.0.1 dev eth0 uid 0 
    cache 
0

Yes this is possible.

The following command sequence should do it:

INTERFACE_IP="192.168.0.1"  # IP address of the external interface (e.g. eth0)
INTERFACE_PORT="80"         # external port to forward
LOCAL_IP="127.0.0.1"        # internal ip address to forward to
LOCAL_PORT="8080"           # internal port to forward to
/usr/sbin/iptables -t nat -A PREROUTING -d $INTERFACE_IP --dport $INTERFACE_PORT -j DNAT --to-destination $LOCAL_IP:$LOCAL_PORT

or a bit less dynamic:

/usr/sbin/iptables -t nat -A PREROUTING -d 192.168.0.1 --dport 80 -j DNAT --to-destination 127.0.0.1:8080

The destination port (:8080) can be omitted if it is the same port as the incoming one.

Be aware that other rules in your iptables might inflict problems to this rule!!

6
  • You omitted the rule in table filter, chain FORWARD to allow the stream. If the policy of FORWARD is set to DROP, then the PREROUTING rule won't be enought.
    – binarym
    Feb 28, 2020 at 10:41
  • Anyway for this to be complete, I think 127.0.0.0/8 's special behaviour with regard to routing and local routing must be addressed: route_localnet. netfilter and conntrack don't care about routing, the routing stack still does. reply packets after their creation are seen from 127.0.0.0/8 to non-127.0.0.0/8 and wouldn't use the lo interface. The routing decision would forbid this by default, and the de-DNAT transformation would happen after the rejecting decision.
    – A.B
    Feb 28, 2020 at 13:01
  • To my knowledge the default policy for all chains is ACCEPT. As there are no more details provided by the OP, all other 'rules' are guesswork and might have more implications than can be foreseen with the provided information.
    – bey0nd
    Feb 28, 2020 at 13:04
  • @bey0nd but please mind my comment. It's not about iptables anymore but about routing
    – A.B
    Feb 28, 2020 at 13:09
  • @A.B. I'm a bit puzzled. The OP asked if it was possible. I answered his question with 'yes' and showed him (one possibility) how to do it. Are you implying that this would conflict with any default functionality of iptables?
    – bey0nd
    Feb 28, 2020 at 13:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .