背景

为什么某些情况下 frp 会被防火墙拦截,有没有办法防止 frp 被防火墙拦截呢?如何实现登录的安全性?

今天我们带着这些问题,一起来探讨一下。

协议

首先需要了解 frp 的协议层级,感兴趣的可以移植我之前的文章 frp 的协议详解

防火墙有基于 ip 限制的,有基于端口限制的,有基于协议限制的,也有基于一些报文内容的。基于报文内容的限制,在 frp 这里可以通过一些配置避免掉,比如报文加密。

在 frp 这里,如果是纯 TCP 连接或者纯 KCP 连接,登录是不带加密属性的,也就是明文的。所以很容易被防火墙识别到,但是如果开启了 TLS,则通信过程中所有的数据全程加密。

下面我们来测试一下。

实践

测试环境

macOS 12.0.1 frp v0.38.0 go1.17


TCP

测试 frp 的默认模式,纯 TCP 的连接。配置信息如下。

  1. # frps
  2. [common]
  3. bind_addr = 0.0.0.0
  4. bind_port = 7003
  5. bind_udp_port = 7001
  6. kcp_bind_port = 7003
  7. dashboard_addr = 0.0.0.0
  8. dashboard_port = 7500
  9. dashboard_user = admin
  10. dashboard_pwd = admin
  11. enable_prometheus = true
  12. tls_only = false
  1. # frpc
  2. [common]
  3. server_addr = 127.0.0.1
  4. server_port = 7003
  5. log_level = trace
  6. tcp_mux = true
  7. login_fail_exit = true
  8. protocol = tcp
  9. tls_enable = false
  10. [ssh]
  11. type = tcp
  12. local_ip = 0.0.0.0
  13. local_port = 22
  14. remote_port = 6000

启动 frps 和 frpc,并且启动 wireshark 抓包 frps 的端口。会得到如下的抓包数据。

  1. client 端发送到 server 端的请求数据。

image.png

  1. server 端返回给 client 端的数据。

image.png

可以看到,当只是使用裸的 TCP 协议的时候,client 端发送的数据和 server 端返回的数据是明文显示的,抓包可以清楚的看到。

KCP

接下来我们来看下纯的 KCP 协议。 KCP 是基于 udp 协议实现的类 TCP 协议。KCP 在协议层面保证了数据不丢失,报文重传等 TCP 特性。更多的细节可以详细看下
https://www.improbable.io/blog/KCP-a-new-low-latency-secure-network-stack

配置文件如下。

  1. # frps
  2. 同上
  1. # frpc
  2. [common]
  3. 同上
  4. protocol = kcp
  5. tls_enable = false
  6. [ssh]
  7. type = tcp
  8. local_ip = 0.0.0.0
  9. local_port = 22
  10. remote_port = 6000

启动 frps 和 frpc,并且启动 wireshark 抓包 frps 的端口。会得到如下的抓包数据。

image.png
image.png

可以看到,即便是用了 KCP,报文依旧是明文的,完全没有加密。

TCP + TLS

接下来开启 TLS 通信。

  1. # frps
  2. 同上
  1. # frpc
  2. [common]
  3. 同上
  4. protocol = tcp
  5. tls_enable = true
  6. [ssh]
  7. type = tcp
  8. local_ip = 0.0.0.0
  9. local_port = 22
  10. remote_port = 6000

启动 frps 和 frpc,并且启动 wireshark 抓包 frps 的端口。会得到如下的抓包数据。
image.png

从报文里看到,frpc 和 frps 完成 TCP 的 3 次握手之后,开始了 TLS 的握手协议,TLS 协议完成握手之后,才开始数据传输。我们大概可以判断报文 67 和 68 应该是客户端发送给服务端的数据和服务端返回给客户端的数据。点进去发现都是被加密过的乱码,完全无法识别报文内容。

也就是说开启了 TLS 之后登录协议也被加密了,大大增强了通信的安全性。

KCP + TLS

接下来测试一下 KCP 开启 TLS 之后我们抓到的数据情况。
具体配置如下。

  1. # frps
  2. 同上
  1. # frpc
  2. [common]
  3. 同上
  4. protocol = kcp
  5. tls_enable = true
  6. [ssh]
  7. type = tcp
  8. local_ip = 0.0.0.0
  9. local_port = 22
  10. remote_port = 6000

启动 frps 和 frpc,并且启动 wireshark 抓包 frps 的端口。会得到如下的抓包数据。

image.png

从抓包内容来看,报文是完全加密的,根本无法识别出来。TLS 在 KCP 协议上完成了握手,然后开始了加密通信。

总结

通过上面的实验,我们总结一下,如果要保证通信的安全性,最好开启 TLS。