Ver Fonte

client plugin: added plugin tls2raw (#4341)

fatedier há 5 meses atrás
pai
commit
69cc422edf

+ 1 - 0
.golangci.yml

@@ -88,6 +88,7 @@ linters-settings:
     excludes:
     - G401
     - G402
+    - G404
     - G501
 
 issues:

+ 1 - 6
Release.md

@@ -1,8 +1,3 @@
 ### Features
 
-* 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.
-* Added `enableHTTP2` option to control whether to enable HTTP/2 in plugin https2http and https2https, default is true.
-
-### Changes
-
-* Plugin https2http & https2https: return 421 `Misdirected Request` if host not match sni.
+* Added a new plugin `tls2raw`: Enables TLS termination and forwarding of decrypted raw traffic to local service.

+ 1 - 1
client/proxy/proxy.go

@@ -192,7 +192,7 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
 	if pxy.proxyPlugin != nil {
 		// if plugin is set, let plugin handle connection first
 		xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
-		pxy.proxyPlugin.Handle(remote, workConn, &extraInfo)
+		pxy.proxyPlugin.Handle(pxy.ctx, remote, workConn, &extraInfo)
 		xl.Debugf("handle by plugin finished")
 		return
 	}

+ 10 - 0
conf/frpc_full_example.toml

@@ -325,6 +325,16 @@ localAddr = "127.0.0.1:80"
 hostHeaderRewrite = "127.0.0.1"
 requestHeaders.set.x-from-where = "frp"
 
+[[proxies]]
+name = "plugin_tls2raw"
+type = "https"
+remotePort = 6008
+[proxies.plugin]
+type = "tls2raw"
+localAddr = "127.0.0.1:80"
+crtPath = "./server.crt"
+keyPath = "./server.key"
+
 [[proxies]]
 name = "secret_tcp"
 # If the type is secret tcp, remotePort is useless

+ 11 - 0
pkg/config/v1/plugin.go

@@ -83,6 +83,7 @@ const (
 	PluginSocks5           = "socks5"
 	PluginStaticFile       = "static_file"
 	PluginUnixDomainSocket = "unix_domain_socket"
+	PluginTLS2Raw          = "tls2raw"
 )
 
 var clientPluginOptionsTypeMap = map[string]reflect.Type{
@@ -94,6 +95,7 @@ var clientPluginOptionsTypeMap = map[string]reflect.Type{
 	PluginSocks5:           reflect.TypeOf(Socks5PluginOptions{}),
 	PluginStaticFile:       reflect.TypeOf(StaticFilePluginOptions{}),
 	PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
+	PluginTLS2Raw:          reflect.TypeOf(TLS2RawPluginOptions{}),
 }
 
 type HTTP2HTTPSPluginOptions struct {
@@ -174,3 +176,12 @@ type UnixDomainSocketPluginOptions struct {
 }
 
 func (o *UnixDomainSocketPluginOptions) Complete() {}
+
+type TLS2RawPluginOptions struct {
+	Type      string `json:"type,omitempty"`
+	LocalAddr string `json:"localAddr,omitempty"`
+	CrtPath   string `json:"crtPath,omitempty"`
+	KeyPath   string `json:"keyPath,omitempty"`
+}
+
+func (o *TLS2RawPluginOptions) Complete() {}

+ 9 - 0
pkg/config/v1/validation/plugin.go

@@ -32,6 +32,8 @@ func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
 		return validateStaticFilePluginOptions(v)
 	case *v1.UnixDomainSocketPluginOptions:
 		return validateUnixDomainSocketPluginOptions(v)
+	case *v1.TLS2RawPluginOptions:
+		return validateTLS2RawPluginOptions(v)
 	}
 	return nil
 }
@@ -70,3 +72,10 @@ func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions)
 	}
 	return nil
 }
+
+func validateTLS2RawPluginOptions(c *v1.TLS2RawPluginOptions) error {
+	if c.LocalAddr == "" {
+		return errors.New("localAddr is required")
+	}
+	return nil
+}

+ 4 - 1
pkg/plugin/client/http2http.go

@@ -12,9 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//go:build !frps
+
 package plugin
 
 import (
+	"context"
 	"io"
 	stdlog "log"
 	"net"
@@ -77,7 +80,7 @@ func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 	return p, nil
 }
 
