diff --git a/common/protocol/grpc/metadata.go b/common/protocol/grpc/metadata.go new file mode 100644 index 00000000000..1991821fe56 --- /dev/null +++ b/common/protocol/grpc/metadata.go @@ -0,0 +1,23 @@ +package grpc + +import ( + "strings" + + "google.golang.org/grpc/metadata" + + "github.com/v2fly/v2ray-core/v4/common/net" +) + +// ParseXForwardedFor parses X-Forwarded-For metadata in gRPC metadata, and return the IP list in it. +func ParseXForwardedFor(md metadata.MD) []net.Address { + xff := md.Get("X-Forwarded-For") + if len(xff) == 0 { + return nil + } + list := strings.Split(xff[0], ",") + addrs := make([]net.Address, 0, len(list)) + for _, proxy := range list { + addrs = append(addrs, net.ParseAddress(proxy)) + } + return addrs +} diff --git a/common/protocol/grpc/metadata_test.go b/common/protocol/grpc/metadata_test.go new file mode 100644 index 00000000000..ee7f56e4f72 --- /dev/null +++ b/common/protocol/grpc/metadata_test.go @@ -0,0 +1,19 @@ +package grpc_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/grpc/metadata" + + "github.com/v2fly/v2ray-core/v4/common/net" + . "github.com/v2fly/v2ray-core/v4/common/protocol/grpc" +) + +func TestParseXForwardedFor(t *testing.T) { + md := metadata.Pairs("X-Forwarded-For", "129.78.138.66, 129.78.64.103") + addrs := ParseXForwardedFor(md) + if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" { + t.Error(r) + } +} diff --git a/transport/internet/grpc/encoding/conn.go b/transport/internet/grpc/encoding/conn.go index 9e84e815e59..171915b3d8e 100644 --- a/transport/internet/grpc/encoding/conn.go +++ b/transport/internet/grpc/encoding/conn.go @@ -9,7 +9,10 @@ import ( "net" "time" + "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" + + grpc_proto "github.com/v2fly/v2ray-core/v4/common/protocol/grpc" ) // GunService is the abstract interface of GunService_TunClient and GunService_TunServer @@ -108,6 +111,16 @@ func NewGunConn(service GunService, over context.CancelFunc) *GunConn { Port: 0, } } + if _, isServer := service.(*gunServiceTunServer); isServer { + if md, ok := metadata.FromIncomingContext(service.Context()); ok { + if forwardedAddrs := grpc_proto.ParseXForwardedFor(md); len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() { + conn.remote = &net.TCPAddr{ + IP: forwardedAddrs[0].IP(), + Port: int(0), + } + } + } + } return conn }