ssh建隧道简单介绍
我想在家里访问我在公司的机器(写程序,查数据,下电影)。
公司为了防止我们用XX软件*了它的端口或者服务器地址。
公司不让我们上XX网站,限制了网址甚至IP。
公司不让我们看关于XX的信息,甚至花血本买了XX设备,能够对内容进行过滤。一看XX内容,链接就中断了。
我爸是搞电脑的,他在家里的路由器上动了手脚,我不能看XXX了。
带着这些问题,我们先从什么是ssh隧道开始。
什么是SSH隧道
我们的电脑在右上角,通过公司带有防火墙功能的路由器接入互联网(当然可能还有交换机什么的在中间连接着你和路由器,但是在我们的问题中交换机并不起到什么关键性的作用)。右下脚的部分是一个网站的服务器,它是我们公司防火墙策略的一部分,也就是说公司不希望我们访问这个服务器。在右上角还有一台机器,它也是属于我们的。但是这台机器并不在我们公司里面,换句话说他不受到公司防火墙的限制。最后也是最重要的一点是,我们能够在公司通过互联网直接访问这台机器。或者说这台位于公司防火墙外面的机器需要拥有一个独立的互联网IP,同时公司的防火墙规则不会屏蔽这台机器,并且这台机器运行着一个OpenSSH服务器。
现在,我们清楚地知道了自己所处的网络环境。并且不难理解我们在公司无法访问那个服务器的原因是:线路A-B-C上A-B之间的防火墙屏蔽了对那个服务器的访问。与此同时,我们也很快注意到,线路A-B-D之间、D-C之间是不受阻碍的。相信你已经想到了,在A-B之间的防火墙不会屏蔽对机器d的访问。因此我们可以通过机器d建立一个通道A-B-D-C,从而访问到机器c上的数据。
这条通道可以用很多技术来建立,这里我们仅仅介绍如何使用SSH服务器来建立这样一个通道-他被称为SSH隧道。
如何建立本地SSH隧道
在我们计划建立一个本地SSH隧道之前,我们必须清楚下面这些数据:
中间服务器d的IP地址
要访问服务器c的IP地址
要访问服务器c的端口
需要访问234.234.234.234的FTP服务,也就是端口21
中间服务器是123.123.123.123
现在我们使用下面这条命令来达成我们的目的
ssh -N -f -L 2121:234.234.234.234:21 123.123.123.123
ftp localhost:2121 # 现在访问本地2121端口,就能连接234.234.234.234的21端口了
这里我们用到了SSH客户端的三个参数,下面我们一一做出解释:
-N 告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发
-f 告诉SSH客户端在后台运行
-L 做本地映射端口,被冒号分割的三个部分含义分别是
需要使用的本地端口号
需要访问的目标机器IP地址(IP: 234.234.234.234)
需要访问的目标机器端口(端口: 21)
最后一个参数是我们用来建立隧道的中间机器的IP地址(IP: 123.123.123.123)
我们再重复一下-L参数的行为。-L X:Y:Z的含义是,将IP为Y的机器的Z端口通过中间服务器映射到本地机器的X端口。
在这条命令成功执行之后,我们已经具有绕过公司防火墙的能力,并且成功访问到了我们喜欢的一个FTP服务器了。
如何建立远程SSH隧道
通过建立本地SSH隧道,我们成功地绕过防火墙开始下载FTP上的资源了。那么当我们在家里的时候想要察看下载进度怎么办呢?大多数公司的网络是通过路由器接入互联网的,公司内部的机器不会直接与互联网连接,也就是不能通过互联网直接访问。通过线路D-B-A访问公司里的机器a便是不可能的。也许你已经注意到了,虽然D-B-A这个方向的连接不通,但是A-B-D这个方向的连接是没有问题的。那么,我们能否利用一条已经连接好的A-B-D方向的连接来完成D-B-A方向的访问呢?答案是肯定的,这就是远程SSH隧道的用途。
与本地SSH一样,我们在建立远程SSH隧道之前要清楚下面几个参数:
需要访问内部机器的远程机器的IP地址(这里是123.123.123.123)
需要让远程机器能访问的内部机器的IP地址(这里因为是想把本机映射出去,因此IP是127.0.0.1)
需要让远程机器能访问的内部机器的端口号(端口:22)
在清楚了上面的参数后,我们使用下面的命令来建立一个远程SSH隧道
ssh -N -f -R 2222:127.0.0.1:22 123.123.123.123
现在,在IP是123.123.123.123的机器上我们用下面的命令就可以登陆公司的IP是192.168.0.100的机器了。
ssh -p 2222 localhost
-N,-f 这两个参数我们已经在本地SSH隧道中介绍过了。我们现在重点说说参数-R。该参数的三个部分的含义分别是:
远程机器使用的端口(2222)
需要映射的内部机器的IP地址(127.0.0.1)
需要映射的内部机器的端口(22)
例如:-R X:Y:Z 就是把我们内部的Y机器的Z端口映射到远程机器的X端口上。
建立SSH隧道的几个技巧
自动重连
隧道可能因为某些原因断开,例如:机器重启,长时间没有数据通信而被路由器切断等等。因此我们可以用程序控制隧道的重新连接,例如一个简单的循环或者使用 djb’s daemontools . 不管用哪种方法,重连时都应避免因输入密码而卡死程序。关于如何安全的避免输入密码的方法,请参考我的 如何实现安全的免密码ssh登录 。这里请注意,如果通过其他程序控制隧道连接,应当避免将SSH客户端放到后台执行,也就是去掉-f参数。
保持长时间连接
有些路由器会把长时间没有通信的连接断开。SSH客户端的TCPKeepAlive选项可以避免这个问题的发生,默认情况下它是被开启的。如果它被关闭了,可以在ssh的命令上加上-o TCPKeepAlive=yes来开启。
另一种方法是,去掉-N参数,加入一个定期能产生输出的命令。例如: top或者vmstat。下面给出一个这种方法的例子:
ssh -R 2222:localhost:22 123.123.123.123 "vmstat 30"
检查隧道状态
有些时候隧道会因为一些原因通信不畅而卡死,例如:由于传输数据量太大,被路由器带入stalled状态。这种时候,往往SSH客户端并不退出,而是卡死在那里。一种应对方法是,使用SSH客户端的ServerAliveInterval和ServerAliveCountMax选项。ServerAliveInterval会在隧道无通信后的一段设置好的时间后发送一个请求给服务器要求服务器响应。如果服务器在ServerAliveCountMax次请求后都没能响应,那么SSH客户端就自动断开连接并退出,将控制权交给你的监控程序。这两个选项的设置方法分别是在ssh时加入-o ServerAliveInterval=n和-o ServerAliveCountMax=m。其中n, m可以自行定义。
如何将端口绑定到外部地址上
使用上面的方法,映射的端口只能绑定在127.0.0.1这个接口上。也就是说,只能被本机自己访问到。如何才能让其他机器访问这个端口呢?我们可以把这个映射的端口绑定在0.0.0.0的接口上,方法是加上参数-b 0.0.0.0。同时还需要打开SSH服务器端的一个选项-GatewayPorts。默认情况下它应当是被打开的。如果被关闭的话,可以在/etc/sshd_config中修改GatewayPorts no为GatewayPorts yes来打开它。
如何寻找中间服务器
如果你家里使用ADSL上网,多半你会比较幸运。一般的ADSL(例如 联通 的ADSL)都是有互联网地址的。你只需要在家里的路由器上一台装有OpenSSH server机器的SSH端口映射出去即可。同时一些提供SSH访问的虚拟主机也可以用于这一用途。例如: Hostmonser 或者 Dreamhost .
通过SSH隧道建立SOCKS服务器
如果我们需要借助一台中间服务器访问很多资源,一个个映射显然不是高明的办法(事实上,高明确实没有用这个方法)。幸好,SSH客户端为我们提供了通过SSH隧道建立SOCKS服务器的功能。
通过下面的命令我们可以建立一个通过123.123.123.123的SOCKS服务器。
ssh -N -f -D 1080 123.123.123 # 将端口绑定在127.0.0.1上
ssh -N -f -D 0.0.0.0:1080 123.123.123.123 # 将端口绑定在0.0.0.0上
通过SSH建立的SOCKS服务器使用的是SOCKS5协议,在为应用程序设置SOCKS代理的时候要特别注意。
ssh命令
一、 man ssh
看看ssh命令有哪些选项吧
由于太长,这里只展示本次相关的选项
> man ssh -D [bind_address:]port Specifies a local ``dynamic'' application-level port forwarding. This works by allocating a socket to listen to port on the local side, optionally bound to the speci- fied bind_address. Whenever a connection is made to this port, the connection is forwarded over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine. Currently the SOCKS4 and SOCKS5 protocols are supported, and ssh will act as a SOCKS server. Only root can forward privileged ports. Dynamic port forwardings can also be specified in the configuration file. IPv6 addresses can be specified by enclosing the address in square brackets. Only the superuser can forward privileged ports. By default, the local port is bound in accordance with the GatewayPorts setting. However, an explicit bind_address may be used to bind the connection to a specific address. The bind_address of ``localhost'' indicates that the listening port be bound for local use only, while an empty address or `*' indicates that the port should be available from all inter- faces. -f Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the back- ground. This implies -n. The recommended way to start X11 programs at a remote site is with something like ssh -f host xterm. If the ExitOnForwardFailure configuration option is set to ``yes'', then a client started with -f will wait for all remote port forwards to be successfully established before placing itself in the background. -L [bind_address:]port:host:hostport Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side. This works by allocating a socket to listen to port on the local side, optionally bound to the specified bind_address. Whenever a connection is made to this port, the connection is forwarded over the secure channel, and a connection is made to host port hostport from the remote machine. Port forwardings can also be specified in the configuration file. IPv6 addresses can be specified by enclosing the address in square brackets. Only the superuser can forward privileged ports. By default, the local port is bound in accordance with the GatewayPorts setting. However, an explicit bind_address may be used to bind the connection to a specific address. The bind_address of ``localhost'' indicates that the listening port be bound for local use only, while an empty address or `*' indicates that the port should be available from all interfaces. -N Do not execute a remote command. This is useful for just forwarding ports (protocol version 2 only). -R [bind_address:]port:host:hostport Specifies that the given port on the remote (server) host is to be forwarded to the given host and port on the local side. This works by allocating a socket to listen to port on the remote side, and whenever a connection is made to this port, the connection is forwarded over the secure channel, and a connection is made to host port hostport from the local machine. Port forwardings can also be specified in the configuration file. Privileged ports can be forwarded only when logging in as root on the remote machine. IPv6 addresses can be specified by enclosing the address in square brackets. By default, the listening socket on the server will be bound to the loopback interface only. This may be overridden by specifying a bind_address. An empty bind_address, or the address `*', indicates that the remote socket should listen on all interfaces. Specifying a remote bind_address will only succeed if the server's GatewayPorts option is enabled (see sshd_config(5)). If the port argument is `0', the listen port will be dynamically allocated on the server and reported to the client at run time. When used together with -O forward the allocated port will be printed to the standard output.
上面罗列出了用用到的选项简单介绍一下:
-D [监听地址]监听端口 用于将远程连接的ssh通过本监听本地的端口形成sock4或者sock5服务;(使用**的同学应该不陌生); -f 让SSH服务运行到后台,做外forward代理的ssh命令运行在后台更省心; -L ssh将监听本地端口,所有对这个端口的数据操作会穿透到ssh远程地址的对应配置端口; -N 不执行远程命令,在ssh作为隧道时很有用; -R ssh将监听远程服务器的某个端口,并将端口的所有数据转发到本地的设置端口;
图解上面配置
-D 将remote能访问的服务都穿透到本地的port上,并提供sock服务; ▲ port: sock5 │ ┌────┴────────┐ ┌─────────────┐ │ localhost │────port: 22──────▶│ remote │ └─────────────┘ └─────────────┘ -L 将远程能访问的某个服务host+port穿透到本地,在本地的port上提供对应透传服务; ▲ bindaddr:port ▲ servicehost:port │ │ ┌───┴─────────┐ ┌────┴────────┐ │ localhost │────port: 22──────▶│ remote │ └─────────────┘ └─────────────┘ -R 将本机能访问的某个服务host+port穿透到remote,在remote机器指定port上提供透传服务; ▲ servicehost:port ▲ bindaddr:port │ │ ┌───┴─────────┐ ┌────┴────────┐ │ localhost │────port: 22──────▶│ remote │ └─────────────┘ └─────────────┘
为了方便起见,假设拥有下面资源
墙外vps: root@outside_vps
本机: localhost
二、 通过墙外ECS提供sock5*
命令
ssh -N -f -D 1080 root@outside_vps
输入密码,执行后,本地1080端口被监听,提供sock5服务;使用chrome+SwitchyOmega就能配置*了,效果和*一样。
三、 本地服务穿透到外网80端口
本地起一个服务监听3003端口,这里用nodejs简单实现:
// s.jsrequire('http').createServer(function (req, res) { res.end('xxx'); }).listen(3003); > node s
ssh 到远程vps,将远程vps的端口监听设置为如下,允许sshd监听处localhost以外的IP:
(在ssh参数中加 -o GatewayPorts=yes 效果一样)
> vim /etc/ssh/sshd_config # GatewayPorts: no------ 改成 ----- GatewayPorts: yes
本地启动
ssh -f -N -R outside_vps:80:localhost:3003 root@outside_vps
启动后,在浏览器输入outside_vps,直接能访问到本地3003的服务
tips: 这里能将本地的http服务透传到外网,同样也能将本地22端口透传(慎用次功能,免得密码太弱或者有安全漏洞时让人有机可乘)
四、 服务代理
场景: 当 A 只能访问 B,B 能连接 C ,A需要访问 C 的某个服务,而不能让A访问到C的其他服务。
此时,-L 参数就派上用场了。我们使用B主机通过SSH -L,将 C上面的制定服务代理到 B 的某个端口让A使用,此时 A 只能访问到C被代理的服务,而访问不了C的其它服务;
以将C的mysql服务穿透到B为例:
在B上执行
ssh -N -f -o GatewayPorts=yes -L *:3336:C:3306 root@C
在A*问
mysql -hB -uuser -P3336 -p
输入密码后,跟访问C的端口效果一样!
上面的场景,在网络隔离,B作为出口机时可能会出现,当然,这种代理用Nginx做TCP代理也能轻松实现。