![]() | ![]() |
[120]Our port forwarding example protects your IMAP connection but doesn't truly protect your email messages. Before reaching your IMAP server, the messages pass through other mail servers and may be intercepted in transit. For end-to-end email security, you and your correspondent should use tools such as PGP or S/MIME to sign and/or encrypt the messages themselves.In short, with minimal configuration changes to your programs, SSH port forwarding protects arbitrary TCP/IP connections by redirecting them through an SSH session. Port forwarding can even pass a connection safely through a firewall if you configure things properly. Once you start securing your communications with port forwarding, you'll wonder how you ever got along without it. Here are examples of what you can do:
TIP: SSH port forwarding is a general proxying mechanism for TCP only. (See the sidebar "TCP Connections" for an overview of TCP concepts.) Forwarding can't work with protocols not built on TCP, such as the UDP-based DNS, DHCP, NFS, and NetBIOS,[121] or with non-IP-based protocols, such as AppleTalk or Novell's SPX/IPX.[121]We're being a little imprecise here. DHCP is entirely based on UDP, so SSH port forwarding can't do anything with it. The others, however, either use both TCP and UDP for different purposes or can sometimes be configured to run over TCP, though they generally use UDP. Nevertheless, in most common situations, SSH can't forward them.
IMAP uses TCP port 143; this means that an IMAP server will be listening for connections on port 143 on the server machine. To tunnel the IMAP connection through SSH, you need to pick a local port on home machine H (between 1024 and 65535) and forward it to the remote socket (S,143). Suppose you randomly pick local port 2001. The following command then creates the tunnel:[122]
[122]You can also use ssh -L2001:S:143 S, substituting "S" for localhost, but we will discuss later why localhost is the better alternative when possible.
The -L option specifies local forwarding, in which the TCP client is on the local machine with the SSH client. The option is followed by three values separated by colons: a local port to listen on (2001), the remote machine name or IP address (S), and the remote, target port number (143). The previous command logs you into S, as it will if you just type ssh S. However, this SSH session has also forwarded TCP port 2001 on H to port 143 on S; the forwarding remains in effect until you log out of the session. To make use of the tunnel, the final step is to tell your email reader to use the forwarded port. Normally your email program connects to port 143 on the server machine, that is, the socket (S,143). Instead, it's configured to connect to port 2001 on home machine H itself, i.e., socket (localhost,2001). So the path of the connection is now as follows:$ ssh -L2001:localhost:143 S
Note the small syntactic differences. In SSH1 and OpenSSH, there are two arguments: the local port number, and the remote socket expressed as host:port. In SSH2, the expression is just as on the command line, except that it must be enclosed in double quotes. If you forget the quotes, ssh2 doesn't complain, but it doesn't forward the port, either. Our example with home machine H and IMAP server S can be set up like this:# SSH1, OpenSSH LocalForward 2001 localhost:143 # SSH2 only LocalForward "2001:localhost:143"
# SSH1, OpenSSH Host local-forwarding-example HostName S LocalForward 2001 localhost:143 # Run on home machine H $ ssh local-forwarding-example
The client configuration keyword GatewayPorts also controls this feature; the default value is no, and giving GatewayPorts=yes does the same thing as -g:# SSH1, OpenSSH $ ssh1 -g -L<localport>:<remotehost>:<remoteport> hostname
There's a reason why GatewayPorts and -g are disabled by default: they represent a security risk. [Section 9.2.4.2, "Access control and the loopback address"]# SSH1, OpenSSH GatewayPorts yes
The -R option specifies remote forwarding. It is followed by three values, separated by colons as before but interpreted slightly differently. The remote port to be forwarded (2001) is now first, followed by the machine name or IP address (localhost) and port number (143). SSH can now forward connections from (localhost,143) to (H,2001). Once this command has run, a secure tunnel has been constructed from the port 2001 on the remote machine H, to port 143 on the server machine S. Now any program on H can use the secure tunnel by connecting to (localhost,2001). As before, the command also runs an SSH terminal session on remote machine H, just as ssh H does. As with local forwarding, you may establish a remote forwarding using a keyword in your client configuration file. The RemoteForward keyword is analogous to LocalForward, with the same syntactic differences between SSH1 and SSH2:$ ssh -R2001:localhost:143 H
For example, here's the preceding forwarding defined in an SSH2-format configuration file:# SSH1, OpenSSH RemoteForward 2001 S:143 # SSH2 only RemoteForward "2001:S:143"
# SSH2 only remote-forwarding-example: Host H RemoteForward "2001:S:143" $ ssh2 remote-forwarding-example
TIP: You might think that the GatewayPorts feature discussed in the last section applies equally well to remote port forwardings. This would make sense as a feature, but as it happens, it isn't done. There would have to be a way for the client to communicate this parameter to the server for a given forwarding, and that feature hasn't been included in the SSH protocol. In SSH1 and SSH2, remotely forwarded ports always listen on all network interfaces and accept connections from anywhere. [Section 9.4, "Forwarding Security: TCP-wrappers and libwrap"] The OpenSSH server does accept the GatewayPorts configuration option, and it applies globally to all remote forwardings established by that server.
This configuration works fine if you connect once:# SSH1 syntax used for illustration Host server.example.com LocalForward 2001 server.example.com:143
But if you try to open a second ssh connection to server.example.com at the same time -- perhaps to run a different program in another window of your workstation -- the attempt will fail:$ ssh server.example.com
Why does this happen? Because your configuration file section tries to forward port 2001 again but finds that port is already in use ("bound" for listening) by the first instance of ssh. You need some way to make the connection but omit the port forwarding. SSH1 (but not OpenSSH) provides a solution, the client configuration keyword ClearAllForwardings. From the name, you might think it terminates existing forwardings, but it doesn't. Rather, it nullifies any forwardings specified in the current ssh command. In the previous example, you can connect without forwardings to server.example.com with:$ ssh server.example.com Local: bind: Address already in use
The original tunnel, set up by the first invocation, continues to exist, but ClearAllForwardings prevents the second invocation from attempting to recreate the tunnel. To illustrate the point further, here's a rather silly command:# SSH1 only $ ssh1 -o ClearAllForwardings=yes server.example.com
The -L option specifies a forwarding, but ClearAllForwardings cancels it. This silly command is identical in function to:$ ssh1 -L2001:localhost:143 -o ClearAllForwardings=yes mymachine
ClearAllForwardings may also be placed in your client configuration file, of course. It seems more useful on the command line, however, where it can be used on the fly without editing a file.$ ssh1 mymachine
TIP: If the TCP client application (whose connections you want to forward) is running locally on the SSH client machine, use local forwarding. Otherwise, the client application is on the remote SSH server machine, and you use remote forwarding.The rest of this section is devoted to dissecting the forwarding process in detail and understanding where this rule comes from.
Since the application server is located on the same machine as the connecting side of the SSH port forwarding, the target host can be "localhost." But the connections between the application client and the SSH listening side, and between the application server and the SSH connecting side, are themselves TCP connections. For convenience, TCP implementations allow programs to make connections between two sockets on the same host. The connection data is simply transferred from one process to another without actually being transmitted on any real network interface. However, in principle, either the application client or server -- or both -- could be on different machines, potentially involving as many as four hosts in a single forwarding (Figure 9-8).$ ssh -L2001:localhost:143 server.example.com
The loopback interface leads back to the host itself. A datagram "transmitted" on the loopback interface immediately appears as an incoming packet on the loopback interface and is picked up and processed by IP as being destined for the local host. The loopback interface is always assigned the same IP address: 127.0.0.1, the loopback address,[123] and the local naming service provides the name "localhost" for that address. This mechanism gives a reliable way for processes to communicate with one another on the local host via IP, regardless of what IP addresses the host may have on real connected networks, or indeed if the host has no real network connections at all. You can always refer to your local host using the well-known loopback address.$ ifconfig -a ... lo0: flags=849<UP,LOOPBACK,RUNNING,MULTICAST> mtu 8232 inet 127.0.0.1 netmask ff000000
[123]Actually, the entire network 127.0.0.0/8 -- comprising 24 million addresses -- is reserved for addresses that refer to the local host. Only the address 127.0.0.1 is commonly used, although we have seen devices use a handful of others for special purposes, such as "reject" interfaces on a terminal server or router.By design, a loopback address is local to its host. One machine can't contact the loopback address of another. Since the loopback address 127.0.0.1 is standard on all IP hosts, any connection to 127.0.0.1 leads a machine to talk to itself. (Plus, the loopback network isn't routed on the Internet.)
[124]Named after the Berkeley sockets library routine bind, commonly used to establish the association.Consider, however, what this means in the case of SSH port forwarding. There is no authentication or access control at all applied to the listening side of a forwarding; it simply accepts any connection and forwards it. If the listening side binds all the host's interfaces for the forwarded port, this means that anyone at all with network connectivity to the listening host -- possibly the whole Internet! -- can use your forwarding. This is obviously not a good situation. To address it, SSH by default binds only the loopback address for the listening side of a forwarding. This means that only other programs on the same host may connect to the forwarded socket. This makes it reasonably safe to use port forwarding on a PC or other single-user machine but is still a security problem on multiuser hosts. On most Unix machines, for example, a knowledgeable user can connect to any listening sockets and see what's on them. Keep this in mind when using port forwarding on a Unix machine. If you want to allow off-host connections to your forwarded ports, you can use the -g switch or GatewayPorts option to have the listening side bind all interfaces, as we did in an earlier example: [Section 9.2.4, "Forwarding Off-Host"]
But be aware of the security implications! You may want to exercise more control over the use of forwarded ports in this situation by using TCP-wrappers, which we discuss later in this chapter.$ ssh1 -g -L P:S:W B
This establishes an interactive SSH session from home machine H to bastion host B and also creates an SSH tunnel from local host H to the email server machine W. Specifically, in response to a connection on port 2001, the local SSH client directs the SSH server running on B to open a connection to port 143 on W, that is, socket W:143. The SSH server can do this because B is inside the firewall. If you configure your email reader to connect to local port 2001, as before, the communication path is now:# Executed on home machine H $ ssh -L2001:W:143 B
or use the GoBackground keyword for the same effect:# SSH2 only $ ssh2 -f -L2001:localhost:143 server.example.com
As a result, ssh2 puts itself into the background and handles connections to the forwarded port 2001, and that is all. It doesn't create an interactive terminal session with standard input, output, and error channels. The -S option also avoids starting a terminal session but unlike -f, it doesn't put the session in the background (in other words, the -f option implies -S ):# SSH2 only GoBackground yes
The -f option is also supported by SSH1 and OpenSSH but its operation is different from that of SSH2. It is intended more for executing remote commands that don't require terminal interaction, such as graphical programs using X. Specifically:# SSH2 only $ ssh2 -S -L2001:localhost:143 server.example.com
This is equivalent to the background command:# SSH1, OpenSSH $ ssh -f zwei.uhr.org xclock
In contrast, SSH2 doesn't require a remote command when using the -f option. You may provide one as earlier, and ssh2 behaves in the same way as its SSH1 or OpenSSH counterparts:# SSH1, OpenSSH $ ssh -n zwei.uhr.org xclock &
but the remote command isn't necessary; you can set up a forwarding and put ssh2 into the background conveniently:$ ssh2 -f zwei.uhr.org xclock
If you tried this with SSH1 or OpenSSH, you see:$ ssh2 -f -L2001:localhost:143 server.example.com
To get around the nuisance of providing an unwanted remote command, use one that does nothing for a long time, such as sleep :# SSH1, OpenSSH $ ssh -f -L2001:localhost:143 server.example.com Cannot fork into background without a command to execute.
# SSH1, OpenSSH $ ssh -f -L2001:localhost:143 server.example.com sleep 1000000
One shot forwarding isn't directly supported by SSH1 or OpenSSH, but you can get the same effect with the following method:# SSH2 only $ ssh2 -fo -L2001:localhost:143 server
$ ssh -f -L2001:localhost:143 server sleep 10
$ ssh -p2001 localhost
ssh waits until that connection ends and then terminates, providing the behavior of one shot forwarding.Waiting for forwarded connections to terminate... The following connections are open: port 2001, connection from localhost port 143
[125]Microsoft Windows and MacOS have no privileged port restriction, so any user can listen on any free port.When setting up the listening side of a tunnel, you generally must select a port number between 1024 and 65535, inclusive. This is because an SSH program running under your user ID, not the superuser's, is responsible for listening on that port. If SSH reports that your chosen port is in already in use, just choose another; it shouldn't be hard to find a free one. For the target side of the tunnel, you can specify any port number, privileged or not. You are attempting to connect to the port, not listen on it. In fact, most of the time the target side is a privileged port, since the most common TCP services have ports in the privileged range. If you are the superuser on a machine with SSH clients, you can perform local forwarding with a privileged port. Likewise, you can forward a remote privileged port if your remote account has superuser privileges. Some TCP applications hardcode the server port numbers and don't permit them to be changed. These applications aren't usable with port forwarding if the operating system has a privileged port restriction. For example, suppose you have an FTP client that's hardwired to connect to the server on the standard FTP control port, 21. To set up port forwarding, you have to forward the local port 21 to the remote port 21. But since port 21 is privileged, you can't use it as a listening port number unless you are the superuser. Fortunately, most Unix TCP-based programs let you set the destination port number for connections, and on PCs and Macs, there's no privileged port restriction.
The forwarded connection is made from the remote machine to either the loopback address or remote.host.net, and in either case, the connection stays on the remote machine and doesn't go over the network. However, the two connections are perceptibly different to the server receiving the forwarded connection. This is because the source sockets of the connections are different. The connection to localhost appears to come from source address 127.0.0.1, whereas the connection to remote.host.net is from the address associated with that name. Most of the time this difference doesn't matter, but sometimes you must take it into account. The application server (e.g., the IMAP daemon) might be doing access control based on source address and not be configured to accept the loopback address. Or it might be running on a multihomed host and have bound only a subset of the addresses the host has, possibly not including the loopback address. Each of these situations is usually an oversight, but you might not be able to do anything about it. If you're getting "connection refused" from the connecting side of the forwarding, but you've verified that the server appears to be running and responding to normal clients, this might be the problem. If the server machine is running Unix, the command netstat -a -n should list all the network connections and listeners on that machine. Look for listeners on the relevant port, and the addresses on which they are listening. Sometimes, the problem can be more acute if the server uses the source IP address itself as part of whatever protocol it's speaking. This problem crops up when trying to forward FTP over SSH. [Section 11.2, "FTP Forwarding"] In general, we recommend using localhost as the forwarding target whenever possible. This way, you are less likely to set up an insecure off-host forwarding by accident.$ ssh -L2001:localhost:143 remote.host.net $ ssh -L2001:remote.host.net:143 remote.host.net
The ssh2 process now waits in the background until the forwarded connections terminate, and then it exits. In contrast, with SSH1 and OpenSSH, if you disconnect a session with active forwardings, you get a warning, but the session stays in the foreground:remote$ logout warning: ssh2[7021]: number of forwarded channels still open, forkedto background to wait for completion. local$
To send it into the background and return to your local shell prompt, use the escape sequence return-tilde-ampersand: [Section 2.3.2, "The Escape Character"]remote$ logout Waiting for forwarded connections to terminate... The following connections are open: port 2002, connection from localhost port 1465
and as with SSH2, the connection exits only after its forwarded connections terminate. Be careful not to use the SSH ^Z escape for this purpose. That sends ssh into the background but suspended, unable to accept TCP connections to its forwarded ports. If you do this accidentally, use your shell's job control commands (e.g., fg and bg ) to resume the process.~& [backgrounded] local$
(This happens commonly if you're experimenting with port forwarding, trying to get something to work.) You know that you have no active SSH command listening on port 2001, so what's going on? If you use the netstat command to look for other listeners on that port, you may see a connection hanging around in the TIME_WAIT state:$ ssh1 -L2001:localhost:21 server.example.com Local: bind: Address already in use
The TIME_WAIT state is an artifact of the TCP protocol. In certain situations, the teardown of a TCP connection can leave one of its socket endpoints unusable for a short period of time, usually only a few minutes. As a result, you cannot reuse the port for TCP forwarding (or anything else) until the teardown completes. If you're impatient, choose another port for the time being (say, 2002 instead of 2001) and get on with your work, or wait a short time for the port to become usable again.$ netstat -an | grep 2001 tcp 0 0 127.0.0.1:2001 127.0.0.1:1472 TIME_WAIT
In addition, SSH2 has the following options:# SSH1, SSH2, OpenSSH AllowTcpForwarding no
The syntax of these is the same as for the AllowUsers and AllowGroups options. [Section 5.5.2.1, "Account access control"] They specify a list of users or groups that are allowed to use port forwarding; the server refuses to honor port forwarding requests for anyone else. Note that these refer to the target account of the SSH session, not the client username (which is often not known). F-Secure SSH1 Server supports the additional keywords AllowForwardingPort, DenyForwardingPort, AllowForwardingTo, and DenyForwardingTo for finer-grained control over forwarding. The two ...Port keywords let you control remote forwardings for given TCP ports, with support for wildcards and numeric ranges. For example, to permit remote forwardings for ports 3000, 4000 through 4500 inclusive, 5000 and higher, and any port number ending in 7:# SSH2 only AllowTcpForwardingForUsers AllowTcpForwardingForGroups
The ...To keywords are similar but control forwardings to particular hosts and ports (i.e., to particular sockets). Host and port specifications are separated by colons and use the same metacharacters as the ...Port keywords:# F-Secure SSH1 only AllowForwardingPort 3000 4000..4050 >5000 *7
The permissible metacharacters/wildcards are shown in the following table:# F-Secure SSH1 only DenyForwardingTo server.example.com:80 other.net:* yoyodyne.com:<1024
Metacharacter | Meaning | Example |
---|---|---|
* | Any digit |
300* |
< | All values less than |
<200 |
> | All values greater than |
>200 |
.. | Range of values (inclusive) |
10..20 |
It's important to realize that the directives in this section don't actually prevent port forwarding, unless you also disable interactive logins and restrict what programs may be run on the remote side. Otherwise, knowledgeable users can simply run their own port-forwarding application over the SSH session. These settings alone might be a sufficient deterrent in a nontechnical community, but they won't stop someone who knows what she's doing.
(SSH2 doesn't currently have this feature.) Any SSH client that authenticates using this key can't perform port forwarding with your SSH server. The same remarks we just made about serverwide port forwarding configuration apply here: the restriction isn't really meaningful unless you further restrict what this key is allowed to do.# SSH1, OpenSSH no-port-forwarding ...key...
![]() | ![]() | ![]() |
9. Port Forwarding and X Forwarding | ![]() | 9.3. X Forwarding |
Copyright © 2002 O'Reilly & Associates. All rights reserved.