config: add VhostEnableH2C flag to support HTTP/2 cleartext upgrade

This commit is contained in:
sword-jin 2024-11-28 02:40:12 +00:00
parent 8593eff752
commit 0a70a6a2d9
7 changed files with 60 additions and 25 deletions

View File

@ -230,6 +230,7 @@ func RegisterServerConfigFlags(cmd *cobra.Command, c *v1.ServerConfig, opts ...R
cmd.PersistentFlags().IntVarP(&c.QUICBindPort, "quic_bind_port", "", 0, "quic bind udp port")
cmd.PersistentFlags().StringVarP(&c.ProxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address")
cmd.PersistentFlags().IntVarP(&c.VhostHTTPPort, "vhost_http_port", "", 0, "vhost http port")
cmd.PersistentFlags().BoolVarP(&c.VhostEnableH2C, "vhost_enable_h2c", "", false, "vhost enable h2c")
cmd.PersistentFlags().IntVarP(&c.VhostHTTPSPort, "vhost_https_port", "", 0, "vhost https port")
cmd.PersistentFlags().Int64VarP(&c.VhostHTTPTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout")
cmd.PersistentFlags().StringVarP(&c.WebServer.Addr, "dashboard_addr", "", "0.0.0.0", "dashboard address")

View File

@ -110,6 +110,7 @@ func Convert_ServerCommonConf_To_v1(conf *ServerCommonConf) *v1.ServerConfig {
out.ProxyBindAddr = conf.ProxyBindAddr
out.VhostHTTPPort = conf.VhostHTTPPort
out.VhostEnableH2C = conf.VhostEnableH2C
out.VhostHTTPSPort = conf.VhostHTTPSPort
out.TCPMuxHTTPConnectPort = conf.TCPMuxHTTPConnectPort
out.TCPMuxPassthrough = conf.TCPMuxPassthrough

View File

@ -61,6 +61,11 @@ type ServerCommonConf struct {
// requests. If this value is 0, the server will not listen for HTTP
// requests. By default, this value is 0.
VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port"`
// VhostEnableH2c specifies whether to enable HTTP/2 cleartext upgrade.
// By default, this value is false. If enabled, the server will be compatible
// with http2 on cleartext, accordingly, the vhost server will use http2
// transport to proxy the request.
VhostEnableH2C bool `json:"vhostEnableH2C,omitempty"`
// VhostHTTPSPort specifies the port that the server listens for HTTPS
// Vhost requests. If this value is 0, the server will not listen for HTTPS
// requests. By default, this value is 0.

View File

@ -47,6 +47,11 @@ type ServerConfig struct {
// VhostHTTPTimeout specifies the response header timeout for the Vhost
// HTTP server, in seconds. By default, this value is 60.
VhostHTTPTimeout int64 `json:"vhostHTTPTimeout,omitempty"`
// VhostEnableH2c specifies whether to enable HTTP/2 cleartext upgrade.
// By default, this value is false. If enabled, the server will be compatible
// with http2 on cleartext, accordingly, the vhost server will use http2
// transport to proxy the request.
VhostEnableH2C bool `json:"vhostEnableH2C,omitempty"`
// VhostHTTPSPort specifies the port that the server listens for HTTPS
// Vhost requests. If this value is 0, the server will not listen for HTTPS
// requests.

View File

@ -16,6 +16,7 @@ package vhost
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
@ -29,6 +30,8 @@ import (
libio "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
httppkg "github.com/fatedier/frp/pkg/util/http"
"github.com/fatedier/frp/pkg/util/log"
@ -38,10 +41,11 @@ var ErrNoRouteFound = errors.New("no route found")
type HTTPReverseProxyOptions struct {
ResponseHeaderTimeoutS int64
EnableH2C bool
}
type HTTPReverseProxy struct {
proxy *httputil.ReverseProxy
proxy http.Handler
vhostRouter *Routers
responseHeaderTimeout time.Duration
@ -55,6 +59,40 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
vhostRouter: vhostRouter,
}
var transport http.RoundTripper
// Create a connection to one proxy routed by route policy.
transport = &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
IdleConnTimeout: 60 * time.Second,
MaxIdleConnsPerHost: 5,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return rp.CreateConnection(ctx.Value(RouteInfoKey).(*RequestRouteInfo), true)
},
Proxy: func(req *http.Request) (*url.URL, error) {
// Use proxy mode if there is host in HTTP first request line.
// GET http://example.com/ HTTP/1.1
// Host: example.com
//
// Normal:
// GET / HTTP/1.1
// Host: example.com
urlHost := req.Context().Value(RouteInfoKey).(*RequestRouteInfo).URLHost
if urlHost != "" {
return req.URL, nil
}
return nil, nil
},
}
if option.EnableH2C {
transport = &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
return rp.CreateConnection(ctx.Value(RouteInfoKey).(*RequestRouteInfo), true)
},
}
}
proxy := &httputil.ReverseProxy{
// Modify incoming requests by route policies.
Rewrite: func(r *httputil.ProxyRequest) {
@ -101,29 +139,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
}
return nil
},
// Create a connection to one proxy routed by route policy.
Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
IdleConnTimeout: 60 * time.Second,
MaxIdleConnsPerHost: 5,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return rp.CreateConnection(ctx.Value(RouteInfoKey).(*RequestRouteInfo), true)
},
Proxy: func(req *http.Request) (*url.URL, error) {
// Use proxy mode if there is host in HTTP first request line.
// GET http://example.com/ HTTP/1.1
// Host: example.com
//
// Normal:
// GET / HTTP/1.1
// Host: example.com
urlHost := req.Context().Value(RouteInfoKey).(*RequestRouteInfo).URLHost
if urlHost != "" {
return req.URL, nil
}
return nil, nil
},
},
Transport: transport,
BufferPool: pool.NewBuffer(32 * 1024),
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
@ -138,7 +154,11 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
_, _ = rw.Write(getNotFoundPageContent())
},
}
rp.proxy = proxy
if option.EnableH2C {
rp.proxy = h2c.NewHandler(proxy, &http2.Server{})
} else {
rp.proxy = proxy
}
return rp
}

View File

@ -70,6 +70,7 @@ type serverInfoResp struct {
Version string `json:"version"`
BindPort int `json:"bindPort"`
VhostHTTPPort int `json:"vhostHTTPPort"`
VhostEnableH2C bool `json:"vhostEnableH2C"`
VhostHTTPSPort int `json:"vhostHTTPSPort"`
TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort"`
KCPBindPort int `json:"kcpBindPort"`
@ -111,6 +112,7 @@ func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
BindPort: svr.cfg.BindPort,
VhostHTTPPort: svr.cfg.VhostHTTPPort,
VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
VhostEnableH2C: svr.cfg.VhostEnableH2C,
TCPMuxHTTPConnectPort: svr.cfg.TCPMuxHTTPConnectPort,
KCPBindPort: svr.cfg.KCPBindPort,
QUICBindPort: svr.cfg.QUICBindPort,

View File

@ -281,6 +281,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
if cfg.VhostHTTPPort > 0 {
rp := vhost.NewHTTPReverseProxy(vhost.HTTPReverseProxyOptions{
ResponseHeaderTimeoutS: cfg.VhostHTTPTimeout,
EnableH2C: cfg.VhostEnableH2C,
}, svr.httpVhostRouter)
svr.rc.HTTPReverseProxy = rp