Categories

Calendar

October 2017
M T W T F S S
« Jun    
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

SSH tunnel tips

wormhole从我两三年前接触 SSH 的时候我就在文档上见过 SSH tunnel 相关的东西,然而当时没有怎么看明白,也就一直没有深究,直到最近需求越来越多了,才终于发现这个东西原来这么有用,于是记录在此。

SSH tunnel 主要有三种,一种是 dynamic application-level port forwarding ,可以用来作为 SOCKS proxy ,通常翻墙就是用的这种端口映射;一种是 Local Forwarding ,主要用于提供常规的加密隧道,例如让 IMAP 协议通过这个加密隧道,避免密码在网络上被人监听到;一种是 Remote Forwarding ,可以用于逆向穿透 NAT 。其实,另外还有一种专门为 X 打造的 X11 Forwarding ,一般不会用到,Linux 下远程登录通常不会需要开启 X 程序,或者有其他更好用的方法,因此我们这里并不打算介绍这个。

首先我们来假定一个网络环境:有一个防火墙,在防火墙内部有一个机器 H ,通过 NAT 访问 Internet ,并且在防火墙外的 Internet 上的机器 T 上有一个 ssh 帐号,而另一台 Internet 上的机器 S 则是一台公共服务器,例如 Google 的服务器。如下图所示:
ssh
首先来说 Local Forwarding ,相关的文档上都用的是阅读邮件这个经典的例子:用户在 T 上有一个 IMAP 服务器,本来他可以直接连接到 T:143 上阅读邮件的,但是这是未加密连接,如果你的 IMAP 服务器是用明文密码验证的方式的话,就很糟糕了,这样你的密码很容易被别人窃听到。这个时候 SSH 隧道就出来了,通过如下命令可以建立一个 SSH 隧道:

ssh -L 9143:localhost:143 T

首先不看参数,这个命令是发起一个 ssh 连接到主机 T ,然后选项 -L 表示 Local Forwarding ,9143 表示本地(亦即 H)的端口,然后 localhost:143 则是远程(即 T)上所对应的主机和端口。这样一来,你在 H 上连接端口 9143 ,就相当于你在 T 上连接 localhost:143 一样,SSH 建立的隧道会帮你在两个主机间传递信息。现在只要配置 H 上的邮件客户端去连接 localhost:9143 ,就能通过加密的方式访问到 T 上的 localhost:143 这个邮件服务器了。注意两个主机上的 localhost 的区别。

当然隧道并不限于 localhost ,如果你愿意,可以做这样一个映射:

ssh -L 9999:www.google.com:80 T

这样,你可以在 H 的浏览器里输入 localhost:9999 访问到 Google 了。注意到 H 和 T 之间的数据传输是加密的,所以,如果 H 是处在伟大的局域网内,而 T 是在外部的话,可以通过这样的方法来访问 Google 而不受防火墙的限制。不过这种方法每个端口只能映射一个网站,很不方便。要用作代理通常还是使用 dynamic application-level port forwarding 的方式,如下:

ssh -D 9999 T

这样建立起来的隧道实际上是一个 SOCKS 代理,当然要使用 SOCKS 代理需要客户端能够支持才行,比如在 Firefox 中,只要在代理处填写 localhost:9999 并勾选 SOCKS5 代理即可。另外,还可以使用诸如 ProxyChains 之类的程序让本身不支持 SOCKS 代理的程序能够“透明地”使用 SOCKS 代理,不过我并没有尝试过,不知道稳定性如何。

最后要介绍的就是 Remote Forwarding 了,首先看命令吧:

ssh -R 9923:10.13.21.88:23 T

这里 10.13.21.88 是 H 所在的局域网里的一台主机,23 是 telnet 默认的端口,其实这就是浙大飘渺水云间 bbs (亦即“88”)的校内地址了,由于是在局域网内,Internet 上的 T 是访问不到它的,现在 H 建立了一个到 T 的 Remote Forwarding ,对应之前的 Local Forwarding 的意思,应该能猜到了吧?现在在 T 上可以通过访问 localhost:9923 来上 88 了。 :D 换句话说,在 T 上访问 localhost:9923 相当于在 H 上访问 10.13.21.88:23 ,这成了一个反向的隧道。

另外,上面三种 Forwarding 默认建立的 socket 都是监听本机的 loopback 地址的,这样外面的机器是不能访问这个端口映射的。对于最后那个飘渺水云间的隧道来讲,默认情况下,T 虽然可以通过 localhost:9923 来上 88 ,但是 S 却不能通过 T:9923 来访问到,而是会得到一个 Connection Refused 的错误,这样做也是为了安全起见,不过,如果确实想要让这个隧道可以从其他主机访问到的话,上面三个命令都可以在映射参数前面再加一个地址参数。例如飘渺水云间的那个映射其实等价于:

ssh -R localhost:9923:10.13.21.88:23 T

把 localhost 改成 * 就可以让其他机器也访问到了(注意加上引号以防止 shell 把星号给展开了):

ssh -R '*:9923:10.13.21.88:23' T

唔,还有一点要说明的是,对于 -L 和 -D 来说,都是这样就可以了,而 -R 则还需要远程的 sshd 配置一下 GatewayPorts 选项,默认情况下是 no ,这样会无视掉客户端的请求,强制 bind 到 loopback 地址上,而 yes 则相当于强制 * ,如果设置成 clientspecified 则允许客户端来选择。因此在 /etc/ssh/sshd_config 文件里添加这样一行:

GatewayPorts clientspecified

再重启 ssh 服务即可。虽然学校提供了 RVPN 可以从校外访问到校内的资源,但是并没有提供 Linux 下的解决方案。在没有校外独立 IP 的情况下用这样的方法在外面访问学校的资源就变得非常有用了。要是 -D 也能有一个对应的 Reverse 的方案也许会更方便一些,不过也许可以用 -R 和 -D 桥接一个:

H$ ssh -D 9999 localhost
H$ ssh -R 9999:localhost:9999 T

在 T 上使用 localhost:9999 作为 SOCKS 代理,而该端口的数据会被 forward 到 H 上的 localhost:9999 ,那里是实际的 SOCKS 代理服务器。不过我没有实际测试过是不是真的可以用。 :p

最后再说一句,FTP 是另一个经典的明文传输密码的协议,但是它却没法通过 SSH 隧道使用,因为它需要另外打开一个 socket 来传输数据,这时就不在 SSH 的接管范围之内了,我想这也许是 SFTP 这个东西诞生的直接原因吧。其实使用了 -R 的端口映射之后,要 scp 回来是一件很容易的事,例如:

H$ ssh -R 9922:localhost:22 T
T$ scp foo.txt -P 9922 localhost:~/

可以将 T 上的 foo.txt 拷贝回 H 的主目录中。当然,如果只是为了做一个端口映射,可不必打开一个远程 shell ,只要再为 ssh 加上 -Nf 参数即可,具体是什么意思自己 man 吧。它会在后台运行,一直等待到对应隧道端口的连接。需要注意的是,在有了第一个连接之后,如果连接数降为零了,它会自动退出。所以我通常还是会打开远程 shell ,不过使用 screen 把它放在后台去,就不会妨碍了,需要的时候可以调出来,也可以方便地控制它什么时候退出。 :)

最后,在客户端的 ~/.ssh/config 里加入

ServerAliveInterval 180

这样可以防止长时间的 idle 导致 ssh 连接被自动断开。

11 comments to SSH tunnel tips

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>