How to redirect port 80 to 8080 while keeping 8080 closed to the Internet?

Solution 1:

This schematic should help you understand how packet handling is done:

Packet flow in Netfilter and General Networking

The filter/INPUT rules see only packets after they were NATed in nat/PREROUTING, so by default can't tell the difference between receiving a packet on port 8080 because a client sent it there directly, from receiving a packet on port 8080 because a client sent it on port 80 which was then redirected to port 8080. In both cases, they see a packet arriving on port 8080. So you need an additional information to be able to tell those two cases apart.

First thing first: this rule should be removed since it's useless:

    -A INPUT -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT

because as explained filter/INPUT won't see a packet on port 80: it's now arriving on port 8080. Don't trust tcpdump blindly here: as seen in the schematic, tcpdump (AF_PACKET) sees packets before all this, so will see port 80.


  • You can use iptables's conntrack match which queries netfilter's connection tracking system and has thus access to the missing information: for this case whether the current incoming packet is part of a connection that underwent a DNAT transformation or not, using --ctstate DNAT (REDIRECT is a special case of DNAT, just like MASQUERADE is a special case of SNAT). There are other options for this match like --ctorigdstport etc. which can probably achieve similar results.

    I'll just put the important rules about this case, rather than all, to illustrate the explanation. Just use them, when needed, at the correct place.

    iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
    

    probably right after the generic ... ESTABLISHED,RELATED -j ACCEPT line:

    iptables -A INPUT -p tcp --dport 8080 -m conntrack --ctstate DNAT -j ACCEPT
    iptables -A INPUT -p tcp --dport 8080 -j DROP
    

    (or don't add this last DROP rule if there's later a catch-all drop rule).

    The first INPUT rule will match traffic arriving at port 8080 which was DNATed (here from initially arriving at port 80), while the second will drop what's remaining arriving at port 8080: direct connection attempts.

    Note: if you're wondering why the state match and the conntrack match look similar, that state has been superseded by conntrack. Actually, internally the kernel module xt_conntrack.ko handles the state match in addition to the conntrack match, for backward compatibility.

  • An other method to transmit this information is by using a packet mark, which is a more generic method and can be applied to many other cases. It's an arbitrary number that will mark the packet (only in the kernel, not on the wire) and can be used in a few places, including the iptables mark match to alter decisions. As the MARK target is not a terminating rule it can be used before the actual REDIRECT rule. As they are displayed back in hexadecimal, I set them also in hexadecimal. The value is yours to choose what it means. Here 0x80 (which is decimal 128) will convey the information "hit port 80 and was redirected to port 8080" all by itself, so no need to recheck the port later, checking the mark validates all. As usual it's the first packet of the connection that counts: each other packet of this connection is handled by the generic conntrack stateful rule ... ESTABLISHED,RELATED -j ACCEPT.

    iptables -t nat -A PREROUTING -p tcp --dport 80 -j MARK --set-mark 0x80
    iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
    

    After the generic ... ESTABLISHED,RELATED -j ACCEPT line:

    iptables -A INPUT -m mark --mark 0x80 -j ACCEPT
    iptables -A INPUT -p tcp --dport 8080 -j DROP
    

I must also warn you against the danger of using a REJECT rule without first dropping (rather than rejecting) INVALID state packets. This might get you random connection reset issues in certain rare congested cases when TCP packets arrive in the wrong order. Relevant documentation of this issue is currently being pondered for addition:

So, instead of:

-A INPUT ... -j REJECT

do consider using:

-A INPUT ... -m conntrack --ctstate INVALID -j DROP
-A INPUT ... -j REJECT

Solution 2:

REDIRECT just changes the port number, so if the connection was:

 client -> public_addres:80

it becomes

client -> public_address:8080

So, you will not be able to block and accept at the same time by doing what you are doing.

First remove this rule:

-A INPUT -p tcp -m tcp --dport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT

And use DNAT instead of REDIRECT, something like:

-A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j DNAT --to 127.0.0.1:8080

That might work.

You could also listen just on 127.0.0.1:8080 (as opposed to 0.0.0.0:8080).