@@ -27,6 +27,7 @@ import (
2727 "crypto/x509"
2828 "crypto/x509/pkix"
2929 _ "embed"
30+ "encoding/binary"
3031 "encoding/pem"
3132 "fmt"
3233 "io"
@@ -43,6 +44,7 @@ import (
4344 "text/template"
4445 "time"
4546
47+ "github.com/dio/transit/e2e/internal/grpctestproto"
4648 "github.com/dio/transit/e2e/sinks/accessloggersink"
4749 "github.com/dio/transit/e2e/sinks/alssink"
4850 "github.com/dio/transit/e2e/sinks/otelsink"
7577 asyncCalloutBodyAddr string
7678 asyncCalloutLocalResponseAddr string
7779 mutableBodyUpstreamAddr string
80+ grpcCalloutAddr string
7881 lbPolicySelectionAddr string
7982 accessLoggerLocalReplyAddr string
8083 accessLoggerFlagsAddr string
@@ -143,6 +146,8 @@ func TestMain(m *testing.M) {
143146 mutableBodyUpstreamPort := freePort ()
144147 mutableBodyRecorder = startRecorderUpstream ()
145148 asyncCalloutLocalResponsePort := freePort ()
149+ grpcCalloutPort := freePort ()
150+ grpcCalloutUpstreamPort := startGRPCCalloutUpstream ()
146151 lbPolicySelectionPort := freePort ()
147152 lbPolicyHost0Port := startIdentifiedUpstream ("lb-host-0" )
148153 lbPolicyHost1Port := startIdentifiedUpstream ("lb-host-1" )
@@ -184,6 +189,7 @@ func TestMain(m *testing.M) {
184189 asyncCalloutBodyAddr = fmt .Sprintf ("http://localhost:%d" , asyncCalloutBodyPort )
185190 mutableBodyUpstreamAddr = fmt .Sprintf ("http://localhost:%d" , mutableBodyUpstreamPort )
186191 asyncCalloutLocalResponseAddr = fmt .Sprintf ("http://localhost:%d" , asyncCalloutLocalResponsePort )
192+ grpcCalloutAddr = fmt .Sprintf ("http://localhost:%d" , grpcCalloutPort )
187193 lbPolicySelectionAddr = fmt .Sprintf ("http://localhost:%d" , lbPolicySelectionPort )
188194 accessLoggerLocalReplyAddr = fmt .Sprintf ("http://localhost:%d" , accessLoggerLocalReplyPort )
189195 accessLoggerFlagsAddr = fmt .Sprintf ("http://localhost:%d" , accessLoggerFlagsPort )
@@ -272,6 +278,8 @@ func TestMain(m *testing.M) {
272278 MutableBodyUpstreamPort : mutableBodyUpstreamPort ,
273279 MutableBodyRecorderPort : mutableBodyRecorder .port ,
274280 AsyncCalloutLocalResponsePort : asyncCalloutLocalResponsePort ,
281+ GRPCCalloutPort : grpcCalloutPort ,
282+ GRPCCalloutUpstreamPort : grpcCalloutUpstreamPort ,
275283 LbPolicySelectionPort : lbPolicySelectionPort ,
276284 LbPolicyHost0Port : lbPolicyHost0Port ,
277285 LbPolicyHost1Port : lbPolicyHost1Port ,
@@ -416,6 +424,62 @@ func startAsyncCalloutUpstream() int {
416424 return l .Addr ().(* net.TCPAddr ).Port
417425}
418426
427+ // startGRPCCalloutUpstream starts a minimal h2c gRPC server for the GRPCCallout
428+ // e2e tests. It handles /e2e.Echo/Echo by decoding a framed EchoRequest proto
429+ // and returning a framed EchoResponse proto with grpc-status: 0 in the trailers.
430+ func startGRPCCalloutUpstream () int {
431+ l , err := net .Listen ("tcp" , "127.0.0.1:0" )
432+ if err != nil {
433+ panic ("startGRPCCalloutUpstream: " + err .Error ())
434+ }
435+ mux := http .NewServeMux ()
436+ mux .HandleFunc ("/e2e.Echo/Echo" , func (w http.ResponseWriter , r * http.Request ) {
437+ body , err := io .ReadAll (r .Body )
438+ if err != nil {
439+ http .Error (w , err .Error (), http .StatusBadRequest )
440+ return
441+ }
442+ if len (body ) < 5 || body [0 ] != 0 {
443+ http .Error (w , "invalid grpc frame" , http .StatusBadRequest )
444+ return
445+ }
446+ msgLen := binary .BigEndian .Uint32 (body [1 :5 ])
447+ end := 5 + int (msgLen )
448+ if end < 5 || end > len (body ) {
449+ http .Error (w , "truncated grpc frame" , http .StatusBadRequest )
450+ return
451+ }
452+ var echoReq grpctestproto.EchoRequest
453+ if err := echoReq .UnmarshalProto (body [5 :end ]); err != nil {
454+ http .Error (w , err .Error (), http .StatusBadRequest )
455+ return
456+ }
457+ echoResp := grpctestproto.EchoResponse {
458+ Text : echoReq .Text ,
459+ Sequence : echoReq .Sequence + 1 ,
460+ }
461+ msg := echoResp .MarshalProto (nil )
462+ frame := make ([]byte , 5 + len (msg ))
463+ binary .BigEndian .PutUint32 (frame [1 :5 ], uint32 (len (msg )))
464+ copy (frame [5 :], msg )
465+
466+ w .Header ().Set ("Content-Type" , "application/grpc+proto" )
467+ w .Header ().Set ("Trailer" , "Grpc-Status" )
468+ w .WriteHeader (http .StatusOK )
469+ _ , _ = w .Write (frame )
470+ w .Header ().Set ("Grpc-Status" , "0" )
471+ })
472+ protocols := new (http.Protocols )
473+ protocols .SetHTTP1 (true )
474+ protocols .SetUnencryptedHTTP2 (true )
475+ srv := & http.Server {
476+ Handler : mux ,
477+ Protocols : protocols ,
478+ }
479+ go srv .Serve (l ) //nolint:errcheck
480+ return l .Addr ().(* net.TCPAddr ).Port
481+ }
482+
419483// startForwardEchoUpstream starts an upstream that echoes received request
420484// headers as "x-received-<lowercase-name>" response headers and returns the
421485// request body. Used to verify that filter mutations reached upstream.
@@ -800,6 +864,8 @@ type envoyPorts struct {
800864 MutableBodyUpstreamPort int
801865 MutableBodyRecorderPort int
802866 AsyncCalloutLocalResponsePort int
867+ GRPCCalloutPort int
868+ GRPCCalloutUpstreamPort int
803869 LbPolicySelectionPort int
804870 LbPolicyHost0Port int
805871 LbPolicyHost1Port int
0 commit comments