Skip to content

Commit

Permalink
Add http2http client plugin with hostHeaderRewrite and requestHeaders…
Browse files Browse the repository at this point in the history
… support (fatedier#4275)
  • Loading branch information
fatedier authored Jun 12, 2024
1 parent f390e4a commit 939c490
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 8 deletions.
10 changes: 2 additions & 8 deletions Release.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
### Fixes
### Features

* Fixed an issue where HTTP/2 was not enabled for https2http and https2https plugins.
* Fixed the issue where the default values of INI configuration parameters are inconsistent with other configuration formats.

### Changes

* Updated the default value of `transport.tcpMuxKeepaliveInterval` from 60 to 30.
* On the Android platform, the Google DNS server is used only when the default DNS server cannot be obtained.
* Added a new plugin "http2http" which allows forwarding HTTP requests to another HTTP server, supporting options like local address binding, host header rewrite, and custom request headers.
10 changes: 10 additions & 0 deletions conf/frpc_full_example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,16 @@ localAddr = "127.0.0.1:443"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"

[[proxies]]
name = "plugin_http2http"
type = "tcp"
remotePort = 6007
[proxies.plugin]
type = "http2http"
localAddr = "127.0.0.1:80"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"

[[proxies]]
name = "secret_tcp"
# If the type is secret tcp, remotePort is useless
Expand Down
10 changes: 10 additions & 0 deletions pkg/config/v1/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const (
PluginSocks5 = "socks5"
PluginStaticFile = "static_file"
PluginUnixDomainSocket = "unix_domain_socket"
PluginHTTP2HTTP = "http2http"
)

var clientPluginOptionsTypeMap = map[string]reflect.Type{
Expand All @@ -86,6 +87,7 @@ var clientPluginOptionsTypeMap = map[string]reflect.Type{
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
}

type HTTP2HTTPSPluginOptions struct {
Expand Down Expand Up @@ -137,3 +139,11 @@ type UnixDomainSocketPluginOptions struct {
Type string `json:"type,omitempty"`
UnixPath string `json:"unixPath,omitempty"`
}

// Added HTTP2HTTPPluginOptions struct
type HTTP2HTTPPluginOptions struct {
Type string `json:"type,omitempty"`
LocalAddr string `json:"localAddr,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
}
91 changes: 91 additions & 0 deletions pkg/plugin/client/http2http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2024 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package plugin

import (
"io"
stdlog "log"
"net"
"net/http"
"net/http/httputil"

"github.com/fatedier/golib/pool"

v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/util/log"
netpkg "github.com/fatedier/frp/pkg/util/net"
)

func init() {
Register(v1.PluginHTTP2HTTP, NewHTTP2HTTPPlugin)
}

type HTTP2HTTPPlugin struct {
opts *v1.HTTP2HTTPPluginOptions

l *Listener
s *http.Server
}

func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
opts := options.(*v1.HTTP2HTTPPluginOptions)

listener := NewProxyListener()

p := &HTTP2HTTPPlugin{
opts: opts,
l: listener,
}

rp := &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
req := r.Out
req.URL.Scheme = "http"
req.URL.Host = p.opts.LocalAddr
if p.opts.HostHeaderRewrite != "" {
req.Host = p.opts.HostHeaderRewrite
}
for k, v := range p.opts.RequestHeaders.Set {
req.Header.Set(k, v)
}
},
BufferPool: pool.NewBuffer(32 * 1024),
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
}

p.s = &http.Server{
Handler: rp,
ReadHeaderTimeout: 0,
}

go func() {
_ = p.s.Serve(listener)
}()

return p, nil
}

func (p *HTTP2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
_ = p.l.PutConn(wrapConn)
}

func (p *HTTP2HTTPPlugin) Name() string {
return v1.PluginHTTP2HTTP
}

func (p *HTTP2HTTPPlugin) Close() error {
return p.s.Close()
}
73 changes: 73 additions & 0 deletions test/e2e/v1/plugin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package plugin
import (
"crypto/tls"
"fmt"
"net/http"
"strconv"

"github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -329,4 +330,76 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
ExpectResp([]byte("test")).
Ensure()
})

ginkgo.Describe("http2http", func() {
ginkgo.It("host header rewrite", func() {
serverConf := consts.DefaultServerConfig

localPort := f.AllocPort()
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "http2http"
type = "tcp"
remotePort = %d
[proxies.plugin]
type = "http2http"
localAddr = "127.0.0.1:%d"
hostHeaderRewrite = "rewrite.test.com"
`, remotePort, localPort)

f.RunProcesses([]string{serverConf}, []string{clientConf})

localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, _ = w.Write([]byte(req.Host))
})),
)
f.RunServer("", localServer)

framework.NewRequestExpect(f).
Port(remotePort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("example.com")
}).
ExpectResp([]byte("rewrite.test.com")).
Ensure()
})

ginkgo.It("set request header", func() {
serverConf := consts.DefaultServerConfig

localPort := f.AllocPort()
remotePort := f.AllocPort()
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
[[proxies]]
name = "http2http"
type = "tcp"
remotePort = %d
[proxies.plugin]
type = "http2http"
localAddr = "127.0.0.1:%d"
requestHeaders.set.x-from-where = "frp"
`, remotePort, localPort)

f.RunProcesses([]string{serverConf}, []string{clientConf})

localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
_, _ = w.Write([]byte(req.Header.Get("x-from-where")))
})),
)
f.RunServer("", localServer)

framework.NewRequestExpect(f).
Port(remotePort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("example.com")
}).
ExpectResp([]byte("frp")).
Ensure()
})
})
})

0 comments on commit 939c490

Please sign in to comment.