-func (p *HTTP2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+func (p *HTTP2HTTPPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
 	_ = p.l.PutConn(wrapConn)
 }

+ 2 - 1
pkg/plugin/client/http2https.go

@@ -17,6 +17,7 @@
 package plugin
 
 import (
+	"context"
 	"crypto/tls"
 	"io"
 	stdlog "log"
@@ -88,7 +89,7 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 	return p, nil
 }
 
-func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+func (p *HTTP2HTTPSPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
 	_ = p.l.PutConn(wrapConn)
 }

+ 2 - 1
pkg/plugin/client/http_proxy.go

@@ -18,6 +18,7 @@ package plugin
 
 import (
 	"bufio"
+	"context"
 	"encoding/base64"
 	"io"
 	"net"
@@ -68,7 +69,7 @@ func (hp *HTTPProxy) Name() string {
 	return v1.PluginHTTPProxy
 }
 
-func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+func (hp *HTTPProxy) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
 
 	sc, rd := libnet.NewSharedConn(wrapConn)

+ 3 - 21
pkg/plugin/client/https2http.go

@@ -17,6 +17,7 @@
 package plugin
 
 import (
+	"context"
 	"crypto/tls"
 	"fmt"
 	"io"
@@ -85,16 +86,7 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 		rp.ServeHTTP(w, r)
 	})
 
-	var (
-		tlsConfig *tls.Config
-		err       error
-	)
-	if opts.CrtPath != "" || opts.KeyPath != "" {
-		tlsConfig, err = p.genTLSConfig()
-	} else {
-		tlsConfig, err = transport.NewServerTLSConfig("", "", "")
-		tlsConfig.InsecureSkipVerify = true
-	}
+	tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
 	if err != nil {
 		return nil, fmt.Errorf("gen TLS config error: %v", err)
 	}
@@ -114,17 +106,7 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 	return p, nil
 }
 
-func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
-	cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
-	if err != nil {
-		return nil, err
-	}
-
-	config := &tls.Config{Certificates: []tls.Certificate{cert}}
-	return config, nil
-}
-
-func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
+func (p *HTTPS2HTTPPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
 	if extra.SrcAddr != nil {
 		wrapConn.SetRemoteAddr(extra.SrcAddr)

+ 3 - 21
pkg/plugin/client/https2https.go

@@ -17,6 +17,7 @@
 package plugin
 
 import (
+	"context"
 	"crypto/tls"
 	"fmt"
 	"io"
@@ -91,16 +92,7 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 		rp.ServeHTTP(w, r)
 	})
 
-	var (
-		tlsConfig *tls.Config
-		err       error
-	)
-	if opts.CrtPath != "" || opts.KeyPath != "" {
-		tlsConfig, err = p.genTLSConfig()
-	} else {
-		tlsConfig, err = transport.NewServerTLSConfig("", "", "")
-		tlsConfig.InsecureSkipVerify = true
-	}
+	tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
 	if err != nil {
 		return nil, fmt.Errorf("gen TLS config error: %v", err)
 	}
@@ -120,17 +112,7 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 	return p, nil
 }
 
