[GH-ISSUE #4501] Cannot handle Websocket traffic #3554

Closed
opened 2026-05-05 14:16:58 -06:00 by gitea-mirror · 7 comments
Owner

Originally created by @JobberRT on GitHub (Oct 18, 2024).
Original GitHub issue: https://github.com/fatedier/frp/issues/4501

Bug Description

frpc cannot handle websocket traffic in both transport.protocol=tcp and transport.protocol=websocket. Websocket server indicates that Websocket Handshake Not Finished.

frpc Version

0.60.0

frps Version

0.60.0

System Architecture

linux/amd64

Configurations

frpc.ini

# A literal address or host name for IPv6 must be enclosed
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
# For single serverAddr field, no need square brackets, like serverAddr = "::".
serverAddr = "{{ .Envs.SERVER_ADDR }}"
serverPort = {{ .Envs.SERVER_PORT }}

# Decide if exit program when first login failed, otherwise continuous relogin to frps
# default is true
loginFailExit = true

# console or real logFile path like ./frpc.log
log.to = "console"
# trace, debug, info, warn, error
log.level = "trace"
log.maxDays = 3
# disable log colors when log.to is console, default is false
log.disablePrintColor = false

auth.method = "token"
# auth.additionalScopes specifies additional scopes to include authentication information.
# Optional values are HeartBeats, NewWorkConns.
# auth.additionalScopes = ["HeartBeats", "NewWorkConns"]

# auth token
auth.token = "{{ .Envs.TOKEN }}"

# Enable golang pprof handlers in admin listener.
webServer.pprofEnable = false

# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
transport.dialServerTimeout = 15

# dialServerKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
# If negative, keep-alive probes are disabled.
transport.dialServerKeepalive = 7200

# connections will be established in advance, default value is zero
transport.poolCount = 5

# If tcp stream multiplexing is used, default is true, it must be same with frps
transport.tcpMux = true

# Specify keep alive interval for tcp mux.
# only valid if tcpMux is enabled.
transport.tcpMuxKeepaliveInterval = 30

# Communication protocol used to connect to server
# supports tcp, kcp, quic, websocket and wss now, default is tcp
transport.protocol = "{{ .Envs.PROTOCOL }}"

# set client binding ip when connect server, default is empty.
# only when protocol = tcp or websocket, the value will be used.
transport.connectServerLocalIP = "0.0.0.0"

# If tls.enable is true, frpc will connect frps by tls.
# Since v0.50.0, the default value has been changed to true, and tls is enabled by default.
transport.tls.enable = true
# transport.tls.certFile = "{{ .Envs.TLS_CERT_PATH }}"
# transport.tls.keyFile = "{{ .Envs.TLS_KEY_PATH }}"
# transport.tls.trustedCaFile = "{{ .Envs.TLS_CA_PATH }}"
# transport.tls.serverName = "{{ .Envs.TLS_SERVER_NAME }}"

# Heartbeat configure, it's not recommended to modify the default value.
# The default value of heartbeatInterval is 10 and heartbeatTimeout is 90. Set negative value
# to disable it.
transport.heartbeatInterval = 15
transport.heartbeatTimeout = 30

# Specify udp packet size, unit is byte. If not set, the default value is 1500.
# This parameter should be same between client and server.
# It affects the udp and sudp proxy.
udpPacketSize = 1500

# Additional metadatas for client.
# metadatas.var1 = "abc"
# metadatas.var2 = "123"

# Include other config files for proxies.
includes = ["./config/*.toml"]

A frpc proxy config example(using rustdesk as example)

[[proxies]]
name = "Rustdesk-hbbr@21117"
type = "tcp"
localIP = "rustdesk-hbbr"
localPort = 21117
remotePort = 21117

transport.bandwidthLimit = "256KB"
transport.bandwidthLimitMode = "client"
transport.useEncryption = true
transport.useCompression = true

healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 15
healthCheck.maxFailed = 5
healthCheck.intervalSeconds = 15

[[proxies]]
name = "Rustdesk-hbbr@21119"
type = "tcp"
localIP = "rustdesk-hbbr"
localPort = 21119
remotePort = 21119

transport.bandwidthLimit = "256KB"
transport.bandwidthLimitMode = "client"
transport.useEncryption = true
transport.useCompression = true

healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 15
healthCheck.maxFailed = 5
healthCheck.intervalSeconds = 15

[[proxies]]
name = "Rustdesk-hbbs@21114"
type = "tcp"
localIP = "rustdesk-hbbs"
localPort = 21114
remotePort = 21114

transport.bandwidthLimit = "256KB"
transport.bandwidthLimitMode = "client"
transport.useEncryption = true
transport.useCompression = true

healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 15
healthCheck.maxFailed = 5
healthCheck.intervalSeconds = 15

[[proxies]]
name = "Rustdesk-hbbs@21115"
type = "tcp"
localIP = "rustdesk-hbbs"
localPort = 21115
remotePort = 21115

transport.bandwidthLimit = "256KB"
transport.bandwidthLimitMode = "client"
transport.useEncryption = true
transport.useCompression = true

healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 15
healthCheck.maxFailed = 5
healthCheck.intervalSeconds = 15

[[proxies]]
name = "Rustdesk-hbbs@21116T"
type = "tcp"
localIP = "rustdesk-hbbs"
localPort = 21116
remotePort = 21116

transport.bandwidthLimit = "256KB"
transport.bandwidthLimitMode = "client"
transport.useEncryption = true
transport.useCompression = true

healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 15
healthCheck.maxFailed = 5
healthCheck.intervalSeconds = 15

[[proxies]]
name = "Rustdesk-hbbs@21116U"
type = "udp"
localIP = "rustdesk-hbbs"
localPort = 21116
remotePort = 21116

transport.bandwidthLimit = "256KB"
transport.bandwidthLimitMode = "client"
transport.useEncryption = true
transport.useCompression = true

[[proxies]]
name = "Rustdesk-hbbs@21118"
type = "tcp"
localIP = "rustdesk-hbbs"
localPort = 21118
remotePort = 21118

transport.bandwidthLimit = "256KB"
transport.bandwidthLimitMode = "client"
transport.useEncryption = true
transport.useCompression = true

healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 15
healthCheck.maxFailed = 5
healthCheck.intervalSeconds = 15

Logs

frpc log

2024-10-18 12:49:37.295 [T] [proxy/proxy.go:224] [661d6f2026d94f8f] [Rustdesk-hbbr@21117] join connections errors: [writeto tcp 172.18.0.2:35598->172.18.0.8:21117: read tcp 172.18.0.2:35598->172.18.0.8:21117: use of closed network connection]

rustdesk log

Caused by:

    Handshake not finished, hbbs::rendezvous_server:src/rendezvous_server.rs:1101:13

[2024-10-18 12:50:13.552107 +00:00] DEBUG [src/rendezvous_server.rs:1097] Tcp connection from [::ffff:172.18.0.2]:39068, ws: true

[2024-10-18 12:50:13.552138 +00:00] DEBUG [src/rendezvous_server.rs:1097] Tcp connection from [::ffff:172.18.0.2]:53500, ws: false

[2024-10-18 12:50:13.552340 +00:00] DEBUG [src/rendezvous_server.rs:1101] WebSocket protocol error: Handshake not finished

Steps to reproduce

No response

Affected area

  • Docs
  • Installation
  • Performance and Scalability
  • Security
  • User Experience
  • Test and Release
  • Developer Infrastructure
  • Client Plugin
  • Server Plugin
  • Extensions
  • Others
Originally created by @JobberRT on GitHub (Oct 18, 2024). Original GitHub issue: https://github.com/fatedier/frp/issues/4501 ### Bug Description `frpc` cannot handle websocket traffic in both `transport.protocol=tcp` and `transport.protocol=websocket`. Websocket server indicates that `Websocket Handshake Not Finished`. ### frpc Version 0.60.0 ### frps Version 0.60.0 ### System Architecture linux/amd64 ### Configurations ## frpc.ini ```toml # A literal address or host name for IPv6 must be enclosed # in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80" # For single serverAddr field, no need square brackets, like serverAddr = "::". serverAddr = "{{ .Envs.SERVER_ADDR }}" serverPort = {{ .Envs.SERVER_PORT }} # Decide if exit program when first login failed, otherwise continuous relogin to frps # default is true loginFailExit = true # console or real logFile path like ./frpc.log log.to = "console" # trace, debug, info, warn, error log.level = "trace" log.maxDays = 3 # disable log colors when log.to is console, default is false log.disablePrintColor = false auth.method = "token" # auth.additionalScopes specifies additional scopes to include authentication information. # Optional values are HeartBeats, NewWorkConns. # auth.additionalScopes = ["HeartBeats", "NewWorkConns"] # auth token auth.token = "{{ .Envs.TOKEN }}" # Enable golang pprof handlers in admin listener. webServer.pprofEnable = false # The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds. transport.dialServerTimeout = 15 # dialServerKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps. # If negative, keep-alive probes are disabled. transport.dialServerKeepalive = 7200 # connections will be established in advance, default value is zero transport.poolCount = 5 # If tcp stream multiplexing is used, default is true, it must be same with frps transport.tcpMux = true # Specify keep alive interval for tcp mux. # only valid if tcpMux is enabled. transport.tcpMuxKeepaliveInterval = 30 # Communication protocol used to connect to server # supports tcp, kcp, quic, websocket and wss now, default is tcp transport.protocol = "{{ .Envs.PROTOCOL }}" # set client binding ip when connect server, default is empty. # only when protocol = tcp or websocket, the value will be used. transport.connectServerLocalIP = "0.0.0.0" # If tls.enable is true, frpc will connect frps by tls. # Since v0.50.0, the default value has been changed to true, and tls is enabled by default. transport.tls.enable = true # transport.tls.certFile = "{{ .Envs.TLS_CERT_PATH }}" # transport.tls.keyFile = "{{ .Envs.TLS_KEY_PATH }}" # transport.tls.trustedCaFile = "{{ .Envs.TLS_CA_PATH }}" # transport.tls.serverName = "{{ .Envs.TLS_SERVER_NAME }}" # Heartbeat configure, it's not recommended to modify the default value. # The default value of heartbeatInterval is 10 and heartbeatTimeout is 90. Set negative value # to disable it. transport.heartbeatInterval = 15 transport.heartbeatTimeout = 30 # Specify udp packet size, unit is byte. If not set, the default value is 1500. # This parameter should be same between client and server. # It affects the udp and sudp proxy. udpPacketSize = 1500 # Additional metadatas for client. # metadatas.var1 = "abc" # metadatas.var2 = "123" # Include other config files for proxies. includes = ["./config/*.toml"] ``` ## A frpc proxy config example(using [rustdesk](https://github.com/rustdesk/rustdesk) as example) ```toml [[proxies]] name = "Rustdesk-hbbr@21117" type = "tcp" localIP = "rustdesk-hbbr" localPort = 21117 remotePort = 21117 transport.bandwidthLimit = "256KB" transport.bandwidthLimitMode = "client" transport.useEncryption = true transport.useCompression = true healthCheck.type = "tcp" healthCheck.timeoutSeconds = 15 healthCheck.maxFailed = 5 healthCheck.intervalSeconds = 15 [[proxies]] name = "Rustdesk-hbbr@21119" type = "tcp" localIP = "rustdesk-hbbr" localPort = 21119 remotePort = 21119 transport.bandwidthLimit = "256KB" transport.bandwidthLimitMode = "client" transport.useEncryption = true transport.useCompression = true healthCheck.type = "tcp" healthCheck.timeoutSeconds = 15 healthCheck.maxFailed = 5 healthCheck.intervalSeconds = 15 [[proxies]] name = "Rustdesk-hbbs@21114" type = "tcp" localIP = "rustdesk-hbbs" localPort = 21114 remotePort = 21114 transport.bandwidthLimit = "256KB" transport.bandwidthLimitMode = "client" transport.useEncryption = true transport.useCompression = true healthCheck.type = "tcp" healthCheck.timeoutSeconds = 15 healthCheck.maxFailed = 5 healthCheck.intervalSeconds = 15 [[proxies]] name = "Rustdesk-hbbs@21115" type = "tcp" localIP = "rustdesk-hbbs" localPort = 21115 remotePort = 21115 transport.bandwidthLimit = "256KB" transport.bandwidthLimitMode = "client" transport.useEncryption = true transport.useCompression = true healthCheck.type = "tcp" healthCheck.timeoutSeconds = 15 healthCheck.maxFailed = 5 healthCheck.intervalSeconds = 15 [[proxies]] name = "Rustdesk-hbbs@21116T" type = "tcp" localIP = "rustdesk-hbbs" localPort = 21116 remotePort = 21116 transport.bandwidthLimit = "256KB" transport.bandwidthLimitMode = "client" transport.useEncryption = true transport.useCompression = true healthCheck.type = "tcp" healthCheck.timeoutSeconds = 15 healthCheck.maxFailed = 5 healthCheck.intervalSeconds = 15 [[proxies]] name = "Rustdesk-hbbs@21116U" type = "udp" localIP = "rustdesk-hbbs" localPort = 21116 remotePort = 21116 transport.bandwidthLimit = "256KB" transport.bandwidthLimitMode = "client" transport.useEncryption = true transport.useCompression = true [[proxies]] name = "Rustdesk-hbbs@21118" type = "tcp" localIP = "rustdesk-hbbs" localPort = 21118 remotePort = 21118 transport.bandwidthLimit = "256KB" transport.bandwidthLimitMode = "client" transport.useEncryption = true transport.useCompression = true healthCheck.type = "tcp" healthCheck.timeoutSeconds = 15 healthCheck.maxFailed = 5 healthCheck.intervalSeconds = 15 ``` ### Logs ## frpc log ``` 2024-10-18 12:49:37.295 [T] [proxy/proxy.go:224] [661d6f2026d94f8f] [Rustdesk-hbbr@21117] join connections errors: [writeto tcp 172.18.0.2:35598->172.18.0.8:21117: read tcp 172.18.0.2:35598->172.18.0.8:21117: use of closed network connection] ``` ## rustdesk log ``` Caused by: Handshake not finished, hbbs::rendezvous_server:src/rendezvous_server.rs:1101:13 [2024-10-18 12:50:13.552107 +00:00] DEBUG [src/rendezvous_server.rs:1097] Tcp connection from [::ffff:172.18.0.2]:39068, ws: true [2024-10-18 12:50:13.552138 +00:00] DEBUG [src/rendezvous_server.rs:1097] Tcp connection from [::ffff:172.18.0.2]:53500, ws: false [2024-10-18 12:50:13.552340 +00:00] DEBUG [src/rendezvous_server.rs:1101] WebSocket protocol error: Handshake not finished ``` ### Steps to reproduce _No response_ ### Affected area - [ ] Docs - [ ] Installation - [ ] Performance and Scalability - [ ] Security - [X] User Experience - [ ] Test and Release - [ ] Developer Infrastructure - [ ] Client Plugin - [ ] Server Plugin - [ ] Extensions - [ ] Others
Author
Owner

@JobberRT commented on GitHub (Oct 18, 2024):

The same problem can also be found when using portainer's web terminal

<!-- gh-comment-id:2422422049 --> @JobberRT commented on GitHub (Oct 18, 2024): The same problem can also be found when using [portainer](https://github.com/portainer/portainer)'s web terminal
Author
Owner

@JobberRT commented on GitHub (Oct 19, 2024):

A further digging tells that If the frpc and its service are on the same private network, websocket will success. For example, frpc and portainer and rustdesk are both in the same network: 172.10.0.0/24. (For rustdesk, the hbbr/hbbs/frpc/controlled client must be at the same network, the controlling client may not)

I start to wonder if the problem is caused by my VPS provider, will check later

<!-- gh-comment-id:2423566566 --> @JobberRT commented on GitHub (Oct 19, 2024): A further digging tells that If the `frpc` and its service are on the same private network, websocket will success. For example, `frpc` and `portainer` and `rustdesk` are both in the same network: `172.10.0.0/24`. (For `rustdesk`, the `hbbr`/`hbbs`/`frpc`/`controlled client` must be at the same network, the `controlling client` may not) I start to wonder if the problem is caused by my VPS provider, will check later
Author
Owner

@JobberRT commented on GitHub (Oct 19, 2024):

Somehow after restarting the portainer and the frpc, portainer's websocket connection become available

I also tried to reinstall the docker, recreate a new frpc and rustdesk container, but rustdesk's webscoket cannot become available like portainer.

And for why rustdesk websocket is available when frpc and the services are in the same network, I verified that its because rustdesk use direct-peering(p2p). After disable the direct-peering, rustdesk become compelete unusable. Besides that, the websocket-available-inside-same-network behavior changed, it requires the controlling client inside the same network as well.

I guess this is not a VPS issue, but a rustdesk issue, I will go to their repo and submit an issue to see if I can get the answer. Meanwhile, I will keep this issue open and close it after I get a positive answer(With the solution if I get one)

<!-- gh-comment-id:2423587860 --> @JobberRT commented on GitHub (Oct 19, 2024): Somehow after restarting the `portainer` and the `frpc`, `portainer`'s websocket connection become available I also tried to reinstall the docker, recreate a new `frpc` and `rustdesk` container, but `rustdesk`'s webscoket cannot become available like `portainer`. And for why `rustdesk` websocket is available when `frpc` and the services are in the same network, I verified that its because `rustdesk` use direct-peering(p2p). After disable the direct-peering, rustdesk become compelete unusable. Besides that, the websocket-available-inside-same-network behavior changed, it requires the controlling client inside the same network as well. I guess this is not a VPS issue, but a `rustdesk` issue, I will go to their repo and submit an issue to see if I can get the answer. Meanwhile, I will keep this issue open and close it after I get a positive answer(With the solution if I get one)
Author
Owner

@fatedier commented on GitHub (Oct 21, 2024):

For proxies of type tcp, frp does not modify any transmitted content.

Generally speaking, if issues arise, they are mostly related to application layer logic, such as restricting access by IP or relying on specific network links.

<!-- gh-comment-id:2425503658 --> @fatedier commented on GitHub (Oct 21, 2024): For proxies of type `tcp`, frp does not modify any transmitted content. Generally speaking, if issues arise, they are mostly related to application layer logic, such as restricting access by IP or relying on specific network links.
Author
Owner

@JobberRT commented on GitHub (Oct 21, 2024):

@fatedier Thanks for the reply, I've read the code of frp and understand your point. And now I'm setting up some test env to dig further

<!-- gh-comment-id:2426744996 --> @JobberRT commented on GitHub (Oct 21, 2024): @fatedier Thanks for the reply, I've read the code of frp and understand your point. And now I'm setting up some test env to dig further
Author
Owner

@JobberRT commented on GitHub (Oct 31, 2024):

I will close this issue since I'm been super busy lately, and I will update the information if I have time and find something later

<!-- gh-comment-id:2449173352 --> @JobberRT commented on GitHub (Oct 31, 2024): I will close this issue since I'm been super busy lately, and I will update the information if I have time and find something later
Author
Owner

@k-nearest-neighbor commented on GitHub (Oct 30, 2025):

  1. arrived at frp github, saw some messy documentation, install steps not even very well documented.
  2. start installing and configuring, notice they are treating "tcp" and "http" both as options for "type" ... that's weird because TCP is layer 4 and HTTP is layer 7 ... assume this thing might have problems supporting websockets which is what I need it for.
  3. Check the issues and find this ticket which seems to suggest that maybe it supports websockets, may not, but probably not.
  4. left in a hurry feeling confident that I avoided a few hours of pointless headache

run.

<!-- gh-comment-id:3470532248 --> @k-nearest-neighbor commented on GitHub (Oct 30, 2025): 1. arrived at frp github, saw some messy documentation, install steps not even very well documented. 2. start installing and configuring, notice they are treating "tcp" and "http" both as options for "type" ... that's weird because TCP is layer 4 and HTTP is layer 7 ... assume this thing might have problems supporting websockets which is what I need it for. 3. Check the issues and find this ticket which seems to suggest that maybe it supports websockets, may not, but probably not. 4. left in a hurry feeling confident that I avoided a few hours of pointless headache run.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/frp#3554
No description provided.