Lyndra's Blog

个人Linux主机通过SSH隧道使服务器访问外网

2024-10-20
系统维护网络安全 NetworkManjaroLinux
16分钟
3024字
温馨提示:本文最后更新于 2025-03-11 ,部分信息可能因时间推移而不再适用,欢迎反馈。

在学校的服务器使用过程中,需要服务器访问外网,但在服务器上可能没有权限创建代理网络,或者不方便使用。而且直接使用服务器访问外网也可能有风险,因此,本文通过ssh隧道,将服务器的网络代理到我们的终端主机,也就是我们自己的电脑上,再在自己的电脑上安装代理软件,实现服务器通过ssh隧道访问外网的功能。

本机代理配置

  本文中 “本机” 一词代表个人电脑,服务器 代表远程连接的服务器电脑。

  本机系统如下:

Terminal window
1
$ neofetch
2
██████████████████ ████████ ling@ling-20ym
3
██████████████████ ████████ --------------
4
██████████████████ ████████ OS: Manjaro Linux x86_64
5
██████████████████ ████████ Host: 20YM Lenovo ThinkBook 16p Gen 2
6
████████ ████████ Kernel: 6.1.112-1-MANJARO
7
████████ ████████ ████████ Uptime: 9 hours, 57 mins
8
████████ ████████ ████████ Packages: 1772 (pacman)
9
████████ ████████ ████████ Shell: bash 5.2.37
10
████████ ████████ ████████ Resolution: 2560x1600, 2560x1440
11
████████ ████████ ████████ DE: Plasma 6.1.5
12
████████ ████████ ████████ WM: KWin
13
████████ ████████ ████████ Theme: [Plasma], Breeze [GTK2/3]
14
████████ ████████ ████████ Icons: [Plasma], McMojave-circle-dark [GTK2/3]
15
████████ ████████ ████████ Terminal: konsole
4 collapsed lines
16
CPU: AMD Ryzen 7 5800H with Radeon Graphics (16) @ 3.200GHz
17
GPU: AMD ATI Radeon Vega Series / Radeon Vega Mobile Series
18
GPU: NVIDIA GeForce RTX 3060 Mobile / Max-Q
19
Memory: 18054MiB / 23392MiB

  采用clash verge作为代理软件,该软件能够开放本地端口作为代理访问路径,为ssh隧道提供了出口端口。

  本人使用tun模式,尽可能的避免DNS泄漏。如果使用代理模式也可以。但 Linux上只有少数程序会走系统代理模式,除了在需要在clash verge中开启代理模式,还需要在对应的软件中手动设置代理。因此还是tun模式更适合linux上使用。参考:Ubuntu下系统代理只能作用于Firefox?

  注:开启代理模式后,即便本机其他程序不走clash,我们通过ssh建立隧道依然可行。

default

  这里开启tun模式后,并保证本机能够正常访问外网后,就可以开始ssh隧道搭建。需要注意,clash verge默认开放的代理端口为7897,可以自行修改。

SSH隧道

  参考:SSH 隧道技术SSH隧道详解与使用AutoSSH实现稳定的内网穿透SSH 隧道简明教程

  SSH隧道提供三种模式:正向 SSH 隧道(本地转发)、反向 SSH 隧道(远程转发)、动态转发 SSH 隧道(Socket服务器)。

  由于一台电脑上可能有多个网卡(意味着有多个 IP),因此在使用 SSH 隧道时,需要指定:1. 从哪一个 IP建立SSH连接。也就是SSH的登陆地址。2. SSH隧道应该连接到哪一个 IP上。也就是隧道地址

  在下面的介绍中,destination 表示服务器的地址,也就是登陆服务器的标识符:Username@Host_IP,host

表示服务器上的隧道地址。host可以和Host_IP不同,但都是服务器上的IP地址。

正向 SSH 隧道

  将本主机上的某网络端口上的所有流量,通过 SSH 隧道转发到远程服务器上的网络端口。

Terminal window
1
ssh -L [bind_address:]port:host:hostport destination

  其中 bind_address 可选(默认为 localhost),是本机上的一个网络ip。该命令会本地开启一个绑定在 [bind_address:]port 上的套接字,并监听。将该套接字上所有的流量都转发到 destination 服务器上的 host

Terminal window
1
ssh -L 9090:localhost:8080 root@10.0.0.2

  例如,这个例子就是将本机 localhost:9090 上的流量转发到 10.0.0.2 服务器上的 localhost:8080。

反向 SSH 隧道

  将远程服务器上的网络端口上的所有流量,通过 SSH 隧道转发到本主机上的某网络端口。

