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):

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