147

I want connections coming in on ppp0 on port 8001 to be routed to 192.168.1.200 on eth0 on port 8080.

I've got these two rules

-A PREROUTING  -p tcp -m tcp --dport 8001 -j DNAT --to-destination 192.168.1.200:8080

-A FORWARD -m state -p tcp -d 192.168.1.200 --dport 8080 --state NEW,ESTABLISHED,RELATED -j ACCEPT

and it doesn't work. What am I missing?

4
  • 7
    NAT HOWTO Dec 5, 2008 at 21:11
  • I'm going to go with the n-p-r tag (although this could be programming-related, though poorly phrased of course.) Dec 5, 2008 at 22:12
  • 2
    How about this: I'm a programmer trying to set up an environment so I can debug my server application in eclipse being called from the innernet. Close enough?
    – stu
    Dec 5, 2008 at 22:30
  • Sure, that's what I meant by "poorly phrased"... Could you edit the question accordingly? Dec 5, 2008 at 23:19

8 Answers 8

118

First of all - you should check if forwarding is allowed at all:

cat /proc/sys/net/ipv4/conf/ppp0/forwarding 
cat /proc/sys/net/ipv4/conf/eth0/forwarding 

If both returns 1 it's ok. If not do the following:

echo '1' | sudo tee /proc/sys/net/ipv4/conf/ppp0/forwarding
echo '1' | sudo tee /proc/sys/net/ipv4/conf/eth0/forwarding

Second thing - DNAT could be applied on nat table only. So, your rule should be extended by adding table specification as well (-t nat):

iptables -t nat -A PREROUTING -p tcp -i ppp0 --dport 8001 -j DNAT --to-destination 192.168.1.200:8080
iptables -A FORWARD -p tcp -d 192.168.1.200 --dport 8080 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

Both rules are applied only to TCP traffic (if you want to alter UDP as well, you need to provide similar rules but with -p udp option set).

Last, but not least is routing configuration. Type:

ip route

and check if 192.168.1.0/24 is among returned routing entries.

9
  • 18
    I personally prefer the sysctl syntax like sysctl net.ipv4.conf.eth0.forwarding=1
    – Doud
    Sep 7, 2012 at 21:10
  • 1
    How do I remove the incorrectly entered rule? Apr 13, 2014 at 2:51
  • 3
    second line: "iptables -A FORWARD -p tcp -d 192.168.1.200 --dport 8080 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT" is NOT required if you don't have firewall restrictions/security, which is the case with most of home LANs, otherwise be careful with -A, be cause it will add it AFTER restrictions/security and may not work (so check -I instead, that is adding IN FRONT of iptables rules) Aug 28, 2014 at 13:34
  • 2
    @ÁronLőrincz, No. Iptables rules are volatile unless explicitly loaded at boot.
    – sherrellbc
    Jan 23, 2017 at 20:59
  • 2
    @Nickolai Leschov, enter same replacing -A with -D Apr 6, 2018 at 1:27
18

You forget postrouting source address SNAT 'ing:

sysctl net.ipv4.ip_forward=1
yours_wan_ip=101.23.3.1
-A PREROUTING  -p tcp -m tcp -d $yours_wan_ip --dport 8001 -j DNAT --to-destination 192.168.1.200:8080

-A FORWARD -m state -p tcp -d 192.168.1.200 --dport 8080 --state NEW,ESTABLISHED,RELATED -j ACCEPT

-A POSTROUTING -t nat -p tcp -m tcp -s 192.168.1.200 --sport 8080 -j SNAT --to-source $yours_wan_ip

And don't forget to set your linux firewall as default gateway on computer with 192.168.1.200 address.

2
  • 1
    You got it backwards on the POSTROUNTING step. At this point the conversation is still about --destination rather than --source.
    – sherrellbc
    Jan 23, 2017 at 21:04
  • embeded system -/bin/sh: sysctl: not found
    – CS QGB
    Dec 20, 2022 at 12:12
18

The accepted solution works when the destination host and the gateway are on the same subnet (like is in your case, both are on eth0 192.168.1.0/24).

Below is a generic solution for when the gateway, source and destination are all on different subnets.

1) Enable IP forwarding:

sysctl net.ipv4.conf.eth0.forwarding=1 
sysctl net.ipv6.conf.eth0.forwarding=1 

//note: if forwarding to/from localhost, also set sysctl net.ipv4.conf.eth0.route_localnet=1


2) Add 2 iptables rules to forward a specific TCP port:

To rewrite the destination IP of the packet (and back in the reply packet):

iptables -A PREROUTING -t nat -p tcp -i ppp0 --dport 8001 -j DNAT --to-destination 192.168.1.200:8080  

To rewrite the source IP of the packet to the IP of the gateway (and back in the reply packet):

iptables -A POSTROUTING -t nat -p tcp -d 192.168.1.200 --dport 8080 -j MASQUERADE

3) If you don't have a default ACCEPT firewall rule, allow traffic to the destination:

iptables -A FORWARD -p tcp -d 192.168.1.200 --dport 8080 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

4) Test the new setup. If it works, make sure the changes persist across reboots:

cat <<EOF > /etc/sysctl.d/99-forwarding.conf
sysctl net.ipv4.conf.eth0.forwarding=1 
sysctl net.ipv6.conf.eth0.forwarding=1 
EOF

iptables-save > /etc/network/iptables.up.rules

