diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 9ffbbfd1d..4883f30ab 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -62,7 +62,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne if err != nil { return nil, errors.New("Cannot dial gRPC").Base(err) } - return encoding.NewMultiHunkConn(grpcService, nil), nil + return encoding.NewMultiHunkConn(grpcService, nil, nil), nil } errors.LogDebug(ctx, "using gRPC tun mode service name: `"+grpcSettings.getServiceName()+"` stream name: `"+grpcSettings.getTunStreamName()+"`") @@ -71,7 +71,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne return nil, errors.New("Cannot dial gRPC").Base(err) } - return encoding.NewHunkConn(grpcService, nil), nil + return encoding.NewHunkConn(grpcService, nil, nil), nil } func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, error) { diff --git a/transport/internet/grpc/encoding/hunkconn.go b/transport/internet/grpc/encoding/hunkconn.go index f1155de8a..92769065e 100644 --- a/transport/internet/grpc/encoding/hunkconn.go +++ b/transport/internet/grpc/encoding/hunkconn.go @@ -9,8 +9,6 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/signal/done" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/peer" ) type HunkConn interface { @@ -38,31 +36,8 @@ func NewHunkReadWriter(hc HunkConn, cancel context.CancelFunc) *HunkReaderWriter return &HunkReaderWriter{hc, cancel, done.New(), nil, 0} } -func NewHunkConn(hc HunkConn, cancel context.CancelFunc) net.Conn { - var rAddr net.Addr - pr, ok := peer.FromContext(hc.Context()) - if ok { - rAddr = pr.Addr - } else { - rAddr = &net.TCPAddr{ - IP: []byte{0, 0, 0, 0}, - Port: 0, - } - } - - md, ok := metadata.FromIncomingContext(hc.Context()) - if ok { - header := md.Get("x-real-ip") - if len(header) > 0 { - realip := net.ParseAddress(header[0]) - if realip.Family().IsIP() { - rAddr = &net.TCPAddr{ - IP: realip.IP(), - Port: 0, - } - } - } - } +func NewHunkConn(hc HunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn { + rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor) wrc := NewHunkReadWriter(hc, cancel) return cnc.NewConnection( cnc.ConnectionInput(wrc), diff --git a/transport/internet/grpc/encoding/multiconn.go b/transport/internet/grpc/encoding/multiconn.go index fa95cba8c..f27a1c35e 100644 --- a/transport/internet/grpc/encoding/multiconn.go +++ b/transport/internet/grpc/encoding/multiconn.go @@ -3,15 +3,12 @@ package encoding import ( "context" "io" - "net" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/errors" - xnet "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net/cnc" "github.com/xtls/xray-core/common/signal/done" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/peer" ) type MultiHunkConn interface { @@ -34,31 +31,8 @@ func NewMultiHunkReadWriter(hc MultiHunkConn, cancel context.CancelFunc) *MultiH return &MultiHunkReaderWriter{hc, cancel, done.New(), nil} } -func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc) net.Conn { - var rAddr net.Addr - pr, ok := peer.FromContext(hc.Context()) - if ok { - rAddr = pr.Addr - } else { - rAddr = &net.TCPAddr{ - IP: []byte{0, 0, 0, 0}, - Port: 0, - } - } - - md, ok := metadata.FromIncomingContext(hc.Context()) - if ok { - header := md.Get("x-real-ip") - if len(header) > 0 { - realip := xnet.ParseAddress(header[0]) - if realip.Family().IsIP() { - rAddr = &net.TCPAddr{ - IP: realip.IP(), - Port: 0, - } - } - } - } +func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn { + rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor) wrc := NewMultiHunkReadWriter(hc, cancel) return cnc.NewConnection( cnc.ConnectionInputMulti(wrc), diff --git a/transport/internet/grpc/encoding/remoteaddr.go b/transport/internet/grpc/encoding/remoteaddr.go new file mode 100644 index 000000000..adb5ac6e6 --- /dev/null +++ b/transport/internet/grpc/encoding/remoteaddr.go @@ -0,0 +1,70 @@ +package encoding + +import ( + "context" + "strings" + + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/net" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" +) + +func remoteAddrFromContext(ctx context.Context, trusted []string) net.Addr { + var remoteAddr net.Addr + if pr, ok := peer.FromContext(ctx); ok { + remoteAddr = pr.Addr + } else { + remoteAddr = &net.TCPAddr{ + IP: []byte{0, 0, 0, 0}, + Port: 0, + } + } + + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return remoteAddr + } + + key := "X-Real-IP" + values := md.Get(key) + if len(values) == 0 || values[0] == "" { + key = "X-Forwarded-For" + values = md.Get(key) + } + if len(values) == 0 || values[0] == "" { + return remoteAddr + } + + isTrusted := false + for _, t := range trusted { + if len(md.Get(t)) > 0 { + isTrusted = true + break + } + } + if !isTrusted { + if len(trusted) == 0 { + errors.LogWarning(context.Background(), `received "`, key, `" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`) + } else { + errors.LogError(context.Background(), `ignored potentially forged "`, key, `" from `, remoteAddr, `: `, values[0]) + } + return remoteAddr + } + + forwardedIP := values[0] + if key == "X-Forwarded-For" { + if idx := strings.IndexByte(forwardedIP, ','); idx >= 0 { + forwardedIP = forwardedIP[:idx] + } + } + + forwardedAddr := net.ParseAddress(forwardedIP) + if forwardedAddr.Family().IsIP() { + remoteAddr = &net.TCPAddr{ + IP: forwardedAddr.IP(), + Port: 0, + } + } + return remoteAddr +} diff --git a/transport/internet/grpc/encoding/remoteaddr_test.go b/transport/internet/grpc/encoding/remoteaddr_test.go new file mode 100644 index 000000000..b89d84feb --- /dev/null +++ b/transport/internet/grpc/encoding/remoteaddr_test.go @@ -0,0 +1,76 @@ +package encoding + +import ( + "context" + "net" + "testing" + + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" +) + +func TestRemoteAddrFromContext(t *testing.T) { + tests := []struct { + name string + metadata metadata.MD + trustedXForwardedFor []string + expectedRemoteAddress string + }{ + { + name: "ignore X-Real-IP without trusted header", + metadata: metadata.Pairs("X-Real-IP", "1.1.1.1"), + expectedRemoteAddress: "127.0.0.1:12345", + }, + { + name: "trust X-Real-IP when configured", + metadata: metadata.Pairs("X-Real-IP", "1.1.1.1"), + trustedXForwardedFor: []string{"X-Real-IP"}, + expectedRemoteAddress: "1.1.1.1:0", + }, + { + name: "trust X-Forwarded-For when configured", + metadata: metadata.Pairs("X-Forwarded-For", "2.2.2.2, 3.3.3.3"), + trustedXForwardedFor: []string{"X-Forwarded-For"}, + expectedRemoteAddress: "2.2.2.2:0", + }, + { + name: "trust X-Forwarded-For with trusted marker", + metadata: metadata.Pairs("X-Forwarded-For", "4.4.4.4", "X-Trusted-CDN", "1"), + trustedXForwardedFor: []string{"X-Trusted-CDN"}, + expectedRemoteAddress: "4.4.4.4:0", + }, + { + name: "ignore X-Forwarded-For without trusted marker", + metadata: metadata.Pairs("X-Forwarded-For", "5.5.5.5"), + trustedXForwardedFor: []string{"X-Trusted-CDN"}, + expectedRemoteAddress: "127.0.0.1:12345", + }, + { + name: "prefer X-Real-IP over X-Forwarded-For", + metadata: metadata.Pairs("X-Real-IP", "6.6.6.6", "X-Forwarded-For", "7.7.7.7", "X-Trusted-CDN", "1"), + trustedXForwardedFor: []string{"X-Trusted-CDN"}, + expectedRemoteAddress: "6.6.6.6:0", + }, + { + name: "do not parse X-Real-IP as a list", + metadata: metadata.Pairs("X-Real-IP", "8.8.8.8, 9.9.9.9", "X-Trusted-CDN", "1"), + trustedXForwardedFor: []string{"X-Trusted-CDN"}, + expectedRemoteAddress: "127.0.0.1:12345", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := peer.NewContext(metadata.NewIncomingContext(context.Background(), test.metadata), &peer.Peer{ + Addr: &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 12345, + }, + }) + remoteAddr := remoteAddrFromContext(ctx, test.trustedXForwardedFor) + if remoteAddr.String() != test.expectedRemoteAddress { + t.Fatalf("unexpected remote address: %s", remoteAddr.String()) + } + }) + } +} diff --git a/transport/internet/grpc/hub.go b/transport/internet/grpc/hub.go index 34f8f1c0e..91bd2ab60 100644 --- a/transport/internet/grpc/hub.go +++ b/transport/internet/grpc/hub.go @@ -19,24 +19,25 @@ import ( type Listener struct { encoding.UnimplementedGRPCServiceServer - ctx context.Context - handler internet.ConnHandler - local net.Addr - config *Config + ctx context.Context + handler internet.ConnHandler + local net.Addr + config *Config + trustedXForwardedFor []string s *grpc.Server } func (l Listener) Tun(server encoding.GRPCService_TunServer) error { tunCtx, cancel := context.WithCancel(l.ctx) - l.handler(encoding.NewHunkConn(server, cancel)) + l.handler(encoding.NewHunkConn(server, cancel, l.trustedXForwardedFor)) <-tunCtx.Done() return nil } func (l Listener) TunMulti(server encoding.GRPCService_TunMultiServer) error { tunCtx, cancel := context.WithCancel(l.ctx) - l.handler(encoding.NewMultiHunkConn(server, cancel)) + l.handler(encoding.NewMultiHunkConn(server, cancel, l.trustedXForwardedFor)) <-tunCtx.Done() return nil } @@ -74,6 +75,9 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i } listener.ctx = ctx + if settings.SocketSettings != nil { + listener.trustedXForwardedFor = settings.SocketSettings.TrustedXForwardedFor + } config := tls.ConfigFromStreamSettings(settings)