11use super :: { NewRequireIdentity , NewStripProxyError , ProxyConnectionClose } ;
2- use crate :: Outbound ;
2+ use crate :: { tcp :: opaque_transport , Outbound } ;
33use linkerd_app_core:: {
44 classify, config, errors, http_tracing, metrics,
55 proxy:: { http, tap} ,
66 svc:: { self , ExtractParam } ,
7- tls, Error , Result , CANONICAL_DST_HEADER ,
7+ tls,
8+ transport:: { self , Remote , ServerAddr } ,
9+ transport_header:: SessionProtocol ,
10+ Error , Result , CANONICAL_DST_HEADER ,
811} ;
912use tokio:: io;
1013
@@ -13,6 +16,12 @@ struct ClientRescue {
1316 emit_headers : bool ,
1417}
1518
19+ #[ derive( Clone , Debug ) ]
20+ pub struct Connect < T > {
21+ version : http:: Version ,
22+ inner : T ,
23+ }
24+
1625impl < C > Outbound < C > {
1726 pub fn push_http_endpoint < T , B > ( self ) -> Outbound < svc:: ArcNewHttp < T , B > >
1827 where
@@ -24,7 +33,7 @@ impl<C> Outbound<C> {
2433 + tap:: Inspect ,
2534 B : http:: HttpBody < Error = Error > + std:: fmt:: Debug + Default + Send + ' static ,
2635 B :: Data : Send + ' static ,
27- C : svc:: Service < T > + Clone + Send + Sync + Unpin + ' static ,
36+ C : svc:: Service < Connect < T > > + Clone + Send + Sync + Unpin + ' static ,
2837 C :: Response : io:: AsyncRead + io:: AsyncWrite + Send + Unpin ,
2938 C :: Error : Into < Error > ,
3039 C :: Future : Send + Unpin + ' static ,
@@ -40,7 +49,9 @@ impl<C> Outbound<C> {
4049 // Initiates an HTTP client on the underlying transport. Prior-knowledge HTTP/2
4150 // is typically used (i.e. when communicating with other proxies); though
4251 // HTTP/1.x fallback is supported as needed.
43- connect
52+ svc:: stack ( connect. into_inner ( ) . into_service ( ) )
53+ . check_service :: < Connect < T > > ( )
54+ . push_map_target ( |( version, inner) | Connect { version, inner } )
4455 . push ( http:: client:: layer ( h1_settings, h2_settings) )
4556 . push_on_service ( svc:: MapErr :: layer ( Into :: < Error > :: into) )
4657 . check_service :: < T > ( )
@@ -133,17 +144,72 @@ impl errors::HttpRescue<Error> for ClientRescue {
133144 }
134145}
135146
147+ // === impl Connect ===
148+
149+ impl < T > svc:: Param < Option < SessionProtocol > > for Connect < T > {
150+ #[ inline]
151+ fn param ( & self ) -> Option < SessionProtocol > {
152+ match self . version {
153+ http:: Version :: Http1 => Some ( SessionProtocol :: Http1 ) ,
154+ http:: Version :: H2 => Some ( SessionProtocol :: Http2 ) ,
155+ }
156+ }
157+ }
158+
159+ impl < T : svc:: Param < Remote < ServerAddr > > > svc:: Param < Remote < ServerAddr > > for Connect < T > {
160+ #[ inline]
161+ fn param ( & self ) -> Remote < ServerAddr > {
162+ self . inner . param ( )
163+ }
164+ }
165+
166+ impl < T : svc:: Param < tls:: ConditionalClientTls > > svc:: Param < tls:: ConditionalClientTls >
167+ for Connect < T >
168+ {
169+ #[ inline]
170+ fn param ( & self ) -> tls:: ConditionalClientTls {
171+ self . inner . param ( )
172+ }
173+ }
174+
175+ impl < T : svc:: Param < Option < opaque_transport:: PortOverride > > >
176+ svc:: Param < Option < opaque_transport:: PortOverride > > for Connect < T >
177+ {
178+ #[ inline]
179+ fn param ( & self ) -> Option < opaque_transport:: PortOverride > {
180+ self . inner . param ( )
181+ }
182+ }
183+
184+ impl < T : svc:: Param < Option < http:: AuthorityOverride > > > svc:: Param < Option < http:: AuthorityOverride > >
185+ for Connect < T >
186+ {
187+ #[ inline]
188+ fn param ( & self ) -> Option < http:: AuthorityOverride > {
189+ self . inner . param ( )
190+ }
191+ }
192+
193+ impl < T : svc:: Param < transport:: labels:: Key > > svc:: Param < transport:: labels:: Key > for Connect < T > {
194+ #[ inline]
195+ fn param ( & self ) -> transport:: labels:: Key {
196+ self . inner . param ( )
197+ }
198+ }
199+
136200#[ cfg( test) ]
137201mod test {
138202 use super :: * ;
139- use crate :: { http, test_util:: * , transport:: addrs:: * } ;
203+ use crate :: { http, test_util:: * } ;
204+ use :: http:: header:: { CONNECTION , UPGRADE } ;
140205 use linkerd_app_core:: {
141206 io,
142207 proxy:: api_resolve:: Metadata ,
143208 svc:: { NewService , ServiceExt } ,
144209 Infallible ,
145210 } ;
146211 use std:: net:: SocketAddr ;
212+ use support:: resolver:: ProtocolHint ;
147213
148214 static WAS_ORIG_PROTO : & str = "request-orig-proto" ;
149215
@@ -155,7 +221,7 @@ mod test {
155221 let addr = SocketAddr :: new ( [ 192 , 0 , 2 , 41 ] . into ( ) , 2041 ) ;
156222
157223 let connect = support:: connect ( )
158- . endpoint_fn_boxed ( addr, |_: http:: Endpoint | serve ( :: http:: Version :: HTTP_11 ) ) ;
224+ . endpoint_fn_boxed ( addr, |_: http:: Connect | serve ( :: http:: Version :: HTTP_11 ) ) ;
159225
160226 // Build the outbound server
161227 let ( rt, _shutdown) = runtime ( ) ;
@@ -192,7 +258,7 @@ mod test {
192258 let addr = SocketAddr :: new ( [ 192 , 0 , 2 , 41 ] . into ( ) , 2042 ) ;
193259
194260 let connect = support:: connect ( )
195- . endpoint_fn_boxed ( addr, |_: http:: Endpoint | serve ( :: http:: Version :: HTTP_2 ) ) ;
261+ . endpoint_fn_boxed ( addr, |_: http:: Connect | serve ( :: http:: Version :: HTTP_2 ) ) ;
196262
197263 // Build the outbound server
198264 let ( rt, _shutdown) = runtime ( ) ;
@@ -231,7 +297,7 @@ mod test {
231297
232298 // Pretend the upstream is a proxy that supports proto upgrades...
233299 let connect = support:: connect ( )
234- . endpoint_fn_boxed ( addr, |_: http:: Endpoint | serve ( :: http:: Version :: HTTP_2 ) ) ;
300+ . endpoint_fn_boxed ( addr, |_: http:: Connect | serve ( :: http:: Version :: HTTP_2 ) ) ;
235301
236302 // Build the outbound server
237303 let ( rt, _shutdown) = runtime ( ) ;
@@ -246,13 +312,7 @@ mod test {
246312 logical_addr : None ,
247313 opaque_protocol : false ,
248314 tls : tls:: ConditionalClientTls :: None ( tls:: NoClientTls :: Disabled ) ,
249- metadata : Metadata :: new (
250- None ,
251- support:: resolver:: ProtocolHint :: Http2 ,
252- None ,
253- None ,
254- None ,
255- ) ,
315+ metadata : Metadata :: new ( None , ProtocolHint :: Http2 , None , None , None ) ,
256316 } ) ;
257317
258318 let req = http:: Request :: builder ( )
@@ -271,6 +331,63 @@ mod test {
271331 ) ;
272332 }
273333
334+ #[ tokio:: test( flavor = "current_thread" ) ]
335+ async fn orig_proto_skipped_on_http_upgrade ( ) {
336+ let _trace = linkerd_tracing:: test:: trace_init ( ) ;
337+
338+ let addr = SocketAddr :: new ( [ 192 , 0 , 2 , 41 ] . into ( ) , 2041 ) ;
339+
340+ // Pretend the upstream is a proxy that supports proto upgrades. The service needs to
341+ // support both HTTP/1 and HTTP/2 because an HTTP/2 connection is maintained by default and
342+ // HTTP/1 connections are created as-needed.
343+ let connect = support:: connect ( ) . endpoint_fn_boxed ( addr, |c : http:: Connect | {
344+ serve ( match svc:: Param :: param ( & c) {
345+ Some ( SessionProtocol :: Http1 ) => :: http:: Version :: HTTP_11 ,
346+ Some ( SessionProtocol :: Http2 ) => :: http:: Version :: HTTP_2 ,
347+ None => unreachable ! ( ) ,
348+ } )
349+ } ) ;
350+
351+ // Build the outbound server
352+ let ( rt, _shutdown) = runtime ( ) ;
353+ let drain = rt. drain . clone ( ) ;
354+ let stack = Outbound :: new ( default_config ( ) , rt)
355+ . with_stack ( connect)
356+ . push_http_endpoint :: < _ , http:: BoxBody > ( )
357+ . into_stack ( )
358+ . push_on_service ( http:: BoxRequest :: layer ( ) )
359+ // We need the server-side upgrade layer to annotate the request so that the client
360+ // knows that an HTTP upgrade is in progress.
361+ . push_on_service ( svc:: layer:: mk ( |svc| {
362+ http:: upgrade:: Service :: new ( svc, drain. clone ( ) )
363+ } ) )
364+ . into_inner ( ) ;
365+
366+ let svc = stack. new_service ( http:: Endpoint {
367+ addr : Remote ( ServerAddr ( addr) ) ,
368+ protocol : http:: Version :: Http1 ,
369+ logical_addr : None ,
370+ opaque_protocol : false ,
371+ tls : tls:: ConditionalClientTls :: None ( tls:: NoClientTls :: Disabled ) ,
372+ metadata : Metadata :: new ( None , ProtocolHint :: Http2 , None , None , None ) ,
373+ } ) ;
374+
375+ let req = http:: Request :: builder ( )
376+ . version ( :: http:: Version :: HTTP_11 )
377+ . uri ( "http://foo.example.com" )
378+ . extension ( http:: ClientHandle :: new ( ( [ 192 , 0 , 2 , 101 ] , 40200 ) . into ( ) ) . 0 )
379+ // The request has upgrade headers
380+ . header ( CONNECTION , "upgrade" )
381+ . header ( UPGRADE , "linkerdrocks" )
382+ . body ( hyper:: Body :: default ( ) )
383+ . unwrap ( ) ;
384+ let rsp = svc. oneshot ( req) . await . unwrap ( ) ;
385+ assert_eq ! ( rsp. status( ) , http:: StatusCode :: NO_CONTENT ) ;
386+ // The request did NOT get a linkerd upgrade header.
387+ assert ! ( rsp. headers( ) . get( WAS_ORIG_PROTO ) . is_none( ) ) ;
388+ assert_eq ! ( rsp. version( ) , :: http:: Version :: HTTP_11 ) ;
389+ }
390+
274391 /// Tests that the the HTTP endpoint stack ignores protocol upgrade hinting for HTTP/2 traffic.
275392 #[ tokio:: test( flavor = "current_thread" ) ]
276393 async fn orig_proto_http2_noop ( ) {
@@ -280,7 +397,7 @@ mod test {
280397
281398 // Pretend the upstream is a proxy that supports proto upgrades...
282399 let connect = support:: connect ( )
283- . endpoint_fn_boxed ( addr, |_: http:: Endpoint | serve ( :: http:: Version :: HTTP_2 ) ) ;
400+ . endpoint_fn_boxed ( addr, |_: http:: Connect | serve ( :: http:: Version :: HTTP_2 ) ) ;
284401
285402 // Build the outbound server
286403 let ( rt, _shutdown) = runtime ( ) ;
@@ -295,13 +412,7 @@ mod test {
295412 logical_addr : None ,
296413 opaque_protocol : false ,
297414 tls : tls:: ConditionalClientTls :: None ( tls:: NoClientTls :: Disabled ) ,
298- metadata : Metadata :: new (
299- None ,
300- support:: resolver:: ProtocolHint :: Http2 ,
301- None ,
302- None ,
303- None ,
304- ) ,
415+ metadata : Metadata :: new ( None , ProtocolHint :: Http2 , None , None , None ) ,
305416 } ) ;
306417
307418 let req = http:: Request :: builder ( )
0 commit comments