frp同时转发远程桌面的TCP和UDP端口

前言

笔记本放在家里,通过远程桌面连上去使用,不用每天都背着电脑跑来跑去了哈哈。但是使用过程中发现鼠标稍稍有一点不跟手,和服务器的延迟并不高,单向顶多十几毫秒,研究了下发现是因为单纯使用 TCP 的问题,所以就有了这篇文章。

初始方案

为了保证安全,初始方案使用了 frp 的 stcp 隧道。需要在两台设备上都部署 frpc,然后和服务器上部署的 frps 通信。

stcp 安全的 TCP 内网代理,需要在被访问者和访问者的机器上都部署 frpc,不需要在服务端暴露端口。

这种情况下远程桌面的流量会通过本地的 frpc 加密后发送到服务器,服务器收到后将数据转发给被控端运行的 fpc,之后转发给本地的远程桌面端口。流程大概是这样的:

我的配置文件如下:

1
2
3
4
# 服务端配置
[common]
bind_port = 7000
token = xxxxxx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 主控端配置
[common]
server_addr = xxx.xxx.xxx.xxx
server_port = 7000
token = xxxxxx

[secret_visitor]
type = stcp
# stcp 的访问者
role = visitor
# 要访问的 stcp 代理的名字
server_name = my
sk = abcdefg
bind_addr = 127.0.0.1
bind_port = 7000
1
2
3
4
5
6
7
8
9
10
11
12
# 被控端配置
[common]
server_addr = xxx.xxx.xxx.xxx
server_port = 7000
token = xxxxx

[my]
type = stcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 3389

使用 stcp 隧道的方式不需要在服务器上另外开端口,可以很好的避免安全问题,并且我在服务器端配置了 token 参数来防止未授权的访问。主控端和被控端的配置文件中也添加了 sk 参数,来防止未经授权的访问。

被控端监听本地的 3389 端口,主控端通过被控端设置的名字来访问被控端的端口,借助服务器的帮助将主控端本地 7000 端口的数据转发到被控端本地的 3389 端口上来实现内网穿透。

问题原因

经过查询,鼠标不跟手的来源和所使用的 TCP 隧道有关。

传输控制协议(英语:Transmission Control Protocol,缩写:TCP)是一种面向连接的、可靠的、基于字节流传输层通信协议

由于 TCP 是一种可靠的传输协议,为了保证可靠性,在某个数据包由于某种原因延迟到达时,内核协议栈会将收到的数据包放在缓冲区中, 等待延迟的数据包到达后再组装起来发送给上层调用者。这样会出现一个问题就是一个数据包的延迟到达会导致上层调用者延迟收到一批数据包,从而造成卡顿或者不跟手等问题。

解决这个问题的方法就是使用 UDP 来通信,由于 UDP 是可以乱序到达的,并且一个两个数据包的延迟到达并不会影响其他数据包的正常解析,所以因为几个数据包延迟或者丢失而造成的画质问题几乎看不出来,所以 UDP 非常适合远程桌面这种实时场景。

正因为如此,微信视频,微信语音等等使用到了实时视频流或者实时音频流的场景底层都是使用了 UDP 来传输,很好的解决数据包延迟到达或者丢包导致使用体验下降的问题。

正所谓在实时性场景中, 低延迟比可靠性更重要

经过查询远程桌面是支持使用 UDP 的,所以问题就变成了如何转发远程桌面的 UDP 流量。

远程桌面协议如何使用UDP

要转发远程桌面的 UDP 流量,首先要知道远程桌面是在何时通过什么端口如何使用 UDP 协议来通信的,这里使用抓包的方法来确认。

开启抓包神器 Wireshark,然后打开远程桌面按照正常的方式来连接(为了方便分析,这里没有使用 frp 转发数据,是通过内网直接连接到了远程桌面服务器,所以会看到数据被发送到了 3389 端口),之后断开连接,分析数据包。