Terminal window
1
ssh -R [bind_address:]port:host:hostport destination

  和正向隧道的命令行格式相同,不同点在于此命令是在服务器上开启一个绑定在 host

上的套接字,并监听。将该套接字上所有的流量都转发到 本机 上的 [bind_address:]port。

Terminal window
1
ssh -R 9090:localhost:8080 root@10.0.0.2

  例如,这个例子就是将10.0.0.2 服务器上的 localhost:8080 上的流量转发到本机 localhost:9090。

动态转发 SSH 隧道

  动态转发 SSH 隧道实际上是将远程服务器作为一个 Socket 服务器,专门转发本地端口上的所有流量到服务器所处的网络中。动态转发不像正向隧道与反向隧道一样转发端口与目标端口是一对一的,动态转发中的转发端口对应的目标是目标主机所在的整个网络。不过使用动态转发访问目标主机所在网络时需要应用程序本身支持代理配置或者使用socket代理工具。

Terminal window
1
ssh -D [bind_address:]port destination

  -D [bind_address:]port 指定监听的端口,会在本地监听该端口,并将请求到该端口流量基于 SOCKS5 协议转发到远程主机上,其中 [bind_address:]可以不填,当不写或者为 * 时表示监听全部地址。示例:-D *:8081,-D 8081,-D 127.0.0.1:8081,-D 192.168.0.1:8081。

  可以参考:SSH 隧道简明教程扩展-跨机器转发​章节,介绍的很清楚。

  下面是从中截出的两张图片,主要应用是在A与B之间创建隧道,最终通过隧道访问到ServerC中的 http 服务。

default

  最常用的就是绕过防火墙,在外网上通过一台可以访问内网的 Server A,访问内容。基于Socket协议的翻墙软件就是这个原理,用一台没被墙的、在国内可以通过SSH访问到的VPS,作为Socket服务器,也就是图中的 Server B。VPS在国外,可以访问国外网络资源,国内就将所有的外网网络请求发往 Server A的SSH 绑定端口,Server A会将其转发给国外网络,例如入中的 Server C。由于 Server A 到 Server B之间是SSH加密传输的,防火墙看不到其中具体访问的网络内容,就不会判断为翻墙。但由于这种方式应用太广泛,现在检测流量特征的手段很强,可以快速判断出并封掉。

default

常用参数

  • -L​:local,表示使用本地端口转发创建 ssh 隧道
  • -R​:remote,表示使用远程端口转发创建 ssh 隧道
  • -D​:dynamic,表示使用动态端口转发创建 ssh 隧道
  • -N​: 表示创建隧道以后不连接到 sshServer端,通常与”-f”选项连用
  • -f​:表示在后台运行ssh隧道,通常与”-N”选项连用
  • -q​ 参数用于启用 静默模式(quiet mode)
  • -g​:表示 ssh 隧道对应的转发端口将监听在主机的所有IP中,不使用”-g选项”时,转发端口默认只监听在主机的本地回环地址中,”-g” 表示开启网关模式,远程端口转发中,无法开启网关功能
  • -C​:启用压缩,可以提高传输速度。
  • -p port​:指定 SSH 服务器监听的端口 (如果不是默认的22端口)。
  • -i 私钥文件​:使用指定的私钥文件进行身份验证。
  • -T​ :用于禁用伪终端分配,使用 -N​ 时,因为本身就没有需要交互的命令,SSH 默认不会分配伪终端,便不需要 -T

应用

  为了达到我们的目的:将服务器上的网络代理到本机上,我们有两种方案:

  1. 建立反向隧道,将服务器端口上的流量转发到本机,服务器上所有的流量都走代理端口。优点是:比较简单,只需要ssh建立隧道即可,不需要安装其他软件,只要主机能访问外网,服务器就可以。缺点是:如果有其他服务器,需要在主机上为每一个服务器都单独创建SSH隧道。如果主机下线了,服务器就无法访问外网了。
  2. 建立 Socket服务器,让服务器的所有流量都走 Socket服务器。但这样的话和建立一个翻墙代理的区别就会很小了,不如直接就在服务器上安装代理软件。优点是:更安全,代理管理起来更方便。一般来说Socket服务器不会轻易下线,保证服务器一直有外网网络。缺点是:每一台服务器上都需要单独配置软件,且Socket服务器需要在外网。

  对于普通的应用场景来说,在自己的电脑上,我们都会安装代理软件,在服务器管理员没有配置跳板机或者跳板机故障时,我们就可以采用第一种,快速 实现 or 恢复 服务器访问外部网络。

  我们假设服务器的ip为10.0.0.2,服务器上有用户名为 server的用户。

