SSH端口转发(SSH隧道)
本地端口转发
场景
有mysql使用经验的童鞋应该都知道,你们公司的mysql是不允许直接通过ip或域名去连接它的(一般都配置成只允许127.0.0.1
或localhost
去连接它,也就是只能本地连接,不能远程连接),如果是使用mysql图形界面客户端连接,一般都是通过在图形界面客户端(如navicat)里使用SSH Tunnel(即SSH隧道)来连接的。
那如果我是直接用mysql命令行客户端呢?比如,在远程主机10.37.129.5
上安装有mysql并且只允许本地访问,则我在自己电脑上想用以下命令去连接10.37.129.5
上的mysql肯定是连接不上的:
mysql -h 10.37.129.5 -P 3306 -uroot -p
但肯定有解决方法,这就是下边要介绍的“ssh本地端口转发”。
本地端口转发
用以下命令可以建立一个本地端口转发进程,用于把发往本地端口3307
中的数据转发到10.37.129.5
的3306
端口中
ssh -f -N -L localhost:3307:localhost:3306 [email protected]
这样,我们就可以使用以下命令在本地电脑上来连接远程服务器10.37.129.5
中的mysql
mysql -h 127.0.0.1 -P 3307 -uroot -p
虽然mysql连接的是本机的3307,但因为有前面的端口转发ssh进程,它会把3307端口中的数据通过10.37.129.5
这台机转发到localhost:3306
中,而对10.37.129.5
这台机来说,localhost
刚好是它自己,于是这个数据最终被转发到10.37.129.5
这台机的3306端口中(3306端口就是mysql默认端口)。
需要注意的是,如果你像下边这样写,可以转发但mysql是无法登录的
ssh -f -N -L localhost:3307:10.37.129.5:3306 [email protected]
可以看到我把localhost
替换成了10.37.129.5
,也就是说mysql服务器会最终识别中间那个冒号的后面部分是localhost:3306
还是10.37.129.5:3306
。
另外需要注意的是,假如我写成下边这样还是有可能无法登录远程的mysql
ssh -f -N -L localhost:3307:127.0.0.1:3306 [email protected]
按前面的规则,10.37.129.5
上的mysql识别到的登录地址是127.0.0.1:3306
,这已经属于本地,按道理来说不应该不能登录呀,是的,按道理是这样的。
但关键就在于mysql允许登录的方式不是简单的允许“本地登录”或允许“远程登录”,而是严格的控制用户及ip/域名,在你的mysql中运行这条命令可查看允许登录的用户和ip:
select `Host`,`User` from user;
这是我的mysql查询结果
127.0.0.1 root
127.0.0.1 xiebruce
localhost mysql.infoschema
localhost mysql.session
localhost mysql.sys
localhost root
在上述结果中,我们可以看到有127.0.0.1 root
和localhost root
两条记录,假如我把127.0.0.1 root
这条记录删除,那么就会造成前面所说的用127.0.0.1:3306
无法登录的情况,这是特例吧,在其它服务中一般不会有这种限制。
-f
后台运行(f我个人认为应该是forward的首字母,开启后台转发模式)-N
不执行远程命令(N可以理解为Not),后面有执行远程命令的例子-f
和-N
一般都是一起用,可以合在一起写成-fN
或-Nf
-L
指定转发规则,L表示Local,本地转发就是这么来的,-L
后面的localhost:3307:localhost:3306
就是它的参数,-L
的参数的基本规则是这样的[bind_address:]port:host:hostport
,其中前面的[bind_address:]port
为转发源地址,后面的host:hostport
为转发目标地址。
根据命令行参数通用规则,方括号括住的表示可以省略不写,也就是说上述端口转发命令可以写成(此时它默认就是127.0.0.1,而不会是localhost,因为localhost只是一个本地域名,它对应的ip就是127.0.0.1
)
ssh -f -N -L 3307:localhost:3306 [email protected]
如果不写-f
,它就不会进入后台,一直占用着终端
ssh -N -L 3307:localhost:3306 [email protected]
当不写-N
时,即写成这样
ssh -f -L 3307:localhost:3306 [email protected]
上述命令会报错“Cannot fork into background without a command to execute.”。
前面说过,-N
表示“不执行远程命令”,那么现在没有-N
了,是不是就代表“要执行远程命令”了呢?没错,就是这样,所以上边命令才会报“Cannot fork into background without a command to execute.”,意思是“因为没有要执行的命令,所以无法fork一个进程到后台执行”!
执行远程命令可以这样写
ssh -f -L 3307:localhost:3306 [email protected] 'echo 111'
按道理,执行上述命令后,屏幕上就会打印出111
,然后进入后台,开启一个端口转发进程,但实际上只会打印出111
,而不会开启进程,把-f
去掉也一样
ssh -L 3307:localhost:3306 [email protected] 'echo 111'
既然上述命令不会开启转发端口进程,其实去掉-L
的所有参数,直接写成这样也是一样的
ssh [email protected] 'echo 111'
该命令其实就是使用ssh执行远程命令了(即在不登录进去目录服务器的情况下,执行目录服务器的命令)。
echo 111
是10.37.129.5
机器上的命令,我只不过是拿echo 111
举个例子,你可以把echo 111
替换成任何10.37.129.5
上可以执行的命令,例如查询端口netstat -tlunp | grep 3306
。
当-f
和-N
都不用,即写成下面这样时,会直接登录到10.37.129.5
机器上
ssh -L 3307:localhost:3306 [email protected]
也就是说,在没有-f
和-N
的情况下,单独的-L
是不起作用的,于是上述命令就相当于直接使用ssh登录命令
ssh [email protected]
所以可以得出结论:启动本地端口转发进程必须使用-N
,而且也肯定会使用-f
,虽然不用它也可以,但不用它的话会导致进程一直在占用终端,无法后台执行,关闭终端就会结束,显然实际使用肯定没有人会这么做。
另外我们还可以把文章开头的命令写成这样(其中10.37.129.2
是本地电脑的ip)
ssh -f -N -L 10.37.129.2:3307:localhost:3306 [email protected]
也就是把原来的localhost
(或127.0.0.1
)换成了本机ip10.37.129.2
,这样就是监听本机ip为10.37.129.2
的网卡的3306
端口(之所以这样说,是因为一台电脑有可能有多个网卡,每个网卡一个ip)。
但是像上述命令这样只是监听本机单个ip,怎样监听所有ip呢?加个-g
即可(g我不知道是gateway或global的意思,都可以吧)
ssh -g -f -N -L 3307:localhost:3306 [email protected]
需要注意的是,加了-g
就不能写本地监听地址了(即3307
不能写成localhost:3307
或者10.37.129.5:3307
了),否则-g
不会起作用。
前面说过-L
的参数的基本规则是这样的[bind_address:]port:host:hostport
,其中前面的[bind_address:]port
为转发源地址,后面的host:hostport
为转发目标地址,这是不是意味着,转发目标地址未必是localhost
或者或者机的地址呢?是的,它可以是其它地址。
比如还有另一台机10.37.129.6
,那么本地电脑上的数据通过10.37.129.5
转发到10.37.129.6
中的3306
端口,可以这么写:
ssh -f -N -L localhost:3307:10.37.129.6:3306 [email protected]
当然,根据前面所述,localhost:
部分是可以省略不写的。
另外,本地端口转发,有时候会叫成“端口映射”,也就是说,前面的例子就相当于是把10.37.129.5
中的localhost:3306
映射到本地,这样我们就可以去连接它。
更多ssh各参数选项的作用,我们可以使用man ssh
来查看。
远程端口转发
远程转发其实就是把-L
(Local,即本地)改成-R
(Remote,即远程)
ssh -f -N -R 9003:localhost:9002 [email protected]
它跟本地端口转发的区别是:
- 远程端口转发模式会在远程服务器开启一个端口监听(但没有进程,可用
netstat -tulnp | grep -v "grep" | grep 端口号
查看); - 本机(执行这句命令的机器)则不会监听端口,但会有一个进程;
- 远程服务器可以通过它上边监听的端口来反向访问到你本机(因此
-R
的R也可理解为Reverse
),这就相当于反向穿透了,因为正常来说,你自己的电脑没有外网ip,服务器是不可能访问到你的电脑的,但是用这种方式就可以; - 此时本机的进程充当服务器角色,而远程服务器反而充当客户端角色;
-R 9003:localhost:9002
参数格式跟-L
的相同,即[bind_address:]port:host:hostport
,其中前面的[bind_address:]port
为转发源地址,后面的host:hostport
为转发目标地址;- 关闭本机的ssh进程,则服务器那边的端口监听会自动关闭。
现在问题来了,哪个是源,哪个是目标?答案是,服务器那边是“源”,本地电脑为“目标”,所以在本例中,服务器那边监听的端口会是9003
,而最终连接时,localhost:9002
表示访问本地电脑的9002
端口(因为此时本地电脑才是“远程”),所以本地电脑必须有一个服务监听9002
端口用来接收服务器那边传过来的数据,而这个服务就看你具体的业务需要了。
最后提一点,其实端口转发是要在sshd配置文件/etc/ssh/sshd_config
中开启AllowTcpForwarding yes
的,它默认是注释的,不过注释里的默认值就是真正的默认值(即默认yes),所以我们可以不打开注释。
参考资料:ssh端口转发:ssh隧道
文章写的很好
感谢你的肯定,但评论不要用假邮箱,因为邮箱是用于发通知的,比如我回复你了,你是会收到邮件通知的,而你用假邮箱就什么都收不到