Repository: fatedier/frp (language: go)

Issue:
[http2]代理一个只支持http2的server, frp返回502错误码
### 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

- [ ] Docs
- [ ] Installation
- [ ] Performance and Scalability
- [ ] Security
- [ ] User Experience
- [X] Test and Release
- [ ] Developer Infrastructure
- [X] Client Plugin
- [ ] Server Plugin
- [ ] Extensions
- [X] Others


Discussion / hints:
看起来是 `https2https` 插件，返回的 `alpn` 列表中 不含 `h2`
建议在 监听 127.0.0.1:12345 的程序 正确配置证书
> 建议在 监听 127.0.0.1:12345 的程序 正确配置证书

证书配置是正确的，因为不通过frp代理直接访问原始端口的情况下能正常访问，并且没有证书错误。
> > 建议在 监听 127.0.0.1:12345 的程序 正确配置证书
> 
> 证书配置是正确的，因为不通过frp代理直接访问原始端口的情况下能正常访问，并且没有证书错误。

可以尝试 注释掉 `[proxies.plugin]` 及以下的行
并添加 `localPort = 12345`
> 并且没有证书错误。

那是因为，给 `curl` 传递了 `-k`
刚试了一下不带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 
```
https2https  插件和Port 12345 服务用的都是Let's Encrypt的证书(同一个证书)
> https2https 插件和Port 12345 服务用的都是Let's Encrypt的证书(同一个证书)

既然是 同一个证书，那么 不需要使用插件
用插件设置了一下http header。现在能确定是什么原因吗?

不知道是否和这个有关：
https://www.cnblogs.com/embedded-linux/p/12585854.html
Golang默认支持HTTP/2协议，只要使用TLS则默认启动HTTP/2特性，但对http Client做一些定制化配置后，会覆盖掉http库的默认行为，导致开启HTTP/1.1。
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里有这个配置项。

Fix patch:
diff --git a/Release.md b/Release.md
index 9f3577bbab5..940fa94f629 100644
--- a/Release.md
+++ b/Release.md
@@ -1,14 +1,3 @@
-### Notable Changes
-
-We have optimized the heartbeat mechanism when tcpmux is enabled (enabled by default). The default value of `heartbeatInterval` has been adjusted to -1. This update ensures that when tcpmux is active, the client does not send additional heartbeats to the server. Since tcpmux incorporates its own heartbeat system, this change effectively reduces unnecessary data consumption, streamlining communication efficiency between client and server.
-
-When connecting to frps versions older than v0.39.0 might encounter compatibility issues due to changes in the heartbeat mechanism. As a temporary workaround, setting the `heartbeatInterval` to 30 can help maintain stable connectivity with these older versions. We recommend updating to the latest frps version to leverage full functionality and improvements.
-
-### Features
-
-* Show tcpmux proxies on the frps dashboard.
-* `http` proxy can modify the response header. For example, `responseHeaders.set.foo = "bar"` will add a new header `foo: bar` to the response.
-
 ### Fixes
 
-* When an HTTP proxy request times out, it returns 504 instead of 404 now.
+* Fixed an issue where HTTP/2 was not enabled for https2http and https2https plugins.
diff --git a/pkg/plugin/client/https2http.go b/pkg/plugin/client/https2http.go
index d7bddcb3505..6d686361e99 100644
--- a/pkg/plugin/client/https2http.go
+++ b/pkg/plugin/client/https2http.go
@@ -72,11 +72,6 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 		ErrorLog:   stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
 	}
 
-	p.s = &http.Server{
-		Handler:           rp,
-		ReadHeaderTimeout: 60 * time.Second,
-	}
-
 	var (
 		tlsConfig *tls.Config
 		err       error
@@ -90,10 +85,15 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 	if err != nil {
 		return nil, fmt.Errorf("gen TLS config error: %v", err)
 	}
-	ln := tls.NewListener(listener, tlsConfig)
+
+	p.s = &http.Server{
+		Handler:           rp,
+		ReadHeaderTimeout: 60 * time.Second,
+		TLSConfig:         tlsConfig,
+	}
 
 	go func() {
-		_ = p.s.Serve(ln)
+		_ = p.s.ServeTLS(listener, "", "")
 	}()
 	return p, nil
 }
diff --git a/pkg/plugin/client/https2https.go b/pkg/plugin/client/https2https.go
index 047d4ff26ab..5ddd4dd692e 100644
--- a/pkg/plugin/client/https2https.go
+++ b/pkg/plugin/client/https2https.go
@@ -78,11 +78,6 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 		ErrorLog:   stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
 	}
 
-	p.s = &http.Server{
-		Handler:           rp,
-		ReadHeaderTimeout: 60 * time.Second,
-	}
-
 	var (
 		tlsConfig *tls.Config
 		err       error
@@ -96,10 +91,15 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 	if err != nil {
 		return nil, fmt.Errorf("gen TLS config error: %v", err)
 	}
-	ln := tls.NewListener(listener, tlsConfig)
+
+	p.s = &http.Server{
+		Handler:           rp,
+		ReadHeaderTimeout: 60 * time.Second,
+		TLSConfig:         tlsConfig,
+	}
 
 	go func() {
-		_ = p.s.Serve(ln)
+		_ = p.s.ServeTLS(listener, "", "")
 	}()
 	return p, nil
 }


Test patch: