fatedier / frp

A fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.
Apache License 2.0
81.42k stars 12.82k forks source link

[http2]代理一个只支持http2的server, frp返回502错误码 #4217

Closed shuguang101 closed 3 weeks ago

shuguang101 commented 1 month ago

Bug Description

使用frp代理一个只支持http2的server时, frp总是返回502错误码 HTTP 178 HTTP/1.1 502 Bad Gateway

使用curl连接frp vhost端口 强制curl使用http2连接,但最终http协议还是failback到http1.1了(frp not agree on a protocol), 返回502错误码

curl -4 -vsk --http2 --request POST  ${frp_addr:frp_port} 命令行输出信息

* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* using HTTP/1.x

使用curl直接连接原始端口 强制curl使用http2,能够协商成功,最终使用http2,返回200 服务能正常访问

curl -4 -vsk --http2  raw_ip:raw_port 命令行输出

* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
* Server certificate:
* using HTTP/2

由于被代理的服务只支持http2, 如果强制使用http1.1访问会失败

curl -4 -vsk --http1.1 raw_ip:raw_port

* ALPN: offers http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* using HTTP/1.x
* old SSL session ID is stale, removing
*** Received HTTP/0.9 when not allowed**
* Closing connection 0
* TLSv1.3 (OUT), TLS alert, close notify (256):

frpc Version

0.57.0

frps Version

0.57.0

System Architecture

linux/amd64

Configurations

frpc

user = "xx"
auth.method = "token"
auth.token = "xx"
udpPacketSize = 1400

loginFailExit = false
serverAddr = "xxxx"
serverPort = xx

transport.poolCount = 0
transport.tcpMux = false
transport.protocol = "tcp"
transport.connectServerLocalIP = "0.0.0.0"

[[proxies]]
name = "https_xxx"
type = "https"
customDomains = ["xxxxx.com"]
transport.proxyProtocolVersion = ""
[proxies.plugin]
type = "https2https"
localAddr = "127.0.0.1:12345"
crtPath = "/xxx/fullchain.pem"
keyPath = "/xxx/privkey.pem"

frps

bindAddr = "xxx"
bindPort = xx

transport.maxPoolCount = 1000
transport.tcpMux = false

vhostHTTPSPort = 443
vhostHTTPTimeout = 120
userConnTimeout = 15
tcpmuxHTTPConnectPort = 0
tcpmuxPassthrough = false

auth.method = "token"
auth.token = "xx"
udpPacketSize = 1400

Logs

No response

Steps to reproduce

  1. 创建一个只支持http2的https服务
  2. 使用https2https代理这个服务
  3. 访问frp会返回502 ...

Affected area

xqzr commented 1 month ago

看起来是 https2https 插件,返回的 alpn 列表中 不含 h2 建议在 监听 127.0.0.1:12345 的程序 正确配置证书

shuguang101 commented 1 month ago

建议在 监听 127.0.0.1:12345 的程序 正确配置证书

证书配置是正确的,因为不通过frp代理直接访问原始端口的情况下能正常访问,并且没有证书错误。

xqzr commented 1 month ago

建议在 监听 127.0.0.1:12345 的程序 正确配置证书

证书配置是正确的,因为不通过frp代理直接访问原始端口的情况下能正常访问,并且没有证书错误。

可以尝试 注释掉 [proxies.plugin] 及以下的行 并添加 localPort = 12345

xqzr commented 1 month ago

并且没有证书错误。

那是因为,给 curl 传递了 -k

shuguang101 commented 1 month ago

刚试了一下不带k也没有证书错误

curl -4 -vs --http2 --request POST
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
* Server certificate:
*  SSL certificate verify ok.
* using HTTP/2
* h2h3 [:method: POST]
* h2h3 [:path: /xxxxx]
* h2h3 [:scheme: https]
* h2h3 [:authority: xxxxx]
* h2h3 [user-agent: curl/7.88.1]
* Using Stream ID: 1 (easy handle 0x562d1b103070)
* * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* We are completely uploaded and fine
< HTTP/2 200 
shuguang101 commented 1 month ago

https2https 插件和Port 12345 服务用的都是Let's Encrypt的证书(同一个证书)

xqzr commented 1 month ago

https2https 插件和Port 12345 服务用的都是Let's Encrypt的证书(同一个证书)

既然是 同一个证书,那么 不需要使用插件

shuguang101 commented 1 month ago

用插件设置了一下http header。现在能确定是什么原因吗?

不知道是否和这个有关: https://www.cnblogs.com/embedded-linux/p/12585854.html Golang默认支持HTTP/2协议,只要使用TLS则默认启动HTTP/2特性,但对http Client做一些定制化配置后,会覆盖掉http库的默认行为,导致开启HTTP/1.1。

shuguang101 commented 4 weeks ago

nginx在配置proxy_pass时可以指定http版本

location / {
  proxy_pass http://1.2.3.0;
  proxy_http_version 1.1;
}

如果无法准确判断http version,是否可以考虑像nginx一样,在插件配置项里添加一个proxy_http_version用于指定http版本。仅仅是个建议哈,我对nginx和http也不是十分熟悉,只是刚刚看到nginx里有这个配置项。