原文出处:https://zhuanlan.zhihu.com/p/148825449
SSH端口转发(隧道)功能强大、用途广泛,仅仅一行代码便可以将两台主机联系起来,对特定的访问请求进行代理。正确书写SSH命令是实现一切的基础。然而许多关于端口转发命令参数的介绍都是浅入深出:罗列名词却不加解释,给出实例却不讲如何举一反三。因而在此尝试整理一份更清晰的使用指引,不涉及原理。不足之处欢迎评论区交流。
一 基本连接
SSH基本的连接命令是:
ssh username@hostname
这里牵扯到了两台主机,一是执行命令、运行SSH客户端的主机,我们称为本地主机A【Host A】;二是接收连接请求、运行SSH服务器的主机,我们称为远程主机B【Host B】。通过密码或密钥等方式验证后,SSH连接建立,主机A可以使用命令行对主机B实施远程控制。
以上命令中,username是主机B上已登录的用户名,hostname则是主机B的设备名、域名或IP等可以在网络(局域网或互联网)上定位的名称。
端口转发涉及的主机较多,这是引起名词混乱的原因之一。在此不深究用词问题,仅以字母代表之。如无特殊说明,SSH连接都建立在由主机A到主机B间,SSH命令都在主机A上被执行。
二 本地端口转发
顾名思义,本地端口转发是将应用【application client】对于本地主机A指定端口X的访问请求转发给主机B,交由主机B对另一指定主机C的指定端口Z发起访问。命令如下:
ssh -L 主机A端口X:主机C:主机C端口Z username@hostname
# 简单理解为:将对A:X的访问转变成对C:Z的访问
客户端在执行端口转发命令的同时,实际上也执行了基本的连接命令。多出来的部分中,「-L」旗标表示使用「本地端口转发」选项,之后是用冒号分隔开的三个需要指定的项。原理上,主机C可以是任何能够被主机B识别到的设备,也可以是主机B自身。
当主机C在其某端口提供某服务【application server】,主机A需要使用该服务却无法直接访问主机C或该端口时,如果发现有SSH:A→B的连接,且主机B能够直接访问主机C的该端口,本地端口转发就派上用场。
此时,访问请求在主机A一侧发生,可以来自于主机A自身,也可以是其他与A连接的设备。图中Host A或Host B的阴影指代主机A或主机B一侧的网络系统。
补充说明
实际上ssh本地端口转发命令的「-L」旗标后可以填写四个参数,完整格式为:
ssh -L [收听接口:]收听端口:目标主机:目标端口 username@hostname
命令中方括号内的部分,即第一个参数可以不写;它的默认值一般是0.0.0.0(OpenSSH客户端配置文件「ssh_config」中「GatewayPorts」选项的值一般为「yes」),意味着SSH隧道会收听所有接口,接受来自任何地址的应用访问请求并进行转发。而如果在此处填写了绑定地址(bind address),SSH隧道连接就会只处理来自绑定地址的应用请求,而对其他地址发来的请求置之不理;如同在(真实世界的)隧道入口设立哨卡,只对白名单牌号的车辆放行。例如在此处填写127.0.0.1,即可实现只有来自主机A本机的应用请求才被SSH隧道转发的效果。
需留意,收听接口是站在主机A的视角上去规定允许与A连接的设备,解决「能够使用SSH端口转发的应用请求从何处来」的问题,类似防火墙的入站;收听端口则依旧是主机A上的那个端口X,不能够跑到别的主机上去。
类似地,远程端口转发和动态端口转发也具有「收听接口」这一可不指明的参数,下文不再赘述。从安全或控制流量的角度,规定绑定地址是一项实用的功能。
场景①
主机B与主机C处于同一内网中,主机B能够与外界联系而主机C不能。这时不处于内网中的主机A如果想要访问主机C,就可以通过SSH连接主机B+端口转发来进行。
台式机B上运行着虚拟机C,虚拟机使用虚拟机软件搭建的虚拟网络与宿主主机B相连接,但在主机B以外无法直接访问该虚拟网络。想要通过SSH,用与台式机B处于同一WiFi下的笔记本A来远程控制虚拟机C,(在A上)执行端口转发命令:
ssh -L 22022:10.0.2.15:22 desktop_user@192.168.1.11 # cmd.1-1
其中,22022号端口是随便选的一个没被占用的端口;192.168.1.11是台式机B在WiFi中的IP;desktop_user是主机B上的用户名;10.0.2.15是虚拟机C在主机B为其搭建的虚拟网络中的IP;22号端口是默认的SSH端口。已知virtual_user是虚拟机C上的用户名,这时在笔记本A上执行应用的访问请求命令:
ssh -p 22022 virtual_user@localhost # cmd.1-2
我们在笔记本A上以SSH协议访问本机(localhost)的22022号端口,这个请求就像通过了隧道(SSH隧道)一样抵达台式机B,台式机B则把这个请求变为对虚拟机C的22号端口的访问,并为A返回结果。其中,使用「-p」旗标是为了访问主机A的特定端口而不是SSH默认的22号端口;由于我们在主机A上执行命令,A管自己叫localhost,假如在其他主机上执行则需相应地改为主机A的域名或IP等他们对A的称呼。
cmd.1-2中我们是将SSH当作普通应用使用的。参考Fig.1,cmd.1-1在A与B之间建立SSH隧道,此时A上的SSH客户端和B上的SSH服务器对应图中的SSH Client和SSH Server;cmd.1-2则表达应用的访问请求,此时A上的SSH客户端和C上的SSH服务器对应图中的application client和application server。
以上cmd.1-1和cmd.1-2合起来实际是想(在A上)进行:
ssh -p 22 virtual_user@10.0.2.15 # cmd.1-3
当然,如果这cmd.1-3能被成功执行的话,就不需要端口转发了。
场景②
防火墙阻止了主机A对主机B一些端口的连接,但主机B仍有部分端口是对主机A开放的。这时主机A如果需要访问主机B上被防火墙阻挡的端口,就可以通过SSH连接主机B+端口转发来进行。需注意,这时所谓的主机C就是主机B。
某某云的云服务器B默认的防火墙设置仅开放了22号端口,其他入方向的访问都被屏蔽了。我们为云服务器B安装了桌面环境,现在想要在自己的计算机A上,通过VNC远程控制云服务器B的桌面。(在A上)执行端口转发命令:
ssh -L 5920:localhost:5901 cloud_user@server.example.com # cmd.2-1
因为C就是B自己,所以C的位置填localhost;5920随便选;5901是云服务器B上VNC服务进程收听的端口;cloud_user是B上的用户名;http://server.example.com是B的域名,换成公网IP也行。
下面在计算机A上打开RealVNC VNC Viewer(VNC客户端),输入VNC服务器地址:
localhost:20
20=5920−5900,这是采用5901到5999之间端口时RealVNC的特殊设定。开始使用优雅(或许吧)的GUI来操作云服务器吧!
三 远程端口转发
当主机C在其某端口提供某服务,主机B需要使用该服务却无法直接访问主机C或该端口时,如果发现有SSH:A→B的连接,且主机A能够直接访问主机C的该端口,远程端口转发就派上用场。
需注意,此时访问请求在主机B一侧发生,而SSH连接的方向却没有变化,仍是由A到B的。因此「本地与远程端口转发互为镜像」的说法并不完全准确;严格意义上的镜像,SSH连接也要变为由B到A,那时则应该是在B上采用本地端口转发。可以看出,采取哪种端口转发主要取决于SSH连接建立的方向。
与本地端口转发的流动方向相反,远程端口转发是将对于远程主机B指定端口Y的访问请求转发给主机A,交由主机A对另一指定主机C的指定端口Z发起访问。命令如下:
ssh -R 主机B端口Y:主机C:主机C端口Z username@hostname
# 简单理解为:将对B:Y的访问转变成对C:Z的访问
username@hostname不变,因为我们仍然以从主机A对主机B发起SSH连接为基础;「-R」旗标表示使用「远程端口转发」选项,之后是用冒号分隔开的三个需要指定的项。原理上,主机C可以是任何能够被主机A识别到的设备,也可以是主机A自身。
场景③
主机A与主机C处于同一内网中,主机A能够与外界联系而主机C不能。这时(在主机A上)如果想让不处于内网中的主机B访问主机C,就可以通过SSH连接主机B+端口转发来进行。
台式机A上运行着虚拟机C,虚拟机使用虚拟机软件搭建的虚拟网络与宿主主机A相连接,但在主机A以外无法直接访问该虚拟网络。想要通过SFTP,用与台式机A处于同一WiFi下的笔记本B来向虚拟机C传输文件,(在A上)执行端口转发命令:
ssh -R 22122:10.0.2.16:22 laptop_user@192.168.1.233 # cmd.3-1
其中,22122号端口是随便选的一个没被占用的端口;192.168.1.233是笔记本B在WiFi中的IP;laptop_user是主机B上的用户名;10.0.2.16是虚拟机C在主机A为其搭建的虚拟网络中的IP;22号端口是默认的SFTP端口。已知virtual_user是虚拟机C上的用户名,这时在笔记本B上执行应用的访问请求命令:
sftp -P 22122 virtual_user@localhost # cmd.3-2
请注意这是一条运行在B上的应用命令;B上的SFTP客户端这时充当Fig.2中的application client。此处localhost是主机B对自己的称呼。对B的22122号端口的访问被转发至A,A访问C,即10.0.2.16的22号端口并将结果返回给B。于是B就通过远程端口转发成功访问了C上的SFTP服务器。
以上cmd.3-1和cmd.3-2合起来实际是想(在B上)进行:
sftp -P 22 virtual_user@10.0.2.15 # cmd.3-3
当然,这cmd.3-3也是不能被直接成功执行的。
场景④
处于内网之中的主机A可以访问公网,但不具有公网IP;公网中的主机B无法找到A,但为A开放各个端口的访问(A可以直接连接B,反之则不行)。这时A想要让B访问自己,就可以通过SSH连接主机B+端口转发来进行。需注意,这时所谓的主机C就是主机A。
注意:OpenSSH服务器对于远程端口转发的设定,默认只接受远程主机B本机上的应用发起的请求。想要从其他连接到B的设备发起请求,需将「sshd_config」文件中「GatewayPorts」选项后的「no」修改为「yes」。
手头上计算机A运行着http服务,但A没有公网IP,其他设备不能使用该服务。恰好云服务器B有公网IP(甚至域名),便于被访问。在不将http服务迁移至云服务器B的前提下,可以使用SSH端口转发使其他设备通过访问B的方式访问A上的http服务。(在A上)执行端口转发命令:
ssh -R 80:localhost:80 cloud_user@server.example.com # cmd.4-1
这时C便是A自己(localhost);80号端口是http默认端口,为简便两个都用默认;cloud_user还是B上的用户名;http://server.example.com还是B的域名。
接下来在其他设备上打开浏览器,输入地址:
http://server.example.com/
于是大家可以通过访问http://server.example.com来访问本地计算机A提供的http服务了。
四 动态端口转发
动态端口转发可以把本地主机A上运行的SSH客户端转变成一个SOCKS代理服务器;实际上它是一种特殊的本地端口转发,或者说叫它「动态本地端口转发」更科学。这个动态,就动在这种转发不规定目标地址(主机C)和目标端口(端口Z),而是去读取应用发起的请求,从请求中获取目标信息。
ssh -D 主机A端口X username@hostname
好像很强,但有一个问题:之前使用固定的端口转发时,应用的访问请求都是指向被转发的那个端口X的,但现在应用的访问请求必须指向目标,以指定动态端口转发的目标。可如果不指向端口X,如何让数据走SSH隧道呢?这就要求我们在系统或应用(浏览器等)中设置一个使用SOCKS5协议、服务器为localhost、端口为X的代理,利用代理使请求走端口X。
这样应用的请求就从X进入隧道,抵达B后其中的目标信息被解析出来,B访问目标后再将结果通过隧道返回给A。比如在开启代理的A上的浏览器中访问http://zhihu.com,经过端口转发,相当于是B在帮A访问http://zhihu.com。
五 端口转发的停止
SSH端口转发完全基于基本的SSH连接,因此,通过在远程终端上执行exit命令、暴力关闭本地终端窗口、远程主机B关机、本地主机A关机等可以切断SSH连接的方式,即可停止SSH端口转发。