我这里有个需求,就是如何将公司局域网内的服务器A开发的网站开放到互联网上。公司局域网的服务器A能够通过主路由器访问互联网,但却没有公网 IP,因此,无法在互联网上直接访问A上的网页。通过 SSH 远程端口转发可以实现这一目的。借此,本篇汇总记录一下 SSH 转发的相关知识。本篇以 Ubuntu18.04 为例,所有端口均指 TCP 端口,建议防火墙开放相应的端口,并保证端口没有被占用。

SSH 简介

如今,SSH 在 Linux/MacOS/Windows 上是标配的软件工具。事实上,SSH 是一种网络协议,由芬兰学者 Tatu Ylonen 于 1995 年设计,用于主机之间的加密登录,将登录信息全部加密。现如今,针对该协议开发有各种实现,有商业的,也有开源的,如 OpenSSH 等。

1
2
3
4
# 使用 SSH 以用户 username 登录到主机 host_ip 上
ssh username@host_ip
# 指定端口登录
ssh -p 2222 uername@host_ip

端口可以在 host_ip 上进行修改,方法如下:

1
2
3
4
5
6
# 在配置文件中修改
sudo vim /etc/ssh/sshd_config
# 添加如下内容
Port 2222
# 重启SSHD
sudo systemctl restart sshd.service

常用的 SSH 登录方式有两种,一种是口令密码登录,一种是密钥认证登录。

口令密码登录:常常第一次登录主机时,为了防止中间人攻击,都会有系统警告提示,包含有 128 位的公钥指纹(原指纹是RSA算法生成的 1024 位的,经过 MD5 加密后得到 128 位)。当确认无误后,输入 yes,然后输入密码,即可登录,此时,本地电脑会将带访问主机的公钥记录在 ~/.ssh/known_hosts 之中,下次连接时,就不会有警告提示了。

密钥认证登录:密钥登录首先需要本机的 ~/.ssh/ 目录下生成密钥文件(可不对私钥设置口令 passphrase ),包含公钥 id_rsa.pub 和 私钥 id_rsa

1
ssh-keygen -t rsa

将公钥拷贝到待访问主机上即可,方法如下:

1
2
3
# 将 id_rsa.pub 拷贝到 host_ip 的用户 username 下的 .ssh/authorized_keys 文件中
# .ssh/authorized_keys 的权限一般是 600
ssh-copy-id username@host_ip

这样下次登录就不用输入口令密码。

使用如下命令,可以查看 ssh 命令的各参数含义:

1
man ssh
  • -p: 表示指定端口(注意在 scp 中指定端口是使用大写的 -P),port
  • -L: 表示本地端口转发,local
  • -R: 表示远程端口转发,remote
  • -D: 表示动态端口转发,dynamic
  • -N: 表示只进行数据转发,不进行命令输入(Do not execute a remote command. This is useful for just forwarding ports.)
  • -T: 表示不分配伪终端 tty(Disable pseudo-terminal allocation.)
  • -g: 表示允许远端主机连接本地主机的转发端口(Allows remote hosts to connect to local forwarded ports.)
  • -f: 表示在后台运行(Requests ssh to go to background just before command execution. )用 kill 进程 ID 关闭
  • -q: 表示静默模式,不显示警告和诊断信息(Quiet mode. Causes most warning and diagnostic messages to be suppressed.)

本地端口转发

当远端主机(IP 地址是 1. 2. 3. X,有用户 jinzhongxu)上有个服务运行在 8000 端口(比如 jupyterhub),但是,我想要在本地(127.0.0.1)的端口(8008)访问,可以使用如下方法:

1
ssh -L 8008:127.0.0.1:8000 -NT jinzhongxu@1.2.3.X

此时,在本地浏览器打开地址:http://127.0.0.1:8008 就可以直接访问。这就是一种简单的本地端口转发。它将远端主机的某个端口转发到本地主机的某个端口上,访问本地端口就相当于访问远端主机的端口。

类似的,可以将远端主机的 22 端口绑定到本机的 2222 端口,

1
ssh -L 2222:127.0.0.1:22 -NT jinzhongxu@1.2.3.X

这样,就可以直接 SSH 到本地的 2222 端口连接到远端主机的终端

1
ssh -p 2222 localhost

其实,本地端口转发的功能远不止这样,它的完整形式是下面样子:

1
ssh -L local_port:remote_target_ip:remote_target_port -NT username@remote_ip

以本地主机运行上面的命令,可以将远端目标主机 remote_target_ip 的指定端口 remote_target_port 经过另一个远端主机 remote_ip (俗称跳板机)绑定到本地主机的指定端口 local_port 上。这样,在本地访问端口 local_port,就相当于访问远端目标主机 remote_target_ip 的端口 remote_target_port。

本地端口转发的使用场景就是:

  1. 本地主机想直接访问本地端口来访问远端主机的端口;
  2. 本地主机无法直接访问远端目标主机的端口,经过跳板机实现访问远端目标主机的端口。此时要求,本地主机能够访问跳板机,跳板机能够访问远端目标主机。

其实场景 1 是场景 2 的特殊情况,此时,远端目标主机就是跳板机的 localhost 或 127.0.0.1,而 remote_ip 为公网 ip 或本地主机可访问的内网 ip,场景 1 常常使用在将远端主机的端口映射到本地主机上的情况。

本地端口转发一般采用 HTTP 协议。

远程端口转发

本地主机(127.0.0.1)上有个服务运行在 8000 端口(比如 jupyterhub),但是,我想要在远端主机(IP 地址是 1. 2. 3. X,有用户 jinzhongxu)的端口(8008)访问,可以使用如下方法:

