Explorar o código

fix x-forwarded-for header (#4111)

fatedier hai 10 meses
pai
achega
590ccda677

+ 6 - 0
Release.md

@@ -1 +1,7 @@
+### Features
 
+* `https2http` and `https2https` plugin now supports `X-Forwared-For` header.
+
+### Fixes
+
+* `X-Forwared-For` header is now correctly set in the request to the backend server for proxy type http.

+ 28 - 26
client/proxy/proxy.go

@@ -158,33 +158,35 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
 
 	// check if we need to send proxy protocol info
 	var extraInfo plugin.ExtraInfo
-	if baseCfg.Transport.ProxyProtocolVersion != "" {
-		if m.SrcAddr != "" && m.SrcPort != 0 {
-			if m.DstAddr == "" {
-				m.DstAddr = "127.0.0.1"
-			}
-			srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
-			dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
-			h := &pp.Header{
-				Command:         pp.PROXY,
-				SourceAddr:      srcAddr,
-				DestinationAddr: dstAddr,
-			}
-
-			if strings.Contains(m.SrcAddr, ".") {
-				h.TransportProtocol = pp.TCPv4
-			} else {
-				h.TransportProtocol = pp.TCPv6
-			}
-
-			if baseCfg.Transport.ProxyProtocolVersion == "v1" {
-				h.Version = 1
-			} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
-				h.Version = 2
-			}
-
-			extraInfo.ProxyProtocolHeader = h
+	if m.SrcAddr != "" && m.SrcPort != 0 {
+		if m.DstAddr == "" {
+			m.DstAddr = "127.0.0.1"
 		}
+		srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
+		dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
+		extraInfo.SrcAddr = srcAddr
+		extraInfo.DstAddr = dstAddr
+	}
+
+	if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
+		h := &pp.Header{
+			Command:         pp.PROXY,
+			SourceAddr:      extraInfo.SrcAddr,
+			DestinationAddr: extraInfo.DstAddr,
+		}
+
+		if strings.Contains(m.SrcAddr, ".") {
+			h.TransportProtocol = pp.TCPv4
+		} else {
+			h.TransportProtocol = pp.TCPv6
+		}
+
+		if baseCfg.Transport.ProxyProtocolVersion == "v1" {
+			h.Version = 1
+		} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
+			h.Version = 2
+		}
+		extraInfo.ProxyProtocolHeader = h
 	}
 
 	if pxy.proxyPlugin != nil {

+ 3 - 0
pkg/plugin/client/http2https.go

@@ -54,6 +54,9 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 
 	rp := &httputil.ReverseProxy{
 		Rewrite: func(r *httputil.ProxyRequest) {
+			r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
+			r.Out.Header["X-Forwarded-Host"] = r.In.Header["X-Forwarded-Host"]
+			r.Out.Header["X-Forwarded-Proto"] = r.In.Header["X-Forwarded-Proto"]
 			req := r.Out
 			req.URL.Scheme = "https"
 			req.URL.Host = p.opts.LocalAddr

+ 6 - 1
pkg/plugin/client/https2http.go

@@ -51,6 +51,8 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 
 	rp := &httputil.ReverseProxy{
 		Rewrite: func(r *httputil.ProxyRequest) {
+			r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
+			r.SetXForwarded()
 			req := r.Out
 			req.URL.Scheme = "http"
 			req.URL.Host = p.opts.LocalAddr
@@ -98,8 +100,11 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
 	return config, nil
 }
 
-func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
+	if extra.SrcAddr != nil {
+		wrapConn.SetRemoteAddr(extra.SrcAddr)
+	}
 	_ = p.l.PutConn(wrapConn)
 }
 

+ 6 - 1
pkg/plugin/client/https2https.go

@@ -56,6 +56,8 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
 
 	rp := &httputil.ReverseProxy{
 		Rewrite: func(r *httputil.ProxyRequest) {
+			r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
+			r.SetXForwarded()
 			req := r.Out
 			req.URL.Scheme = "https"
 			req.URL.Host = p.opts.LocalAddr
@@ -104,8 +106,11 @@ func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
 	return config, nil
 }
 
-func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
+func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
 	wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
+	if extra.SrcAddr != nil {
+		wrapConn.SetRemoteAddr(extra.SrcAddr)
+	}
 	_ = p.l.PutConn(wrapConn)
 }
 

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

@@ -50,6 +50,8 @@ func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) {
 
 type ExtraInfo struct {
 	ProxyProtocolHeader *pp.Header
+	SrcAddr             net.Addr
+	DstAddr             net.Addr
 }
 
 type Plugin interface {

+ 10 - 1
pkg/util/net/conn.go

@@ -76,9 +76,11 @@ type WrapReadWriteCloserConn struct {
 	io.ReadWriteCloser
 
 	underConn net.Conn
+
+	remoteAddr net.Addr
 }
 
-func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) net.Conn {
+func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) *WrapReadWriteCloserConn {
 	return &WrapReadWriteCloserConn{
 		ReadWriteCloser: rwc,
 		underConn:       underConn,
@@ -92,7 +94,14 @@ func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr {
 	return (*net.TCPAddr)(nil)
 }
 
+func (conn *WrapReadWriteCloserConn) SetRemoteAddr(addr net.Addr) {
+	conn.remoteAddr = addr
+}
+
 func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr {
+	if conn.remoteAddr != nil {
+		return conn.remoteAddr
+	}
 	if conn.underConn != nil {
 		return conn.underConn.RemoteAddr()
 	}

+ 1 - 0
pkg/util/vhost/http.go

@@ -59,6 +59,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
 	proxy := &httputil.ReverseProxy{
 		// Modify incoming requests by route policies.
 		Rewrite: func(r *httputil.ProxyRequest) {
+			r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
 			r.SetXForwarded()
 			req := r.Out
 			req.URL.Scheme = "http"

+ 1 - 1
test/e2e/framework/request.go

@@ -113,7 +113,7 @@ func (e *RequestExpect) Ensure(fns ...EnsureFunc) {
 		if !bytes.Equal(e.expectResp, ret.Content) {
 			flog.Tracef("Response info: %+v", ret)
 		}
-		ExpectEqualValuesWithOffset(1, ret.Content, e.expectResp, e.explain...)
+		ExpectEqualValuesWithOffset(1, string(ret.Content), string(e.expectResp), e.explain...)
 	} else {
 		for _, fn := range fns {
 			ok := fn(ret)

+ 141 - 21
test/e2e/v1/features/real_ip.go

@@ -2,6 +2,7 @@ package features
 
 import (
 	"bufio"
+	"crypto/tls"
 	"fmt"
 	"net"
 	"net/http"
@@ -9,6 +10,7 @@ import (
 	"github.com/onsi/ginkgo/v2"
 	pp "github.com/pires/go-proxyproto"
 
+	"github.com/fatedier/frp/pkg/transport"
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
@@ -21,23 +23,24 @@ import (
 var _ = ginkgo.Describe("[Feature: Real IP]", func() {
 	f := framework.NewDefaultFramework()
 
-	ginkgo.It("HTTP X-Forwarded-For", func() {
-		vhostHTTPPort := f.AllocPort()
-		serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
+	ginkgo.Describe("HTTP X-forwarded-For", func() {
+		ginkgo.It("Client Without Header", func() {
+			vhostHTTPPort := f.AllocPort()
+			serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
 		vhostHTTPPort = %d
 		`, vhostHTTPPort)
 
-		localPort := f.AllocPort()
-		localServer := httpserver.New(
-			httpserver.WithBindPort(localPort),
-			httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-				_, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For")))
-			})),
-		)
-		f.RunServer("", localServer)
-
-		clientConf := consts.DefaultClientConfig
-		clientConf += fmt.Sprintf(`
+			localPort := f.AllocPort()
+			localServer := httpserver.New(
+				httpserver.WithBindPort(localPort),
+				httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+					_, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For")))
+				})),
+			)
+			f.RunServer("", localServer)
+
+			clientConf := consts.DefaultClientConfig
+			clientConf += fmt.Sprintf(`
 		[[proxies]]
 		name = "test"
 		type = "http"
@@ -45,14 +48,131 @@ var _ = ginkgo.Describe("[Feature: Real IP]", func() {
 		customDomains = ["normal.example.com"]
 		`, localPort)
 
-		f.RunProcesses([]string{serverConf}, []string{clientConf})
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
 
-		framework.NewRequestExpect(f).Port(vhostHTTPPort).
-			RequestModify(func(r *request.Request) {
-				r.HTTP().HTTPHost("normal.example.com")
-			}).
-			ExpectResp([]byte("127.0.0.1")).
-			Ensure()
+			framework.NewRequestExpect(f).Port(vhostHTTPPort).
+				RequestModify(func(r *request.Request) {
+					r.HTTP().HTTPHost("normal.example.com")
+				}).
+				ExpectResp([]byte("127.0.0.1")).
+				Ensure()
+		})
+
+		ginkgo.It("Client With Header", func() {
+			vhostHTTPPort := f.AllocPort()
+			serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
+		vhostHTTPPort = %d
+		`, vhostHTTPPort)
+
+			localPort := f.AllocPort()
+			localServer := httpserver.New(
+				httpserver.WithBindPort(localPort),
+				httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+					_, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For")))
+				})),
+			)
+			f.RunServer("", localServer)
+
+			clientConf := consts.DefaultClientConfig
+			clientConf += fmt.Sprintf(`
+		[[proxies]]
+		name = "test"
+		type = "http"
+		localPort = %d
+		customDomains = ["normal.example.com"]
+		`, localPort)
+
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+			framework.NewRequestExpect(f).Port(vhostHTTPPort).
+				RequestModify(func(r *request.Request) {
+					r.HTTP().HTTPHost("normal.example.com")
+					r.HTTP().HTTPHeaders(map[string]string{"x-forwarded-for": "2.2.2.2"})
+				}).
+				ExpectResp([]byte("2.2.2.2, 127.0.0.1")).
+				Ensure()
+		})
+
+		ginkgo.It("http2https plugin", func() {
+			vhostHTTPPort := f.AllocPort()
+			serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
+		vhostHTTPPort = %d
+		`, vhostHTTPPort)
+
+			localPort := f.AllocPort()
+
+			clientConf := consts.DefaultClientConfig
+			clientConf += fmt.Sprintf(`
+		[[proxies]]
+		name = "test"
+		type = "http"
+		customDomains = ["normal.example.com"]
+		[proxies.plugin]
+		type = "http2https"
+		localAddr = "127.0.0.1:%d"
+		`, localPort)
+
+			f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+			tlsConfig, err := transport.NewServerTLSConfig("", "", "")
+			framework.ExpectNoError(err)
+
+			localServer := httpserver.New(
+				httpserver.WithBindPort(localPort),
+				httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+					_, _ = w.Write([]byte(req.Header.Get("X-Forwarded-For")))
+				})),
+				httpserver.WithTLSConfig(tlsConfig),
+			)
+			f.RunServer("", localServer)
+
+			framework.NewRequestExpect(f).Port(vhostHTTPPort).
+				RequestModify(func(r *request.Request) {
+					r.HTTP().HTTPHost("normal.example.com")
+					r.HTTP().HTTPHeaders(map[string]string{"x-forwarded-for": "2.2.2.2, 3.3.3.3"})
+				}).
+				ExpectResp([]byte("2.2.2.2, 3.3.3.3, 127.0.0.1")).
+				Ensure()
+		})
+
+		ginkgo.It("https2http plugin", func() {
+			vhostHTTPSPort := f.AllocPort()
+			serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
+		vhostHTTPSPort = %d
+		`, vhostHTTPSPort)
+
+			localPort := f.AllocPort()
+
+			clientConf := consts.DefaultClientConfig
+			clientConf += fmt.Sprintf(`
+		[[proxies]]
+		name = "test"
+		type = "https"
+		customDomains = ["normal.example.com"]
+		[proxies.plugin]
+		type = "https2http"
+		localAddr = "127.0.0.1:%d"
+		`, 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-Forwarded-For")))
+				})),
+			)
+			f.RunServer("", localServer)
+
+			framework.NewRequestExpect(f).Port(vhostHTTPSPort).
+				RequestModify(func(r *request.Request) {
+					r.HTTPS().HTTPHost("normal.example.com").
+						HTTPHeaders(map[string]string{"x-forwarded-for": "2.2.2.2"}).
+						TLSConfig(&tls.Config{ServerName: "normal.example.com", InsecureSkipVerify: true})
+				}).
+				ExpectResp([]byte("2.2.2.2, 127.0.0.1")).
+				Ensure()
+		})
 	})
 
 	ginkgo.Describe("Proxy Protocol", func() {