外网主机通过 ssh 连接局域网主机

我觉得,远程办公该是程序员最心仪的工作方式之一了吧。想想看,坐在家里喝着茶,看着书,需要的时候分分钟就把原本需要到公司里才能干的活给了结了,是不是有一种运筹帷幄之感?当然了,要实现远程办公,首先得连上远程主机。不过,远程主机(不管是家里的,还是公司的电脑)一般都处在局域网内部,并不拥有固定的外网 ip。这该如何是好?不慌!一般而言,要想突破内网限制,只要能成功进行 NAT 打洞,所有问题都将迎刃而解。所谓 NAT,英文名称是 Network Address Translation,顾名思义是一种转换网络地址的方式,也就是一种能够把局域网内部 ip 和端口映射成外网 ip 和端口的技术。利用这种技术使得外部主机与局域网的主机建立连接,通常也叫做 NAT 打洞。关于 NAT 打洞,未来我会用一篇文章做专门的介绍。为了缩小篇幅,暂时只能假设大家对 NAT 原理已经足够了解。本文要探讨的是利用 ssh 实现 NAT 打洞,并让外网主机可以连接到局域网内部的主机。

熟悉 NAT 打洞原理的读者应该知道,一般情况下局域网内部的主机应先向一台拥有固定 ip 地址的主机发起连接请求,从而暴露自己的外网 ip 和绑定的端口。这里同样需要这样的步骤。当局域网内部的主机 A 向拥有固定 ip 地址的主机 B 发起连接请求时,主机 B 就知道了主机 A 的外网 ip 和绑定的端口,从而可以向其发送数据。只要另一台主机 C 连接到主机 B,借由主机 B 把数据传递到主机 A,实际上就等价于主机 C 连接上了主机 A。这就是本篇所讨论的远程登录的机制。

有了上面的机制,我们就可以开始配置了。首先配置拥有外网 ip 的主机 B,让其能够自动转发 ssh 的数据。打开主机 B 的 sshd 配置文件

# vim /etc/ssh/sshd_config

在文件中添加或修改记录为

GatewayPorts yes

这就使得主机 B 的 ssh 可以做端口转发(Ports Forwarding)。文件保存后重启主机 B 的 ssh 服务

# systemctl restart ssh

接下来需要让处在局域网内部的主机 A 向主机 B 发起端口转发的连接。在主机 A 上运行命令:

$ ssh -p 4086 -N -f -R 2020:localhost:22 user@110.73.180.212

我们有必要解释下这个命令的含义。假设主机 B 的 ip 是 110.73.180.212,拥有一个名为 user 的用户,ssh 对外暴露的端口号是 4086(没配置过 ssh 端口号的主机,默认端口号是 22,一般也就不需要在命令行中指定端口号了)。这时候,主机 A 发起的 ssh 连接就应该配好这些信息,对应的就是上面命令中的 -p 4086 user@110.73.180.212。这和我们平常进行 ssh 连接并没有什么区别,如果还需要指定别的什么参数,比如 -o ServerAliveInterval=30 等,都可以照常指定。真正不同的地方在于 -N-R 参数。-N 参数表明我们只做端口转发,而不执行远程命令。而 -R 2020:localhost:22 则表明当有人试图用 2020 端口来连接主机 B 的时候,就把它转发给主机 A 的 22 端口(即主机 A 的本地 ssh 端口)。至于 -f 参数,只是指定 ssh 在后台运行而已。

我想,到此我们还需要稍微做一点更深入的理解。尽管 -R 2020:localhost:22 已经指定了转发给主机 A 的本地 ssh 端口号为 22,但真正通过 NAT 被映射后传给主机 B 的外网端口号却并不是 22。读者可以试着理解下,局域网内部的两台主机若都开放端口号 22,别人如何来辨识呢?可见,NAT 对外映射的端口号,既不与本地指定的端口号一致,也不会让任何两个请求的对外端口号相同。当然我们实际上并不需要关心这个真正的端口号是多少,只要主机 B 知道就可以了。

至此我们就已经配置好远程连接需要的各项参数了。以后,局域网外部的任何一台主机,只要在发起 ssh 连接的时候,指定 ip 号为 110.73.180.212,端口号为 2020,以及主机 A 的用户名,就可以连接到主机 A 了。例如,

$ ssh -p 2020 auser@110.73.180.212

这里 auser 是存在于主机 A 上的一个用户名。

留下评论