-func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
-	cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
-	if err != nil {
-		return nil, err
-	}
-
-	config := &tls.Config{Certificates: []tls.Certificate{cert}}
-	return config, nil
-}
-
-func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
+func (p *HTTPS2HTTPSPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
 	if extra.SrcAddr != nil {
 		wrapConn.SetRemoteAddr(extra.SrcAddr)

+ 2 - 1
pkg/plugin/client/plugin.go

@@ -15,6 +15,7 @@
 package plugin
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"net"
@@ -57,7 +58,7 @@ type ExtraInfo struct {
 type Plugin interface {
 	Name() string
 
-	Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo)
+	Handle(ctx context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo)
 	Close() error
 }
 

+ 2 - 1
pkg/plugin/client/socks5.go

@@ -17,6 +17,7 @@
 package plugin
 
 import (
+	"context"
 	"io"
 	"log"
 	"net"
@@ -50,7 +51,7 @@ func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
 	return
 }
 
-func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+func (sp *Socks5Plugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
 	defer conn.Close()
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
 	_ = sp.Server.ServeConn(wrapConn)

+ 2 - 1
pkg/plugin/client/static_file.go

@@ -17,6 +17,7 @@
 package plugin
 
 import (
+	"context"
 	"io"
 	"net"
 	"net/http"
@@ -69,7 +70,7 @@ func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
 	return sp, nil
 }
 
-func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+func (sp *StaticFilePlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
 	_ = sp.l.PutConn(wrapConn)
 }

+ 83 - 0
pkg/plugin/client/tls2raw.go

@@ -0,0 +1,83 @@
+// 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.
+
+//go:build !frps
+
+package plugin
+
+import (
+	"context"
+	"crypto/tls"
+	"io"
+	"net"
+
+	libio "github.com/fatedier/golib/io"
+
+	v1 "github.com/fatedier/frp/pkg/config/v1"
+	"github.com/fatedier/frp/pkg/transport"
+	netpkg "github.com/fatedier/frp/pkg/util/net"
+	"github.com/fatedier/frp/pkg/util/xlog"
+)
+
+func init() {
+	Register(v1.PluginTLS2Raw, NewTLS2RawPlugin)
+}
+
+type TLS2RawPlugin struct {
+	opts *v1.TLS2RawPluginOptions
+
+	tlsConfig *tls.Config
+}
+
+func NewTLS2RawPlugin(options v1.ClientPluginOptions) (Plugin, error) {
+	opts := options.(*v1.TLS2RawPluginOptions)
+
+	p := &TLS2RawPlugin{
+		opts: opts,
+	}
+
+	tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
+	if err != nil {
+		return nil, err
+	}
+	p.tlsConfig = tlsConfig
+	return p, nil
+}
+
+func (p *TLS2RawPlugin) Handle(ctx context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+	xl := xlog.FromContextSafe(ctx)
+
+	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
+	tlsConn := tls.Server(wrapConn, p.tlsConfig)
+
+	if err := tlsConn.Handshake(); err != nil {
+		xl.Warnf("tls handshake error: %v", err)
+		return
+	}
+	rawConn, err := net.Dial("tcp", p.opts.LocalAddr)
+	if err != nil {
+		xl.Warnf("dial to local addr error: %v", err)
+		return
+	}
+
+	libio.Join(tlsConn, rawConn)
+}
+
+func (p *TLS2RawPlugin) Name() string {
+	return v1.PluginTLS2Raw
+}
+
+func (p *TLS2RawPlugin) Close() error {
+	return nil
+}

+ 5 - 1
pkg/plugin/client/unix_domain_socket.go

@@ -17,12 +17,14 @@
 package plugin
 
 import (
+	"context"
 	"io"
 	"net"
 
 	libio "github.com/fatedier/golib/io"
 
 	v1 "github.com/fatedier/frp/pkg/config/v1"
+	"github.com/fatedier/frp/pkg/util/xlog"
 )
 
 func init() {
@@ -48,9 +50,11 @@ func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err er
 	return
 }
 
-func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) {
+func (uds *UnixDomainSocketPlugin) Handle(ctx context.Context, conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) {
+	xl := xlog.FromContextSafe(ctx)
 	localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
 	if err != nil {
+		xl.Warnf("dial to uds %s error: %v", uds.UnixAddr, err)
 		return
 	}
 	if extra.ProxyProtocolHeader != nil {

+ 46 - 0
test/e2e/v1/plugin/client.go

@@ -402,4 +402,50 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
 				Ensure()
 		})
 	})
+
+	ginkgo.It("tls2raw", func() {
+		generator := &cert.SelfSignedCertGenerator{}
+		artifacts, err := generator.Generate("example.com")
+		framework.ExpectNoError(err)
+		crtPath := f.WriteTempFile("tls2raw_server.crt", string(artifacts.Cert))
+		keyPath := f.WriteTempFile("tls2raw_server.key", string(artifacts.Key))
+
+		serverConf := consts.DefaultServerConfig
+		vhostHTTPSPort := f.AllocPort()
+		serverConf += fmt.Sprintf(`
+		vhostHTTPSPort = %d
+		`, vhostHTTPSPort)
+
+		localPort := f.AllocPort()
+		clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
+		[[proxies]]
+		name = "tls2raw-test"
+		type = "https"
+		customDomains = ["example.com"]
+		[proxies.plugin]
+		type = "tls2raw"
+		localAddr = "127.0.0.1:%d"
+		crtPath = "%s"
+		keyPath = "%s"
+		`, localPort, crtPath, keyPath)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		localServer := httpserver.New(
+			httpserver.WithBindPort(localPort),
+			httpserver.WithResponse([]byte("test")),
+		)
+		f.RunServer("", localServer)
+
+		framework.NewRequestExpect(f).
+			Port(vhostHTTPSPort).
+			RequestModify(func(r *request.Request) {
+				r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
+					ServerName:         "example.com",
+					InsecureSkipVerify: true,
+				})
+			}).
+			ExpectResp([]byte("test")).
+			Ensure()
+	})
 })