SSH gateway: how to understand ssh -L when remote comes first?

I understand some ssh forwarding basics, but this SuperUser post seems backwards to me. In other words, with 2 hosts, this…

ssh -L 0.0.0.0:10022:localhost:22 root@A

…seems to allow a connection from localhost to A. But with 3 hosts, this…

ssh -L 0.0.0.0:10022:A:22 root@B

…allows localhost to A though B? Why not localhost to B through A?

The ssh man page seems to describe the 2-hosts option, not the 3-hosts option:

-L [bind_address:]port:host:hostport

Whenever a connection is made to the local port or socket, the connection is forwarded over the secure channel, and a connection is made to either host port hostport, or the Unix socket remote_socket, from the remote machine.


Solution 1:

It's not clear to me what your (mis)understanding really is. I guess the confusion might be because of the word "localhost".

Localhost is a relative term. By definition, in context of any machine localhost should refer to this machine exactly. Practically every Linux resolves localhost as IP address 127.0.0.1 (I put IPv6 aside) thanks to a proper entry in /etc/hosts file. 127.0.0.1 should be assigned to a loopback interface.

In the linked answer most occurrences of the word localhost refer to the machine (of three) that is neither host1 nor host2; this is the local machine where commands are invoked. Similarly, when you say "localhost" you probably mean neither A nor B. From now on let's call this local computer the client.

Basically you run this on the client:

ssh -L bind_address:port:host:hostport user@server

There are two computers involved: the client and the server. Certain parts of the command are valid in context of either the client or the server.

  • ssh -L is the executable with option that the client understands (the server may not have ssh at all).
  • server is the address of the server from the client's point of view (server may not even be aware it has such-and-such address or name).
  • user is a username existing on the server (it may not exist on the client).
  • bind_address and port are respectively the address (interface) and TCP port on which the client's ssh will listen (I don't know if these parameters are even passed to the server at all, the server doesn't need them). In your case 0.0.0.0 means "every available interface".
  • host and hostport are respectively the address and TCP port to which the server should send packets tunneled from the client. These parameters are for the server; host is resolved on the server. From the client's point of view host may be an invalid address or it may resolve to something completely different – it doesn't matter because the client doesn't resolve it at all; host is just a character string passed to the server, it means nothing more on the client's side.

This means if there's a literal localhost as this host parameter, it is "localhost" from the server's point of view, i.e. the server itself. It doesn't mean "the client".


With this knowledge let's analyze your examples.

ssh -L 0.0.0.0:10022:localhost:22 root@A

This captures everything that enters the TCP port 10022 of the client; captured packets will be recreated on the server A and destined to localhost:22, but localhost on the server means "the loopback interface of the server A itself".

ssh -L 0.0.0.0:10022:A:22 root@B

This captures everything that enters the TCP port 10022 of the client; captured packets will be recreated on the server B and destined to A:22 from there.

Indeed it can be described as "localhost to A though B", where "localhost" means the client.