mirror of
https://github.com/fatedier/frp.git
synced 2025-06-17 00:38:22 +00:00
Compare commits
3 Commits
b3f1ac0e21
...
7e6f36f2d6
Author | SHA1 | Date | |
---|---|---|---|
|
7e6f36f2d6 | ||
|
939c490768 | ||
|
9568151860 |
10
Release.md
10
Release.md
@ -1,9 +1,3 @@
|
|||||||
### Fixes
|
### Features
|
||||||
|
|
||||||
* Fixed an issue where HTTP/2 was not enabled for https2http and https2https plugins.
|
* 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.
|
||||||
* 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.
|
|
||||||
|
@ -30,6 +30,9 @@ import (
|
|||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
"github.com/radovskyb/watcher"
|
||||||
|
"github.com/fatedier/frp/pkg/config"
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
@ -156,6 +159,11 @@ func NewService(options ServiceOptions) (*Service, error) {
|
|||||||
if webServer != nil {
|
if webServer != nil {
|
||||||
webServer.RouteRegister(s.registerRouteHandlers)
|
webServer.RouteRegister(s.registerRouteHandlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.Common.ConfigAutoReload {
|
||||||
|
go s.MonitorClientConfig(options.ConfigFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,3 +416,63 @@ type statusExporterImpl struct {
|
|||||||
func (s *statusExporterImpl) GetProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
func (s *statusExporterImpl) GetProxyStatus(name string) (*proxy.WorkingStatus, bool) {
|
||||||
return s.getProxyStatusFunc(name)
|
return s.getProxyStatusFunc(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (s *Service) MonitorClientConfig(cfgFilePath string) {
|
||||||
|
cliCfg, _, _, _, err := config.LoadClientConfig(cfgFilePath, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cliCfg.ConfigAutoReload {
|
||||||
|
log.Infof("auto reload on config change is disabled.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w := watcher.New()
|
||||||
|
w.SetMaxEvents(1)
|
||||||
|
w.FilterOps(watcher.Write)
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-w.Event:
|
||||||
|
log.Infof("config file changed: %s", event.Path)
|
||||||
|
cliCfg, pxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(cfgFilePath, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("reload frpc proxy config error: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := validation.ValidateAllClientConfig(cliCfg, pxyCfgs, visitorCfgs); err != nil {
|
||||||
|
log.Infof("reload frpc proxy config error: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.UpdateAllConfigurer(pxyCfgs, visitorCfgs); err != nil {
|
||||||
|
log.Infof("reload frpc proxy config error: %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Infof("success reload conf")
|
||||||
|
case err := <-w.Error:
|
||||||
|
log.Infof("error: %s", err.Error())
|
||||||
|
case <-w.Closed:
|
||||||
|
done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := w.Add(cfgFilePath); err != nil {
|
||||||
|
log.Infof("error adding file to watcher: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := w.Start(time.Second); err != nil {
|
||||||
|
log.Infof("error starting watcher: %s", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-done
|
||||||
|
}
|
@ -12,6 +12,9 @@ serverPort = 7000
|
|||||||
# STUN server to help penetrate NAT hole.
|
# STUN server to help penetrate NAT hole.
|
||||||
# natHoleStunServer = "stun.easyvoip.com:3478"
|
# natHoleStunServer = "stun.easyvoip.com:3478"
|
||||||
|
|
||||||
|
# if true, autoreload is enabled
|
||||||
|
configAutoReload = true
|
||||||
|
|
||||||
# Decide if exit program when first login failed, otherwise continuous relogin to frps
|
# Decide if exit program when first login failed, otherwise continuous relogin to frps
|
||||||
# default is true
|
# default is true
|
||||||
loginFailExit = true
|
loginFailExit = true
|
||||||
@ -315,6 +318,16 @@ localAddr = "127.0.0.1:443"
|
|||||||
hostHeaderRewrite = "127.0.0.1"
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
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]]
|
[[proxies]]
|
||||||
name = "secret_tcp"
|
name = "secret_tcp"
|
||||||
# If the type is secret tcp, remotePort is useless
|
# If the type is secret tcp, remotePort is useless
|
||||||
|
1
go.mod
1
go.mod
@ -59,6 +59,7 @@ require (
|
|||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
github.com/radovskyb/watcher v1.0.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/templexxx/cpu v0.1.0 // indirect
|
github.com/templexxx/cpu v0.1.0 // indirect
|
||||||
github.com/templexxx/xorsimd v0.4.2 // indirect
|
github.com/templexxx/xorsimd v0.4.2 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -112,6 +112,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
|
|||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||||
|
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||||
|
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||||
|
@ -46,6 +46,9 @@ type ClientCommonConfig struct {
|
|||||||
ServerPort int `json:"serverPort,omitempty"`
|
ServerPort int `json:"serverPort,omitempty"`
|
||||||
// STUN server to help penetrate NAT hole.
|
// STUN server to help penetrate NAT hole.
|
||||||
NatHoleSTUNServer string `json:"natHoleStunServer,omitempty"`
|
NatHoleSTUNServer string `json:"natHoleStunServer,omitempty"`
|
||||||
|
|
||||||
|
//
|
||||||
|
ConfigAutoReload bool `json:"configAutoReload,omitempty"`
|
||||||
// DNSServer specifies a DNS server address for FRPC to use. If this value
|
// DNSServer specifies a DNS server address for FRPC to use. If this value
|
||||||
// is "", the default DNS will be used.
|
// is "", the default DNS will be used.
|
||||||
DNSServer string `json:"dnsServer,omitempty"`
|
DNSServer string `json:"dnsServer,omitempty"`
|
||||||
|
@ -76,6 +76,7 @@ const (
|
|||||||
PluginSocks5 = "socks5"
|
PluginSocks5 = "socks5"
|
||||||
PluginStaticFile = "static_file"
|
PluginStaticFile = "static_file"
|
||||||
PluginUnixDomainSocket = "unix_domain_socket"
|
PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
|
PluginHTTP2HTTP = "http2http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||||
@ -86,6 +87,7 @@ var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
|||||||
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
||||||
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
||||||
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
||||||
|
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTP2HTTPSPluginOptions struct {
|
type HTTP2HTTPSPluginOptions struct {
|
||||||
@ -137,3 +139,11 @@ type UnixDomainSocketPluginOptions struct {
|
|||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
UnixPath string `json:"unixPath,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
pkg/plugin/client/http2http.go
Normal file
91
pkg/plugin/client/http2http.go
Normal file
@ -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()
|
||||||
|
}
|
@ -3,6 +3,7 @@ package plugin
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
"github.com/onsi/ginkgo/v2"
|
||||||
@ -329,4 +330,76 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
|
|||||||
ExpectResp([]byte("test")).
|
ExpectResp([]byte("test")).
|
||||||
Ensure()
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user