Merge pull request #1064 from rodaine/set-authority

Add DialOption to overwrite :authority pseudo-header
diff --git a/benchmark/benchmark.go b/benchmark/benchmark.go
index d114327..fc3304a 100644
--- a/benchmark/benchmark.go
+++ b/benchmark/benchmark.go
@@ -47,7 +47,8 @@
 	"google.golang.org/grpc/grpclog"
 )
 
-func newPayload(t testpb.PayloadType, size int) *testpb.Payload {
+// Allows reuse of the same testpb.Payload object.
+func setPayload(p *testpb.Payload, t testpb.PayloadType, size int) {
 	if size < 0 {
 		grpclog.Fatalf("Requested a response with invalid length %d", size)
 	}
@@ -59,10 +60,15 @@
 	default:
 		grpclog.Fatalf("Unsupported payload type: %d", t)
 	}
-	return &testpb.Payload{
-		Type: t,
-		Body: body,
-	}
+	p.Type = t
+	p.Body = body
+	return
+}
+
+func newPayload(t testpb.PayloadType, size int) *testpb.Payload {
+	p := new(testpb.Payload)
+	setPayload(p, t, size)
+	return p
 }
 
 type testServer struct {
@@ -75,8 +81,13 @@
 }
 
 func (s *testServer) StreamingCall(stream testpb.BenchmarkService_StreamingCallServer) error {
+	response := &testpb.SimpleResponse{
+		Payload: new(testpb.Payload),
+	}
+	in := new(testpb.SimpleRequest)
 	for {
-		in, err := stream.Recv()
+		// use ServerStream directly to reuse the same testpb.SimpleRequest object
+		err := stream.(grpc.ServerStream).RecvMsg(in)
 		if err == io.EOF {
 			// read done.
 			return nil
@@ -84,9 +95,8 @@
 		if err != nil {
 			return err
 		}
-		if err := stream.Send(&testpb.SimpleResponse{
-			Payload: newPayload(in.ResponseType, int(in.ResponseSize)),
-		}); err != nil {
+		setPayload(response.Payload, in.ResponseType, int(in.ResponseSize))
+		if err := stream.Send(response); err != nil {
 			return err
 		}
 	}
diff --git a/call.go b/call.go
index ba17721..81b52be 100644
--- a/call.go
+++ b/call.go
@@ -42,6 +42,7 @@
 	"golang.org/x/net/context"
 	"golang.org/x/net/trace"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/stats"
 	"google.golang.org/grpc/transport"
 )
@@ -85,6 +86,9 @@
 		dopts.copts.StatsHandler.HandleRPC(ctx, inPayload)
 	}
 	c.trailerMD = stream.Trailer()
+	if peer, ok := peer.FromContext(stream.Context()); ok {
+		c.peer = peer
+	}
 	return nil
 }
 
diff --git a/interop/client/client.go b/interop/client/client.go
index 7961752..38bad3f 100644
--- a/interop/client/client.go
+++ b/interop/client/client.go
@@ -52,7 +52,7 @@
 	serviceAccountKeyFile = flag.String("service_account_key_file", "", "Path to service account json key file")
 	oauthScope            = flag.String("oauth_scope", "", "The scope for OAuth2 tokens")
 	defaultServiceAccount = flag.String("default_service_account", "", "Email of GCE default service account")
-	serverHost            = flag.String("server_host", "127.0.0.1", "The server host name")
+	serverHost            = flag.String("server_host", "localhost", "The server host name")
 	serverPort            = flag.Int("server_port", 10000, "The server port number")
 	tlsServerName         = flag.String("server_host_override", "", "The server name use to verify the hostname returned by TLS handshake if it is not empty. Otherwise, --server_host is used.")
 	testCase              = flag.String("test_case", "large_unary",
diff --git a/interop/http2/negative_http2_client.go b/interop/http2/negative_http2_client.go
new file mode 100644
index 0000000..9c09ad7
--- /dev/null
+++ b/interop/http2/negative_http2_client.go
@@ -0,0 +1,174 @@
+/*
+ *
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Client used to test http2 error edge cases like GOAWAYs and RST_STREAMs
+ *
+ * Documentation:
+ *	 https://github.com/grpc/grpc/blob/master/doc/negative-http2-interop-test-descriptions.md
+ */
+
+package main
+
+import (
+	"flag"
+	"net"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/golang/protobuf/proto"
+	"golang.org/x/net/context"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/grpclog"
+	"google.golang.org/grpc/interop"
+	testpb "google.golang.org/grpc/interop/grpc_testing"
+)
+
+var (
+	serverHost = flag.String("server_host", "127.0.0.1", "The server host name")
+	serverPort = flag.Int("server_port", 8080, "The server port number")
+	testCase   = flag.String("test_case", "goaway",
+		`Configure different test cases. Valid options are:
+        goaway : client sends two requests, the server will send a goaway in between;
+        rst_after_header : server will send rst_stream after it sends headers;
+        rst_during_data : server will send rst_stream while sending data;
+        rst_after_data : server will send rst_stream after sending data;
+        ping : server will send pings between each http2 frame;
+        max_streams : server will ensure that the max_concurrent_streams limit is upheld;`)
+	largeReqSize  = 271828
+	largeRespSize = 314159
+)
+
+func largeSimpleRequest() *testpb.SimpleRequest {
+	pl := interop.ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
+	return &testpb.SimpleRequest{
+		ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
+		ResponseSize: proto.Int32(int32(largeRespSize)),
+		Payload:      pl,
+	}
+}
+
+// sends two unary calls. The server asserts that the calls use different connections.
+func goaway(tc testpb.TestServiceClient) {
+	interop.DoLargeUnaryCall(tc)
+	// sleep to ensure that the client has time to recv the GOAWAY.
+	// TODO(ncteisen): make this less hacky.
+	time.Sleep(1 * time.Second)
+	interop.DoLargeUnaryCall(tc)
+}
+
+func rstAfterHeader(tc testpb.TestServiceClient) {
+	req := largeSimpleRequest()
+	reply, err := tc.UnaryCall(context.Background(), req)
+	if reply != nil {
+		grpclog.Fatalf("Client received reply despite server sending rst stream after header")
+	}
+	if grpc.Code(err) != codes.Internal {
+		grpclog.Fatalf("%v.UnaryCall() = _, %v, want _, %v", tc, grpc.Code(err), codes.Internal)
+	}
+}
+
+func rstDuringData(tc testpb.TestServiceClient) {
+	req := largeSimpleRequest()
+	reply, err := tc.UnaryCall(context.Background(), req)
+	if reply != nil {
+		grpclog.Fatalf("Client received reply despite server sending rst stream during data")
+	}
+	if grpc.Code(err) != codes.Unknown {
+		grpclog.Fatalf("%v.UnaryCall() = _, %v, want _, %v", tc, grpc.Code(err), codes.Unknown)
+	}
+}
+
+func rstAfterData(tc testpb.TestServiceClient) {
+	req := largeSimpleRequest()
+	reply, err := tc.UnaryCall(context.Background(), req)
+	if reply != nil {
+		grpclog.Fatalf("Client received reply despite server sending rst stream after data")
+	}
+	if grpc.Code(err) != codes.Internal {
+		grpclog.Fatalf("%v.UnaryCall() = _, %v, want _, %v", tc, grpc.Code(err), codes.Internal)
+	}
+}
+
+func ping(tc testpb.TestServiceClient) {
+	// The server will assert that every ping it sends was ACK-ed by the client.
+	interop.DoLargeUnaryCall(tc)
+}
+
+func maxStreams(tc testpb.TestServiceClient) {
+	interop.DoLargeUnaryCall(tc)
+	var wg sync.WaitGroup
+	for i := 0; i < 15; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			interop.DoLargeUnaryCall(tc)
+		}()
+	}
+	wg.Wait()
+}
+
+func main() {
+	flag.Parse()
+	serverAddr := net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
+	var opts []grpc.DialOption
+	opts = append(opts, grpc.WithInsecure())
+	conn, err := grpc.Dial(serverAddr, opts...)
+	if err != nil {
+		grpclog.Fatalf("Fail to dial: %v", err)
+	}
+	defer conn.Close()
+	tc := testpb.NewTestServiceClient(conn)
+	switch *testCase {
+	case "goaway":
+		goaway(tc)
+		grpclog.Println("goaway done")
+	case "rst_after_header":
+		rstAfterHeader(tc)
+		grpclog.Println("rst_after_header done")
+	case "rst_during_data":
+		rstDuringData(tc)
+		grpclog.Println("rst_during_data done")
+	case "rst_after_data":
+		rstAfterData(tc)
+		grpclog.Println("rst_after_data done")
+	case "ping":
+		ping(tc)
+		grpclog.Println("ping done")
+	case "max_streams":
+		maxStreams(tc)
+		grpclog.Println("max_streams done")
+	default:
+		grpclog.Fatal("Unsupported test case: ", *testCase)
+	}
+}
diff --git a/interop/test_utils.go b/interop/test_utils.go
index 0517874..e4e427c 100644
--- a/interop/test_utils.go
+++ b/interop/test_utils.go
@@ -60,7 +60,8 @@
 	trailingMetadataKey = "x-grpc-test-echo-trailing-bin"
 )
 
