From c9ffaae68621f543b848aa447d76411e3656751f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 17 Dec 2024 12:04:33 +0100 Subject: [PATCH] Add rewrite_port on frontends Signed-off-by: Eloi DEMOLIS --- bin/src/ctl/request_builder.rs | 2 + command/src/command.proto | 1 + command/src/config.rs | 5 +++ command/src/request.rs | 1 + command/src/response.rs | 3 ++ lib/src/http.rs | 55 +++-------------------- lib/src/https.rs | 45 ++----------------- lib/src/protocol/kawa_h1/mod.rs | 79 +++++++++++++++++++++++++++------ lib/src/router/mod.rs | 11 ++++- 9 files changed, 97 insertions(+), 105 deletions(-) diff --git a/bin/src/ctl/request_builder.rs b/bin/src/ctl/request_builder.rs index bbb9f2074..a4094aff6 100644 --- a/bin/src/ctl/request_builder.rs +++ b/bin/src/ctl/request_builder.rs @@ -254,6 +254,7 @@ impl CommandManager { redirect_scheme: todo!(), rewrite_host: todo!(), rewrite_path: todo!(), + rewrite_port: todo!(), }) .into(), ), @@ -306,6 +307,7 @@ impl CommandManager { redirect_scheme: todo!(), rewrite_host: todo!(), rewrite_path: todo!(), + rewrite_port: todo!(), }) .into(), ), diff --git a/command/src/command.proto b/command/src/command.proto index 497c93169..7ce05681a 100644 --- a/command/src/command.proto +++ b/command/src/command.proto @@ -265,6 +265,7 @@ message RequestHttpFrontend { optional RedirectScheme redirect_scheme = 9; optional string rewrite_host = 10; optional string rewrite_path = 11; + optional uint32 rewrite_port = 12; } message RequestTcpFrontend { diff --git a/command/src/config.rs b/command/src/config.rs index 4a4d49237..bfd3236bf 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -671,6 +671,7 @@ pub struct FileClusterFrontendConfig { pub redirect_scheme: Option, pub rewrite_host: Option, pub rewrite_path: Option, + pub rewrite_port: Option, } impl FileClusterFrontendConfig { @@ -760,6 +761,7 @@ impl FileClusterFrontendConfig { redirect_scheme: self.redirect_scheme, rewrite_host: self.rewrite_host.clone(), rewrite_path: self.rewrite_path.clone(), + rewrite_port: self.rewrite_port.clone(), }) } } @@ -914,6 +916,7 @@ pub struct HttpFrontendConfig { pub redirect_scheme: Option, pub rewrite_host: Option, pub rewrite_path: Option, + pub rewrite_port: Option, } impl HttpFrontendConfig { @@ -955,6 +958,7 @@ impl HttpFrontendConfig { redirect_scheme: self.redirect_scheme.map(Into::into), rewrite_host: self.rewrite_host.clone(), rewrite_path: self.rewrite_path.clone(), + rewrite_port: self.rewrite_port.map(|x| x as u32), }) .into(), ); @@ -973,6 +977,7 @@ impl HttpFrontendConfig { redirect_scheme: self.redirect_scheme.map(Into::into), rewrite_host: self.rewrite_host.clone(), rewrite_path: self.rewrite_path.clone(), + rewrite_port: self.rewrite_port.map(|x| x as u32), }) .into(), ); diff --git a/command/src/request.rs b/command/src/request.rs index 286f8e349..2dd91a5d0 100644 --- a/command/src/request.rs +++ b/command/src/request.rs @@ -171,6 +171,7 @@ impl RequestHttpFrontend { tags: Some(self.tags), rewrite_host: self.rewrite_host, rewrite_path: self.rewrite_path, + rewrite_port: self.rewrite_port.map(|x| x as u16), }) } } diff --git a/command/src/response.rs b/command/src/response.rs index 292e0ae1f..af91a01cf 100644 --- a/command/src/response.rs +++ b/command/src/response.rs @@ -44,6 +44,8 @@ pub struct HttpFrontend { pub rewrite_host: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rewrite_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rewrite_port: Option, pub tags: Option>, } @@ -61,6 +63,7 @@ impl From for RequestHttpFrontend { redirect_scheme: Some(val.redirect_scheme.into()), rewrite_host: val.rewrite_host, rewrite_path: val.rewrite_path, + rewrite_port: val.rewrite_port.map(|x| x as u32), } } } diff --git a/lib/src/http.rs b/lib/src/http.rs index 8f9c3b7b2..f8197568f 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -430,61 +430,16 @@ impl L7ListenerHandler for HttpListener { self.config.connect_timeout } - // redundant, already called once in extract_route fn frontend_from_request( &self, host: &str, - uri: &str, + path: &str, method: &Method, ) -> Result { - let start = Instant::now(); - let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) { - Ok(tuple) => tuple, - Err(parse_error) => { - // parse_error contains a slice of given_host, which should NOT escape this scope - return Err(FrontendFromRequestError::HostParse { - host: host.to_owned(), - error: parse_error.to_string(), - }); - } - }; - if remaining_input != &b""[..] { - return Err(FrontendFromRequestError::InvalidCharsAfterHost( - host.to_owned(), - )); - } - - /*if port == Some(&b"80"[..]) { - // it is alright to call from_utf8_unchecked, - // we already verified that there are only ascii - // chars in there - unsafe { from_utf8_unchecked(hostname) } - } else { - host - } - */ - let host = unsafe { from_utf8_unchecked(hostname) }; - - let route = self.fronts.lookup(host, uri, method).map_err(|e| { + self.fronts.lookup(host, path, method).map_err(|e| { incr!("http.failed_backend_matching"); FrontendFromRequestError::NoClusterFound(e) - })?; - - let now = Instant::now(); - - if let RouteResult::Flow { - direction: RouteDirection::Forward(cluster_id), - .. - } = &route - { - time!( - "frontend_matching_time", - cluster_id, - (now - start).as_millis() - ); - } - - Ok(route) + }) } } @@ -1339,6 +1294,7 @@ mod tests { redirect_scheme: RedirectScheme::UseSame, rewrite_host: None, rewrite_path: None, + rewrite_port: None, tags: None, }) .expect("Could not add http frontend"); @@ -1354,6 +1310,7 @@ mod tests { redirect_scheme: RedirectScheme::UseSame, rewrite_host: None, rewrite_path: None, + rewrite_port: None, tags: None, }) .expect("Could not add http frontend"); @@ -1369,6 +1326,7 @@ mod tests { redirect_scheme: RedirectScheme::UseSame, rewrite_host: None, rewrite_path: None, + rewrite_port: None, tags: None, }) .expect("Could not add http frontend"); @@ -1384,6 +1342,7 @@ mod tests { redirect_scheme: RedirectScheme::UseSame, rewrite_host: None, rewrite_path: None, + rewrite_port: None, tags: None, }) .expect("Could not add http frontend"); diff --git a/lib/src/https.rs b/lib/src/https.rs index 42a50937d..8047175ae 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -567,52 +567,13 @@ impl L7ListenerHandler for HttpsListener { fn frontend_from_request( &self, host: &str, - uri: &str, + path: &str, method: &Method, ) -> Result { - let start = Instant::now(); - let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) { - Ok(tuple) => tuple, - Err(parse_error) => { - // parse_error contains a slice of given_host, which should NOT escape this scope - return Err(FrontendFromRequestError::HostParse { - host: host.to_owned(), - error: parse_error.to_string(), - }); - } - }; - - if remaining_input != &b""[..] { - return Err(FrontendFromRequestError::InvalidCharsAfterHost( - host.to_owned(), - )); - } - - // it is alright to call from_utf8_unchecked, - // we already verified that there are only ascii - // chars in there - let host = unsafe { from_utf8_unchecked(hostname) }; - - let route = self.fronts.lookup(host, uri, method).map_err(|e| { + self.fronts.lookup(host, path, method).map_err(|e| { incr!("http.failed_backend_matching"); FrontendFromRequestError::NoClusterFound(e) - })?; - - let now = Instant::now(); - - if let RouteResult::Flow { - direction: RouteDirection::Forward(cluster_id), - .. - } = &route - { - time!( - "frontend_matching_time", - cluster_id, - (now - start).as_millis() - ); - } - - Ok(route) + }) } } diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 67fb96fdb..dcac5fc0c 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -8,10 +8,12 @@ use std::{ io::ErrorKind, net::{Shutdown, SocketAddr}, rc::{Rc, Weak}, + str::from_utf8_unchecked, time::{Duration, Instant}, }; use mio::{net::TcpStream, Interest, Token}; +use parser::hostname_and_port; use rusty_ulid::Ulid; use sozu_command::{ config::MAX_LOOP_ITERATIONS, @@ -40,8 +42,9 @@ use crate::{ sozu_command::{logging::LogContext, ready::Ready}, timer::TimeoutContainer, AcceptError, BackendConnectAction, BackendConnectionError, BackendConnectionStatus, - L7ListenerHandler, L7Proxy, ListenerHandler, Protocol, ProxySession, Readiness, - RetrieveClusterError, SessionIsToBeClosed, SessionMetrics, SessionResult, StateResult, + FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerHandler, Protocol, ProxySession, + Readiness, RetrieveClusterError, SessionIsToBeClosed, SessionMetrics, SessionResult, + StateResult, }; /// This macro is defined uniquely in this module to help the tracking of kawa h1 @@ -1257,7 +1260,7 @@ impl Http { self.set_answer(DefaultAnswer::Answer400 { message: "Could not extract the route after connection started, this should not happen.".into(), - phase: self.request_stream.parsing_phase.marker(), + phase: kawa::ParsingPhaseMarker::StatusLine, successfully_parsed: "null".into(), partially_parsed: "null".into(), invalid: "null".into(), @@ -1266,6 +1269,38 @@ impl Http (unsafe { from_utf8_unchecked(hostname) }, port), + Ok(_) => { + let host = host.to_owned(); + self.set_answer(DefaultAnswer::Answer400 { + message: "Invalid characters after hostname, this should not happen.".into(), + phase: kawa::ParsingPhaseMarker::StatusLine, + successfully_parsed: "null".into(), + partially_parsed: "null".into(), + invalid: "null".into(), + }); + return Err(RetrieveClusterError::RetrieveFrontend( + FrontendFromRequestError::InvalidCharsAfterHost(host), + )); + } + Err(parse_error) => { + let host = host.to_owned(); + let error = parse_error.to_string(); + self.set_answer(DefaultAnswer::Answer400 { + message: "Could not parse port from hostname, this should not happen.".into(), + phase: kawa::ParsingPhaseMarker::StatusLine, + successfully_parsed: "null".into(), + partially_parsed: "null".into(), + invalid: "null".into(), + }); + return Err(RetrieveClusterError::RetrieveFrontend( + FrontendFromRequestError::HostParse { host, error }, + )); + } + }; + + let start = Instant::now(); let route_result = self .listener .borrow() @@ -1288,25 +1323,43 @@ impl Http { let is_https = matches!(proxy.borrow().kind(), ListenerType::Https); if let RouteDirection::Forward(cluster_id) = &flow { - if !is_https - && proxy - .borrow() - .clusters() - .get(cluster_id) - .map(|cluster| cluster.https_redirect) - .unwrap_or(false) - { + time!( + "frontend_matching_time", + cluster_id, + start.elapsed().as_millis() + ); + let (https_redirect, https_redirect_port, authentication) = proxy + .borrow() + .clusters() + .get(cluster_id) + .map(|cluster| (cluster.https_redirect, Some(8443), None::<()>)) + .unwrap_or((false, None, None)); + if !is_https && https_redirect { + let port = + https_redirect_port.map_or(String::new(), |port| format!(":{port}")); self.set_answer(DefaultAnswer::Answer301 { - location: format!("https://{host}{path}"), + location: format!("https://{host}{port}{path}"), }); return Err(RetrieveClusterError::Redirected); } + if let Some(authentication) = authentication { + return Err(RetrieveClusterError::UnauthorizedRoute); + } } let host = rewritten_host.as_deref().unwrap_or(host); let path = rewritten_path.as_deref().unwrap_or(path); + let port = rewritten_port.map_or_else( + || { + port.map_or(String::new(), |port| { + format!(":{}", unsafe { from_utf8_unchecked(port) }) + }) + }, + |port| format!(":{port}"), + ); match flow { RouteDirection::Forward(cluster_id) => Ok(cluster_id), RouteDirection::Permanent(redirect_scheme) => { @@ -1319,7 +1372,7 @@ impl Http, rewrite_path: Option, - // rewrite_port? + rewrite_port: Option, }, } @@ -747,6 +748,7 @@ impl Route { redirect_scheme: RedirectScheme, rewrite_host: Option, rewrite_path: Option, + rewrite_port: Option, ) -> Result { let flow = match (cluster_id, redirect) { (Some(cluster_id), RedirectPolicy::Forward) => RouteDirection::Forward(cluster_id), @@ -807,6 +809,7 @@ impl Route { capture_cap_path, rewrite_host, rewrite_path, + rewrite_port, }) } @@ -818,6 +821,7 @@ impl Route { capture_cap_path: 0, rewrite_host: None, rewrite_path: None, + rewrite_port: None, } } } @@ -829,7 +833,7 @@ pub enum RouteResult { direction: RouteDirection, rewritten_host: Option, rewritten_path: Option, - // rewritten_port? + rewritten_port: Option, }, } @@ -847,6 +851,7 @@ impl RouteResult { capture_cap_path, rewrite_host, rewrite_path, + rewrite_port, .. } => { let mut captures_path = Vec::with_capacity(*capture_cap_path); @@ -875,6 +880,7 @@ impl RouteResult { rewritten_path: rewrite_path .as_ref() .map(|rewrite| rewrite.run(&captures_host, &captures_path)), + rewritten_port: *rewrite_port, } } } @@ -955,6 +961,7 @@ impl RouteResult { direction: RouteDirection::Forward(cluster_id), rewritten_host: None, rewritten_path: None, + rewritten_port: None, } } }