根据流量特征将抓到的流量分为几个部分。

  • 第一个红色方框中为 TCP 三次握手包

  • 第二个红色方框中为 TLS 握手包,其中最后一个数据包标红了,因为连接被 RST 了,具体为什么被 RST,猜测可能是我们在使用远程桌面时常见的证书错误(见下图)

  • 上面的 TCP 连接被 RST 了,所以第三个红色方框中是重新进行 TCP 握手的数据包

  • 因为在弹出证书不受信任时我们选择了是,所以第四个红色方框中是重新进行 TLS 握手的数据包

  • 第五个红色方框中是使用 TLS 隧道传输的通信数据

继续往下分析:

  • 使用 TLS 隧道传输一部分数据之后,可以看到出现了 UDP 数据包,本地的远程桌面客户端首先尝试发送了一个 UDP 数据包从本地的 64792 端口发送到服务器的 3389 端口,见第一个红色方框
  • 接收到了服务器的回应(见第二个红色方框)
  • 服务器和客户端进行了通讯后,确认了 UDP 协议是可以正常通信的,所以后面的通信全部使用 UDP 协议来进行。当然,后面的通信中还会有部分的 TLS 数据包(不重要就没有截图),猜测可能是进行一些心跳或者认证之类的通信,由于不清楚具体协议不具体研究

清楚了正常的远程桌面协议是如何使用 UDP 协议进行通信的流程之后,接下来抓包分析目前我使用 stcp 隧道时远程桌面的通信数据。

前边都是正常的 TCP 握手和 TLS 握手数据,就不截图了,主要看图中选中的这行 UDP 数据包,由本地的 57387 端口发送到服务器的 48698 端口,但是没有接收到服务器的回应,远程桌面客户端认为与服务器的 UDP 通信不成功,所以下面的通信仍然由 TLS 隧道来传送。

注意这里 UDP 使用的端口和 TCP 使用的端口是同一个。

对比一下可以得知,正常的远程桌面通过 TCP 来传输握手,心跳,认证信息等数据,认证之后通过 UDP 来传输具体的画面数据。

而我只设置了内网穿透转发 TCP 的数据,导致 UDP 通信不成功, 所以只能使用 TCP 来传输所有的心跳,握手和画面数据。

了解了以上信息,解决方案就很明显了,添加一条内网穿透隧道,增加本地 UDP 端口的数据转发就可以了。

解决方案

增加一条 sudp 隧道,转发本地 TCP 同端口的 UDP 数据到远程桌面服务器即可。

新增 sudp 之后我的配置文件如下:

1
# 服务器配置未改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 被控端配置
[common]
server_addr = xxx.xxx.xxx.xxx
server_port = 7000
token = xxxxx

[my]
type = stcp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 3389

[my2]
type = sudp
# 只有 sk 一致的用户才能访问到此服务
sk = abcdefg
local_ip = 127.0.0.1
local_port = 3389
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 主控端配置
[common]
server_addr = xxx.xxx.xxx.xxx
server_port = 7000
token = xxxxxx

[secret_ssh_visitor]
type = stcp
# stcp 的访问者
role = visitor
# 要访问的 stcp 代理的名字,这里注意要和被控端对应上,stcp对应stcp,sudp对应sudp
server_name = my
sk = abcdefg
# 绑定本地端口用于访问 SSH 服务
bind_addr = 127.0.0.1
bind_port = 4869

[secret_ssh_visitor2]
type = sudp
# stcp 的访问者
role = visitor
# 要访问的 stcp 代理的名字,这里注意要和被控端对应上,stcp对应stcp,sudp对应sudp
server_name = my2
sk = abcdefg
# 绑定本地端口用于访问 SSH 服务
bind_addr = 127.0.0.1
bind_port = 4869

使用以上配置即可解决远程桌面只能使用 TCP 传输数据的问题。

如何判断是否使用了UDP

判断远程桌面是否使用了 UDP 的方法如下:

将远程桌面全屏,鼠标移动到最上方,会自动显示出远程桌面的连接栏,单击箭头所指向的位置即可。

如果提示:

则为已经开启 UDP。

如果提示:

则为未开启 UDP。

总结

本篇文章通过抓包的方式确定了远程桌面不跟手的问题所在,并且简单的增加了一条隧道就解决了远程桌面不能使用 UDP 传输数据的问题。由此可见,使用 Wireshark 抓包分析在日常调试网络问题中,还是比较可靠和有用的。

本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接https://blog.d77.xyz/archives/2c30f363.html