echo '#!/bin/sh' > /etc/network/if-pre-up.d/iptables
echo "`which iptables-restore` < /etc/network/iptables.up.rules" >> /etc/network/if-pre-up.d/iptables
chmod +x /etc/network/if-pre-up.d/iptables
2
  • 4
    I guess that should be the accepted answer because the others do not consider the POSTROUTING part which solves the problem of calling wanIP:8081 from the LAN.
    – Mike
    Nov 28, 2021 at 20:34
  • This worked for me, first time, after months of flailing with various inferior alternatives. Most excellent. I guess someone named "rusty" can be trusted when it comes to iptables... :-) May 30, 2023 at 22:59
16

I think what you want is:

iptables -A FORWARD -m state -p tcp -d 192.168.1.200 --dport 8080 --state 
    NEW,ESTABLISHED,RELATED -j ACCEPT

iptables -t nat -A PREROUTING -p tcp --dport 8001 -j DNAT --to-destination
    192.168.1.200:8080
2
  • 3
    ummm... that IS what I have already. I use iptables-restore to load it so each is in its own section, but that's what I wrote above.
    – stu
    Dec 5, 2008 at 22:21
  • Okay, the syntax looked bad in the original. Did you try -i ppp0 in the rules? What is the problem exactly? Dec 5, 2008 at 23:17
9

I have created the following bash script for doing this on my linux router. It automatically infers the WAN IP and confirms your selections before proceeding.

#!/bin/bash

# decide which action to use
action="add"
if [[ "-r" == "$1" ]]; then
  action="remove"
  shift
fi

# break out components
dest_addr_lan="$1"
dest_port_wan="$2"
dest_port_lan="$3"

# figure out our WAN ip
wan_addr=`curl -4 -s icanhazip.com`

# auto fill our dest lan port if we need to
if [ -z $dest_port_lan ]; then
  dest_port_lan="$dest_port_wan"
fi

# print info for review
echo "Destination LAN Address: $dest_addr_lan"
echo "Destination Port WAN: $dest_port_wan"
echo "Destination Port LAN: $dest_port_lan"
echo "WAN Address: $wan_addr"

# confirm with user
read -p "Does everything look correct? " -n 1 -r
echo    # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]; then
  if [[ "remove" == "$action" ]]; then
    iptables -t nat -D PREROUTING  -p tcp -m tcp -d $wan_addr --dport     $dest_port_wan -j DNAT --to-destination $dest_addr_lan:$dest_port_lan
    iptables -D FORWARD -m state -p tcp -d $dest_addr_lan --dport     $dest_port_lan --state NEW,ESTABLISHED,RELATED -j ACCEPT
    iptables -t nat -D POSTROUTING -p tcp -m tcp -s $dest_addr_lan --sport     $dest_port_lan -j SNAT --to-source $wan_addr
    echo "Forwarding rule removed"
  else
    iptables -t nat -A PREROUTING  -p tcp -m tcp -d $wan_addr --dport     $dest_port_wan -j DNAT --to-destination $dest_addr_lan:$dest_port_lan
    iptables -A FORWARD -m state -p tcp -d $dest_addr_lan --dport     $dest_port_lan --state NEW,ESTABLISHED,RELATED -j ACCEPT
    iptables -t nat -A POSTROUTING -p tcp -m tcp -s $dest_addr_lan --sport $dest_port_lan -j SNAT --to-source $wan_addr
    echo "Forwarding rule added"
  fi
else
  echo "Info not confirmed, exiting..."
fi

The use of the script is simple just copy and paste it to a file and then.

# chmod +x port_forward.sh
# ./port_forward.sh 192.168.1.100 3000
... confirm details ... press y
# Forwarding rule added

To remove the same rule

# ./port_forward.sh -r 192.168.1.100 3000
... confirm details ... press y
# Forwarding rule removed

I thought this might save someone time on their respective router.

3

I had the task to make MACHINE_A into thinking that the service is running physically on MACHINE_B, but transparently re-route all requests to MACHINE_C.

The trick was to use MASQUERADE.

sysctl net.ipv4.ip_forward=1

iptables -t nat -A PREROUTING -p tcp -d MACHINE_B --dport 443 -j DNAT --to-destination MACHINE_C

iptables -t nat -A POSTROUTING -s MACHINE_A -o INTERFACE_NAME -j MASQUERADE

Please note that you might want to tweak the commands:

  1. To allow packet forwardning on a specific interface only. For example:

    sysctl net.ipv4.conf.eth0.forwarding=1
    
  2. To allow not only MACHINE_A, but also all others to use port forwarding, remove:

    -s MACHINE_A
    
1

Try

echo "1" > /proc/sys/net/ipv4/conf/ppp0/forwarding
echo "1" > /proc/sys/net/ipv4/conf/eth0/forwarding

These files tell the kernel it's allowed to forward packets between the interfaces.

0

This command doesn't work for me:

-A POSTROUTING -t nat -p tcp -m tcp -s 192.168.1.200 --sport 8080 -j SNAT --to-source $yours_wan_ip

I have 2 LAN interfaces and FORWARD work when I'll written:

iptables -t nat -A POSTROUTING -o $LAN_IF -p tcp -m tcp --dport $FW_PORT -j SNAT --to-source $LAN_IP
  • LAN_IF - LAN interface (eg. eth1, br0...)
  • FW_PORD - forwarded port (on detination host)
  • LAN_IP - IP address on LAN interface (on the router)

PREROUTING and FORWARD are necessary too, of course :)

You must log in to answer this question.

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