@@ -9,12 +9,15 @@ use linkerd_proxy_http::{ClientHandle, HasH2Reason};
99use linkerd_timeout:: { FailFastError , ResponseTimeout } ;
1010use linkerd_tls as tls;
1111use pin_project:: pin_project;
12+ use std:: fmt;
1213use std:: pin:: Pin ;
1314use std:: task:: { Context , Poll } ;
1415use thiserror:: Error ;
1516use tonic:: { self as grpc, Code } ;
1617use tracing:: { debug, warn} ;
1718
19+ pub const L5D_PROXY_ERROR : & str = "l5d-proxy-error" ;
20+
1821metrics ! {
1922 inbound_http_errors_total: Counter {
2023 "The total number of inbound HTTP requests that could not be processed due to a proxy error."
@@ -44,7 +47,7 @@ pub struct LabelError(());
4447#[ derive( Copy , Clone , Debug , Error ) ]
4548#[ error( "{}" , self . message) ]
4649pub struct HttpError {
47- http : http :: StatusCode ,
50+ http : StatusCode ,
4851 grpc : Code ,
4952 message : & ' static str ,
5053 reason : Reason ,
@@ -217,8 +220,12 @@ impl<RspB: Default + hyper::body::HttpBody> respond::Respond<http::Response<RspB
217220 close. close ( ) ;
218221 }
219222
223+ // Set the l5d error header on all responses.
224+ let mut builder = http:: Response :: builder ( ) ;
225+ builder = set_l5d_proxy_error_header ( builder, & * error) ;
226+
220227 if self . is_grpc {
221- let mut rsp = http :: Response :: builder ( )
228+ let mut rsp = builder
222229 . version ( http:: Version :: HTTP_2 )
223230 . header ( http:: header:: CONTENT_LENGTH , "0" )
224231 . header ( http:: header:: CONTENT_TYPE , GRPC_CONTENT_TYPE )
@@ -229,32 +236,83 @@ impl<RspB: Default + hyper::body::HttpBody> respond::Respond<http::Response<RspB
229236 return Ok ( rsp) ;
230237 }
231238
232- let status = http_status ( & * error) ;
233- debug ! ( %status, version = ?self . version, "Handling error with HTTP response" ) ;
234- Ok ( http:: Response :: builder ( )
239+ let rsp = set_http_status ( builder, & * error)
235240 . version ( self . version )
236- . status ( status)
237241 . header ( http:: header:: CONTENT_LENGTH , "0" )
238242 . body ( ResponseBody :: default ( ) )
239- . expect ( "error response must be valid" ) )
243+ . expect ( "error response must be valid" ) ;
244+ let status = rsp. status ( ) ;
245+ debug ! ( %status, version = ?self . version, "Handling error with HTTP response" ) ;
246+ Ok ( rsp)
240247 }
241248 }
242249 }
243250}
244251
245- fn http_status ( error : & ( dyn std:: error:: Error + ' static ) ) -> StatusCode {
252+ fn set_l5d_proxy_error_header (
253+ mut builder : http:: response:: Builder ,
254+ error : & ( dyn std:: error:: Error + ' static ) ,
255+ ) -> http:: response:: Builder {
256+ if let Some ( HttpError { message, .. } ) = error. downcast_ref :: < HttpError > ( ) {
257+ builder. header ( L5D_PROXY_ERROR , HeaderValue :: from_static ( message) )
258+ } else if error. is :: < ResponseTimeout > ( ) {
259+ builder. header (
260+ L5D_PROXY_ERROR ,
261+ HeaderValue :: from_static ( "request timed out" ) ,
262+ )
263+ } else if error. is :: < ConnectTimeout > ( ) {
264+ builder. header (
265+ L5D_PROXY_ERROR ,
266+ HeaderValue :: from_static ( "failed to connect" ) ,
267+ )
268+ } else if let Some ( e) = error. downcast_ref :: < FailFastError > ( ) {
269+ builder. header (
270+ L5D_PROXY_ERROR ,
271+ HeaderValue :: from_str ( & e. to_string ( ) ) . unwrap_or_else ( |error| {
272+ warn ! ( %error, "Failed to encode fail-fast error message" ) ;
273+ HeaderValue :: from_static ( "service in fail-fast" )
274+ } ) ,
275+ )
276+ } else if error. is :: < tower:: timeout:: error:: Elapsed > ( ) {
277+ builder. header (
278+ L5D_PROXY_ERROR ,
279+ HeaderValue :: from_static ( "proxy dispatch timed out" ) ,
280+ )
281+ } else if error. is :: < IdentityRequired > ( ) {
282+ if let Ok ( msg) = HeaderValue :: from_str ( & error. to_string ( ) ) {
283+ builder = builder. header ( L5D_PROXY_ERROR , msg)
284+ }
285+ builder
286+ } else if let Some ( source) = error. source ( ) {
287+ set_l5d_proxy_error_header ( builder, source)
288+ } else {
289+ builder. header (
290+ L5D_PROXY_ERROR ,
291+ HeaderValue :: from_static ( "proxy received invalid response" ) ,
292+ )
293+ }
294+ }
295+
296+ fn set_http_status (
297+ builder : http:: response:: Builder ,
298+ error : & ( dyn std:: error:: Error + ' static ) ,
299+ ) -> http:: response:: Builder {
246300 if let Some ( HttpError { http, .. } ) = error. downcast_ref :: < HttpError > ( ) {
247- * http
301+ builder . status ( * http)
248302 } else if error. is :: < ResponseTimeout > ( ) {
249- http:: StatusCode :: GATEWAY_TIMEOUT
250- } else if error. is :: < FailFastError > ( ) || error. is :: < tower:: timeout:: error:: Elapsed > ( ) {
251- http:: StatusCode :: SERVICE_UNAVAILABLE
303+ builder. status ( StatusCode :: GATEWAY_TIMEOUT )
304+ } else if error. is :: < ConnectTimeout > ( ) {
305+ builder. status ( StatusCode :: GATEWAY_TIMEOUT )
306+ } else if error. is :: < FailFastError > ( ) {
307+ builder. status ( StatusCode :: SERVICE_UNAVAILABLE )
308+ } else if error. is :: < tower:: timeout:: error:: Elapsed > ( ) {
309+ builder. status ( StatusCode :: SERVICE_UNAVAILABLE )
252310 } else if error. is :: < IdentityRequired > ( ) {
253- http :: StatusCode :: FORBIDDEN
311+ builder . status ( StatusCode :: FORBIDDEN )
254312 } else if let Some ( source) = error. source ( ) {
255- http_status ( source)
313+ set_http_status ( builder , source)
256314 } else {
257- http :: StatusCode :: BAD_GATEWAY
315+ builder . status ( StatusCode :: BAD_GATEWAY )
258316 }
259317}
260318
@@ -346,8 +404,8 @@ pub struct IdentityRequired {
346404 pub found : Option < tls:: client:: ServerId > ,
347405}
348406
349- impl std :: fmt:: Display for IdentityRequired {
350- fn fmt ( & self , f : & mut std :: fmt:: Formatter < ' _ > ) -> std :: fmt:: Result {
407+ impl fmt:: Display for IdentityRequired {
408+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
351409 match self . found {
352410 Some ( ref found) => write ! (
353411 f,
@@ -396,7 +454,7 @@ impl error_metrics::LabelError<Error> for LabelError {
396454}
397455
398456impl FmtLabels for Reason {
399- fn fmt_labels ( & self , f : & mut std :: fmt:: Formatter < ' _ > ) -> std :: fmt:: Result {
457+ fn fmt_labels ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
400458 write ! (
401459 f,
402460 "message=\" {}\" " ,
@@ -447,7 +505,7 @@ impl HttpError {
447505 pub fn identity_required ( message : & ' static str ) -> Self {
448506 Self {
449507 message,
450- http : http :: StatusCode :: FORBIDDEN ,
508+ http : StatusCode :: FORBIDDEN ,
451509 grpc : Code :: Unauthenticated ,
452510 reason : Reason :: IdentityRequired ,
453511 }
@@ -456,7 +514,7 @@ impl HttpError {
456514 pub fn not_found ( message : & ' static str ) -> Self {
457515 Self {
458516 message,
459- http : http :: StatusCode :: NOT_FOUND ,
517+ http : StatusCode :: NOT_FOUND ,
460518 grpc : Code :: NotFound ,
461519 reason : Reason :: NotFound ,
462520 }
@@ -465,13 +523,24 @@ impl HttpError {
465523 pub fn gateway_loop ( ) -> Self {
466524 Self {
467525 message : "gateway loop detected" ,
468- http : http :: StatusCode :: LOOP_DETECTED ,
526+ http : StatusCode :: LOOP_DETECTED ,
469527 grpc : Code :: Aborted ,
470528 reason : Reason :: GatewayLoop ,
471529 }
472530 }
473531
474- pub fn status ( & self ) -> http :: StatusCode {
532+ pub fn status ( & self ) -> StatusCode {
475533 self . http
476534 }
477535}
536+
537+ #[ derive( Debug ) ]
538+ pub ( crate ) struct ConnectTimeout ( pub std:: time:: Duration ) ;
539+
540+ impl fmt:: Display for ConnectTimeout {
541+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
542+ write ! ( f, "connect timed out after {:?}" , self . 0 )
543+ }
544+ }
545+
546+ impl std:: error:: Error for ConnectTimeout { }
0 commit comments