-func clientNewPayload(t testpb.PayloadType, size int) *testpb.Payload {
+// ClientNewPayload returns a payload of the given type and size.
+func ClientNewPayload(t testpb.PayloadType, size int) *testpb.Payload {
 	if size < 0 {
 		grpclog.Fatalf("Requested a response with invalid length %d", size)
 	}
@@ -91,7 +92,7 @@
 
 // DoLargeUnaryCall performs a unary RPC with large payload in the request and response.
 func DoLargeUnaryCall(tc testpb.TestServiceClient, args ...grpc.CallOption) {
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
 	req := &testpb.SimpleRequest{
 		ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseSize: proto.Int32(int32(largeRespSize)),
@@ -116,16 +117,14 @@
 	}
 	var sum int
 	for _, s := range reqSizes {
-		pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, s)
+		pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, s)
 		req := &testpb.StreamingInputCallRequest{
 			Payload: pl,
 		}
 		if err := stream.Send(req); err != nil {
-			grpclog.Fatalf("%v.Send(%v) = %v", stream, req, err)
+			grpclog.Fatalf("%v has error %v while sending %v", stream, err, req)
 		}
 		sum += s
-		grpclog.Printf("Sent a request of size %d, aggregated size %d", s, sum)
-
 	}
 	reply, err := stream.CloseAndRecv()
 	if err != nil {
@@ -173,7 +172,7 @@
 		respCnt++
 	}
 	if rpcStatus != io.EOF {
-		grpclog.Fatalf("Failed to finish the server streaming rpc: %v", err)
+		grpclog.Fatalf("Failed to finish the server streaming rpc: %v", rpcStatus)
 	}
 	if respCnt != len(respSizes) {
 		grpclog.Fatalf("Got %d reply, want %d", len(respSizes), respCnt)
@@ -193,14 +192,14 @@
 				Size: proto.Int32(int32(respSizes[index])),
 			},
 		}
