31

Consider the following situation:

At my home, I have a router (which is connected to internet), server (S) and my main machine (M). S is reachable from the internet (it has static IP), and it is up 24/7, while M is not.

Sometimes, I want to make some app (which listens on some port on M, for example 8888) accessible from outer internet.

For that, I wanted to set up some port on S (2222) to forward to M's port 8888, so that anybody accessing S:2222 would feel like he was accessing M:8888.

I tried to use ssh port forwarding, my best attempt was as follows:

ssh -L 2222:M:8888 -N M

But that only allows me to access 2222 port from server itself, not from other machines.

Is there some way to do it properly? Preferably, I'd like it to be a simple command, which I would be able to start and shut down with ^C when I don't need that forwarding anymore.

1
  • Try localhost.run which is a website you can get to at localhost.run I think there is a way to get your product out there.. Commented Jun 3, 2019 at 7:21

5 Answers 5

20

Yes, this is called GatewayPorts in SSH. An excerpt from ssh_config(5):

GatewayPorts
        Specifies whether remote hosts are allowed to connect to local
        forwarded ports.  By default, ssh(1) binds local port forwardings
        to the loopback address.  This prevents other remote hosts from
        connecting to forwarded ports.  GatewayPorts can be used to spec‐
        ify that ssh should bind local port forwardings to the wildcard
        address, thus allowing remote hosts to connect to forwarded
        ports.  The argument must be “yes” or “no”.  The default is “no”.

And you can use localhost instead of M in the forwarding, as you're forwarding to the same machine as you're SSH-ing to -- if I understand your question correctly.

So, the command will become this:

ssh -L 2222:localhost:8888 -N -o GatewayPorts=yes hostname-of-M

and will look like this in netstat -nltp:

tcp        0      0    0.0.0.0:2222   0.0.0.0:*  LISTEN  5113/ssh

Now anyone accessing this machine at port 2222 TCP will actually talk to localhost:8888 as seen in machine M. Note that this is not the same as plain forwarding to port 8888 of M.

3
  • 1
    Thanks! That works! But there is some strangeness - for some reason, the output contains line "bind: Address already in use". What that could mean? Commented Dec 6, 2012 at 9:29
  • 1
    You already have some process running on that port. Use the same netstat command to find out what exactly. Probably another similar SSH still running in the background and kill it using the PID netstat tells you. Commented Dec 6, 2012 at 9:40
  • Fun thing is that I already done that - no processes on those ports, on both S and M. If there were some, then the whole construction probably would have failed to work. Commented Dec 6, 2012 at 10:01
17

There is another way. You may set up port forwarding from S:2222 to W:8888 with iptables. Single command:

iptables -t nat -A PREROUTING -p tcp --dport 2222 \
         -j DNAT --to-destination 1.2.3.4:8888

where 1.2.3.4 is M's IP address. It is called NAT (Network Address Translation).

4
  • 1
    As you're only doing destination NAT here (opposed to source NAT), this will only work reliably in specific situations and could require to modify routing tables. For forwarding to virtual machines (M) running on the host (S), this might work well, though. Commented Dec 6, 2012 at 10:23
  • This command should be issued on gateway. I assume that S and M are on the same LAN. Source NAT would be done automatically by connection tracker in modern Linux kernels. I have such config in my office network and it works perfectly. However, it is possible to make command more specific (for example, telling iptables -i eth0 where eth0 is outer interface). Commented Dec 6, 2012 at 10:43
  • If S and M are on the same LAN, you don't need port forwarding there, as no traffic between them passes the gateway. Commented Dec 6, 2012 at 10:47
  • 1
    Yeap, but connecting to M:8888 is made from Internet, I assume. Somebody form Internet -> S:2222 -> M and S LAN router with iptables -> M:8888 Commented Dec 6, 2012 at 10:58
13

More alternatives: netcat (traditional) or socat

On the server (S):

socat tcp-listen:2222,reuseaddr,fork tcp:M:8888

or

nc -l -p 2222 -c 'nc M 8888'

Details see in: Simple way to create a tunnel from one local port to another?

4

Concise answer

The original command is mostly fine, but it doesn't specify the address to listen on, which results in the process listening on 127.0.0.1 by default.

Change your command from ssh -L 2222:M:8888 -N M to ssh -L S:2222:M:8888 -N M and it should work fine.

Detailed answer

The issue you experience is caused by the use of a short notation in -L - "localPort:remoteHost:remotePort".

If you use it, ssh listens on 127.0.0.1.

For example:

ssh -L 2222:M:8888 -N M

Will result in something like this (note the address being listened on):

sudo netstat -nlp | grep 2222
tcp        0      0 127.0.0.1:2222          0.0.0.0:*               LISTEN      16208/ssh

If you use the full notation ("localAddress:localPort:remoteHost:remotePort"):

ssh -L external-address-of-S:2222:M:8888 -N M

Will result in something like

sudo netstat -nlp | grep 2222
tcp        0      0 external-address-of-S:2222          0.0.0.0:*               LISTEN      16208/ssh

P.S. GatewayPorts is a bit more generic solution, but it behaves differently if your server has more than one IP address. This solution lets you pick the IP to listen on (and lets you forward the same port on different IPs to different destinations) while GatewayPorts listens on all of them and forwards them to the same destination. Which one works better for you will depend on your requirements - but both will work for the OP.

P.P.S. I realize it's a very old question, but it's still very relevant today (probably more so due to the widespread use of cloud) and I believe it's missing a simple answer that might help the people who encounter a similar issue.

0

Port forwarding without daemon nor persistent tunnel

I just had this kind of problem. In my situation, I'm migrating a slapd service (LDAP server). All clients are configured to reach the LDAP server using a CNAME (ldap-server.example.com). The correct way (and it will be the final state) will be to fix/update the CNAME. But the CNAME is managed by other people and I would like to redirect incomming ldaps connection from the old server to the new server now.

I just run these three commands on the old server when the network interface is up:

sysctl net.ipv4.ip_forward=1
iptables -t nat -A PREROUTING -p tcp -d 10.0.0.62 --dport 636 -j DNAT --to-destination 10.0.0.35:636
iptables -t nat -A POSTROUTING -o enp3s0 -j MASQUERADE

where 10.0.0.62 is the IP of the old server, 10.0.0.35 is the IP of the new server, 636 is the port I'm forwarding (ldaps) and enp3s0 is the (only one) network interface on the old server.

By default, TCP forwarding is disabled, hence the first line. Without it, no packets are redirected at all.

The second line redirects incoming TCP (ldaps) packets to the new server.

The third line is important: without it, redirected packets still have the LDAP client IP as IP source, hence new server answers are directly sent to the client (that do not understand why it receives packets from a machine it never contacts). With the third line, the source IP of forwarded packets is changed (for the local IP, the old server IP) and the kernel tracks this change. So answers are sent to this (old server) machine and also redirected to the LDAP client adjusting source/dest IP (thanks to the kernel masquerading).

So, I got a port forwarding done at kernel level, without the need of processes (nc, ssh, etc.) nor daemons (sshd, etc.)

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.