1
ssh -R 8008:localhost:8000 -NT jinzhongxu@1.2.3.X

此时,就可以在浏览器打开地址: http://1.2.3.X:8008 访问本地主机的 jupyterhub 服务了。这就是一种简单的远程端口转发。它将本地主机的某个端口转发到远端主机的某个端口上,访问远端主机端口就相当于访问本地主机的端口。

类似的,可以将本地的 80 端口远程转发到远端主机的 80 端口,这样,访问远端主机的 IP 地址,就可以直接访问本地的网站。这常常是基于对网站安全性考虑的,对远端主机(常常是 VPS )安全性(如数据丢失等)不相信,但又想把网站开发给大家访问,或者因为地理原因,有些人无法直接访问本地的网站,只能通过互联网访问,此时,就需要进行远程端口转发,将本地端口绑定到远端端口,通过公网 IP 访问。此时,命令常常如下:

1
ssh -R 80:localhost:80 -NT jinzhongxu@1.2.3.X

此时,直接访问地址:http://1.2.3.X 就可以打开网站。

有时候,因为家庭网络和公司网络都是局域网,没有公网 IP,但都能访问互联网,在阿里云上海开通了一个具有公网 IP:1.2.3.X 的 VPS,想要实现在家访问公司里的个人办公电脑或服务器 A,就可以首先在 A 上开启远程端口转发:

1
ssh -R 2222:localhost:22 -NT -g -f jinzhongxu@1.2.3.X

回到家,通过 SSH 访问 VPS 的端口 2222 就可以访问公司服务器 A:

1
ssh -p 2222 jinzhongxu@1.2.3.X

此示例的详细描述可参考我的另一篇文章:SSH 转发的一些记录 。事实上,通过在远端主机上运行如下命令,也可以使得远端主机访问本地主机,打破内网限制,实现内网穿透:

1
ssh -p 2222 jinzhongxu@localhost

其实,远程端口转发的功能远不止这样,它的完整形式是下面样子:

1
2
3
4
5
6
# 不限连接者IP,需配置 /etc/ssh/sshd_config 中的 GatewayPorts yes;或者服务器部内进行本地端口转发,从127.0.0.1:remote_port 到 0.0.0.0:remote_port
# 当 GatewayPorts no 时,只能服务器本身自己能连接
ssh -R remote_port:target_ip:target_port -NT username@remote_ip

# 限制连接者IP,需配置 /etc/ssh/sshd_config 中的 GatewayPorts clientspecified
ssh -R connector_ip:remote_port:target_ip:target_port -NT username@remote_ip

以本地主机运行上面的命令,可以将目标主机 target_ip 的指定端口 target_port 经过本地主机(俗称跳板机)绑定到远端主机的指定端口 remote_port 上。这样,在远端访问端口 remote_port,就相当于访问目标主机 target_ip 的端口 target_port。

远程端口转发的使用场景就是:

  1. 直接访问远端主机的端口实现访问本地主机的端口;
  2. 远端主机无法直接访问目标主机的端口,经过本地跳板机实现访问目标主机的端口。此时,要求本地跳板机能够访问目标主机机和远端主机。

其实场景 1 是场景 2 的特殊情况,此时,目标主机就是跳板机的 localhost 或 127.0.0.1,而远端主机 remote_ip 为公网 ip 或本地主机可访问的内网 ip,场景 1 常常使用在将本地主机的端口映射到远端主机上的情况。

远程端口转发一般采用 HTTP 协议。

动态端口转发

动态端口转发常采用如下形式:

1
ssh -D 8000 jinzhongxu@remote_ip -NT

将本地的 8000 端口绑定到远端主机 remote_ip 上。

动态端口转发一般采用 SOCKS(5) 协议。

退出其他用户的SSH登录

查看在线登录用户

1
w

查看自己使用的终端

1
who am i

退出非法登录

1
pkill -kill -t pts/x

强制退出

1
pkill -o -t pts/x

端口占用的解决方法

查看端口是否在占用

1
2
3
4
5
6
7
8
9
10
# 查看8000端口是否被占用

sudo netstat -tunlp | grep 8000
tcp6 0 0 :::8000 :::* LISTEN 6288/node

# 关闭端口,在后面的 p 指定了端口占用的进程 ID 信息,直接杀死该进程即可释放端口
kill 6288

# 强制杀死进程
kill -9 6288

运行或禁止端口转发

在服务器的 /etc/ssh/sshd_config 中可以进行配置:

  1. 启用服务器配置文件中的 AllowTcpForwarding 选项才能允许端口转发。默认情况下,允许转发。此选项的可能值是 yes 或 all 允许所有 TCP 转发,no 阻止所有 TCP 转发,local 允许本地转发,remote 允许远程转发;
  2. GatewayPorts 配置选项也会影响远程端口转发。可能的值是 no(仅允许来自服务器主机的本地连接;默认值)、yes(Internet 上的任何人都可以连接到远程转发端口)和 clientspecified(客户端可以指定可以连接的 IP 地址,如果未指定,任何人都可以)。
  3. 另一个有趣的选项是 AllowStreamLocalForwarding,它可用于转发 Unix 域套接字。它允许与 AllowTcpForwarding 相同的值。默认值为 yes。

建议:端口转发会让外部服务器访问本地服务器,让服务器面临风险,因此,最好少用或不用时断开。

参考链接

  1. SSH原理与运用(一):远程登录
  2. SSH原理与运用(二):远程操作与端口转发
  3. SSH的三种端口转发
  4. SSH 端口转发
  5. Linux踢出其他正在SSH登陆用户