-		pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, reqSizes[index])
+		pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, reqSizes[index])
 		req := &testpb.StreamingOutputCallRequest{
 			ResponseType:       testpb.PayloadType_COMPRESSABLE.Enum(),
 			ResponseParameters: respParam,
 			Payload:            pl,
 		}
 		if err := stream.Send(req); err != nil {
-			grpclog.Fatalf("%v.Send(%v) = %v", stream, req, err)
+			grpclog.Fatalf("%v has error %v while sending %v", stream, err, req)
 		}
 		reply, err := stream.Recv()
 		if err != nil {
@@ -249,7 +248,7 @@
 		}
 		grpclog.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
 	}
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, 27182)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, 27182)
 	req := &testpb.StreamingOutputCallRequest{
 		ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
 		Payload:      pl,
@@ -266,7 +265,7 @@
 
 // DoComputeEngineCreds performs a unary RPC with compute engine auth.
 func DoComputeEngineCreds(tc testpb.TestServiceClient, serviceAccount, oauthScope string) {
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
 	req := &testpb.SimpleRequest{
 		ResponseType:   testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseSize:   proto.Int32(int32(largeRespSize)),
@@ -298,7 +297,7 @@
 
 // DoServiceAccountCreds performs a unary RPC with service account auth.
 func DoServiceAccountCreds(tc testpb.TestServiceClient, serviceAccountKeyFile, oauthScope string) {
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
 	req := &testpb.SimpleRequest{
 		ResponseType:   testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseSize:   proto.Int32(int32(largeRespSize)),
@@ -323,7 +322,7 @@
 
 // DoJWTTokenCreds performs a unary RPC with JWT token auth.
 func DoJWTTokenCreds(tc testpb.TestServiceClient, serviceAccountKeyFile string) {
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
 	req := &testpb.SimpleRequest{
 		ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseSize: proto.Int32(int32(largeRespSize)),
@@ -357,7 +356,7 @@
 
 // DoOauth2TokenCreds performs a unary RPC with OAUTH2 token auth.
 func DoOauth2TokenCreds(tc testpb.TestServiceClient, serviceAccountKeyFile, oauthScope string) {
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
 	req := &testpb.SimpleRequest{
 		ResponseType:   testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseSize:   proto.Int32(int32(largeRespSize)),
@@ -383,7 +382,7 @@
 // DoPerRPCCreds performs a unary RPC with per RPC OAUTH2 token.
 func DoPerRPCCreds(tc testpb.TestServiceClient, serviceAccountKeyFile, oauthScope string) {
 	jsonKey := getServiceAccountJSONKey(serviceAccountKeyFile)
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize)
 	req := &testpb.SimpleRequest{
 		ResponseType:   testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseSize:   proto.Int32(int32(largeRespSize)),
@@ -441,14 +440,14 @@
 			Size: proto.Int32(31415),
 		},
 	}
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, 27182)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, 27182)
 	req := &testpb.StreamingOutputCallRequest{
 		ResponseType:       testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseParameters: respParam,
 		Payload:            pl,
 	}
 	if err := stream.Send(req); err != nil {
-		grpclog.Fatalf("%v.Send(%v) = %v", stream, req, err)
+		grpclog.Fatalf("%v has error %v while sending %v", stream, err, req)
 	}
 	if _, err := stream.Recv(); err != nil {
 		grpclog.Fatalf("%v.Recv() = %v", stream, err)
@@ -486,7 +485,7 @@
 // DoCustomMetadata checks that metadata is echoed back to the client.
 func DoCustomMetadata(tc testpb.TestServiceClient, args ...grpc.CallOption) {
 	// Testing with UnaryCall.
-	pl := clientNewPayload(testpb.PayloadType_COMPRESSABLE, 1)
+	pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, 1)
 	req := &testpb.SimpleRequest{
 		ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
 		ResponseSize: proto.Int32(int32(1)),
@@ -526,7 +525,7 @@
 		Payload:            pl,
 	}
 	if err := stream.Send(streamReq); err != nil {
-		grpclog.Fatalf("%v.Send(%v) = %v", stream, streamReq, err)
+		grpclog.Fatalf("%v has error %v while sending %v", stream, err, streamReq)
 	}
 	streamHeader, err := stream.Header()
 	if err != nil {
@@ -570,7 +569,7 @@
 		ResponseStatus: respStatus,
 	}
 	if err := stream.Send(streamReq); err != nil {
-		grpclog.Fatalf("%v.Send(%v) = %v, want <nil>", stream, streamReq, err)
+		grpclog.Fatalf("%v has error %v while sending %v, want <nil>", stream, err, streamReq)
 	}
 	if err := stream.CloseSend(); err != nil {
 		grpclog.Fatalf("%v.CloseSend() = %v, want <nil>", stream, err)
diff --git a/reflection/grpc_testing/proto2.pb.go b/reflection/grpc_testing/proto2.pb.go
index ac49de4..0b503d6 100644
--- a/reflection/grpc_testing/proto2.pb.go
+++ b/reflection/grpc_testing/proto2.pb.go
@@ -2,6 +2,22 @@
 // source: proto2.proto
 // DO NOT EDIT!
 
+/*
+Package grpc_testing is a generated protocol buffer package.
+
+It is generated from these files:
+	proto2.proto
+	proto2_ext.proto
+	proto2_ext2.proto
+	test.proto
+
+It has these top-level messages:
+	ToBeExtended
+	Extension
+	AnotherExtension
+	SearchResponse
+	SearchRequest
+*/
 package grpc_testing
 
 import proto "github.com/golang/protobuf/proto"
@@ -13,26 +29,32 @@
 var _ = fmt.Errorf
 var _ = math.Inf
 
-type ToBeExtened struct {
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type ToBeExtended struct {
 	Foo                          *int32 `protobuf:"varint,1,req,name=foo" json:"foo,omitempty"`
 	proto.XXX_InternalExtensions `json:"-"`
 	XXX_unrecognized             []byte `json:"-"`
 }
 
-func (m *ToBeExtened) Reset()                    { *m = ToBeExtened{} }
-func (m *ToBeExtened) String() string            { return proto.CompactTextString(m) }
-func (*ToBeExtened) ProtoMessage()               {}
-func (*ToBeExtened) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
+func (m *ToBeExtended) Reset()                    { *m = ToBeExtended{} }
+func (m *ToBeExtended) String() string            { return proto.CompactTextString(m) }
+func (*ToBeExtended) ProtoMessage()               {}
+func (*ToBeExtended) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
 
-var extRange_ToBeExtened = []proto.ExtensionRange{
-	{10, 20},
+var extRange_ToBeExtended = []proto.ExtensionRange{
+	{10, 30},
 }
 
-func (*ToBeExtened) ExtensionRangeArray() []proto.ExtensionRange {
-	return extRange_ToBeExtened
+func (*ToBeExtended) ExtensionRangeArray() []proto.ExtensionRange {
+	return extRange_ToBeExtended
 }
 
-func (m *ToBeExtened) GetFoo() int32 {
+func (m *ToBeExtended) GetFoo() int32 {
 	if m != nil && m.Foo != nil {
 		return *m.Foo
 	}
@@ -40,17 +62,17 @@
 }
 
 func init() {
-	proto.RegisterType((*ToBeExtened)(nil), "grpc.testing.ToBeExtened")
+	proto.RegisterType((*ToBeExtended)(nil), "grpc.testing.ToBeExtended")
 }
 
-func init() { proto.RegisterFile("proto2.proto", fileDescriptor1) }
+func init() { proto.RegisterFile("proto2.proto", fileDescriptor0) }
 
-var fileDescriptor1 = []byte{
-	// 85 bytes of a gzipped FileDescriptorProto
+var fileDescriptor0 = []byte{
+	// 86 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x28, 0xca, 0x2f,
 	0xc9, 0x37, 0xd2, 0x03, 0x53, 0x42, 0x3c, 0xe9, 0x45, 0x05, 0xc9, 0x7a, 0x25, 0xa9, 0xc5, 0x25,
-	0x99, 0x79, 0xe9, 0x4a, 0xaa, 0x5c, 0xdc, 0x21, 0xf9, 0x4e, 0xa9, 0xae, 0x15, 0x25, 0xa9, 0x79,
-	0xa9, 0x29, 0x42, 0x02, 0x5c, 0xcc, 0x69, 0xf9, 0xf9, 0x12, 0x8c, 0x0a, 0x4c, 0x1a, 0xac, 0x41,
-	0x20, 0xa6, 0x16, 0x0b, 0x07, 0x97, 0x80, 0x28, 0x20, 0x00, 0x00, 0xff, 0xff, 0xc9, 0xed, 0xbc,
-	0xc2, 0x43, 0x00, 0x00, 0x00,
+	0x99, 0x79, 0xe9, 0x4a, 0x6a, 0x5c, 0x3c, 0x21, 0xf9, 0x4e, 0xa9, 0xae, 0x15, 0x25, 0xa9, 0x79,
+	0x29, 0xa9, 0x29, 0x42, 0x02, 0x5c, 0xcc, 0x69, 0xf9, 0xf9, 0x12, 0x8c, 0x0a, 0x4c, 0x1a, 0xac,
+	0x41, 0x20, 0xa6, 0x16, 0x0b, 0x07, 0x97, 0x80, 0x3c, 0x20, 0x00, 0x00, 0xff, 0xff, 0x74, 0x86,
+	0x9c, 0x08, 0x44, 0x00, 0x00, 0x00,
 }
diff --git a/reflection/grpc_testing/proto2.proto b/reflection/grpc_testing/proto2.proto
index f79adc4..6b120f3 100644
--- a/reflection/grpc_testing/proto2.proto
+++ b/reflection/grpc_testing/proto2.proto
@@ -2,7 +2,7 @@
 
 package grpc.testing;
 
-message ToBeExtened {
+message ToBeExtended {
   required int32 foo = 1;
-  extensions 10 to 20;
+  extensions 10 to 30;
 }
diff --git a/reflection/grpc_testing/proto2_ext.pb.go b/reflection/grpc_testing/proto2_ext.pb.go
index 0120ca9..dbd0942 100644
--- a/reflection/grpc_testing/proto2_ext.pb.go
+++ b/reflection/grpc_testing/proto2_ext.pb.go
@@ -2,20 +2,6 @@
 // source: proto2_ext.proto
 // DO NOT EDIT!
 
-/*
-Package grpc_testing is a generated protocol buffer package.
-
-It is generated from these files:
-	proto2_ext.proto
-	proto2.proto
-	test.proto
-
-It has these top-level messages:
-	Extension
-	ToBeExtened
-	SearchResponse
-	SearchRequest
-*/
 package grpc_testing
 
 import proto "github.com/golang/protobuf/proto"
@@ -27,62 +13,71 @@
 var _ = fmt.Errorf
 var _ = math.Inf
 
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
-
 type Extension struct {
-	Baz              *int32 `protobuf:"varint,1,opt,name=baz" json:"baz,omitempty"`
+	Whatzit          *int32 `protobuf:"varint,1,opt,name=whatzit" json:"whatzit,omitempty"`
 	XXX_unrecognized []byte `json:"-"`
 }
 
 func (m *Extension) Reset()                    { *m = Extension{} }
 func (m *Extension) String() string            { return proto.CompactTextString(m) }
 func (*Extension) ProtoMessage()               {}
-func (*Extension) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+func (*Extension) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
 
-func (m *Extension) GetBaz() int32 {
-	if m != nil && m.Baz != nil {
-		return *m.Baz
+func (m *Extension) GetWhatzit() int32 {
+	if m != nil && m.Whatzit != nil {
+		return *m.Whatzit
 	}
 	return 0
 }
 
-var E_Bar = &proto.ExtensionDesc{
-	ExtendedType:  (*ToBeExtened)(nil),
+var E_Foo = &proto.ExtensionDesc{
+	ExtendedType:  (*ToBeExtended)(nil),
 	ExtensionType: (*int32)(nil),
 	Field:         13,
+	Name:          "grpc.testing.foo",
+	Tag:           "varint,13,opt,name=foo",
+	Filename:      "proto2_ext.proto",
+}
+
+var E_Bar = &proto.ExtensionDesc{
+	ExtendedType:  (*ToBeExtended)(nil),
+	ExtensionType: (*Extension)(nil),
+	Field:         17,
 	Name:          "grpc.testing.bar",
-	Tag:           "varint,13,opt,name=bar",
+	Tag:           "bytes,17,opt,name=bar",
+	Filename:      "proto2_ext.proto",
 }
 
 var E_Baz = &proto.ExtensionDesc{
-	ExtendedType:  (*ToBeExtened)(nil),
-	ExtensionType: (*Extension)(nil),
-	Field:         17,
+	ExtendedType:  (*ToBeExtended)(nil),
+	ExtensionType: (*SearchRequest)(nil),
+	Field:         19,
 	Name:          "grpc.testing.baz",
-	Tag:           "bytes,17,opt,name=baz",
+	Tag:           "bytes,19,opt,name=baz",
+	Filename:      "proto2_ext.proto",
 }
 
 func init() {
 	proto.RegisterType((*Extension)(nil), "grpc.testing.Extension")
+	proto.RegisterExtension(E_Foo)
 	proto.RegisterExtension(E_Bar)
 	proto.RegisterExtension(E_Baz)
 }
 
-func init() { proto.RegisterFile("proto2_ext.proto", fileDescriptor0) }
+func init() { proto.RegisterFile("proto2_ext.proto", fileDescriptor1) }
 
-var fileDescriptor0 = []byte{
-	// 130 bytes of a gzipped FileDescriptorProto
+var fileDescriptor1 = []byte{
+	// 179 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x28, 0x28, 0xca, 0x2f,
 	0xc9, 0x37, 0x8a, 0x4f, 0xad, 0x28, 0xd1, 0x03, 0x33, 0x85, 0x78, 0xd2, 0x8b, 0x0a, 0x92, 0xf5,
-	0x4a, 0x52, 0x8b, 0x4b, 0x32, 0xf3, 0xd2, 0xa5, 0x78, 0x20, 0xf2, 0x10, 0x39, 0x25, 0x59, 0x2e,
-	0x4e, 0xd7, 0x8a, 0x92, 0xd4, 0xbc, 0xe2, 0xcc, 0xfc, 0x3c, 0x21, 0x01, 0x2e, 0xe6, 0xa4, 0xc4,
-	0x2a, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xd6, 0x20, 0x10, 0xd3, 0x4a, 0x1b, 0x24, 0x52, 0x24, 0x24,
-	0xa9, 0x87, 0x6c, 0x84, 0x5e, 0x48, 0xbe, 0x53, 0x2a, 0x58, 0x57, 0x6a, 0x8a, 0x04, 0x2f, 0x4c,
-	0x71, 0x91, 0x95, 0x0b, 0x58, 0x3b, 0x3e, 0xc5, 0x82, 0x40, 0xc5, 0xdc, 0x46, 0xe2, 0xa8, 0x0a,
-	0xe0, 0xf6, 0x83, 0xad, 0x04, 0x04, 0x00, 0x00, 0xff, 0xff, 0x59, 0xfa, 0x16, 0xbc, 0xc0, 0x00,
-	0x00, 0x00,
+	0x4a, 0x52, 0x8b, 0x4b, 0x32, 0xf3, 0xd2, 0xa5, 0x78, 0x20, 0xf2, 0x10, 0x39, 0x29, 0x2e, 0x90,
+	0x30, 0x84, 0xad, 0xa4, 0xca, 0xc5, 0xe9, 0x5a, 0x51, 0x92, 0x9a, 0x57, 0x9c, 0x99, 0x9f, 0x27,
+	0x24, 0xc1, 0xc5, 0x5e, 0x9e, 0x91, 0x58, 0x52, 0x95, 0x59, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1,
+	0x1a, 0x04, 0xe3, 0x5a, 0xe9, 0x70, 0x31, 0xa7, 0xe5, 0xe7, 0x0b, 0x49, 0xe9, 0x21, 0x1b, 0xab,
+	0x17, 0x92, 0xef, 0x94, 0x0a, 0xd6, 0x9d, 0x92, 0x9a, 0x22, 0xc1, 0x0b, 0xd6, 0x01, 0x52, 0x66,
+	0xe5, 0xca, 0xc5, 0x9c, 0x94, 0x58, 0x84, 0x57, 0xb5, 0xa0, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0x38,
+	0xaa, 0x0a, 0xb8, 0x4b, 0x82, 0x40, 0xfa, 0xad, 0x3c, 0x41, 0xc6, 0x54, 0xe1, 0x35, 0x46, 0x18,
+	0x6c, 0x8c, 0x34, 0xaa, 0x8a, 0xe0, 0xd4, 0xc4, 0xa2, 0xe4, 0x8c, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4,
+	0xe2, 0x12, 0x90, 0x51, 0x55, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x71, 0x6b, 0x94, 0x9f, 0x21,
+	0x01, 0x00, 0x00,
 }
diff --git a/reflection/grpc_testing/proto2_ext.proto b/reflection/grpc_testing/proto2_ext.proto
index c2dd737..b669141 100644
--- a/reflection/grpc_testing/proto2_ext.proto
+++ b/reflection/grpc_testing/proto2_ext.proto
@@ -3,11 +3,14 @@
 package grpc.testing;
 
 import "proto2.proto";
-extend ToBeExtened {
-  optional int32 bar = 13;
-  optional Extension baz = 17;
+import "test.proto";
+
+extend ToBeExtended {
+  optional int32 foo = 13;
+  optional Extension bar = 17;
+  optional SearchRequest baz = 19;
 }
 
 message Extension {
-  optional int32 baz = 1;
+  optional int32 whatzit = 1;
 }
diff --git a/reflection/grpc_testing/proto2_ext2.pb.go b/reflection/grpc_testing/proto2_ext2.pb.go
new file mode 100644
index 0000000..0aaec7c
--- /dev/null
+++ b/reflection/grpc_testing/proto2_ext2.pb.go
@@ -0,0 +1,72 @@
+// Code generated by protoc-gen-go.
+// source: proto2_ext2.proto
+// DO NOT EDIT!
+
+package grpc_testing
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+type AnotherExtension struct {
+	Whatchamacallit  *int32 `protobuf:"varint,1,opt,name=whatchamacallit" json:"whatchamacallit,omitempty"`
+	XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *AnotherExtension) Reset()                    { *m = AnotherExtension{} }
+func (m *AnotherExtension) String() string            { return proto.CompactTextString(m) }
+func (*AnotherExtension) ProtoMessage()               {}
+func (*AnotherExtension) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} }
+
+func (m *AnotherExtension) GetWhatchamacallit() int32 {
+	if m != nil && m.Whatchamacallit != nil {
+		return *m.Whatchamacallit
+	}
+	return 0
+}
+
+var E_Frob = &proto.ExtensionDesc{
+	ExtendedType:  (*ToBeExtended)(nil),
+	ExtensionType: (*string)(nil),
+	Field:         23,
+	Name:          "grpc.testing.frob",
+	Tag:           "bytes,23,opt,name=frob",
+	Filename:      "proto2_ext2.proto",
+}
+
+var E_Nitz = &proto.ExtensionDesc{
+	ExtendedType:  (*ToBeExtended)(nil),
+	ExtensionType: (*AnotherExtension)(nil),
+	Field:         29,
+	Name:          "grpc.testing.nitz",
+	Tag:           "bytes,29,opt,name=nitz",
+	Filename:      "proto2_ext2.proto",
+}
+
+func init() {
+	proto.RegisterType((*AnotherExtension)(nil), "grpc.testing.AnotherExtension")
+	proto.RegisterExtension(E_Frob)
+	proto.RegisterExtension(E_Nitz)
+}
+
+func init() { proto.RegisterFile("proto2_ext2.proto", fileDescriptor2) }
+
+var fileDescriptor2 = []byte{
+	// 165 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0x28, 0xca, 0x2f,
+	0xc9, 0x37, 0x8a, 0x4f, 0xad, 0x28, 0x31, 0xd2, 0x03, 0xb3, 0x85, 0x78, 0xd2, 0x8b, 0x0a, 0x92,
+	0xf5, 0x4a, 0x52, 0x8b, 0x4b, 0x32, 0xf3, 0xd2, 0xa5, 0x78, 0x20, 0x0a, 0x20, 0x72, 0x4a, 0x36,
+	0x5c, 0x02, 0x8e, 0x79, 0xf9, 0x25, 0x19, 0xa9, 0x45, 0xae, 0x15, 0x25, 0xa9, 0x79, 0xc5, 0x99,
+	0xf9, 0x79, 0x42, 0x1a, 0x5c, 0xfc, 0xe5, 0x19, 0x89, 0x25, 0xc9, 0x19, 0x89, 0xb9, 0x89, 0xc9,
+	0x89, 0x39, 0x39, 0x99, 0x25, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xac, 0x41, 0xe8, 0xc2, 0x56, 0x7a,
+	0x5c, 0x2c, 0x69, 0x45, 0xf9, 0x49, 0x42, 0x52, 0x7a, 0xc8, 0x56, 0xe8, 0x85, 0xe4, 0x3b, 0xa5,
+	0x82, 0x8d, 0x4b, 0x49, 0x4d, 0x91, 0x10, 0x57, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xab, 0xb3, 0xf2,
+	0xe3, 0x62, 0xc9, 0xcb, 0x2c, 0xa9, 0xc2, 0xab, 0x5e, 0x56, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x0e,
+	0x55, 0x05, 0xba, 0x1b, 0x83, 0xc0, 0xe6, 0x00, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x7e, 0x0d,
+	0x26, 0xed, 0x00, 0x00, 0x00,
+}
diff --git a/reflection/grpc_testing/proto2_ext2.proto b/reflection/grpc_testing/proto2_ext2.proto
new file mode 100644
index 0000000..16fa69e
--- /dev/null
+++ b/reflection/grpc_testing/proto2_ext2.proto
@@ -0,0 +1,14 @@
+syntax = "proto2";
+
+package grpc.testing;
+
+import "proto2.proto";
+
+extend ToBeExtended {
+  optional string frob = 23;
+  optional AnotherExtension nitz = 29;
+}
+
+message AnotherExtension {
+  optional int32 whatchamacallit = 1;
+}
diff --git a/reflection/grpc_testing/test.pb.go b/reflection/grpc_testing/test.pb.go
index 607dfd3..27d71fc 100644
--- a/reflection/grpc_testing/test.pb.go
+++ b/reflection/grpc_testing/test.pb.go
@@ -25,7 +25,7 @@
 func (m *SearchResponse) Reset()                    { *m = SearchResponse{} }
 func (m *SearchResponse) String() string            { return proto.CompactTextString(m) }
 func (*SearchResponse) ProtoMessage()               {}
-func (*SearchResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} }
+func (*SearchResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} }
 
 func (m *SearchResponse) GetResults() []*SearchResponse_Result {
 	if m != nil {
@@ -43,7 +43,28 @@
 func (m *SearchResponse_Result) Reset()                    { *m = SearchResponse_Result{} }
 func (m *SearchResponse_Result) String() string            { return proto.CompactTextString(m) }
 func (*SearchResponse_Result) ProtoMessage()               {}
-func (*SearchResponse_Result) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0, 0} }
+func (*SearchResponse_Result) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0, 0} }
+
+func (m *SearchResponse_Result) GetUrl() string {
+	if m != nil {
+		return m.Url
+	}
+	return ""
+}
+
+func (m *SearchResponse_Result) GetTitle() string {
+	if m != nil {
+		return m.Title
+	}
+	return ""
+}
+
+func (m *SearchResponse_Result) GetSnippets() []string {
+	if m != nil {
+		return m.Snippets
+	}
+	return nil
+}
 
 type SearchRequest struct {
 	Query string `protobuf:"bytes,1,opt,name=query" json:"query,omitempty"`
@@ -52,7 +73,14 @@
 func (m *SearchRequest) Reset()                    { *m = SearchRequest{} }
 func (m *SearchRequest) String() string            { return proto.CompactTextString(m) }
 func (*SearchRequest) ProtoMessage()               {}
-func (*SearchRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} }
+func (*SearchRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} }
+
+func (m *SearchRequest) GetQuery() string {
+	if m != nil {
+		return m.Query
+	}
+	return ""
+}
 
 func init() {
 	proto.RegisterType((*SearchResponse)(nil), "grpc.testing.SearchResponse")
@@ -198,23 +226,23 @@
 	Metadata: "test.proto",
 }
 
-func init() { proto.RegisterFile("test.proto", fileDescriptor2) }
+func init() { proto.RegisterFile("test.proto", fileDescriptor3) }
 
-var fileDescriptor2 = []byte{
-	// 227 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e,
-	0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x49, 0x2f, 0x2a, 0x48, 0xd6, 0x03, 0x09, 0x64,
-	0xe6, 0xa5, 0x2b, 0xcd, 0x65, 0xe4, 0xe2, 0x0b, 0x4e, 0x4d, 0x2c, 0x4a, 0xce, 0x08, 0x4a, 0x2d,
-	0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0xb2, 0xe5, 0x62, 0x2f, 0x4a, 0x2d, 0x2e, 0xcd, 0x29, 0x29,
-	0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x36, 0x52, 0xd6, 0x43, 0xd6, 0xa2, 0x87, 0xaa, 0x5c, 0x2f,
-	0x08, 0xac, 0x36, 0x08, 0xa6, 0x47, 0xca, 0x87, 0x8b, 0x0d, 0x22, 0x24, 0x24, 0xc0, 0xc5, 0x5c,
-	0x5a, 0x94, 0x03, 0x34, 0x84, 0x51, 0x83, 0x33, 0x08, 0xc4, 0x14, 0x12, 0xe1, 0x62, 0x2d, 0xc9,
-	0x2c, 0xc9, 0x49, 0x95, 0x60, 0x02, 0x8b, 0x41, 0x38, 0x42, 0x52, 0x5c, 0x1c, 0xc5, 0x79, 0x99,
-	0x05, 0x05, 0xa9, 0x40, 0x1b, 0x99, 0x81, 0x36, 0x72, 0x06, 0xc1, 0xf9, 0x4a, 0xaa, 0x5c, 0xbc,
-	0x30, 0xfb, 0x0a, 0x4b, 0x81, 0x0e, 0x00, 0x19, 0x01, 0x64, 0x14, 0x55, 0x42, 0x8d, 0x85, 0x70,
-	0x8c, 0x96, 0x31, 0xc2, 0xd4, 0x05, 0xa7, 0x16, 0x95, 0x65, 0x26, 0xa7, 0x0a, 0x39, 0x73, 0xb1,
-	0x41, 0x04, 0x84, 0xa4, 0xb1, 0x3b, 0x1f, 0x6c, 0x9c, 0x94, 0x0c, 0x3e, 0xbf, 0x09, 0x05, 0x70,
-	0xf1, 0x07, 0x97, 0x14, 0xa5, 0x26, 0xe6, 0x02, 0xe5, 0x28, 0x36, 0x4d, 0x83, 0xd1, 0x80, 0x31,
-	0x89, 0x0d, 0x1c, 0x09, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x20, 0xd6, 0x09, 0xb8, 0x92,
-	0x01, 0x00, 0x00,
+var fileDescriptor3 = []byte{
+	// 231 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x91, 0xbd, 0x4a, 0xc5, 0x40,
+	0x10, 0x85, 0x59, 0x83, 0xd1, 0x3b, 0xfe, 0x32, 0x58, 0x84, 0x68, 0x11, 0xae, 0x08, 0xa9, 0x16,
+	0xb9, 0xd6, 0x56, 0xb6, 0x16, 0xb2, 0x79, 0x82, 0x6b, 0x18, 0xe2, 0x42, 0x4c, 0x36, 0x33, 0x13,
+	0xc1, 0x87, 0xb1, 0xf5, 0x39, 0x25, 0x59, 0x23, 0x0a, 0x62, 0x63, 0xb7, 0xe7, 0xe3, 0xcc, 0xb7,
+	0xbb, 0x0c, 0x80, 0x92, 0xa8, 0x0d, 0xdc, 0x6b, 0x8f, 0x87, 0x0d, 0x87, 0xda, 0x4e, 0xc0, 0x77,
+	0xcd, 0xfa, 0xcd, 0xc0, 0x71, 0x45, 0x5b, 0xae, 0x9f, 0x1c, 0x49, 0xe8, 0x3b, 0x21, 0xbc, 0x85,
+	0x3d, 0x26, 0x19, 0x5b, 0x95, 0xcc, 0x14, 0x49, 0x79, 0xb0, 0xb9, 0xb4, 0xdf, 0x47, 0xec, 0xcf,
+	0xba, 0x75, 0x73, 0xd7, 0x2d, 0x33, 0xf9, 0x3d, 0xa4, 0x11, 0xe1, 0x29, 0x24, 0x23, 0xb7, 0x99,
+	0x29, 0x4c, 0xb9, 0x72, 0xd3, 0x11, 0xcf, 0x60, 0x57, 0xbd, 0xb6, 0x94, 0xed, 0xcc, 0x2c, 0x06,
+	0xcc, 0x61, 0x5f, 0x3a, 0x1f, 0x02, 0xa9, 0x64, 0x49, 0x91, 0x94, 0x2b, 0xf7, 0x95, 0xd7, 0x57,
+	0x70, 0xb4, 0xdc, 0x37, 0x8c, 0x24, 0x3a, 0x29, 0x86, 0x91, 0xf8, 0xf5, 0x53, 0x1b, 0xc3, 0xe6,
+	0xdd, 0x2c, 0xbd, 0x8a, 0xf8, 0xc5, 0xd7, 0x84, 0x77, 0x90, 0x46, 0x80, 0xe7, 0xbf, 0x3f, 0x7f,
+	0xd6, 0xe5, 0x17, 0x7f, 0xfd, 0x0d, 0x1f, 0xe0, 0xa4, 0x52, 0xa6, 0xed, 0xb3, 0xef, 0x9a, 0x7f,
+	0xdb, 0x4a, 0x73, 0x6d, 0x1e, 0xd3, 0x79, 0x09, 0x37, 0x1f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x20,
+	0xd6, 0x09, 0xb8, 0x92, 0x01, 0x00, 0x00,
 }
diff --git a/reflection/serverreflection.go b/reflection/serverreflection.go
index d26eac3..f28304d 100644
--- a/reflection/serverreflection.go
+++ b/reflection/serverreflection.go
@@ -156,9 +156,7 @@
 		return nil, fmt.Errorf("failed to find registered extension for extension number %v", ext)
 	}
 
-	extT := reflect.TypeOf(extDesc.ExtensionType).Elem()
-
-	return s.fileDescForType(extT)
+	return s.decodeFileDesc(proto.FileDescriptor(extDesc.Filename))
 }
 
 func (s *serverReflectionServer) allExtensionNumbersForType(st reflect.Type) ([]int32, error) {
diff --git a/reflection/serverreflection_test.go b/reflection/serverreflection_test.go
index 1759e66..daef8ff 100644
--- a/reflection/serverreflection_test.go
+++ b/reflection/serverreflection_test.go
@@ -51,13 +51,15 @@
 var (
 	s = &serverReflectionServer{}
 	// fileDescriptor of each test proto file.
-	fdTest      *dpb.FileDescriptorProto
-	fdProto2    *dpb.FileDescriptorProto
-	fdProto2Ext *dpb.FileDescriptorProto
+	fdTest       *dpb.FileDescriptorProto
+	fdProto2     *dpb.FileDescriptorProto
+	fdProto2Ext  *dpb.FileDescriptorProto
+	fdProto2Ext2 *dpb.FileDescriptorProto
 	// fileDescriptor marshalled.
-	fdTestByte      []byte
-	fdProto2Byte    []byte
-	fdProto2ExtByte []byte
+	fdTestByte       []byte
+	fdProto2Byte     []byte
+	fdProto2ExtByte  []byte
+	fdProto2Ext2Byte []byte
 )
 
 func loadFileDesc(filename string) (*dpb.FileDescriptorProto, []byte) {
@@ -80,6 +82,7 @@
 	fdTest, fdTestByte = loadFileDesc("test.proto")
 	fdProto2, fdProto2Byte = loadFileDesc("proto2.proto")
 	fdProto2Ext, fdProto2ExtByte = loadFileDesc("proto2_ext.proto")
+	fdProto2Ext2, fdProto2Ext2Byte = loadFileDesc("proto2_ext2.proto")
 }
 
 func TestFileDescForType(t *testing.T) {
@@ -88,7 +91,7 @@
 		wantFd *dpb.FileDescriptorProto
 	}{
 		{reflect.TypeOf(pb.SearchResponse_Result{}), fdTest},
-		{reflect.TypeOf(pb.ToBeExtened{}), fdProto2},
+		{reflect.TypeOf(pb.ToBeExtended{}), fdProto2},
 	} {
 		fd, err := s.fileDescForType(test.st)
 		if err != nil || !reflect.DeepEqual(fd, test.wantFd) {
@@ -128,7 +131,11 @@
 		extNum int32
 		want   *dpb.FileDescriptorProto
 	}{
-		{reflect.TypeOf(pb.ToBeExtened{}), 17, fdProto2Ext},
+		{reflect.TypeOf(pb.ToBeExtended{}), 13, fdProto2Ext},
+		{reflect.TypeOf(pb.ToBeExtended{}), 17, fdProto2Ext},
+		{reflect.TypeOf(pb.ToBeExtended{}), 19, fdProto2Ext},
+		{reflect.TypeOf(pb.ToBeExtended{}), 23, fdProto2Ext2},
+		{reflect.TypeOf(pb.ToBeExtended{}), 29, fdProto2Ext2},
 	} {
 		fd, err := s.fileDescContainingExtension(test.st, test.extNum)
 		if err != nil || !reflect.DeepEqual(fd, test.want) {
@@ -149,7 +156,7 @@
 		st   reflect.Type
 		want []int32
 	}{
-		{reflect.TypeOf(pb.ToBeExtened{}), []int32{13, 17}},
+		{reflect.TypeOf(pb.ToBeExtended{}), []int32{13, 17, 19, 23, 29}},
 	} {
 		r, err := s.allExtensionNumbersForType(test.st)
 		sort.Sort(intArray(r))
@@ -278,7 +285,7 @@
 		{"grpc.testing.SearchService.Search", fdTestByte},
 		{"grpc.testing.SearchService.StreamingSearch", fdTestByte},
 		{"grpc.testing.SearchResponse", fdTestByte},
-		{"grpc.testing.ToBeExtened", fdProto2Byte},
+		{"grpc.testing.ToBeExtended", fdProto2Byte},
 	} {
 		if err := stream.Send(&rpb.ServerReflectionRequest{
 			MessageRequest: &rpb.ServerReflectionRequest_FileContainingSymbol{
@@ -309,7 +316,7 @@
 		"grpc.testing.SerchService",
 		"grpc.testing.SearchService.SearchE",
 		"grpc.tesing.SearchResponse",
-		"gpc.testing.ToBeExtened",
+		"gpc.testing.ToBeExtended",
 	} {
 		if err := stream.Send(&rpb.ServerReflectionRequest{
 			MessageRequest: &rpb.ServerReflectionRequest_FileContainingSymbol{
@@ -338,7 +345,11 @@
 		extNum   int32
 		want     []byte
 	}{
-		{"grpc.testing.ToBeExtened", 17, fdProto2ExtByte},
+		{"grpc.testing.ToBeExtended", 13, fdProto2ExtByte},
+		{"grpc.testing.ToBeExtended", 17, fdProto2ExtByte},
+		{"grpc.testing.ToBeExtended", 19, fdProto2ExtByte},
+		{"grpc.testing.ToBeExtended", 23, fdProto2Ext2Byte},
+		{"grpc.testing.ToBeExtended", 29, fdProto2Ext2Byte},
 	} {
 		if err := stream.Send(&rpb.ServerReflectionRequest{
 			MessageRequest: &rpb.ServerReflectionRequest_FileContainingExtension{
@@ -372,8 +383,8 @@
 		typeName string
 		extNum   int32
 	}{
-		{"grpc.testing.ToBExtened", 17},
-		{"grpc.testing.ToBeExtened", 15},
+		{"grpc.testing.ToBExtended", 17},
+		{"grpc.testing.ToBeExtended", 15},
 	} {
 		if err := stream.Send(&rpb.ServerReflectionRequest{
 			MessageRequest: &rpb.ServerReflectionRequest_FileContainingExtension{
@@ -404,7 +415,7 @@
 		typeName string
 		want     []int32
 	}{
-		{"grpc.testing.ToBeExtened", []int32{13, 17}},
+		{"grpc.testing.ToBeExtended", []int32{13, 17, 19, 23, 29}},
 	} {
 		if err := stream.Send(&rpb.ServerReflectionRequest{
 			MessageRequest: &rpb.ServerReflectionRequest_AllExtensionNumbersOfType{
@@ -435,7 +446,7 @@
 
 func testAllExtensionNumbersOfTypeError(t *testing.T, stream rpb.ServerReflection_ServerReflectionInfoClient) {
 	for _, test := range []string{
-		"grpc.testing.ToBeExtenedE",
+		"grpc.testing.ToBeExtendedE",
 	} {
 		if err := stream.Send(&rpb.ServerReflectionRequest{
 			MessageRequest: &rpb.ServerReflectionRequest_AllExtensionNumbersOfType{
diff --git a/rpc_util.go b/rpc_util.go
index fa69d58..da88dad 100644
--- a/rpc_util.go
+++ b/rpc_util.go
@@ -48,6 +48,7 @@
 	"golang.org/x/net/context"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/metadata"
+	"google.golang.org/grpc/peer"
 	"google.golang.org/grpc/stats"
 	"google.golang.org/grpc/transport"
 )
@@ -140,6 +141,7 @@
 	failFast  bool
 	headerMD  metadata.MD
 	trailerMD metadata.MD
+	peer      *peer.Peer
 	traceInfo traceInfo // in trace.go
 }
 
@@ -183,6 +185,14 @@
 	})
 }
 
+// Peer returns a CallOption that retrieves peer information for a
+// unary RPC.
+func Peer(peer *peer.Peer) CallOption {
+	return afterCall(func(c *callInfo) {
+		*peer = *c.peer
+	})
+}
+
 // FailFast configures the action to take when an RPC is attempted on broken
 // connections or unreachable servers. If failfast is true, the RPC will fail
 // immediately. Otherwise, the RPC client will block the call until a
diff --git a/test/end2end_test.go b/test/end2end_test.go
index 6fe45f2..902bec4 100644
--- a/test/end2end_test.go
+++ b/test/end2end_test.go
@@ -1440,6 +1440,43 @@
 	}
 }
 
+func TestPeerClientSide(t *testing.T) {
+	defer leakCheck(t)()
+	for _, e := range listTestEnv() {
+		testPeerClientSide(t, e)
+	}
+}
+
+func testPeerClientSide(t *testing.T, e env) {
+	te := newTest(t, e)
+	te.userAgent = testAppUA
+	te.startServer(&testServer{security: e.security})
+	defer te.tearDown()
+	tc := testpb.NewTestServiceClient(te.clientConn())
+	peer := new(peer.Peer)
+	if _, err := tc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(peer), grpc.FailFast(false)); err != nil {
+		t.Fatalf("TestService/EmptyCall(_, _) = _, %v, want _, <nil>", err)
+	}
+	pa := peer.Addr.String()
+	if e.network == "unix" {
+		if pa != te.srvAddr {
+			t.Fatalf("peer.Addr = %v, want %v", pa, te.srvAddr)
+		}
+		return
+	}
+	_, pp, err := net.SplitHostPort(pa)
+	if err != nil {
+		t.Fatalf("Failed to parse address from peer.")
+	}
+	_, sp, err := net.SplitHostPort(te.srvAddr)
+	if err != nil {
+		t.Fatalf("Failed to parse address of test server.")
+	}
+	if pp != sp {
+		t.Fatalf("peer.Addr = localhost:%v, want localhost:%v", pp, sp)
+	}
+}
+
 func TestMetadataUnaryRPC(t *testing.T) {
 	defer leakCheck(t)()
 	for _, e := range listTestEnv() {