Duplicating a service on multiple ports in Linux


Question: how do you provide a service on two distinct ports on the same machine?

The simple answer is to run it twice of course. Only problem is that it's easier said than done, just like Carl Sagan's famous "to make an apple pie from scratch, first you must invent the Universe".

For one, it may be that the first service is handled by the OS (e.g.: one of the distribution's web server package) which is inconvenient to duplicate: you'll have to work out all the bugs the maintainers have worked around and with the OS fighting you (well, unless you keep abreast with your distro's development).

Second, you'll have to duplicate every configuration change to keep things in sync and may even run into either concurrency issues (deadlocks or one process overwriting the files of the other) or synchronization issues if data is kept separate.

One possibility would be to be able to somehow fool the client into thinking it's talking to port A when really it's talking to port B. But what would that actually require?

Well, from the client's perspective things must look like this:

client

That is, the client expects to send packets to the server on port 8080 and it expects to receive replies from the server AND from that port. On the other hand, the server expects to receive packets on the only port it is listening on and will only send replies from that port (port 80 here):

server

So, what we need is a sort of magic "translator" box, a digital phone line operator of sort:

magic box

Trying it out

Well, it just so happens that Linux has this nifty thing called netfilters (iptables) that's able to modify network traffic. In other words: iptables may be able to act as our magic translator!

Indeed, two of the TARGETS listed in iptables-extensions(8) are SNAT (Source NAT) and DNAT (Destination NAT). These rules are meant for IP address translation, but the manpage seems to indicate that it is possible to specify just a port, so let's try that:

# iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination :80
# iptables -t nat -A POSTROUTING -p tcp --sport 80 -j SNAT --to-source :8080

Does it work?

$ telnet mysite.example.com 8080
Trying 192.99.145.34...
Connected to dioo.ca.
Escape character is '^]'.
GET / HTTP/1.1

HTTP/1.1 400 Bad Request
Date: Fri, 18 Aug 2017 03:59:16 GMT
Server: Apache
Content-Length: 226
Connection: close
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>
Connection closed by foreign host.

Neeto! But how does it work with the other rules? Does it bypass INPUT rules matching on the new port?

Before the connection (notice the '0' in the 'pkts' column):

# iptables -L INPUT -vn --lin | grep 'dpt:80\|num'
num   pkts bytes target     prot opt in     out     source               destination
*        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80

After the connection (notice the '2' in the 'pkts' column):

# iptables -L INPUT -vn --lin | grep 'dpt:80\|num'
num   pkts bytes target     prot opt in     out     source               destination
*        2   120 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80

Looks like it does!