Skip to content

Commit

Permalink
fix x-forwarded-for header (#4111)
Browse files Browse the repository at this point in the history
  • Loading branch information
fatedier authored Mar 28, 2024
1 parent 86f90f4 commit 590ccda
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 51 deletions.
6 changes: 6 additions & 0 deletions Release.md
Original file line number Diff line number Diff line change
@@ -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.
54 changes: 28 additions & 26 deletions client/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions pkg/plugin/client/http2https.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion pkg/plugin/client/https2http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down
7 changes: 6 additions & 1 deletion pkg/plugin/client/https2https.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/plugin/client/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 10 additions & 1 deletion pkg/util/net/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
}
Expand Down
1 change: 1 addition & 0 deletions pkg/util/vhost/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/framework/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
162 changes: 141 additions & 21 deletions test/e2e/v1/features/real_ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package features

import (
"bufio"
"crypto/tls"
"fmt"
"net"
"net/http"

"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"
Expand All @@ -21,38 +23,156 @@ 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"
localPort = %d
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() {
Expand Down

0 comments on commit 590ccda

Please sign in to comment.