My problem is that when I print out the socket information using getsockname and getpeername, the source address is always the server's. If I connect to the server directly through a port that is not affected by the iptables rule I see the client address as expected. Why is port forwarding affecting the source address?
I'll explain, but be warned that this is going to be a long explanation.
Let's first understand what is a TCP Connection: It is a stateful connection-oriented data interchange between an 'initiator tuple' (IP,Port)
and a 'terminator tuple' (IP,port)
.
In other words, the TCP Connection is defined by a four-ple of (Client_IP,Client_Port,Server_IP,Server_Port)
.
This means, all information transfer for that TCP Connection needs to fulfill the four-ple definition.
Now, let's assume that your server is located at 1.1.1.1, and the client at 2.2.2.2. The client opens the connection to your server's port 80. Assuming the client's ephemeral port is 34567, we have the four-ple (2.2.2.2,34567,1.1.1.1,80).
On your server, the packet gets redirected to 1.1.1.1:10080. But this results in a different four-ple of connection! E.g., (2.2.2.2,34567,1.1.1.1,10080).
What will the actual listener do? Why it would respond directly to 2.2.2.2:34567, resulting in the packet to be dropped by the client, because the client has the four-ple of (2.2.2.2,34567,1.1.1.1,80) and not (2.2.2.2,34567,1.1.1.1,10080).
So, in this case, the REDIRECT
target have to create a temporary four-ple mapping:
(2.2.2.2,34567,1.1.1.1,80) --> (1.1.1.1,x,1.1.1.1,10080)
(x is a temporary, ephemeral port to allow TCP Connection to be built).
With this temporary mapping, the server at 10080 responds back to netfilter
, and a reverse mapping happens:
(1.1.1.1,x,1.1.1.1,10080) --> (2.2.2.2,34567,1.1.1.1,80)
netfilter
sends the packet which has been reverse-mapped to the Client, and all is well.
So, if you need to know the client's IP address, you can NOT use the REDIRECT
target. Rather, put your actual server in a different subnet, and use DNAT
target, with your Linux server acting as a full router. Assuming your actual server has a local LAN address of 192.168.1.51, the mapping will be:
(2.2.2.2,34567,1.1.1.1,80) --> (2.2.2.2,34567,192.168.1.51,80)
The server at 192.168.1.51 will reply to 2.2.2.2, and the router will reverse map:
(2.2.2.2,34567,192.168.1.51,80) --> (2.2.2.2,34567,1.1.1.1,80)
And all's well.