本机配置

  本机上我们安装clash verge,正常启动后,就会clash verge就会监听 localhost:7897,并转发流量到代理服务器。本示例为方便起见,将主机和远程服务器的端口都设置为 7897,实际上远程服务器可以设置为其他没有被使用的端口,主机也可以在clash verge中将代理端口改为其他端口后,将SSH隧道的本地端口同步修改。

  运行:

Terminal window
1
ssh -R 7897:localhost:7897 -fqCN server@10.0.0.2

  为远程服务器的 localhost:7897 和本机的 localhost:7897建立ssh隧道。转发方向为:远程服务器 SSH隧道 -> 本机 localhost:7897 -> clash verge。

  ​-fqCN​:后台运行、静默模式、启动压缩模式,加快速度、只转发端口,不连接终端。

远程服务器配置

  远程服务器上需要在终端中添加代理变量,也可以写到终端变量文件(~/.bashrc 或者 ~/.profile )中,每次登陆自动生效。使得所有的流量走代理端口,也就是本地回环地址的7897端口。转发方向:服务器其他网络 -> 服务器 localhost的7897端口 -> 服务器SSH隧道

Terminal window
1
export http_proxy="localhost:7897"
2
export https_proxy="localhost:7897"

设置好后不能正常联网

  在实践过程中,我遇到了按照上述配置后,主机网络正常访问外网,服务器和主机之间SSH隧道通信正常,但服务器无法访问外网。如下:

  服务器:

Terminal window
1
$ curl -x socks5://localhost:7897 https://www.google.com
2
curl: (35) error:0A000126:SSL routines::unexpected eof while reading

  添加参数 -v​ 打印详细日志输出,可以看到使用了本地的DNS解析地址。但国内的DNS在没被代理的时候,会被CFW污染,因此这个地址实际上不是谷歌的服务器IP,但由于已经缓存下来了,所以在访问的时候会通过该IP来访问。于是,传递给代理的ip也是这个错误ip,就会导致访问出错。

Terminal window
1
$ curl -v -x socks5://localhost:7897 https://www.google.com
2
* Trying 127.0.0.1:7897...
3
* SOCKS5 connect to IPv4 199.59.148.229:443 (locally resolved)

  解决办法:

  1. 等几分钟中…等DNS缓存过期。

  2. 安装 proxychain,并在其中配置proxy_dns​,使得DNS也通过代理查询。

    配置 proxychain

    Terminal window
    1
    $ vim /etc/proxychains.conf
    2
    ...
    3
    # Proxy DNS requests - no leak for DNS data
    4
    proxy_dns
    5
    ...
    6
    [ProxyList]
    7
    # add proxy here ...
    8
    # meanwile
    9
    # defaults set to "tor"
    10
    11
    https 127.0.0.1 7897
    12
    socks5 127.0.0.1 7897

    通过 proxychain 访问

    Terminal window
    1
    $ proxychains curl -I https://www.youtube.com
  3. 在使用 systemd 管理服务的 Linux 上。可以通过重启系统解析服务来情况 DNS 缓存 sudo systemctl restart systemd-resolved​。

  正常情况下:

Terminal window
1
$ curl -v -x socks5://localhost:7897 https://www.google.com
2
* Trying 127.0.0.1:7897...
3
* SOCKS5 connect to IPv4 31.13.94.41:443 (locally resolved)
4
* SOCKS5 request granted.
5
* Connected to (nil) (127.0.0.1) port 7897 (#0)
6
* ALPN, offering h2
7
* ALPN, offering http/1.1
8
* CAfile: /etc/ssl/certs/ca-certificates.crt
9
* CApath: /etc/ssl/certs
10
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
11
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
12
* TLSv1.2 (IN), TLS header, Certificate Status (22):
13
* TLSv1.3 (IN), TLS handshake, Server hello (2):
14
* TLSv1.2 (IN), TLS header, Finished (20):

重新建立SSH 隧道后服务器无法访问外网

  可能会出现 SSH 隧道已经被清空,但服务器端还没有退出对应的线程的情况,这时需要我们手动找出对应的线程,并kill掉。再重新建立 SSH 隧道。

Terminal window
1
$ sudo lsof -i :7897
2
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
3
sshd 2129065 lyndra 7u IPv6 48338339 0t0 TCP ip6-localhost:7897 (LISTEN)
4
sshd 2129065 lyndra 9u IPv4 48338340 0t0 TCP localhost:7897 (LISTEN)
5
6
$ kill 2129065

  如果需要长时间稳定使用SSH 隧道,可以使用autossh。

本文标题:个人Linux主机通过SSH隧道使服务器访问外网
文章作者:Lyndra
发布时间:2024-10-20
总访问量
总访客数人次
Copyright 2025
站点地图