From befbde1737734da229ce91683a341cf000d0ddd9 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 10 Aug 2023 10:19:29 +0200 Subject: [PATCH 01/44] Add ALPN field to listener config, change listener builder Signed-off-by: Eloi DEMOLIS --- command/src/command.proto | 6 +++ command/src/config.rs | 84 ++++++++++++++++++++++----------------- lib/src/https.rs | 42 ++++++++++---------- 3 files changed, 76 insertions(+), 56 deletions(-) diff --git a/command/src/command.proto b/command/src/command.proto index ad93b9e34..ab3ea72fe 100644 --- a/command/src/command.proto +++ b/command/src/command.proto @@ -162,6 +162,7 @@ message HttpsListenerConfig { // agains session tracking. Defaults to 4. required uint64 send_tls13_tickets = 20; optional CustomHttpAnswers http_answers = 21; + repeated AlpnProtocol alpn = 22; } // details of an TCP listener @@ -366,6 +367,11 @@ enum TlsVersion { TLS_V1_3 = 5; } +enum AlpnProtocol { + Http11 = 0; + H2 = 1; +} + // A cluster is what binds a frontend to backends with routing rules message Cluster { required string cluster_id = 1; diff --git a/command/src/config.rs b/command/src/config.rs index a121f99ee..ca78cd837 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -61,7 +61,7 @@ use crate::{ certificate::split_certificate_chain, logging::AccessLogFormat, proto::command::{ - request::RequestType, ActivateListener, AddBackend, AddCertificate, CertificateAndKey, + request::RequestType, ActivateListener, AddBackend, AddCertificate, AlpnProtocol, CertificateAndKey, Cluster, CustomHttpAnswers, HttpListenerConfig, HttpsListenerConfig, ListenerType, LoadBalancingAlgorithms, LoadBalancingParams, LoadMetric, MetricsConfiguration, PathRule, ProtobufAccessLogFormat, ProxyProtocolConfig, Request, RequestHttpFrontend, @@ -71,8 +71,10 @@ use crate::{ ObjectKind, }; +pub const DEFAULT_ALPN: [AlpnProtocol; 1] = [AlpnProtocol::Http11]; + /// provides all supported cipher suites exported by Rustls TLS -/// provider as it support only strongly secure ones. +/// provider as it supports only strongly secure ones. /// /// See the [documentation](https://docs.rs/rustls/latest/rustls/static.ALL_CIPHER_SUITES.html) pub const DEFAULT_RUSTLS_CIPHER_LIST: [&str; 9] = [ @@ -240,6 +242,7 @@ pub struct ListenerBuilder { pub address: SocketAddr, pub protocol: Option, pub public_address: Option, + pub alpn: Option>, pub answer_301: Option, pub answer_400: Option, pub answer_401: Option, @@ -302,6 +305,7 @@ impl ListenerBuilder { fn new(address: SocketAddress, protocol: ListenerProtocol) -> ListenerBuilder { ListenerBuilder { address: address.into(), + alpn: None, answer_301: None, answer_401: None, answer_400: None, @@ -331,14 +335,14 @@ impl ListenerBuilder { } } - pub fn with_public_address(&mut self, public_address: Option) -> &mut Self { + pub fn with_public_address(mut self, public_address: Option) -> Self { if let Some(address) = public_address { self.public_address = Some(address); } self } - pub fn with_answer_404_path(&mut self, answer_404_path: Option) -> &mut Self + pub fn with_answer_404_path(mut self, answer_404_path: Option) -> Self where S: ToString, { @@ -348,7 +352,7 @@ impl ListenerBuilder { self } - pub fn with_answer_503_path(&mut self, answer_503_path: Option) -> &mut Self + pub fn with_answer_503_path(mut self, answer_503_path: Option) -> Self where S: ToString, { @@ -358,27 +362,27 @@ impl ListenerBuilder { self } - pub fn with_tls_versions(&mut self, tls_versions: Vec) -> &mut Self { + pub fn with_tls_versions(mut self, tls_versions: Vec) -> Self { self.tls_versions = Some(tls_versions); self } - pub fn with_cipher_list(&mut self, cipher_list: Option>) -> &mut Self { + pub fn with_cipher_list(mut self, cipher_list: Option>) -> Self { self.cipher_list = cipher_list; self } - pub fn with_cipher_suites(&mut self, cipher_suites: Option>) -> &mut Self { + pub fn with_cipher_suites(mut self, cipher_suites: Option>) -> Self { self.cipher_suites = cipher_suites; self } - pub fn with_expect_proxy(&mut self, expect_proxy: bool) -> &mut Self { + pub fn with_expect_proxy(mut self, expect_proxy: bool) -> Self { self.expect_proxy = Some(expect_proxy); self } - pub fn with_sticky_name(&mut self, sticky_name: Option) -> &mut Self + pub fn with_sticky_name(mut self, sticky_name: Option) -> Self where S: ToString, { @@ -388,7 +392,7 @@ impl ListenerBuilder { self } - pub fn with_certificate(&mut self, certificate: S) -> &mut Self + pub fn with_certificate(mut self, certificate: S) -> Self where S: ToString, { @@ -396,12 +400,12 @@ impl ListenerBuilder { self } - pub fn with_certificate_chain(&mut self, certificate_chain: String) -> &mut Self { + pub fn with_certificate_chain(mut self, certificate_chain: String) -> Self { self.certificate = Some(certificate_chain); self } - pub fn with_key(&mut self, key: String) -> &mut Self + pub fn with_key(mut self, key: String) -> Self where S: ToString, { @@ -409,22 +413,22 @@ impl ListenerBuilder { self } - pub fn with_front_timeout(&mut self, front_timeout: Option) -> &mut Self { + pub fn with_front_timeout(mut self, front_timeout: Option) -> Self { self.front_timeout = front_timeout; self } - pub fn with_back_timeout(&mut self, back_timeout: Option) -> &mut Self { + pub fn with_back_timeout(mut self, back_timeout: Option) -> Self { self.back_timeout = back_timeout; self } - pub fn with_connect_timeout(&mut self, connect_timeout: Option) -> &mut Self { + pub fn with_connect_timeout(mut self, connect_timeout: Option) -> Self { self.connect_timeout = connect_timeout; self } - pub fn with_request_timeout(&mut self, request_timeout: Option) -> &mut Self { + pub fn with_request_timeout(mut self, request_timeout: Option) -> Self { self.request_timeout = request_timeout; self } @@ -455,11 +459,11 @@ impl ListenerBuilder { } /// build an HTTP listener with config timeouts, using defaults if no config is provided - pub fn to_http(&mut self, config: Option<&Config>) -> Result { + pub fn to_http(mut self, config: Option<&Config>) -> Result { if self.protocol != Some(ListenerProtocol::Http) { return Err(ConfigError::WrongListenerProtocol { expected: ListenerProtocol::Http, - found: self.protocol.to_owned(), + found: self.protocol, }); } @@ -473,7 +477,7 @@ impl ListenerBuilder { address: self.address.into(), public_address: self.public_address.map(|a| a.into()), expect_proxy: self.expect_proxy.unwrap_or(false), - sticky_name: self.sticky_name.clone(), + sticky_name: self.sticky_name, front_timeout: self.front_timeout.unwrap_or(DEFAULT_FRONT_TIMEOUT), back_timeout: self.back_timeout.unwrap_or(DEFAULT_BACK_TIMEOUT), connect_timeout: self.connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT), @@ -486,34 +490,47 @@ impl ListenerBuilder { } /// build an HTTPS listener using defaults if no config or values were provided upstream - pub fn to_tls(&mut self, config: Option<&Config>) -> Result { + pub fn to_tls(mut self, config: Option<&Config>) -> Result { if self.protocol != Some(ListenerProtocol::Https) { return Err(ConfigError::WrongListenerProtocol { expected: ListenerProtocol::Https, - found: self.protocol.to_owned(), + found: self.protocol, }); } + if let Some(config) = config { + self.assign_config_timeouts(config); + } + + let http_answers = self.get_http_answers()?; + let default_alpn = DEFAULT_ALPN.into_iter().map(|p| p as i32).collect(); + + let alpn = self + .alpn + .as_ref() + .map(|alpn| alpn.iter().map(|p| *p as i32).collect()) + .unwrap_or(default_alpn); + let default_cipher_list = DEFAULT_RUSTLS_CIPHER_LIST .into_iter() .map(String::from) .collect(); - let cipher_list = self.cipher_list.clone().unwrap_or(default_cipher_list); + let cipher_list = self.cipher_list.unwrap_or(default_cipher_list); let default_cipher_suites = DEFAULT_CIPHER_SUITES .into_iter() .map(String::from) .collect(); - let cipher_suites = self.cipher_suites.clone().unwrap_or(default_cipher_suites); + let cipher_suites = self.cipher_suites.unwrap_or(default_cipher_suites); - let signature_algorithms: Vec = DEFAULT_SIGNATURE_ALGORITHMS + let signature_algorithms = DEFAULT_SIGNATURE_ALGORITHMS .into_iter() .map(String::from) .collect(); - let groups_list: Vec = DEFAULT_GROUPS_LIST.into_iter().map(String::from).collect(); + let groups_list = DEFAULT_GROUPS_LIST.into_iter().map(String::from).collect(); let versions = match self.tls_versions { None => vec![TlsVersion::TlsV12 as i32, TlsVersion::TlsV13 as i32], @@ -550,15 +567,10 @@ impl ListenerBuilder { .map(split_certificate_chain) .unwrap_or_default(); - let http_answers = self.get_http_answers()?; - - if let Some(config) = config { - self.assign_config_timeouts(config); - } - let https_listener_config = HttpsListenerConfig { + alpn, address: self.address.into(), - sticky_name: self.sticky_name.clone(), + sticky_name: self.sticky_name, public_address: self.public_address.map(|a| a.into()), cipher_list, versions, @@ -584,11 +596,11 @@ impl ListenerBuilder { } /// build an HTTPS listener using defaults if no config or values were provided upstream - pub fn to_tcp(&mut self, config: Option<&Config>) -> Result { + pub fn to_tcp(mut self, config: Option<&Config>) -> Result { if self.protocol != Some(ListenerProtocol::Tcp) { return Err(ConfigError::WrongListenerProtocol { expected: ListenerProtocol::Tcp, - found: self.protocol.to_owned(), + found: self.protocol, }); } @@ -1284,7 +1296,7 @@ impl ConfigBuilder { } } - fn push_tls_listener(&mut self, mut listener: ListenerBuilder) -> Result<(), ConfigError> { + fn push_tls_listener(&mut self, listener: ListenerBuilder) -> Result<(), ConfigError> { let listener = listener.to_tls(Some(&self.built))?; self.built.https_listeners.push(listener); Ok(()) diff --git a/lib/src/https.rs b/lib/src/https.rs index abe29af4a..e4956cbfd 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -38,10 +38,11 @@ use sozu_command::{ certificate::Fingerprint, config::DEFAULT_CIPHER_SUITES, proto::command::{ - request::RequestType, response_content::ContentType, AddCertificate, CertificateSummary, - CertificatesByAddress, Cluster, HttpsListenerConfig, ListOfCertificatesByAddress, - ListenerType, RemoveCertificate, RemoveListener, ReplaceCertificate, RequestHttpFrontend, - ResponseContent, TlsVersion, WorkerRequest, WorkerResponse, + request::RequestType, response_content::ContentType, AddCertificate, AlpnProtocol, + CertificateSummary, CertificatesByAddress, Cluster, HttpsListenerConfig, + ListOfCertificatesByAddress, ListenerType, RemoveCertificate, RemoveListener, + ReplaceCertificate, RequestHttpFrontend, ResponseContent, TlsVersion, WorkerRequest, + WorkerResponse, }, ready::Ready, response::HttpFrontend, @@ -73,9 +74,6 @@ use crate::{ SessionMetrics, SessionResult, StateMachineBuilder, StateResult, }; -// const SERVER_PROTOS: &[&str] = &["http/1.1", "h2"]; -const SERVER_PROTOS: &[&str] = &["http/1.1"]; - #[derive(Debug, Clone, PartialEq, Eq)] pub struct TlsCluster { cluster_id: String, @@ -99,11 +97,6 @@ StateMachineBuilder! { } } -pub enum AlpnProtocols { - H2, - Http11, -} - pub struct HttpsSession { answers: Rc>, configured_backend_timeout: Duration, @@ -274,14 +267,14 @@ impl HttpsSession { ); let alpn = match alpn { - Some("http/1.1") => AlpnProtocols::Http11, - Some("h2") => AlpnProtocols::H2, + Some("http/1.1") => AlpnProtocol::Http11, + Some("h2") => AlpnProtocol::H2, Some(other) => { error!("Unsupported ALPN protocol: {}", other); return None; } // Some client don't fill in the ALPN protocol, in this case we default to Http/1.1 - None => AlpnProtocols::Http11, + None => AlpnProtocol::Http11, }; if let Some(version) = handshake.session.protocol_version() { @@ -298,7 +291,7 @@ impl HttpsSession { gauge_add!("protocol.tls.handshake", -1); match alpn { - AlpnProtocols::Http11 => { + AlpnProtocol::Http11 => { let mut http = Http::new( self.answers.clone(), self.configured_backend_timeout, @@ -322,7 +315,7 @@ impl HttpsSession { gauge_add!("protocol.https", 1); Some(HttpsStateMachine::Http(http)) } - AlpnProtocols::H2 => { + AlpnProtocol::H2 => { let mut http = Http2::new( front_stream, self.frontend_token, @@ -732,11 +725,20 @@ impl HttpsListener { .with_cert_resolver(resolver); server_config.send_tls13_tickets = config.send_tls13_tickets as usize; - let mut protocols = SERVER_PROTOS + let protocols = config + .alpn .iter() - .map(|proto| proto.as_bytes().to_vec()) + .filter_map(|protocol| match AlpnProtocol::try_from(*protocol) { + Ok(AlpnProtocol::Http11) => Some("http/1.1"), + Ok(AlpnProtocol::H2) => Some("h2"), + other_protocol => { + error!("unsupported ALPN protocol: {:?}", other_protocol); + None + } + }) + .map(|protocol| protocol.as_bytes().to_vec()) .collect::>(); - server_config.alpn_protocols.append(&mut protocols); + server_config.alpn_protocols = protocols; Ok(server_config) } From b11de16fa870e716423034caa83a4f7502190ba1 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 11 Aug 2023 15:18:42 +0200 Subject: [PATCH 02/44] Add h2 flag to frontends and in Router Signed-off-by: Eloi DEMOLIS --- bin/src/cli.rs | 2 + bin/src/ctl/request_builder.rs | 4 + command/src/command.proto | 1 + command/src/config.rs | 10 ++- command/src/proto/display.rs | 4 +- command/src/request.rs | 1 + command/src/response.rs | 2 + command/src/state.rs | 4 +- lib/src/http.rs | 30 +++++-- lib/src/https.rs | 46 ++++++++--- lib/src/protocol/kawa_h1/mod.rs | 4 +- lib/src/router/mod.rs | 137 +++++++++++++++++++++++++------- 12 files changed, 192 insertions(+), 53 deletions(-) diff --git a/bin/src/cli.rs b/bin/src/cli.rs index 61871da46..ea4e8a96e 100644 --- a/bin/src/cli.rs +++ b/bin/src/cli.rs @@ -422,6 +422,8 @@ pub enum HttpFrontendCmd { method: Option, #[clap(long = "tags", help = "Specify tag (key-value pair) to apply on front-end (example: 'key=value, other-key=other-value')", value_parser = parse_tags)] tags: Option>, + #[clap(help = "the frontend uses http2 with prio-knowledge")] + h2: Option, }, #[clap(name = "remove")] Remove { diff --git a/bin/src/ctl/request_builder.rs b/bin/src/ctl/request_builder.rs index eb07929d7..028dac079 100644 --- a/bin/src/ctl/request_builder.rs +++ b/bin/src/ctl/request_builder.rs @@ -238,6 +238,7 @@ impl CommandManager { method, cluster_id: route, tags, + h2, } => self.send_request( RequestType::AddHttpFrontend(RequestHttpFrontend { cluster_id: route.into(), @@ -250,6 +251,7 @@ impl CommandManager { Some(tags) => tags, None => BTreeMap::new(), }, + h2: h2.unwrap_or(false), }) .into(), ), @@ -286,6 +288,7 @@ impl CommandManager { method, cluster_id: route, tags, + h2, } => self.send_request( RequestType::AddHttpsFrontend(RequestHttpFrontend { cluster_id: route.into(), @@ -298,6 +301,7 @@ impl CommandManager { Some(tags) => tags, None => BTreeMap::new(), }, + h2: h2.unwrap_or(false), }) .into(), ), diff --git a/command/src/command.proto b/command/src/command.proto index ab3ea72fe..d4aebe8c6 100644 --- a/command/src/command.proto +++ b/command/src/command.proto @@ -248,6 +248,7 @@ message RequestHttpFrontend { required RulePosition position = 6 [default = TREE]; // custom tags to identify the frontend in the access logs map tags = 7; + required bool h2 = 8; } message RequestTcpFrontend { diff --git a/command/src/config.rs b/command/src/config.rs index ca78cd837..b8794ed4a 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -679,6 +679,7 @@ pub struct FileClusterFrontendConfig { #[serde(default)] pub position: RulePosition, pub tags: Option>, + pub h2: Option, } impl FileClusterFrontendConfig { @@ -764,6 +765,7 @@ impl FileClusterFrontendConfig { path, method: self.method.clone(), tags: self.tags.clone(), + h2: self.h2.unwrap_or(false), }) } } @@ -789,6 +791,7 @@ pub struct FileClusterConfig { pub frontends: Vec, pub backends: Vec, pub protocol: FileClusterProtocolConfig, + pub http_version: Option, pub sticky_session: Option, pub https_redirect: Option, #[serde(default)] @@ -914,6 +917,7 @@ pub struct HttpFrontendConfig { #[serde(default)] pub position: RulePosition, pub tags: Option>, + pub h2: bool, } impl HttpFrontendConfig { @@ -953,6 +957,7 @@ impl HttpFrontendConfig { path: self.path.clone(), method: self.method.clone(), position: self.position.into(), + h2: self.h2, tags, }) .into(), @@ -967,6 +972,7 @@ impl HttpFrontendConfig { path: self.path.clone(), method: self.method.clone(), position: self.position.into(), + h2: self.h2, tags, }) .into(), @@ -1302,13 +1308,13 @@ impl ConfigBuilder { Ok(()) } - fn push_http_listener(&mut self, mut listener: ListenerBuilder) -> Result<(), ConfigError> { + fn push_http_listener(&mut self, listener: ListenerBuilder) -> Result<(), ConfigError> { let listener = listener.to_http(Some(&self.built))?; self.built.http_listeners.push(listener); Ok(()) } - fn push_tcp_listener(&mut self, mut listener: ListenerBuilder) -> Result<(), ConfigError> { + fn push_tcp_listener(&mut self, listener: ListenerBuilder) -> Result<(), ConfigError> { let listener = listener.to_tcp(Some(&self.built))?; self.built.tcp_listeners.push(listener); Ok(()) diff --git a/command/src/proto/display.rs b/command/src/proto/display.rs index f7173d941..75f34e69c 100644 --- a/command/src/proto/display.rs +++ b/command/src/proto/display.rs @@ -54,9 +54,9 @@ impl Display for CertificateSummary { impl Display for QueryCertificatesFilters { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(d) = self.domain.clone() { - write!(f, "domain:{}", d) + write!(f, "domain:{d}") } else if let Some(fp) = self.fingerprint.clone() { - write!(f, "domain:{}", fp) + write!(f, "domain:{fp}") } else { write!(f, "all certificates") } diff --git a/command/src/request.rs b/command/src/request.rs index f43e7c621..496140ce2 100644 --- a/command/src/request.rs +++ b/command/src/request.rs @@ -173,6 +173,7 @@ impl RequestHttpFrontend { } })?, tags: Some(self.tags), + h2: self.h2, }) } } diff --git a/command/src/response.rs b/command/src/response.rs index 341dbc7ae..9b1b87c10 100644 --- a/command/src/response.rs +++ b/command/src/response.rs @@ -39,6 +39,7 @@ pub struct HttpFrontend { #[serde(default)] pub position: RulePosition, pub tags: Option>, + pub h2: bool, } impl From for RequestHttpFrontend { @@ -54,6 +55,7 @@ impl From for RequestHttpFrontend { path: val.path, method: val.method, position: val.position.into(), + h2: val.h2, tags, } } diff --git a/command/src/state.rs b/command/src/state.rs index bb40f4494..02ab78fb6 100644 --- a/command/src/state.rs +++ b/command/src/state.rs @@ -465,7 +465,7 @@ impl ConfigState { if tcp_frontends.contains(&tcp_frontend) { return Err(StateError::Exists { kind: ObjectKind::TcpFrontend, - id: format!("{:?}", tcp_frontend), + id: format!("{tcp_frontend:?}"), }); } @@ -482,7 +482,7 @@ impl ConfigState { .get_mut(&front_to_remove.cluster_id) .ok_or(StateError::NotFound { kind: ObjectKind::TcpFrontend, - id: format!("{:?}", front_to_remove), + id: format!("{front_to_remove:?}"), })?; let len = tcp_frontends.len(); diff --git a/lib/src/http.rs b/lib/src/http.rs index b1be32ec9..51e29ecc6 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -472,7 +472,7 @@ impl L7ListenerHandler for HttpListener { let now = Instant::now(); - if let Route::ClusterId(cluster) = &route { + if let Route::Cluster { id: cluster, .. } = &route { time!("frontend_matching_time", cluster, (now - start).as_millis()); } @@ -688,7 +688,7 @@ impl HttpProxy { if !socket_errors.is_empty() { return Err(ProxyError::SoftStop { proxy_protocol: "HTTP".to_string(), - error: format!("Error deregistering listen sockets: {:?}", socket_errors), + error: format!("Error deregistering listen sockets: {socket_errors:?}"), }); } @@ -711,7 +711,7 @@ impl HttpProxy { if !socket_errors.is_empty() { return Err(ProxyError::HardStop { proxy_protocol: "HTTP".to_string(), - error: format!("Error deregistering listen sockets: {:?}", socket_errors), + error: format!("Error deregistering listen sockets: {socket_errors:?}"), }); } @@ -1326,6 +1326,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id1), tags: None, + h2: false, }) .expect("Could not add http frontend"); fronts @@ -1337,6 +1338,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id2), tags: None, + h2: false, }) .expect("Could not add http frontend"); fronts @@ -1348,6 +1350,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id3), tags: None, + h2: false, }) .expect("Could not add http frontend"); fronts @@ -1359,6 +1362,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some("cluster_1".to_owned()), tags: None, + h2: false, }) .expect("Could not add http frontend"); @@ -1388,19 +1392,31 @@ mod tests { let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get); assert_eq!( frontend1.expect("should find frontend"), - Route::ClusterId("cluster_1".to_string()) + Route::Cluster { + id: "cluster_1".to_string(), + h2: false + } ); assert_eq!( frontend2.expect("should find frontend"), - Route::ClusterId("cluster_1".to_string()) + Route::Cluster { + id: "cluster_1".to_string(), + h2: false + } ); assert_eq!( frontend3.expect("should find frontend"), - Route::ClusterId("cluster_2".to_string()) + Route::Cluster { + id: "cluster_2".to_string(), + h2: false + } ); assert_eq!( frontend4.expect("should find frontend"), - Route::ClusterId("cluster_3".to_string()) + Route::Cluster { + id: "cluster_3".to_string(), + h2: false + } ); assert!(frontend5.is_err()); } diff --git a/lib/src/https.rs b/lib/src/https.rs index e4956cbfd..3fe35bce5 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -599,7 +599,7 @@ impl L7ListenerHandler for HttpsListener { let now = Instant::now(); - if let Route::ClusterId(cluster) = &route { + if let Route::Cluster { id: cluster, .. } = &route { time!("frontend_matching_time", cluster, (now - start).as_millis()); } @@ -848,7 +848,7 @@ impl HttpsProxy { if !socket_errors.is_empty() { return Err(ProxyError::SoftStop { proxy_protocol: "HTTPS".to_string(), - error: format!("Error deregistering listen sockets: {:?}", socket_errors), + error: format!("Error deregistering listen sockets: {socket_errors:?}"), }); } @@ -871,7 +871,7 @@ impl HttpsProxy { if !socket_errors.is_empty() { return Err(ProxyError::HardStop { proxy_protocol: "HTTPS".to_string(), - error: format!("Error deregistering listen sockets: {:?}", socket_errors), + error: format!("Error deregistering listen sockets: {socket_errors:?}"), }); } @@ -1550,25 +1550,37 @@ mod tests { "lolcatho.st".as_bytes(), &PathRule::Prefix(uri1), &MethodRule::new(None), - &Route::ClusterId(cluster_id1.clone()) + &Route::Cluster { + id: cluster_id1.clone(), + h2: false + } )); assert!(fronts.add_tree_rule( "lolcatho.st".as_bytes(), &PathRule::Prefix(uri2), &MethodRule::new(None), - &Route::ClusterId(cluster_id2) + &Route::Cluster { + id: cluster_id2, + h2: false + } )); assert!(fronts.add_tree_rule( "lolcatho.st".as_bytes(), &PathRule::Prefix(uri3), &MethodRule::new(None), - &Route::ClusterId(cluster_id3) + &Route::Cluster { + id: cluster_id3, + h2: false + } )); assert!(fronts.add_tree_rule( "other.domain".as_bytes(), &PathRule::Prefix("test".to_string()), &MethodRule::new(None), - &Route::ClusterId(cluster_id1) + &Route::Cluster { + id: cluster_id1, + h2: false + } )); let address = SocketAddress::new_v4(127, 0, 0, 1, 1032); @@ -1609,25 +1621,37 @@ mod tests { let frontend1 = listener.frontend_from_request("lolcatho.st", "/", &Method::Get); assert_eq!( frontend1.expect("should find a frontend"), - Route::ClusterId("cluster_1".to_string()) + Route::Cluster { + id: "cluster_1".to_string(), + h2: false + } ); println!("TEST {}", line!()); let frontend2 = listener.frontend_from_request("lolcatho.st", "/test", &Method::Get); assert_eq!( frontend2.expect("should find a frontend"), - Route::ClusterId("cluster_1".to_string()) + Route::Cluster { + id: "cluster_1".to_string(), + h2: false + } ); println!("TEST {}", line!()); let frontend3 = listener.frontend_from_request("lolcatho.st", "/yolo/test", &Method::Get); assert_eq!( frontend3.expect("should find a frontend"), - Route::ClusterId("cluster_2".to_string()) + Route::Cluster { + id: "cluster_2".to_string(), + h2: false + } ); println!("TEST {}", line!()); let frontend4 = listener.frontend_from_request("lolcatho.st", "/yolo/swag", &Method::Get); assert_eq!( frontend4.expect("should find a frontend"), - Route::ClusterId("cluster_3".to_string()) + Route::Cluster { + id: "cluster_3".to_string(), + h2: false + } ); println!("TEST {}", line!()); let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get); diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 797981776..13df4746f 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -1264,8 +1264,8 @@ impl Http cluster_id, + let (cluster_id, _) = match route { + Route::Cluster { id, h2 } => (id, h2), Route::Deny => { self.set_answer(DefaultAnswer::Answer401 {}); return Err(RetrieveClusterError::UnauthorizedRoute); diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index acc37e9a6..06c5b453d 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -133,7 +133,10 @@ impl Router { let method_rule = MethodRule::new(front.method.clone()); let route = match &front.cluster_id { - Some(cluster_id) => Route::ClusterId(cluster_id.clone()), + Some(cluster_id) => Route::Cluster { + id: cluster_id.clone(), + h2: front.h2, + }, None => Route::Deny, }; @@ -161,7 +164,7 @@ impl Router { } }; if !success { - return Err(RouterError::AddRoute(format!("{:?}", front))); + return Err(RouterError::AddRoute(format!("{front:?}"))); } Ok(()) } @@ -196,7 +199,7 @@ impl Router { } }; if !remove_success { - return Err(RouterError::RemoveRoute(format!("{:?}", front))); + return Err(RouterError::RemoveRoute(format!("{front:?}"))); } Ok(()) } @@ -597,7 +600,7 @@ pub enum Route { /// send a 401 default answer Deny, /// the cluster to which the frontend belongs - ClusterId(ClusterId), + Cluster { id: ClusterId, h2: bool }, } #[cfg(test)] @@ -716,27 +719,42 @@ mod tests { b"*.sozu.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("base".to_string()) + &Route::Cluster { + id: "base".to_string(), + h2: false + } )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::ClusterId("base".to_string())) + Ok(Route::Cluster { + id: "base".to_string(), + h2: false + }) ); assert!(router.add_tree_rule( b"*.sozu.io", &PathRule::Prefix("/api".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("api".to_string()) + &Route::Cluster { + id: "api".to_string(), + h2: false + } )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/ap", &Method::Get), - Ok(Route::ClusterId("base".to_string())) + Ok(Route::Cluster { + id: "base".to_string(), + h2: false + }) ); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::ClusterId("api".to_string())) + Ok(Route::Cluster { + id: "api".to_string(), + h2: false + }) ); } @@ -755,27 +773,42 @@ mod tests { b"*.sozu.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("base".to_string()) + &Route::Cluster { + id: "base".to_string(), + h2: false + } )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::ClusterId("base".to_string())) + Ok(Route::Cluster { + id: "base".to_string(), + h2: false + }) ); assert!(router.add_tree_rule( b"api.sozu.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("api".to_string()) + &Route::Cluster { + id: "api".to_string(), + h2: false + } )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::ClusterId("base".to_string())) + Ok(Route::Cluster { + id: "base".to_string(), + h2: false + }) ); assert_eq!( router.lookup("api.sozu.io", "/api", &Method::Get), - Ok(Route::ClusterId("api".to_string())) + Ok(Route::Cluster { + id: "api".to_string(), + h2: false + }) ); } @@ -787,23 +820,35 @@ mod tests { b"www./.*/.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("base".to_string()) + &Route::Cluster { + id: "base".to_string(), + h2: false + } )); println!("{:#?}", router.tree); assert!(router.add_tree_rule( b"www.doc./.*/.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("doc".to_string()) + &Route::Cluster { + id: "doc".to_string(), + h2: false + } )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/", &Method::Get), - Ok(Route::ClusterId("base".to_string())) + Ok(Route::Cluster { + id: "base".to_string(), + h2: false + }) ); assert_eq!( router.lookup("www.doc.sozu.io", "/", &Method::Get), - Ok(Route::ClusterId("doc".to_string())) + Ok(Route::Cluster { + id: "doc".to_string(), + h2: false + }) ); assert!(router.remove_tree_rule( b"www./.*/.io", @@ -814,7 +859,10 @@ mod tests { assert!(router.lookup("www.sozu.io", "/", &Method::Get).is_err()); assert_eq!( router.lookup("www.doc.sozu.io", "/", &Method::Get), - Ok(Route::ClusterId("doc".to_string())) + Ok(Route::Cluster { + id: "doc".to_string(), + h2: false + }) ); } @@ -826,30 +874,45 @@ mod tests { &"*".parse::().unwrap(), &PathRule::Prefix("/.well-known/acme-challenge".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("acme".to_string()) + &Route::Cluster { + id: "acme".to_string(), + h2: false + } )); assert!(router.add_tree_rule( "www.example.com".as_bytes(), &PathRule::Prefix("/".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("example".to_string()) + &Route::Cluster { + id: "example".to_string(), + h2: false + } )); assert!(router.add_tree_rule( "*.test.example.com".as_bytes(), &PathRule::Regex(Regex::new("/hello[A-Z]+/").unwrap()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("examplewildcard".to_string()) + &Route::Cluster { + id: "examplewildcard".to_string(), + h2: false + } )); assert!(router.add_tree_rule( "/test[0-9]/.example.com".as_bytes(), &PathRule::Prefix("/".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::ClusterId("exampleregex".to_string()) + &Route::Cluster { + id: "exampleregex".to_string(), + h2: false + } )); assert_eq!( router.lookup("www.example.com", "/helloA", &Method::new(&b"GET"[..])), - Ok(Route::ClusterId("example".to_string())) + Ok(Route::Cluster { + id: "example".to_string(), + h2: false + }) ); assert_eq!( router.lookup( @@ -857,7 +920,10 @@ mod tests { "/.well-known/acme-challenge", &Method::new(&b"GET"[..]) ), - Ok(Route::ClusterId("acme".to_string())) + Ok(Route::Cluster { + id: "acme".to_string(), + h2: false + }) ); assert!(router .lookup("www.test.example.com", "/", &Method::new(&b"GET"[..])) @@ -868,11 +934,28 @@ mod tests { "/helloAB/", &Method::new(&b"GET"[..]) ), - Ok(Route::ClusterId("examplewildcard".to_string())) + Ok(Route::Cluster { + id: "examplewildcard".to_string(), + h2: true + }) + ); + assert_eq!( + router.lookup( + "www.test.example.com", + "/helloAB/", + &Method::new(&b"GET"[..]) + ), + Ok(Route::Cluster { + id: "examplewildcard".to_string(), + h2: false + }) ); assert_eq!( router.lookup("test1.example.com", "/helloAB/", &Method::new(&b"GET"[..])), - Ok(Route::ClusterId("exampleregex".to_string())) + Ok(Route::Cluster { + id: "exampleregex".to_string(), + h2: false + }) ); } } From 86213e8b768c06bb494a96c20a822a3ff260eecc Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 14 Aug 2023 20:32:33 +0200 Subject: [PATCH 03/44] Mux SessionState (test) Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 54 +++- lib/src/protocol/mod.rs | 1 + lib/src/protocol/mux/mod.rs | 446 +++++++++++++++++++++++++++ lib/src/protocol/mux/parser.rs | 468 +++++++++++++++++++++++++++++ lib/src/protocol/mux/serializer.rs | 37 +++ 5 files changed, 1004 insertions(+), 2 deletions(-) create mode 100644 lib/src/protocol/mux/mod.rs create mode 100644 lib/src/protocol/mux/parser.rs create mode 100644 lib/src/protocol/mux/serializer.rs diff --git a/lib/src/https.rs b/lib/src/https.rs index 3fe35bce5..344842774 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -59,6 +59,7 @@ use crate::{ parser::{hostname_and_port, Method}, ResponseStream, }, + mux::Mux, proxy_protocol::expect::ExpectProxyProtocol, rustls::TlsHandshake, Http, Pipe, SessionState, @@ -70,8 +71,8 @@ use crate::{ tls::MutexCertificateResolver, util::UnwrapLog, AcceptError, CachedTags, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerError, - ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, SessionIsToBeClosed, - SessionMetrics, SessionResult, StateMachineBuilder, StateResult, + ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, Readiness, + SessionIsToBeClosed, SessionMetrics, SessionResult, StateMachineBuilder, StateResult, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -91,6 +92,7 @@ StateMachineBuilder! { enum HttpsStateMachine impl SessionState { Expect(ExpectProxyProtocol, ServerConnection), Handshake(TlsHandshake), + Mux(Mux), Http(Http), WebSocket(Pipe), Http2(Http2) -> todo!("H2"), @@ -189,6 +191,7 @@ impl HttpsSession { HttpsStateMachine::Expect(expect, ssl) => self.upgrade_expect(expect, ssl), HttpsStateMachine::Handshake(handshake) => self.upgrade_handshake(handshake), HttpsStateMachine::Http(http) => self.upgrade_http(http), + HttpsStateMachine::Mux(mux) => unimplemented!(), HttpsStateMachine::Http2(_) => self.upgrade_http2(), HttpsStateMachine::WebSocket(wss) => self.upgrade_websocket(wss), HttpsStateMachine::FailedUpgrade(_) => unreachable!(), @@ -276,6 +279,7 @@ impl HttpsSession { // Some client don't fill in the ALPN protocol, in this case we default to Http/1.1 None => AlpnProtocol::Http11, }; + println!("ALPN: {alpn:?}"); if let Some(version) = handshake.session.protocol_version() { incr!(rustls_version_str(version)); @@ -290,6 +294,50 @@ impl HttpsSession { }; gauge_add!("protocol.tls.handshake", -1); + // return Some(HttpsStateMachine::Mux(Mux::new( + // self.frontend_token, + // handshake.request_id, + // self.listener.clone(), + // self.pool.clone(), + // self.public_address, + // self.peer_address, + // self.sticky_name.clone(), + // ))); + use crate::protocol::mux; + let frontend = match alpn { + AlpnProtocol::Http11 => mux::Connection::H1(mux::ConnectionH1 { + socket: front_stream, + position: mux::Position::Server, + readiness: Readiness { + interest: Ready::READABLE | Ready::HUP | Ready::ERROR, + event: handshake.frontend_readiness.event, + }, + stream: 0, + }), + AlpnProtocol::H2 => mux::Connection::H2(mux::ConnectionH2 { + socket: front_stream, + position: mux::Position::Server, + readiness: Readiness { + interest: Ready::READABLE | Ready::HUP | Ready::ERROR, + event: handshake.frontend_readiness.event, + }, + streams: HashMap::new(), + state: mux::H2State::ClientPreface, + }), + }; + let mut mux = Mux { + frontend_token: self.frontend_token, + frontend, + backends: HashMap::new(), + streams: Vec::new(), + listener: self.listener.clone(), + pool: self.pool.clone(), + public_address: self.public_address, + peer_address: self.peer_address, + sticky_name: self.sticky_name.clone(), + }; + mux.create_stream(handshake.request_id).ok()?; + return Some(HttpsStateMachine::Mux(mux)); match alpn { AlpnProtocol::Http11 => { let mut http = Http::new( @@ -416,6 +464,7 @@ impl ProxySession for HttpsSession { StateMarker::Expect => gauge_add!("protocol.proxy.expect", -1), StateMarker::Handshake => gauge_add!("protocol.tls.handshake", -1), StateMarker::Http => gauge_add!("protocol.https", -1), + StateMarker::Mux => gauge_add!("protocol.https", -1), StateMarker::WebSocket => { gauge_add!("protocol.wss", -1); gauge_add!("websocket.active_requests", -1); @@ -484,6 +533,7 @@ impl ProxySession for HttpsSession { } fn ready(&mut self, session: Rc>) -> SessionIsToBeClosed { + println!("READY"); self.metrics.service_start(); let session_result = diff --git a/lib/src/protocol/mod.rs b/lib/src/protocol/mod.rs index 1342173a8..88dde18cb 100644 --- a/lib/src/protocol/mod.rs +++ b/lib/src/protocol/mod.rs @@ -1,5 +1,6 @@ pub mod h2; pub mod kawa_h1; +pub mod mux; pub mod pipe; pub mod proxy_protocol; pub mod rustls; diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs new file mode 100644 index 000000000..16476349e --- /dev/null +++ b/lib/src/protocol/mux/mod.rs @@ -0,0 +1,446 @@ +use std::{ + cell::RefCell, + collections::HashMap, + io::Write, + net::SocketAddr, + rc::{Rc, Weak}, +}; + +use mio::{net::TcpStream, Token}; +use rusty_ulid::Ulid; +use sozu_command::ready::Ready; + +mod parser; +mod serializer; + +use crate::{ + https::HttpsListener, + pool::{Checkout, Pool}, + protocol::SessionState, + socket::{FrontRustls, SocketHandler, SocketResult}, + AcceptError, L7Proxy, ProxySession, Readiness, SessionMetrics, SessionResult, StateResult, +}; + +/// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer +type GenericHttpStream = kawa::Kawa; +type StreamId = usize; +type GlobalStreamId = usize; + +pub enum Position { + Client, + Server, +} + +pub struct ConnectionH1 { + pub socket: Front, + pub position: Position, + pub readiness: Readiness, + pub stream: GlobalStreamId, +} + +pub enum H2State { + ClientPreface, + ServerPreface, + Connected, + Error, +} +pub struct ConnectionH2 { + pub socket: Front, + pub position: Position, + pub readiness: Readiness, + pub state: H2State, + pub streams: HashMap, + // context_hpack: HpackContext, + // settings: SettiongsH2, +} +pub struct Stream { + pub request_id: Ulid, + pub front: GenericHttpStream, + pub back: GenericHttpStream, +} + +pub enum Connection { + H1(ConnectionH1), + H2(ConnectionH2), +} +impl Connection { + fn readiness(&self) -> &Readiness { + match self { + Connection::H1(c) => &c.readiness, + Connection::H2(c) => &c.readiness, + } + } + fn readiness_mut(&mut self) -> &mut Readiness { + match self { + Connection::H1(c) => &mut c.readiness, + Connection::H2(c) => &mut c.readiness, + } + } + fn readable(&mut self, streams: &mut [Stream]) { + match self { + Connection::H1(c) => c.readable(streams), + Connection::H2(c) => c.readable(streams), + } + } + fn writable(&mut self, streams: &mut [Stream]) { + match self { + Connection::H1(c) => c.writable(streams), + Connection::H2(c) => c.writable(streams), + } + } +} + +pub struct Mux { + pub frontend_token: Token, + pub frontend: Connection, + pub backends: HashMap>, + pub streams: Vec, + pub listener: Rc>, + pub pool: Weak>, + pub public_address: SocketAddr, + pub peer_address: Option, + pub sticky_name: String, +} + +impl SessionState for Mux { + fn ready( + &mut self, + session: Rc>, + proxy: Rc>, + metrics: &mut SessionMetrics, + ) -> SessionResult { + let mut counter = 0; + let max_loop_iterations = 100000; + + if self.frontend.readiness().event.is_hup() { + return SessionResult::Close; + } + + let streams = &mut self.streams; + while counter < max_loop_iterations { + let mut dirty = false; + + if self.frontend.readiness().filter_interest().is_readable() { + self.frontend.readable(streams); + dirty = true; + } + + for (_, backend) in self.backends.iter_mut() { + if backend.readiness().filter_interest().is_writable() { + backend.writable(streams); + dirty = true; + } + + if backend.readiness().filter_interest().is_readable() { + backend.readable(streams); + dirty = true; + } + } + + if self.frontend.readiness().filter_interest().is_writable() { + self.frontend.writable(streams); + dirty = true; + } + + for backend in self.backends.values() { + if backend.readiness().filter_interest().is_hup() + || backend.readiness().filter_interest().is_error() + { + return SessionResult::Close; + } + } + + if !dirty { + break; + } + + counter += 1; + } + + if counter == max_loop_iterations { + incr!("http.infinite_loop.error"); + return SessionResult::Close; + } + + SessionResult::Continue + } + + fn update_readiness(&mut self, token: Token, events: sozu_command::ready::Ready) { + if token == self.frontend_token { + self.frontend.readiness_mut().event |= events; + } else if let Some(c) = self.backends.get_mut(&token) { + c.readiness_mut().event |= events; + } + } + + fn timeout(&mut self, token: Token, metrics: &mut SessionMetrics) -> StateResult { + println!("MuxState::timeout({token:?})"); + StateResult::CloseSession + } + + fn cancel_timeouts(&mut self) { + println!("MuxState::cancel_timeouts"); + } + + fn print_state(&self, context: &str) { + error!( + "\ +{} Session(Mux) +\tFrontend: +\t\ttoken: {:?}\treadiness: {:?}", + context, + self.frontend_token, + self.frontend.readiness() + ); + } + fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) { + let s = match &mut self.frontend { + Connection::H1(c) => &mut c.socket, + Connection::H2(c) => &mut c.socket, + }; + let mut b = [0; 1024]; + let (size, status) = s.socket_read(&mut b); + println!("{size} {status:?} {:?}", &b[..size]); + } +} + +impl Mux { + // pub fn new( + // frontend_token: Token, + // request_id: Ulid, + // listener: Rc>, + // pool: Weak>, + // public_address: SocketAddr, + // peer_address: Option, + // sticky_name: String, + // ) -> Self { + // Self { + // frontend_token, + // frontend: todo!(), + // backends: todo!(), + // streams: todo!(), + // } + // } + pub fn front_socket(&self) -> &TcpStream { + match &self.frontend { + Connection::H1(c) => &c.socket.stream, + Connection::H2(c) => &c.socket.stream, + } + } + + pub fn create_stream(&mut self, request_id: Ulid) -> Result { + let (front_buffer, back_buffer) = match self.pool.upgrade() { + Some(pool) => { + let mut pool = pool.borrow_mut(); + match (pool.checkout(), pool.checkout()) { + (Some(front_buffer), Some(back_buffer)) => (front_buffer, back_buffer), + _ => return Err(AcceptError::BufferCapacityReached), + } + } + None => return Err(AcceptError::BufferCapacityReached), + }; + self.streams.push(Stream { + request_id, + front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), + back: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(back_buffer)), + }); + Ok(self.streams.len() - 1) + } +} + +impl ConnectionH2 { + fn readable(&mut self, streams: &mut [Stream]) { + println!("======= MUX H2 READABLE"); + match (&self.state, &self.position) { + (H2State::ClientPreface, Position::Client) => { + error!("Waiting for ClientPreface to finish writing") + } + (H2State::ClientPreface, Position::Server) => { + let stream = &mut streams[0]; + let kawa = &mut stream.front; + let mut i = [0; 33]; + + // let (size, status) = self.socket.socket_read(kawa.storage.space()); + // println!("{:02x?}", &kawa.storage.buffer()[..size]); + // unreachable!(); + + let (size, status) = self.socket.socket_read(&mut i); + + println!("{size} {status:?} {i:02x?}"); + let i = match parser::preface(&i) { + Ok((i, _)) => i, + Err(_) => todo!(), + }; + let header = match parser::frame_header(&i) { + Ok(( + _, + header @ parser::FrameHeader { + payload_len: _, + frame_type: parser::FrameType::Settings, + flags: 0, + stream_id: 0, + }, + )) => header, + _ => todo!(), + }; + let (size, status) = self + .socket + .socket_read(&mut kawa.storage.space()[..header.payload_len as usize]); + kawa.storage.fill(size); + let i = kawa.storage.data(); + println!(" {size} {status:?} {i:02x?}"); + match parser::settings_frame(i, &header) { + Ok((_, settings)) => println!("{settings:?}"), + Err(_) => todo!(), + } + let kawa = &mut stream.back; + self.state = H2State::ServerPreface; + match serializer::gen_frame_header( + kawa.storage.space(), + &parser::FrameHeader { + payload_len: 6 * 2, + frame_type: parser::FrameType::Settings, + flags: 0, + stream_id: 0, + }, + ) { + Ok((_, size)) => kawa.storage.fill(size), + Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), + }; + // kawa.storage + // .write(&[1, 3, 0, 0, 0, 100, 0, 4, 0, 1, 0, 0]) + // .unwrap(); + match serializer::gen_frame_header( + kawa.storage.space(), + &parser::FrameHeader { + payload_len: 0, + frame_type: parser::FrameType::Settings, + flags: 1, + stream_id: 0, + }, + ) { + Ok((_, size)) => kawa.storage.fill(size), + Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), + }; + self.readiness.interest.insert(Ready::WRITABLE); + self.readiness.interest.remove(Ready::READABLE); + } + (H2State::ServerPreface, Position::Client) => todo!("Receive server Settings"), + (H2State::ServerPreface, Position::Server) => { + error!("waiting for ServerPreface to finish writing") + } + (H2State::Connected, Position::Server) => { + let mut header = [0; 9]; + let (size, status) = self.socket.socket_read(&mut header); + println!(" size: {size}, status: {status:?}"); + if size == 0 { + self.readiness.event.remove(Ready::READABLE); + return; + } + println!("{:?}", &header[..size]); + let len = match parser::frame_header(&header) { + Ok((_, h)) => { + println!("{h:?}"); + h.payload_len as usize + } + Err(_) => { + self.state = H2State::Error; + return; + } + }; + let kawa = &mut streams[0].front; + kawa.storage.clear(); + let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..len]); + kawa.storage.fill(size); + let i = kawa.storage.data(); + println!(" {size} {status:?} {i:?}"); + } + _ => unreachable!(), + } + } + fn writable(&mut self, streams: &mut [Stream]) { + println!("======= MUX H2 WRITABLE"); + match (&self.state, &self.position) { + (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), + (H2State::ClientPreface, Position::Server) => unreachable!(), + (H2State::ServerPreface, Position::Client) => unreachable!(), + (H2State::Connected, Position::Server) | (H2State::ServerPreface, Position::Server) => { + let stream = &mut streams[0]; + let kawa = &mut stream.back; + println!("{:?}", kawa.storage.data()); + let (size, status) = self.socket.socket_write(kawa.storage.data()); + println!(" size: {size}, status: {status:?}"); + let size = kawa.storage.available_data(); + kawa.storage.consume(size); + if kawa.storage.is_empty() { + self.readiness.interest.remove(Ready::WRITABLE); + self.readiness.interest.insert(Ready::READABLE); + self.state = H2State::Connected; + } + } + _ => unreachable!(), + } + // for global_stream_id in self.streams.values() { + // let stream = &mut streams[*global_stream_id]; + // let kawa = match self.position { + // Position::Client => &mut stream.back, + // Position::Server => &mut stream.front, + // }; + // kawa.prepare(&mut kawa::h2::BlockConverter); + // let (size, status) = self.socket.socket_write_vectored(&kawa.as_io_slice()); + // println!(" size: {size}, status: {status:?}"); + // kawa.consume(size); + // } + } +} + +impl ConnectionH1 { + fn readable(&mut self, streams: &mut [Stream]) { + println!("======= MUX H1 READABLE"); + let stream = &mut streams[self.stream]; + let kawa = match self.position { + Position::Client => &mut stream.front, + Position::Server => &mut stream.back, + }; + let (size, status) = self.socket.socket_read(kawa.storage.space()); + println!(" size: {size}, status: {status:?}"); + if size > 0 { + kawa.storage.fill(size); + } else { + self.readiness.event.remove(Ready::READABLE); + } + match status { + SocketResult::Continue => {} + SocketResult::Closed => todo!(), + SocketResult::Error => todo!(), + SocketResult::WouldBlock => self.readiness.event.remove(Ready::READABLE), + } + kawa::h1::parse(kawa, &mut kawa::h1::NoCallbacks); + kawa::debug_kawa(kawa); + if kawa.is_terminated() { + self.readiness.interest.remove(Ready::READABLE); + } + } + fn writable(&mut self, streams: &mut [Stream]) { + println!("======= MUX H1 WRITABLE"); + let stream = &mut streams[self.stream]; + let kawa = match self.position { + Position::Client => &mut stream.back, + Position::Server => &mut stream.front, + }; + kawa.prepare(&mut kawa::h1::BlockConverter); + let bufs = kawa.as_io_slice(); + if bufs.is_empty() { + self.readiness.interest.remove(Ready::WRITABLE); + return; + } + let (size, status) = self.socket.socket_write_vectored(&bufs); + println!(" size: {size}, status: {status:?}"); + if size > 0 { + kawa.consume(size); + // self.backend_readiness.interest.insert(Ready::READABLE); + } else { + self.readiness.event.remove(Ready::WRITABLE); + } + } +} diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs new file mode 100644 index 000000000..1b53da321 --- /dev/null +++ b/lib/src/protocol/mux/parser.rs @@ -0,0 +1,468 @@ +use std::convert::From; + +use nom::{ + bytes::streaming::{tag, take}, + combinator::{complete, map, map_opt}, + error::{ErrorKind, ParseError}, + multi::many0, + number::streaming::{be_u16, be_u24, be_u32, be_u8}, + sequence::tuple, + Err, HexDisplay, IResult, Offset, +}; + +#[derive(Clone, Debug, PartialEq)] +pub struct FrameHeader { + pub payload_len: u32, + pub frame_type: FrameType, + pub flags: u8, + pub stream_id: u32, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum FrameType { + Data, + Headers, + Priority, + RstStream, + Settings, + PushPromise, + Ping, + GoAway, + WindowUpdate, + Continuation, +} + +/* +const NO_ERROR: u32 = 0x0; +const PROTOCOL_ERROR: u32 = 0x1; +const INTERNAL_ERROR: u32 = 0x2; +const FLOW_CONTROL_ERROR: u32 = 0x3; +const SETTINGS_TIMEOUT: u32 = 0x4; +const STREAM_CLOSED: u32 = 0x5; +const FRAME_SIZE_ERROR: u32 = 0x6; +const REFUSED_STREAM: u32 = 0x7; +const CANCEL: u32 = 0x8; +const COMPRESSION_ERROR: u32 = 0x9; +const CONNECT_ERROR: u32 = 0xa; +const ENHANCE_YOUR_CALM: u32 = 0xb; +const INADEQUATE_SECURITY: u32 = 0xc; +const HTTP_1_1_REQUIRED: u32 = 0xd; +*/ + +#[derive(Clone, Debug, PartialEq)] +pub struct Error<'a> { + pub input: &'a [u8], + pub error: InnerError, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum InnerError { + Nom(ErrorKind), + NoError, + ProtocolError, + InternalError, + FlowControlError, + SettingsTimeout, + StreamClosed, + FrameSizeError, + RefusedStream, + Cancel, + CompressionError, + ConnectError, + EnhanceYourCalm, + InadequateSecurity, + HTTP11Required, +} + +impl<'a> Error<'a> { + pub fn new(input: &'a [u8], error: InnerError) -> Error<'a> { + Error { input, error } + } +} + +impl<'a> ParseError<&'a [u8]> for Error<'a> { + fn from_error_kind(input: &'a [u8], kind: ErrorKind) -> Self { + Error { + input, + error: InnerError::Nom(kind), + } + } + + fn append(input: &'a [u8], kind: ErrorKind, other: Self) -> Self { + Error { + input, + error: InnerError::Nom(kind), + } + } +} + +impl<'a> From<(&'a [u8], ErrorKind)> for Error<'a> { + fn from((input, kind): (&'a [u8], ErrorKind)) -> Self { + Error { + input, + error: InnerError::Nom(kind), + } + } +} + +pub fn preface(i: &[u8]) -> IResult<&[u8], &[u8]> { + tag(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")(i) +} + +// https://httpwg.org/specs/rfc7540.html#rfc.section.4.1 +/*named!(pub frame_header, + do_parse!( + payload_len: dbg_dmp!(be_u24) >> + frame_type: map_opt!(be_u8, convert_frame_type) >> + flags: dbg_dmp!(be_u8) >> + stream_id: dbg_dmp!(verify!(be_u32, |id| { + match frame_type { + + } + }) >> + (FrameHeader { payload_len, frame_type, flags, stream_id }) + ) +); + */ + +pub fn frame_header(input: &[u8]) -> IResult<&[u8], FrameHeader, Error> { + let (i1, payload_len) = be_u24(input)?; + let (i2, frame_type) = map_opt(be_u8, convert_frame_type)(i1)?; + let (i3, flags) = be_u8(i2)?; + let (i4, stream_id) = be_u32(i3)?; + + Ok(( + i4, + FrameHeader { + payload_len, + frame_type, + flags, + stream_id, + }, + )) +} + +fn convert_frame_type(t: u8) -> Option { + info!("got frame type: {}", t); + match t { + 0 => Some(FrameType::Data), + 1 => Some(FrameType::Headers), + 2 => Some(FrameType::Priority), + 3 => Some(FrameType::RstStream), + 4 => Some(FrameType::Settings), + 5 => Some(FrameType::PushPromise), + 6 => Some(FrameType::Ping), + 7 => Some(FrameType::GoAway), + 8 => Some(FrameType::WindowUpdate), + 9 => Some(FrameType::Continuation), + _ => None, + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Frame<'a> { + Data(Data<'a>), + Headers(Headers<'a>), + Priority, + RstStream(RstStream), + Settings(Settings), + PushPromise, + Ping(Ping), + GoAway, + WindowUpdate(WindowUpdate), + Continuation, +} + +impl<'a> Frame<'a> { + pub fn is_stream_specific(&self) -> bool { + match self { + Frame::Data(_) + | Frame::Headers(_) + | Frame::Priority + | Frame::RstStream(_) + | Frame::PushPromise + | Frame::Continuation => true, + Frame::Settings(_) | Frame::Ping(_) | Frame::GoAway => false, + Frame::WindowUpdate(w) => w.stream_id != 0, + } + } + + pub fn stream_id(&self) -> u32 { + match self { + Frame::Data(d) => d.stream_id, + Frame::Headers(h) => h.stream_id, + Frame::Priority => unimplemented!(), + Frame::RstStream(r) => r.stream_id, + Frame::PushPromise => unimplemented!(), + Frame::Continuation => unimplemented!(), + Frame::Settings(_) | Frame::Ping(_) | Frame::GoAway => 0, + Frame::WindowUpdate(w) => w.stream_id, + } + } +} + +pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { + let (i, header) = frame_header(input)?; + + info!("got frame header: {:?}", header); + + if header.payload_len > max_frame_size { + return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + } + + let valid_stream_id = match header.frame_type { + FrameType::Data + | FrameType::Headers + | FrameType::Priority + | FrameType::RstStream + | FrameType::PushPromise + | FrameType::Continuation => header.stream_id != 0, + FrameType::Settings | FrameType::Ping | FrameType::GoAway => header.stream_id == 0, + FrameType::WindowUpdate => true, + }; + + if !valid_stream_id { + return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + } + + let f = match header.frame_type { + FrameType::Data => data_frame(i, &header)?, + FrameType::Headers => headers_frame(i, &header)?, + FrameType::Priority => { + if header.payload_len != 5 { + return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + } + unimplemented!(); + } + FrameType::RstStream => { + if header.payload_len != 4 { + return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + } + rst_stream_frame(i, &header)? + } + FrameType::PushPromise => { + unimplemented!(); + } + FrameType::Continuation => { + unimplemented!(); + } + FrameType::Settings => { + if header.payload_len % 6 != 0 { + return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + } + settings_frame(i, &header)? + } + FrameType::Ping => { + if header.payload_len != 8 { + return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + } + ping_frame(i, &header)? + } + FrameType::GoAway => { + unimplemented!(); + } + FrameType::WindowUpdate => { + if header.payload_len != 4 { + return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + } + window_update_frame(i, &header)? + } + }; + + Ok(f) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Data<'a> { + pub stream_id: u32, + pub payload: &'a [u8], + pub end_stream: bool, +} + +pub fn data_frame<'a, 'b>( + input: &'a [u8], + header: &'b FrameHeader, +) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { + let (remaining, i) = take(header.payload_len)(input)?; + + let (i1, pad_length) = if header.flags & 0x8 != 0 { + let (i, pad_length) = be_u8(i)?; + (i, Some(pad_length)) + } else { + (i, None) + }; + + if pad_length.is_some() && i1.len() <= pad_length.unwrap() as usize { + return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + } + + let (_, payload) = take(i1.len() - pad_length.unwrap_or(0) as usize)(i1)?; + + Ok(( + remaining, + Frame::Data(Data { + stream_id: header.stream_id, + payload, + end_stream: header.flags & 0x1 != 0, + }), + )) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Headers<'a> { + pub stream_id: u32, + pub stream_dependency: Option, + pub weight: Option, + pub header_block_fragment: &'a [u8], + pub end_stream: bool, + pub end_headers: bool, + pub priority: bool, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct StreamDependency { + pub exclusive: bool, + pub stream_id: u32, +} + +pub fn headers_frame<'a, 'b>( + input: &'a [u8], + header: &'b FrameHeader, +) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { + let (remaining, i) = take(header.payload_len)(input)?; + + let (i1, pad_length) = if header.flags & 0x8 != 0 { + let (i, pad_length) = be_u8(i)?; + (i, Some(pad_length)) + } else { + (i, None) + }; + + let (i2, stream_dependency) = if header.flags & 0x20 != 0 { + let (i, stream) = map(be_u32, |i| StreamDependency { + exclusive: i & 0x8000 != 0, + stream_id: i & 0x7FFF, + })(i1)?; + (i, Some(stream)) + } else { + (i1, None) + }; + + let (i3, weight) = if header.flags & 0x20 != 0 { + let (i, weight) = be_u8(i2)?; + (i, Some(weight)) + } else { + (i2, None) + }; + + if pad_length.is_some() && i3.len() <= pad_length.unwrap() as usize { + return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + } + + let (_, header_block_fragment) = take(i3.len() - pad_length.unwrap_or(0) as usize)(i3)?; + + Ok(( + remaining, + Frame::Headers(Headers { + stream_id: header.stream_id, + stream_dependency, + weight, + header_block_fragment, + end_stream: header.flags & 0x1 != 0, + end_headers: header.flags & 0x4 != 0, + priority: header.flags & 0x20 != 0, + }), + )) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct RstStream { + pub stream_id: u32, + pub error_code: u32, +} + +pub fn rst_stream_frame<'a, 'b>( + input: &'a [u8], + header: &'b FrameHeader, +) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { + let (i, error_code) = be_u32(input)?; + Ok(( + i, + Frame::RstStream(RstStream { + stream_id: header.stream_id, + error_code, + }), + )) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Settings { + pub settings: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Setting { + pub identifier: u16, + pub value: u32, +} + +pub fn settings_frame<'a, 'b>( + input: &'a [u8], + header: &'b FrameHeader, +) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { + let (i, data) = take(header.payload_len)(input)?; + + let (_, settings) = many0(map( + complete(tuple((be_u16, be_u32))), + |(identifier, value)| Setting { identifier, value }, + ))(data)?; + + Ok((i, Frame::Settings(Settings { settings }))) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Ping { + pub payload: [u8; 8], +} + +pub fn ping_frame<'a, 'b>( + input: &'a [u8], + header: &'b FrameHeader, +) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { + let (i, data) = take(8usize)(input)?; + + let mut p = Ping { payload: [0; 8] }; + + for i in 0..8 { + p.payload[i] = data[i]; + } + + Ok((i, Frame::Ping(p))) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct WindowUpdate { + pub stream_id: u32, + pub increment: u32, +} + +pub fn window_update_frame<'a, 'b>( + input: &'a [u8], + header: &'b FrameHeader, +) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { + let (i, increment) = be_u32(input)?; + let increment = increment & 0x7FFF; + + //FIXME: if stream id is 0, trat it as connection error? + if increment == 0 { + return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + } + + Ok(( + i, + Frame::WindowUpdate(WindowUpdate { + stream_id: header.stream_id, + increment, + }), + )) +} diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs new file mode 100644 index 000000000..ddc6c437c --- /dev/null +++ b/lib/src/protocol/mux/serializer.rs @@ -0,0 +1,37 @@ +use cookie_factory::{ + bytes::{be_u24, be_u32, be_u8}, + gen, + sequence::tuple, + GenError, +}; + +use super::parser::{FrameHeader, FrameType}; + +pub fn gen_frame_header<'a, 'b>( + buf: &'a mut [u8], + frame: &'b FrameHeader, +) -> Result<(&'a mut [u8], usize), GenError> { + let serializer = tuple(( + be_u24(frame.payload_len), + be_u8(serialize_frame_type(&frame.frame_type)), + be_u8(frame.flags), + be_u32(frame.stream_id), + )); + + gen(serializer, buf).map(|(buf, size)| (buf, size as usize)) +} + +pub fn serialize_frame_type(f: &FrameType) -> u8 { + match *f { + FrameType::Data => 0, + FrameType::Headers => 1, + FrameType::Priority => 2, + FrameType::RstStream => 3, + FrameType::Settings => 4, + FrameType::PushPromise => 5, + FrameType::Ping => 6, + FrameType::GoAway => 7, + FrameType::WindowUpdate => 8, + FrameType::Continuation => 9, + } +} From 838794ce9cce557ec844bc60987aa6c9f4eecd74 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 16 Aug 2023 14:38:49 +0200 Subject: [PATCH 04/44] Mutualize socket_read at the beginning of Mux::readable with an "expect" field Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 2 + lib/src/protocol/mux/mod.rs | 157 ++++++++++++++++++--------------- lib/src/protocol/mux/parser.rs | 6 +- 3 files changed, 90 insertions(+), 75 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index 344842774..e6ae369e3 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -323,6 +323,7 @@ impl HttpsSession { }, streams: HashMap::new(), state: mux::H2State::ClientPreface, + expect: Some((0, 24 + 9)), }), }; let mut mux = Mux { @@ -479,6 +480,7 @@ impl ProxySession for HttpsSession { StateMarker::Http => incr!("https.upgrade.http.failed"), StateMarker::WebSocket => incr!("https.upgrade.wss.failed"), StateMarker::Http2 => incr!("https.upgrade.http2.failed"), + StateMarker::Mux => incr!("https.upgrade.mux.failed"), } return; } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 16476349e..ae1be63e2 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -1,7 +1,6 @@ use std::{ cell::RefCell, collections::HashMap, - io::Write, net::SocketAddr, rc::{Rc, Weak}, }; @@ -26,6 +25,7 @@ type GenericHttpStream = kawa::Kawa; type StreamId = usize; type GlobalStreamId = usize; +#[derive(Debug, Clone, Copy)] pub enum Position { Client, Server, @@ -38,10 +38,13 @@ pub struct ConnectionH1 { pub stream: GlobalStreamId, } +#[derive(Debug)] pub enum H2State { ClientPreface, - ServerPreface, - Connected, + ClientSettings, + ServerSettings, + Header, + Frame, Error, } pub struct ConnectionH2 { @@ -50,6 +53,7 @@ pub struct ConnectionH2 { pub readiness: Readiness, pub state: H2State, pub streams: HashMap, + pub expect: Option<(GlobalStreamId, usize)>, // context_hpack: HpackContext, // settings: SettiongsH2, } @@ -59,6 +63,21 @@ pub struct Stream { pub back: GenericHttpStream, } +impl Stream { + pub fn front(&mut self, position: Position) -> &mut GenericHttpStream { + match position { + Position::Client => &mut self.back, + Position::Server => &mut self.front, + } + } + pub fn back(&mut self, position: Position) -> &mut GenericHttpStream { + match position { + Position::Client => &mut self.front, + Position::Server => &mut self.back, + } + } +} + pub enum Connection { H1(ConnectionH1), H2(ConnectionH2), @@ -205,22 +224,6 @@ impl SessionState for Mux { } impl Mux { - // pub fn new( - // frontend_token: Token, - // request_id: Ulid, - // listener: Rc>, - // pool: Weak>, - // public_address: SocketAddr, - // peer_address: Option, - // sticky_name: String, - // ) -> Self { - // Self { - // frontend_token, - // frontend: todo!(), - // backends: todo!(), - // streams: todo!(), - // } - // } pub fn front_socket(&self) -> &TcpStream { match &self.frontend { Connection::H1(c) => &c.socket.stream, @@ -251,50 +254,63 @@ impl Mux { impl ConnectionH2 { fn readable(&mut self, streams: &mut [Stream]) { println!("======= MUX H2 READABLE"); + let kawa = if let Some((stream_id, amount)) = self.expect { + let kawa = streams[stream_id].front(self.position); + let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); + println!("{:?}({stream_id}, {amount}) {size} {status:?}", self.state); + if size > 0 { + kawa.storage.fill(size); + if size == amount { + self.expect = None; + } else { + self.expect = Some((stream_id, amount - size)); + return; + } + } else { + self.readiness.event.remove(Ready::READABLE); + return; + } + kawa + } else { + self.readiness.event.remove(Ready::READABLE); + return; + }; match (&self.state, &self.position) { (H2State::ClientPreface, Position::Client) => { error!("Waiting for ClientPreface to finish writing") } (H2State::ClientPreface, Position::Server) => { - let stream = &mut streams[0]; - let kawa = &mut stream.front; - let mut i = [0; 33]; - - // let (size, status) = self.socket.socket_read(kawa.storage.space()); - // println!("{:02x?}", &kawa.storage.buffer()[..size]); - // unreachable!(); - - let (size, status) = self.socket.socket_read(&mut i); - - println!("{size} {status:?} {i:02x?}"); - let i = match parser::preface(&i) { + let i = kawa.storage.data(); + let i = match parser::preface(i) { Ok((i, _)) => i, - Err(_) => todo!(), + Err(e) => panic!("{e:?}"), }; - let header = match parser::frame_header(&i) { + match parser::frame_header(i) { Ok(( _, - header @ parser::FrameHeader { - payload_len: _, + parser::FrameHeader { + payload_len, frame_type: parser::FrameType::Settings, flags: 0, stream_id: 0, }, - )) => header, + )) => { + kawa.storage.clear(); + self.state = H2State::ClientSettings; + self.expect = Some((0, payload_len as usize)); + } _ => todo!(), }; - let (size, status) = self - .socket - .socket_read(&mut kawa.storage.space()[..header.payload_len as usize]); - kawa.storage.fill(size); + } + (H2State::ClientSettings, Position::Server) => { let i = kawa.storage.data(); - println!(" {size} {status:?} {i:02x?}"); - match parser::settings_frame(i, &header) { + match parser::settings_frame(i, i.len()) { Ok((_, settings)) => println!("{settings:?}"), - Err(_) => todo!(), + Err(e) => panic!("{e:?}"), } - let kawa = &mut stream.back; - self.state = H2State::ServerPreface; + kawa.storage.clear(); + let kawa = &mut streams[0].back; + self.state = H2State::ServerSettings; match serializer::gen_frame_header( kawa.storage.space(), &parser::FrameHeader { @@ -325,46 +341,42 @@ impl ConnectionH2 { self.readiness.interest.insert(Ready::WRITABLE); self.readiness.interest.remove(Ready::READABLE); } - (H2State::ServerPreface, Position::Client) => todo!("Receive server Settings"), - (H2State::ServerPreface, Position::Server) => { + (H2State::ServerSettings, Position::Client) => todo!("Receive server Settings"), + (H2State::ServerSettings, Position::Server) => { error!("waiting for ServerPreface to finish writing") } - (H2State::Connected, Position::Server) => { - let mut header = [0; 9]; - let (size, status) = self.socket.socket_read(&mut header); - println!(" size: {size}, status: {status:?}"); - if size == 0 { - self.readiness.event.remove(Ready::READABLE); - return; - } - println!("{:?}", &header[..size]); - let len = match parser::frame_header(&header) { - Ok((_, h)) => { - println!("{h:?}"); - h.payload_len as usize - } - Err(_) => { - self.state = H2State::Error; - return; + (H2State::Header, Position::Server) => { + let i = kawa.storage.data(); + println!(" header: {i:?}"); + match parser::frame_header(i) { + Ok((_, header)) => { + println!("{header:?}"); + kawa.storage.clear(); + self.state = H2State::Frame; + self.expect = + Some((header.stream_id as usize, header.payload_len as usize)); } + Err(e) => panic!("{e:?}"), }; - let kawa = &mut streams[0].front; - kawa.storage.clear(); - let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..len]); - kawa.storage.fill(size); + } + (H2State::Frame, Position::Server) => { let i = kawa.storage.data(); - println!(" {size} {status:?} {i:?}"); + println!(" data: {i:?}"); + kawa.storage.clear(); + self.state = H2State::Header; + self.expect = Some((0, 9)); } _ => unreachable!(), } } + fn writable(&mut self, streams: &mut [Stream]) { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), (H2State::ClientPreface, Position::Server) => unreachable!(), - (H2State::ServerPreface, Position::Client) => unreachable!(), - (H2State::Connected, Position::Server) | (H2State::ServerPreface, Position::Server) => { + (H2State::ServerSettings, Position::Client) => unreachable!(), + (H2State::ServerSettings, Position::Server) => { let stream = &mut streams[0]; let kawa = &mut stream.back; println!("{:?}", kawa.storage.data()); @@ -375,7 +387,8 @@ impl ConnectionH2 { if kawa.storage.is_empty() { self.readiness.interest.remove(Ready::WRITABLE); self.readiness.interest.insert(Ready::READABLE); - self.state = H2State::Connected; + self.state = H2State::Header; + self.expect = Some((0, 9)); } } _ => unreachable!(), diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index 1b53da321..59faebdc6 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -250,7 +250,7 @@ pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Fram if header.payload_len % 6 != 0 { return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); } - settings_frame(i, &header)? + settings_frame(i, header.payload_len as usize)? } FrameType::Ping => { if header.payload_len != 8 { @@ -408,9 +408,9 @@ pub struct Setting { pub fn settings_frame<'a, 'b>( input: &'a [u8], - header: &'b FrameHeader, + payload_len: usize, ) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { - let (i, data) = take(header.payload_len)(input)?; + let (i, data) = take(payload_len)(input)?; let (_, settings) = many0(map( complete(tuple((be_u16, be_u32))), From 1ad3514accd0e0769c5c66615f7ee6548148574e Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 18 Aug 2023 00:01:46 +0200 Subject: [PATCH 05/44] Add mechanisms to handle H2 frames Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 35 ++-- lib/src/protocol/mux/mod.rs | 288 +++++++++++++++++++++++++-------- lib/src/protocol/mux/parser.rs | 69 ++++---- 3 files changed, 272 insertions(+), 120 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index e6ae369e3..4b95f58c7 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -304,40 +304,27 @@ impl HttpsSession { // self.sticky_name.clone(), // ))); use crate::protocol::mux; - let frontend = match alpn { - AlpnProtocol::Http11 => mux::Connection::H1(mux::ConnectionH1 { - socket: front_stream, - position: mux::Position::Server, - readiness: Readiness { - interest: Ready::READABLE | Ready::HUP | Ready::ERROR, - event: handshake.frontend_readiness.event, - }, - stream: 0, - }), - AlpnProtocol::H2 => mux::Connection::H2(mux::ConnectionH2 { - socket: front_stream, - position: mux::Position::Server, - readiness: Readiness { - interest: Ready::READABLE | Ready::HUP | Ready::ERROR, - event: handshake.frontend_readiness.event, - }, - streams: HashMap::new(), - state: mux::H2State::ClientPreface, - expect: Some((0, 24 + 9)), - }), + let mut frontend = match alpn { + AlpnProtocol::Http11 => mux::Connection::new_h1_server(front_stream), + AlpnProtocol::H2 => mux::Connection::new_h2_server(front_stream), }; + frontend.readiness_mut().event = handshake.frontend_readiness.event; let mut mux = Mux { frontend_token: self.frontend_token, frontend, backends: HashMap::new(), - streams: Vec::new(), + streams: mux::Streams { + streams: Vec::new(), + pool: self.pool.clone(), + }, listener: self.listener.clone(), - pool: self.pool.clone(), public_address: self.public_address, peer_address: self.peer_address, sticky_name: self.sticky_name.clone(), }; - mux.create_stream(handshake.request_id).ok()?; + mux.streams + .create_stream(handshake.request_id, 1 << 16) + .ok()?; return Some(HttpsStateMachine::Mux(mux)); match alpn { AlpnProtocol::Http11 => { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index ae1be63e2..aa5d37434 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -3,6 +3,7 @@ use std::{ collections::HashMap, net::SocketAddr, rc::{Rc, Weak}, + str::from_utf8_unchecked, }; use mio::{net::TcpStream, Token}; @@ -22,7 +23,7 @@ use crate::{ /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer type GenericHttpStream = kawa::Kawa; -type StreamId = usize; +type StreamId = u32; type GlobalStreamId = usize; #[derive(Debug, Clone, Copy)] @@ -32,9 +33,9 @@ pub enum Position { } pub struct ConnectionH1 { - pub socket: Front, pub position: Position, pub readiness: Readiness, + pub socket: Front, pub stream: GlobalStreamId, } @@ -44,21 +45,47 @@ pub enum H2State { ClientSettings, ServerSettings, Header, - Frame, + Frame(parser::FrameHeader), Error, } + +#[derive(Debug)] +pub struct H2Settings { + settings_header_table_size: u32, + settings_enable_push: bool, + settings_max_concurrent_streams: u32, + settings_initial_window_size: u32, + settings_max_frame_size: u32, + settings_max_header_list_size: u32, +} + +impl Default for H2Settings { + fn default() -> Self { + Self { + settings_header_table_size: 4096, + settings_enable_push: true, + settings_max_concurrent_streams: u32::MAX, + settings_initial_window_size: (1 << 16) - 1, + settings_max_frame_size: 1 << 14, + settings_max_header_list_size: u32::MAX, + } + } +} + pub struct ConnectionH2 { - pub socket: Front, + pub decoder: hpack::Decoder<'static>, + pub expect: Option<(GlobalStreamId, usize)>, pub position: Position, pub readiness: Readiness, + pub settings: H2Settings, + pub socket: Front, pub state: H2State, pub streams: HashMap, - pub expect: Option<(GlobalStreamId, usize)>, - // context_hpack: HpackContext, - // settings: SettiongsH2, } + pub struct Stream { pub request_id: Ulid, + pub window: i32, pub front: GenericHttpStream, pub back: GenericHttpStream, } @@ -82,26 +109,81 @@ pub enum Connection { H1(ConnectionH1), H2(ConnectionH2), } + impl Connection { - fn readiness(&self) -> &Readiness { + pub fn new_h1_server(front_stream: Front) -> Connection { + Connection::H1(ConnectionH1 { + socket: front_stream, + position: Position::Server, + readiness: Readiness { + interest: Ready::READABLE | Ready::HUP | Ready::ERROR, + event: Ready::EMPTY, + }, + stream: 0, + }) + } + pub fn new_h1_client(front_stream: Front) -> Connection { + Connection::H1(ConnectionH1 { + socket: front_stream, + position: Position::Client, + readiness: Readiness { + interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, + event: Ready::EMPTY, + }, + stream: 0, + }) + } + + pub fn new_h2_server(front_stream: Front) -> Connection { + Connection::H2(ConnectionH2 { + socket: front_stream, + position: Position::Server, + readiness: Readiness { + interest: Ready::READABLE | Ready::HUP | Ready::ERROR, + event: Ready::EMPTY, + }, + streams: HashMap::from([(0, 0)]), + state: H2State::ClientPreface, + expect: Some((0, 24 + 9)), + settings: H2Settings::default(), + decoder: hpack::Decoder::new(), + }) + } + pub fn new_h2_client(front_stream: Front) -> Connection { + Connection::H2(ConnectionH2 { + socket: front_stream, + position: Position::Client, + readiness: Readiness { + interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, + event: Ready::EMPTY, + }, + streams: HashMap::from([(0, 0)]), + state: H2State::ClientPreface, + expect: None, + settings: H2Settings::default(), + decoder: hpack::Decoder::new(), + }) + } + + pub fn readiness(&self) -> &Readiness { match self { Connection::H1(c) => &c.readiness, Connection::H2(c) => &c.readiness, } } - fn readiness_mut(&mut self) -> &mut Readiness { + pub fn readiness_mut(&mut self) -> &mut Readiness { match self { Connection::H1(c) => &mut c.readiness, Connection::H2(c) => &mut c.readiness, } } - fn readable(&mut self, streams: &mut [Stream]) { + fn readable(&mut self, streams: &mut Streams) { match self { Connection::H1(c) => c.readable(streams), Connection::H2(c) => c.readable(streams), } } - fn writable(&mut self, streams: &mut [Stream]) { + fn writable(&mut self, streams: &mut Streams) { match self { Connection::H1(c) => c.writable(streams), Connection::H2(c) => c.writable(streams), @@ -109,16 +191,67 @@ impl Connection { } } +pub struct Streams { + pub streams: Vec, + pub pool: Weak>, +} + pub struct Mux { pub frontend_token: Token, pub frontend: Connection, pub backends: HashMap>, - pub streams: Vec, pub listener: Rc>, - pub pool: Weak>, pub public_address: SocketAddr, pub peer_address: Option, pub sticky_name: String, + pub streams: Streams, +} + +impl Streams { + pub fn create_stream( + &mut self, + request_id: Ulid, + window: u32, + ) -> Result { + let (front_buffer, back_buffer) = match self.pool.upgrade() { + Some(pool) => { + let mut pool = pool.borrow_mut(); + match (pool.checkout(), pool.checkout()) { + (Some(front_buffer), Some(back_buffer)) => (front_buffer, back_buffer), + _ => return Err(AcceptError::BufferCapacityReached), + } + } + None => return Err(AcceptError::BufferCapacityReached), + }; + self.streams.push(Stream { + request_id, + window: window as i32, + front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), + back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), + }); + Ok(self.streams.len() - 1) + } +} + +impl std::ops::Deref for Streams { + type Target = [Stream]; + fn deref(&self) -> &Self::Target { + &self.streams + } +} +impl std::ops::DerefMut for Streams { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.streams + } +} + +impl Mux { + pub fn front_socket(&self) -> &TcpStream { + match &self.frontend { + Connection::H1(c) => &c.socket.stream, + Connection::H2(c) => &c.socket.stream, + } + } } impl SessionState for Mux { @@ -223,36 +356,8 @@ impl SessionState for Mux { } } -impl Mux { - pub fn front_socket(&self) -> &TcpStream { - match &self.frontend { - Connection::H1(c) => &c.socket.stream, - Connection::H2(c) => &c.socket.stream, - } - } - - pub fn create_stream(&mut self, request_id: Ulid) -> Result { - let (front_buffer, back_buffer) = match self.pool.upgrade() { - Some(pool) => { - let mut pool = pool.borrow_mut(); - match (pool.checkout(), pool.checkout()) { - (Some(front_buffer), Some(back_buffer)) => (front_buffer, back_buffer), - _ => return Err(AcceptError::BufferCapacityReached), - } - } - None => return Err(AcceptError::BufferCapacityReached), - }; - self.streams.push(Stream { - request_id, - front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), - back: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(back_buffer)), - }); - Ok(self.streams.len() - 1) - } -} - impl ConnectionH2 { - fn readable(&mut self, streams: &mut [Stream]) { + fn readable(&mut self, streams: &mut Streams) { println!("======= MUX H2 READABLE"); let kawa = if let Some((stream_id, amount)) = self.expect { let kawa = streams[stream_id].front(self.position); @@ -305,10 +410,12 @@ impl ConnectionH2 { (H2State::ClientSettings, Position::Server) => { let i = kawa.storage.data(); match parser::settings_frame(i, i.len()) { - Ok((_, settings)) => println!("{settings:?}"), + Ok((_, settings)) => { + kawa.storage.clear(); + self.handle(settings, streams); + } Err(e) => panic!("{e:?}"), } - kawa.storage.clear(); let kawa = &mut streams[0].back; self.state = H2State::ServerSettings; match serializer::gen_frame_header( @@ -352,17 +459,34 @@ impl ConnectionH2 { Ok((_, header)) => { println!("{header:?}"); kawa.storage.clear(); - self.state = H2State::Frame; - self.expect = - Some((header.stream_id as usize, header.payload_len as usize)); + let stream_id = if let Some(stream_id) = self.streams.get(&header.stream_id) + { + *stream_id + } else { + self.create_stream(header.stream_id, streams) + }; + let stream_id = if header.frame_type == parser::FrameType::Headers { + 0 + } else { + stream_id + }; + println!("{} {} {:#?}", header.stream_id, stream_id, self.streams); + self.expect = Some((stream_id as usize, header.payload_len as usize)); + self.state = H2State::Frame(header); } Err(e) => panic!("{e:?}"), }; } - (H2State::Frame, Position::Server) => { + (H2State::Frame(header), Position::Server) => { let i = kawa.storage.data(); println!(" data: {i:?}"); - kawa.storage.clear(); + match parser::frame_body(i, header, self.settings.settings_max_frame_size) { + Ok((_, frame)) => { + kawa.storage.clear(); + self.handle(frame, streams); + } + Err(e) => panic!("{e:?}"), + } self.state = H2State::Header; self.expect = Some((0, 9)); } @@ -370,7 +494,7 @@ impl ConnectionH2 { } } - fn writable(&mut self, streams: &mut [Stream]) { + fn writable(&mut self, streams: &mut Streams) { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), @@ -393,22 +517,60 @@ impl ConnectionH2 { } _ => unreachable!(), } - // for global_stream_id in self.streams.values() { - // let stream = &mut streams[*global_stream_id]; - // let kawa = match self.position { - // Position::Client => &mut stream.back, - // Position::Server => &mut stream.front, - // }; - // kawa.prepare(&mut kawa::h2::BlockConverter); - // let (size, status) = self.socket.socket_write_vectored(&kawa.as_io_slice()); - // println!(" size: {size}, status: {status:?}"); - // kawa.consume(size); - // } + } + + pub fn create_stream(&mut self, stream_id: StreamId, streams: &mut Streams) -> GlobalStreamId { + match streams.create_stream(Ulid::generate(), self.settings.settings_initial_window_size) { + Ok(global_stream_id) => { + self.streams.insert(stream_id, global_stream_id); + global_stream_id + } + Err(e) => panic!("{e:?}"), + } + } + + fn handle(&mut self, frame: parser::Frame, streams: &mut Streams) { + println!("{frame:?}"); + match frame { + parser::Frame::Data(_) => todo!(), + parser::Frame::Headers(headers) => { + let kawa = streams[0].front(self.position); + let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); + println!("{buffer:?}"); + let result = self.decoder.decode(buffer).unwrap(); + for (k, v) in result { + unsafe { println!("{} {}", from_utf8_unchecked(&k), from_utf8_unchecked(&v)) }; + } + } + parser::Frame::Priority => todo!(), + parser::Frame::RstStream(_) => todo!(), + parser::Frame::Settings(settings) => { + for setting in settings.settings { + match setting.identifier { + 1 => self.settings.settings_header_table_size = setting.value, + 2 => self.settings.settings_enable_push = setting.value == 1, + 3 => self.settings.settings_max_concurrent_streams = setting.value, + 4 => self.settings.settings_initial_window_size = setting.value, + 5 => self.settings.settings_max_frame_size = setting.value, + 6 => self.settings.settings_max_header_list_size = setting.value, + other => panic!("setting_id: {other}"), + } + } + println!("{:#?}", self.settings); + } + parser::Frame::PushPromise => todo!(), + parser::Frame::Ping(_) => todo!(), + parser::Frame::GoAway => todo!(), + parser::Frame::WindowUpdate(update) => { + streams[update.stream_id as usize].window += update.increment as i32; + } + parser::Frame::Continuation => todo!(), + } } } impl ConnectionH1 { - fn readable(&mut self, streams: &mut [Stream]) { + fn readable(&mut self, streams: &mut Streams) { println!("======= MUX H1 READABLE"); let stream = &mut streams[self.stream]; let kawa = match self.position { @@ -434,7 +596,7 @@ impl ConnectionH1 { self.readiness.interest.remove(Ready::READABLE); } } - fn writable(&mut self, streams: &mut [Stream]) { + fn writable(&mut self, streams: &mut Streams) { println!("======= MUX H1 WRITABLE"); let stream = &mut streams[self.stream]; let kawa = match self.position { diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index 59faebdc6..c7558d7e9 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -1,5 +1,6 @@ use std::convert::From; +use kawa::repr::Slice; use nom::{ bytes::streaming::{tag, take}, combinator::{complete, map, map_opt}, @@ -159,10 +160,10 @@ fn convert_frame_type(t: u8) -> Option { } } -#[derive(Clone, Debug, PartialEq)] -pub enum Frame<'a> { - Data(Data<'a>), - Headers(Headers<'a>), +#[derive(Clone, Debug)] +pub enum Frame { + Data(Data), + Headers(Headers), Priority, RstStream(RstStream), Settings(Settings), @@ -173,7 +174,7 @@ pub enum Frame<'a> { Continuation, } -impl<'a> Frame<'a> { +impl Frame { pub fn is_stream_specific(&self) -> bool { match self { Frame::Data(_) @@ -201,13 +202,13 @@ impl<'a> Frame<'a> { } } -pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { - let (i, header) = frame_header(input)?; - - info!("got frame header: {:?}", header); - +pub fn frame_body<'a>( + i: &'a [u8], + header: &FrameHeader, + max_frame_size: u32, +) -> IResult<&'a [u8], Frame, Error<'a>> { if header.payload_len > max_frame_size { - return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } let valid_stream_id = match header.frame_type { @@ -222,7 +223,7 @@ pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Fram }; if !valid_stream_id { - return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + return Err(Err::Failure(Error::new(i, InnerError::ProtocolError))); } let f = match header.frame_type { @@ -230,13 +231,13 @@ pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Fram FrameType::Headers => headers_frame(i, &header)?, FrameType::Priority => { if header.payload_len != 5 { - return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } unimplemented!(); } FrameType::RstStream => { if header.payload_len != 4 { - return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } rst_stream_frame(i, &header)? } @@ -248,13 +249,13 @@ pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Fram } FrameType::Settings => { if header.payload_len % 6 != 0 { - return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } settings_frame(i, header.payload_len as usize)? } FrameType::Ping => { if header.payload_len != 8 { - return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } ping_frame(i, &header)? } @@ -263,7 +264,7 @@ pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Fram } FrameType::WindowUpdate => { if header.payload_len != 4 { - return Err(Err::Failure(Error::new(input, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } window_update_frame(i, &header)? } @@ -272,17 +273,17 @@ pub fn frame<'a>(input: &'a [u8], max_frame_size: u32) -> IResult<&'a [u8], Fram Ok(f) } -#[derive(Clone, Debug, PartialEq)] -pub struct Data<'a> { +#[derive(Clone, Debug)] +pub struct Data { pub stream_id: u32, - pub payload: &'a [u8], + pub payload: Slice, pub end_stream: bool, } pub fn data_frame<'a, 'b>( input: &'a [u8], header: &'b FrameHeader, -) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { +) -> IResult<&'a [u8], Frame, Error<'a>> { let (remaining, i) = take(header.payload_len)(input)?; let (i1, pad_length) = if header.flags & 0x8 != 0 { @@ -302,18 +303,19 @@ pub fn data_frame<'a, 'b>( remaining, Frame::Data(Data { stream_id: header.stream_id, - payload, + payload: Slice::new(input, payload), end_stream: header.flags & 0x1 != 0, }), )) } -#[derive(Clone, Debug, PartialEq)] -pub struct Headers<'a> { +#[derive(Clone, Debug)] +pub struct Headers { pub stream_id: u32, pub stream_dependency: Option, pub weight: Option, - pub header_block_fragment: &'a [u8], + pub header_block_fragment: Slice, + // pub header_block_fragment: &'a [u8], pub end_stream: bool, pub end_headers: bool, pub priority: bool, @@ -328,7 +330,7 @@ pub struct StreamDependency { pub fn headers_frame<'a, 'b>( input: &'a [u8], header: &'b FrameHeader, -) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { +) -> IResult<&'a [u8], Frame, Error<'a>> { let (remaining, i) = take(header.payload_len)(input)?; let (i1, pad_length) = if header.flags & 0x8 != 0 { @@ -341,7 +343,7 @@ pub fn headers_frame<'a, 'b>( let (i2, stream_dependency) = if header.flags & 0x20 != 0 { let (i, stream) = map(be_u32, |i| StreamDependency { exclusive: i & 0x8000 != 0, - stream_id: i & 0x7FFF, + stream_id: i & 0x7FFFFFFF, })(i1)?; (i, Some(stream)) } else { @@ -367,7 +369,8 @@ pub fn headers_frame<'a, 'b>( stream_id: header.stream_id, stream_dependency, weight, - header_block_fragment, + // header_block_fragment, + header_block_fragment: Slice::new(input, header_block_fragment), end_stream: header.flags & 0x1 != 0, end_headers: header.flags & 0x4 != 0, priority: header.flags & 0x20 != 0, @@ -384,7 +387,7 @@ pub struct RstStream { pub fn rst_stream_frame<'a, 'b>( input: &'a [u8], header: &'b FrameHeader, -) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { +) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, error_code) = be_u32(input)?; Ok(( i, @@ -409,7 +412,7 @@ pub struct Setting { pub fn settings_frame<'a, 'b>( input: &'a [u8], payload_len: usize, -) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { +) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, data) = take(payload_len)(input)?; let (_, settings) = many0(map( @@ -428,7 +431,7 @@ pub struct Ping { pub fn ping_frame<'a, 'b>( input: &'a [u8], header: &'b FrameHeader, -) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { +) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, data) = take(8usize)(input)?; let mut p = Ping { payload: [0; 8] }; @@ -449,9 +452,9 @@ pub struct WindowUpdate { pub fn window_update_frame<'a, 'b>( input: &'a [u8], header: &'b FrameHeader, -) -> IResult<&'a [u8], Frame<'a>, Error<'a>> { +) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, increment) = be_u32(input)?; - let increment = increment & 0x7FFF; + let increment = increment & 0x7FFFFFFF; //FIXME: if stream id is 0, trat it as connection error? if increment == 0 { From d140e966b4d2607b09ece8e36b78f38b03a8abff Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 21 Aug 2023 13:03:42 +0200 Subject: [PATCH 06/44] Remork Streams - Rename Context - Mutualize hpack decoder (one in Context, not per Connection) - Separate streams in a new Streams struct - Separate stream zero from the others - PoC hpack decode from stream zero into another stream Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 74 ++------------ lib/src/protocol/kawa_h1/mod.rs | 4 +- lib/src/protocol/mux/mod.rs | 176 +++++++++++++++++++++----------- 3 files changed, 126 insertions(+), 128 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index 4b95f58c7..ba5f59f48 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -71,8 +71,8 @@ use crate::{ tls::MutexCertificateResolver, util::UnwrapLog, AcceptError, CachedTags, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerError, - ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, Readiness, - SessionIsToBeClosed, SessionMetrics, SessionResult, StateMachineBuilder, StateResult, + ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, SessionIsToBeClosed, + SessionMetrics, SessionResult, StateMachineBuilder, StateResult, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -191,7 +191,7 @@ impl HttpsSession { HttpsStateMachine::Expect(expect, ssl) => self.upgrade_expect(expect, ssl), HttpsStateMachine::Handshake(handshake) => self.upgrade_handshake(handshake), HttpsStateMachine::Http(http) => self.upgrade_http(http), - HttpsStateMachine::Mux(mux) => unimplemented!(), + HttpsStateMachine::Mux(_) => unimplemented!(), HttpsStateMachine::Http2(_) => self.upgrade_http2(), HttpsStateMachine::WebSocket(wss) => self.upgrade_websocket(wss), HttpsStateMachine::FailedUpgrade(_) => unreachable!(), @@ -255,12 +255,6 @@ impl HttpsSession { } fn upgrade_handshake(&mut self, handshake: TlsHandshake) -> Option { - // Add 1st routing phase - // - get SNI - // - get ALPN - // - find corresponding listener - // - determine next protocol (tcps, https ,http2) - let sni = handshake.session.server_name(); let alpn = handshake.session.alpn_protocol(); let alpn = alpn.and_then(|alpn| from_utf8(alpn).ok()); @@ -294,79 +288,25 @@ impl HttpsSession { }; gauge_add!("protocol.tls.handshake", -1); - // return Some(HttpsStateMachine::Mux(Mux::new( - // self.frontend_token, - // handshake.request_id, - // self.listener.clone(), - // self.pool.clone(), - // self.public_address, - // self.peer_address, - // self.sticky_name.clone(), - // ))); + use crate::protocol::mux; let mut frontend = match alpn { AlpnProtocol::Http11 => mux::Connection::new_h1_server(front_stream), AlpnProtocol::H2 => mux::Connection::new_h2_server(front_stream), }; frontend.readiness_mut().event = handshake.frontend_readiness.event; - let mut mux = Mux { + let mux = Mux { frontend_token: self.frontend_token, frontend, backends: HashMap::new(), - streams: mux::Streams { - streams: Vec::new(), - pool: self.pool.clone(), - }, + context: mux::Context::new(self.pool.clone(), handshake.request_id, 1 << 16).ok()?, listener: self.listener.clone(), public_address: self.public_address, peer_address: self.peer_address, sticky_name: self.sticky_name.clone(), }; - mux.streams - .create_stream(handshake.request_id, 1 << 16) - .ok()?; + gauge_add!("protocol.https", 1); return Some(HttpsStateMachine::Mux(mux)); - match alpn { - AlpnProtocol::Http11 => { - let mut http = Http::new( - self.answers.clone(), - self.configured_backend_timeout, - self.configured_connect_timeout, - self.configured_frontend_timeout, - handshake.container_frontend_timeout, - front_stream, - self.frontend_token, - self.listener.clone(), - self.pool.clone(), - Protocol::HTTPS, - self.public_address, - handshake.request_id, - self.peer_address, - self.sticky_name.clone(), - ) - .ok()?; - - http.frontend_readiness.event = handshake.frontend_readiness.event; - - gauge_add!("protocol.https", 1); - Some(HttpsStateMachine::Http(http)) - } - AlpnProtocol::H2 => { - let mut http = Http2::new( - front_stream, - self.frontend_token, - self.pool.clone(), - Some(self.public_address), - None, - self.sticky_name.clone(), - ); - - http.frontend.readiness.event = handshake.frontend_readiness.event; - - gauge_add!("protocol.http2", 1); - Some(HttpsStateMachine::Http2(http)) - } - } } fn upgrade_http(&self, http: Http) -> Option { diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 13df4746f..18ff48db7 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -1264,8 +1264,8 @@ impl Http (id, h2), + let cluster_id = match route { + Route::Cluster { id, .. } => id, Route::Deny => { self.set_answer(DefaultAnswer::Answer401 {}); return Err(RetrieveClusterError::UnauthorizedRoute); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index aa5d37434..2dc3721b7 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -1,9 +1,9 @@ use std::{ cell::RefCell, collections::HashMap, + io::Write, net::SocketAddr, rc::{Rc, Weak}, - str::from_utf8_unchecked, }; use mio::{net::TcpStream, Token}; @@ -36,6 +36,7 @@ pub struct ConnectionH1 { pub position: Position, pub readiness: Readiness, pub socket: Front, + /// note: a Server H1 will always reference stream 0, but a client can reference any stream pub stream: GlobalStreamId, } @@ -73,7 +74,7 @@ impl Default for H2Settings { } pub struct ConnectionH2 { - pub decoder: hpack::Decoder<'static>, + // pub decoder: hpack::Decoder<'static>, pub expect: Option<(GlobalStreamId, usize)>, pub position: Position, pub readiness: Readiness, @@ -146,7 +147,6 @@ impl Connection { state: H2State::ClientPreface, expect: Some((0, 24 + 9)), settings: H2Settings::default(), - decoder: hpack::Decoder::new(), }) } pub fn new_h2_client(front_stream: Front) -> Connection { @@ -161,7 +161,6 @@ impl Connection { state: H2State::ClientPreface, expect: None, settings: H2Settings::default(), - decoder: hpack::Decoder::new(), }) } @@ -177,23 +176,29 @@ impl Connection { Connection::H2(c) => &mut c.readiness, } } - fn readable(&mut self, streams: &mut Streams) { + fn readable(&mut self, context: &mut Context) { match self { - Connection::H1(c) => c.readable(streams), - Connection::H2(c) => c.readable(streams), + Connection::H1(c) => c.readable(context), + Connection::H2(c) => c.readable(context), } } - fn writable(&mut self, streams: &mut Streams) { + fn writable(&mut self, context: &mut Context) { match self { - Connection::H1(c) => c.writable(streams), - Connection::H2(c) => c.writable(streams), + Connection::H1(c) => c.writable(context), + Connection::H2(c) => c.writable(context), } } } pub struct Streams { - pub streams: Vec, + zero: Stream, + others: Vec, +} + +pub struct Context { + pub streams: Streams, pub pool: Weak>, + pub decoder: hpack::Decoder<'static>, } pub struct Mux { @@ -204,16 +209,16 @@ pub struct Mux { pub public_address: SocketAddr, pub peer_address: Option, pub sticky_name: String, - pub streams: Streams, + pub context: Context, } -impl Streams { - pub fn create_stream( - &mut self, +impl Context { + pub fn new_stream( + pool: Weak>, request_id: Ulid, window: u32, - ) -> Result { - let (front_buffer, back_buffer) = match self.pool.upgrade() { + ) -> Result { + let (front_buffer, back_buffer) = match pool.upgrade() { Some(pool) => { let mut pool = pool.borrow_mut(); match (pool.checkout(), pool.checkout()) { @@ -223,25 +228,48 @@ impl Streams { } None => return Err(AcceptError::BufferCapacityReached), }; - self.streams.push(Stream { + Ok(Stream { request_id, window: window as i32, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), - }); - Ok(self.streams.len() - 1) + }) + } + + pub fn create_stream( + &mut self, + request_id: Ulid, + window: u32, + ) -> Result { + self.streams + .others + .push(Self::new_stream(self.pool.clone(), request_id, window)?); + Ok(self.streams.others.len()) } -} -impl std::ops::Deref for Streams { - type Target = [Stream]; - fn deref(&self) -> &Self::Target { - &self.streams + pub fn new( + pool: Weak>, + request_id: Ulid, + window: u32, + ) -> Result { + Ok(Self { + streams: Streams { + zero: Context::new_stream(pool.clone(), request_id, window)?, + others: Vec::new(), + }, + pool, + decoder: hpack::Decoder::new(), + }) } } -impl std::ops::DerefMut for Streams { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.streams + +impl Streams { + pub fn get(&mut self, stream_id: GlobalStreamId) -> &mut Stream { + if stream_id == 0 { + &mut self.zero + } else { + &mut self.others[stream_id - 1] + } } } @@ -257,9 +285,9 @@ impl Mux { impl SessionState for Mux { fn ready( &mut self, - session: Rc>, - proxy: Rc>, - metrics: &mut SessionMetrics, + _session: Rc>, + _proxy: Rc>, + _metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; let max_loop_iterations = 100000; @@ -268,29 +296,29 @@ impl SessionState for Mux { return SessionResult::Close; } - let streams = &mut self.streams; + let context = &mut self.context; while counter < max_loop_iterations { let mut dirty = false; if self.frontend.readiness().filter_interest().is_readable() { - self.frontend.readable(streams); + self.frontend.readable(context); dirty = true; } for (_, backend) in self.backends.iter_mut() { if backend.readiness().filter_interest().is_writable() { - backend.writable(streams); + backend.writable(context); dirty = true; } if backend.readiness().filter_interest().is_readable() { - backend.readable(streams); + backend.readable(context); dirty = true; } } if self.frontend.readiness().filter_interest().is_writable() { - self.frontend.writable(streams); + self.frontend.writable(context); dirty = true; } @@ -325,7 +353,7 @@ impl SessionState for Mux { } } - fn timeout(&mut self, token: Token, metrics: &mut SessionMetrics) -> StateResult { + fn timeout(&mut self, token: Token, _metrics: &mut SessionMetrics) -> StateResult { println!("MuxState::timeout({token:?})"); StateResult::CloseSession } @@ -353,14 +381,17 @@ impl SessionState for Mux { let mut b = [0; 1024]; let (size, status) = s.socket_read(&mut b); println!("{size} {status:?} {:?}", &b[..size]); + for stream in &self.context.streams.others { + kawa::debug_kawa(&stream.front); + } } } impl ConnectionH2 { - fn readable(&mut self, streams: &mut Streams) { + fn readable(&mut self, context: &mut Context) { println!("======= MUX H2 READABLE"); let kawa = if let Some((stream_id, amount)) = self.expect { - let kawa = streams[stream_id].front(self.position); + let kawa = context.streams.get(stream_id).front(self.position); let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); println!("{:?}({stream_id}, {amount}) {size} {status:?}", self.state); if size > 0 { @@ -412,11 +443,11 @@ impl ConnectionH2 { match parser::settings_frame(i, i.len()) { Ok((_, settings)) => { kawa.storage.clear(); - self.handle(settings, streams); + self.handle(settings, context); } Err(e) => panic!("{e:?}"), } - let kawa = &mut streams[0].back; + let kawa = &mut context.streams.zero.back; self.state = H2State::ServerSettings; match serializer::gen_frame_header( kawa.storage.space(), @@ -463,7 +494,7 @@ impl ConnectionH2 { { *stream_id } else { - self.create_stream(header.stream_id, streams) + self.create_stream(header.stream_id, context) }; let stream_id = if header.frame_type == parser::FrameType::Headers { 0 @@ -483,7 +514,7 @@ impl ConnectionH2 { match parser::frame_body(i, header, self.settings.settings_max_frame_size) { Ok((_, frame)) => { kawa.storage.clear(); - self.handle(frame, streams); + self.handle(frame, context); } Err(e) => panic!("{e:?}"), } @@ -494,15 +525,14 @@ impl ConnectionH2 { } } - fn writable(&mut self, streams: &mut Streams) { + fn writable(&mut self, context: &mut Context) { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), (H2State::ClientPreface, Position::Server) => unreachable!(), (H2State::ServerSettings, Position::Client) => unreachable!(), (H2State::ServerSettings, Position::Server) => { - let stream = &mut streams[0]; - let kawa = &mut stream.back; + let kawa = &mut context.streams.zero.back; println!("{:?}", kawa.storage.data()); let (size, status) = self.socket.socket_write(kawa.storage.data()); println!(" size: {size}, status: {status:?}"); @@ -519,8 +549,8 @@ impl ConnectionH2 { } } - pub fn create_stream(&mut self, stream_id: StreamId, streams: &mut Streams) -> GlobalStreamId { - match streams.create_stream(Ulid::generate(), self.settings.settings_initial_window_size) { + pub fn create_stream(&mut self, stream_id: StreamId, context: &mut Context) -> GlobalStreamId { + match context.create_stream(Ulid::generate(), self.settings.settings_initial_window_size) { Ok(global_stream_id) => { self.streams.insert(stream_id, global_stream_id); global_stream_id @@ -529,18 +559,45 @@ impl ConnectionH2 { } } - fn handle(&mut self, frame: parser::Frame, streams: &mut Streams) { + fn handle(&mut self, frame: parser::Frame, context: &mut Context) { println!("{frame:?}"); match frame { parser::Frame::Data(_) => todo!(), parser::Frame::Headers(headers) => { - let kawa = streams[0].front(self.position); + // if !headers.end_headers { + // self.state = H2State::Continuation + // } + let global_stream_id = self.streams.get(&headers.stream_id).unwrap(); + let kawa = context.streams.zero.front(self.position); let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); + // this is Kawa's Territory, it may be rewritten in Kawa and called as: + // kawa::h2::handle_header(buffer, kawa, EditorCallBacks) + let kawa = context.streams.others[*global_stream_id - 1].front(self.position); println!("{buffer:?}"); - let result = self.decoder.decode(buffer).unwrap(); - for (k, v) in result { - unsafe { println!("{} {}", from_utf8_unchecked(&k), from_utf8_unchecked(&v)) }; - } + context + .decoder + .decode_with_cb(buffer, |k, v| { + // Proof of concept that we can decompress stream 0 fragments into another stream + // this is incomplete as pseudo headers should be sorted out and stored as a kawa::StatusLine + let start = kawa.storage.end as u32; + kawa.storage.write(&k).unwrap(); + kawa.storage.write(&v).unwrap(); + let len_key = k.len() as u32; + let len_val = v.len() as u32; + kawa.push_block(kawa::Block::Header(kawa::Pair { + key: kawa::Store::Slice(kawa::repr::Slice { + start, + len: len_key, + }), + val: kawa::Store::Slice(kawa::repr::Slice { + start: start + len_key, + len: len_val, + }), + })); + }) + .unwrap(); + // everything has been parsed + kawa.storage.head = kawa.storage.end; } parser::Frame::Priority => todo!(), parser::Frame::RstStream(_) => todo!(), @@ -562,7 +619,8 @@ impl ConnectionH2 { parser::Frame::Ping(_) => todo!(), parser::Frame::GoAway => todo!(), parser::Frame::WindowUpdate(update) => { - streams[update.stream_id as usize].window += update.increment as i32; + let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); + context.streams.get(global_stream_id).window += update.increment as i32; } parser::Frame::Continuation => todo!(), } @@ -570,9 +628,9 @@ impl ConnectionH2 { } impl ConnectionH1 { - fn readable(&mut self, streams: &mut Streams) { + fn readable(&mut self, context: &mut Context) { println!("======= MUX H1 READABLE"); - let stream = &mut streams[self.stream]; + let stream = &mut context.streams.get(self.stream); let kawa = match self.position { Position::Client => &mut stream.front, Position::Server => &mut stream.back, @@ -596,9 +654,9 @@ impl ConnectionH1 { self.readiness.interest.remove(Ready::READABLE); } } - fn writable(&mut self, streams: &mut Streams) { + fn writable(&mut self, context: &mut Context) { println!("======= MUX H1 WRITABLE"); - let stream = &mut streams[self.stream]; + let stream = &mut context.streams.get(self.stream); let kawa = match self.position { Position::Client => &mut stream.back, Position::Server => &mut stream.front, From fa2ca5ecf62f70f9bc94bec0f1bbb4b7529928d1 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 22 Aug 2023 11:32:22 +0200 Subject: [PATCH 07/44] Continue frame handling: - Implement Priority, PushPromise, GoAway and Continuation frame parsing - Add kawa_h1::HttpContext to Streams - Separate headers handling into pkawa module (will be relocated to Kawa) - Use kawa_h1::editor callbacks to edit h2 headers and HttpContext Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/mod.rs | 86 ++++++------ lib/src/protocol/mux/parser.rs | 235 +++++++++++++++++++++++++-------- lib/src/protocol/mux/pkawa.rs | 107 +++++++++++++++ 3 files changed, 334 insertions(+), 94 deletions(-) create mode 100644 lib/src/protocol/mux/pkawa.rs diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 2dc3721b7..22d2b5ae2 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -6,21 +6,25 @@ use std::{ rc::{Rc, Weak}, }; +use kawa::h1::ParserCallbacks; use mio::{net::TcpStream, Token}; use rusty_ulid::Ulid; use sozu_command::ready::Ready; mod parser; +mod pkawa; mod serializer; use crate::{ https::HttpsListener, pool::{Checkout, Pool}, - protocol::SessionState, + protocol::{mux::parser::error_code_to_str, SessionState}, socket::{FrontRustls, SocketHandler, SocketResult}, AcceptError, L7Proxy, ProxySession, Readiness, SessionMetrics, SessionResult, StateResult, }; +use super::http::editor::HttpContext; + /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer type GenericHttpStream = kawa::Kawa; type StreamId = u32; @@ -85,10 +89,11 @@ pub struct ConnectionH2 { } pub struct Stream { - pub request_id: Ulid, + // pub request_id: Ulid, pub window: i32, pub front: GenericHttpStream, pub back: GenericHttpStream, + pub context: HttpContext, } impl Stream { @@ -229,10 +234,27 @@ impl Context { None => return Err(AcceptError::BufferCapacityReached), }; Ok(Stream { - request_id, window: window as i32, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), + context: HttpContext { + keep_alive_backend: false, + keep_alive_frontend: false, + sticky_session_found: None, + method: None, + authority: None, + path: None, + status: None, + reason: None, + user_agent: None, + closing: false, + id: request_id, + protocol: crate::Protocol::HTTPS, + public_address: "0.0.0.0:80".parse().unwrap(), + session_address: None, + sticky_name: "SOZUBALANCEID".to_owned(), + sticky_session: None, + }, }) } @@ -381,8 +403,16 @@ impl SessionState for Mux { let mut b = [0; 1024]; let (size, status) = s.socket_read(&mut b); println!("{size} {status:?} {:?}", &b[..size]); - for stream in &self.context.streams.others { - kawa::debug_kawa(&stream.front); + for stream in &mut self.context.streams.others { + let kawa = &mut stream.front; + kawa::debug_kawa(kawa); + kawa.prepare(&mut kawa::h1::BlockConverter); + let out = kawa.as_io_slice(); + let mut writer = std::io::BufWriter::new(Vec::new()); + let amount = writer.write_vectored(&out).unwrap(); + println!("amount: {amount}\n{}", unsafe { + std::str::from_utf8_unchecked(writer.buffer()) + }); } } } @@ -496,10 +526,10 @@ impl ConnectionH2 { } else { self.create_stream(header.stream_id, context) }; - let stream_id = if header.frame_type == parser::FrameType::Headers { - 0 - } else { + let stream_id = if header.frame_type == parser::FrameType::Data { stream_id + } else { + 0 }; println!("{} {} {:#?}", header.stream_id, stream_id, self.streams); self.expect = Some((stream_id as usize, header.payload_len as usize)); @@ -570,36 +600,12 @@ impl ConnectionH2 { let global_stream_id = self.streams.get(&headers.stream_id).unwrap(); let kawa = context.streams.zero.front(self.position); let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); - // this is Kawa's Territory, it may be rewritten in Kawa and called as: - // kawa::h2::handle_header(buffer, kawa, EditorCallBacks) - let kawa = context.streams.others[*global_stream_id - 1].front(self.position); - println!("{buffer:?}"); - context - .decoder - .decode_with_cb(buffer, |k, v| { - // Proof of concept that we can decompress stream 0 fragments into another stream - // this is incomplete as pseudo headers should be sorted out and stored as a kawa::StatusLine - let start = kawa.storage.end as u32; - kawa.storage.write(&k).unwrap(); - kawa.storage.write(&v).unwrap(); - let len_key = k.len() as u32; - let len_val = v.len() as u32; - kawa.push_block(kawa::Block::Header(kawa::Pair { - key: kawa::Store::Slice(kawa::repr::Slice { - start, - len: len_key, - }), - val: kawa::Store::Slice(kawa::repr::Slice { - start: start + len_key, - len: len_val, - }), - })); - }) - .unwrap(); - // everything has been parsed - kawa.storage.head = kawa.storage.end; + let stream = &mut context.streams.others[*global_stream_id - 1]; + let kawa = &mut stream.front; + pkawa::handle_header(kawa, buffer, &mut context.decoder); + stream.context.on_headers(kawa); } - parser::Frame::Priority => todo!(), + parser::Frame::Priority(priority) => (), parser::Frame::RstStream(_) => todo!(), parser::Frame::Settings(settings) => { for setting in settings.settings { @@ -615,14 +621,14 @@ impl ConnectionH2 { } println!("{:#?}", self.settings); } - parser::Frame::PushPromise => todo!(), + parser::Frame::PushPromise(_) => todo!(), parser::Frame::Ping(_) => todo!(), - parser::Frame::GoAway => todo!(), + parser::Frame::GoAway(goaway) => panic!("{}", error_code_to_str(goaway.error_code)), parser::Frame::WindowUpdate(update) => { let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); context.streams.get(global_stream_id).window += update.increment as i32; } - parser::Frame::Continuation => todo!(), + parser::Frame::Continuation(_) => todo!(), } } } diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index c7558d7e9..24134ec58 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -8,7 +8,7 @@ use nom::{ multi::many0, number::streaming::{be_u16, be_u24, be_u32, be_u8}, sequence::tuple, - Err, HexDisplay, IResult, Offset, + Err, IResult, }; #[derive(Clone, Debug, PartialEq)] @@ -33,7 +33,6 @@ pub enum FrameType { Continuation, } -/* const NO_ERROR: u32 = 0x0; const PROTOCOL_ERROR: u32 = 0x1; const INTERNAL_ERROR: u32 = 0x2; @@ -48,7 +47,26 @@ const CONNECT_ERROR: u32 = 0xa; const ENHANCE_YOUR_CALM: u32 = 0xb; const INADEQUATE_SECURITY: u32 = 0xc; const HTTP_1_1_REQUIRED: u32 = 0xd; -*/ + +pub fn error_code_to_str(error_code: u32) -> &'static str { + match error_code { + NO_ERROR => "NO_ERROR", + PROTOCOL_ERROR => "PROTOCOL_ERROR", + INTERNAL_ERROR => "INTERNAL_ERROR", + FLOW_CONTROL_ERROR => "FLOW_CONTROL_ERROR", + SETTINGS_TIMEOUT => "SETTINGS_TIMEOUT", + STREAM_CLOSED => "STREAM_CLOSED", + FRAME_SIZE_ERROR => "FRAME_SIZE_ERROR", + REFUSED_STREAM => "REFUSED_STREAM", + CANCEL => "CANCEL", + COMPRESSION_ERROR => "COMPRESSION_ERROR", + CONNECT_ERROR => "CONNECT_ERROR", + ENHANCE_YOUR_CALM => "ENHANCE_YOUR_CALM", + INADEQUATE_SECURITY => "INADEQUATE_SECURITY", + HTTP_1_1_REQUIRED => "HTTP_1_1_REQUIRED", + _ => "UNKNOWN_ERROR", + } +} #[derive(Clone, Debug, PartialEq)] pub struct Error<'a> { @@ -127,13 +145,13 @@ pub fn preface(i: &[u8]) -> IResult<&[u8], &[u8]> { */ pub fn frame_header(input: &[u8]) -> IResult<&[u8], FrameHeader, Error> { - let (i1, payload_len) = be_u24(input)?; - let (i2, frame_type) = map_opt(be_u8, convert_frame_type)(i1)?; - let (i3, flags) = be_u8(i2)?; - let (i4, stream_id) = be_u32(i3)?; + let (i, payload_len) = be_u24(input)?; + let (i, frame_type) = map_opt(be_u8, convert_frame_type)(i)?; + let (i, flags) = be_u8(i)?; + let (i, stream_id) = be_u32(i)?; Ok(( - i4, + i, FrameHeader { payload_len, frame_type, @@ -164,14 +182,14 @@ fn convert_frame_type(t: u8) -> Option { pub enum Frame { Data(Data), Headers(Headers), - Priority, + Priority(Priority), RstStream(RstStream), Settings(Settings), - PushPromise, + PushPromise(PushPromise), Ping(Ping), - GoAway, + GoAway(GoAway), WindowUpdate(WindowUpdate), - Continuation, + Continuation(Continuation), } impl Frame { @@ -179,11 +197,11 @@ impl Frame { match self { Frame::Data(_) | Frame::Headers(_) - | Frame::Priority + | Frame::Priority(_) | Frame::RstStream(_) - | Frame::PushPromise - | Frame::Continuation => true, - Frame::Settings(_) | Frame::Ping(_) | Frame::GoAway => false, + | Frame::PushPromise(_) + | Frame::Continuation(_) => true, + Frame::Settings(_) | Frame::Ping(_) | Frame::GoAway(_) => false, Frame::WindowUpdate(w) => w.stream_id != 0, } } @@ -192,11 +210,11 @@ impl Frame { match self { Frame::Data(d) => d.stream_id, Frame::Headers(h) => h.stream_id, - Frame::Priority => unimplemented!(), + Frame::Priority(p) => p.stream_id, Frame::RstStream(r) => r.stream_id, - Frame::PushPromise => unimplemented!(), - Frame::Continuation => unimplemented!(), - Frame::Settings(_) | Frame::Ping(_) | Frame::GoAway => 0, + Frame::PushPromise(p) => p.stream_id, + Frame::Continuation(c) => c.stream_id, + Frame::Settings(_) | Frame::Ping(_) | Frame::GoAway(_) => 0, Frame::WindowUpdate(w) => w.stream_id, } } @@ -233,7 +251,7 @@ pub fn frame_body<'a>( if header.payload_len != 5 { return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } - unimplemented!(); + priority_frame(i, header)? } FrameType::RstStream => { if header.payload_len != 4 { @@ -241,9 +259,7 @@ pub fn frame_body<'a>( } rst_stream_frame(i, &header)? } - FrameType::PushPromise => { - unimplemented!(); - } + FrameType::PushPromise => push_promise_frame(i, &header)?, FrameType::Continuation => { unimplemented!(); } @@ -259,9 +275,7 @@ pub fn frame_body<'a>( } ping_frame(i, &header)? } - FrameType::GoAway => { - unimplemented!(); - } + FrameType::GoAway => goaway_frame(i, &header)?, FrameType::WindowUpdate => { if header.payload_len != 4 { return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); @@ -280,24 +294,24 @@ pub struct Data { pub end_stream: bool, } -pub fn data_frame<'a, 'b>( +pub fn data_frame<'a>( input: &'a [u8], - header: &'b FrameHeader, + header: &FrameHeader, ) -> IResult<&'a [u8], Frame, Error<'a>> { let (remaining, i) = take(header.payload_len)(input)?; - let (i1, pad_length) = if header.flags & 0x8 != 0 { + let (i, pad_length) = if header.flags & 0x8 != 0 { let (i, pad_length) = be_u8(i)?; (i, Some(pad_length)) } else { (i, None) }; - if pad_length.is_some() && i1.len() <= pad_length.unwrap() as usize { + if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); } - let (_, payload) = take(i1.len() - pad_length.unwrap_or(0) as usize)(i1)?; + let (_, payload) = take(i.len() - pad_length.unwrap_or(0) as usize)(i)?; Ok(( remaining, @@ -327,41 +341,46 @@ pub struct StreamDependency { pub stream_id: u32, } -pub fn headers_frame<'a, 'b>( +fn stream_dependency<'a>(i: &'a [u8]) -> IResult<&'a [u8], StreamDependency, Error<'a>> { + let (i, stream) = map(be_u32, |i| StreamDependency { + exclusive: i & 0x8000 != 0, + stream_id: i & 0x7FFFFFFF, + })(i)?; + Ok((i, stream)) +} + +pub fn headers_frame<'a>( input: &'a [u8], - header: &'b FrameHeader, + header: &FrameHeader, ) -> IResult<&'a [u8], Frame, Error<'a>> { let (remaining, i) = take(header.payload_len)(input)?; - let (i1, pad_length) = if header.flags & 0x8 != 0 { + let (i, pad_length) = if header.flags & 0x8 != 0 { let (i, pad_length) = be_u8(i)?; (i, Some(pad_length)) } else { (i, None) }; - let (i2, stream_dependency) = if header.flags & 0x20 != 0 { - let (i, stream) = map(be_u32, |i| StreamDependency { - exclusive: i & 0x8000 != 0, - stream_id: i & 0x7FFFFFFF, - })(i1)?; - (i, Some(stream)) + let (i, stream_dependency) = if header.flags & 0x20 != 0 { + let (i, dep) = stream_dependency(i)?; + (i, Some(dep)) } else { - (i1, None) + (i, None) }; - let (i3, weight) = if header.flags & 0x20 != 0 { - let (i, weight) = be_u8(i2)?; + let (i, weight) = if header.flags & 0x20 != 0 { + let (i, weight) = be_u8(i)?; (i, Some(weight)) } else { - (i2, None) + (i, None) }; - if pad_length.is_some() && i3.len() <= pad_length.unwrap() as usize { + if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); } - let (_, header_block_fragment) = take(i3.len() - pad_length.unwrap_or(0) as usize)(i3)?; + let (_, header_block_fragment) = take(i.len() - pad_length.unwrap_or(0) as usize)(i)?; Ok(( remaining, @@ -378,15 +397,38 @@ pub fn headers_frame<'a, 'b>( )) } +#[derive(Clone, Debug, PartialEq)] +pub struct Priority { + pub stream_id: u32, + pub stream_dependency: StreamDependency, + pub weight: u8, +} + +pub fn priority_frame<'a>( + input: &'a [u8], + header: &FrameHeader, +) -> IResult<&'a [u8], Frame, Error<'a>> { + let (i, stream_dependency) = stream_dependency(input)?; + let (i, weight) = be_u8(i)?; + Ok(( + i, + Frame::Priority(Priority { + stream_dependency, + stream_id: header.stream_id, + weight, + }), + )) +} + #[derive(Clone, Debug, PartialEq)] pub struct RstStream { pub stream_id: u32, pub error_code: u32, } -pub fn rst_stream_frame<'a, 'b>( +pub fn rst_stream_frame<'a>( input: &'a [u8], - header: &'b FrameHeader, + header: &FrameHeader, ) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, error_code) = be_u32(input)?; Ok(( @@ -409,7 +451,7 @@ pub struct Setting { pub value: u32, } -pub fn settings_frame<'a, 'b>( +pub fn settings_frame<'a>( input: &'a [u8], payload_len: usize, ) -> IResult<&'a [u8], Frame, Error<'a>> { @@ -423,14 +465,53 @@ pub fn settings_frame<'a, 'b>( Ok((i, Frame::Settings(Settings { settings }))) } +#[derive(Clone, Debug)] +pub struct PushPromise { + pub stream_id: u32, + pub promised_stream_id: u32, + pub header_block_fragment: Slice, + pub end_headers: bool, +} + +pub fn push_promise_frame<'a>( + input: &'a [u8], + header: &FrameHeader, +) -> IResult<&'a [u8], Frame, Error<'a>> { + let (remaining, i) = take(header.payload_len)(input)?; + + let (i, pad_length) = if header.flags & 0x8 != 0 { + let (i, pad_length) = be_u8(i)?; + (i, Some(pad_length)) + } else { + (i, None) + }; + + if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { + return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + } + + let (i, promised_stream_id) = be_u32(i)?; + let (_, header_block_fragment) = take(i.len() - pad_length.unwrap_or(0) as usize)(i)?; + + Ok(( + remaining, + Frame::PushPromise(PushPromise { + stream_id: header.stream_id, + promised_stream_id, + header_block_fragment: Slice::new(input, header_block_fragment), + end_headers: header.flags & 0x4 != 0, + }), + )) +} + #[derive(Clone, Debug, PartialEq)] pub struct Ping { pub payload: [u8; 8], } -pub fn ping_frame<'a, 'b>( +pub fn ping_frame<'a>( input: &'a [u8], - header: &'b FrameHeader, + header: &FrameHeader, ) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, data) = take(8usize)(input)?; @@ -443,15 +524,39 @@ pub fn ping_frame<'a, 'b>( Ok((i, Frame::Ping(p))) } +#[derive(Clone, Debug)] +pub struct GoAway { + pub last_stream_id: u32, + pub error_code: u32, + pub additional_debug_data: Slice, +} + +pub fn goaway_frame<'a>( + input: &'a [u8], + header: &FrameHeader, +) -> IResult<&'a [u8], Frame, Error<'a>> { + let (remaining, i) = take(header.payload_len)(input)?; + let (i, last_stream_id) = be_u32(i)?; + let (additional_debug_data, error_code) = be_u32(i)?; + Ok(( + remaining, + Frame::GoAway(GoAway { + last_stream_id, + error_code, + additional_debug_data: Slice::new(input, additional_debug_data), + }), + )) +} + #[derive(Clone, Debug, PartialEq)] pub struct WindowUpdate { pub stream_id: u32, pub increment: u32, } -pub fn window_update_frame<'a, 'b>( +pub fn window_update_frame<'a>( input: &'a [u8], - header: &'b FrameHeader, + header: &FrameHeader, ) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, increment) = be_u32(input)?; let increment = increment & 0x7FFFFFFF; @@ -469,3 +574,25 @@ pub fn window_update_frame<'a, 'b>( }), )) } + +#[derive(Clone, Debug)] +pub struct Continuation { + pub stream_id: u32, + pub header_block_fragment: Slice, + pub end_headers: bool, +} + +pub fn continuation_frame<'a>( + input: &'a [u8], + header: &FrameHeader, +) -> IResult<&'a [u8], Frame, Error<'a>> { + let (i, header_block_fragment) = take(header.payload_len)(input)?; + Ok(( + i, + Frame::Continuation(Continuation { + stream_id: header.stream_id, + header_block_fragment: Slice::new(input, header_block_fragment), + end_headers: header.flags & 0x4 != 0, + }), + )) +} diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs new file mode 100644 index 000000000..85d836641 --- /dev/null +++ b/lib/src/protocol/mux/pkawa.rs @@ -0,0 +1,107 @@ +use std::{io::Write, str::from_utf8_unchecked}; + +use crate::protocol::http::parser::compare_no_case; + +use super::GenericHttpStream; + +pub fn handle_header(kawa: &mut GenericHttpStream, input: &[u8], decoder: &mut hpack::Decoder) { + println!("{input:?}"); + kawa.push_block(kawa::Block::StatusLine); + kawa.detached.status_line = match kawa.kind { + kawa::Kind::Request => { + let mut method = kawa::Store::Empty; + let mut authority = kawa::Store::Empty; + let mut path = kawa::Store::Empty; + let mut scheme = kawa::Store::Empty; + decoder + .decode_with_cb(input, |k, v| { + let start = kawa.storage.end as u32; + kawa.storage.write(&k).unwrap(); + kawa.storage.write(&v).unwrap(); + let len_key = k.len() as u32; + let len_val = v.len() as u32; + let key = kawa::Store::Slice(kawa::repr::Slice { + start, + len: len_key, + }); + let val = kawa::Store::Slice(kawa::repr::Slice { + start: start + len_key, + len: len_val, + }); + + if compare_no_case(&k, b":method") { + method = val; + } else if compare_no_case(&k, b":authority") { + authority = val; + } else if compare_no_case(&k, b":path") { + path = val; + } else if compare_no_case(&k, b":scheme") { + scheme = val; + } else { + kawa.push_block(kawa::Block::Header(kawa::Pair { key, val })); + } + }) + .unwrap(); + let buffer = kawa.storage.data(); + let uri = unsafe { + format!( + "{}://{}{}", + from_utf8_unchecked(scheme.data(buffer)), + from_utf8_unchecked(authority.data(buffer)), + from_utf8_unchecked(path.data(buffer)) + ) + }; + println!("Reconstructed URI: {uri}"); + kawa::StatusLine::Request { + version: kawa::Version::V20, + method, + uri: kawa::Store::from_string(uri), + authority, + path, + } + } + kawa::Kind::Response => { + let mut code = 0; + let mut status = kawa::Store::Empty; + decoder + .decode_with_cb(input, |k, v| { + let start = kawa.storage.end as u32; + kawa.storage.write(&k).unwrap(); + kawa.storage.write(&v).unwrap(); + let len_key = k.len() as u32; + let len_val = v.len() as u32; + let key = kawa::Store::Slice(kawa::repr::Slice { + start, + len: len_key, + }); + let val = kawa::Store::Slice(kawa::repr::Slice { + start: start + len_key, + len: len_val, + }); + + if compare_no_case(&k, b":status") { + status = val; + code = unsafe { + std::str::from_utf8_unchecked(&k) + .parse::() + .ok() + .unwrap() + } + } else { + kawa.push_block(kawa::Block::Header(kawa::Pair { key, val })); + } + }) + .unwrap(); + kawa::StatusLine::Response { + version: kawa::Version::V20, + code, + status, + reason: kawa::Store::Empty, + } + } + }; + + // everything has been parsed + kawa.storage.head = kawa.storage.end; + kawa.parsing_phase = kawa::ParsingPhase::Chunks { first: true }; +} From 5a18f93ca91e20953124809afdb47b12a06bad3a Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 22 Aug 2023 14:51:33 +0200 Subject: [PATCH 08/44] Split mux in h1 and h2 files Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/h1.rs | 66 ++++++ lib/src/protocol/mux/h2.rs | 274 ++++++++++++++++++++++++ lib/src/protocol/mux/mod.rs | 416 +++++------------------------------- 3 files changed, 391 insertions(+), 365 deletions(-) create mode 100644 lib/src/protocol/mux/h1.rs create mode 100644 lib/src/protocol/mux/h2.rs diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs new file mode 100644 index 000000000..d6d05f53d --- /dev/null +++ b/lib/src/protocol/mux/h1.rs @@ -0,0 +1,66 @@ +use sozu_command::ready::Ready; + +use crate::{ + protocol::mux::{Context, GlobalStreamId, Position}, + socket::{SocketHandler, SocketResult}, + Readiness, +}; + +pub struct ConnectionH1 { + pub position: Position, + pub readiness: Readiness, + pub socket: Front, + /// note: a Server H1 will always reference stream 0, but a client can reference any stream + pub stream: GlobalStreamId, +} + +impl ConnectionH1 { + pub fn readable(&mut self, context: &mut Context) { + println!("======= MUX H1 READABLE"); + let stream = &mut context.streams.get(self.stream); + let kawa = match self.position { + Position::Client => &mut stream.front, + Position::Server => &mut stream.back, + }; + let (size, status) = self.socket.socket_read(kawa.storage.space()); + println!(" size: {size}, status: {status:?}"); + if size > 0 { + kawa.storage.fill(size); + } else { + self.readiness.event.remove(Ready::READABLE); + } + match status { + SocketResult::Continue => {} + SocketResult::Closed => todo!(), + SocketResult::Error => todo!(), + SocketResult::WouldBlock => self.readiness.event.remove(Ready::READABLE), + } + kawa::h1::parse(kawa, &mut kawa::h1::NoCallbacks); + kawa::debug_kawa(kawa); + if kawa.is_terminated() { + self.readiness.interest.remove(Ready::READABLE); + } + } + pub fn writable(&mut self, context: &mut Context) { + println!("======= MUX H1 WRITABLE"); + let stream = &mut context.streams.get(self.stream); + let kawa = match self.position { + Position::Client => &mut stream.back, + Position::Server => &mut stream.front, + }; + kawa.prepare(&mut kawa::h1::BlockConverter); + let bufs = kawa.as_io_slice(); + if bufs.is_empty() { + self.readiness.interest.remove(Ready::WRITABLE); + return; + } + let (size, status) = self.socket.socket_write_vectored(&bufs); + println!(" size: {size}, status: {status:?}"); + if size > 0 { + kawa.consume(size); + // self.backend_readiness.interest.insert(Ready::READABLE); + } else { + self.readiness.event.remove(Ready::WRITABLE); + } + } +} diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs new file mode 100644 index 000000000..4322cc336 --- /dev/null +++ b/lib/src/protocol/mux/h2.rs @@ -0,0 +1,274 @@ +use std::collections::HashMap; + +use kawa::h1::ParserCallbacks; +use rusty_ulid::Ulid; +use sozu_command::ready::Ready; + +use crate::{ + protocol::mux::{ + parser::{self, error_code_to_str, FrameHeader}, + pkawa, serializer, Context, GlobalStreamId, Position, StreamId, + }, + socket::SocketHandler, + Readiness, +}; + +#[derive(Debug)] +pub enum H2State { + ClientPreface, + ClientSettings, + ServerSettings, + Header, + Frame(FrameHeader), + Error, +} + +#[derive(Debug)] +pub struct H2Settings { + settings_header_table_size: u32, + settings_enable_push: bool, + settings_max_concurrent_streams: u32, + settings_initial_window_size: u32, + settings_max_frame_size: u32, + settings_max_header_list_size: u32, +} + +impl Default for H2Settings { + fn default() -> Self { + Self { + settings_header_table_size: 4096, + settings_enable_push: true, + settings_max_concurrent_streams: u32::MAX, + settings_initial_window_size: (1 << 16) - 1, + settings_max_frame_size: 1 << 14, + settings_max_header_list_size: u32::MAX, + } + } +} + +pub struct ConnectionH2 { + // pub decoder: hpack::Decoder<'static>, + pub expect: Option<(GlobalStreamId, usize)>, + pub position: Position, + pub readiness: Readiness, + pub settings: H2Settings, + pub socket: Front, + pub state: H2State, + pub streams: HashMap, +} + +impl ConnectionH2 { + pub fn readable(&mut self, context: &mut Context) { + println!("======= MUX H2 READABLE"); + let kawa = if let Some((stream_id, amount)) = self.expect { + let kawa = context.streams.get(stream_id).front(self.position); + let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); + println!("{:?}({stream_id}, {amount}) {size} {status:?}", self.state); + if size > 0 { + kawa.storage.fill(size); + if size == amount { + self.expect = None; + } else { + self.expect = Some((stream_id, amount - size)); + return; + } + } else { + self.readiness.event.remove(Ready::READABLE); + return; + } + kawa + } else { + self.readiness.event.remove(Ready::READABLE); + return; + }; + match (&self.state, &self.position) { + (H2State::ClientPreface, Position::Client) => { + error!("Waiting for ClientPreface to finish writing") + } + (H2State::ClientPreface, Position::Server) => { + let i = kawa.storage.data(); + let i = match parser::preface(i) { + Ok((i, _)) => i, + Err(e) => panic!("{e:?}"), + }; + match parser::frame_header(i) { + Ok(( + _, + parser::FrameHeader { + payload_len, + frame_type: parser::FrameType::Settings, + flags: 0, + stream_id: 0, + }, + )) => { + kawa.storage.clear(); + self.state = H2State::ClientSettings; + self.expect = Some((0, payload_len as usize)); + } + _ => todo!(), + }; + } + (H2State::ClientSettings, Position::Server) => { + let i = kawa.storage.data(); + match parser::settings_frame(i, i.len()) { + Ok((_, settings)) => { + kawa.storage.clear(); + self.handle(settings, context); + } + Err(e) => panic!("{e:?}"), + } + let kawa = &mut context.streams.zero.back; + self.state = H2State::ServerSettings; + match serializer::gen_frame_header( + kawa.storage.space(), + &parser::FrameHeader { + payload_len: 6 * 2, + frame_type: parser::FrameType::Settings, + flags: 0, + stream_id: 0, + }, + ) { + Ok((_, size)) => kawa.storage.fill(size), + Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), + }; + // kawa.storage + // .write(&[1, 3, 0, 0, 0, 100, 0, 4, 0, 1, 0, 0]) + // .unwrap(); + match serializer::gen_frame_header( + kawa.storage.space(), + &parser::FrameHeader { + payload_len: 0, + frame_type: parser::FrameType::Settings, + flags: 1, + stream_id: 0, + }, + ) { + Ok((_, size)) => kawa.storage.fill(size), + Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), + }; + self.readiness.interest.insert(Ready::WRITABLE); + self.readiness.interest.remove(Ready::READABLE); + } + (H2State::ServerSettings, Position::Client) => todo!("Receive server Settings"), + (H2State::ServerSettings, Position::Server) => { + error!("waiting for ServerPreface to finish writing") + } + (H2State::Header, Position::Server) => { + let i = kawa.storage.data(); + println!(" header: {i:?}"); + match parser::frame_header(i) { + Ok((_, header)) => { + println!("{header:?}"); + kawa.storage.clear(); + let stream_id = if let Some(stream_id) = self.streams.get(&header.stream_id) + { + *stream_id + } else { + self.create_stream(header.stream_id, context) + }; + let stream_id = if header.frame_type == parser::FrameType::Data { + stream_id + } else { + 0 + }; + println!("{} {} {:#?}", header.stream_id, stream_id, self.streams); + self.expect = Some((stream_id as usize, header.payload_len as usize)); + self.state = H2State::Frame(header); + } + Err(e) => panic!("{e:?}"), + }; + } + (H2State::Frame(header), Position::Server) => { + let i = kawa.storage.data(); + println!(" data: {i:?}"); + match parser::frame_body(i, header, self.settings.settings_max_frame_size) { + Ok((_, frame)) => { + kawa.storage.clear(); + self.handle(frame, context); + } + Err(e) => panic!("{e:?}"), + } + self.state = H2State::Header; + self.expect = Some((0, 9)); + } + _ => unreachable!(), + } + } + + pub fn writable(&mut self, context: &mut Context) { + println!("======= MUX H2 WRITABLE"); + match (&self.state, &self.position) { + (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), + (H2State::ClientPreface, Position::Server) => unreachable!(), + (H2State::ServerSettings, Position::Client) => unreachable!(), + (H2State::ServerSettings, Position::Server) => { + let kawa = &mut context.streams.zero.back; + println!("{:?}", kawa.storage.data()); + let (size, status) = self.socket.socket_write(kawa.storage.data()); + println!(" size: {size}, status: {status:?}"); + let size = kawa.storage.available_data(); + kawa.storage.consume(size); + if kawa.storage.is_empty() { + self.readiness.interest.remove(Ready::WRITABLE); + self.readiness.interest.insert(Ready::READABLE); + self.state = H2State::Header; + self.expect = Some((0, 9)); + } + } + _ => unreachable!(), + } + } + + pub fn create_stream(&mut self, stream_id: StreamId, context: &mut Context) -> GlobalStreamId { + match context.create_stream(Ulid::generate(), self.settings.settings_initial_window_size) { + Ok(global_stream_id) => { + self.streams.insert(stream_id, global_stream_id); + global_stream_id + } + Err(e) => panic!("{e:?}"), + } + } + + fn handle(&mut self, frame: parser::Frame, context: &mut Context) { + println!("{frame:?}"); + match frame { + parser::Frame::Data(_) => todo!(), + parser::Frame::Headers(headers) => { + // if !headers.end_headers { + // self.state = H2State::Continuation + // } + let global_stream_id = self.streams.get(&headers.stream_id).unwrap(); + let kawa = context.streams.zero.front(self.position); + let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); + let stream = &mut context.streams.others[*global_stream_id - 1]; + let kawa = &mut stream.front; + pkawa::handle_header(kawa, buffer, &mut context.decoder); + stream.context.on_headers(kawa); + } + parser::Frame::Priority(priority) => (), + parser::Frame::RstStream(_) => todo!(), + parser::Frame::Settings(settings) => { + for setting in settings.settings { + match setting.identifier { + 1 => self.settings.settings_header_table_size = setting.value, + 2 => self.settings.settings_enable_push = setting.value == 1, + 3 => self.settings.settings_max_concurrent_streams = setting.value, + 4 => self.settings.settings_initial_window_size = setting.value, + 5 => self.settings.settings_max_frame_size = setting.value, + 6 => self.settings.settings_max_header_list_size = setting.value, + other => panic!("setting_id: {other}"), + } + } + println!("{:#?}", self.settings); + } + parser::Frame::PushPromise(_) => todo!(), + parser::Frame::Ping(_) => todo!(), + parser::Frame::GoAway(goaway) => panic!("{}", error_code_to_str(goaway.error_code)), + parser::Frame::WindowUpdate(update) => { + let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); + context.streams.get(global_stream_id).window += update.increment as i32; + } + parser::Frame::Continuation(_) => todo!(), + } + } +} diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 22d2b5ae2..6b4f63fe5 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -6,11 +6,12 @@ use std::{ rc::{Rc, Weak}, }; -use kawa::h1::ParserCallbacks; use mio::{net::TcpStream, Token}; use rusty_ulid::Ulid; use sozu_command::ready::Ready; +mod h1; +mod h2; mod parser; mod pkawa; mod serializer; @@ -18,12 +19,16 @@ mod serializer; use crate::{ https::HttpsListener, pool::{Checkout, Pool}, - protocol::{mux::parser::error_code_to_str, SessionState}, - socket::{FrontRustls, SocketHandler, SocketResult}, + protocol::{ + http::editor::HttpContext, + mux::{h1::ConnectionH1, h2::ConnectionH2}, + SessionState, + }, + socket::{FrontRustls, SocketHandler}, AcceptError, L7Proxy, ProxySession, Readiness, SessionMetrics, SessionResult, StateResult, }; -use super::http::editor::HttpContext; +use self::h2::{H2State, H2Settings}; /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer type GenericHttpStream = kawa::Kawa; @@ -36,81 +41,6 @@ pub enum Position { Server, } -pub struct ConnectionH1 { - pub position: Position, - pub readiness: Readiness, - pub socket: Front, - /// note: a Server H1 will always reference stream 0, but a client can reference any stream - pub stream: GlobalStreamId, -} - -#[derive(Debug)] -pub enum H2State { - ClientPreface, - ClientSettings, - ServerSettings, - Header, - Frame(parser::FrameHeader), - Error, -} - -#[derive(Debug)] -pub struct H2Settings { - settings_header_table_size: u32, - settings_enable_push: bool, - settings_max_concurrent_streams: u32, - settings_initial_window_size: u32, - settings_max_frame_size: u32, - settings_max_header_list_size: u32, -} - -impl Default for H2Settings { - fn default() -> Self { - Self { - settings_header_table_size: 4096, - settings_enable_push: true, - settings_max_concurrent_streams: u32::MAX, - settings_initial_window_size: (1 << 16) - 1, - settings_max_frame_size: 1 << 14, - settings_max_header_list_size: u32::MAX, - } - } -} - -pub struct ConnectionH2 { - // pub decoder: hpack::Decoder<'static>, - pub expect: Option<(GlobalStreamId, usize)>, - pub position: Position, - pub readiness: Readiness, - pub settings: H2Settings, - pub socket: Front, - pub state: H2State, - pub streams: HashMap, -} - -pub struct Stream { - // pub request_id: Ulid, - pub window: i32, - pub front: GenericHttpStream, - pub back: GenericHttpStream, - pub context: HttpContext, -} - -impl Stream { - pub fn front(&mut self, position: Position) -> &mut GenericHttpStream { - match position { - Position::Client => &mut self.back, - Position::Server => &mut self.front, - } - } - pub fn back(&mut self, position: Position) -> &mut GenericHttpStream { - match position { - Position::Client => &mut self.front, - Position::Server => &mut self.back, - } - } -} - pub enum Connection { H1(ConnectionH1), H2(ConnectionH2), @@ -195,28 +125,50 @@ impl Connection { } } +pub struct Stream { + // pub request_id: Ulid, + pub window: i32, + pub front: GenericHttpStream, + pub back: GenericHttpStream, + pub context: HttpContext, +} + +impl Stream { + pub fn front(&mut self, position: Position) -> &mut GenericHttpStream { + match position { + Position::Client => &mut self.back, + Position::Server => &mut self.front, + } + } + pub fn back(&mut self, position: Position) -> &mut GenericHttpStream { + match position { + Position::Client => &mut self.front, + Position::Server => &mut self.back, + } + } +} + pub struct Streams { zero: Stream, others: Vec, } +impl Streams { + pub fn get(&mut self, stream_id: GlobalStreamId) -> &mut Stream { + if stream_id == 0 { + &mut self.zero + } else { + &mut self.others[stream_id - 1] + } + } +} + pub struct Context { pub streams: Streams, pub pool: Weak>, pub decoder: hpack::Decoder<'static>, } -pub struct Mux { - pub frontend_token: Token, - pub frontend: Connection, - pub backends: HashMap>, - pub listener: Rc>, - pub public_address: SocketAddr, - pub peer_address: Option, - pub sticky_name: String, - pub context: Context, -} - impl Context { pub fn new_stream( pool: Weak>, @@ -285,14 +237,15 @@ impl Context { } } -impl Streams { - pub fn get(&mut self, stream_id: GlobalStreamId) -> &mut Stream { - if stream_id == 0 { - &mut self.zero - } else { - &mut self.others[stream_id - 1] - } - } +pub struct Mux { + pub frontend_token: Token, + pub frontend: Connection, + pub backends: HashMap>, + pub listener: Rc>, + pub public_address: SocketAddr, + pub peer_address: Option, + pub sticky_name: String, + pub context: Context, } impl Mux { @@ -416,270 +369,3 @@ impl SessionState for Mux { } } } - -impl ConnectionH2 { - fn readable(&mut self, context: &mut Context) { - println!("======= MUX H2 READABLE"); - let kawa = if let Some((stream_id, amount)) = self.expect { - let kawa = context.streams.get(stream_id).front(self.position); - let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); - println!("{:?}({stream_id}, {amount}) {size} {status:?}", self.state); - if size > 0 { - kawa.storage.fill(size); - if size == amount { - self.expect = None; - } else { - self.expect = Some((stream_id, amount - size)); - return; - } - } else { - self.readiness.event.remove(Ready::READABLE); - return; - } - kawa - } else { - self.readiness.event.remove(Ready::READABLE); - return; - }; - match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Client) => { - error!("Waiting for ClientPreface to finish writing") - } - (H2State::ClientPreface, Position::Server) => { - let i = kawa.storage.data(); - let i = match parser::preface(i) { - Ok((i, _)) => i, - Err(e) => panic!("{e:?}"), - }; - match parser::frame_header(i) { - Ok(( - _, - parser::FrameHeader { - payload_len, - frame_type: parser::FrameType::Settings, - flags: 0, - stream_id: 0, - }, - )) => { - kawa.storage.clear(); - self.state = H2State::ClientSettings; - self.expect = Some((0, payload_len as usize)); - } - _ => todo!(), - }; - } - (H2State::ClientSettings, Position::Server) => { - let i = kawa.storage.data(); - match parser::settings_frame(i, i.len()) { - Ok((_, settings)) => { - kawa.storage.clear(); - self.handle(settings, context); - } - Err(e) => panic!("{e:?}"), - } - let kawa = &mut context.streams.zero.back; - self.state = H2State::ServerSettings; - match serializer::gen_frame_header( - kawa.storage.space(), - &parser::FrameHeader { - payload_len: 6 * 2, - frame_type: parser::FrameType::Settings, - flags: 0, - stream_id: 0, - }, - ) { - Ok((_, size)) => kawa.storage.fill(size), - Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), - }; - // kawa.storage - // .write(&[1, 3, 0, 0, 0, 100, 0, 4, 0, 1, 0, 0]) - // .unwrap(); - match serializer::gen_frame_header( - kawa.storage.space(), - &parser::FrameHeader { - payload_len: 0, - frame_type: parser::FrameType::Settings, - flags: 1, - stream_id: 0, - }, - ) { - Ok((_, size)) => kawa.storage.fill(size), - Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), - }; - self.readiness.interest.insert(Ready::WRITABLE); - self.readiness.interest.remove(Ready::READABLE); - } - (H2State::ServerSettings, Position::Client) => todo!("Receive server Settings"), - (H2State::ServerSettings, Position::Server) => { - error!("waiting for ServerPreface to finish writing") - } - (H2State::Header, Position::Server) => { - let i = kawa.storage.data(); - println!(" header: {i:?}"); - match parser::frame_header(i) { - Ok((_, header)) => { - println!("{header:?}"); - kawa.storage.clear(); - let stream_id = if let Some(stream_id) = self.streams.get(&header.stream_id) - { - *stream_id - } else { - self.create_stream(header.stream_id, context) - }; - let stream_id = if header.frame_type == parser::FrameType::Data { - stream_id - } else { - 0 - }; - println!("{} {} {:#?}", header.stream_id, stream_id, self.streams); - self.expect = Some((stream_id as usize, header.payload_len as usize)); - self.state = H2State::Frame(header); - } - Err(e) => panic!("{e:?}"), - }; - } - (H2State::Frame(header), Position::Server) => { - let i = kawa.storage.data(); - println!(" data: {i:?}"); - match parser::frame_body(i, header, self.settings.settings_max_frame_size) { - Ok((_, frame)) => { - kawa.storage.clear(); - self.handle(frame, context); - } - Err(e) => panic!("{e:?}"), - } - self.state = H2State::Header; - self.expect = Some((0, 9)); - } - _ => unreachable!(), - } - } - - fn writable(&mut self, context: &mut Context) { - println!("======= MUX H2 WRITABLE"); - match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), - (H2State::ClientPreface, Position::Server) => unreachable!(), - (H2State::ServerSettings, Position::Client) => unreachable!(), - (H2State::ServerSettings, Position::Server) => { - let kawa = &mut context.streams.zero.back; - println!("{:?}", kawa.storage.data()); - let (size, status) = self.socket.socket_write(kawa.storage.data()); - println!(" size: {size}, status: {status:?}"); - let size = kawa.storage.available_data(); - kawa.storage.consume(size); - if kawa.storage.is_empty() { - self.readiness.interest.remove(Ready::WRITABLE); - self.readiness.interest.insert(Ready::READABLE); - self.state = H2State::Header; - self.expect = Some((0, 9)); - } - } - _ => unreachable!(), - } - } - - pub fn create_stream(&mut self, stream_id: StreamId, context: &mut Context) -> GlobalStreamId { - match context.create_stream(Ulid::generate(), self.settings.settings_initial_window_size) { - Ok(global_stream_id) => { - self.streams.insert(stream_id, global_stream_id); - global_stream_id - } - Err(e) => panic!("{e:?}"), - } - } - - fn handle(&mut self, frame: parser::Frame, context: &mut Context) { - println!("{frame:?}"); - match frame { - parser::Frame::Data(_) => todo!(), - parser::Frame::Headers(headers) => { - // if !headers.end_headers { - // self.state = H2State::Continuation - // } - let global_stream_id = self.streams.get(&headers.stream_id).unwrap(); - let kawa = context.streams.zero.front(self.position); - let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); - let stream = &mut context.streams.others[*global_stream_id - 1]; - let kawa = &mut stream.front; - pkawa::handle_header(kawa, buffer, &mut context.decoder); - stream.context.on_headers(kawa); - } - parser::Frame::Priority(priority) => (), - parser::Frame::RstStream(_) => todo!(), - parser::Frame::Settings(settings) => { - for setting in settings.settings { - match setting.identifier { - 1 => self.settings.settings_header_table_size = setting.value, - 2 => self.settings.settings_enable_push = setting.value == 1, - 3 => self.settings.settings_max_concurrent_streams = setting.value, - 4 => self.settings.settings_initial_window_size = setting.value, - 5 => self.settings.settings_max_frame_size = setting.value, - 6 => self.settings.settings_max_header_list_size = setting.value, - other => panic!("setting_id: {other}"), - } - } - println!("{:#?}", self.settings); - } - parser::Frame::PushPromise(_) => todo!(), - parser::Frame::Ping(_) => todo!(), - parser::Frame::GoAway(goaway) => panic!("{}", error_code_to_str(goaway.error_code)), - parser::Frame::WindowUpdate(update) => { - let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); - context.streams.get(global_stream_id).window += update.increment as i32; - } - parser::Frame::Continuation(_) => todo!(), - } - } -} - -impl ConnectionH1 { - fn readable(&mut self, context: &mut Context) { - println!("======= MUX H1 READABLE"); - let stream = &mut context.streams.get(self.stream); - let kawa = match self.position { - Position::Client => &mut stream.front, - Position::Server => &mut stream.back, - }; - let (size, status) = self.socket.socket_read(kawa.storage.space()); - println!(" size: {size}, status: {status:?}"); - if size > 0 { - kawa.storage.fill(size); - } else { - self.readiness.event.remove(Ready::READABLE); - } - match status { - SocketResult::Continue => {} - SocketResult::Closed => todo!(), - SocketResult::Error => todo!(), - SocketResult::WouldBlock => self.readiness.event.remove(Ready::READABLE), - } - kawa::h1::parse(kawa, &mut kawa::h1::NoCallbacks); - kawa::debug_kawa(kawa); - if kawa.is_terminated() { - self.readiness.interest.remove(Ready::READABLE); - } - } - fn writable(&mut self, context: &mut Context) { - println!("======= MUX H1 WRITABLE"); - let stream = &mut context.streams.get(self.stream); - let kawa = match self.position { - Position::Client => &mut stream.back, - Position::Server => &mut stream.front, - }; - kawa.prepare(&mut kawa::h1::BlockConverter); - let bufs = kawa.as_io_slice(); - if bufs.is_empty() { - self.readiness.interest.remove(Ready::WRITABLE); - return; - } - let (size, status) = self.socket.socket_write_vectored(&bufs); - println!(" size: {size}, status: {status:?}"); - if size > 0 { - kawa.consume(size); - // self.backend_readiness.interest.insert(Ready::READABLE); - } else { - self.readiness.event.remove(Ready::WRITABLE); - } - } -} From 12c50806dd0b274b15adbfc7b1c12862e1e0bfd7 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 22 Aug 2023 16:20:48 +0200 Subject: [PATCH 09/44] Define MuxResult for inter MuxSession control flow Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/h1.rs | 10 ++-- lib/src/protocol/mux/h2.rs | 102 +++++++++++++++++++++++------------- lib/src/protocol/mux/mod.rs | 45 ++++++++++++---- 3 files changed, 107 insertions(+), 50 deletions(-) diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index d6d05f53d..24bf2abb2 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -1,7 +1,7 @@ use sozu_command::ready::Ready; use crate::{ - protocol::mux::{Context, GlobalStreamId, Position}, + protocol::mux::{Context, GlobalStreamId, MuxResult, Position}, socket::{SocketHandler, SocketResult}, Readiness, }; @@ -15,7 +15,7 @@ pub struct ConnectionH1 { } impl ConnectionH1 { - pub fn readable(&mut self, context: &mut Context) { + pub fn readable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H1 READABLE"); let stream = &mut context.streams.get(self.stream); let kawa = match self.position { @@ -40,8 +40,9 @@ impl ConnectionH1 { if kawa.is_terminated() { self.readiness.interest.remove(Ready::READABLE); } + MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context) { + pub fn writable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H1 WRITABLE"); let stream = &mut context.streams.get(self.stream); let kawa = match self.position { @@ -52,7 +53,7 @@ impl ConnectionH1 { let bufs = kawa.as_io_slice(); if bufs.is_empty() { self.readiness.interest.remove(Ready::WRITABLE); - return; + return MuxResult::Continue; } let (size, status) = self.socket.socket_write_vectored(&bufs); println!(" size: {size}, status: {status:?}"); @@ -62,5 +63,6 @@ impl ConnectionH1 { } else { self.readiness.event.remove(Ready::WRITABLE); } + MuxResult::Continue } } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 4322cc336..6a107c016 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -6,8 +6,8 @@ use sozu_command::ready::Ready; use crate::{ protocol::mux::{ - parser::{self, error_code_to_str, FrameHeader}, - pkawa, serializer, Context, GlobalStreamId, Position, StreamId, + parser::{self, error_code_to_str, Frame, FrameHeader, FrameType}, + pkawa, serializer, Context, GlobalStreamId, MuxResult, Position, StreamId, }, socket::SocketHandler, Readiness, @@ -58,7 +58,7 @@ pub struct ConnectionH2 { } impl ConnectionH2 { - pub fn readable(&mut self, context: &mut Context) { + pub fn readable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H2 READABLE"); let kawa = if let Some((stream_id, amount)) = self.expect { let kawa = context.streams.get(stream_id).front(self.position); @@ -70,16 +70,16 @@ impl ConnectionH2 { self.expect = None; } else { self.expect = Some((stream_id, amount - size)); - return; + return MuxResult::Continue; } } else { self.readiness.event.remove(Ready::READABLE); - return; + return MuxResult::Continue; } kawa } else { self.readiness.event.remove(Ready::READABLE); - return; + return MuxResult::Continue; }; match (&self.state, &self.position) { (H2State::ClientPreface, Position::Client) => { @@ -94,9 +94,9 @@ impl ConnectionH2 { match parser::frame_header(i) { Ok(( _, - parser::FrameHeader { + FrameHeader { payload_len, - frame_type: parser::FrameType::Settings, + frame_type: FrameType::Settings, flags: 0, stream_id: 0, }, @@ -121,9 +121,9 @@ impl ConnectionH2 { self.state = H2State::ServerSettings; match serializer::gen_frame_header( kawa.storage.space(), - &parser::FrameHeader { + &FrameHeader { payload_len: 6 * 2, - frame_type: parser::FrameType::Settings, + frame_type: FrameType::Settings, flags: 0, stream_id: 0, }, @@ -136,9 +136,9 @@ impl ConnectionH2 { // .unwrap(); match serializer::gen_frame_header( kawa.storage.space(), - &parser::FrameHeader { + &FrameHeader { payload_len: 0, - frame_type: parser::FrameType::Settings, + frame_type: FrameType::Settings, flags: 1, stream_id: 0, }, @@ -166,7 +166,7 @@ impl ConnectionH2 { } else { self.create_stream(header.stream_id, context) }; - let stream_id = if header.frame_type == parser::FrameType::Data { + let stream_id = if header.frame_type == FrameType::Data { stream_id } else { 0 @@ -181,21 +181,23 @@ impl ConnectionH2 { (H2State::Frame(header), Position::Server) => { let i = kawa.storage.data(); println!(" data: {i:?}"); - match parser::frame_body(i, header, self.settings.settings_max_frame_size) { - Ok((_, frame)) => { - kawa.storage.clear(); - self.handle(frame, context); - } - Err(e) => panic!("{e:?}"), - } + let frame = + match parser::frame_body(i, header, self.settings.settings_max_frame_size) { + Ok((_, frame)) => frame, + Err(e) => panic!("{e:?}"), + }; + kawa.storage.clear(); + let state_result = self.handle(frame, context); self.state = H2State::Header; self.expect = Some((0, 9)); + return state_result; } _ => unreachable!(), } + MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context) { + pub fn writable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), @@ -214,6 +216,7 @@ impl ConnectionH2 { self.state = H2State::Header; self.expect = Some((0, 9)); } + MuxResult::Continue } _ => unreachable!(), } @@ -229,25 +232,43 @@ impl ConnectionH2 { } } - fn handle(&mut self, frame: parser::Frame, context: &mut Context) { + fn handle(&mut self, frame: Frame, context: &mut Context) -> MuxResult { println!("{frame:?}"); match frame { - parser::Frame::Data(_) => todo!(), - parser::Frame::Headers(headers) => { - // if !headers.end_headers { - // self.state = H2State::Continuation - // } - let global_stream_id = self.streams.get(&headers.stream_id).unwrap(); + Frame::Data(_) => todo!(), + Frame::Headers(headers) => { + if !headers.end_headers { + todo!(); + // self.state = H2State::Continuation + } + let global_stream_id = *self.streams.get(&headers.stream_id).unwrap(); let kawa = context.streams.zero.front(self.position); let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); - let stream = &mut context.streams.others[*global_stream_id - 1]; + let stream = &mut context.streams.others[global_stream_id - 1]; let kawa = &mut stream.front; pkawa::handle_header(kawa, buffer, &mut context.decoder); stream.context.on_headers(kawa); + return MuxResult::Connect(global_stream_id); } - parser::Frame::Priority(priority) => (), - parser::Frame::RstStream(_) => todo!(), - parser::Frame::Settings(settings) => { + Frame::PushPromise(push_promise) => match self.position { + Position::Client => { + todo!("if enabled forward the push") + } + Position::Server => { + println!("A client should not push promises"); + return MuxResult::CloseSession; + } + }, + Frame::Priority(priority) => (), + Frame::RstStream(rst_stream) => { + println!( + "RstStream({} -> {})", + rst_stream.error_code, + error_code_to_str(rst_stream.error_code) + ); + // context.streams.get(priority.stream_id).close() + } + Frame::Settings(settings) => { for setting in settings.settings { match setting.identifier { 1 => self.settings.settings_header_table_size = setting.value, @@ -261,14 +282,21 @@ impl ConnectionH2 { } println!("{:#?}", self.settings); } - parser::Frame::PushPromise(_) => todo!(), - parser::Frame::Ping(_) => todo!(), - parser::Frame::GoAway(goaway) => panic!("{}", error_code_to_str(goaway.error_code)), - parser::Frame::WindowUpdate(update) => { + Frame::Ping(_) => todo!(), + Frame::GoAway(goaway) => { + println!( + "GoAway({} -> {})", + goaway.error_code, + error_code_to_str(goaway.error_code) + ); + return MuxResult::CloseSession; + } + Frame::WindowUpdate(update) => { let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); context.streams.get(global_stream_id).window += update.increment as i32; } - parser::Frame::Continuation(_) => todo!(), + Frame::Continuation(_) => todo!(), } + MuxResult::Continue } } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 6b4f63fe5..906445459 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -28,7 +28,7 @@ use crate::{ AcceptError, L7Proxy, ProxySession, Readiness, SessionMetrics, SessionResult, StateResult, }; -use self::h2::{H2State, H2Settings}; +use self::h2::{H2Settings, H2State}; /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer type GenericHttpStream = kawa::Kawa; @@ -41,6 +41,13 @@ pub enum Position { Server, } +pub enum MuxResult { + Continue, + CloseSession, + Close(GlobalStreamId), + Connect(GlobalStreamId), +} + pub enum Connection { H1(ConnectionH1), H2(ConnectionH2), @@ -111,13 +118,13 @@ impl Connection { Connection::H2(c) => &mut c.readiness, } } - fn readable(&mut self, context: &mut Context) { + fn readable(&mut self, context: &mut Context) -> MuxResult { match self { Connection::H1(c) => c.readable(context), Connection::H2(c) => c.readable(context), } } - fn writable(&mut self, context: &mut Context) { + fn writable(&mut self, context: &mut Context) -> MuxResult { match self { Connection::H1(c) => c.writable(context), Connection::H2(c) => c.writable(context), @@ -128,8 +135,8 @@ impl Connection { pub struct Stream { // pub request_id: Ulid, pub window: i32, - pub front: GenericHttpStream, - pub back: GenericHttpStream, + front: GenericHttpStream, + back: GenericHttpStream, pub context: HttpContext, } @@ -276,24 +283,44 @@ impl SessionState for Mux { let mut dirty = false; if self.frontend.readiness().filter_interest().is_readable() { - self.frontend.readable(context); + match self.frontend.readable(context) { + MuxResult::Continue => (), + MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Close(_) => todo!(), + MuxResult::Connect(_) => todo!(), + } dirty = true; } for (_, backend) in self.backends.iter_mut() { if backend.readiness().filter_interest().is_writable() { - backend.writable(context); + match backend.writable(context) { + MuxResult::Continue => (), + MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Close(_) => todo!(), + MuxResult::Connect(_) => unreachable!(), + } dirty = true; } if backend.readiness().filter_interest().is_readable() { - backend.readable(context); + match backend.readable(context) { + MuxResult::Continue => (), + MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Close(_) => todo!(), + MuxResult::Connect(_) => unreachable!(), + } dirty = true; } } if self.frontend.readiness().filter_interest().is_writable() { - self.frontend.writable(context); + match self.frontend.writable(context) { + MuxResult::Continue => (), + MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Close(_) => todo!(), + MuxResult::Connect(_) => unreachable!(), + } dirty = true; } From 0a3459b831b2365d3a71be910ece16ee5f1967a0 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 23 Aug 2023 18:56:40 +0200 Subject: [PATCH 10/44] Front to Back: - Add routing capabilities to MuxSession - Implement Data frame handling - Fix handle_headers - Don't store pseudo headers key in kawa - Rename front and back Kawa fields to rbuffer, wbuffer A MuxSession can now process an incoming H2 request, connect to the corresponding backend, translate it in H1, send it and receive the H1 response. We still lack the capability to connect to H2 backends and forward responses. Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 7 +- lib/src/protocol/kawa_h1/editor.rs | 27 +++- lib/src/protocol/kawa_h1/mod.rs | 38 +---- lib/src/protocol/mux/h1.rs | 17 +- lib/src/protocol/mux/h2.rs | 70 +++++--- lib/src/protocol/mux/mod.rs | 247 +++++++++++++++++++++++++---- lib/src/protocol/mux/pkawa.rs | 60 +++++-- 7 files changed, 360 insertions(+), 106 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index ba5f59f48..b70ae49b4 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -298,9 +298,11 @@ impl HttpsSession { let mux = Mux { frontend_token: self.frontend_token, frontend, - backends: HashMap::new(), context: mux::Context::new(self.pool.clone(), handshake.request_id, 1 << 16).ok()?, - listener: self.listener.clone(), + router: mux::Router { + listener: self.listener.clone(), + backends: HashMap::new(), + }, public_address: self.public_address, peer_address: self.peer_address, sticky_name: self.sticky_name.clone(), @@ -552,7 +554,6 @@ impl L7ListenerHandler for HttpsListener { 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(), diff --git a/lib/src/protocol/kawa_h1/editor.rs b/lib/src/protocol/kawa_h1/editor.rs index 26a8d3658..476a97c6e 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -8,7 +8,7 @@ use rusty_ulid::Ulid; use crate::{ pool::Checkout, protocol::http::{parser::compare_no_case, GenericHttpStream, Method}, - Protocol, + Protocol, RetrieveClusterError, }; use sozu_command_lib::logging::LogContext; @@ -365,4 +365,29 @@ impl HttpContext { backend_id: self.backend_id.as_deref(), } } + + // -> host, path, method + pub fn extract_route(&self) -> Result<(&str, &str, &Method), RetrieveClusterError> { + let given_method = self.method.as_ref().ok_or(RetrieveClusterError::NoMethod)?; + let given_authority = self + .authority + .as_deref() + .ok_or(RetrieveClusterError::NoHost)?; + let given_path = self.path.as_deref().ok_or(RetrieveClusterError::NoPath)?; + + Ok((given_authority, given_path, given_method)) + } + + pub fn get_route(&self) -> String { + if let Some(method) = &self.method { + if let Some(authority) = &self.authority { + if let Some(path) = &self.path { + return format!("{method} {authority}{path}"); + } + return format!("{method} {authority}"); + } + return format!("{method}"); + } + String::new() + } } diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 18ff48db7..e1b2ec712 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -1027,7 +1027,7 @@ impl Http Http host, path, method - pub fn extract_route(&self) -> Result<(&str, &str, &Method), RetrieveClusterError> { - let given_method = self - .context - .method - .as_ref() - .ok_or(RetrieveClusterError::NoMethod)?; - let given_authority = self - .context - .authority - .as_deref() - .ok_or(RetrieveClusterError::NoHost)?; - let given_path = self - .context - .path - .as_deref() - .ok_or(RetrieveClusterError::NoPath)?; - - Ok((given_authority, given_path, given_method)) - } - - pub fn get_route(&self) -> String { - if let Some(method) = &self.context.method { - if let Some(authority) = &self.context.authority { - if let Some(path) = &self.context.path { - return format!("{method} {authority}{path}"); - } - return format!("{method} {authority}"); - } - return format!("{method}"); - } - String::new() - } - fn cluster_id_from_request( &mut self, proxy: Rc>, ) -> Result { - let (host, uri, method) = match self.extract_route() { + let (host, uri, method) = match self.context.extract_route() { Ok(tuple) => tuple, Err(cluster_error) => { self.set_answer(DefaultAnswer::Answer400 { diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 24bf2abb2..f78143c69 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -18,10 +18,7 @@ impl ConnectionH1 { pub fn readable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H1 READABLE"); let stream = &mut context.streams.get(self.stream); - let kawa = match self.position { - Position::Client => &mut stream.front, - Position::Server => &mut stream.back, - }; + let kawa = stream.rbuffer(self.position); let (size, status) = self.socket.socket_read(kawa.storage.space()); println!(" size: {size}, status: {status:?}"); if size > 0 { @@ -37,6 +34,9 @@ impl ConnectionH1 { } kawa::h1::parse(kawa, &mut kawa::h1::NoCallbacks); kawa::debug_kawa(kawa); + if kawa.is_error() { + return MuxResult::Close(self.stream); + } if kawa.is_terminated() { self.readiness.interest.remove(Ready::READABLE); } @@ -45,11 +45,9 @@ impl ConnectionH1 { pub fn writable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H1 WRITABLE"); let stream = &mut context.streams.get(self.stream); - let kawa = match self.position { - Position::Client => &mut stream.back, - Position::Server => &mut stream.front, - }; + let kawa = stream.wbuffer(self.position); kawa.prepare(&mut kawa::h1::BlockConverter); + kawa::debug_kawa(kawa); let bufs = kawa.as_io_slice(); if bufs.is_empty() { self.readiness.interest.remove(Ready::WRITABLE); @@ -63,6 +61,9 @@ impl ConnectionH1 { } else { self.readiness.event.remove(Ready::WRITABLE); } + if kawa.is_terminated() && kawa.is_completed() { + self.readiness.interest.insert(Ready::READABLE); + } MuxResult::Continue } } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 6a107c016..abfa87908 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, str::from_utf8_unchecked}; use kawa::h1::ParserCallbacks; use rusty_ulid::Ulid; @@ -60,23 +60,25 @@ pub struct ConnectionH2 { impl ConnectionH2 { pub fn readable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H2 READABLE"); - let kawa = if let Some((stream_id, amount)) = self.expect { - let kawa = context.streams.get(stream_id).front(self.position); - let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); - println!("{:?}({stream_id}, {amount}) {size} {status:?}", self.state); - if size > 0 { - kawa.storage.fill(size); - if size == amount { - self.expect = None; + let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect { + let kawa = context.streams.get(stream_id).rbuffer(self.position); + if amount > 0 { + let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); + println!("{:?}({stream_id}, {amount}) {size} {status:?}", self.state); + if size > 0 { + kawa.storage.fill(size); + if size == amount { + self.expect = None; + } else { + self.expect = Some((stream_id, amount - size)); + return MuxResult::Continue; + } } else { - self.expect = Some((stream_id, amount - size)); + self.readiness.event.remove(Ready::READABLE); return MuxResult::Continue; } - } else { - self.readiness.event.remove(Ready::READABLE); - return MuxResult::Continue; } - kawa + (stream_id, kawa) } else { self.readiness.event.remove(Ready::READABLE); return MuxResult::Continue; @@ -186,7 +188,9 @@ impl ConnectionH2 { Ok((_, frame)) => frame, Err(e) => panic!("{e:?}"), }; - kawa.storage.clear(); + if stream_id == 0 { + kawa.storage.clear(); + } let state_result = self.handle(frame, context); self.state = H2State::Header; self.expect = Some((0, 9)); @@ -235,19 +239,47 @@ impl ConnectionH2 { fn handle(&mut self, frame: Frame, context: &mut Context) -> MuxResult { println!("{frame:?}"); match frame { - Frame::Data(_) => todo!(), + Frame::Data(data) => { + let mut slice = data.payload; + let global_stream_id = *self.streams.get(&data.stream_id).unwrap(); + let stream = &mut context.streams.others[global_stream_id - 1]; + let kawa = stream.rbuffer(self.position); + slice.start += kawa.storage.head as u32; + kawa.storage.head += slice.len(); + let buffer = kawa.storage.buffer(); + let payload = slice.data(buffer); + println!("{:?}", unsafe { from_utf8_unchecked(payload) }); + kawa.push_block(kawa::Block::Chunk(kawa::Chunk { + data: kawa::Store::Slice(slice), + })); + if data.end_stream { + kawa.push_block(kawa::Block::Flags(kawa::Flags { + end_body: true, + end_chunk: false, + end_header: false, + end_stream: true, + })); + kawa.parsing_phase = kawa::ParsingPhase::Terminated; + } + } Frame::Headers(headers) => { if !headers.end_headers { todo!(); // self.state = H2State::Continuation } let global_stream_id = *self.streams.get(&headers.stream_id).unwrap(); - let kawa = context.streams.zero.front(self.position); + let kawa = context.streams.zero.rbuffer(self.position); let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); let stream = &mut context.streams.others[global_stream_id - 1]; let kawa = &mut stream.front; - pkawa::handle_header(kawa, buffer, &mut context.decoder); - stream.context.on_headers(kawa); + pkawa::handle_header( + kawa, + buffer, + headers.end_stream, + &mut context.decoder, + &mut stream.context, + ); + kawa::debug_kawa(kawa); return MuxResult::Connect(global_stream_id); } Frame::PushPromise(push_promise) => match self.position { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 906445459..de18bb709 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -6,7 +6,7 @@ use std::{ rc::{Rc, Weak}, }; -use mio::{net::TcpStream, Token}; +use mio::{net::TcpStream, Interest, Token}; use rusty_ulid::Ulid; use sozu_command::ready::Ready; @@ -17,6 +17,7 @@ mod pkawa; mod serializer; use crate::{ + backends::{Backend, BackendError}, https::HttpsListener, pool::{Checkout, Pool}, protocol::{ @@ -24,8 +25,10 @@ use crate::{ mux::{h1::ConnectionH1, h2::ConnectionH2}, SessionState, }, + router::Route, socket::{FrontRustls, SocketHandler}, - AcceptError, L7Proxy, ProxySession, Readiness, SessionMetrics, SessionResult, StateResult, + AcceptError, BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, + RetrieveClusterError, SessionMetrics, SessionResult, StateResult, }; use self::h2::{H2Settings, H2State}; @@ -65,7 +68,7 @@ impl Connection { stream: 0, }) } - pub fn new_h1_client(front_stream: Front) -> Connection { + pub fn new_h1_client(front_stream: Front, stream_id: GlobalStreamId) -> Connection { Connection::H1(ConnectionH1 { socket: front_stream, position: Position::Client, @@ -73,7 +76,7 @@ impl Connection { interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - stream: 0, + stream: stream_id, }) } @@ -118,6 +121,12 @@ impl Connection { Connection::H2(c) => &mut c.readiness, } } + pub fn socket(&self) -> &TcpStream { + match self { + Connection::H1(c) => &c.socket.socket_ref(), + Connection::H2(c) => &c.socket.socket_ref(), + } + } fn readable(&mut self, context: &mut Context) -> MuxResult { match self { Connection::H1(c) => c.readable(context), @@ -141,13 +150,13 @@ pub struct Stream { } impl Stream { - pub fn front(&mut self, position: Position) -> &mut GenericHttpStream { + pub fn rbuffer(&mut self, position: Position) -> &mut GenericHttpStream { match position { Position::Client => &mut self.back, Position::Server => &mut self.front, } } - pub fn back(&mut self, position: Position) -> &mut GenericHttpStream { + pub fn wbuffer(&mut self, position: Position) -> &mut GenericHttpStream { match position { Position::Client => &mut self.front, Position::Server => &mut self.back, @@ -197,6 +206,8 @@ impl Context { front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), context: HttpContext { + backend_id: None, + cluster_id: None, keep_alive_backend: false, keep_alive_frontend: false, sticky_session_found: None, @@ -244,11 +255,175 @@ impl Context { } } +pub struct Router { + pub listener: Rc>, + pub backends: HashMap>, +} + +impl Router { + fn connect( + &mut self, + stream_id: GlobalStreamId, + context: &mut Context, + session: Rc>, + proxy: Rc>, + metrics: &mut SessionMetrics, + ) -> Result<(), BackendConnectionError> { + let context = &mut context.streams.others[stream_id - 1].context; + // we should get if the route is H2 or not here + // for now we assume it's H1 + let cluster_id = self + .cluster_id_from_request(context, proxy.clone()) + .map_err(BackendConnectionError::RetrieveClusterError)?; + println!("{cluster_id}!!!!!"); + + let frontend_should_stick = proxy + .borrow() + .clusters() + .get(&cluster_id) + .map(|cluster| cluster.sticky_session) + .unwrap_or(false); + + let mut socket = self.backend_from_request( + &cluster_id, + frontend_should_stick, + context, + proxy.clone(), + metrics, + )?; + + if let Err(e) = socket.set_nodelay(true) { + error!( + "error setting nodelay on back socket({:?}): {:?}", + socket, e + ); + } + // self.backend_readiness.interest = Ready::WRITABLE | Ready::HUP | Ready::ERROR; + // self.backend_connection_status = BackendConnectionStatus::Connecting(Instant::now()); + + let backend_token = proxy.borrow().add_session(session); + + if let Err(e) = proxy.borrow().register_socket( + &mut socket, + backend_token, + Interest::READABLE | Interest::WRITABLE, + ) { + error!("error registering back socket({:?}): {:?}", socket, e); + } + + self.backends + .insert(backend_token, Connection::new_h1_client(socket, stream_id)); + Ok(()) + } + + fn cluster_id_from_request( + &mut self, + context: &mut HttpContext, + proxy: Rc>, + ) -> Result { + let (host, uri, method) = match context.extract_route() { + Ok(tuple) => tuple, + Err(cluster_error) => { + panic!("{}", cluster_error); + // self.set_answer(DefaultAnswerStatus::Answer400, None); + // return Err(cluster_error); + } + }; + + let route_result = self + .listener + .borrow() + .frontend_from_request(host, uri, method); + + let route = match route_result { + Ok(route) => route, + Err(frontend_error) => { + panic!("{}", frontend_error); + // self.set_answer(DefaultAnswerStatus::Answer404, None); + // return Err(RetrieveClusterError::RetrieveFrontend(frontend_error)); + } + }; + + let cluster_id = match route { + Route::Cluster { id, .. } => id, + Route::Deny => { + panic!("Route::Deny"); + // self.set_answer(DefaultAnswerStatus::Answer401, None); + // return Err(RetrieveClusterError::UnauthorizedRoute); + } + }; + + Ok(cluster_id) + } + + pub fn backend_from_request( + &mut self, + cluster_id: &str, + frontend_should_stick: bool, + context: &mut HttpContext, + proxy: Rc>, + metrics: &mut SessionMetrics, + ) -> Result { + let (backend, conn) = self + .get_backend_for_sticky_session( + cluster_id, + frontend_should_stick, + context.sticky_session_found.as_deref(), + proxy, + ) + .map_err(|backend_error| { + panic!("{backend_error}") + // self.set_answer(DefaultAnswerStatus::Answer503, None); + // BackendConnectionError::Backend(backend_error) + })?; + + if frontend_should_stick { + // update sticky name in case it changed I guess? + context.sticky_name = self.listener.borrow().get_sticky_name().to_string(); + + context.sticky_session = Some( + backend + .borrow() + .sticky_id + .clone() + .unwrap_or_else(|| backend.borrow().backend_id.clone()), + ); + } + + // metrics.backend_id = Some(backend.borrow().backend_id.clone()); + // metrics.backend_start(); + // self.set_backend_id(backend.borrow().backend_id.clone()); + // self.backend = Some(backend); + + Ok(conn) + } + + fn get_backend_for_sticky_session( + &self, + cluster_id: &str, + frontend_should_stick: bool, + sticky_session: Option<&str>, + proxy: Rc>, + ) -> Result<(Rc>, TcpStream), BackendError> { + match (frontend_should_stick, sticky_session) { + (true, Some(sticky_session)) => proxy + .borrow() + .backends() + .borrow_mut() + .backend_from_sticky_session(cluster_id, sticky_session), + _ => proxy + .borrow() + .backends() + .borrow_mut() + .backend_from_cluster_id(cluster_id), + } + } +} + pub struct Mux { pub frontend_token: Token, pub frontend: Connection, - pub backends: HashMap>, - pub listener: Rc>, + pub router: Router, pub public_address: SocketAddr, pub peer_address: Option, pub sticky_name: String, @@ -257,19 +432,16 @@ pub struct Mux { impl Mux { pub fn front_socket(&self) -> &TcpStream { - match &self.frontend { - Connection::H1(c) => &c.socket.stream, - Connection::H2(c) => &c.socket.stream, - } + self.frontend.socket() } } impl SessionState for Mux { fn ready( &mut self, - _session: Rc>, - _proxy: Rc>, - _metrics: &mut SessionMetrics, + session: Rc>, + proxy: Rc>, + metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; let max_loop_iterations = 100000; @@ -287,19 +459,32 @@ impl SessionState for Mux { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), - MuxResult::Connect(_) => todo!(), + MuxResult::Connect(stream_id) => { + match self.router.connect( + stream_id, + context, + session.clone(), + proxy.clone(), + metrics, + ) { + Ok(_) => (), + Err(error) => { + println!("{error}"); + } + } + } } dirty = true; } - for (_, backend) in self.backends.iter_mut() { + for (_, backend) in self.router.backends.iter_mut() { if backend.readiness().filter_interest().is_writable() { match backend.writable(context) { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), MuxResult::Connect(_) => unreachable!(), - } + } dirty = true; } @@ -309,7 +494,7 @@ impl SessionState for Mux { MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), MuxResult::Connect(_) => unreachable!(), - } + } dirty = true; } } @@ -324,10 +509,11 @@ impl SessionState for Mux { dirty = true; } - for backend in self.backends.values() { + for backend in self.router.backends.values() { if backend.readiness().filter_interest().is_hup() || backend.readiness().filter_interest().is_error() { + println!("{:?} {:?}", backend.readiness(), backend.socket()); return SessionResult::Close; } } @@ -350,7 +536,7 @@ impl SessionState for Mux { fn update_readiness(&mut self, token: Token, events: sozu_command::ready::Ready) { if token == self.frontend_token { self.frontend.readiness_mut().event |= events; - } else if let Some(c) = self.backends.get_mut(&token) { + } else if let Some(c) = self.router.backends.get_mut(&token) { c.readiness_mut().event |= events; } } @@ -384,15 +570,16 @@ impl SessionState for Mux { let (size, status) = s.socket_read(&mut b); println!("{size} {status:?} {:?}", &b[..size]); for stream in &mut self.context.streams.others { - let kawa = &mut stream.front; - kawa::debug_kawa(kawa); - kawa.prepare(&mut kawa::h1::BlockConverter); - let out = kawa.as_io_slice(); - let mut writer = std::io::BufWriter::new(Vec::new()); - let amount = writer.write_vectored(&out).unwrap(); - println!("amount: {amount}\n{}", unsafe { - std::str::from_utf8_unchecked(writer.buffer()) - }); + for kawa in [&mut stream.front, &mut stream.back] { + kawa::debug_kawa(kawa); + kawa.prepare(&mut kawa::h1::BlockConverter); + let out = kawa.as_io_slice(); + let mut writer = std::io::BufWriter::new(Vec::new()); + let amount = writer.write_vectored(&out).unwrap(); + println!("amount: {amount}\n{}", unsafe { + std::str::from_utf8_unchecked(writer.buffer()) + }); + } } } } diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index 85d836641..874b55b0f 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -1,10 +1,20 @@ use std::{io::Write, str::from_utf8_unchecked}; -use crate::protocol::http::parser::compare_no_case; +use kawa::h1::ParserCallbacks; + +use crate::{pool::Checkout, protocol::http::parser::compare_no_case}; use super::GenericHttpStream; -pub fn handle_header(kawa: &mut GenericHttpStream, input: &[u8], decoder: &mut hpack::Decoder) { +pub fn handle_header( + kawa: &mut GenericHttpStream, + input: &[u8], + end_stream: bool, + decoder: &mut hpack::Decoder, + callbacks: &mut C, +) where + C: ParserCallbacks, +{ println!("{input:?}"); kawa.push_block(kawa::Block::StatusLine); kawa.detached.status_line = match kawa.kind { @@ -16,16 +26,11 @@ pub fn handle_header(kawa: &mut GenericHttpStream, input: &[u8], decoder: &mut h decoder .decode_with_cb(input, |k, v| { let start = kawa.storage.end as u32; - kawa.storage.write(&k).unwrap(); kawa.storage.write(&v).unwrap(); let len_key = k.len() as u32; let len_val = v.len() as u32; - let key = kawa::Store::Slice(kawa::repr::Slice { - start, - len: len_key, - }); let val = kawa::Store::Slice(kawa::repr::Slice { - start: start + len_key, + start, len: len_val, }); @@ -38,6 +43,16 @@ pub fn handle_header(kawa: &mut GenericHttpStream, input: &[u8], decoder: &mut h } else if compare_no_case(&k, b":scheme") { scheme = val; } else { + if compare_no_case(&k, b"content-length") { + let length = + unsafe { from_utf8_unchecked(&v).parse::().unwrap() }; + kawa.body_size = kawa::BodySize::Length(length); + } + kawa.storage.write(&k).unwrap(); + let key = kawa::Store::Slice(kawa::repr::Slice { + start: start + len_val, + len: len_key, + }); kawa.push_block(kawa::Block::Header(kawa::Pair { key, val })); } }) @@ -103,5 +118,32 @@ pub fn handle_header(kawa: &mut GenericHttpStream, input: &[u8], decoder: &mut h // everything has been parsed kawa.storage.head = kawa.storage.end; - kawa.parsing_phase = kawa::ParsingPhase::Chunks { first: true }; + + callbacks.on_headers(kawa); + + kawa.push_block(kawa::Block::Flags(kawa::Flags { + end_body: false, + end_chunk: false, + end_header: true, + end_stream: false, + })); + + if end_stream { + kawa.push_block(kawa::Block::Flags(kawa::Flags { + end_body: true, + end_chunk: false, + end_header: false, + end_stream: true, + })); + kawa.body_size = kawa::BodySize::Length(0); + } + kawa.parsing_phase = match kawa.body_size { + kawa::BodySize::Chunked => kawa::ParsingPhase::Chunks { first: true }, + kawa::BodySize::Length(0) => kawa::ParsingPhase::Terminated, + kawa::BodySize::Length(_) => kawa::ParsingPhase::Body, + kawa::BodySize::Empty => { + println!("HTTP is just the worst..."); + kawa::ParsingPhase::Body + }, + }; } From e51421fd1792b5901a2c20390e6cb8ce2823109e Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 24 Aug 2023 14:17:55 +0200 Subject: [PATCH 11/44] Maintenance: - Move H2 stream 0 out of Mux context into ConnectionH2 (so each H2 connection can have its own unshared stream 0) - Change pool errors propagation - Add ack flag on Settings frame - Add split methods on Stream (for borrowing reason) Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 18 ++-- lib/src/protocol/mux/h1.rs | 4 +- lib/src/protocol/mux/h2.rs | 114 +++++++++++++++-------- lib/src/protocol/mux/mod.rs | 164 +++++++++++++++++---------------- lib/src/protocol/mux/parser.rs | 37 ++++---- lib/src/protocol/mux/pkawa.rs | 21 ++--- 6 files changed, 203 insertions(+), 155 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index b70ae49b4..002af9278 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -290,15 +290,21 @@ impl HttpsSession { gauge_add!("protocol.tls.handshake", -1); use crate::protocol::mux; + let mut context = mux::Context::new(self.pool.clone()); let mut frontend = match alpn { - AlpnProtocol::Http11 => mux::Connection::new_h1_server(front_stream), - AlpnProtocol::H2 => mux::Connection::new_h2_server(front_stream), + AlpnProtocol::Http11 => { + context.create_stream(handshake.request_id, 1 << 16)?; + mux::Connection::new_h1_server(front_stream) + } + AlpnProtocol::H2 => mux::Connection::new_h2_server(front_stream, self.pool.clone())?, }; frontend.readiness_mut().event = handshake.frontend_readiness.event; - let mux = Mux { + + gauge_add!("protocol.https", 1); + Some(HttpsStateMachine::Mux(Mux { frontend_token: self.frontend_token, frontend, - context: mux::Context::new(self.pool.clone(), handshake.request_id, 1 << 16).ok()?, + context, router: mux::Router { listener: self.listener.clone(), backends: HashMap::new(), @@ -306,9 +312,7 @@ impl HttpsSession { public_address: self.public_address, peer_address: self.peer_address, sticky_name: self.sticky_name.clone(), - }; - gauge_add!("protocol.https", 1); - return Some(HttpsStateMachine::Mux(mux)); + })) } fn upgrade_http(&self, http: Http) -> Option { diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index f78143c69..283d7562d 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -17,7 +17,7 @@ pub struct ConnectionH1 { impl ConnectionH1 { pub fn readable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H1 READABLE"); - let stream = &mut context.streams.get(self.stream); + let stream = &mut context.streams[self.stream]; let kawa = stream.rbuffer(self.position); let (size, status) = self.socket.socket_read(kawa.storage.space()); println!(" size: {size}, status: {status:?}"); @@ -44,7 +44,7 @@ impl ConnectionH1 { } pub fn writable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H1 WRITABLE"); - let stream = &mut context.streams.get(self.stream); + let stream = &mut context.streams[self.stream]; let kawa = stream.wbuffer(self.position); kawa.prepare(&mut kawa::h1::BlockConverter); kawa::debug_kawa(kawa); diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index abfa87908..78f469399 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, str::from_utf8_unchecked}; -use kawa::h1::ParserCallbacks; use rusty_ulid::Ulid; use sozu_command::ready::Ready; @@ -13,6 +12,8 @@ use crate::{ Readiness, }; +use super::GenericHttpStream; + #[derive(Debug)] pub enum H2State { ClientPreface, @@ -47,24 +48,37 @@ impl Default for H2Settings { } pub struct ConnectionH2 { - // pub decoder: hpack::Decoder<'static>, - pub expect: Option<(GlobalStreamId, usize)>, + pub expect: Option<(H2StreamId, usize)>, pub position: Position, pub readiness: Readiness, pub settings: H2Settings, pub socket: Front, pub state: H2State, pub streams: HashMap, + pub zero: GenericHttpStream, + pub window: u32, +} + +#[derive(Debug, Clone, Copy)] +pub enum H2StreamId { + Zero, + Global(GlobalStreamId), } impl ConnectionH2 { pub fn readable(&mut self, context: &mut Context) -> MuxResult { println!("======= MUX H2 READABLE"); let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect { - let kawa = context.streams.get(stream_id).rbuffer(self.position); + let kawa = match stream_id { + H2StreamId::Zero => &mut self.zero, + H2StreamId::Global(stream_id) => context.streams[stream_id].rbuffer(self.position), + }; if amount > 0 { let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); - println!("{:?}({stream_id}, {amount}) {size} {status:?}", self.state); + println!( + "{:?}({stream_id:?}, {amount}) {size} {status:?}", + self.state + ); if size > 0 { kawa.storage.fill(size); if size == amount { @@ -74,9 +88,12 @@ impl ConnectionH2 { return MuxResult::Continue; } } else { + // We wanted to read (amoun > 0) but there is nothing yet (size == 0) self.readiness.event.remove(Ready::READABLE); return MuxResult::Continue; } + } else { + self.expect = None; } (stream_id, kawa) } else { @@ -105,26 +122,34 @@ impl ConnectionH2 { )) => { kawa.storage.clear(); self.state = H2State::ClientSettings; - self.expect = Some((0, payload_len as usize)); + self.expect = Some((H2StreamId::Zero, payload_len as usize)); } _ => todo!(), }; } (H2State::ClientSettings, Position::Server) => { let i = kawa.storage.data(); - match parser::settings_frame(i, i.len()) { + match parser::settings_frame( + i, + &FrameHeader { + payload_len: i.len() as u32, + frame_type: FrameType::Settings, + flags: 0, + stream_id: 0, + }, + ) { Ok((_, settings)) => { kawa.storage.clear(); self.handle(settings, context); } Err(e) => panic!("{e:?}"), } - let kawa = &mut context.streams.zero.back; self.state = H2State::ServerSettings; + let kawa = &mut self.zero; match serializer::gen_frame_header( kawa.storage.space(), &FrameHeader { - payload_len: 6 * 2, + payload_len: 0, frame_type: FrameType::Settings, flags: 0, stream_id: 0, @@ -162,19 +187,23 @@ impl ConnectionH2 { Ok((_, header)) => { println!("{header:?}"); kawa.storage.clear(); - let stream_id = if let Some(stream_id) = self.streams.get(&header.stream_id) - { - *stream_id + let stream_id = if header.stream_id == 0 { + H2StreamId::Zero } else { - self.create_stream(header.stream_id, context) + let stream_id = + if let Some(stream_id) = self.streams.get(&header.stream_id) { + *stream_id + } else { + self.create_stream(header.stream_id, context) + }; + if header.frame_type == FrameType::Data { + H2StreamId::Global(stream_id) + } else { + H2StreamId::Zero + } }; - let stream_id = if header.frame_type == FrameType::Data { - stream_id - } else { - 0 - }; - println!("{} {} {:#?}", header.stream_id, stream_id, self.streams); - self.expect = Some((stream_id as usize, header.payload_len as usize)); + println!("{} {stream_id:?} {:#?}", header.stream_id, self.streams); + self.expect = Some((stream_id, header.payload_len as usize)); self.state = H2State::Frame(header); } Err(e) => panic!("{e:?}"), @@ -188,12 +217,12 @@ impl ConnectionH2 { Ok((_, frame)) => frame, Err(e) => panic!("{e:?}"), }; - if stream_id == 0 { + if let H2StreamId::Zero = stream_id { kawa.storage.clear(); } let state_result = self.handle(frame, context); self.state = H2State::Header; - self.expect = Some((0, 9)); + self.expect = Some((H2StreamId::Zero, 9)); return state_result; } _ => unreachable!(), @@ -208,7 +237,7 @@ impl ConnectionH2 { (H2State::ClientPreface, Position::Server) => unreachable!(), (H2State::ServerSettings, Position::Client) => unreachable!(), (H2State::ServerSettings, Position::Server) => { - let kawa = &mut context.streams.zero.back; + let kawa = &mut self.zero; println!("{:?}", kawa.storage.data()); let (size, status) = self.socket.socket_write(kawa.storage.data()); println!(" size: {size}, status: {status:?}"); @@ -218,7 +247,7 @@ impl ConnectionH2 { self.readiness.interest.remove(Ready::WRITABLE); self.readiness.interest.insert(Ready::READABLE); self.state = H2State::Header; - self.expect = Some((0, 9)); + self.expect = Some((H2StreamId::Zero, 9)); } MuxResult::Continue } @@ -227,13 +256,11 @@ impl ConnectionH2 { } pub fn create_stream(&mut self, stream_id: StreamId, context: &mut Context) -> GlobalStreamId { - match context.create_stream(Ulid::generate(), self.settings.settings_initial_window_size) { - Ok(global_stream_id) => { - self.streams.insert(stream_id, global_stream_id); - global_stream_id - } - Err(e) => panic!("{e:?}"), - } + let global_stream_id = context + .create_stream(Ulid::generate(), self.settings.settings_initial_window_size) + .unwrap(); + self.streams.insert(stream_id, global_stream_id); + global_stream_id } fn handle(&mut self, frame: Frame, context: &mut Context) -> MuxResult { @@ -242,7 +269,7 @@ impl ConnectionH2 { Frame::Data(data) => { let mut slice = data.payload; let global_stream_id = *self.streams.get(&data.stream_id).unwrap(); - let stream = &mut context.streams.others[global_stream_id - 1]; + let stream = &mut context.streams[global_stream_id]; let kawa = stream.rbuffer(self.position); slice.start += kawa.storage.head as u32; kawa.storage.head += slice.len(); @@ -268,18 +295,18 @@ impl ConnectionH2 { // self.state = H2State::Continuation } let global_stream_id = *self.streams.get(&headers.stream_id).unwrap(); - let kawa = context.streams.zero.rbuffer(self.position); + let kawa = &mut self.zero; let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); - let stream = &mut context.streams.others[global_stream_id - 1]; - let kawa = &mut stream.front; + let stream = &mut context.streams[global_stream_id]; + let parts = &mut stream.split(self.position); pkawa::handle_header( - kawa, + parts.rbuffer, buffer, headers.end_stream, &mut context.decoder, - &mut stream.context, + parts.context, ); - kawa::debug_kawa(kawa); + kawa::debug_kawa(parts.rbuffer); return MuxResult::Connect(global_stream_id); } Frame::PushPromise(push_promise) => match self.position { @@ -301,6 +328,9 @@ impl ConnectionH2 { // context.streams.get(priority.stream_id).close() } Frame::Settings(settings) => { + if settings.ack { + return MuxResult::Continue; + } for setting in settings.settings { match setting.identifier { 1 => self.settings.settings_header_table_size = setting.value, @@ -324,8 +354,12 @@ impl ConnectionH2 { return MuxResult::CloseSession; } Frame::WindowUpdate(update) => { - let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); - context.streams.get(global_stream_id).window += update.increment as i32; + if update.stream_id == 0 { + self.window += update.increment; + } else { + let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); + context.streams[global_stream_id].window += update.increment as i32; + } } Frame::Continuation(_) => todo!(), } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index de18bb709..35d11fac6 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -27,11 +27,11 @@ use crate::{ }, router::Route, socket::{FrontRustls, SocketHandler}, - AcceptError, BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, + BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, RetrieveClusterError, SessionMetrics, SessionResult, StateResult, }; -use self::h2::{H2Settings, H2State}; +use self::h2::{H2Settings, H2State, H2StreamId}; /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer type GenericHttpStream = kawa::Kawa; @@ -80,33 +80,49 @@ impl Connection { }) } - pub fn new_h2_server(front_stream: Front) -> Connection { - Connection::H2(ConnectionH2 { + pub fn new_h2_server( + front_stream: Front, + pool: Weak>, + ) -> Option> { + let buffer = pool + .upgrade() + .and_then(|pool| pool.borrow_mut().checkout())?; + Some(Connection::H2(ConnectionH2 { socket: front_stream, position: Position::Server, readiness: Readiness { interest: Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - streams: HashMap::from([(0, 0)]), + streams: HashMap::new(), state: H2State::ClientPreface, - expect: Some((0, 24 + 9)), + expect: Some((H2StreamId::Zero, 24 + 9)), settings: H2Settings::default(), - }) + zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), + window: 1 << 16, + })) } - pub fn new_h2_client(front_stream: Front) -> Connection { - Connection::H2(ConnectionH2 { + pub fn new_h2_client( + front_stream: Front, + pool: Weak>, + ) -> Option> { + let buffer = pool + .upgrade() + .and_then(|pool| pool.borrow_mut().checkout())?; + Some(Connection::H2(ConnectionH2 { socket: front_stream, position: Position::Client, readiness: Readiness { interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - streams: HashMap::from([(0, 0)]), + streams: HashMap::new(), state: H2State::ClientPreface, expect: None, settings: H2Settings::default(), - }) + zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), + window: 1 << 16, + })) } pub fn readiness(&self) -> &Readiness { @@ -123,8 +139,8 @@ impl Connection { } pub fn socket(&self) -> &TcpStream { match self { - Connection::H1(c) => &c.socket.socket_ref(), - Connection::H2(c) => &c.socket.socket_ref(), + Connection::H1(c) => c.socket.socket_ref(), + Connection::H2(c) => c.socket.socket_ref(), } } fn readable(&mut self, context: &mut Context) -> MuxResult { @@ -149,59 +165,27 @@ pub struct Stream { pub context: HttpContext, } -impl Stream { - pub fn rbuffer(&mut self, position: Position) -> &mut GenericHttpStream { - match position { - Position::Client => &mut self.back, - Position::Server => &mut self.front, - } - } - pub fn wbuffer(&mut self, position: Position) -> &mut GenericHttpStream { - match position { - Position::Client => &mut self.front, - Position::Server => &mut self.back, - } - } -} - -pub struct Streams { - zero: Stream, - others: Vec, -} - -impl Streams { - pub fn get(&mut self, stream_id: GlobalStreamId) -> &mut Stream { - if stream_id == 0 { - &mut self.zero - } else { - &mut self.others[stream_id - 1] - } - } +/// This struct allows to mutably borrow the read and write buffers (dependant on the position) +/// as well as the context of a Stream at the same time +pub struct StreamParts<'a> { + pub rbuffer: &'a mut GenericHttpStream, + pub wbuffer: &'a mut GenericHttpStream, + pub context: &'a mut HttpContext, } -pub struct Context { - pub streams: Streams, - pub pool: Weak>, - pub decoder: hpack::Decoder<'static>, -} - -impl Context { - pub fn new_stream( - pool: Weak>, - request_id: Ulid, - window: u32, - ) -> Result { +impl Stream { + pub fn new(pool: Weak>, request_id: Ulid, window: u32) -> Option { let (front_buffer, back_buffer) = match pool.upgrade() { Some(pool) => { let mut pool = pool.borrow_mut(); match (pool.checkout(), pool.checkout()) { (Some(front_buffer), Some(back_buffer)) => (front_buffer, back_buffer), - _ => return Err(AcceptError::BufferCapacityReached), + _ => return None, } } - None => return Err(AcceptError::BufferCapacityReached), + None => return None, }; - Ok(Stream { + Some(Self { window: window as i32, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), @@ -227,31 +211,53 @@ impl Context { }, }) } + pub fn split(&mut self, position: Position) -> StreamParts<'_> { + match position { + Position::Client => StreamParts { + rbuffer: &mut self.back, + wbuffer: &mut self.front, + context: &mut self.context, + }, + Position::Server => StreamParts { + rbuffer: &mut self.front, + wbuffer: &mut self.back, + context: &mut self.context, + }, + } + } + pub fn rbuffer(&mut self, position: Position) -> &mut GenericHttpStream { + match position { + Position::Client => &mut self.back, + Position::Server => &mut self.front, + } + } + pub fn wbuffer(&mut self, position: Position) -> &mut GenericHttpStream { + match position { + Position::Client => &mut self.front, + Position::Server => &mut self.back, + } + } +} - pub fn create_stream( - &mut self, - request_id: Ulid, - window: u32, - ) -> Result { +pub struct Context { + pub streams: Vec, + pub pool: Weak>, + pub decoder: hpack::Decoder<'static>, +} + +impl Context { + pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option { self.streams - .others - .push(Self::new_stream(self.pool.clone(), request_id, window)?); - Ok(self.streams.others.len()) + .push(Stream::new(self.pool.clone(), request_id, window)?); + Some(self.streams.len() - 1) } - pub fn new( - pool: Weak>, - request_id: Ulid, - window: u32, - ) -> Result { - Ok(Self { - streams: Streams { - zero: Context::new_stream(pool.clone(), request_id, window)?, - others: Vec::new(), - }, + pub fn new(pool: Weak>) -> Context { + Self { + streams: Vec::new(), pool, decoder: hpack::Decoder::new(), - }) + } } } @@ -269,7 +275,7 @@ impl Router { proxy: Rc>, metrics: &mut SessionMetrics, ) -> Result<(), BackendConnectionError> { - let context = &mut context.streams.others[stream_id - 1].context; + let context = &mut context.streams[stream_id].context; // we should get if the route is H2 or not here // for now we assume it's H1 let cluster_id = self @@ -319,7 +325,7 @@ impl Router { fn cluster_id_from_request( &mut self, context: &mut HttpContext, - proxy: Rc>, + _proxy: Rc>, ) -> Result { let (host, uri, method) = match context.extract_route() { Ok(tuple) => tuple, @@ -362,7 +368,7 @@ impl Router { frontend_should_stick: bool, context: &mut HttpContext, proxy: Rc>, - metrics: &mut SessionMetrics, + _metrics: &mut SessionMetrics, ) -> Result { let (backend, conn) = self .get_backend_for_sticky_session( @@ -569,7 +575,7 @@ impl SessionState for Mux { let mut b = [0; 1024]; let (size, status) = s.socket_read(&mut b); println!("{size} {status:?} {:?}", &b[..size]); - for stream in &mut self.context.streams.others { + for stream in &mut self.context.streams { for kawa in [&mut stream.front, &mut stream.back] { kawa::debug_kawa(kawa); kawa.prepare(&mut kawa::h1::BlockConverter); diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index 24134ec58..dfb5d0338 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -245,8 +245,8 @@ pub fn frame_body<'a>( } let f = match header.frame_type { - FrameType::Data => data_frame(i, &header)?, - FrameType::Headers => headers_frame(i, &header)?, + FrameType::Data => data_frame(i, header)?, + FrameType::Headers => headers_frame(i, header)?, FrameType::Priority => { if header.payload_len != 5 { return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); @@ -257,30 +257,28 @@ pub fn frame_body<'a>( if header.payload_len != 4 { return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } - rst_stream_frame(i, &header)? - } - FrameType::PushPromise => push_promise_frame(i, &header)?, - FrameType::Continuation => { - unimplemented!(); + rst_stream_frame(i, header)? } + FrameType::PushPromise => push_promise_frame(i, header)?, + FrameType::Continuation => continuation_frame(i, header)?, FrameType::Settings => { if header.payload_len % 6 != 0 { return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } - settings_frame(i, header.payload_len as usize)? + settings_frame(i, header)? } FrameType::Ping => { if header.payload_len != 8 { return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } - ping_frame(i, &header)? + ping_frame(i, header)? } - FrameType::GoAway => goaway_frame(i, &header)?, + FrameType::GoAway => goaway_frame(i, header)?, FrameType::WindowUpdate => { if header.payload_len != 4 { return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); } - window_update_frame(i, &header)? + window_update_frame(i, header)? } }; @@ -341,7 +339,7 @@ pub struct StreamDependency { pub stream_id: u32, } -fn stream_dependency<'a>(i: &'a [u8]) -> IResult<&'a [u8], StreamDependency, Error<'a>> { +fn stream_dependency(i: &[u8]) -> IResult<&[u8], StreamDependency, Error<'_>> { let (i, stream) = map(be_u32, |i| StreamDependency { exclusive: i & 0x8000 != 0, stream_id: i & 0x7FFFFFFF, @@ -443,6 +441,7 @@ pub fn rst_stream_frame<'a>( #[derive(Clone, Debug, PartialEq)] pub struct Settings { pub settings: Vec, + pub ack: bool, } #[derive(Clone, Debug, PartialEq)] @@ -453,16 +452,22 @@ pub struct Setting { pub fn settings_frame<'a>( input: &'a [u8], - payload_len: usize, + header: &FrameHeader, ) -> IResult<&'a [u8], Frame, Error<'a>> { - let (i, data) = take(payload_len)(input)?; + let (i, data) = take(header.payload_len)(input)?; let (_, settings) = many0(map( complete(tuple((be_u16, be_u32))), |(identifier, value)| Setting { identifier, value }, ))(data)?; - Ok((i, Frame::Settings(Settings { settings }))) + Ok(( + i, + Frame::Settings(Settings { + settings, + ack: header.flags & 0x1 != 0, + }), + )) } #[derive(Clone, Debug)] @@ -511,7 +516,7 @@ pub struct Ping { pub fn ping_frame<'a>( input: &'a [u8], - header: &FrameHeader, + _header: &FrameHeader, ) -> IResult<&'a [u8], Frame, Error<'a>> { let (i, data) = take(8usize)(input)?; diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index 874b55b0f..c616d4ca1 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -15,7 +15,6 @@ pub fn handle_header( ) where C: ParserCallbacks, { - println!("{input:?}"); kawa.push_block(kawa::Block::StatusLine); kawa.detached.status_line = match kawa.kind { kawa::Kind::Request => { @@ -26,7 +25,7 @@ pub fn handle_header( decoder .decode_with_cb(input, |k, v| { let start = kawa.storage.end as u32; - kawa.storage.write(&v).unwrap(); + kawa.storage.write_all(&v).unwrap(); let len_key = k.len() as u32; let len_val = v.len() as u32; let val = kawa::Store::Slice(kawa::repr::Slice { @@ -48,7 +47,7 @@ pub fn handle_header( unsafe { from_utf8_unchecked(&v).parse::().unwrap() }; kawa.body_size = kawa::BodySize::Length(length); } - kawa.storage.write(&k).unwrap(); + kawa.storage.write_all(&k).unwrap(); let key = kawa::Store::Slice(kawa::repr::Slice { start: start + len_val, len: len_key, @@ -81,16 +80,11 @@ pub fn handle_header( decoder .decode_with_cb(input, |k, v| { let start = kawa.storage.end as u32; - kawa.storage.write(&k).unwrap(); - kawa.storage.write(&v).unwrap(); + kawa.storage.write_all(&v).unwrap(); let len_key = k.len() as u32; let len_val = v.len() as u32; - let key = kawa::Store::Slice(kawa::repr::Slice { - start, - len: len_key, - }); let val = kawa::Store::Slice(kawa::repr::Slice { - start: start + len_key, + start, len: len_val, }); @@ -103,6 +97,11 @@ pub fn handle_header( .unwrap() } } else { + kawa.storage.write_all(&k).unwrap(); + let key = kawa::Store::Slice(kawa::repr::Slice { + start: start + len_val, + len: len_key, + }); kawa.push_block(kawa::Block::Header(kawa::Pair { key, val })); } }) @@ -144,6 +143,6 @@ pub fn handle_header( kawa::BodySize::Empty => { println!("HTTP is just the worst..."); kawa::ParsingPhase::Body - }, + } }; } From 96a6d3a7527898fb08fea9392b7c3fd63b6a7c45 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 25 Aug 2023 00:27:57 +0200 Subject: [PATCH 12/44] PoC: pass proxied endpoints to proxy functions through trait Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/h1.rs | 12 +++++++-- lib/src/protocol/mux/h2.rs | 12 ++++++--- lib/src/protocol/mux/mod.rs | 51 +++++++++++++++++++++++++++++-------- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 283d7562d..b002701e6 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -6,6 +6,8 @@ use crate::{ Readiness, }; +use super::UpdateReadiness; + pub struct ConnectionH1 { pub position: Position, pub readiness: Readiness, @@ -15,7 +17,10 @@ pub struct ConnectionH1 { } impl ConnectionH1 { - pub fn readable(&mut self, context: &mut Context) -> MuxResult { + pub fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + where + E: UpdateReadiness, + { println!("======= MUX H1 READABLE"); let stream = &mut context.streams[self.stream]; let kawa = stream.rbuffer(self.position); @@ -42,7 +47,10 @@ impl ConnectionH1 { } MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context) -> MuxResult { + pub fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + where + E: UpdateReadiness, + { println!("======= MUX H1 WRITABLE"); let stream = &mut context.streams[self.stream]; let kawa = stream.wbuffer(self.position); diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 78f469399..951e4e2d8 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -12,7 +12,7 @@ use crate::{ Readiness, }; -use super::GenericHttpStream; +use super::{GenericHttpStream, UpdateReadiness}; #[derive(Debug)] pub enum H2State { @@ -66,7 +66,10 @@ pub enum H2StreamId { } impl ConnectionH2 { - pub fn readable(&mut self, context: &mut Context) -> MuxResult { + pub fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + where + E: UpdateReadiness, + { println!("======= MUX H2 READABLE"); let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect { let kawa = match stream_id { @@ -230,7 +233,10 @@ impl ConnectionH2 { MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context) -> MuxResult { + pub fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + where + E: UpdateReadiness, + { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 35d11fac6..14d4f9f99 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -56,6 +56,11 @@ pub enum Connection { H2(ConnectionH2), } +pub trait UpdateReadiness { + fn readiness(&self, token: Token) -> &Readiness; + fn readiness_mut(&mut self, token: Token) -> &mut Readiness; +} + impl Connection { pub fn new_h1_server(front_stream: Front) -> Connection { Connection::H1(ConnectionH1 { @@ -143,20 +148,46 @@ impl Connection { Connection::H2(c) => c.socket.socket_ref(), } } - fn readable(&mut self, context: &mut Context) -> MuxResult { + fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + where + E: UpdateReadiness, + { match self { - Connection::H1(c) => c.readable(context), - Connection::H2(c) => c.readable(context), + Connection::H1(c) => c.readable(context, endpoint), + Connection::H2(c) => c.readable(context, endpoint), } } - fn writable(&mut self, context: &mut Context) -> MuxResult { + fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + where + E: UpdateReadiness, + { match self { - Connection::H1(c) => c.writable(context), - Connection::H2(c) => c.writable(context), + Connection::H1(c) => c.writable(context, endpoint), + Connection::H2(c) => c.writable(context, endpoint), } } } +struct EndpointServer<'a>(&'a mut Connection); +struct EndpointClient<'a>(&'a mut Router); + +impl<'a> UpdateReadiness for EndpointServer<'a> { + fn readiness(&self, _token: Token) -> &Readiness { + self.0.readiness() + } + fn readiness_mut(&mut self, _token: Token) -> &mut Readiness { + self.0.readiness_mut() + } +} +impl<'a> UpdateReadiness for EndpointClient<'a> { + fn readiness(&self, token: Token) -> &Readiness { + self.0.backends.get(&token).unwrap().readiness() + } + fn readiness_mut(&mut self, token: Token) -> &mut Readiness { + self.0.backends.get_mut(&token).unwrap().readiness_mut() + } +} + pub struct Stream { // pub request_id: Ulid, pub window: i32, @@ -461,7 +492,7 @@ impl SessionState for Mux { let mut dirty = false; if self.frontend.readiness().filter_interest().is_readable() { - match self.frontend.readable(context) { + match self.frontend.readable(context, EndpointClient(&mut self.router)) { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), @@ -485,7 +516,7 @@ impl SessionState for Mux { for (_, backend) in self.router.backends.iter_mut() { if backend.readiness().filter_interest().is_writable() { - match backend.writable(context) { + match backend.writable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), @@ -495,7 +526,7 @@ impl SessionState for Mux { } if backend.readiness().filter_interest().is_readable() { - match backend.readable(context) { + match backend.readable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), @@ -506,7 +537,7 @@ impl SessionState for Mux { } if self.frontend.readiness().filter_interest().is_writable() { - match self.frontend.writable(context) { + match self.frontend.writable(context, EndpointClient(&mut self.router)) { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), From 8b98da5a5f10c8dfdb6320ae5d3db17ce3a39989 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 25 Aug 2023 11:50:27 +0200 Subject: [PATCH 13/44] Insert writable readiness in opposit endpoint upon receiving proxyable response Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/h1.rs | 8 ++++++- lib/src/protocol/mux/h2.rs | 35 +++++++++++++++++++++++----- lib/src/protocol/mux/mod.rs | 46 ++++++++++++++++++++++++++++++++++--- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index b002701e6..9fd3e4912 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -17,7 +17,7 @@ pub struct ConnectionH1 { } impl ConnectionH1 { - pub fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + pub fn readable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where E: UpdateReadiness, { @@ -45,6 +45,12 @@ impl ConnectionH1 { if kawa.is_terminated() { self.readiness.interest.remove(Ready::READABLE); } + if kawa.is_main_phase() { + endpoint + .readiness_mut(stream.token.unwrap()) + .interest + .insert(Ready::WRITABLE) + } MuxResult::Continue } pub fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 951e4e2d8..fd4768ad9 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -143,7 +143,7 @@ impl ConnectionH2 { ) { Ok((_, settings)) => { kawa.storage.clear(); - self.handle(settings, context); + self.handle(settings, context, endpoint); } Err(e) => panic!("{e:?}"), } @@ -223,7 +223,7 @@ impl ConnectionH2 { if let H2StreamId::Zero = stream_id { kawa.storage.clear(); } - let state_result = self.handle(frame, context); + let state_result = self.handle(frame, context, endpoint); self.state = H2State::Header; self.expect = Some((H2StreamId::Zero, 9)); return state_result; @@ -239,8 +239,10 @@ impl ConnectionH2 { { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Client) => todo!("Send PRI + client Settings"), + (H2State::ClientPreface, Position::Client) => todo!("Send PRI"), (H2State::ClientPreface, Position::Server) => unreachable!(), + (H2State::ClientSettings, Position::Client) => todo!("Send Settings"), + (H2State::ClientSettings, Position::Server) => unreachable!(), (H2State::ServerSettings, Position::Client) => unreachable!(), (H2State::ServerSettings, Position::Server) => { let kawa = &mut self.zero; @@ -257,7 +259,19 @@ impl ConnectionH2 { } MuxResult::Continue } - _ => unreachable!(), + (H2State::Error, _) => unreachable!(), + // Proxying states (Header/Frame) + (_, _) => { + for stream_id in self.streams.values() { + let kawa = context.streams[*stream_id].wbuffer(self.position); + if kawa.is_main_phase() { + kawa.prepare(&mut kawa::h2::BlockConverter); + kawa::debug_kawa(kawa); + } + } + self.readiness.interest.remove(Ready::WRITABLE); + MuxResult::Continue + } } } @@ -269,7 +283,10 @@ impl ConnectionH2 { global_stream_id } - fn handle(&mut self, frame: Frame, context: &mut Context) -> MuxResult { + fn handle(&mut self, frame: Frame, context: &mut Context, mut endpoint: E) -> MuxResult + where + E: UpdateReadiness, + { println!("{frame:?}"); match frame { Frame::Data(data) => { @@ -313,7 +330,13 @@ impl ConnectionH2 { parts.context, ); kawa::debug_kawa(parts.rbuffer); - return MuxResult::Connect(global_stream_id); + match self.position { + Position::Client => endpoint + .readiness_mut(stream.token.unwrap()) + .interest + .insert(Ready::WRITABLE), + Position::Server => return MuxResult::Connect(global_stream_id), + }; } Frame::PushPromise(push_promise) => match self.position { Position::Client => { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 14d4f9f99..e7ae222ad 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -171,6 +171,8 @@ impl Connection { struct EndpointServer<'a>(&'a mut Connection); struct EndpointClient<'a>(&'a mut Router); +// note: EndpointServer are used by client Connection, they do not know the frontend Token +// they will use the Stream's Token which is their backend token impl<'a> UpdateReadiness for EndpointServer<'a> { fn readiness(&self, _token: Token) -> &Readiness { self.0.readiness() @@ -188,9 +190,38 @@ impl<'a> UpdateReadiness for EndpointClient<'a> { } } +// enum Stream { +// Idle { +// window: i32, +// }, +// Open { +// window: i32, +// token: Token, +// front: GenericHttpStream, +// back: GenericHttpStream, +// context: HttpContext, +// }, +// Reserved { +// window: i32, +// token: Token, +// position: Position, +// buffer: GenericHttpStream, +// context: HttpContext, +// }, +// HalfClosed { +// window: i32, +// token: Token, +// position: Position, +// buffer: GenericHttpStream, +// context: HttpContext, +// }, +// Closed, +// } + pub struct Stream { // pub request_id: Ulid, pub window: i32, + pub token: Option, front: GenericHttpStream, back: GenericHttpStream, pub context: HttpContext, @@ -218,6 +249,7 @@ impl Stream { }; Some(Self { window: window as i32, + token: None, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), context: HttpContext { @@ -306,7 +338,8 @@ impl Router { proxy: Rc>, metrics: &mut SessionMetrics, ) -> Result<(), BackendConnectionError> { - let context = &mut context.streams[stream_id].context; + let stream = &mut context.streams[stream_id]; + let context = &mut stream.context; // we should get if the route is H2 or not here // for now we assume it's H1 let cluster_id = self @@ -348,6 +381,7 @@ impl Router { error!("error registering back socket({:?}): {:?}", socket, e); } + stream.token.insert(backend_token); self.backends .insert(backend_token, Connection::new_h1_client(socket, stream_id)); Ok(()) @@ -492,7 +526,10 @@ impl SessionState for Mux { let mut dirty = false; if self.frontend.readiness().filter_interest().is_readable() { - match self.frontend.readable(context, EndpointClient(&mut self.router)) { + match self + .frontend + .readable(context, EndpointClient(&mut self.router)) + { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), @@ -537,7 +574,10 @@ impl SessionState for Mux { } if self.frontend.readiness().filter_interest().is_writable() { - match self.frontend.writable(context, EndpointClient(&mut self.router)) { + match self + .frontend + .writable(context, EndpointClient(&mut self.router)) + { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), From 618fdb2d7c437efbc4426ce69dd84dab8762cb02 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 25 Aug 2023 18:00:02 +0200 Subject: [PATCH 14/44] First H2<->H1 round trip! - Move hpack Decoder back to individual ConnectionH2 - Add basic H2 kawa BlockConverter - Add basic ConnectionH2 writable implementation H2 headers are hard to work with. We should write our own implementation of hpack to address the following issues: - Use of transmute to change an hpack Decoder in Encoder - No support for uppercased header names - Duplication of header tables (one per connection) - Aggressive decoding/reencoding Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 159 ++++++++++++++++++++++++++++++ lib/src/protocol/mux/h2.rs | 35 ++++++- lib/src/protocol/mux/mod.rs | 9 +- lib/src/protocol/mux/parser.rs | 19 +--- lib/src/protocol/mux/pkawa.rs | 24 ++--- 5 files changed, 212 insertions(+), 34 deletions(-) create mode 100644 lib/src/protocol/mux/converter.rs diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs new file mode 100644 index 000000000..aea781709 --- /dev/null +++ b/lib/src/protocol/mux/converter.rs @@ -0,0 +1,159 @@ +use kawa::{AsBuffer, Block, BlockConverter, Chunk, Flags, Kawa, Pair, StatusLine, Store}; + +use super::{ + parser::{FrameHeader, FrameType}, + serializer::gen_frame_header, + StreamId, +}; + +pub struct H2BlockConverter<'a> { + pub stream_id: StreamId, + pub encoder: &'a mut hpack::Encoder<'static>, + pub out: Vec, +} + +impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { + fn call(&mut self, block: Block, kawa: &mut Kawa) { + let buffer = kawa.storage.mut_buffer(); + match block { + Block::StatusLine => match kawa.detached.status_line.pop() { + StatusLine::Request { + method, + authority, + path, + .. + } => { + self.encoder + .encode_header_into((b":method", method.data(buffer)), &mut self.out) + .unwrap(); + self.encoder + .encode_header_into((b":authority", authority.data(buffer)), &mut self.out) + .unwrap(); + self.encoder + .encode_header_into((b":path", path.data(buffer)), &mut self.out) + .unwrap(); + self.encoder + .encode_header_into((b":scheme", b"https"), &mut self.out) + .unwrap(); + } + StatusLine::Response { status, .. } => { + self.encoder + .encode_header_into((b":status", status.data(buffer)), &mut self.out) + .unwrap(); + } + StatusLine::Unknown => unreachable!(), + }, + Block::Cookies => { + if kawa.detached.jar.is_empty() { + return; + } + for cookie in kawa + .detached + .jar + .drain(..) + .filter(|cookie| !cookie.is_elided()) + { + let cookie = [cookie.key.data(buffer), b"=", cookie.val.data(buffer)].concat(); + self.encoder + .encode_header_into((b"cookie", &cookie), &mut self.out) + .unwrap(); + } + } + Block::Header(Pair { + key: Store::Empty, .. + }) => { + // elided header + } + Block::Header(Pair { key, val }) => { + { + // let key = key.data(kawa.storage.buffer()); + // let val = val.data(kawa.storage.buffer()); + // if compare_no_case(key, b"connection") + // || compare_no_case(key, b"host") + // || compare_no_case(key, b"http2-settings") + // || compare_no_case(key, b"keep-alive") + // || compare_no_case(key, b"proxy-connection") + // || compare_no_case(key, b"te") && !compare_no_case(val, b"trailers") + // || compare_no_case(key, b"trailer") + // || compare_no_case(key, b"transfer-encoding") + // || compare_no_case(key, b"upgrade") + // { + // return; + // } + } + self.encoder + .encode_header_into((key.data(buffer), val.data(buffer)), &mut self.out) + .unwrap(); + } + Block::ChunkHeader(_) => { + // this converter doesn't align H1 chunks on H2 data frames + } + Block::Chunk(Chunk { data }) => { + let mut header = [0; 9]; + let payload_len = match &data { + Store::Empty => 0, + Store::Detached(s) | Store::Slice(s) => s.len, + Store::Static(s) => s.len() as u32, + Store::Alloc(a, i) => a.len() as u32 - i, + }; + gen_frame_header( + &mut header, + &FrameHeader { + payload_len, + frame_type: FrameType::Data, + flags: 0, + stream_id: self.stream_id, + }, + ) + .unwrap(); + kawa.push_out(Store::from_slice(&header)); + kawa.push_out(data); + kawa.push_delimiter() + } + Block::Flags(Flags { + end_header, + end_stream, + .. + }) => { + if end_header { + let mut payload = Vec::new(); + std::mem::swap(&mut self.out, &mut payload); + let mut header = [0; 9]; + let flags = if end_stream { 1 } else { 0 } | if end_header { 4 } else { 0 }; + gen_frame_header( + &mut header, + &FrameHeader { + payload_len: payload.len() as u32, + frame_type: FrameType::Headers, + flags, + stream_id: self.stream_id, + }, + ) + .unwrap(); + kawa.push_out(Store::from_slice(&header)); + kawa.push_out(Store::Alloc(payload.into_boxed_slice(), 0)); + } + if end_stream { + let mut header = [0; 9]; + gen_frame_header( + &mut header, + &FrameHeader { + payload_len: 0, + frame_type: FrameType::Data, + flags: 1, + stream_id: self.stream_id, + }, + ) + .unwrap(); + kawa.push_out(Store::from_slice(&header)); + } + if end_header || end_stream { + kawa.push_delimiter() + } + } + } + } + fn finalize(&mut self, _kawa: &mut Kawa) { + assert!(self.out.is_empty()); + } +} diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index fd4768ad9..7b33f3eea 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -5,6 +5,7 @@ use sozu_command::ready::Ready; use crate::{ protocol::mux::{ + converter, parser::{self, error_code_to_str, Frame, FrameHeader, FrameType}, pkawa, serializer, Context, GlobalStreamId, MuxResult, Position, StreamId, }, @@ -48,6 +49,7 @@ impl Default for H2Settings { } pub struct ConnectionH2 { + pub decoder: hpack::Decoder<'static>, pub expect: Option<(H2StreamId, usize)>, pub position: Position, pub readiness: Readiness, @@ -262,14 +264,37 @@ impl ConnectionH2 { (H2State::Error, _) => unreachable!(), // Proxying states (Header/Frame) (_, _) => { - for stream_id in self.streams.values() { - let kawa = context.streams[*stream_id].wbuffer(self.position); + let mut converter = converter::H2BlockConverter { + stream_id: 0, + encoder: unsafe { std::mem::transmute(&mut self.decoder) }, + out: Vec::new(), + }; + let mut want_write = false; + for (stream_id, global_stream_id) in &self.streams { + let kawa = context.streams[*global_stream_id].wbuffer(self.position); if kawa.is_main_phase() { - kawa.prepare(&mut kawa::h2::BlockConverter); + converter.stream_id = *stream_id; + kawa.prepare(&mut converter); kawa::debug_kawa(kawa); + while !kawa.out.is_empty() { + let bufs = kawa.as_io_slice(); + let (size, status) = self.socket.socket_write_vectored(&bufs); + println!(" size: {size}, status: {status:?}"); + if size > 0 { + kawa.consume(size); + } else { + want_write = true; + break; + } + } + if kawa.is_terminated() && kawa.is_completed() { + // close stream + } } } - self.readiness.interest.remove(Ready::WRITABLE); + if !want_write { + self.readiness.interest.remove(Ready::WRITABLE); + } MuxResult::Continue } } @@ -326,7 +351,7 @@ impl ConnectionH2 { parts.rbuffer, buffer, headers.end_stream, - &mut context.decoder, + &mut self.decoder, parts.context, ); kawa::debug_kawa(parts.rbuffer); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index e7ae222ad..3c1dcea1a 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -10,6 +10,7 @@ use mio::{net::TcpStream, Interest, Token}; use rusty_ulid::Ulid; use sozu_command::ready::Ready; +mod converter; mod h1; mod h2; mod parser; @@ -105,6 +106,7 @@ impl Connection { settings: H2Settings::default(), zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, + decoder: hpack::Decoder::new(), })) } pub fn new_h2_client( @@ -127,6 +129,7 @@ impl Connection { settings: H2Settings::default(), zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, + decoder: hpack::Decoder::new(), })) } @@ -305,7 +308,6 @@ impl Stream { pub struct Context { pub streams: Vec, pub pool: Weak>, - pub decoder: hpack::Decoder<'static>, } impl Context { @@ -319,7 +321,6 @@ impl Context { Self { streams: Vec::new(), pool, - decoder: hpack::Decoder::new(), } } } @@ -381,7 +382,7 @@ impl Router { error!("error registering back socket({:?}): {:?}", socket, e); } - stream.token.insert(backend_token); + stream.token = Some(backend_token); self.backends .insert(backend_token, Connection::new_h1_client(socket, stream_id)); Ok(()) @@ -610,7 +611,7 @@ impl SessionState for Mux { SessionResult::Continue } - fn update_readiness(&mut self, token: Token, events: sozu_command::ready::Ready) { + fn update_readiness(&mut self, token: Token, events: Ready) { if token == self.frontend_token { self.frontend.readiness_mut().event |= events; } else if let Some(c) = self.router.backends.get_mut(&token) { diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index dfb5d0338..eaca86a8f 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -360,18 +360,12 @@ pub fn headers_frame<'a>( (i, None) }; - let (i, stream_dependency) = if header.flags & 0x20 != 0 { - let (i, dep) = stream_dependency(i)?; - (i, Some(dep)) - } else { - (i, None) - }; - - let (i, weight) = if header.flags & 0x20 != 0 { + let (i, stream_dependency, weight) = if header.flags & 0x20 != 0 { + let (i, stream_dependency) = stream_dependency(i)?; let (i, weight) = be_u8(i)?; - (i, Some(weight)) + (i, Some(stream_dependency), Some(weight)) } else { - (i, None) + (i, None, None) }; if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { @@ -521,10 +515,7 @@ pub fn ping_frame<'a>( let (i, data) = take(8usize)(input)?; let mut p = Ping { payload: [0; 8] }; - - for i in 0..8 { - p.payload[i] = data[i]; - } + p.payload[..8].copy_from_slice(&data[..8]); Ok((i, Frame::Ping(p))) } diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index c616d4ca1..d44412abe 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -56,20 +56,22 @@ pub fn handle_header( } }) .unwrap(); - let buffer = kawa.storage.data(); - let uri = unsafe { - format!( - "{}://{}{}", - from_utf8_unchecked(scheme.data(buffer)), - from_utf8_unchecked(authority.data(buffer)), - from_utf8_unchecked(path.data(buffer)) - ) - }; - println!("Reconstructed URI: {uri}"); + // uri is only used by H1 statusline, in most cases it only consists of the path + // a better algorithm should be used though + // let buffer = kawa.storage.data(); + // let uri = unsafe { + // format!( + // "{}://{}{}", + // from_utf8_unchecked(scheme.data(buffer)), + // from_utf8_unchecked(authority.data(buffer)), + // from_utf8_unchecked(path.data(buffer)) + // ) + // }; + // println!("Reconstructed URI: {uri}"); kawa::StatusLine::Request { version: kawa::Version::V20, method, - uri: kawa::Store::from_string(uri), + uri: path.clone(), //kawa::Store::from_string(uri), authority, path, } From fc849bd6e4b9f6f171001e7b485df0de2f802736 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 28 Aug 2023 09:19:00 +0200 Subject: [PATCH 15/44] Fix H1 round trip: - H1 Connection servers send connect event upon receiving the full request headers - Add Readable interest to H1 and H2 clients, this avoids blocking on H1 close delimited requests - Header names are lowercased before sent to H2 hpack compression to avoid capitalised H1 headers breaking H2 connections Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 5 +++- lib/src/protocol/mux/h1.rs | 38 +++++++++++++++++-------------- lib/src/protocol/mux/h2.rs | 13 ++++------- lib/src/protocol/mux/mod.rs | 4 ++-- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index aea781709..2b021a547 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -82,7 +82,10 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { // } } self.encoder - .encode_header_into((key.data(buffer), val.data(buffer)), &mut self.out) + .encode_header_into( + (&key.data(buffer).to_ascii_lowercase(), val.data(buffer)), + &mut self.out, + ) .unwrap(); } Block::ChunkHeader(_) => { diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 9fd3e4912..f70f4357a 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -1,13 +1,11 @@ use sozu_command::ready::Ready; use crate::{ - protocol::mux::{Context, GlobalStreamId, MuxResult, Position}, - socket::{SocketHandler, SocketResult}, + protocol::mux::{Context, GlobalStreamId, MuxResult, Position, UpdateReadiness}, + socket::SocketHandler, Readiness, }; -use super::UpdateReadiness; - pub struct ConnectionH1 { pub position: Position, pub readiness: Readiness, @@ -23,21 +21,24 @@ impl ConnectionH1 { { println!("======= MUX H1 READABLE"); let stream = &mut context.streams[self.stream]; - let kawa = stream.rbuffer(self.position); + let parts = stream.split(self.position); + let kawa = parts.rbuffer; let (size, status) = self.socket.socket_read(kawa.storage.space()); println!(" size: {size}, status: {status:?}"); if size > 0 { kawa.storage.fill(size); } else { self.readiness.event.remove(Ready::READABLE); + return MuxResult::Continue; } - match status { - SocketResult::Continue => {} - SocketResult::Closed => todo!(), - SocketResult::Error => todo!(), - SocketResult::WouldBlock => self.readiness.event.remove(Ready::READABLE), - } - kawa::h1::parse(kawa, &mut kawa::h1::NoCallbacks); + // match status { + // SocketResult::Continue => {} + // SocketResult::Closed => todo!(), + // SocketResult::Error => todo!(), + // SocketResult::WouldBlock => self.readiness.event.remove(Ready::READABLE), + // } + let was_initial = kawa.is_initial(); + kawa::h1::parse(kawa, parts.context); kawa::debug_kawa(kawa); if kawa.is_error() { return MuxResult::Close(self.stream); @@ -45,11 +46,14 @@ impl ConnectionH1 { if kawa.is_terminated() { self.readiness.interest.remove(Ready::READABLE); } - if kawa.is_main_phase() { - endpoint - .readiness_mut(stream.token.unwrap()) - .interest - .insert(Ready::WRITABLE) + if was_initial && kawa.is_main_phase() { + match self.position { + Position::Client => endpoint + .readiness_mut(stream.token.unwrap()) + .interest + .insert(Ready::WRITABLE), + Position::Server => return MuxResult::Connect(self.stream), + }; } MuxResult::Continue } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 7b33f3eea..7797bbb8b 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -251,14 +251,11 @@ impl ConnectionH2 { println!("{:?}", kawa.storage.data()); let (size, status) = self.socket.socket_write(kawa.storage.data()); println!(" size: {size}, status: {status:?}"); - let size = kawa.storage.available_data(); - kawa.storage.consume(size); - if kawa.storage.is_empty() { - self.readiness.interest.remove(Ready::WRITABLE); - self.readiness.interest.insert(Ready::READABLE); - self.state = H2State::Header; - self.expect = Some((H2StreamId::Zero, 9)); - } + kawa.storage.clear(); + self.readiness.interest.remove(Ready::WRITABLE); + self.readiness.interest.insert(Ready::READABLE); + self.state = H2State::Header; + self.expect = Some((H2StreamId::Zero, 9)); MuxResult::Continue } (H2State::Error, _) => unreachable!(), diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 3c1dcea1a..080618a48 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -79,7 +79,7 @@ impl Connection { socket: front_stream, position: Position::Client, readiness: Readiness { - interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, + interest: Ready::WRITABLE | Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, stream: stream_id, @@ -120,7 +120,7 @@ impl Connection { socket: front_stream, position: Position::Client, readiness: Readiness { - interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, + interest: Ready::WRITABLE | Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, streams: HashMap::new(), From 61865b206b94a99737da832a54e517c7f1f2aed9 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 28 Aug 2023 18:23:02 +0200 Subject: [PATCH 16/44] H2 header fixes: - Separate hpack encoder and decoder for each ConnectionH2 - Filter out forbidden H2 headers in converter - Remove kawa namespace prefix in pkawa Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 35 +++++++++-------- lib/src/protocol/mux/h2.rs | 3 +- lib/src/protocol/mux/mod.rs | 4 +- lib/src/protocol/mux/pkawa.rs | 63 ++++++++++++++++--------------- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 2b021a547..2a8c76477 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -1,5 +1,9 @@ +use std::str::from_utf8_unchecked; + use kawa::{AsBuffer, Block, BlockConverter, Chunk, Flags, Kawa, Pair, StatusLine, Store}; +use crate::protocol::http::parser::compare_no_case; + use super::{ parser::{FrameHeader, FrameType}, serializer::gen_frame_header, @@ -14,7 +18,7 @@ pub struct H2BlockConverter<'a> { impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { fn call(&mut self, block: Block, kawa: &mut Kawa) { - let buffer = kawa.storage.mut_buffer(); + let buffer = kawa.storage.buffer(); match block { Block::StatusLine => match kawa.detached.status_line.pop() { StatusLine::Request { @@ -66,20 +70,21 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { } Block::Header(Pair { key, val }) => { { - // let key = key.data(kawa.storage.buffer()); - // let val = val.data(kawa.storage.buffer()); - // if compare_no_case(key, b"connection") - // || compare_no_case(key, b"host") - // || compare_no_case(key, b"http2-settings") - // || compare_no_case(key, b"keep-alive") - // || compare_no_case(key, b"proxy-connection") - // || compare_no_case(key, b"te") && !compare_no_case(val, b"trailers") - // || compare_no_case(key, b"trailer") - // || compare_no_case(key, b"transfer-encoding") - // || compare_no_case(key, b"upgrade") - // { - // return; - // } + let key = key.data(buffer); + let val = val.data(buffer); + if compare_no_case(key, b"connection") + || compare_no_case(key, b"host") + || compare_no_case(key, b"http2-settings") + || compare_no_case(key, b"keep-alive") + || compare_no_case(key, b"proxy-connection") + || compare_no_case(key, b"te") && !compare_no_case(val, b"trailers") + || compare_no_case(key, b"trailer") + || compare_no_case(key, b"transfer-encoding") + || compare_no_case(key, b"upgrade") + { + println!("Elided H2 header: {}", unsafe { from_utf8_unchecked(key) }); + return; + } } self.encoder .encode_header_into( diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 7797bbb8b..4d6f0a8e8 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -50,6 +50,7 @@ impl Default for H2Settings { pub struct ConnectionH2 { pub decoder: hpack::Decoder<'static>, + pub encoder: hpack::Encoder<'static>, pub expect: Option<(H2StreamId, usize)>, pub position: Position, pub readiness: Readiness, @@ -263,7 +264,7 @@ impl ConnectionH2 { (_, _) => { let mut converter = converter::H2BlockConverter { stream_id: 0, - encoder: unsafe { std::mem::transmute(&mut self.decoder) }, + encoder: &mut self.encoder, out: Vec::new(), }; let mut want_write = false; diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 080618a48..142d1f8ae 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -107,6 +107,7 @@ impl Connection { zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, decoder: hpack::Decoder::new(), + encoder: hpack::Encoder::new(), })) } pub fn new_h2_client( @@ -130,6 +131,7 @@ impl Connection { zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, decoder: hpack::Decoder::new(), + encoder: hpack::Encoder::new(), })) } @@ -592,7 +594,7 @@ impl SessionState for Mux { || backend.readiness().filter_interest().is_error() { println!("{:?} {:?}", backend.readiness(), backend.socket()); - return SessionResult::Close; + // return SessionResult::Close; } } diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index d44412abe..f4ec9280d 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -1,6 +1,9 @@ use std::{io::Write, str::from_utf8_unchecked}; -use kawa::h1::ParserCallbacks; +use kawa::{ + h1::ParserCallbacks, repr::Slice, Block, BodySize, Flags, Kind, Pair, ParsingPhase, StatusLine, + Store, Version, +}; use crate::{pool::Checkout, protocol::http::parser::compare_no_case}; @@ -15,20 +18,20 @@ pub fn handle_header( ) where C: ParserCallbacks, { - kawa.push_block(kawa::Block::StatusLine); + kawa.push_block(Block::StatusLine); kawa.detached.status_line = match kawa.kind { - kawa::Kind::Request => { - let mut method = kawa::Store::Empty; - let mut authority = kawa::Store::Empty; - let mut path = kawa::Store::Empty; - let mut scheme = kawa::Store::Empty; + Kind::Request => { + let mut method = Store::Empty; + let mut authority = Store::Empty; + let mut path = Store::Empty; + let mut scheme = Store::Empty; decoder .decode_with_cb(input, |k, v| { let start = kawa.storage.end as u32; kawa.storage.write_all(&v).unwrap(); let len_key = k.len() as u32; let len_val = v.len() as u32; - let val = kawa::Store::Slice(kawa::repr::Slice { + let val = Store::Slice(Slice { start, len: len_val, }); @@ -45,14 +48,14 @@ pub fn handle_header( if compare_no_case(&k, b"content-length") { let length = unsafe { from_utf8_unchecked(&v).parse::().unwrap() }; - kawa.body_size = kawa::BodySize::Length(length); + kawa.body_size = BodySize::Length(length); } kawa.storage.write_all(&k).unwrap(); - let key = kawa::Store::Slice(kawa::repr::Slice { + let key = Store::Slice(Slice { start: start + len_val, len: len_key, }); - kawa.push_block(kawa::Block::Header(kawa::Pair { key, val })); + kawa.push_block(Block::Header(Pair { key, val })); } }) .unwrap(); @@ -68,24 +71,24 @@ pub fn handle_header( // ) // }; // println!("Reconstructed URI: {uri}"); - kawa::StatusLine::Request { - version: kawa::Version::V20, + StatusLine::Request { + version: Version::V20, method, - uri: path.clone(), //kawa::Store::from_string(uri), + uri: path.clone(), //Store::from_string(uri), authority, path, } } - kawa::Kind::Response => { + Kind::Response => { let mut code = 0; - let mut status = kawa::Store::Empty; + let mut status = Store::Empty; decoder .decode_with_cb(input, |k, v| { let start = kawa.storage.end as u32; kawa.storage.write_all(&v).unwrap(); let len_key = k.len() as u32; let len_val = v.len() as u32; - let val = kawa::Store::Slice(kawa::repr::Slice { + let val = Store::Slice(Slice { start, len: len_val, }); @@ -100,19 +103,19 @@ pub fn handle_header( } } else { kawa.storage.write_all(&k).unwrap(); - let key = kawa::Store::Slice(kawa::repr::Slice { + let key = Store::Slice(Slice { start: start + len_val, len: len_key, }); - kawa.push_block(kawa::Block::Header(kawa::Pair { key, val })); + kawa.push_block(Block::Header(Pair { key, val })); } }) .unwrap(); - kawa::StatusLine::Response { - version: kawa::Version::V20, + StatusLine::Response { + version: Version::V20, code, status, - reason: kawa::Store::Empty, + reason: Store::Empty, } } }; @@ -122,7 +125,7 @@ pub fn handle_header( callbacks.on_headers(kawa); - kawa.push_block(kawa::Block::Flags(kawa::Flags { + kawa.push_block(Block::Flags(Flags { end_body: false, end_chunk: false, end_header: true, @@ -130,21 +133,21 @@ pub fn handle_header( })); if end_stream { - kawa.push_block(kawa::Block::Flags(kawa::Flags { + kawa.push_block(Block::Flags(Flags { end_body: true, end_chunk: false, end_header: false, end_stream: true, })); - kawa.body_size = kawa::BodySize::Length(0); + kawa.body_size = BodySize::Length(0); } kawa.parsing_phase = match kawa.body_size { - kawa::BodySize::Chunked => kawa::ParsingPhase::Chunks { first: true }, - kawa::BodySize::Length(0) => kawa::ParsingPhase::Terminated, - kawa::BodySize::Length(_) => kawa::ParsingPhase::Body, - kawa::BodySize::Empty => { + BodySize::Chunked => ParsingPhase::Chunks { first: true }, + BodySize::Length(0) => ParsingPhase::Terminated, + BodySize::Length(_) => ParsingPhase::Body, + BodySize::Empty => { println!("HTTP is just the worst..."); - kawa::ParsingPhase::Body + ParsingPhase::Body } }; } From 6f4571b2d07cc385140ece9f471cad589e57592a Mon Sep 17 00:00:00 2001 From: hcaumeil <78665596+hcaumeil@users.noreply.github.com> Date: Mon, 28 Aug 2023 18:23:11 +0200 Subject: [PATCH 17/44] H2<->H2 Settings handshake --- lib/src/protocol/mux/h2.rs | 104 +++++++++++++++++++++-------- lib/src/protocol/mux/serializer.rs | 66 +++++++++++++++++- 2 files changed, 139 insertions(+), 31 deletions(-) diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 4d6f0a8e8..cea00e0c5 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -27,12 +27,12 @@ pub enum H2State { #[derive(Debug)] pub struct H2Settings { - settings_header_table_size: u32, - settings_enable_push: bool, - settings_max_concurrent_streams: u32, - settings_initial_window_size: u32, - settings_max_frame_size: u32, - settings_max_header_list_size: u32, + pub settings_header_table_size: u32, + pub settings_enable_push: bool, + pub settings_max_concurrent_streams: u32, + pub settings_initial_window_size: u32, + pub settings_max_frame_size: u32, + pub settings_max_header_list_size: u32, } impl Default for H2Settings { @@ -135,7 +135,7 @@ impl ConnectionH2 { } (H2State::ClientSettings, Position::Server) => { let i = kawa.storage.data(); - match parser::settings_frame( + let settings = match parser::settings_frame( i, &FrameHeader { payload_len: i.len() as u32, @@ -146,10 +146,10 @@ impl ConnectionH2 { ) { Ok((_, settings)) => { kawa.storage.clear(); - self.handle(settings, context, endpoint); + settings } Err(e) => panic!("{e:?}"), - } + }; self.state = H2State::ServerSettings; let kawa = &mut self.zero; match serializer::gen_frame_header( @@ -164,25 +164,34 @@ impl ConnectionH2 { Ok((_, size)) => kawa.storage.fill(size), Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), }; - // kawa.storage - // .write(&[1, 3, 0, 0, 0, 100, 0, 4, 0, 1, 0, 0]) - // .unwrap(); - match serializer::gen_frame_header( - kawa.storage.space(), - &FrameHeader { - payload_len: 0, - frame_type: FrameType::Settings, - flags: 1, - stream_id: 0, - }, - ) { - Ok((_, size)) => kawa.storage.fill(size), - Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), + + self.handle(settings, context, endpoint); + } + (H2State::ServerSettings, Position::Client) => { + let i = kawa.storage.data(); + + match parser::frame_header(i) { + Ok(( + _, + FrameHeader { + payload_len, + frame_type: FrameType::Settings, + flags: 0, + stream_id: 0, + }, + )) => { + kawa.storage.clear(); + self.expect = Some((H2StreamId::Zero, payload_len as usize)); + self.state = H2State::Frame(FrameHeader { + payload_len, + frame_type: FrameType::Settings, + flags: 0, + stream_id: 0, + }) + } + _ => todo!(), }; - self.readiness.interest.insert(Ready::WRITABLE); - self.readiness.interest.remove(Ready::READABLE); } - (H2State::ServerSettings, Position::Client) => todo!("Receive server Settings"), (H2State::ServerSettings, Position::Server) => { error!("waiting for ServerPreface to finish writing") } @@ -215,7 +224,7 @@ impl ConnectionH2 { Err(e) => panic!("{e:?}"), }; } - (H2State::Frame(header), Position::Server) => { + (H2State::Frame(header), _) => { let i = kawa.storage.data(); println!(" data: {i:?}"); let frame = @@ -242,9 +251,32 @@ impl ConnectionH2 { { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Client) => todo!("Send PRI"), + (H2State::ClientPreface, Position::Client) => { + let pri = serializer::H2_PRI.as_bytes(); + let kawa = &mut self.zero; + + kawa.storage.space()[0..pri.len()].copy_from_slice(pri); + kawa.storage.fill(pri.len()); + + self.state = H2State::ClientSettings; + MuxResult::Continue + } (H2State::ClientPreface, Position::Server) => unreachable!(), - (H2State::ClientSettings, Position::Client) => todo!("Send Settings"), + (H2State::ClientSettings, Position::Client) => { + let kawa = &mut self.zero; + match serializer::gen_settings(kawa.storage.space(), &self.settings) { + Ok((_, size)) => kawa.storage.fill(size), + Err(e) => panic!("{e:?}"), + }; + let (size, status) = self.socket.socket_write(kawa.storage.data()); + self.state = H2State::ServerSettings; + self.readiness.interest.remove(Ready::WRITABLE); + self.readiness.interest.insert(Ready::READABLE); + kawa.storage.clear(); + + self.expect = Some((H2StreamId::Zero, 9)); + MuxResult::Continue + } (H2State::ClientSettings, Position::Server) => unreachable!(), (H2State::ServerSettings, Position::Client) => unreachable!(), (H2State::ServerSettings, Position::Server) => { @@ -290,6 +322,11 @@ impl ConnectionH2 { } } } + + let kawa = &mut self.zero; + self.socket.socket_write(kawa.storage.data()); + kawa.storage.clear(); + if !want_write { self.readiness.interest.remove(Ready::WRITABLE); } @@ -395,6 +432,15 @@ impl ConnectionH2 { } } println!("{:#?}", self.settings); + + let kawa = &mut self.zero; + kawa.storage.space()[0..serializer::SETTINGS_ACKNOWLEDGEMENT.len()] + .copy_from_slice(&serializer::SETTINGS_ACKNOWLEDGEMENT); + kawa.storage + .fill(serializer::SETTINGS_ACKNOWLEDGEMENT.len()); + + self.readiness.interest.insert(Ready::WRITABLE); + self.readiness.interest.remove(Ready::READABLE); } Frame::Ping(_) => todo!(), Frame::GoAway(goaway) => { diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs index ddc6c437c..ff70a9521 100644 --- a/lib/src/protocol/mux/serializer.rs +++ b/lib/src/protocol/mux/serializer.rs @@ -1,11 +1,21 @@ +use core::slice; + use cookie_factory::{ - bytes::{be_u24, be_u32, be_u8}, + bytes::{be_u16, be_u24, be_u32, be_u8}, + combinator::slice, gen, sequence::tuple, GenError, }; -use super::parser::{FrameHeader, FrameType}; +use super::{ + h2::H2Settings, + parser::{FrameHeader, FrameType}, +}; + +pub const H2_PRI: &str = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; +pub const SETTINGS_ACKNOWLEDGEMENT: [u8; 9] = [0, 0, 0, 4, 1, 0, 0, 0, 0]; +pub const PING_ACKNOWLEDGEMENT_HEADER: [u8; 9] = [0, 0, 0, 6, 1, 0, 0, 0, 0]; pub fn gen_frame_header<'a, 'b>( buf: &'a mut [u8], @@ -35,3 +45,55 @@ pub fn serialize_frame_type(f: &FrameType) -> u8 { FrameType::Continuation => 9, } } + +// pub fn gen_settings_acknoledgement<'a>(buf: &'a mut [u8]) { +// for (i, b) in SETTINGS_ACKNOWLEDGEMENT.iter().enumerate() { +// buf[i] = *b; +// } +// } + +pub fn gen_ping_acknolegment<'a>( + buf: &'a mut [u8], + payload: &[u8], +) -> Result<(&'a mut [u8], usize), GenError> { + gen( + tuple((slice(PING_ACKNOWLEDGEMENT_HEADER), slice(payload))), + buf, + ) + .map(|(buf, size)| (buf, size as usize)) +} + +pub fn gen_settings<'a>( + buf: &'a mut [u8], + settings: &H2Settings, +) -> Result<(&'a mut [u8], usize), GenError> { + gen_frame_header( + buf, + &FrameHeader { + payload_len: 6 * 6, + frame_type: FrameType::Settings, + flags: 0, + stream_id: 0, + }, + ) + .and_then(|(buf, old_size)| { + gen( + tuple(( + be_u16(1), + be_u32(settings.settings_header_table_size), + be_u16(2), + be_u32(settings.settings_enable_push as u32), + be_u16(3), + be_u32(settings.settings_max_concurrent_streams), + be_u16(4), + be_u32(settings.settings_initial_window_size), + be_u16(5), + be_u32(settings.settings_max_frame_size), + be_u16(6), + be_u32(settings.settings_max_header_list_size), + )), + buf, + ) + .map(|(buf, size)| (buf, (old_size + size as usize))) + }) +} From 4f01cd925209ad03cb5c8a2d0deb977cb8909428 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 30 Aug 2023 13:21:07 +0200 Subject: [PATCH 18/44] Mux routing: - Add BackendStatus in Position::Client, this will be used for reconnection and connection reuse - Rename UpdateReadiness trait to Endpoint - Add close method to Connection and Endpoint, for clients it handles the half closed streams - Add end_stream method to Connection and Endpoint, for clients it handles connection reusability - Add start_stream method to Connection and Endpoint, for clients it attaches a new stream - Handle stream termination for H1 and H2 servers - Router::connect tries to bundle and reuse connections (very basic scheme) - Mark newly created clients as connecting (todo: handle success and failure appropriately) - Implement Debug for Connection and its parts - Force reponse length to 0 for HEAD requests in editor Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/kawa_h1/editor.rs | 2 +- lib/src/protocol/mux/h1.rs | 91 +++++++++- lib/src/protocol/mux/h2.rs | 104 +++++++++-- lib/src/protocol/mux/mod.rs | 274 ++++++++++++++++++++++------- lib/src/protocol/mux/serializer.rs | 2 - lib/src/socket.rs | 6 + 6 files changed, 384 insertions(+), 95 deletions(-) diff --git a/lib/src/protocol/kawa_h1/editor.rs b/lib/src/protocol/kawa_h1/editor.rs index 476a97c6e..2b3abdb5c 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -38,7 +38,7 @@ pub struct HttpContext { pub user_agent: Option, // ========== Read only - /// signals wether Kawa should write a "Connection" header with a "close" value (request and response) + /// signals whether Kawa should write a "Connection" header with a "close" value (request and response) pub closing: bool, /// the value of the custom header, named "Sozu-Id", that Kawa should write (request and response) pub id: Ulid, diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index f70f4357a..f7818cf46 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -1,7 +1,7 @@ use sozu_command::ready::Ready; use crate::{ - protocol::mux::{Context, GlobalStreamId, MuxResult, Position, UpdateReadiness}, + protocol::mux::{BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, Position}, socket::SocketHandler, Readiness, }; @@ -14,14 +14,25 @@ pub struct ConnectionH1 { pub stream: GlobalStreamId, } +impl std::fmt::Debug for ConnectionH1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ConnectionH1") + .field("position", &self.position) + .field("readiness", &self.readiness) + .field("socket", &self.socket.socket_ref()) + .field("stream", &self.stream) + .finish() + } +} + impl ConnectionH1 { pub fn readable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where - E: UpdateReadiness, + E: Endpoint, { println!("======= MUX H1 READABLE"); let stream = &mut context.streams[self.stream]; - let parts = stream.split(self.position); + let parts = stream.split(&self.position); let kawa = parts.rbuffer; let (size, status) = self.socket.socket_read(kawa.storage.space()); println!(" size: {size}, status: {status:?}"); @@ -48,7 +59,7 @@ impl ConnectionH1 { } if was_initial && kawa.is_main_phase() { match self.position { - Position::Client => endpoint + Position::Client(_) => endpoint .readiness_mut(stream.token.unwrap()) .interest .insert(Ready::WRITABLE), @@ -57,13 +68,13 @@ impl ConnectionH1 { } MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + pub fn writable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where - E: UpdateReadiness, + E: Endpoint, { println!("======= MUX H1 WRITABLE"); let stream = &mut context.streams[self.stream]; - let kawa = stream.wbuffer(self.position); + let kawa = stream.wbuffer(&self.position); kawa.prepare(&mut kawa::h1::BlockConverter); kawa::debug_kawa(kawa); let bufs = kawa.as_io_slice(); @@ -80,8 +91,72 @@ impl ConnectionH1 { self.readiness.event.remove(Ready::WRITABLE); } if kawa.is_terminated() && kawa.is_completed() { - self.readiness.interest.insert(Ready::READABLE); + match self.position { + Position::Client(_) => self.readiness.interest.insert(Ready::READABLE), + Position::Server => { + endpoint.end_stream(stream.token.unwrap(), self.stream, context) + } + } } MuxResult::Continue } + + pub fn close(&mut self, context: &mut Context, mut endpoint: E) + where + E: Endpoint, + { + match self.position { + Position::Client(BackendStatus::KeepAlive(_)) + | Position::Client(BackendStatus::Disconnecting) => { + println!("close detached client ConnectionH1"); + return; + } + Position::Client(BackendStatus::Connecting(_)) => todo!("reconnect"), + Position::Client(_) => {} + Position::Server => unreachable!(), + } + endpoint.end_stream( + context.streams[self.stream].token.unwrap(), + self.stream, + context, + ) + } + + pub fn end_stream(&mut self, stream: usize, context: &mut Context) { + assert_eq!(stream, self.stream); + let stream_context = &mut context.streams[stream].context; + println!("end H1 stream {stream}: {stream_context:#?}"); + self.stream = usize::MAX; + let mut owned_position = Position::Server; + std::mem::swap(&mut owned_position, &mut self.position); + match owned_position { + Position::Client(BackendStatus::Connected(cluster_id)) + | Position::Client(BackendStatus::Connecting(cluster_id)) => { + self.position = if stream_context.keep_alive_backend { + Position::Client(BackendStatus::KeepAlive(cluster_id)) + } else { + Position::Client(BackendStatus::Disconnecting) + } + } + Position::Client(BackendStatus::KeepAlive(_)) + | Position::Client(BackendStatus::Disconnecting) => unreachable!(), + Position::Server => todo!(), + } + } + + pub fn start_stream(&mut self, stream: usize, context: &mut Context) { + println!("start H1 stream {stream}"); + self.stream = stream; + let mut owned_position = Position::Server; + std::mem::swap(&mut owned_position, &mut self.position); + match owned_position { + Position::Client(BackendStatus::KeepAlive(cluster_id)) => { + self.position = Position::Client(BackendStatus::Connecting(cluster_id)) + } + Position::Server => unreachable!(), + _ => { + self.position = owned_position; + } + } + } } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index cea00e0c5..d38f9459d 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -13,7 +13,7 @@ use crate::{ Readiness, }; -use super::{GenericHttpStream, UpdateReadiness}; +use super::{Endpoint, GenericHttpStream, BackendStatus}; #[derive(Debug)] pub enum H2State { @@ -61,6 +61,21 @@ pub struct ConnectionH2 { pub zero: GenericHttpStream, pub window: u32, } +impl std::fmt::Debug for ConnectionH2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ConnectionH2") + .field("expect", &self.expect) + .field("position", &self.position) + .field("readiness", &self.readiness) + .field("settings", &self.settings) + .field("socket", &self.socket.socket_ref()) + .field("state", &self.state) + .field("streams", &self.streams) + .field("zero", &self.zero.storage.meter(20)) + .field("window", &self.window) + .finish() + } +} #[derive(Debug, Clone, Copy)] pub enum H2StreamId { @@ -71,13 +86,13 @@ pub enum H2StreamId { impl ConnectionH2 { pub fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where - E: UpdateReadiness, + E: Endpoint, { println!("======= MUX H2 READABLE"); let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect { let kawa = match stream_id { H2StreamId::Zero => &mut self.zero, - H2StreamId::Global(stream_id) => context.streams[stream_id].rbuffer(self.position), + H2StreamId::Global(stream_id) => context.streams[stream_id].rbuffer(&self.position), }; if amount > 0 { let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); @@ -107,7 +122,7 @@ impl ConnectionH2 { return MuxResult::Continue; }; match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Client) => { + (H2State::ClientPreface, Position::Client(_)) => { error!("Waiting for ClientPreface to finish writing") } (H2State::ClientPreface, Position::Server) => { @@ -167,7 +182,7 @@ impl ConnectionH2 { self.handle(settings, context, endpoint); } - (H2State::ServerSettings, Position::Client) => { + (H2State::ServerSettings, Position::Client(_)) => { let i = kawa.storage.data(); match parser::frame_header(i) { @@ -200,7 +215,7 @@ impl ConnectionH2 { println!(" header: {i:?}"); match parser::frame_header(i) { Ok((_, header)) => { - println!("{header:?}"); + println!("{header:#?}"); kawa.storage.clear(); let stream_id = if header.stream_id == 0 { H2StreamId::Zero @@ -245,13 +260,13 @@ impl ConnectionH2 { MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + pub fn writable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where - E: UpdateReadiness, + E: Endpoint, { println!("======= MUX H2 WRITABLE"); match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Client) => { + (H2State::ClientPreface, Position::Client(_)) => { let pri = serializer::H2_PRI.as_bytes(); let kawa = &mut self.zero; @@ -262,7 +277,7 @@ impl ConnectionH2 { MuxResult::Continue } (H2State::ClientPreface, Position::Server) => unreachable!(), - (H2State::ClientSettings, Position::Client) => { + (H2State::ClientSettings, Position::Client(_)) => { let kawa = &mut self.zero; match serializer::gen_settings(kawa.storage.space(), &self.settings) { Ok((_, size)) => kawa.storage.fill(size), @@ -278,7 +293,7 @@ impl ConnectionH2 { MuxResult::Continue } (H2State::ClientSettings, Position::Server) => unreachable!(), - (H2State::ServerSettings, Position::Client) => unreachable!(), + (H2State::ServerSettings, Position::Client(_)) => unreachable!(), (H2State::ServerSettings, Position::Server) => { let kawa = &mut self.zero; println!("{:?}", kawa.storage.data()); @@ -300,8 +315,10 @@ impl ConnectionH2 { out: Vec::new(), }; let mut want_write = false; + let mut dead_streams = Vec::new(); for (stream_id, global_stream_id) in &self.streams { - let kawa = context.streams[*global_stream_id].wbuffer(self.position); + let stream = &mut context.streams[*global_stream_id]; + let kawa = stream.wbuffer(&self.position); if kawa.is_main_phase() { converter.stream_id = *stream_id; kawa.prepare(&mut converter); @@ -318,10 +335,23 @@ impl ConnectionH2 { } } if kawa.is_terminated() && kawa.is_completed() { - // close stream + match self.position { + Position::Client(_) => {} + Position::Server => { + endpoint.end_stream( + stream.token.unwrap(), + *global_stream_id, + context, + ); + dead_streams.push(*stream_id); + } + } } } } + for stream_id in dead_streams { + self.streams.remove(&stream_id).unwrap(); + } let kawa = &mut self.zero; self.socket.socket_write(kawa.storage.data()); @@ -345,15 +375,15 @@ impl ConnectionH2 { fn handle(&mut self, frame: Frame, context: &mut Context, mut endpoint: E) -> MuxResult where - E: UpdateReadiness, + E: Endpoint, { - println!("{frame:?}"); + println!("{frame:#?}"); match frame { Frame::Data(data) => { let mut slice = data.payload; let global_stream_id = *self.streams.get(&data.stream_id).unwrap(); let stream = &mut context.streams[global_stream_id]; - let kawa = stream.rbuffer(self.position); + let kawa = stream.rbuffer(&self.position); slice.start += kawa.storage.head as u32; kawa.storage.head += slice.len(); let buffer = kawa.storage.buffer(); @@ -381,7 +411,7 @@ impl ConnectionH2 { let kawa = &mut self.zero; let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); let stream = &mut context.streams[global_stream_id]; - let parts = &mut stream.split(self.position); + let parts = &mut stream.split(&self.position); pkawa::handle_header( parts.rbuffer, buffer, @@ -391,7 +421,7 @@ impl ConnectionH2 { ); kawa::debug_kawa(parts.rbuffer); match self.position { - Position::Client => endpoint + Position::Client(_) => endpoint .readiness_mut(stream.token.unwrap()) .interest .insert(Ready::WRITABLE), @@ -399,7 +429,7 @@ impl ConnectionH2 { }; } Frame::PushPromise(push_promise) => match self.position { - Position::Client => { + Position::Client(_) => { todo!("if enabled forward the push") } Position::Server => { @@ -463,4 +493,40 @@ impl ConnectionH2 { } MuxResult::Continue } + + pub fn close(&mut self, context: &mut Context, mut endpoint: E) + where + E: Endpoint, + { + match self.position { + Position::Client(BackendStatus::Connecting(_)) => todo!("reconnect"), + Position::Client(_) => {} + Position::Server => unreachable!(), + } + for global_stream_id in self.streams.values() { + endpoint.end_stream( + context.streams[*global_stream_id].token.unwrap(), + *global_stream_id, + context, + ) + } + } + + pub fn end_stream(&mut self, stream: usize, context: &mut Context) { + let stream_context = &mut context.streams[stream].context; + println!("end H2 stream {stream}: {stream_context:#?}"); + for (stream_id, global_stream_id) in &self.streams { + if *global_stream_id == stream { + let id = *stream_id; + self.streams.remove(&id); + break; + } + } + todo!() + } + + pub fn start_stream(&mut self, stream: usize, context: &mut Context) { + println!("start new H2 stream {stream}"); + todo!() + } } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 142d1f8ae..74f8d11e9 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -39,12 +39,20 @@ type GenericHttpStream = kawa::Kawa; type StreamId = u32; type GlobalStreamId = usize; -#[derive(Debug, Clone, Copy)] +#[derive(Debug)] pub enum Position { - Client, + Client(BackendStatus), Server, } +#[derive(Debug)] +pub enum BackendStatus { + Connecting(String), + Connected(String), + KeepAlive(String), + Disconnecting, +} + pub enum MuxResult { Continue, CloseSession, @@ -52,14 +60,17 @@ pub enum MuxResult { Connect(GlobalStreamId), } +#[derive(Debug)] pub enum Connection { H1(ConnectionH1), H2(ConnectionH2), } -pub trait UpdateReadiness { +pub trait Endpoint { fn readiness(&self, token: Token) -> &Readiness; fn readiness_mut(&mut self, token: Token) -> &mut Readiness; + fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context); + fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context); } impl Connection { @@ -74,15 +85,15 @@ impl Connection { stream: 0, }) } - pub fn new_h1_client(front_stream: Front, stream_id: GlobalStreamId) -> Connection { + pub fn new_h1_client(front_stream: Front, cluster_id: String) -> Connection { Connection::H1(ConnectionH1 { socket: front_stream, - position: Position::Client, + position: Position::Client(BackendStatus::Connecting(cluster_id)), readiness: Readiness { interest: Ready::WRITABLE | Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - stream: stream_id, + stream: 0, }) } @@ -112,6 +123,7 @@ impl Connection { } pub fn new_h2_client( front_stream: Front, + cluster_id: String, pool: Weak>, ) -> Option> { let buffer = pool @@ -119,7 +131,7 @@ impl Connection { .and_then(|pool| pool.borrow_mut().checkout())?; Some(Connection::H2(ConnectionH2 { socket: front_stream, - position: Position::Client, + position: Position::Client(BackendStatus::Connecting(cluster_id)), readiness: Readiness { interest: Ready::WRITABLE | Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, @@ -147,6 +159,18 @@ impl Connection { Connection::H2(c) => &mut c.readiness, } } + fn position(&self) -> &Position { + match self { + Connection::H1(c) => &c.position, + Connection::H2(c) => &c.position, + } + } + fn position_mut(&mut self) -> &mut Position { + match self { + Connection::H1(c) => &mut c.position, + Connection::H2(c) => &mut c.position, + } + } pub fn socket(&self) -> &TcpStream { match self { Connection::H1(c) => c.socket.socket_ref(), @@ -155,7 +179,7 @@ impl Connection { } fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where - E: UpdateReadiness, + E: Endpoint, { match self { Connection::H1(c) => c.readable(context, endpoint), @@ -164,13 +188,37 @@ impl Connection { } fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where - E: UpdateReadiness, + E: Endpoint, { match self { Connection::H1(c) => c.writable(context, endpoint), Connection::H2(c) => c.writable(context, endpoint), } } + + fn close(&mut self, context: &mut Context, endpoint: E) + where + E: Endpoint, + { + match self { + Connection::H1(c) => c.close(context, endpoint), + Connection::H2(c) => c.close(context, endpoint), + } + } + + fn end_stream(&mut self, stream: usize, context: &mut Context) { + match self { + Connection::H1(c) => c.end_stream(stream, context), + Connection::H2(c) => c.end_stream(stream, context), + } + } + + fn start_stream(&mut self, stream: usize, context: &mut Context) { + match self { + Connection::H1(c) => c.start_stream(stream, context), + Connection::H2(c) => c.start_stream(stream, context), + } + } } struct EndpointServer<'a>(&'a mut Connection); @@ -178,21 +226,48 @@ struct EndpointClient<'a>(&'a mut Router); // note: EndpointServer are used by client Connection, they do not know the frontend Token // they will use the Stream's Token which is their backend token -impl<'a> UpdateReadiness for EndpointServer<'a> { +impl<'a> Endpoint for EndpointServer<'a> { fn readiness(&self, _token: Token) -> &Readiness { self.0.readiness() } fn readiness_mut(&mut self, _token: Token) -> &mut Readiness { self.0.readiness_mut() } + + fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + // this may be used to forward H2<->H2 RstStream + // or to handle backend hup + self.0.end_stream(stream, context); + } + + fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + // this may be used to forward H2<->H2 PushPromise + todo!() + } } -impl<'a> UpdateReadiness for EndpointClient<'a> { +impl<'a> Endpoint for EndpointClient<'a> { fn readiness(&self, token: Token) -> &Readiness { self.0.backends.get(&token).unwrap().readiness() } fn readiness_mut(&mut self, token: Token) -> &mut Readiness { self.0.backends.get_mut(&token).unwrap().readiness_mut() } + + fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + self.0 + .backends + .get_mut(&token) + .unwrap() + .end_stream(stream, context); + } + + fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + self.0 + .backends + .get_mut(&token) + .unwrap() + .start_stream(stream, context); + } } // enum Stream { @@ -260,8 +335,8 @@ impl Stream { context: HttpContext { backend_id: None, cluster_id: None, - keep_alive_backend: false, - keep_alive_frontend: false, + keep_alive_backend: true, + keep_alive_frontend: true, sticky_session_found: None, method: None, authority: None, @@ -279,9 +354,9 @@ impl Stream { }, }) } - pub fn split(&mut self, position: Position) -> StreamParts<'_> { + pub fn split(&mut self, position: &Position) -> StreamParts<'_> { match position { - Position::Client => StreamParts { + Position::Client(_) => StreamParts { rbuffer: &mut self.back, wbuffer: &mut self.front, context: &mut self.context, @@ -293,15 +368,15 @@ impl Stream { }, } } - pub fn rbuffer(&mut self, position: Position) -> &mut GenericHttpStream { + pub fn rbuffer(&mut self, position: &Position) -> &mut GenericHttpStream { match position { - Position::Client => &mut self.back, + Position::Client(_) => &mut self.back, Position::Server => &mut self.front, } } - pub fn wbuffer(&mut self, position: Position) -> &mut GenericHttpStream { + pub fn wbuffer(&mut self, position: &Position) -> &mut GenericHttpStream { match position { - Position::Client => &mut self.front, + Position::Client(_) => &mut self.front, Position::Server => &mut self.back, } } @@ -342,13 +417,13 @@ impl Router { metrics: &mut SessionMetrics, ) -> Result<(), BackendConnectionError> { let stream = &mut context.streams[stream_id]; - let context = &mut stream.context; - // we should get if the route is H2 or not here - // for now we assume it's H1 - let cluster_id = self - .cluster_id_from_request(context, proxy.clone()) + // when reused, a stream should be detached from its old connection, if not we could end + // with concurrent connections on a single endpoint + assert!(stream.token.is_none()); + let stream_context = &mut stream.context; + let (cluster_id, h2) = self + .route_from_request(stream_context, proxy.clone()) .map_err(BackendConnectionError::RetrieveClusterError)?; - println!("{cluster_id}!!!!!"); let frontend_should_stick = proxy .borrow() @@ -357,44 +432,95 @@ impl Router { .map(|cluster| cluster.sticky_session) .unwrap_or(false); - let mut socket = self.backend_from_request( - &cluster_id, - frontend_should_stick, - context, - proxy.clone(), - metrics, - )?; - - if let Err(e) = socket.set_nodelay(true) { - error!( - "error setting nodelay on back socket({:?}): {:?}", - socket, e - ); + let mut reuse_token = None; + // let mut priority = 0; + let mut reuse_connecting = true; + for (token, backend) in &self.backends { + match (h2, reuse_connecting, backend.position()) { + (_, _, Position::Server) => panic!("Backend connection behaves like a server"), + (_, _, Position::Client(BackendStatus::Disconnecting)) => {} + + (true, _, Position::Client(BackendStatus::Connected(old_cluster_id))) => { + if *old_cluster_id == cluster_id { + reuse_token = Some(*token); + reuse_connecting = false; + break; + } + } + (true, true, Position::Client(BackendStatus::Connecting(old_cluster_id))) => { + if *old_cluster_id == cluster_id { + reuse_token = Some(*token) + } + } + (true, false, Position::Client(BackendStatus::Connecting(_))) => {} + (true, _, Position::Client(BackendStatus::KeepAlive(_))) => { + panic!("ConnectionH2 behaves like H1") + } + + (false, _, Position::Client(BackendStatus::KeepAlive(old_cluster_id))) => { + if *old_cluster_id == cluster_id { + reuse_token = Some(*token); + reuse_connecting = false; + break; + } + } + // can't bundle H1 streams together + (false, _, Position::Client(BackendStatus::Connected(_))) + | (false, _, Position::Client(BackendStatus::Connecting(_))) => {} + } } - // self.backend_readiness.interest = Ready::WRITABLE | Ready::HUP | Ready::ERROR; - // self.backend_connection_status = BackendConnectionStatus::Connecting(Instant::now()); + println!("connect: {cluster_id} (stick={frontend_should_stick}, h2={h2}) -> (reuse={reuse_token:?})"); - let backend_token = proxy.borrow().add_session(session); + let token = if let Some(token) = reuse_token { + token + } else { + let mut socket = self.backend_from_request( + &cluster_id, + frontend_should_stick, + stream_context, + proxy.clone(), + metrics, + )?; + + if let Err(e) = socket.set_nodelay(true) { + error!( + "error setting nodelay on back socket({:?}): {:?}", + socket, e + ); + } + // self.backend_readiness.interest = Ready::WRITABLE | Ready::HUP | Ready::ERROR; + // self.backend_connection_status = BackendConnectionStatus::Connecting(Instant::now()); - if let Err(e) = proxy.borrow().register_socket( - &mut socket, - backend_token, - Interest::READABLE | Interest::WRITABLE, - ) { - error!("error registering back socket({:?}): {:?}", socket, e); - } + let token = proxy.borrow().add_session(session); + + if let Err(e) = proxy.borrow().register_socket( + &mut socket, + token, + Interest::READABLE | Interest::WRITABLE, + ) { + error!("error registering back socket({:?}): {:?}", socket, e); + } - stream.token = Some(backend_token); + let connection = Connection::new_h1_client(socket, cluster_id); + self.backends.insert(token, connection); + token + }; + + // link stream to backend + stream.token = Some(token); + // link backend to stream self.backends - .insert(backend_token, Connection::new_h1_client(socket, stream_id)); + .get_mut(&token) + .unwrap() + .start_stream(stream_id, context); Ok(()) } - fn cluster_id_from_request( + fn route_from_request( &mut self, context: &mut HttpContext, _proxy: Rc>, - ) -> Result { + ) -> Result<(String, bool), RetrieveClusterError> { let (host, uri, method) = match context.extract_route() { Ok(tuple) => tuple, Err(cluster_error) => { @@ -419,7 +545,7 @@ impl Router { }; let cluster_id = match route { - Route::Cluster { id, .. } => id, + Route::Cluster { id, h2 } => (id, h2), Route::Deny => { panic!("Route::Deny"); // self.set_answer(DefaultAnswerStatus::Answer401, None); @@ -554,8 +680,28 @@ impl SessionState for Mux { dirty = true; } - for (_, backend) in self.router.backends.iter_mut() { - if backend.readiness().filter_interest().is_writable() { + let mut dead_backends = Vec::new(); + for (token, backend) in self.router.backends.iter_mut() { + let readiness = backend.readiness().filter_interest(); + if readiness.is_hup() || readiness.is_error() { + println!( + "{token:?} -> {:?} {:?}", + backend.readiness(), + backend.socket() + ); + backend.close(context, EndpointServer(&mut self.frontend)); + dead_backends.push(*token); + } + if readiness.is_writable() { + let mut owned_position = Position::Server; + let position = backend.position_mut(); + std::mem::swap(&mut owned_position, position); + match owned_position { + Position::Client(BackendStatus::Connecting(cluster_id)) => { + *position = Position::Client(BackendStatus::Connected(cluster_id)); + } + _ => *position = owned_position, + } match backend.writable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, @@ -565,7 +711,7 @@ impl SessionState for Mux { dirty = true; } - if backend.readiness().filter_interest().is_readable() { + if readiness.is_readable() { match backend.readable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => (), MuxResult::CloseSession => return SessionResult::Close, @@ -575,6 +721,13 @@ impl SessionState for Mux { dirty = true; } } + if !dead_backends.is_empty() { + for token in &dead_backends { + self.router.backends.remove(token); + } + println!("FRONTEND: {:#?}", &self.frontend); + println!("BACKENDS: {:#?}", self.router.backends); + } if self.frontend.readiness().filter_interest().is_writable() { match self @@ -589,15 +742,6 @@ impl SessionState for Mux { dirty = true; } - for backend in self.router.backends.values() { - if backend.readiness().filter_interest().is_hup() - || backend.readiness().filter_interest().is_error() - { - println!("{:?} {:?}", backend.readiness(), backend.socket()); - // return SessionResult::Close; - } - } - if !dirty { break; } diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs index ff70a9521..e8d5a595d 100644 --- a/lib/src/protocol/mux/serializer.rs +++ b/lib/src/protocol/mux/serializer.rs @@ -1,5 +1,3 @@ -use core::slice; - use cookie_factory::{ bytes::{be_u16, be_u24, be_u32, be_u8}, combinator::slice, diff --git a/lib/src/socket.rs b/lib/src/socket.rs index 1a494264e..76d741ba1 100644 --- a/lib/src/socket.rs +++ b/lib/src/socket.rs @@ -172,6 +172,12 @@ pub struct FrontRustls { pub session: ServerConnection, } +impl std::fmt::Debug for FrontRustls { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("FrontRustls") + } +} + impl SocketHandler for FrontRustls { fn socket_read(&mut self, buf: &mut [u8]) -> (usize, SocketResult) { let mut size = 0usize; From f7a7558181a8abbafe7009f70e9aa44f5962daf6 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 31 Aug 2023 15:05:49 +0200 Subject: [PATCH 19/44] Set default RulePosition to Tree when parsing config.toml Signed-off-by: Eloi DEMOLIS --- command/src/config.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/command/src/config.rs b/command/src/config.rs index b8794ed4a..ab2b1389e 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -661,6 +661,11 @@ pub enum PathRuleType { Equals, } +/// Congruent with command.proto +fn default_rule_position() -> RulePosition { + RulePosition::Tree +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct FileClusterFrontendConfig { @@ -676,7 +681,7 @@ pub struct FileClusterFrontendConfig { pub certificate_chain: Option, #[serde(default)] pub tls_versions: Vec, - #[serde(default)] + #[serde(default = "default_rule_position")] pub position: RulePosition, pub tags: Option>, pub h2: Option, From 691616f9ecec020260d0e1f11efe7630e1b889ee Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 31 Aug 2023 23:24:01 +0200 Subject: [PATCH 20/44] H2 client endpoint: - Create an H2 client connection if the cluster is tagged h2 - Write stream 0 first, make sure it's empty before continuing - Mark closed H2 streams as inactive and reuse them - Generate new increasing stream ideas (todo: make sure to "advertize" them in the right order) - Fix response status parsing - Temporary H1 compatibility fixes (should be fixed in Kawa): - Use "Default" as H2 response reason - Add "Content-Length: 0" on body less H2 messages Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/h1.rs | 10 ++--- lib/src/protocol/mux/h2.rs | 81 ++++++++++++++++++++++++----------- lib/src/protocol/mux/mod.rs | 75 +++++++++++++++++++++----------- lib/src/protocol/mux/pkawa.rs | 18 +++++--- 4 files changed, 123 insertions(+), 61 deletions(-) diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index f7818cf46..d69faf112 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -30,7 +30,7 @@ impl ConnectionH1 { where E: Endpoint, { - println!("======= MUX H1 READABLE"); + println!("======= MUX H1 READABLE {:?}", self.position); let stream = &mut context.streams[self.stream]; let parts = stream.split(&self.position); let kawa = parts.rbuffer; @@ -72,7 +72,7 @@ impl ConnectionH1 { where E: Endpoint, { - println!("======= MUX H1 WRITABLE"); + println!("======= MUX H1 WRITABLE {:?}", self.position); let stream = &mut context.streams[self.stream]; let kawa = stream.wbuffer(&self.position); kawa.prepare(&mut kawa::h1::BlockConverter); @@ -122,7 +122,7 @@ impl ConnectionH1 { ) } - pub fn end_stream(&mut self, stream: usize, context: &mut Context) { + pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { assert_eq!(stream, self.stream); let stream_context = &mut context.streams[stream].context; println!("end H1 stream {stream}: {stream_context:#?}"); @@ -144,8 +144,8 @@ impl ConnectionH1 { } } - pub fn start_stream(&mut self, stream: usize, context: &mut Context) { - println!("start H1 stream {stream}"); + pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + println!("start H1 stream {stream} {:?}", self.readiness); self.stream = stream; let mut owned_position = Position::Server; std::mem::swap(&mut owned_position, &mut self.position); diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index d38f9459d..d92edaa78 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -13,7 +13,7 @@ use crate::{ Readiness, }; -use super::{Endpoint, GenericHttpStream, BackendStatus}; +use super::{BackendStatus, Endpoint, GenericHttpStream}; #[derive(Debug)] pub enum H2State { @@ -57,6 +57,7 @@ pub struct ConnectionH2 { pub settings: H2Settings, pub socket: Front, pub state: H2State, + pub last_stream_id: StreamId, pub streams: HashMap, pub zero: GenericHttpStream, pub window: u32, @@ -88,7 +89,7 @@ impl ConnectionH2 { where E: Endpoint, { - println!("======= MUX H2 READABLE"); + println!("======= MUX H2 READABLE {:?}", self.position); let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect { let kawa = match stream_id { H2StreamId::Zero => &mut self.zero, @@ -109,7 +110,7 @@ impl ConnectionH2 { return MuxResult::Continue; } } else { - // We wanted to read (amoun > 0) but there is nothing yet (size == 0) + // We wanted to read (amount > 0) but there is nothing yet (size == 0) self.readiness.event.remove(Ready::READABLE); return MuxResult::Continue; } @@ -122,9 +123,13 @@ impl ConnectionH2 { return MuxResult::Continue; }; match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Client(_)) => { - error!("Waiting for ClientPreface to finish writing") - } + (H2State::Error, _) + | (H2State::ServerSettings, Position::Server) + | (H2State::ClientPreface, Position::Client(_)) + | (H2State::ClientSettings, Position::Client(_)) => panic!( + "Unexpected combination: (Writable, {:?}, {:?})", + self.state, self.position + ), (H2State::ClientPreface, Position::Server) => { let i = kawa.storage.data(); let i = match parser::preface(i) { @@ -207,10 +212,7 @@ impl ConnectionH2 { _ => todo!(), }; } - (H2State::ServerSettings, Position::Server) => { - error!("waiting for ServerPreface to finish writing") - } - (H2State::Header, Position::Server) => { + (H2State::Header, _) => { let i = kawa.storage.data(); println!(" header: {i:?}"); match parser::frame_header(i) { @@ -255,7 +257,6 @@ impl ConnectionH2 { self.expect = Some((H2StreamId::Zero, 9)); return state_result; } - _ => unreachable!(), } MuxResult::Continue } @@ -264,9 +265,17 @@ impl ConnectionH2 { where E: Endpoint, { - println!("======= MUX H2 WRITABLE"); + println!("======= MUX H2 WRITABLE {:?}", self.position); match (&self.state, &self.position) { + (H2State::Error, _) + | (H2State::ClientPreface, Position::Server) + | (H2State::ClientSettings, Position::Server) + | (H2State::ServerSettings, Position::Client(_)) => panic!( + "Unexpected combination: (Readable, {:?}, {:?})", + self.state, self.position + ), (H2State::ClientPreface, Position::Client(_)) => { + println!("Ppreparing preface"); let pri = serializer::H2_PRI.as_bytes(); let kawa = &mut self.zero; @@ -276,14 +285,15 @@ impl ConnectionH2 { self.state = H2State::ClientSettings; MuxResult::Continue } - (H2State::ClientPreface, Position::Server) => unreachable!(), (H2State::ClientSettings, Position::Client(_)) => { + println!("Sending preface and settings"); let kawa = &mut self.zero; match serializer::gen_settings(kawa.storage.space(), &self.settings) { Ok((_, size)) => kawa.storage.fill(size), Err(e) => panic!("{e:?}"), }; let (size, status) = self.socket.socket_write(kawa.storage.data()); + println!(" size: {size}, status: {status:?}"); self.state = H2State::ServerSettings; self.readiness.interest.remove(Ready::WRITABLE); self.readiness.interest.insert(Ready::READABLE); @@ -292,8 +302,6 @@ impl ConnectionH2 { self.expect = Some((H2StreamId::Zero, 9)); MuxResult::Continue } - (H2State::ClientSettings, Position::Server) => unreachable!(), - (H2State::ServerSettings, Position::Client(_)) => unreachable!(), (H2State::ServerSettings, Position::Server) => { let kawa = &mut self.zero; println!("{:?}", kawa.storage.data()); @@ -306,9 +314,20 @@ impl ConnectionH2 { self.expect = Some((H2StreamId::Zero, 9)); MuxResult::Continue } - (H2State::Error, _) => unreachable!(), // Proxying states (Header/Frame) (_, _) => { + let kawa = &mut self.zero; + while !kawa.storage.is_empty() { + let (size, status) = self.socket.socket_write(kawa.storage.data()); + println!(" size: {size}, status: {status:?}"); + if size > 0 { + kawa.storage.consume(size); + } else { + return MuxResult::Continue; + } + } + self.readiness.interest.insert(Ready::READABLE); + let mut converter = converter::H2BlockConverter { stream_id: 0, encoder: &mut self.encoder, @@ -338,6 +357,9 @@ impl ConnectionH2 { match self.position { Position::Client(_) => {} Position::Server => { + // mark stream as reusable + stream.active = false; + println!("Recycle stream: {global_stream_id}"); endpoint.end_stream( stream.token.unwrap(), *global_stream_id, @@ -353,10 +375,6 @@ impl ConnectionH2 { self.streams.remove(&stream_id).unwrap(); } - let kawa = &mut self.zero; - self.socket.socket_write(kawa.storage.data()); - kawa.storage.clear(); - if !want_write { self.readiness.interest.remove(Ready::WRITABLE); } @@ -369,10 +387,21 @@ impl ConnectionH2 { let global_stream_id = context .create_stream(Ulid::generate(), self.settings.settings_initial_window_size) .unwrap(); + if stream_id > self.last_stream_id { + self.last_stream_id = stream_id >> 1; + } self.streams.insert(stream_id, global_stream_id); global_stream_id } + pub fn new_stream_id(&mut self) -> StreamId { + self.last_stream_id += 2; + match self.position { + Position::Client(_) => self.last_stream_id + 1, + Position::Server => self.last_stream_id, + } + } + fn handle(&mut self, frame: Frame, context: &mut Context, mut endpoint: E) -> MuxResult where E: Endpoint, @@ -512,7 +541,7 @@ impl ConnectionH2 { } } - pub fn end_stream(&mut self, stream: usize, context: &mut Context) { + pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { let stream_context = &mut context.streams[stream].context; println!("end H2 stream {stream}: {stream_context:#?}"); for (stream_id, global_stream_id) in &self.streams { @@ -522,11 +551,13 @@ impl ConnectionH2 { break; } } - todo!() + // todo!() } - pub fn start_stream(&mut self, stream: usize, context: &mut Context) { - println!("start new H2 stream {stream}"); - todo!() + pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + println!("start new H2 stream {stream} {:?}", self.readiness); + let stream_id = self.new_stream_id(); + self.streams.insert(stream_id, stream); + self.readiness.interest.insert(Ready::WRITABLE); } } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 74f8d11e9..f274d93ae 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -119,6 +119,7 @@ impl Connection { window: 1 << 16, decoder: hpack::Decoder::new(), encoder: hpack::Encoder::new(), + last_stream_id: 0, })) } pub fn new_h2_client( @@ -133,7 +134,7 @@ impl Connection { socket: front_stream, position: Position::Client(BackendStatus::Connecting(cluster_id)), readiness: Readiness { - interest: Ready::WRITABLE | Ready::READABLE | Ready::HUP | Ready::ERROR, + interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, streams: HashMap::new(), @@ -144,6 +145,7 @@ impl Connection { window: 1 << 16, decoder: hpack::Decoder::new(), encoder: hpack::Encoder::new(), + last_stream_id: 0, })) } @@ -206,14 +208,14 @@ impl Connection { } } - fn end_stream(&mut self, stream: usize, context: &mut Context) { + fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { match self { Connection::H1(c) => c.end_stream(stream, context), Connection::H2(c) => c.end_stream(stream, context), } } - fn start_stream(&mut self, stream: usize, context: &mut Context) { + fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { match self { Connection::H1(c) => c.start_stream(stream, context), Connection::H2(c) => c.start_stream(stream, context), @@ -300,6 +302,7 @@ impl<'a> Endpoint for EndpointClient<'a> { pub struct Stream { // pub request_id: Ulid, + pub active: bool, pub window: i32, pub token: Option, front: GenericHttpStream, @@ -315,6 +318,29 @@ pub struct StreamParts<'a> { pub context: &'a mut HttpContext, } +fn temporary_http_context(request_id: Ulid) -> HttpContext { + HttpContext { + backend_id: None, + cluster_id: None, + keep_alive_backend: true, + keep_alive_frontend: true, + sticky_session_found: None, + method: None, + authority: None, + path: None, + status: None, + reason: None, + user_agent: None, + closing: false, + id: request_id, + protocol: crate::Protocol::HTTPS, + public_address: "0.0.0.0:80".parse().unwrap(), + session_address: None, + sticky_name: "SOZUBALANCEID".to_owned(), + sticky_session: None, + } +} + impl Stream { pub fn new(pool: Weak>, request_id: Ulid, window: u32) -> Option { let (front_buffer, back_buffer) = match pool.upgrade() { @@ -328,30 +354,12 @@ impl Stream { None => return None, }; Some(Self { + active: true, window: window as i32, token: None, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), - context: HttpContext { - backend_id: None, - cluster_id: None, - keep_alive_backend: true, - keep_alive_frontend: true, - sticky_session_found: None, - method: None, - authority: None, - path: None, - status: None, - reason: None, - user_agent: None, - closing: false, - id: request_id, - protocol: crate::Protocol::HTTPS, - public_address: "0.0.0.0:80".parse().unwrap(), - session_address: None, - sticky_name: "SOZUBALANCEID".to_owned(), - sticky_session: None, - }, + context: temporary_http_context(request_id), }) } pub fn split(&mut self, position: &Position) -> StreamParts<'_> { @@ -389,6 +397,20 @@ pub struct Context { impl Context { pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option { + for (stream_id, stream) in self.streams.iter_mut().enumerate() { + if !stream.active { + println!("Reuse stream: {stream_id}"); + stream.window = window as i32; + stream.context = temporary_http_context(request_id); + stream.back.clear(); + stream.back.storage.clear(); + stream.front.clear(); + stream.front.storage.clear(); + stream.token = None; + stream.active = true; + return Some(stream_id); + } + } self.streams .push(Stream::new(self.pool.clone(), request_id, window)?); Some(self.streams.len() - 1) @@ -472,6 +494,7 @@ impl Router { println!("connect: {cluster_id} (stick={frontend_should_stick}, h2={h2}) -> (reuse={reuse_token:?})"); let token = if let Some(token) = reuse_token { + println!("reused backend: {:#?}", self.backends.get(&token).unwrap()); token } else { let mut socket = self.backend_from_request( @@ -501,7 +524,11 @@ impl Router { error!("error registering back socket({:?}): {:?}", socket, e); } - let connection = Connection::new_h1_client(socket, cluster_id); + let connection = if h2 { + Connection::new_h2_client(socket, cluster_id, context.pool.clone()).unwrap() + } else { + Connection::new_h1_client(socket, cluster_id) + }; self.backends.insert(token, connection); token }; diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index f4ec9280d..d106da0d8 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -95,12 +95,7 @@ pub fn handle_header( if compare_no_case(&k, b":status") { status = val; - code = unsafe { - std::str::from_utf8_unchecked(&k) - .parse::() - .ok() - .unwrap() - } + code = unsafe { from_utf8_unchecked(&v).parse::().ok().unwrap() } } else { kawa.storage.write_all(&k).unwrap(); let key = Store::Slice(Slice { @@ -115,7 +110,7 @@ pub fn handle_header( version: Version::V20, code, status, - reason: Store::Empty, + reason: Store::Static(b"Default"), } } }; @@ -125,6 +120,15 @@ pub fn handle_header( callbacks.on_headers(kawa); + if end_stream { + if let BodySize::Empty = kawa.body_size { + kawa.push_block(Block::Header(Pair { + key: Store::Static(b"Content-Length"), + val: Store::Static(b"0"), + })); + } + } + kawa.push_block(Block::Flags(Flags { end_body: false, end_chunk: false, From 43ee66e9ddb9878157ab7a1322cde3fb40436eb5 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 4 Sep 2023 18:07:00 +0200 Subject: [PATCH 21/44] h2 improvments: - Add expect_write to H2 connection to handle partial writes - Centralize H2 socket writing in the same way as socket reading (with expect_read) - Use expect_write for ClientPreface, ClientSettings, ServerSettings and Pong frames - Try a very basic priority ordering (whith dummy priorities) Other improvments: - Add update_readiness_after_{read, write} to handle socket result and early return - Fix H1 keep-alive by properly resetting Kawa and HttpContext - Fix FrontTls read returning WouldBlock even when it shouldn't Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/kawa_h1/editor.rs | 1 + lib/src/protocol/kawa_h1/mod.rs | 1 - lib/src/protocol/mux/h1.rs | 42 +++--- lib/src/protocol/mux/h2.rs | 207 +++++++++++++++++------------ lib/src/protocol/mux/mod.rs | 63 +++++++-- lib/src/protocol/mux/pkawa.rs | 7 + lib/src/socket.rs | 2 +- 7 files changed, 209 insertions(+), 114 deletions(-) diff --git a/lib/src/protocol/kawa_h1/editor.rs b/lib/src/protocol/kawa_h1/editor.rs index 2b3abdb5c..9903292a6 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -356,6 +356,7 @@ impl HttpContext { self.status = None; self.reason = None; self.user_agent = None; + self.id = Ulid::generate(); } pub fn log_context(&self) -> LogContext { diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index e1b2ec712..07840b0db 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -268,7 +268,6 @@ impl Http return, }; - self.context.id = Ulid::generate(); self.context.reset(); self.request_stream.clear(); diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index d69faf112..3067f2bc2 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -1,7 +1,10 @@ use sozu_command::ready::Ready; use crate::{ - protocol::mux::{BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, Position}, + protocol::mux::{ + update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, + Endpoint, GlobalStreamId, MuxResult, Position, + }, socket::SocketHandler, Readiness, }; @@ -12,6 +15,7 @@ pub struct ConnectionH1 { pub socket: Front, /// note: a Server H1 will always reference stream 0, but a client can reference any stream pub stream: GlobalStreamId, + pub requests: usize, } impl std::fmt::Debug for ConnectionH1 { @@ -35,19 +39,11 @@ impl ConnectionH1 { let parts = stream.split(&self.position); let kawa = parts.rbuffer; let (size, status) = self.socket.socket_read(kawa.storage.space()); - println!(" size: {size}, status: {status:?}"); - if size > 0 { - kawa.storage.fill(size); - } else { - self.readiness.event.remove(Ready::READABLE); + kawa.storage.fill(size); + if update_readiness_after_read(size, status, &mut self.readiness) { return MuxResult::Continue; } - // match status { - // SocketResult::Continue => {} - // SocketResult::Closed => todo!(), - // SocketResult::Error => todo!(), - // SocketResult::WouldBlock => self.readiness.event.remove(Ready::READABLE), - // } + let was_initial = kawa.is_initial(); kawa::h1::parse(kawa, parts.context); kawa::debug_kawa(kawa); @@ -58,6 +54,8 @@ impl ConnectionH1 { self.readiness.interest.remove(Ready::READABLE); } if was_initial && kawa.is_main_phase() { + self.requests += 1; + println!("REQUESTS: {}", self.requests); match self.position { Position::Client(_) => endpoint .readiness_mut(stream.token.unwrap()) @@ -83,18 +81,22 @@ impl ConnectionH1 { return MuxResult::Continue; } let (size, status) = self.socket.socket_write_vectored(&bufs); - println!(" size: {size}, status: {status:?}"); - if size > 0 { - kawa.consume(size); - // self.backend_readiness.interest.insert(Ready::READABLE); - } else { - self.readiness.event.remove(Ready::WRITABLE); + kawa.consume(size); + if update_readiness_after_write(size, status, &mut self.readiness) { + return MuxResult::Continue; } + if kawa.is_terminated() && kawa.is_completed() { match self.position { Position::Client(_) => self.readiness.interest.insert(Ready::READABLE), Position::Server => { - endpoint.end_stream(stream.token.unwrap(), self.stream, context) + stream.context.reset(); + stream.back.clear(); + stream.back.storage.clear(); + stream.front.clear(); + // do not clear stream.front because of H1 pipelining + let token = stream.token.take().unwrap(); + endpoint.end_stream(token, self.stream, context); } } } @@ -112,7 +114,7 @@ impl ConnectionH1 { return; } Position::Client(BackendStatus::Connecting(_)) => todo!("reconnect"), - Position::Client(_) => {} + Position::Client(BackendStatus::Connected(_)) => {} Position::Server => unreachable!(), } endpoint.end_stream( diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index d92edaa78..1500bfc03 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -7,7 +7,8 @@ use crate::{ protocol::mux::{ converter, parser::{self, error_code_to_str, Frame, FrameHeader, FrameType}, - pkawa, serializer, Context, GlobalStreamId, MuxResult, Position, StreamId, + pkawa, serializer, update_readiness_after_read, update_readiness_after_write, Context, + GlobalStreamId, MuxResult, Position, StreamId, }, socket::SocketHandler, Readiness, @@ -51,7 +52,8 @@ impl Default for H2Settings { pub struct ConnectionH2 { pub decoder: hpack::Decoder<'static>, pub encoder: hpack::Encoder<'static>, - pub expect: Option<(H2StreamId, usize)>, + pub expect_read: Option<(H2StreamId, usize)>, + pub expect_write: Option, pub position: Position, pub readiness: Readiness, pub settings: H2Settings, @@ -65,7 +67,7 @@ pub struct ConnectionH2 { impl std::fmt::Debug for ConnectionH2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ConnectionH2") - .field("expect", &self.expect) + .field("expect", &self.expect_read) .field("position", &self.position) .field("readiness", &self.readiness) .field("settings", &self.settings) @@ -81,7 +83,7 @@ impl std::fmt::Debug for ConnectionH2 { #[derive(Debug, Clone, Copy)] pub enum H2StreamId { Zero, - Global(GlobalStreamId), + Other(StreamId, GlobalStreamId), } impl ConnectionH2 { @@ -90,32 +92,29 @@ impl ConnectionH2 { E: Endpoint, { println!("======= MUX H2 READABLE {:?}", self.position); - let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect { + let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect_read { let kawa = match stream_id { H2StreamId::Zero => &mut self.zero, - H2StreamId::Global(stream_id) => context.streams[stream_id].rbuffer(&self.position), + H2StreamId::Other(stream_id, global_stream_id) => { + context.streams[global_stream_id].rbuffer(&self.position) + } }; + println!("{:?}({stream_id:?}, {amount})", self.state); if amount > 0 { let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); - println!( - "{:?}({stream_id:?}, {amount}) {size} {status:?}", - self.state - ); - if size > 0 { - kawa.storage.fill(size); + kawa.storage.fill(size); + if update_readiness_after_read(size, status, &mut self.readiness) { + return MuxResult::Continue; + } else { if size == amount { - self.expect = None; + self.expect_read = None; } else { - self.expect = Some((stream_id, amount - size)); + self.expect_read = Some((stream_id, amount - size)); return MuxResult::Continue; } - } else { - // We wanted to read (amount > 0) but there is nothing yet (size == 0) - self.readiness.event.remove(Ready::READABLE); - return MuxResult::Continue; } } else { - self.expect = None; + self.expect_read = None; } (stream_id, kawa) } else { @@ -148,7 +147,7 @@ impl ConnectionH2 { )) => { kawa.storage.clear(); self.state = H2State::ClientSettings; - self.expect = Some((H2StreamId::Zero, payload_len as usize)); + self.expect_read = Some((H2StreamId::Zero, payload_len as usize)); } _ => todo!(), }; @@ -170,7 +169,6 @@ impl ConnectionH2 { } Err(e) => panic!("{e:?}"), }; - self.state = H2State::ServerSettings; let kawa = &mut self.zero; match serializer::gen_frame_header( kawa.storage.space(), @@ -185,11 +183,12 @@ impl ConnectionH2 { Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), }; + self.state = H2State::ServerSettings; + self.expect_write = Some(H2StreamId::Zero); self.handle(settings, context, endpoint); } (H2State::ServerSettings, Position::Client(_)) => { let i = kawa.storage.data(); - match parser::frame_header(i) { Ok(( _, @@ -201,7 +200,7 @@ impl ConnectionH2 { }, )) => { kawa.storage.clear(); - self.expect = Some((H2StreamId::Zero, payload_len as usize)); + self.expect_read = Some((H2StreamId::Zero, payload_len as usize)); self.state = H2State::Frame(FrameHeader { payload_len, frame_type: FrameType::Settings, @@ -219,23 +218,25 @@ impl ConnectionH2 { Ok((_, header)) => { println!("{header:#?}"); kawa.storage.clear(); - let stream_id = if header.stream_id == 0 { - H2StreamId::Zero - } else { - let stream_id = - if let Some(stream_id) = self.streams.get(&header.stream_id) { - *stream_id - } else { - self.create_stream(header.stream_id, context) - }; - if header.frame_type == FrameType::Data { - H2StreamId::Global(stream_id) - } else { + let stream_id = header.stream_id; + let stream_id = + if stream_id == 0 || header.frame_type == FrameType::RstStream { H2StreamId::Zero - } - }; + } else { + let global_stream_id = + if let Some(global_stream_id) = self.streams.get(&stream_id) { + *global_stream_id + } else { + self.create_stream(stream_id, context) + }; + if header.frame_type == FrameType::Data { + H2StreamId::Other(stream_id, global_stream_id) + } else { + H2StreamId::Zero + } + }; println!("{} {stream_id:?} {:#?}", header.stream_id, self.streams); - self.expect = Some((stream_id, header.payload_len as usize)); + self.expect_read = Some((stream_id, header.payload_len as usize)); self.state = H2State::Frame(header); } Err(e) => panic!("{e:?}"), @@ -254,7 +255,7 @@ impl ConnectionH2 { } let state_result = self.handle(frame, context, endpoint); self.state = H2State::Header; - self.expect = Some((H2StreamId::Zero, 9)); + self.expect_read = Some((H2StreamId::Zero, 9)); return state_result; } } @@ -266,6 +267,21 @@ impl ConnectionH2 { E: Endpoint, { println!("======= MUX H2 WRITABLE {:?}", self.position); + if let Some(H2StreamId::Zero) = self.expect_write { + let kawa = &mut self.zero; + println!("{:?}", kawa.storage.data()); + while !kawa.storage.is_empty() { + let (size, status) = self.socket.socket_write(kawa.storage.data()); + kawa.storage.consume(size); + if update_readiness_after_write(size, status, &mut self.readiness) { + return MuxResult::Continue; + } + } + // when H2StreamId::Zero is used to write READABLE is disabled + // so when we finish the write we enable READABLE again + self.readiness.interest.insert(Ready::READABLE); + self.expect_write = None; + } match (&self.state, &self.position) { (H2State::Error, _) | (H2State::ClientPreface, Position::Server) @@ -275,68 +291,80 @@ impl ConnectionH2 { self.state, self.position ), (H2State::ClientPreface, Position::Client(_)) => { - println!("Ppreparing preface"); + println!("Preparing preface and settings"); let pri = serializer::H2_PRI.as_bytes(); let kawa = &mut self.zero; kawa.storage.space()[0..pri.len()].copy_from_slice(pri); kawa.storage.fill(pri.len()); + match serializer::gen_settings(kawa.storage.space(), &self.settings) { + Ok((_, size)) => kawa.storage.fill(size), + Err(e) => panic!("{e:?}"), + }; self.state = H2State::ClientSettings; + self.expect_write = Some(H2StreamId::Zero); MuxResult::Continue } (H2State::ClientSettings, Position::Client(_)) => { - println!("Sending preface and settings"); - let kawa = &mut self.zero; - match serializer::gen_settings(kawa.storage.space(), &self.settings) { - Ok((_, size)) => kawa.storage.fill(size), - Err(e) => panic!("{e:?}"), - }; - let (size, status) = self.socket.socket_write(kawa.storage.data()); - println!(" size: {size}, status: {status:?}"); + println!("Sent preface and settings"); self.state = H2State::ServerSettings; self.readiness.interest.remove(Ready::WRITABLE); - self.readiness.interest.insert(Ready::READABLE); - kawa.storage.clear(); - - self.expect = Some((H2StreamId::Zero, 9)); + self.expect_read = Some((H2StreamId::Zero, 9)); MuxResult::Continue } (H2State::ServerSettings, Position::Server) => { - let kawa = &mut self.zero; - println!("{:?}", kawa.storage.data()); - let (size, status) = self.socket.socket_write(kawa.storage.data()); - println!(" size: {size}, status: {status:?}"); - kawa.storage.clear(); - self.readiness.interest.remove(Ready::WRITABLE); - self.readiness.interest.insert(Ready::READABLE); self.state = H2State::Header; - self.expect = Some((H2StreamId::Zero, 9)); + self.readiness.interest.remove(Ready::WRITABLE); + self.expect_read = Some((H2StreamId::Zero, 9)); MuxResult::Continue } // Proxying states (Header/Frame) (_, _) => { - let kawa = &mut self.zero; - while !kawa.storage.is_empty() { - let (size, status) = self.socket.socket_write(kawa.storage.data()); - println!(" size: {size}, status: {status:?}"); - if size > 0 { - kawa.storage.consume(size); - } else { - return MuxResult::Continue; + let mut dead_streams = Vec::new(); + + if let Some(H2StreamId::Other(stream_id, global_stream_id)) = self.expect_write { + let stream = &mut context.streams[global_stream_id]; + let kawa = stream.wbuffer(&self.position); + while !kawa.out.is_empty() { + let bufs = kawa.as_io_slice(); + let (size, status) = self.socket.socket_write_vectored(&bufs); + kawa.consume(size); + if update_readiness_after_write(size, status, &mut self.readiness) { + return MuxResult::Continue; + } + } + self.expect_write = None; + if kawa.is_terminated() && kawa.is_completed() { + match self.position { + Position::Client(_) => {} + Position::Server => { + // mark stream as reusable + stream.active = false; + println!("Recycle stream: {global_stream_id}"); + endpoint.end_stream( + stream.token.unwrap(), + global_stream_id, + context, + ); + dead_streams.push(stream_id); + } + } } } - self.readiness.interest.insert(Ready::READABLE); let mut converter = converter::H2BlockConverter { stream_id: 0, encoder: &mut self.encoder, out: Vec::new(), }; - let mut want_write = false; - let mut dead_streams = Vec::new(); - for (stream_id, global_stream_id) in &self.streams { - let stream = &mut context.streams[*global_stream_id]; + let mut priorities = self.streams.keys().collect::>(); + priorities.sort(); + + println!("PRIORITIES: {priorities:?}"); + 'outer: for stream_id in priorities { + let global_stream_id = *self.streams.get(stream_id).unwrap(); + let stream = &mut context.streams[global_stream_id]; let kawa = stream.wbuffer(&self.position); if kawa.is_main_phase() { converter.stream_id = *stream_id; @@ -345,12 +373,11 @@ impl ConnectionH2 { while !kawa.out.is_empty() { let bufs = kawa.as_io_slice(); let (size, status) = self.socket.socket_write_vectored(&bufs); - println!(" size: {size}, status: {status:?}"); - if size > 0 { - kawa.consume(size); - } else { - want_write = true; - break; + kawa.consume(size); + if update_readiness_after_write(size, status, &mut self.readiness) { + self.expect_write = + Some(H2StreamId::Other(*stream_id, global_stream_id)); + break 'outer; } } if kawa.is_terminated() && kawa.is_completed() { @@ -362,7 +389,7 @@ impl ConnectionH2 { println!("Recycle stream: {global_stream_id}"); endpoint.end_stream( stream.token.unwrap(), - *global_stream_id, + global_stream_id, context, ); dead_streams.push(*stream_id); @@ -375,7 +402,8 @@ impl ConnectionH2 { self.streams.remove(&stream_id).unwrap(); } - if !want_write { + if self.expect_write.is_none() { + // We wrote everything self.readiness.interest.remove(Ready::WRITABLE); } MuxResult::Continue @@ -500,8 +528,18 @@ impl ConnectionH2 { self.readiness.interest.insert(Ready::WRITABLE); self.readiness.interest.remove(Ready::READABLE); + self.expect_write = Some(H2StreamId::Zero); + } + Frame::Ping(ping) => { + let kawa = &mut self.zero; + match serializer::gen_ping_acknolegment(kawa.storage.space(), &ping.payload) { + Ok((_, size)) => kawa.storage.fill(size), + Err(e) => panic!("could not serialize PingFrame: {e:?}"), + }; + self.readiness.interest.insert(Ready::WRITABLE); + self.readiness.interest.remove(Ready::READABLE); + self.expect_write = Some(H2StreamId::Zero); } - Frame::Ping(_) => todo!(), Frame::GoAway(goaway) => { println!( "GoAway({} -> {})", @@ -533,6 +571,7 @@ impl ConnectionH2 { Position::Server => unreachable!(), } for global_stream_id in self.streams.values() { + println!("end stream: {global_stream_id}"); endpoint.end_stream( context.streams[*global_stream_id].token.unwrap(), *global_stream_id, @@ -548,10 +587,10 @@ impl ConnectionH2 { if *global_stream_id == stream { let id = *stream_id; self.streams.remove(&id); - break; + return; } } - // todo!() + panic!(); } pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index f274d93ae..0d4a65a73 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -27,7 +27,7 @@ use crate::{ SessionState, }, router::Route, - socket::{FrontRustls, SocketHandler}, + socket::{FrontRustls, SocketHandler, SocketResult}, BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, RetrieveClusterError, SessionMetrics, SessionResult, StateResult, }; @@ -83,6 +83,7 @@ impl Connection { event: Ready::EMPTY, }, stream: 0, + requests: 0, }) } pub fn new_h1_client(front_stream: Front, cluster_id: String) -> Connection { @@ -94,6 +95,7 @@ impl Connection { event: Ready::EMPTY, }, stream: 0, + requests: 0, }) } @@ -113,7 +115,8 @@ impl Connection { }, streams: HashMap::new(), state: H2State::ClientPreface, - expect: Some((H2StreamId::Zero, 24 + 9)), + expect_read: Some((H2StreamId::Zero, 24 + 9)), + expect_write: None, settings: H2Settings::default(), zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, @@ -139,7 +142,8 @@ impl Connection { }, streams: HashMap::new(), state: H2State::ClientPreface, - expect: None, + expect_read: None, + expect_write: None, settings: H2Settings::default(), zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, @@ -272,6 +276,52 @@ impl<'a> Endpoint for EndpointClient<'a> { } } +fn update_readiness_after_read( + size: usize, + status: SocketResult, + readiness: &mut Readiness, +) -> bool { + println!(" size={size}, status={status:?}"); + match status { + SocketResult::Continue => {} + SocketResult::Closed | SocketResult::Error => { + readiness.event.remove(Ready::ALL); + } + SocketResult::WouldBlock => { + readiness.event.remove(Ready::READABLE); + } + } + if size > 0 { + false + } else { + readiness.event.remove(Ready::READABLE); + true + } +} +fn update_readiness_after_write( + size: usize, + status: SocketResult, + readiness: &mut Readiness, +) -> bool { + println!(" size={size}, status={status:?}"); + match status { + SocketResult::Continue => {} + SocketResult::Closed | SocketResult::Error => { + // even if the socket closed there might be something left to read + readiness.event.remove(Ready::WRITABLE); + } + SocketResult::WouldBlock => { + readiness.event.remove(Ready::WRITABLE); + } + } + if size > 0 { + false + } else { + readiness.event.remove(Ready::WRITABLE); + true + } +} + // enum Stream { // Idle { // window: i32, @@ -711,11 +761,7 @@ impl SessionState for Mux { for (token, backend) in self.router.backends.iter_mut() { let readiness = backend.readiness().filter_interest(); if readiness.is_hup() || readiness.is_error() { - println!( - "{token:?} -> {:?} {:?}", - backend.readiness(), - backend.socket() - ); + println!("{token:?} -> {:?}", backend); backend.close(context, EndpointServer(&mut self.frontend)); dead_backends.push(*token); } @@ -778,6 +824,7 @@ impl SessionState for Mux { if counter == max_loop_iterations { incr!("http.infinite_loop.error"); + panic!(); return SessionResult::Close; } diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index d106da0d8..02df82d69 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -44,6 +44,8 @@ pub fn handle_header( path = val; } else if compare_no_case(&k, b":scheme") { scheme = val; + } else if compare_no_case(&k, b"cookie") { + panic!("cookies should be split in pairs"); } else { if compare_no_case(&k, b"content-length") { let length = @@ -145,6 +147,11 @@ pub fn handle_header( })); kawa.body_size = BodySize::Length(0); } + + if kawa.parsing_phase == ParsingPhase::Terminated { + return; + } + kawa.parsing_phase = match kawa.body_size { BodySize::Chunked => ParsingPhase::Chunks { first: true }, BodySize::Length(0) => ParsingPhase::Terminated, diff --git a/lib/src/socket.rs b/lib/src/socket.rs index 76d741ba1..e3c9cdd4e 100644 --- a/lib/src/socket.rs +++ b/lib/src/socket.rs @@ -197,7 +197,7 @@ impl SocketHandler for FrontRustls { break; } - if !can_read | is_error | is_closed { + if !can_read || is_error || is_closed { break; } From 9deb6b5d56e577156482c449f0287cac069ed3b5 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 6 Sep 2023 10:51:41 +0200 Subject: [PATCH 22/44] Error handling: - Add H2Error to CloseSession - Call goaway upon CloseSession - Replace many panics with CloseSession Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 7 +- lib/src/lib.rs | 2 + lib/src/protocol/mux/h1.rs | 21 +-- lib/src/protocol/mux/h2.rs | 227 ++++++++++++++++++++------------- lib/src/protocol/mux/mod.rs | 109 ++++++++++------ lib/src/protocol/mux/parser.rs | 34 +++-- lib/src/protocol/mux/pkawa.rs | 2 +- 7 files changed, 253 insertions(+), 149 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index 002af9278..c329872a2 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -462,19 +462,24 @@ impl ProxySession for HttpsSession { token, super::ready_to_string(events) ); + println!("EVENT: {token:?}->{events:?}"); self.last_event = Instant::now(); self.metrics.wait_start(); self.state.update_readiness(token, events); } fn ready(&mut self, session: Rc>) -> SessionIsToBeClosed { - println!("READY"); + let start = std::time::Instant::now(); + println!("READY {start:?}"); self.metrics.service_start(); let session_result = self.state .ready(session.clone(), self.proxy.clone(), &mut self.metrics); + let end = std::time::Instant::now(); + println!("READY END {end:?} -> {:?}", end.duration_since(start)); + let to_be_closed = match session_result { SessionResult::Close => true, SessionResult::Continue => false, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 0c2bca139..903bd8443 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -585,6 +585,8 @@ pub enum BackendConnectionError { MaxConnectionRetries(Option), #[error("the sessions slab has reached maximum capacity")] MaxSessionsMemory, + #[error("the checkout pool has reached maximum capacity")] + MaxBuffers, #[error("error from the backend: {0}")] Backend(BackendError), #[error("failed to retrieve the cluster: {0}")] diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 3067f2bc2..359698c00 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -1,9 +1,10 @@ use sozu_command::ready::Ready; use crate::{ + println_, protocol::mux::{ - update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, - Endpoint, GlobalStreamId, MuxResult, Position, + debug_kawa, update_readiness_after_read, update_readiness_after_write, BackendStatus, + Context, Endpoint, GlobalStreamId, MuxResult, Position, }, socket::SocketHandler, Readiness, @@ -34,7 +35,7 @@ impl ConnectionH1 { where E: Endpoint, { - println!("======= MUX H1 READABLE {:?}", self.position); + println_!("======= MUX H1 READABLE {:?}", self.position); let stream = &mut context.streams[self.stream]; let parts = stream.split(&self.position); let kawa = parts.rbuffer; @@ -46,7 +47,7 @@ impl ConnectionH1 { let was_initial = kawa.is_initial(); kawa::h1::parse(kawa, parts.context); - kawa::debug_kawa(kawa); + debug_kawa(kawa); if kawa.is_error() { return MuxResult::Close(self.stream); } @@ -55,7 +56,7 @@ impl ConnectionH1 { } if was_initial && kawa.is_main_phase() { self.requests += 1; - println!("REQUESTS: {}", self.requests); + println_!("REQUESTS: {}", self.requests); match self.position { Position::Client(_) => endpoint .readiness_mut(stream.token.unwrap()) @@ -70,11 +71,11 @@ impl ConnectionH1 { where E: Endpoint, { - println!("======= MUX H1 WRITABLE {:?}", self.position); + println_!("======= MUX H1 WRITABLE {:?}", self.position); let stream = &mut context.streams[self.stream]; let kawa = stream.wbuffer(&self.position); kawa.prepare(&mut kawa::h1::BlockConverter); - kawa::debug_kawa(kawa); + debug_kawa(kawa); let bufs = kawa.as_io_slice(); if bufs.is_empty() { self.readiness.interest.remove(Ready::WRITABLE); @@ -110,7 +111,7 @@ impl ConnectionH1 { match self.position { Position::Client(BackendStatus::KeepAlive(_)) | Position::Client(BackendStatus::Disconnecting) => { - println!("close detached client ConnectionH1"); + println_!("close detached client ConnectionH1"); return; } Position::Client(BackendStatus::Connecting(_)) => todo!("reconnect"), @@ -127,7 +128,7 @@ impl ConnectionH1 { pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { assert_eq!(stream, self.stream); let stream_context = &mut context.streams[stream].context; - println!("end H1 stream {stream}: {stream_context:#?}"); + println_!("end H1 stream {stream}: {stream_context:#?}"); self.stream = usize::MAX; let mut owned_position = Position::Server; std::mem::swap(&mut owned_position, &mut self.position); @@ -147,7 +148,7 @@ impl ConnectionH1 { } pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { - println!("start H1 stream {stream} {:?}", self.readiness); + println_!("start H1 stream {stream} {:?}", self.readiness); self.stream = stream; let mut owned_position = Position::Server; std::mem::swap(&mut owned_position, &mut self.position); diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 1500bfc03..1f94f51a3 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -4,17 +4,28 @@ use rusty_ulid::Ulid; use sozu_command::ready::Ready; use crate::{ + println_, protocol::mux::{ - converter, - parser::{self, error_code_to_str, Frame, FrameHeader, FrameType}, - pkawa, serializer, update_readiness_after_read, update_readiness_after_write, Context, - GlobalStreamId, MuxResult, Position, StreamId, + converter, debug_kawa, + parser::{self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error}, + pkawa, serializer, update_readiness_after_read, update_readiness_after_write, + BackendStatus, Context, Endpoint, GenericHttpStream, GlobalStreamId, MuxResult, Position, + StreamId, }, socket::SocketHandler, Readiness, }; -use super::{BackendStatus, Endpoint, GenericHttpStream}; +#[inline(always)] +fn error_nom_to_h2(error: nom::Err) -> MuxResult { + match error { + nom::Err::Error(parser::Error { + error: parser::InnerError::H2(e), + .. + }) => return MuxResult::CloseSession(e), + _ => return MuxResult::CloseSession(H2Error::ProtocolError), + } +} #[derive(Debug)] pub enum H2State { @@ -49,6 +60,8 @@ impl Default for H2Settings { } } +struct Prioriser {} + pub struct ConnectionH2 { pub decoder: hpack::Decoder<'static>, pub encoder: hpack::Encoder<'static>, @@ -56,7 +69,8 @@ pub struct ConnectionH2 { pub expect_write: Option, pub position: Position, pub readiness: Readiness, - pub settings: H2Settings, + pub local_settings: H2Settings, + pub peer_settings: H2Settings, pub socket: Front, pub state: H2State, pub last_stream_id: StreamId, @@ -70,7 +84,8 @@ impl std::fmt::Debug for ConnectionH2 { .field("expect", &self.expect_read) .field("position", &self.position) .field("readiness", &self.readiness) - .field("settings", &self.settings) + .field("local_settings", &self.local_settings) + .field("peer_settings", &self.peer_settings) .field("socket", &self.socket.socket_ref()) .field("state", &self.state) .field("streams", &self.streams) @@ -91,7 +106,7 @@ impl ConnectionH2 { where E: Endpoint, { - println!("======= MUX H2 READABLE {:?}", self.position); + println_!("======= MUX H2 READABLE {:?}", self.position); let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect_read { let kawa = match stream_id { H2StreamId::Zero => &mut self.zero, @@ -99,7 +114,7 @@ impl ConnectionH2 { context.streams[global_stream_id].rbuffer(&self.position) } }; - println!("{:?}({stream_id:?}, {amount})", self.state); + println_!("{:?}({stream_id:?}, {amount})", self.state); if amount > 0 { let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); kawa.storage.fill(size); @@ -125,7 +140,7 @@ impl ConnectionH2 { (H2State::Error, _) | (H2State::ServerSettings, Position::Server) | (H2State::ClientPreface, Position::Client(_)) - | (H2State::ClientSettings, Position::Client(_)) => panic!( + | (H2State::ClientSettings, Position::Client(_)) => unreachable!( "Unexpected combination: (Writable, {:?}, {:?})", self.state, self.position ), @@ -133,7 +148,7 @@ impl ConnectionH2 { let i = kawa.storage.data(); let i = match parser::preface(i) { Ok((i, _)) => i, - Err(e) => panic!("{e:?}"), + Err(_) => return MuxResult::CloseSession(H2Error::ProtocolError), }; match parser::frame_header(i) { Ok(( @@ -149,7 +164,7 @@ impl ConnectionH2 { self.state = H2State::ClientSettings; self.expect_read = Some((H2StreamId::Zero, payload_len as usize)); } - _ => todo!(), + _ => return MuxResult::CloseSession(H2Error::ProtocolError), }; } (H2State::ClientSettings, Position::Server) => { @@ -167,7 +182,7 @@ impl ConnectionH2 { kawa.storage.clear(); settings } - Err(e) => panic!("{e:?}"), + Err(_) => return MuxResult::CloseSession(H2Error::ProtocolError), }; let kawa = &mut self.zero; match serializer::gen_frame_header( @@ -180,7 +195,10 @@ impl ConnectionH2 { }, ) { Ok((_, size)) => kawa.storage.fill(size), - Err(e) => panic!("could not serialize HeaderFrame: {e:?}"), + Err(e) => { + println!("could not serialize HeaderFrame: {e:?}"); + return MuxResult::CloseSession(H2Error::InternalError); + } }; self.state = H2State::ServerSettings; @@ -192,7 +210,7 @@ impl ConnectionH2 { match parser::frame_header(i) { Ok(( _, - FrameHeader { + header @ FrameHeader { payload_len, frame_type: FrameType::Settings, flags: 0, @@ -201,55 +219,58 @@ impl ConnectionH2 { )) => { kawa.storage.clear(); self.expect_read = Some((H2StreamId::Zero, payload_len as usize)); - self.state = H2State::Frame(FrameHeader { - payload_len, - frame_type: FrameType::Settings, - flags: 0, - stream_id: 0, - }) + self.state = H2State::Frame(header) } - _ => todo!(), + _ => return MuxResult::CloseSession(H2Error::ProtocolError), }; } (H2State::Header, _) => { let i = kawa.storage.data(); - println!(" header: {i:?}"); + println_!(" header: {i:?}"); match parser::frame_header(i) { Ok((_, header)) => { - println!("{header:#?}"); + println_!("{header:#?}"); kawa.storage.clear(); let stream_id = header.stream_id; - let stream_id = - if stream_id == 0 || header.frame_type == FrameType::RstStream { - H2StreamId::Zero + let stream_id = if stream_id == 0 + || header.frame_type == FrameType::RstStream + { + H2StreamId::Zero + } else { + let global_stream_id = if let Some(global_stream_id) = + self.streams.get(&stream_id) + { + *global_stream_id } else { - let global_stream_id = - if let Some(global_stream_id) = self.streams.get(&stream_id) { - *global_stream_id - } else { - self.create_stream(stream_id, context) - }; - if header.frame_type == FrameType::Data { - H2StreamId::Other(stream_id, global_stream_id) - } else { - H2StreamId::Zero + match self.create_stream(stream_id, context) { + Some(global_stream_id) => global_stream_id, + None => return MuxResult::CloseSession(H2Error::InternalError), } }; - println!("{} {stream_id:?} {:#?}", header.stream_id, self.streams); + if header.frame_type == FrameType::Data { + H2StreamId::Other(stream_id, global_stream_id) + } else { + H2StreamId::Zero + } + }; + println_!("{} {stream_id:?} {:#?}", header.stream_id, self.streams); self.expect_read = Some((stream_id, header.payload_len as usize)); self.state = H2State::Frame(header); } - Err(e) => panic!("{e:?}"), + Err(e) => return error_nom_to_h2(e), }; } (H2State::Frame(header), _) => { let i = kawa.storage.data(); - println!(" data: {i:?}"); - let frame = - match parser::frame_body(i, header, self.settings.settings_max_frame_size) { - Ok((_, frame)) => frame, - Err(e) => panic!("{e:?}"), - }; + println_!(" data: {i:?}"); + let frame = match parser::frame_body( + i, + header, + self.local_settings.settings_max_frame_size, + ) { + Ok((_, frame)) => frame, + Err(e) => return error_nom_to_h2(e), + }; if let H2StreamId::Zero = stream_id { kawa.storage.clear(); } @@ -266,10 +287,10 @@ impl ConnectionH2 { where E: Endpoint, { - println!("======= MUX H2 WRITABLE {:?}", self.position); + println_!("======= MUX H2 WRITABLE {:?}", self.position); if let Some(H2StreamId::Zero) = self.expect_write { let kawa = &mut self.zero; - println!("{:?}", kawa.storage.data()); + println_!("{:?}", kawa.storage.data()); while !kawa.storage.is_empty() { let (size, status) = self.socket.socket_write(kawa.storage.data()); kawa.storage.consume(size); @@ -286,20 +307,23 @@ impl ConnectionH2 { (H2State::Error, _) | (H2State::ClientPreface, Position::Server) | (H2State::ClientSettings, Position::Server) - | (H2State::ServerSettings, Position::Client(_)) => panic!( + | (H2State::ServerSettings, Position::Client(_)) => unreachable!( "Unexpected combination: (Readable, {:?}, {:?})", self.state, self.position ), (H2State::ClientPreface, Position::Client(_)) => { - println!("Preparing preface and settings"); + println_!("Preparing preface and settings"); let pri = serializer::H2_PRI.as_bytes(); let kawa = &mut self.zero; kawa.storage.space()[0..pri.len()].copy_from_slice(pri); kawa.storage.fill(pri.len()); - match serializer::gen_settings(kawa.storage.space(), &self.settings) { + match serializer::gen_settings(kawa.storage.space(), &self.local_settings) { Ok((_, size)) => kawa.storage.fill(size), - Err(e) => panic!("{e:?}"), + Err(e) => { + println!("could not serialize SettingsFrame: {e:?}"); + return MuxResult::CloseSession(H2Error::InternalError); + } }; self.state = H2State::ClientSettings; @@ -307,7 +331,7 @@ impl ConnectionH2 { MuxResult::Continue } (H2State::ClientSettings, Position::Client(_)) => { - println!("Sent preface and settings"); + println_!("Sent preface and settings"); self.state = H2State::ServerSettings; self.readiness.interest.remove(Ready::WRITABLE); self.expect_read = Some((H2StreamId::Zero, 9)); @@ -341,7 +365,7 @@ impl ConnectionH2 { Position::Server => { // mark stream as reusable stream.active = false; - println!("Recycle stream: {global_stream_id}"); + println_!("Recycle stream: {global_stream_id}"); endpoint.end_stream( stream.token.unwrap(), global_stream_id, @@ -361,7 +385,7 @@ impl ConnectionH2 { let mut priorities = self.streams.keys().collect::>(); priorities.sort(); - println!("PRIORITIES: {priorities:?}"); + println_!("PRIORITIES: {priorities:?}"); 'outer: for stream_id in priorities { let global_stream_id = *self.streams.get(stream_id).unwrap(); let stream = &mut context.streams[global_stream_id]; @@ -369,7 +393,7 @@ impl ConnectionH2 { if kawa.is_main_phase() { converter.stream_id = *stream_id; kawa.prepare(&mut converter); - kawa::debug_kawa(kawa); + debug_kawa(kawa); while !kawa.out.is_empty() { let bufs = kawa.as_io_slice(); let (size, status) = self.socket.socket_write_vectored(&bufs); @@ -386,7 +410,7 @@ impl ConnectionH2 { Position::Server => { // mark stream as reusable stream.active = false; - println!("Recycle stream: {global_stream_id}"); + println_!("Recycle stream: {global_stream_id}"); endpoint.end_stream( stream.token.unwrap(), global_stream_id, @@ -411,15 +435,20 @@ impl ConnectionH2 { } } - pub fn create_stream(&mut self, stream_id: StreamId, context: &mut Context) -> GlobalStreamId { - let global_stream_id = context - .create_stream(Ulid::generate(), self.settings.settings_initial_window_size) - .unwrap(); - if stream_id > self.last_stream_id { + pub fn create_stream( + &mut self, + stream_id: StreamId, + context: &mut Context, + ) -> Option { + let global_stream_id = context.create_stream( + Ulid::generate(), + self.peer_settings.settings_initial_window_size, + )?; + if (stream_id >> 1) > self.last_stream_id { self.last_stream_id = stream_id >> 1; } self.streams.insert(stream_id, global_stream_id); - global_stream_id + Some(global_stream_id) } pub fn new_stream_id(&mut self) -> StreamId { @@ -434,18 +463,21 @@ impl ConnectionH2 { where E: Endpoint, { - println!("{frame:#?}"); + println_!("{frame:#?}"); match frame { Frame::Data(data) => { let mut slice = data.payload; - let global_stream_id = *self.streams.get(&data.stream_id).unwrap(); + let global_stream_id = match self.streams.get(&data.stream_id) { + Some(global_stream_id) => *global_stream_id, + None => return MuxResult::CloseSession(H2Error::ProtocolError), + }; let stream = &mut context.streams[global_stream_id]; let kawa = stream.rbuffer(&self.position); slice.start += kawa.storage.head as u32; kawa.storage.head += slice.len(); let buffer = kawa.storage.buffer(); let payload = slice.data(buffer); - println!("{:?}", unsafe { from_utf8_unchecked(payload) }); + println_!("{:?}", unsafe { from_utf8_unchecked(payload) }); kawa.push_block(kawa::Block::Chunk(kawa::Chunk { data: kawa::Store::Slice(slice), })); @@ -464,6 +496,7 @@ impl ConnectionH2 { todo!(); // self.state = H2State::Continuation } + // can this fail? let global_stream_id = *self.streams.get(&headers.stream_id).unwrap(); let kawa = &mut self.zero; let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); @@ -476,7 +509,7 @@ impl ConnectionH2 { &mut self.decoder, parts.context, ); - kawa::debug_kawa(parts.rbuffer); + debug_kawa(parts.rbuffer); match self.position { Position::Client(_) => endpoint .readiness_mut(stream.token.unwrap()) @@ -487,21 +520,25 @@ impl ConnectionH2 { } Frame::PushPromise(push_promise) => match self.position { Position::Client(_) => { - todo!("if enabled forward the push") + if self.local_settings.settings_enable_push { + todo!("forward the push") + } else { + return MuxResult::CloseSession(H2Error::ProtocolError); + } } Position::Server => { - println!("A client should not push promises"); - return MuxResult::CloseSession; + println_!("A client should not push promises"); + return MuxResult::CloseSession(H2Error::ProtocolError); } }, Frame::Priority(priority) => (), Frame::RstStream(rst_stream) => { - println!( + println_!( "RstStream({} -> {})", rst_stream.error_code, error_code_to_str(rst_stream.error_code) ); - // context.streams.get(priority.stream_id).close() + self.streams.remove(&rst_stream.stream_id); } Frame::Settings(settings) => { if settings.ack { @@ -509,16 +546,16 @@ impl ConnectionH2 { } for setting in settings.settings { match setting.identifier { - 1 => self.settings.settings_header_table_size = setting.value, - 2 => self.settings.settings_enable_push = setting.value == 1, - 3 => self.settings.settings_max_concurrent_streams = setting.value, - 4 => self.settings.settings_initial_window_size = setting.value, - 5 => self.settings.settings_max_frame_size = setting.value, - 6 => self.settings.settings_max_header_list_size = setting.value, - other => panic!("setting_id: {other}"), + 1 => self.peer_settings.settings_header_table_size = setting.value, + 2 => self.peer_settings.settings_enable_push = setting.value == 1, + 3 => self.peer_settings.settings_max_concurrent_streams = setting.value, + 4 => self.peer_settings.settings_initial_window_size = setting.value, + 5 => self.peer_settings.settings_max_frame_size = setting.value, + 6 => self.peer_settings.settings_max_header_list_size = setting.value, + other => println!("unknown setting_id: {other}, we MUST ignore this"), } } - println!("{:#?}", self.settings); + println_!("{:#?}", self.peer_settings); let kawa = &mut self.zero; kawa.storage.space()[0..serializer::SETTINGS_ACKNOWLEDGEMENT.len()] @@ -534,25 +571,31 @@ impl ConnectionH2 { let kawa = &mut self.zero; match serializer::gen_ping_acknolegment(kawa.storage.space(), &ping.payload) { Ok((_, size)) => kawa.storage.fill(size), - Err(e) => panic!("could not serialize PingFrame: {e:?}"), + Err(e) => { + println!("could not serialize PingFrame: {e:?}"); + return MuxResult::CloseSession(H2Error::InternalError); + } }; self.readiness.interest.insert(Ready::WRITABLE); self.readiness.interest.remove(Ready::READABLE); self.expect_write = Some(H2StreamId::Zero); } Frame::GoAway(goaway) => { - println!( + println_!( "GoAway({} -> {})", goaway.error_code, error_code_to_str(goaway.error_code) ); - return MuxResult::CloseSession; + todo!(); } Frame::WindowUpdate(update) => { if update.stream_id == 0 { self.window += update.increment; } else { - let global_stream_id = *self.streams.get(&update.stream_id).unwrap(); + let global_stream_id = match self.streams.get(&update.stream_id) { + Some(global_stream_id) => *global_stream_id, + None => return MuxResult::CloseSession(H2Error::ProtocolError), + }; context.streams[global_stream_id].window += update.increment as i32; } } @@ -571,7 +614,7 @@ impl ConnectionH2 { Position::Server => unreachable!(), } for global_stream_id in self.streams.values() { - println!("end stream: {global_stream_id}"); + println_!("end stream: {global_stream_id}"); endpoint.end_stream( context.streams[*global_stream_id].token.unwrap(), *global_stream_id, @@ -582,19 +625,27 @@ impl ConnectionH2 { pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { let stream_context = &mut context.streams[stream].context; - println!("end H2 stream {stream}: {stream_context:#?}"); + println_!("end H2 stream {stream}: {stream_context:#?}"); + let mut found = false; for (stream_id, global_stream_id) in &self.streams { if *global_stream_id == stream { let id = *stream_id; self.streams.remove(&id); - return; + found = true; + break; } } - panic!(); + if !found { + panic!(); + } + match self.position { + Position::Client(_) => {} + Position::Server => todo!(), + } } pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { - println!("start new H2 stream {stream} {:?}", self.readiness); + println_!("start new H2 stream {stream} {:?}", self.readiness); let stream_id = self.new_stream_id(); self.streams.insert(stream_id, stream); self.readiness.interest.insert(Ready::WRITABLE); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 0d4a65a73..efd148a54 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -23,7 +23,11 @@ use crate::{ pool::{Checkout, Pool}, protocol::{ http::editor::HttpContext, - mux::{h1::ConnectionH1, h2::ConnectionH2}, + mux::{ + h1::ConnectionH1, + h2::{ConnectionH2, H2Settings, H2State, H2StreamId}, + parser::H2Error, + }, SessionState, }, router::Route, @@ -32,7 +36,16 @@ use crate::{ RetrieveClusterError, SessionMetrics, SessionResult, StateResult, }; -use self::h2::{H2Settings, H2State, H2StreamId}; +#[macro_export] +macro_rules! println_ { + ($($t:expr),*) => { + println!($($t),*) + // $(let _ = &$t;)* + }; +} +fn debug_kawa(_kawa: &GenericHttpStream) { + // kawa::debug_kawa(_kawa); +} /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer type GenericHttpStream = kawa::Kawa; @@ -55,7 +68,7 @@ pub enum BackendStatus { pub enum MuxResult { Continue, - CloseSession, + CloseSession(H2Error), Close(GlobalStreamId), Connect(GlobalStreamId), } @@ -117,7 +130,8 @@ impl Connection { state: H2State::ClientPreface, expect_read: Some((H2StreamId::Zero, 24 + 9)), expect_write: None, - settings: H2Settings::default(), + local_settings: H2Settings::default(), + peer_settings: H2Settings::default(), zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, decoder: hpack::Decoder::new(), @@ -144,7 +158,8 @@ impl Connection { state: H2State::ClientPreface, expect_read: None, expect_write: None, - settings: H2Settings::default(), + local_settings: H2Settings::default(), + peer_settings: H2Settings::default(), zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), window: 1 << 16, decoder: hpack::Decoder::new(), @@ -281,7 +296,7 @@ fn update_readiness_after_read( status: SocketResult, readiness: &mut Readiness, ) -> bool { - println!(" size={size}, status={status:?}"); + println_!(" size={size}, status={status:?}"); match status { SocketResult::Continue => {} SocketResult::Closed | SocketResult::Error => { @@ -303,7 +318,7 @@ fn update_readiness_after_write( status: SocketResult, readiness: &mut Readiness, ) -> bool { - println!(" size={size}, status={status:?}"); + println_!(" size={size}, status={status:?}"); match status { SocketResult::Continue => {} SocketResult::Closed | SocketResult::Error => { @@ -449,7 +464,7 @@ impl Context { pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option { for (stream_id, stream) in self.streams.iter_mut().enumerate() { if !stream.active { - println!("Reuse stream: {stream_id}"); + println_!("Reuse stream: {stream_id}"); stream.window = window as i32; stream.context = temporary_http_context(request_id); stream.back.clear(); @@ -509,7 +524,9 @@ impl Router { let mut reuse_connecting = true; for (token, backend) in &self.backends { match (h2, reuse_connecting, backend.position()) { - (_, _, Position::Server) => panic!("Backend connection behaves like a server"), + (_, _, Position::Server) => { + unreachable!("Backend connection behaves like a server") + } (_, _, Position::Client(BackendStatus::Disconnecting)) => {} (true, _, Position::Client(BackendStatus::Connected(old_cluster_id))) => { @@ -525,8 +542,10 @@ impl Router { } } (true, false, Position::Client(BackendStatus::Connecting(_))) => {} - (true, _, Position::Client(BackendStatus::KeepAlive(_))) => { - panic!("ConnectionH2 behaves like H1") + (true, _, Position::Client(BackendStatus::KeepAlive(old_cluster_id))) => { + if *old_cluster_id == cluster_id { + unreachable!("ConnectionH2 behaves like H1") + } } (false, _, Position::Client(BackendStatus::KeepAlive(old_cluster_id))) => { @@ -541,10 +560,10 @@ impl Router { | (false, _, Position::Client(BackendStatus::Connecting(_))) => {} } } - println!("connect: {cluster_id} (stick={frontend_should_stick}, h2={h2}) -> (reuse={reuse_token:?})"); + println_!("connect: {cluster_id} (stick={frontend_should_stick}, h2={h2}) -> (reuse={reuse_token:?})"); let token = if let Some(token) = reuse_token { - println!("reused backend: {:#?}", self.backends.get(&token).unwrap()); + println_!("reused backend: {:#?}", self.backends.get(&token).unwrap()); token } else { let mut socket = self.backend_from_request( @@ -575,7 +594,10 @@ impl Router { } let connection = if h2 { - Connection::new_h2_client(socket, cluster_id, context.pool.clone()).unwrap() + match Connection::new_h2_client(socket, cluster_id, context.pool.clone()) { + Some(connection) => connection, + None => return Err(BackendConnectionError::MaxBuffers), + } } else { Connection::new_h1_client(socket, cluster_id) }; @@ -601,7 +623,9 @@ impl Router { let (host, uri, method) = match context.extract_route() { Ok(tuple) => tuple, Err(cluster_error) => { - panic!("{}", cluster_error); + // we are past kawa parsing if it succeeded this can't fail + // if the request was malformed it was caught by kawa and we sent a 400 + panic!("{cluster_error}"); // self.set_answer(DefaultAnswerStatus::Answer400, None); // return Err(cluster_error); } @@ -615,18 +639,18 @@ impl Router { let route = match route_result { Ok(route) => route, Err(frontend_error) => { - panic!("{}", frontend_error); + println!("{}", frontend_error); // self.set_answer(DefaultAnswerStatus::Answer404, None); - // return Err(RetrieveClusterError::RetrieveFrontend(frontend_error)); + return Err(RetrieveClusterError::RetrieveFrontend(frontend_error)); } }; let cluster_id = match route { Route::Cluster { id, h2 } => (id, h2), Route::Deny => { - panic!("Route::Deny"); + println!("Route::Deny"); // self.set_answer(DefaultAnswerStatus::Answer401, None); - // return Err(RetrieveClusterError::UnauthorizedRoute); + return Err(RetrieveClusterError::UnauthorizedRoute); } }; @@ -649,9 +673,9 @@ impl Router { proxy, ) .map_err(|backend_error| { - panic!("{backend_error}") + println!("{backend_error}"); // self.set_answer(DefaultAnswerStatus::Answer503, None); - // BackendConnectionError::Backend(backend_error) + BackendConnectionError::Backend(backend_error) })?; if frontend_should_stick { @@ -711,6 +735,10 @@ impl Mux { pub fn front_socket(&self) -> &TcpStream { self.frontend.socket() } + + fn goaway(&mut self, e: H2Error) { + todo!() + } } impl SessionState for Mux { @@ -727,17 +755,19 @@ impl SessionState for Mux { return SessionResult::Close; } - let context = &mut self.context; + let start = std::time::Instant::now(); + println_!("{start:?}"); while counter < max_loop_iterations { let mut dirty = false; + let context = &mut self.context; if self.frontend.readiness().filter_interest().is_readable() { match self .frontend .readable(context, EndpointClient(&mut self.router)) { MuxResult::Continue => (), - MuxResult::CloseSession => return SessionResult::Close, + MuxResult::CloseSession(e) => self.goaway(e), MuxResult::Close(_) => todo!(), MuxResult::Connect(stream_id) => { match self.router.connect( @@ -749,7 +779,7 @@ impl SessionState for Mux { ) { Ok(_) => (), Err(error) => { - println!("{error}"); + println_!("{error}"); } } } @@ -757,11 +787,12 @@ impl SessionState for Mux { dirty = true; } + let context = &mut self.context; let mut dead_backends = Vec::new(); for (token, backend) in self.router.backends.iter_mut() { let readiness = backend.readiness().filter_interest(); if readiness.is_hup() || readiness.is_error() { - println!("{token:?} -> {:?}", backend); + println_!("{token:?} -> {:?}", backend); backend.close(context, EndpointServer(&mut self.frontend)); dead_backends.push(*token); } @@ -777,7 +808,10 @@ impl SessionState for Mux { } match backend.writable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => (), - MuxResult::CloseSession => return SessionResult::Close, + MuxResult::CloseSession(e) => { + self.goaway(e); + break; + } MuxResult::Close(_) => todo!(), MuxResult::Connect(_) => unreachable!(), } @@ -787,7 +821,10 @@ impl SessionState for Mux { if readiness.is_readable() { match backend.readable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => (), - MuxResult::CloseSession => return SessionResult::Close, + MuxResult::CloseSession(e) => { + self.goaway(e); + break; + } MuxResult::Close(_) => todo!(), MuxResult::Connect(_) => unreachable!(), } @@ -798,17 +835,18 @@ impl SessionState for Mux { for token in &dead_backends { self.router.backends.remove(token); } - println!("FRONTEND: {:#?}", &self.frontend); - println!("BACKENDS: {:#?}", self.router.backends); + println_!("FRONTEND: {:#?}", &self.frontend); + println_!("BACKENDS: {:#?}", self.router.backends); } + let context = &mut self.context; if self.frontend.readiness().filter_interest().is_writable() { match self .frontend .writable(context, EndpointClient(&mut self.router)) { MuxResult::Continue => (), - MuxResult::CloseSession => return SessionResult::Close, + MuxResult::CloseSession(e) => self.goaway(e), MuxResult::Close(_) => todo!(), MuxResult::Connect(_) => unreachable!(), } @@ -824,7 +862,6 @@ impl SessionState for Mux { if counter == max_loop_iterations { incr!("http.infinite_loop.error"); - panic!(); return SessionResult::Close; } @@ -840,12 +877,12 @@ impl SessionState for Mux { } fn timeout(&mut self, token: Token, _metrics: &mut SessionMetrics) -> StateResult { - println!("MuxState::timeout({token:?})"); + println_!("MuxState::timeout({token:?})"); StateResult::CloseSession } fn cancel_timeouts(&mut self) { - println!("MuxState::cancel_timeouts"); + println_!("MuxState::cancel_timeouts"); } fn print_state(&self, context: &str) { @@ -866,15 +903,15 @@ impl SessionState for Mux { }; let mut b = [0; 1024]; let (size, status) = s.socket_read(&mut b); - println!("{size} {status:?} {:?}", &b[..size]); + println_!("{size} {status:?} {:?}", &b[..size]); for stream in &mut self.context.streams { for kawa in [&mut stream.front, &mut stream.back] { - kawa::debug_kawa(kawa); + debug_kawa(kawa); kawa.prepare(&mut kawa::h1::BlockConverter); let out = kawa.as_io_slice(); let mut writer = std::io::BufWriter::new(Vec::new()); let amount = writer.write_vectored(&out).unwrap(); - println!("amount: {amount}\n{}", unsafe { + println_!("amount: {amount}\n{}", unsafe { std::str::from_utf8_unchecked(writer.buffer()) }); } diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index eaca86a8f..a985f560f 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -2,11 +2,11 @@ use std::convert::From; use kawa::repr::Slice; use nom::{ - bytes::streaming::{tag, take}, + bytes::complete::{tag, take}, combinator::{complete, map, map_opt}, error::{ErrorKind, ParseError}, multi::many0, - number::streaming::{be_u16, be_u24, be_u32, be_u8}, + number::complete::{be_u16, be_u24, be_u32, be_u8}, sequence::tuple, Err, IResult, }; @@ -77,6 +77,11 @@ pub struct Error<'a> { #[derive(Clone, Debug, PartialEq)] pub enum InnerError { Nom(ErrorKind), + H2(H2Error), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum H2Error { NoError, ProtocolError, InternalError, @@ -97,6 +102,9 @@ impl<'a> Error<'a> { pub fn new(input: &'a [u8], error: InnerError) -> Error<'a> { Error { input, error } } + pub fn new_h2(input: &'a [u8], error: H2Error) -> Error<'a> { + Error { input, error: InnerError::H2(error) } + } } impl<'a> ParseError<&'a [u8]> for Error<'a> { @@ -226,7 +234,7 @@ pub fn frame_body<'a>( max_frame_size: u32, ) -> IResult<&'a [u8], Frame, Error<'a>> { if header.payload_len > max_frame_size { - return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); } let valid_stream_id = match header.frame_type { @@ -241,7 +249,7 @@ pub fn frame_body<'a>( }; if !valid_stream_id { - return Err(Err::Failure(Error::new(i, InnerError::ProtocolError))); + return Err(Err::Failure(Error::new_h2(i, H2Error::ProtocolError))); } let f = match header.frame_type { @@ -249,13 +257,13 @@ pub fn frame_body<'a>( FrameType::Headers => headers_frame(i, header)?, FrameType::Priority => { if header.payload_len != 5 { - return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); } priority_frame(i, header)? } FrameType::RstStream => { if header.payload_len != 4 { - return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); } rst_stream_frame(i, header)? } @@ -263,20 +271,20 @@ pub fn frame_body<'a>( FrameType::Continuation => continuation_frame(i, header)?, FrameType::Settings => { if header.payload_len % 6 != 0 { - return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); } settings_frame(i, header)? } FrameType::Ping => { if header.payload_len != 8 { - return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); } ping_frame(i, header)? } FrameType::GoAway => goaway_frame(i, header)?, FrameType::WindowUpdate => { if header.payload_len != 4 { - return Err(Err::Failure(Error::new(i, InnerError::FrameSizeError))); + return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); } window_update_frame(i, header)? } @@ -306,7 +314,7 @@ pub fn data_frame<'a>( }; if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { - return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); } let (_, payload) = take(i.len() - pad_length.unwrap_or(0) as usize)(i)?; @@ -369,7 +377,7 @@ pub fn headers_frame<'a>( }; if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { - return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); } let (_, header_block_fragment) = take(i.len() - pad_length.unwrap_or(0) as usize)(i)?; @@ -486,7 +494,7 @@ pub fn push_promise_frame<'a>( }; if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { - return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); } let (i, promised_stream_id) = be_u32(i)?; @@ -559,7 +567,7 @@ pub fn window_update_frame<'a>( //FIXME: if stream id is 0, trat it as connection error? if increment == 0 { - return Err(Err::Failure(Error::new(input, InnerError::ProtocolError))); + return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); } Ok(( diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index 02df82d69..cb047ca3e 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -45,7 +45,7 @@ pub fn handle_header( } else if compare_no_case(&k, b":scheme") { scheme = val; } else if compare_no_case(&k, b"cookie") { - panic!("cookies should be split in pairs"); + todo!("cookies should be split in pairs"); } else { if compare_no_case(&k, b"content-length") { let length = From 6d602f0947e49884544395f11eb27c352cbe7d6f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Sat, 9 Sep 2023 21:31:52 +0200 Subject: [PATCH 23/44] Error handling and connection retry: - Added StreamState to Stream to merge active and token field as well as adding a "Link" variant to represent a Stream asking for connection - Comment and implement start_stream and end_stream. In particular end_stream on a Server handles the error and reconnection logic - Add set_default_answer which produces a default Kawa answer - Add forcefully_terminate_answer which properly terminates H1 streams and sends RstStream on H2 streams - Add connection loop with max retries and error handling in ready Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 40 ++-- lib/src/protocol/mux/h1.rs | 85 +++++--- lib/src/protocol/mux/h2.rs | 140 +++++++------ lib/src/protocol/mux/mod.rs | 303 +++++++++++++++++++---------- lib/src/protocol/mux/parser.rs | 5 +- lib/src/protocol/mux/serializer.rs | 21 +- 6 files changed, 397 insertions(+), 197 deletions(-) diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 2a8c76477..0013f3b03 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -5,13 +5,14 @@ use kawa::{AsBuffer, Block, BlockConverter, Chunk, Flags, Kawa, Pair, StatusLine use crate::protocol::http::parser::compare_no_case; use super::{ - parser::{FrameHeader, FrameType}, - serializer::gen_frame_header, - StreamId, + parser::{FrameHeader, FrameType, H2Error}, + serializer::{gen_frame_header, gen_rst_stream}, + StreamId, StreamState, }; pub struct H2BlockConverter<'a> { pub stream_id: StreamId, + pub state: StreamState, pub encoder: &'a mut hpack::Encoder<'static>, pub out: Vec, } @@ -140,20 +141,25 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { .unwrap(); kawa.push_out(Store::from_slice(&header)); kawa.push_out(Store::Alloc(payload.into_boxed_slice(), 0)); - } - if end_stream { - let mut header = [0; 9]; - gen_frame_header( - &mut header, - &FrameHeader { - payload_len: 0, - frame_type: FrameType::Data, - flags: 1, - stream_id: self.stream_id, - }, - ) - .unwrap(); - kawa.push_out(Store::from_slice(&header)); + } else if end_stream { + if kawa.is_error() { + let mut frame = [0; 13]; + gen_rst_stream(&mut frame, self.stream_id, H2Error::InternalError).unwrap(); + kawa.push_out(Store::from_slice(&frame)); + } else { + let mut header = [0; 9]; + gen_frame_header( + &mut header, + &FrameHeader { + payload_len: 0, + frame_type: FrameType::Data, + flags: 1, + stream_id: self.stream_id, + }, + ) + .unwrap(); + kawa.push_out(Store::from_slice(&header)); + } } if end_header || end_stream { kawa.push_delimiter() diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 359698c00..edaed8c8b 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -3,8 +3,8 @@ use sozu_command::ready::Ready; use crate::{ println_, protocol::mux::{ - debug_kawa, update_readiness_after_read, update_readiness_after_write, BackendStatus, - Context, Endpoint, GlobalStreamId, MuxResult, Position, + debug_kawa, set_default_answer, update_readiness_after_read, update_readiness_after_write, + BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, Position, StreamState, forcefully_terminate_answer, }, socket::SocketHandler, Readiness, @@ -49,24 +49,43 @@ impl ConnectionH1 { kawa::h1::parse(kawa, parts.context); debug_kawa(kawa); if kawa.is_error() { - return MuxResult::Close(self.stream); + match self.position { + Position::Client(_) => { + let StreamState::Linked(token) = stream.state else { unreachable!() }; + let global_stream_id = self.stream; + self.readiness.interest.remove(Ready::ALL); + self.end_stream(global_stream_id, context); + endpoint.end_stream(token, global_stream_id, context); + } + Position::Server => { + set_default_answer(&mut stream.back, &mut self.readiness, 400); + stream.state = StreamState::Unlinked; + } + } + return MuxResult::Continue; } if kawa.is_terminated() { self.readiness.interest.remove(Ready::READABLE); } if was_initial && kawa.is_main_phase() { - self.requests += 1; - println_!("REQUESTS: {}", self.requests); match self.position { - Position::Client(_) => endpoint - .readiness_mut(stream.token.unwrap()) - .interest - .insert(Ready::WRITABLE), - Position::Server => return MuxResult::Connect(self.stream), + Position::Client(_) => { + let StreamState::Linked(token) = stream.state else { unreachable!() }; + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } + Position::Server => { + self.requests += 1; + println_!("REQUESTS: {}", self.requests); + stream.state = StreamState::Link + } }; } MuxResult::Continue } + pub fn writable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where E: Endpoint, @@ -95,9 +114,10 @@ impl ConnectionH1 { stream.back.clear(); stream.back.storage.clear(); stream.front.clear(); - // do not clear stream.front because of H1 pipelining - let token = stream.token.take().unwrap(); - endpoint.end_stream(token, self.stream, context); + // do not clear stream.front.storage because of H1 pipelining + if let StreamState::Linked(token) = stream.state { + endpoint.end_stream(token, self.stream, context); + } } } } @@ -114,21 +134,20 @@ impl ConnectionH1 { println_!("close detached client ConnectionH1"); return; } - Position::Client(BackendStatus::Connecting(_)) => todo!("reconnect"), - Position::Client(BackendStatus::Connected(_)) => {} + Position::Client(BackendStatus::Connecting(_)) + | Position::Client(BackendStatus::Connected(_)) => {} Position::Server => unreachable!(), } - endpoint.end_stream( - context.streams[self.stream].token.unwrap(), - self.stream, - context, - ) + // reconnection is handled by the server + let StreamState::Linked(token) = context.streams[self.stream].state else {unreachable!()}; + endpoint.end_stream(token, self.stream, context) } pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { assert_eq!(stream, self.stream); - let stream_context = &mut context.streams[stream].context; - println_!("end H1 stream {stream}: {stream_context:#?}"); + let stream = &mut context.streams[stream]; + let stream_context = &mut stream.context; + println_!("end H1 stream {}: {stream_context:#?}", self.stream); self.stream = usize::MAX; let mut owned_position = Position::Server; std::mem::swap(&mut owned_position, &mut self.position); @@ -143,7 +162,27 @@ impl ConnectionH1 { } Position::Client(BackendStatus::KeepAlive(_)) | Position::Client(BackendStatus::Disconnecting) => unreachable!(), - Position::Server => todo!(), + Position::Server => match (stream.front.consumed, stream.back.is_main_phase()) { + (true, true) => { + // we have a "forwardable" answer from the back + // if the answer is not terminated we send an RstStream to properly clean the stream + // if it is terminated, we finish the transfer, the backend is not necessary anymore + if !stream.back.is_terminated() { + forcefully_terminate_answer(&mut stream.back, &mut self.readiness); + } + stream.state = StreamState::Unlinked; + } + (true, false) => { + set_default_answer(&mut stream.back, &mut self.readiness, 502); + stream.state = StreamState::Unlinked; + } + (false, false) => { + // we do not have an answer, but the request is untouched so we can retry + println!("H1 RECONNECT"); + stream.state = StreamState::Link; + } + (false, true) => unreachable!(), + }, } } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 1f94f51a3..4803febbd 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -10,7 +10,7 @@ use crate::{ parser::{self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error}, pkawa, serializer, update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, Endpoint, GenericHttpStream, GlobalStreamId, MuxResult, Position, - StreamId, + StreamId, StreamState, set_default_answer, forcefully_terminate_answer, }, socket::SocketHandler, Readiness, @@ -359,18 +359,17 @@ impl ConnectionH2 { } } self.expect_write = None; - if kawa.is_terminated() && kawa.is_completed() { + if (kawa.is_terminated() || kawa.is_error()) && kawa.is_completed() { match self.position { Position::Client(_) => {} Position::Server => { // mark stream as reusable - stream.active = false; println_!("Recycle stream: {global_stream_id}"); - endpoint.end_stream( - stream.token.unwrap(), - global_stream_id, - context, - ); + let mut state = StreamState::Recycle; + std::mem::swap(&mut stream.state, &mut state); + if let StreamState::Linked(token) = state { + endpoint.end_stream(token, global_stream_id, context); + } dead_streams.push(stream_id); } } @@ -379,6 +378,7 @@ impl ConnectionH2 { let mut converter = converter::H2BlockConverter { stream_id: 0, + state: StreamState::Idle, encoder: &mut self.encoder, out: Vec::new(), }; @@ -389,35 +389,35 @@ impl ConnectionH2 { 'outer: for stream_id in priorities { let global_stream_id = *self.streams.get(stream_id).unwrap(); let stream = &mut context.streams[global_stream_id]; + converter.state = stream.state; let kawa = stream.wbuffer(&self.position); - if kawa.is_main_phase() { + if kawa.is_main_phase() || kawa.is_error() { converter.stream_id = *stream_id; kawa.prepare(&mut converter); debug_kawa(kawa); - while !kawa.out.is_empty() { - let bufs = kawa.as_io_slice(); - let (size, status) = self.socket.socket_write_vectored(&bufs); - kawa.consume(size); - if update_readiness_after_write(size, status, &mut self.readiness) { - self.expect_write = - Some(H2StreamId::Other(*stream_id, global_stream_id)); - break 'outer; - } + } + while !kawa.out.is_empty() { + let bufs = kawa.as_io_slice(); + let (size, status) = self.socket.socket_write_vectored(&bufs); + kawa.consume(size); + if update_readiness_after_write(size, status, &mut self.readiness) { + self.expect_write = + Some(H2StreamId::Other(*stream_id, global_stream_id)); + break 'outer; } - if kawa.is_terminated() && kawa.is_completed() { - match self.position { - Position::Client(_) => {} - Position::Server => { - // mark stream as reusable - stream.active = false; - println_!("Recycle stream: {global_stream_id}"); - endpoint.end_stream( - stream.token.unwrap(), - global_stream_id, - context, - ); - dead_streams.push(*stream_id); + } + if (kawa.is_terminated() || kawa.is_error()) && kawa.is_completed() { + match self.position { + Position::Client(_) => {} + Position::Server => { + // mark stream as reusable + println_!("Recycle stream: {global_stream_id}"); + let mut state = StreamState::Recycle; + std::mem::swap(&mut stream.state, &mut state); + if let StreamState::Linked(token) = state { + endpoint.end_stream(token, global_stream_id, context); } + dead_streams.push(*stream_id); } } } @@ -511,11 +511,14 @@ impl ConnectionH2 { ); debug_kawa(parts.rbuffer); match self.position { - Position::Client(_) => endpoint - .readiness_mut(stream.token.unwrap()) - .interest - .insert(Ready::WRITABLE), - Position::Server => return MuxResult::Connect(global_stream_id), + Position::Client(_) => { + let StreamState::Linked(token) = stream.state else { unreachable!() }; + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } + Position::Server => stream.state = StreamState::Link, }; } Frame::PushPromise(push_promise) => match self.position { @@ -609,38 +612,61 @@ impl ConnectionH2 { E: Endpoint, { match self.position { - Position::Client(BackendStatus::Connecting(_)) => todo!("reconnect"), - Position::Client(_) => {} + Position::Client(BackendStatus::Connected(_)) + | Position::Client(BackendStatus::Connecting(_)) => {} + Position::Client(BackendStatus::Disconnecting) + | Position::Client(BackendStatus::KeepAlive(_)) => unreachable!(), Position::Server => unreachable!(), } + // reconnection is handled by the server for each stream separately for global_stream_id in self.streams.values() { println_!("end stream: {global_stream_id}"); - endpoint.end_stream( - context.streams[*global_stream_id].token.unwrap(), - *global_stream_id, - context, - ) + let StreamState::Linked(token) = context.streams[*global_stream_id].state else { unreachable!() }; + endpoint.end_stream(token, *global_stream_id, context) } } pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { let stream_context = &mut context.streams[stream].context; println_!("end H2 stream {stream}: {stream_context:#?}"); - let mut found = false; - for (stream_id, global_stream_id) in &self.streams { - if *global_stream_id == stream { - let id = *stream_id; - self.streams.remove(&id); - found = true; - break; - } - } - if !found { - panic!(); - } match self.position { - Position::Client(_) => {} - Position::Server => todo!(), + Position::Client(_) => { + for (stream_id, global_stream_id) in &self.streams { + if *global_stream_id == stream { + let id = *stream_id; + self.streams.remove(&id); + return; + } + } + unreachable!() + } + Position::Server => { + let stream = &mut context.streams[stream]; + match (stream.front.consumed, stream.back.is_main_phase()) { + (_, true) => { + // front might not have been consumed (in case of PushPromise) + // we have a "forwardable" answer from the back + // if the answer is not terminated we send an RstStream to properly clean the stream + // if it is terminated, we finish the transfer, the backend is not necessary anymore + if !stream.back.is_terminated() { + forcefully_terminate_answer(&mut stream.back, &mut self.readiness); + } + stream.state = StreamState::Unlinked + } + (true, false) => { + // we do not have an answer, but the request has already been partially consumed + // so we can't retry, send a 502 bad gateway instead + // note: it might be possible to send a RstStream with an adequate error code + set_default_answer(&mut stream.back, &mut self.readiness, 502); + stream.state = StreamState::Unlinked; + } + (false, false) => { + // we do not have an answer, but the request is untouched so we can retry + println!("H2 RECONNECT"); + stream.state = StreamState::Link + } + } + } } } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index efd148a54..119926e02 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -31,6 +31,7 @@ use crate::{ SessionState, }, router::Route, + server::CONN_RETRIES, socket::{FrontRustls, SocketHandler, SocketResult}, BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, RetrieveClusterError, SessionMetrics, SessionResult, StateResult, @@ -39,12 +40,13 @@ use crate::{ #[macro_export] macro_rules! println_ { ($($t:expr),*) => { + // print!("{}:{} ", file!(), line!()); println!($($t),*) // $(let _ = &$t;)* }; } fn debug_kawa(_kawa: &GenericHttpStream) { - // kawa::debug_kawa(_kawa); + kawa::debug_kawa(_kawa); } /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer @@ -52,6 +54,51 @@ type GenericHttpStream = kawa::Kawa; type StreamId = u32; type GlobalStreamId = usize; +/// Replace the content of the kawa buffer with a default Sozu answer for a given status code +fn set_default_answer(kawa: &mut GenericHttpStream, readiness: &mut Readiness, code: u16) { + kawa.clear(); + kawa.storage.clear(); + kawa.detached.status_line = kawa::StatusLine::Response { + version: kawa::Version::V20, + code, + status: kawa::Store::from_string(code.to_string()), + reason: kawa::Store::Static(b"Sozu Default Answer"), + }; + kawa.push_block(kawa::Block::StatusLine); + kawa.push_block(kawa::Block::Header(kawa::Pair { + key: kawa::Store::Static(b"Cache-Control"), + val: kawa::Store::Static(b"no-cache"), + })); + kawa.push_block(kawa::Block::Header(kawa::Pair { + key: kawa::Store::Static(b"Connection"), + val: kawa::Store::Static(b"close"), + })); + kawa.push_block(kawa::Block::Header(kawa::Pair { + key: kawa::Store::Static(b"Content-Length"), + val: kawa::Store::Static(b"0"), + })); + kawa.push_block(kawa::Block::Flags(kawa::Flags { + end_body: false, + end_chunk: false, + end_header: true, + end_stream: true, + })); + kawa.parsing_phase = kawa::ParsingPhase::Terminated; + readiness.interest.insert(Ready::WRITABLE); +} + +fn forcefully_terminate_answer(kawa: &mut GenericHttpStream, readiness: &mut Readiness) { + kawa.push_block(kawa::Block::Flags(kawa::Flags { + end_body: false, + end_chunk: false, + end_header: false, + end_stream: true, + })); + kawa.parsing_phase = kawa::ParsingPhase::Terminated; + debug_kawa(kawa); + readiness.interest.insert(Ready::WRITABLE); +} + #[derive(Debug)] pub enum Position { Client(BackendStatus), @@ -69,8 +116,6 @@ pub enum BackendStatus { pub enum MuxResult { Continue, CloseSession(H2Error), - Close(GlobalStreamId), - Connect(GlobalStreamId), } #[derive(Debug)] @@ -82,7 +127,15 @@ pub enum Connection { pub trait Endpoint { fn readiness(&self, token: Token) -> &Readiness; fn readiness_mut(&mut self, token: Token) -> &mut Readiness; + /// If end_stream is called on a client it means the stream has PROPERLY finished, + /// the server has completed serving the response and informs the endpoint that this stream won't be used anymore. + /// If end_stream is called on a server it means the stream was BROKEN, the client was most likely disconnected or encountered an error + /// it is for the server to decide if the stream can be retried or an error should be sent. It should be GUARANTEED that all bytes from + /// the backend were read. However it is almost certain that all bytes were not already sent to the client. fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context); + /// If start_stream is called on a client it means the stream should be attached to this endpoint, + /// the stream might be recovering from a disconnection, in any case at this point its response MUST be empty. + /// If the start_stream is called on a H2 server it means the stream is a server push and its request MUST be empty. fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context); } @@ -365,11 +418,25 @@ fn update_readiness_after_write( // Closed, // } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StreamState { + Idle, + /// the Stream is asking for connection, this will trigger a call to connect + Link, + /// the Stream is linked to a Client (note that the client might not be connected) + Linked(Token), + /// the Stream was linked to a Client, but the connection closed, the client was removed + /// and this Stream could not be retried (it should be terminated) + Unlinked, + /// the Stream is unlinked and can be reused + Recycle, +} + pub struct Stream { // pub request_id: Ulid, - pub active: bool, pub window: i32, - pub token: Option, + pub attempts: u8, + pub state: StreamState, front: GenericHttpStream, back: GenericHttpStream, pub context: HttpContext, @@ -419,9 +486,9 @@ impl Stream { None => return None, }; Some(Self { - active: true, + state: StreamState::Idle, + attempts: 0, window: window as i32, - token: None, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), context: temporary_http_context(request_id), @@ -463,16 +530,16 @@ pub struct Context { impl Context { pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option { for (stream_id, stream) in self.streams.iter_mut().enumerate() { - if !stream.active { + if stream.state == StreamState::Recycle { println_!("Reuse stream: {stream_id}"); + stream.state = StreamState::Idle; + stream.attempts = 0; stream.window = window as i32; stream.context = temporary_http_context(request_id); stream.back.clear(); stream.back.storage.clear(); stream.front.clear(); stream.front.storage.clear(); - stream.token = None; - stream.active = true; return Some(stream_id); } } @@ -506,7 +573,12 @@ impl Router { let stream = &mut context.streams[stream_id]; // when reused, a stream should be detached from its old connection, if not we could end // with concurrent connections on a single endpoint - assert!(stream.token.is_none()); + assert!(matches!(stream.state, StreamState::Link)); + if stream.attempts >= CONN_RETRIES { + return Err(BackendConnectionError::MaxConnectionRetries(None)); + } + stream.attempts += 1; + let stream_context = &mut stream.context; let (cluster_id, h2) = self .route_from_request(stream_context, proxy.clone()) @@ -606,7 +678,7 @@ impl Router { }; // link stream to backend - stream.token = Some(token); + stream.state = StreamState::Linked(token); // link backend to stream self.backends .get_mut(&token) @@ -626,8 +698,6 @@ impl Router { // we are past kawa parsing if it succeeded this can't fail // if the request was malformed it was caught by kawa and we sent a 400 panic!("{cluster_error}"); - // self.set_answer(DefaultAnswerStatus::Answer400, None); - // return Err(cluster_error); } }; @@ -757,112 +827,149 @@ impl SessionState for Mux { let start = std::time::Instant::now(); println_!("{start:?}"); - while counter < max_loop_iterations { - let mut dirty = false; + loop { + loop { + let context = &mut self.context; + if self.frontend.readiness().filter_interest().is_readable() { + match self + .frontend + .readable(context, EndpointClient(&mut self.router)) + { + MuxResult::Continue => (), + MuxResult::CloseSession(e) => self.goaway(e), + } + } - let context = &mut self.context; - if self.frontend.readiness().filter_interest().is_readable() { - match self - .frontend - .readable(context, EndpointClient(&mut self.router)) - { - MuxResult::Continue => (), - MuxResult::CloseSession(e) => self.goaway(e), - MuxResult::Close(_) => todo!(), - MuxResult::Connect(stream_id) => { - match self.router.connect( - stream_id, - context, - session.clone(), - proxy.clone(), - metrics, - ) { - Ok(_) => (), - Err(error) => { - println_!("{error}"); + let mut all_backends_readiness_are_empty = true; + let context = &mut self.context; + let mut dead_backends = Vec::new(); + for (token, backend) in self.router.backends.iter_mut() { + let readiness = backend.readiness_mut(); + let dead = readiness.filter_interest().is_hup() + || readiness.filter_interest().is_error(); + if dead { + println_!("Backend({token:?}) -> {readiness:?}"); + readiness.event.remove(Ready::WRITABLE); + } + + if backend.readiness().filter_interest().is_writable() { + let mut owned_position = Position::Server; + let position = backend.position_mut(); + std::mem::swap(&mut owned_position, position); + match owned_position { + Position::Client(BackendStatus::Connecting(cluster_id)) => { + *position = Position::Client(BackendStatus::Connected(cluster_id)); + } + _ => *position = owned_position, + } + match backend.writable(context, EndpointServer(&mut self.frontend)) { + MuxResult::Continue => (), + MuxResult::CloseSession(e) => { + self.goaway(e); + break; } } } - } - dirty = true; - } - let context = &mut self.context; - let mut dead_backends = Vec::new(); - for (token, backend) in self.router.backends.iter_mut() { - let readiness = backend.readiness().filter_interest(); - if readiness.is_hup() || readiness.is_error() { - println_!("{token:?} -> {:?}", backend); - backend.close(context, EndpointServer(&mut self.frontend)); - dead_backends.push(*token); - } - if readiness.is_writable() { - let mut owned_position = Position::Server; - let position = backend.position_mut(); - std::mem::swap(&mut owned_position, position); - match owned_position { - Position::Client(BackendStatus::Connecting(cluster_id)) => { - *position = Position::Client(BackendStatus::Connected(cluster_id)); + if backend.readiness().filter_interest().is_readable() { + match backend.readable(context, EndpointServer(&mut self.frontend)) { + MuxResult::Continue => (), + MuxResult::CloseSession(e) => { + self.goaway(e); + break; + } } - _ => *position = owned_position, } - match backend.writable(context, EndpointServer(&mut self.frontend)) { - MuxResult::Continue => (), - MuxResult::CloseSession(e) => { - self.goaway(e); - break; - } - MuxResult::Close(_) => todo!(), - MuxResult::Connect(_) => unreachable!(), + + if dead && !backend.readiness().filter_interest().is_readable() { + println_!("Closing {:#?}", backend); + backend.close(context, EndpointServer(&mut self.frontend)); + dead_backends.push(*token); } - dirty = true; + + if !backend.readiness().filter_interest().is_empty() { + all_backends_readiness_are_empty = false; + } + } + if !dead_backends.is_empty() { + for token in &dead_backends { + self.router.backends.remove(token); + } + println_!("FRONTEND: {:#?}", &self.frontend); + println_!("BACKENDS: {:#?}", self.router.backends); } - if readiness.is_readable() { - match backend.readable(context, EndpointServer(&mut self.frontend)) { + let context = &mut self.context; + if self.frontend.readiness().filter_interest().is_writable() { + match self + .frontend + .writable(context, EndpointClient(&mut self.router)) + { MuxResult::Continue => (), - MuxResult::CloseSession(e) => { - self.goaway(e); - break; - } - MuxResult::Close(_) => todo!(), - MuxResult::Connect(_) => unreachable!(), + MuxResult::CloseSession(e) => self.goaway(e), } - dirty = true; } - } - if !dead_backends.is_empty() { - for token in &dead_backends { - self.router.backends.remove(token); + + if self.frontend.readiness().filter_interest().is_empty() + && all_backends_readiness_are_empty + { + break; + } + + counter += 1; + if counter >= max_loop_iterations { + incr!("http.infinite_loop.error"); + return SessionResult::Close; } - println_!("FRONTEND: {:#?}", &self.frontend); - println_!("BACKENDS: {:#?}", self.router.backends); } let context = &mut self.context; - if self.frontend.readiness().filter_interest().is_writable() { - match self - .frontend - .writable(context, EndpointClient(&mut self.router)) - { - MuxResult::Continue => (), - MuxResult::CloseSession(e) => self.goaway(e), - MuxResult::Close(_) => todo!(), - MuxResult::Connect(_) => unreachable!(), + let front_readiness = self.frontend.readiness_mut(); + let mut dirty = false; + for stream_id in 0..context.streams.len() { + if context.streams[stream_id].state == StreamState::Link { + dirty = true; + match self.router.connect( + stream_id, + context, + session.clone(), + proxy.clone(), + metrics, + ) { + Ok(_) => {} + Err(error) => { + println_!("Connection error: {error}"); + let kawa = &mut context.streams[stream_id].back; + use BackendConnectionError as BE; + match error { + BE::Backend(BackendError::NoBackendForCluster(_)) + | BE::MaxConnectionRetries(_) + | BE::MaxSessionsMemory + | BE::MaxBuffers => { + set_default_answer(kawa, front_readiness, 503); + } + BE::RetrieveClusterError( + RetrieveClusterError::RetrieveFrontend(_), + ) => { + set_default_answer(kawa, front_readiness, 404); + } + BE::RetrieveClusterError( + RetrieveClusterError::UnauthorizedRoute, + ) => { + set_default_answer(kawa, front_readiness, 401); + } + + BE::Backend(_) => {} + BE::RetrieveClusterError(_) => unreachable!(), + BE::NotFound(_) => unreachable!(), + } + } + } } - dirty = true; } - if !dirty { break; } - - counter += 1; - } - - if counter == max_loop_iterations { - incr!("http.infinite_loop.error"); - return SessionResult::Close; } SessionResult::Continue diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index a985f560f..05071aa0e 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -103,7 +103,10 @@ impl<'a> Error<'a> { Error { input, error } } pub fn new_h2(input: &'a [u8], error: H2Error) -> Error<'a> { - Error { input, error: InnerError::H2(error) } + Error { + input, + error: InnerError::H2(error), + } } } diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs index e8d5a595d..a86ff68ee 100644 --- a/lib/src/protocol/mux/serializer.rs +++ b/lib/src/protocol/mux/serializer.rs @@ -8,7 +8,7 @@ use cookie_factory::{ use super::{ h2::H2Settings, - parser::{FrameHeader, FrameType}, + parser::{FrameHeader, FrameType, H2Error}, }; pub const H2_PRI: &str = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; @@ -95,3 +95,22 @@ pub fn gen_settings<'a>( .map(|(buf, size)| (buf, (old_size + size as usize))) }) } + +pub fn gen_rst_stream<'a>( + buf: &'a mut [u8], + stream_id: u32, + error_code: H2Error, +) -> Result<(&'a mut [u8], usize), GenError> { + gen_frame_header( + buf, + &FrameHeader { + payload_len: 4, + frame_type: FrameType::RstStream, + flags: 0, + stream_id, + }, + ) + .and_then(|(buf, old_size)| { + gen(be_u32(error_code as u32), buf).map(|(buf, size)| (buf, (old_size + size as usize))) + }) +} From 5e888d08e4be8c817d4b56a18a32893295d76240 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 12 Sep 2023 17:59:14 +0200 Subject: [PATCH 24/44] Proxying enhancements: - Primitive GoAway and connection error handling for H2 (todo: stream error handling) - Add `force_disconnect` method to H2 that triggers the clean up routine by faking socket disconnection (experimental) - Reading parts of a message properly inserts WRITABLE in the opposite endpoint readiness interest - Fix stream_id numbering - `set_default_answer` and `forcefully_terminate_answer` update StreamState to Unlinked - Replace std::mem::swap with more appropriate variants --- lib/src/lib.rs | 4 +- lib/src/protocol/mux/converter.rs | 3 +- lib/src/protocol/mux/h1.rs | 54 +++++----- lib/src/protocol/mux/h2.rs | 167 ++++++++++++++++++----------- lib/src/protocol/mux/mod.rs | 71 ++++++------ lib/src/protocol/mux/serializer.rs | 23 ++++ 6 files changed, 190 insertions(+), 132 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 903bd8443..3e700b76f 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -495,9 +495,7 @@ macro_rules! StateMachineBuilder { /// leaving a FailedUpgrade in its place. /// The FailedUpgrade retains the marker of the previous State. fn take(&mut self) -> $state_name { - let mut owned_state = $state_name::FailedUpgrade(self.marker()); - std::mem::swap(&mut owned_state, self); - owned_state + std::mem::replace(self, $state_name::FailedUpgrade(self.marker())) } _fn_impl!{front_socket(&, self) -> &mio::net::TcpStream} } diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 0013f3b03..cb216ff41 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -125,8 +125,7 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { .. }) => { if end_header { - let mut payload = Vec::new(); - std::mem::swap(&mut self.out, &mut payload); + let payload = std::mem::replace(&mut self.out, Vec::new()); let mut header = [0; 9]; let flags = if end_stream { 1 } else { 0 } | if end_header { 4 } else { 0 }; gen_frame_header( diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index edaed8c8b..7a499f195 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -3,8 +3,9 @@ use sozu_command::ready::Ready; use crate::{ println_, protocol::mux::{ - debug_kawa, set_default_answer, update_readiness_after_read, update_readiness_after_write, - BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, Position, StreamState, forcefully_terminate_answer, + debug_kawa, forcefully_terminate_answer, set_default_answer, update_readiness_after_read, + update_readiness_after_write, BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, + Position, StreamState, }, socket::SocketHandler, Readiness, @@ -58,8 +59,7 @@ impl ConnectionH1 { endpoint.end_stream(token, global_stream_id, context); } Position::Server => { - set_default_answer(&mut stream.back, &mut self.readiness, 400); - stream.state = StreamState::Unlinked; + set_default_answer(stream, &mut self.readiness, 400); } } return MuxResult::Continue; @@ -67,7 +67,7 @@ impl ConnectionH1 { if kawa.is_terminated() { self.readiness.interest.remove(Ready::READABLE); } - if was_initial && kawa.is_main_phase() { + if kawa.is_main_phase() { match self.position { Position::Client(_) => { let StreamState::Linked(token) = stream.state else { unreachable!() }; @@ -77,12 +77,14 @@ impl ConnectionH1 { .insert(Ready::WRITABLE) } Position::Server => { - self.requests += 1; - println_!("REQUESTS: {}", self.requests); - stream.state = StreamState::Link + if was_initial { + self.requests += 1; + println_!("REQUESTS: {}", self.requests); + stream.state = StreamState::Link + } } - }; - } + } + }; MuxResult::Continue } @@ -115,7 +117,9 @@ impl ConnectionH1 { stream.back.storage.clear(); stream.front.clear(); // do not clear stream.front.storage because of H1 pipelining - if let StreamState::Linked(token) = stream.state { + stream.attempts = 0; + let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); + if let StreamState::Linked(token) = old_state { endpoint.end_stream(token, self.stream, context); } } @@ -148,14 +152,12 @@ impl ConnectionH1 { let stream = &mut context.streams[stream]; let stream_context = &mut stream.context; println_!("end H1 stream {}: {stream_context:#?}", self.stream); - self.stream = usize::MAX; - let mut owned_position = Position::Server; - std::mem::swap(&mut owned_position, &mut self.position); - match owned_position { + match &mut self.position { Position::Client(BackendStatus::Connected(cluster_id)) | Position::Client(BackendStatus::Connecting(cluster_id)) => { + self.stream = usize::MAX; self.position = if stream_context.keep_alive_backend { - Position::Client(BackendStatus::KeepAlive(cluster_id)) + Position::Client(BackendStatus::KeepAlive(std::mem::take(cluster_id))) } else { Position::Client(BackendStatus::Disconnecting) } @@ -168,13 +170,14 @@ impl ConnectionH1 { // if the answer is not terminated we send an RstStream to properly clean the stream // if it is terminated, we finish the transfer, the backend is not necessary anymore if !stream.back.is_terminated() { - forcefully_terminate_answer(&mut stream.back, &mut self.readiness); + forcefully_terminate_answer(stream, &mut self.readiness); + } else { + stream.state = StreamState::Unlinked; + self.readiness.interest.insert(Ready::WRITABLE); } - stream.state = StreamState::Unlinked; } (true, false) => { - set_default_answer(&mut stream.back, &mut self.readiness, 502); - stream.state = StreamState::Unlinked; + set_default_answer(stream, &mut self.readiness, 502); } (false, false) => { // we do not have an answer, but the request is untouched so we can retry @@ -189,16 +192,13 @@ impl ConnectionH1 { pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { println_!("start H1 stream {stream} {:?}", self.readiness); self.stream = stream; - let mut owned_position = Position::Server; - std::mem::swap(&mut owned_position, &mut self.position); - match owned_position { + match &mut self.position { Position::Client(BackendStatus::KeepAlive(cluster_id)) => { - self.position = Position::Client(BackendStatus::Connecting(cluster_id)) + self.position = + Position::Client(BackendStatus::Connecting(std::mem::take(cluster_id))) } + Position::Client(_) => {} Position::Server => unreachable!(), - _ => { - self.position = owned_position; - } } } } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 4803febbd..a5ac18782 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -6,24 +6,24 @@ use sozu_command::ready::Ready; use crate::{ println_, protocol::mux::{ - converter, debug_kawa, + converter, debug_kawa, forcefully_terminate_answer, parser::{self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error}, - pkawa, serializer, update_readiness_after_read, update_readiness_after_write, - BackendStatus, Context, Endpoint, GenericHttpStream, GlobalStreamId, MuxResult, Position, - StreamId, StreamState, set_default_answer, forcefully_terminate_answer, + pkawa, serializer, set_default_answer, update_readiness_after_read, + update_readiness_after_write, BackendStatus, Context, Endpoint, GenericHttpStream, + GlobalStreamId, MuxResult, Position, StreamId, StreamState, }, socket::SocketHandler, Readiness, }; #[inline(always)] -fn error_nom_to_h2(error: nom::Err) -> MuxResult { +fn error_nom_to_h2(error: nom::Err) -> H2Error { match error { nom::Err::Error(parser::Error { error: parser::InnerError::H2(e), .. - }) => return MuxResult::CloseSession(e), - _ => return MuxResult::CloseSession(H2Error::ProtocolError), + }) => return e, + _ => return H2Error::ProtocolError, } } @@ -34,6 +34,7 @@ pub enum H2State { ServerSettings, Header, Frame(FrameHeader), + GoAway, Error, } @@ -138,6 +139,7 @@ impl ConnectionH2 { }; match (&self.state, &self.position) { (H2State::Error, _) + | (H2State::GoAway, _) | (H2State::ServerSettings, Position::Server) | (H2State::ClientPreface, Position::Client(_)) | (H2State::ClientSettings, Position::Client(_)) => unreachable!( @@ -148,7 +150,7 @@ impl ConnectionH2 { let i = kawa.storage.data(); let i = match parser::preface(i) { Ok((i, _)) => i, - Err(_) => return MuxResult::CloseSession(H2Error::ProtocolError), + Err(_) => return self.force_disconnect(), }; match parser::frame_header(i) { Ok(( @@ -164,7 +166,7 @@ impl ConnectionH2 { self.state = H2State::ClientSettings; self.expect_read = Some((H2StreamId::Zero, payload_len as usize)); } - _ => return MuxResult::CloseSession(H2Error::ProtocolError), + _ => return self.force_disconnect(), }; } (H2State::ClientSettings, Position::Server) => { @@ -182,7 +184,7 @@ impl ConnectionH2 { kawa.storage.clear(); settings } - Err(_) => return MuxResult::CloseSession(H2Error::ProtocolError), + Err(_) => return self.force_disconnect(), }; let kawa = &mut self.zero; match serializer::gen_frame_header( @@ -197,13 +199,13 @@ impl ConnectionH2 { Ok((_, size)) => kawa.storage.fill(size), Err(e) => { println!("could not serialize HeaderFrame: {e:?}"); - return MuxResult::CloseSession(H2Error::InternalError); + return self.force_disconnect(); } }; self.state = H2State::ServerSettings; self.expect_write = Some(H2StreamId::Zero); - self.handle(settings, context, endpoint); + self.handle_frame(settings, context, endpoint); } (H2State::ServerSettings, Position::Client(_)) => { let i = kawa.storage.data(); @@ -221,7 +223,7 @@ impl ConnectionH2 { self.expect_read = Some((H2StreamId::Zero, payload_len as usize)); self.state = H2State::Frame(header) } - _ => return MuxResult::CloseSession(H2Error::ProtocolError), + _ => return self.force_disconnect(), }; } (H2State::Header, _) => { @@ -232,32 +234,30 @@ impl ConnectionH2 { println_!("{header:#?}"); kawa.storage.clear(); let stream_id = header.stream_id; - let stream_id = if stream_id == 0 - || header.frame_type == FrameType::RstStream - { - H2StreamId::Zero - } else { - let global_stream_id = if let Some(global_stream_id) = - self.streams.get(&stream_id) - { - *global_stream_id + let stream_id = + if stream_id == 0 || header.frame_type == FrameType::RstStream { + H2StreamId::Zero } else { - match self.create_stream(stream_id, context) { - Some(global_stream_id) => global_stream_id, - None => return MuxResult::CloseSession(H2Error::InternalError), + let global_stream_id = + if let Some(global_stream_id) = self.streams.get(&stream_id) { + *global_stream_id + } else { + match self.create_stream(stream_id, context) { + Some(global_stream_id) => global_stream_id, + None => return self.goaway(H2Error::InternalError), + } + }; + if header.frame_type == FrameType::Data { + H2StreamId::Other(stream_id, global_stream_id) + } else { + H2StreamId::Zero } }; - if header.frame_type == FrameType::Data { - H2StreamId::Other(stream_id, global_stream_id) - } else { - H2StreamId::Zero - } - }; println_!("{} {stream_id:?} {:#?}", header.stream_id, self.streams); self.expect_read = Some((stream_id, header.payload_len as usize)); self.state = H2State::Frame(header); } - Err(e) => return error_nom_to_h2(e), + Err(e) => panic!("stream error: {:?}", error_nom_to_h2(e)), }; } (H2State::Frame(header), _) => { @@ -269,12 +269,12 @@ impl ConnectionH2 { self.local_settings.settings_max_frame_size, ) { Ok((_, frame)) => frame, - Err(e) => return error_nom_to_h2(e), + Err(e) => panic!("stream error: {:?}", error_nom_to_h2(e)), }; if let H2StreamId::Zero = stream_id { kawa.storage.clear(); } - let state_result = self.handle(frame, context, endpoint); + let state_result = self.handle_frame(frame, context, endpoint); self.state = H2State::Header; self.expect_read = Some((H2StreamId::Zero, 9)); return state_result; @@ -311,6 +311,7 @@ impl ConnectionH2 { "Unexpected combination: (Readable, {:?}, {:?})", self.state, self.position ), + (H2State::GoAway, _) => self.force_disconnect(), (H2State::ClientPreface, Position::Client(_)) => { println_!("Preparing preface and settings"); let pri = serializer::H2_PRI.as_bytes(); @@ -322,7 +323,7 @@ impl ConnectionH2 { Ok((_, size)) => kawa.storage.fill(size), Err(e) => { println!("could not serialize SettingsFrame: {e:?}"); - return MuxResult::CloseSession(H2Error::InternalError); + return self.force_disconnect(); } }; @@ -343,8 +344,8 @@ impl ConnectionH2 { self.expect_read = Some((H2StreamId::Zero, 9)); MuxResult::Continue } - // Proxying states (Header/Frame) - (_, _) => { + // Proxying states + (H2State::Header, _) | (H2State::Frame(_), _) => { let mut dead_streams = Vec::new(); if let Some(H2StreamId::Other(stream_id, global_stream_id)) = self.expect_write { @@ -365,8 +366,8 @@ impl ConnectionH2 { Position::Server => { // mark stream as reusable println_!("Recycle stream: {global_stream_id}"); - let mut state = StreamState::Recycle; - std::mem::swap(&mut stream.state, &mut state); + let state = + std::mem::replace(&mut stream.state, StreamState::Recycle); if let StreamState::Linked(token) = state { endpoint.end_stream(token, global_stream_id, context); } @@ -412,8 +413,8 @@ impl ConnectionH2 { Position::Server => { // mark stream as reusable println_!("Recycle stream: {global_stream_id}"); - let mut state = StreamState::Recycle; - std::mem::swap(&mut stream.state, &mut state); + let state = + std::mem::replace(&mut stream.state, StreamState::Recycle); if let StreamState::Linked(token) = state { endpoint.end_stream(token, global_stream_id, context); } @@ -435,6 +436,27 @@ impl ConnectionH2 { } } + pub fn goaway(&mut self, error: H2Error) -> MuxResult { + self.state = H2State::Error; + self.expect_read = None; + self.expect_write = Some(H2StreamId::Zero); + let kawa = &mut self.zero; + + match serializer::gen_goaway(kawa.storage.space(), self.last_stream_id, error) { + Ok((_, size)) => { + kawa.storage.fill(size); + self.state = H2State::GoAway; + self.expect_write = Some(H2StreamId::Zero); + self.readiness.interest = Ready::WRITABLE | Ready::HUP | Ready::ERROR; + MuxResult::Continue + } + Err(e) => { + println!("could not serialize GoAwayFrame: {e:?}"); + self.force_disconnect() + } + } + } + pub fn create_stream( &mut self, stream_id: StreamId, @@ -444,8 +466,8 @@ impl ConnectionH2 { Ulid::generate(), self.peer_settings.settings_initial_window_size, )?; - if (stream_id >> 1) > self.last_stream_id { - self.last_stream_id = stream_id >> 1; + if stream_id > self.last_stream_id { + self.last_stream_id = stream_id & !1; } self.streams.insert(stream_id, global_stream_id); Some(global_stream_id) @@ -459,7 +481,7 @@ impl ConnectionH2 { } } - fn handle(&mut self, frame: Frame, context: &mut Context, mut endpoint: E) -> MuxResult + fn handle_frame(&mut self, frame: Frame, context: &mut Context, mut endpoint: E) -> MuxResult where E: Endpoint, { @@ -469,7 +491,7 @@ impl ConnectionH2 { let mut slice = data.payload; let global_stream_id = match self.streams.get(&data.stream_id) { Some(global_stream_id) => *global_stream_id, - None => return MuxResult::CloseSession(H2Error::ProtocolError), + None => panic!("stream error"), }; let stream = &mut context.streams[global_stream_id]; let kawa = stream.rbuffer(&self.position); @@ -490,6 +512,16 @@ impl ConnectionH2 { })); kawa.parsing_phase = kawa::ParsingPhase::Terminated; } + match self.position { + Position::Client(_) => { + let StreamState::Linked(token) = stream.state else { unreachable!() }; + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } + Position::Server => {} + }; } Frame::Headers(headers) => { if !headers.end_headers { @@ -526,12 +558,12 @@ impl ConnectionH2 { if self.local_settings.settings_enable_push { todo!("forward the push") } else { - return MuxResult::CloseSession(H2Error::ProtocolError); + return self.goaway(H2Error::ProtocolError); } } Position::Server => { println_!("A client should not push promises"); - return MuxResult::CloseSession(H2Error::ProtocolError); + return self.goaway(H2Error::ProtocolError); } }, Frame::Priority(priority) => (), @@ -576,7 +608,7 @@ impl ConnectionH2 { Ok((_, size)) => kawa.storage.fill(size), Err(e) => { println!("could not serialize PingFrame: {e:?}"); - return MuxResult::CloseSession(H2Error::InternalError); + return self.force_disconnect(); } }; self.readiness.interest.insert(Ready::WRITABLE); @@ -589,17 +621,15 @@ impl ConnectionH2 { goaway.error_code, error_code_to_str(goaway.error_code) ); - todo!(); + return self.goaway(H2Error::NoError); } Frame::WindowUpdate(update) => { if update.stream_id == 0 { self.window += update.increment; } else { - let global_stream_id = match self.streams.get(&update.stream_id) { - Some(global_stream_id) => *global_stream_id, - None => return MuxResult::CloseSession(H2Error::ProtocolError), - }; - context.streams[global_stream_id].window += update.increment as i32; + if let Some(global_stream_id) = self.streams.get(&update.stream_id) { + context.streams[*global_stream_id].window += update.increment as i32; + } } } Frame::Continuation(_) => todo!(), @@ -607,15 +637,27 @@ impl ConnectionH2 { MuxResult::Continue } + fn force_disconnect(&mut self) -> MuxResult { + self.state = H2State::Error; + match self.position { + Position::Client(_) => { + self.position = Position::Client(BackendStatus::Disconnecting); + self.readiness.event = Ready::HUP; + MuxResult::Continue + } + Position::Server => MuxResult::CloseSession, + } + } + pub fn close(&mut self, context: &mut Context, mut endpoint: E) where E: Endpoint, { match self.position { Position::Client(BackendStatus::Connected(_)) - | Position::Client(BackendStatus::Connecting(_)) => {} - Position::Client(BackendStatus::Disconnecting) - | Position::Client(BackendStatus::KeepAlive(_)) => unreachable!(), + | Position::Client(BackendStatus::Connecting(_)) + | Position::Client(BackendStatus::Disconnecting) => {} + Position::Client(BackendStatus::KeepAlive(_)) => unreachable!(), Position::Server => unreachable!(), } // reconnection is handled by the server for each stream separately @@ -649,17 +691,18 @@ impl ConnectionH2 { // if the answer is not terminated we send an RstStream to properly clean the stream // if it is terminated, we finish the transfer, the backend is not necessary anymore if !stream.back.is_terminated() { - forcefully_terminate_answer(&mut stream.back, &mut self.readiness); + forcefully_terminate_answer(stream, &mut self.readiness); + } else { + stream.state = StreamState::Unlinked; + self.readiness.interest.insert(Ready::WRITABLE); } - stream.state = StreamState::Unlinked } (true, false) => { // we do not have an answer, but the request has already been partially consumed // so we can't retry, send a 502 bad gateway instead // note: it might be possible to send a RstStream with an adequate error code - set_default_answer(&mut stream.back, &mut self.readiness, 502); - stream.state = StreamState::Unlinked; - } + set_default_answer(stream, &mut self.readiness, 502); + } (false, false) => { // we do not have an answer, but the request is untouched so we can retry println!("H2 RECONNECT"); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 119926e02..c9f60ef79 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -26,7 +26,6 @@ use crate::{ mux::{ h1::ConnectionH1, h2::{ConnectionH2, H2Settings, H2State, H2StreamId}, - parser::H2Error, }, SessionState, }, @@ -54,8 +53,9 @@ type GenericHttpStream = kawa::Kawa; type StreamId = u32; type GlobalStreamId = usize; -/// Replace the content of the kawa buffer with a default Sozu answer for a given status code -fn set_default_answer(kawa: &mut GenericHttpStream, readiness: &mut Readiness, code: u16) { +/// Replace the content of the kawa message with a default Sozu answer for a given status code +fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) { + let kawa = &mut stream.back; kawa.clear(); kawa.storage.clear(); kawa.detached.status_line = kawa::StatusLine::Response { @@ -84,10 +84,14 @@ fn set_default_answer(kawa: &mut GenericHttpStream, readiness: &mut Readiness, c end_stream: true, })); kawa.parsing_phase = kawa::ParsingPhase::Terminated; + stream.state = StreamState::Unlinked; readiness.interest.insert(Ready::WRITABLE); } -fn forcefully_terminate_answer(kawa: &mut GenericHttpStream, readiness: &mut Readiness) { +/// Forcefully terminates a kawa message by setting the "end_stream" flag and setting the parsing_phase to Error. +/// An H2 converter will produce an RstStream frame. +fn forcefully_terminate_answer(stream: &mut Stream, readiness: &mut Readiness) { + let kawa = &mut stream.back; kawa.push_block(kawa::Block::Flags(kawa::Flags { end_body: false, end_chunk: false, @@ -96,6 +100,7 @@ fn forcefully_terminate_answer(kawa: &mut GenericHttpStream, readiness: &mut Rea })); kawa.parsing_phase = kawa::ParsingPhase::Terminated; debug_kawa(kawa); + stream.state = StreamState::Unlinked; readiness.interest.insert(Ready::WRITABLE); } @@ -115,13 +120,7 @@ pub enum BackendStatus { pub enum MuxResult { Continue, - CloseSession(H2Error), -} - -#[derive(Debug)] -pub enum Connection { - H1(ConnectionH1), - H2(ConnectionH2), + CloseSession, } pub trait Endpoint { @@ -139,6 +138,12 @@ pub trait Endpoint { fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context); } +#[derive(Debug)] +pub enum Connection { + H1(ConnectionH1), + H2(ConnectionH2), +} + impl Connection { pub fn new_h1_server(front_stream: Front) -> Connection { Connection::H1(ConnectionH1 { @@ -805,10 +810,6 @@ impl Mux { pub fn front_socket(&self) -> &TcpStream { self.frontend.socket() } - - fn goaway(&mut self, e: H2Error) { - todo!() - } } impl SessionState for Mux { @@ -835,8 +836,8 @@ impl SessionState for Mux { .frontend .readable(context, EndpointClient(&mut self.router)) { - MuxResult::Continue => (), - MuxResult::CloseSession(e) => self.goaway(e), + MuxResult::Continue => {} + MuxResult::CloseSession => return SessionResult::Close, } } @@ -853,31 +854,25 @@ impl SessionState for Mux { } if backend.readiness().filter_interest().is_writable() { - let mut owned_position = Position::Server; let position = backend.position_mut(); - std::mem::swap(&mut owned_position, position); - match owned_position { + match position { Position::Client(BackendStatus::Connecting(cluster_id)) => { - *position = Position::Client(BackendStatus::Connected(cluster_id)); + *position = Position::Client(BackendStatus::Connected( + std::mem::take(cluster_id), + )); } - _ => *position = owned_position, + _ => {} } match backend.writable(context, EndpointServer(&mut self.frontend)) { - MuxResult::Continue => (), - MuxResult::CloseSession(e) => { - self.goaway(e); - break; - } + MuxResult::Continue => {} + MuxResult::CloseSession => return SessionResult::Close, } } if backend.readiness().filter_interest().is_readable() { match backend.readable(context, EndpointServer(&mut self.frontend)) { - MuxResult::Continue => (), - MuxResult::CloseSession(e) => { - self.goaway(e); - break; - } + MuxResult::Continue => {} + MuxResult::CloseSession => return SessionResult::Close, } } @@ -905,8 +900,8 @@ impl SessionState for Mux { .frontend .writable(context, EndpointClient(&mut self.router)) { - MuxResult::Continue => (), - MuxResult::CloseSession(e) => self.goaway(e), + MuxResult::Continue => {} + MuxResult::CloseSession => return SessionResult::Close, } } @@ -939,24 +934,24 @@ impl SessionState for Mux { Ok(_) => {} Err(error) => { println_!("Connection error: {error}"); - let kawa = &mut context.streams[stream_id].back; + let stream = &mut context.streams[stream_id]; use BackendConnectionError as BE; match error { BE::Backend(BackendError::NoBackendForCluster(_)) | BE::MaxConnectionRetries(_) | BE::MaxSessionsMemory | BE::MaxBuffers => { - set_default_answer(kawa, front_readiness, 503); + set_default_answer(stream, front_readiness, 503); } BE::RetrieveClusterError( RetrieveClusterError::RetrieveFrontend(_), ) => { - set_default_answer(kawa, front_readiness, 404); + set_default_answer(stream, front_readiness, 404); } BE::RetrieveClusterError( RetrieveClusterError::UnauthorizedRoute, ) => { - set_default_answer(kawa, front_readiness, 401); + set_default_answer(stream, front_readiness, 401); } BE::Backend(_) => {} diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs index a86ff68ee..fa94ead4d 100644 --- a/lib/src/protocol/mux/serializer.rs +++ b/lib/src/protocol/mux/serializer.rs @@ -114,3 +114,26 @@ pub fn gen_rst_stream<'a>( gen(be_u32(error_code as u32), buf).map(|(buf, size)| (buf, (old_size + size as usize))) }) } + +pub fn gen_goaway<'a>( + buf: &'a mut [u8], + last_stream_id: u32, + error_code: H2Error, +) -> Result<(&'a mut [u8], usize), GenError> { + gen_frame_header( + buf, + &FrameHeader { + payload_len: 4, + frame_type: FrameType::GoAway, + flags: 0, + stream_id: 0, + }, + ) + .and_then(|(buf, old_size)| { + gen( + tuple((be_u32(last_stream_id), be_u32(error_code as u32))), + buf, + ) + .map(|(buf, size)| (buf, (old_size + size as usize))) + }) +} From d13eb0d31a7fbe2581ee3b62826a5289051db8d4 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 19 Sep 2023 12:12:10 +0200 Subject: [PATCH 25/44] Use Mux State in HTTP Session: - Parameterize Mux with the Front type - Replace kawa_h1 State by mux in HttpStateMachine - Handle 1xx in Server ConnectionH1 - Add Upgrade to MuxResult to allow h1 to ws upgrade (implement upgrade_mux in HTTP Session) - Implement Mux::shutting_down - Handle https redirection in mux::Router::connect - Implement a working 301 default answer - Use the new default answer factory in e2e tests - Fix front/back readiness for h1<->h1 Mux connections Signed-off-by: Eloi DEMOLIS --- e2e/src/http_utils/mod.rs | 18 ++++ e2e/src/tests/tests.rs | 15 ++- lib/src/http.rs | 124 +++++++++++++++++++---- lib/src/https.rs | 12 +-- lib/src/lib.rs | 2 + lib/src/protocol/kawa_h1/editor.rs | 15 ++- lib/src/protocol/kawa_h1/mod.rs | 11 --- lib/src/protocol/mux/h1.rs | 84 +++++++++++++--- lib/src/protocol/mux/mod.rs | 154 ++++++++++++++++++++++------- 9 files changed, 342 insertions(+), 93 deletions(-) diff --git a/e2e/src/http_utils/mod.rs b/e2e/src/http_utils/mod.rs index 9e6248df2..144136162 100644 --- a/e2e/src/http_utils/mod.rs +++ b/e2e/src/http_utils/mod.rs @@ -33,3 +33,21 @@ pub fn immutable_answer(status: u16) -> String { _ => unimplemented!() } } + +// use std::io::Write; +// use kawa; + +// /// the default kawa answer for the error code provided, converted to HTTP/1.1 +// pub fn default_answer(code: u16) -> String { +// let mut kawa_answer = kawa::Kawa::new( +// kawa::Kind::Response, +// kawa::Buffer::new(kawa::SliceBuffer(&mut [])), +// ); +// sozu_lib::protocol::mux::fill_default_answer(&mut kawa_answer, code); +// kawa_answer.prepare(&mut kawa::h1::converter::H1BlockConverter); +// let out = kawa_answer.as_io_slice(); +// let mut writer = std::io::BufWriter::new(Vec::new()); +// writer.write_vectored(&out).expect("WRITE"); +// let result = unsafe { std::str::from_utf8_unchecked(writer.buffer()) }; +// result.to_string() +// } diff --git a/e2e/src/tests/tests.rs b/e2e/src/tests/tests.rs index 6bcedfc06..34e72e32c 100644 --- a/e2e/src/tests/tests.rs +++ b/e2e/src/tests/tests.rs @@ -776,7 +776,8 @@ fn try_http_behaviors() -> State { && response.ends_with(&expected_response_end) ); - info!("server closes, expecting 503"); + // FIXME: do we want 502 or 503??? + info!("server closes, expecting 502"); // TODO: what if the client continue to use the closed stream client.connect(); client.send(); @@ -1242,7 +1243,9 @@ pub fn try_stick() -> State { backend1.send(0); let response = client.receive(); println!("response: {response:?}"); - assert!(request.unwrap().starts_with("GET /api HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nCookie: foo=bar\r\nX-Forwarded-For:")); + assert!(request.unwrap().starts_with( + "GET /api HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nCookie: foo=bar\r\n" + )); assert!(response.unwrap().starts_with("HTTP/1.1 200 OK\r\nContent-Length: 5\r\nSet-Cookie: SOZUBALANCEID=sticky_cluster_0-0; Path=/\r\nSozu-Id:")); // invalid sticky_session @@ -1255,7 +1258,9 @@ pub fn try_stick() -> State { backend2.send(0); let response = client.receive(); println!("response: {response:?}"); - assert!(request.unwrap().starts_with("GET /api HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nCookie: foo=bar\r\nX-Forwarded-For:")); + assert!(request.unwrap().starts_with( + "GET /api HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nCookie: foo=bar\r\n" + )); assert!(response.unwrap().starts_with("HTTP/1.1 200 OK\r\nContent-Length: 5\r\nSet-Cookie: SOZUBALANCEID=sticky_cluster_0-1; Path=/\r\nSozu-Id:")); // good sticky_session (force use backend2, round-robin would have chosen backend1) @@ -1268,7 +1273,9 @@ pub fn try_stick() -> State { backend2.send(0); let response = client.receive(); println!("response: {response:?}"); - assert!(request.unwrap().starts_with("GET /api HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nCookie: foo=bar\r\nX-Forwarded-For:")); + assert!(request.unwrap().starts_with( + "GET /api HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nCookie: foo=bar\r\n" + )); assert!(response .unwrap() .starts_with("HTTP/1.1 200 OK\r\nContent-Length: 5\r\nSozu-Id:")); diff --git a/lib/src/http.rs b/lib/src/http.rs index 51e29ecc6..b09b30d10 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -36,6 +36,7 @@ use crate::{ parser::{hostname_and_port, Method}, ResponseStream, }, + mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, Http, Pipe, SessionState, }, @@ -62,7 +63,8 @@ StateMachineBuilder! { /// 3. WebSocket (passthrough) enum HttpStateMachine impl SessionState { Expect(ExpectProxyProtocol), - Http(Http), + // Http(Http), + Mux(Mux), WebSocket(Pipe), } } @@ -121,22 +123,36 @@ impl HttpSession { gauge_add!("protocol.http", 1); let session_address = sock.peer_addr().ok(); - HttpStateMachine::Http(Http::new( - answers.clone(), - configured_backend_timeout, - configured_connect_timeout, - configured_frontend_timeout, - container_frontend_timeout, - sock, - token, - listener.clone(), - pool.clone(), - Protocol::HTTP, + let mut context = mux::Context::new(pool.clone()); + context + .create_stream(request_id, 1 << 16) + .ok_or(AcceptError::BufferCapacityReached)?; + let frontend = mux::Connection::new_h1_server(sock); + HttpStateMachine::Mux(Mux { + frontend_token: token, + frontend, + router: mux::Router::new(listener.clone()), public_address, - request_id, - session_address, - sticky_name.clone(), - )?) + peer_address: session_address, + sticky_name: sticky_name.clone(), + context, + }) + // HttpStateMachine::Http(Http::new( + // answers.clone(), + // configured_backend_timeout, + // configured_connect_timeout, + // configured_frontend_timeout, + // container_frontend_timeout, + // sock, + // token, + // listener.clone(), + // pool.clone(), + // Protocol::HTTP, + // public_address, + // request_id, + // session_address, + // sticky_name.clone(), + // )?) }; let metrics = SessionMetrics::new(Some(wait_time)); @@ -160,7 +176,8 @@ impl HttpSession { pub fn upgrade(&mut self) -> SessionIsToBeClosed { debug!("HTTP::upgrade"); let new_state = match self.state.take() { - HttpStateMachine::Http(http) => self.upgrade_http(http), + // HttpStateMachine::Http(http) => self.upgrade_http(http), + HttpStateMachine::Mux(mux) => self.upgrade_mux(mux), HttpStateMachine::Expect(expect) => self.upgrade_expect(expect), HttpStateMachine::WebSocket(ws) => self.upgrade_websocket(ws), HttpStateMachine::FailedUpgrade(_) => unreachable!(), @@ -208,7 +225,8 @@ impl HttpSession { gauge_add!("protocol.proxy.expect", -1); gauge_add!("protocol.http", 1); - Some(HttpStateMachine::Http(http)) + unimplemented!(); + // Some(HttpStateMachine::Http(http)) } _ => None, } @@ -227,8 +245,8 @@ impl HttpSession { return None; } }; + let ws_context = http.context.websocket_context(); - let ws_context = http.websocket_context(); let mut container_frontend_timeout = http.container_frontend_timeout; let mut container_backend_timeout = http.container_backend_timeout; container_frontend_timeout.reset(); @@ -269,6 +287,69 @@ impl HttpSession { Some(HttpStateMachine::WebSocket(pipe)) } + fn upgrade_mux(&mut self, mut mux: Mux) -> Option { + debug!("mux switching to ws"); + let stream = mux.context.streams.pop().unwrap(); + + let (frontend_readiness, frontend_socket) = match mux.frontend { + mux::Connection::H1(mux::ConnectionH1 { + readiness, socket, .. + }) => (readiness, socket), + // only h1<->h1 connections can upgrade to websocket + mux::Connection::H2(_) => unreachable!(), + }; + + let mux::StreamState::Linked(back_token) = stream.state else { unreachable!() }; + let backend = mux.router.backends.remove(&back_token).unwrap(); + let (cluster_id, backend_readiness, backend_socket) = match backend { + mux::Connection::H1(mux::ConnectionH1 { + position: mux::Position::Client(mux::BackendStatus::Connected(cluster_id)), + readiness, + socket, + .. + }) => (cluster_id, readiness, socket), + // the backend disconnected just after upgrade, abort + mux::Connection::H1(_) => return None, + // only h1<->h1 connections can upgrade to websocket + mux::Connection::H2(_) => unreachable!(), + }; + + let ws_context = stream.context.websocket_context(); + + // let mut container_frontend_timeout = http.container_frontend_timeout; + // let mut container_backend_timeout = http.container_backend_timeout; + // container_frontend_timeout.reset(); + // container_backend_timeout.reset(); + + let mut pipe = Pipe::new( + stream.back.storage.buffer, + None, + Some(backend_socket), + None, + None, + None, + Some(cluster_id), + stream.front.storage.buffer, + self.frontend_token, + frontend_socket, + self.listener.clone(), + Protocol::HTTP, + stream.context.id, + stream.context.session_address, + ws_context, + ); + + pipe.frontend_readiness.event = frontend_readiness.event; + pipe.backend_readiness.event = backend_readiness.event; + pipe.set_back_token(back_token); + + gauge_add!("protocol.http", -1); + gauge_add!("protocol.ws", 1); + gauge_add!("http.active_requests", -1); + gauge_add!("websocket.active_requests", 1); + Some(HttpStateMachine::WebSocket(pipe)) + } + fn upgrade_websocket(&self, ws: Pipe) -> Option { // what do we do here? error!("Upgrade called on WS, this should not happen"); @@ -288,7 +369,8 @@ impl ProxySession for HttpSession { // Restore gauges match self.state.marker() { StateMarker::Expect => gauge_add!("protocol.proxy.expect", -1), - StateMarker::Http => gauge_add!("protocol.http", -1), + // StateMarker::Http => gauge_add!("protocol.http", -1), + StateMarker::Mux => gauge_add!("protocol.http", -1), StateMarker::WebSocket => { gauge_add!("protocol.ws", -1); gauge_add!("websocket.active_requests", -1); @@ -298,7 +380,7 @@ impl ProxySession for HttpSession { if self.state.failed() { match self.state.marker() { StateMarker::Expect => incr!("http.upgrade.expect.failed"), - StateMarker::Http => incr!("http.upgrade.http.failed"), + StateMarker::Mux => incr!("http.upgrade.http.failed"), StateMarker::WebSocket => incr!("http.upgrade.ws.failed"), } return; diff --git a/lib/src/https.rs b/lib/src/https.rs index c329872a2..4ef48d1ab 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -59,7 +59,7 @@ use crate::{ parser::{hostname_and_port, Method}, ResponseStream, }, - mux::Mux, + mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, rustls::TlsHandshake, Http, Pipe, SessionState, @@ -92,7 +92,7 @@ StateMachineBuilder! { enum HttpsStateMachine impl SessionState { Expect(ExpectProxyProtocol, ServerConnection), Handshake(TlsHandshake), - Mux(Mux), + Mux(Mux), Http(Http), WebSocket(Pipe), Http2(Http2) -> todo!("H2"), @@ -289,7 +289,6 @@ impl HttpsSession { gauge_add!("protocol.tls.handshake", -1); - use crate::protocol::mux; let mut context = mux::Context::new(self.pool.clone()); let mut frontend = match alpn { AlpnProtocol::Http11 => { @@ -305,10 +304,7 @@ impl HttpsSession { frontend_token: self.frontend_token, frontend, context, - router: mux::Router { - listener: self.listener.clone(), - backends: HashMap::new(), - }, + router: mux::Router::new(self.listener.clone()), public_address: self.public_address, peer_address: self.peer_address, sticky_name: self.sticky_name.clone(), @@ -328,8 +324,8 @@ impl HttpsSession { return None; } }; + let ws_context = http.context.websocket_context(); - let ws_context = http.websocket_context(); let mut container_frontend_timeout = http.container_frontend_timeout; let mut container_backend_timeout = http.container_backend_timeout; container_frontend_timeout.reset(); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 3e700b76f..8bfbce847 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -604,6 +604,8 @@ pub enum RetrieveClusterError { UnauthorizedRoute, #[error("{0}")] RetrieveFrontend(FrontendFromRequestError), + #[error("https redirect")] + HttpsRedirect, } /// Used in sessions diff --git a/lib/src/protocol/kawa_h1/editor.rs b/lib/src/protocol/kawa_h1/editor.rs index 9903292a6..f3b2b24f4 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -7,7 +7,10 @@ use rusty_ulid::Ulid; use crate::{ pool::Checkout, - protocol::http::{parser::compare_no_case, GenericHttpStream, Method}, + protocol::{ + http::{parser::compare_no_case, GenericHttpStream, Method}, + pipe::WebSocketContext, + }, Protocol, RetrieveClusterError, }; @@ -391,4 +394,14 @@ impl HttpContext { } String::new() } + + pub fn websocket_context(&self) -> WebSocketContext { + WebSocketContext::Http { + method: self.method.clone(), + authority: self.authority.clone(), + path: self.path.clone(), + reason: self.reason.clone(), + status: self.status, + } + } } diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 07840b0db..375a6d203 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -903,17 +903,6 @@ impl Http WebSocketContext { - WebSocketContext::Http { - method: self.context.method.clone(), - authority: self.context.authority.clone(), - path: self.context.path.clone(), - reason: self.context.reason.clone(), - status: self.context.status, - } - } - pub fn log_request(&self, metrics: &SessionMetrics, error: bool, message: Option<&str>) { let listener = self.listener.borrow(); let tags = self.context.authority.as_ref().and_then(|host| { diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 7a499f195..33c806e31 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -77,6 +77,12 @@ impl ConnectionH1 { .insert(Ready::WRITABLE) } Position::Server => { + if let StreamState::Linked(token) = stream.state { + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } if was_initial { self.requests += 1; println_!("REQUESTS: {}", self.requests); @@ -112,15 +118,56 @@ impl ConnectionH1 { match self.position { Position::Client(_) => self.readiness.interest.insert(Ready::READABLE), Position::Server => { - stream.context.reset(); - stream.back.clear(); - stream.back.storage.clear(); - stream.front.clear(); - // do not clear stream.front.storage because of H1 pipelining - stream.attempts = 0; + if stream.context.closing { + return MuxResult::CloseSession; + } + let kawa = &mut stream.back; + match kawa.detached.status_line { + kawa::StatusLine::Response { code: 101, .. } => { + println!("============== HANDLE UPGRADE!"); + // unimplemented!(); + return MuxResult::Upgrade; + } + kawa::StatusLine::Response { code: 100, .. } => { + println!("============== HANDLE CONTINUE!"); + // after a 100 continue, we expect the client to continue with its request + self.readiness.interest.insert(Ready::READABLE); + kawa.clear(); + return MuxResult::Continue; + } + kawa::StatusLine::Response { code: 103, .. } => { + println!("============== HANDLE EARLY HINT!"); + if let StreamState::Linked(token) = stream.state { + // after a 103 early hints, we expect the server to send its response + endpoint + .readiness_mut(token) + .interest + .insert(Ready::READABLE); + kawa.clear(); + return MuxResult::Continue; + } else { + return MuxResult::CloseSession; + } + } + _ => {} + } let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); - if let StreamState::Linked(token) = old_state { - endpoint.end_stream(token, self.stream, context); + if stream.context.keep_alive_frontend { + println!("{old_state:?} {:?}", self.readiness); + if let StreamState::Linked(token) = old_state { + println!("{:?}", endpoint.readiness(token)); + endpoint.end_stream(token, self.stream, context); + } + self.readiness.interest.insert(Ready::READABLE); + let stream = &mut context.streams[self.stream]; + stream.context.reset(); + stream.back.clear(); + stream.back.storage.clear(); + stream.front.clear(); + // do not clear stream.front.storage because of H1 pipelining + stream.attempts = 0; + } else { + return MuxResult::CloseSession; } } } @@ -128,6 +175,17 @@ impl ConnectionH1 { MuxResult::Continue } + fn force_disconnect(&mut self) -> MuxResult { + match self.position { + Position::Client(_) => { + self.position = Position::Client(BackendStatus::Disconnecting); + self.readiness.event = Ready::HUP; + MuxResult::Continue + } + Position::Server => MuxResult::CloseSession, + } + } + pub fn close(&mut self, context: &mut Context, mut endpoint: E) where E: Endpoint, @@ -156,10 +214,11 @@ impl ConnectionH1 { Position::Client(BackendStatus::Connected(cluster_id)) | Position::Client(BackendStatus::Connecting(cluster_id)) => { self.stream = usize::MAX; - self.position = if stream_context.keep_alive_backend { - Position::Client(BackendStatus::KeepAlive(std::mem::take(cluster_id))) + if stream_context.keep_alive_backend { + self.position = + Position::Client(BackendStatus::KeepAlive(std::mem::take(cluster_id))) } else { - Position::Client(BackendStatus::Disconnecting) + self.force_disconnect(); } } Position::Client(BackendStatus::KeepAlive(_)) @@ -177,6 +236,8 @@ impl ConnectionH1 { } } (true, false) => { + // we do not have an answer, but the request has already been partially consumed + // so we can't retry, send a 502 bad gateway instead set_default_answer(stream, &mut self.readiness, 502); } (false, false) => { @@ -191,6 +252,7 @@ impl ConnectionH1 { pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { println_!("start H1 stream {stream} {:?}", self.readiness); + self.readiness.interest.insert(Ready::ALL); self.stream = stream; match &mut self.position { Position::Client(BackendStatus::KeepAlive(cluster_id)) => { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index c9f60ef79..41f3ab1e6 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -8,7 +8,7 @@ use std::{ use mio::{net::TcpStream, Interest, Token}; use rusty_ulid::Ulid; -use sozu_command::ready::Ready; +use sozu_command::{proto::command::ListenerType, ready::Ready}; mod converter; mod h1; @@ -19,23 +19,21 @@ mod serializer; use crate::{ backends::{Backend, BackendError}, - https::HttpsListener, pool::{Checkout, Pool}, protocol::{ http::editor::HttpContext, - mux::{ - h1::ConnectionH1, - h2::{ConnectionH2, H2Settings, H2State, H2StreamId}, - }, + mux::h2::{H2Settings, H2State, H2StreamId}, SessionState, }, router::Route, server::CONN_RETRIES, - socket::{FrontRustls, SocketHandler, SocketResult}, + socket::{SocketHandler, SocketResult}, BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, - RetrieveClusterError, SessionMetrics, SessionResult, StateResult, + RetrieveClusterError, SessionIsToBeClosed, SessionMetrics, SessionResult, StateResult, }; +pub use crate::protocol::mux::{h1::ConnectionH1, h2::ConnectionH2}; + #[macro_export] macro_rules! println_ { ($($t:expr),*) => { @@ -53,11 +51,22 @@ type GenericHttpStream = kawa::Kawa; type StreamId = u32; type GlobalStreamId = usize; -/// Replace the content of the kawa message with a default Sozu answer for a given status code -fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) { - let kawa = &mut stream.back; - kawa.clear(); - kawa.storage.clear(); +pub fn fill_default_301_answer(kawa: &mut kawa::Kawa, host: &str, uri: &str) { + kawa.detached.status_line = kawa::StatusLine::Response { + version: kawa::Version::V20, + code: 301, + status: kawa::Store::Static(b"301"), + reason: kawa::Store::Static(b"Moved Permanently"), + }; + kawa.push_block(kawa::Block::StatusLine); + kawa.push_block(kawa::Block::Header(kawa::Pair { + key: kawa::Store::Static(b"Location"), + val: kawa::Store::from_string(format!("https://{host}{uri}")), + })); + terminate_default_answer(kawa, false); +} + +pub fn fill_default_answer(kawa: &mut kawa::Kawa, code: u16) { kawa.detached.status_line = kawa::StatusLine::Response { version: kawa::Version::V20, code, @@ -65,14 +74,20 @@ fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) reason: kawa::Store::Static(b"Sozu Default Answer"), }; kawa.push_block(kawa::Block::StatusLine); - kawa.push_block(kawa::Block::Header(kawa::Pair { - key: kawa::Store::Static(b"Cache-Control"), - val: kawa::Store::Static(b"no-cache"), - })); - kawa.push_block(kawa::Block::Header(kawa::Pair { - key: kawa::Store::Static(b"Connection"), - val: kawa::Store::Static(b"close"), - })); + terminate_default_answer(kawa, true); +} + +pub fn terminate_default_answer(kawa: &mut kawa::Kawa, close: bool) { + if close { + kawa.push_block(kawa::Block::Header(kawa::Pair { + key: kawa::Store::Static(b"Cache-Control"), + val: kawa::Store::Static(b"no-cache"), + })); + kawa.push_block(kawa::Block::Header(kawa::Pair { + key: kawa::Store::Static(b"Connection"), + val: kawa::Store::Static(b"close"), + })); + } kawa.push_block(kawa::Block::Header(kawa::Pair { key: kawa::Store::Static(b"Content-Length"), val: kawa::Store::Static(b"0"), @@ -84,6 +99,20 @@ fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) end_stream: true, })); kawa.parsing_phase = kawa::ParsingPhase::Terminated; +} + +/// Replace the content of the kawa message with a default Sozu answer for a given status code +fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) { + let kawa = &mut stream.back; + kawa.clear(); + kawa.storage.clear(); + if code == 301 { + let host = stream.context.authority.as_deref().unwrap(); + let uri = stream.context.path.as_deref().unwrap(); + fill_default_301_answer(kawa, host, uri); + } else { + fill_default_answer(kawa, code); + } stream.state = StreamState::Unlinked; readiness.interest.insert(Ready::WRITABLE); } @@ -98,7 +127,7 @@ fn forcefully_terminate_answer(stream: &mut Stream, readiness: &mut Readiness) { end_header: false, end_stream: true, })); - kawa.parsing_phase = kawa::ParsingPhase::Terminated; + kawa.parsing_phase.error("Termination".into()); debug_kawa(kawa); stream.state = StreamState::Unlinked; readiness.interest.insert(Ready::WRITABLE); @@ -120,6 +149,7 @@ pub enum BackendStatus { pub enum MuxResult { Continue, + Upgrade, CloseSession, } @@ -238,13 +268,13 @@ impl Connection { Connection::H2(c) => &mut c.readiness, } } - fn position(&self) -> &Position { + pub fn position(&self) -> &Position { match self { Connection::H1(c) => &c.position, Connection::H2(c) => &c.position, } } - fn position_mut(&mut self) -> &mut Position { + pub fn position_mut(&mut self) -> &mut Position { match self { Connection::H1(c) => &mut c.position, Connection::H2(c) => &mut c.position, @@ -300,12 +330,12 @@ impl Connection { } } -struct EndpointServer<'a>(&'a mut Connection); +struct EndpointServer<'a, Front: SocketHandler>(&'a mut Connection); struct EndpointClient<'a>(&'a mut Router); // note: EndpointServer are used by client Connection, they do not know the frontend Token // they will use the Stream's Token which is their backend token -impl<'a> Endpoint for EndpointServer<'a> { +impl<'a, Front: SocketHandler> Endpoint for EndpointServer<'a, Front> { fn readiness(&self, _token: Token) -> &Readiness { self.0.readiness() } @@ -442,8 +472,8 @@ pub struct Stream { pub window: i32, pub attempts: u8, pub state: StreamState, - front: GenericHttpStream, - back: GenericHttpStream, + pub front: GenericHttpStream, + pub back: GenericHttpStream, pub context: HttpContext, } @@ -562,11 +592,18 @@ impl Context { } pub struct Router { - pub listener: Rc>, + pub listener: Rc>, pub backends: HashMap>, } impl Router { + pub fn new(listener: Rc>) -> Self { + Self { + listener, + backends: HashMap::new(), + } + } + fn connect( &mut self, stream_id: GlobalStreamId, @@ -589,12 +626,18 @@ impl Router { .route_from_request(stream_context, proxy.clone()) .map_err(BackendConnectionError::RetrieveClusterError)?; - let frontend_should_stick = proxy + let (frontend_should_stick, frontend_should_redirect_https) = proxy .borrow() .clusters() .get(&cluster_id) - .map(|cluster| cluster.sticky_session) - .unwrap_or(false); + .map(|cluster| (cluster.sticky_session, cluster.https_redirect)) + .unwrap_or((false, false)); + + if frontend_should_redirect_https && matches!(proxy.borrow().kind(), ListenerType::Http) { + return Err(BackendConnectionError::RetrieveClusterError( + RetrieveClusterError::HttpsRedirect, + )); + } let mut reuse_token = None; // let mut priority = 0; @@ -796,9 +839,9 @@ impl Router { } } -pub struct Mux { +pub struct Mux { pub frontend_token: Token, - pub frontend: Connection, + pub frontend: Connection, pub router: Router, pub public_address: SocketAddr, pub peer_address: Option, @@ -806,13 +849,13 @@ pub struct Mux { pub context: Context, } -impl Mux { +impl Mux { pub fn front_socket(&self) -> &TcpStream { self.frontend.socket() } } -impl SessionState for Mux { +impl SessionState for Mux { fn ready( &mut self, session: Rc>, @@ -838,6 +881,7 @@ impl SessionState for Mux { { MuxResult::Continue => {} MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Upgrade => return SessionResult::Upgrade, } } @@ -846,6 +890,7 @@ impl SessionState for Mux { let mut dead_backends = Vec::new(); for (token, backend) in self.router.backends.iter_mut() { let readiness = backend.readiness_mut(); + println!("{token:?} -> {readiness:?}"); let dead = readiness.filter_interest().is_hup() || readiness.filter_interest().is_error(); if dead { @@ -865,6 +910,7 @@ impl SessionState for Mux { } match backend.writable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => {} + MuxResult::Upgrade => unreachable!(), // only frontend can upgrade MuxResult::CloseSession => return SessionResult::Close, } } @@ -872,6 +918,7 @@ impl SessionState for Mux { if backend.readiness().filter_interest().is_readable() { match backend.readable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => {} + MuxResult::Upgrade => unreachable!(), // only frontend can upgrade MuxResult::CloseSession => return SessionResult::Close, } } @@ -890,7 +937,7 @@ impl SessionState for Mux { for token in &dead_backends { self.router.backends.remove(token); } - println_!("FRONTEND: {:#?}", &self.frontend); + println_!("FRONTEND: {:#?}", self.frontend); println_!("BACKENDS: {:#?}", self.router.backends); } @@ -902,6 +949,7 @@ impl SessionState for Mux { { MuxResult::Continue => {} MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Upgrade => return SessionResult::Upgrade, } } @@ -953,6 +1001,9 @@ impl SessionState for Mux { ) => { set_default_answer(stream, front_readiness, 401); } + BE::RetrieveClusterError(RetrieveClusterError::HttpsRedirect) => { + set_default_answer(stream, front_readiness, 301); + } BE::Backend(_) => {} BE::RetrieveClusterError(_) => unreachable!(), @@ -998,6 +1049,7 @@ impl SessionState for Mux { self.frontend.readiness() ); } + fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) { let s = match &mut self.frontend { Connection::H1(c) => &mut c.socket, @@ -1019,4 +1071,32 @@ impl SessionState for Mux { } } } + + fn shutting_down(&mut self) -> SessionIsToBeClosed { + let mut can_stop = true; + for stream in &mut self.context.streams { + match stream.state { + StreamState::Linked(_) => { + can_stop = false; + } + StreamState::Unlinked => { + let front = &stream.front; + let back = &stream.back; + kawa::debug_kawa(front); + kawa::debug_kawa(back); + if front.is_initial() + && front.storage.is_empty() + && back.is_initial() + && back.storage.is_empty() + { + continue; + } + stream.context.closing = true; + can_stop = false; + } + _ => {} + } + } + can_stop + } } From c0129fe7947fab27481a7329ebbd984380165c6f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 25 Sep 2023 11:42:07 +0200 Subject: [PATCH 26/44] H2 Continuation frames for Headers Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 3 +- lib/src/protocol/mux/h2.rs | 54 +++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index cb216ff41..44b3cc726 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -7,12 +7,11 @@ use crate::protocol::http::parser::compare_no_case; use super::{ parser::{FrameHeader, FrameType, H2Error}, serializer::{gen_frame_header, gen_rst_stream}, - StreamId, StreamState, + StreamId, }; pub struct H2BlockConverter<'a> { pub stream_id: StreamId, - pub state: StreamState, pub encoder: &'a mut hpack::Encoder<'static>, pub out: Vec, } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index a5ac18782..70ec25fed 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -7,7 +7,7 @@ use crate::{ println_, protocol::mux::{ converter, debug_kawa, forcefully_terminate_answer, - parser::{self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error}, + parser::{self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error, Headers}, pkawa, serializer, set_default_answer, update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, Endpoint, GenericHttpStream, GlobalStreamId, MuxResult, Position, StreamId, StreamState, @@ -34,6 +34,8 @@ pub enum H2State { ServerSettings, Header, Frame(FrameHeader), + ContinuationHeader(Headers), + ContinuationFrame(Headers), GoAway, Error, } @@ -68,16 +70,16 @@ pub struct ConnectionH2 { pub encoder: hpack::Encoder<'static>, pub expect_read: Option<(H2StreamId, usize)>, pub expect_write: Option, - pub position: Position, - pub readiness: Readiness, + pub last_stream_id: StreamId, pub local_settings: H2Settings, pub peer_settings: H2Settings, + pub position: Position, + pub readiness: Readiness, pub socket: Front, pub state: H2State, - pub last_stream_id: StreamId, pub streams: HashMap, - pub zero: GenericHttpStream, pub window: u32, + pub zero: GenericHttpStream, } impl std::fmt::Debug for ConnectionH2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -205,7 +207,7 @@ impl ConnectionH2 { self.state = H2State::ServerSettings; self.expect_write = Some(H2StreamId::Zero); - self.handle_frame(settings, context, endpoint); + return self.handle_frame(settings, context, endpoint); } (H2State::ServerSettings, Position::Client(_)) => { let i = kawa.storage.data(); @@ -260,6 +262,24 @@ impl ConnectionH2 { Err(e) => panic!("stream error: {:?}", error_nom_to_h2(e)), }; } + (H2State::ContinuationHeader(headers), _) => { + let i = kawa.storage.data(); + println_!(" continuation header: {i:?}"); + match parser::frame_header(i) { + Ok((_, header)) => { + println_!("{header:#?}"); + kawa.storage.end -= 9; + let stream_id = header.stream_id; + assert_eq!(stream_id, headers.stream_id); + self.expect_read = Some((H2StreamId::Zero, header.payload_len as usize)); + let mut headers = headers.clone(); + headers.end_headers = header.flags & 0x4 != 0; + headers.header_block_fragment.len += header.payload_len; + self.state = H2State::ContinuationFrame(headers); + } + Err(e) => panic!("stream error: {:?}", error_nom_to_h2(e)), + }; + } (H2State::Frame(header), _) => { let i = kawa.storage.data(); println_!(" data: {i:?}"); @@ -274,10 +294,17 @@ impl ConnectionH2 { if let H2StreamId::Zero = stream_id { kawa.storage.clear(); } - let state_result = self.handle_frame(frame, context, endpoint); self.state = H2State::Header; self.expect_read = Some((H2StreamId::Zero, 9)); - return state_result; + return self.handle_frame(frame, context, endpoint); + } + (H2State::ContinuationFrame(headers), _) => { + let i = kawa.storage.data(); + println_!(" data: {i:?}"); + let headers = headers.clone(); + self.state = H2State::Header; + self.expect_read = Some((H2StreamId::Zero, 9)); + return self.handle_frame(Frame::Headers(headers), context, endpoint); } } MuxResult::Continue @@ -345,7 +372,10 @@ impl ConnectionH2 { MuxResult::Continue } // Proxying states - (H2State::Header, _) | (H2State::Frame(_), _) => { + (H2State::Header, _) + | (H2State::Frame(_), _) + | (H2State::ContinuationFrame(_), _) + | (H2State::ContinuationHeader(_), _) => { let mut dead_streams = Vec::new(); if let Some(H2StreamId::Other(stream_id, global_stream_id)) = self.expect_write { @@ -379,7 +409,6 @@ impl ConnectionH2 { let mut converter = converter::H2BlockConverter { stream_id: 0, - state: StreamState::Idle, encoder: &mut self.encoder, out: Vec::new(), }; @@ -390,7 +419,6 @@ impl ConnectionH2 { 'outer: for stream_id in priorities { let global_stream_id = *self.streams.get(stream_id).unwrap(); let stream = &mut context.streams[global_stream_id]; - converter.state = stream.state; let kawa = stream.wbuffer(&self.position); if kawa.is_main_phase() || kawa.is_error() { converter.stream_id = *stream_id; @@ -525,8 +553,8 @@ impl ConnectionH2 { } Frame::Headers(headers) => { if !headers.end_headers { - todo!(); - // self.state = H2State::Continuation + self.state = H2State::ContinuationHeader(headers); + return MuxResult::Continue; } // can this fail? let global_stream_id = *self.streams.get(&headers.stream_id).unwrap(); From 388a3e2a50d8f20316e179d9c8753ac0868b03d9 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 27 Sep 2023 14:54:28 +0200 Subject: [PATCH 27/44] Add settings for connect protocol and no RFC 7540 priorities Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/h2.rs | 41 +++++++++++++++++++------------ lib/src/protocol/mux/mod.rs | 44 ++++++++++++++++++---------------- lib/src/protocol/mux/parser.rs | 1 - lib/src/protocol/mux/pkawa.rs | 2 ++ 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 70ec25fed..51e87acb8 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -48,6 +48,10 @@ pub struct H2Settings { pub settings_initial_window_size: u32, pub settings_max_frame_size: u32, pub settings_max_header_list_size: u32, + /// RFC 8441 + pub settings_enable_connect_protocol: bool, + /// RFC 9218 + pub settings_no_rfc7540_priorities: bool, } impl Default for H2Settings { @@ -55,15 +59,26 @@ impl Default for H2Settings { Self { settings_header_table_size: 4096, settings_enable_push: true, - settings_max_concurrent_streams: u32::MAX, + settings_max_concurrent_streams: 256, settings_initial_window_size: (1 << 16) - 1, settings_max_frame_size: 1 << 14, settings_max_header_list_size: u32::MAX, + settings_enable_connect_protocol: false, + settings_no_rfc7540_priorities: true, } } } -struct Prioriser {} +pub struct Prioriser {} + +impl Prioriser { + pub fn new() -> Self { + Self {} + } + pub fn push_priority(&mut self, priority: parser::Priority) { + println!("DEPRECATED: {priority:?}"); + } +} pub struct ConnectionH2 { pub decoder: hpack::Decoder<'static>, @@ -74,6 +89,7 @@ pub struct ConnectionH2 { pub local_settings: H2Settings, pub peer_settings: H2Settings, pub position: Position, + pub prioriser: Prioriser, pub readiness: Readiness, pub socket: Front, pub state: H2State, @@ -189,18 +205,10 @@ impl ConnectionH2 { Err(_) => return self.force_disconnect(), }; let kawa = &mut self.zero; - match serializer::gen_frame_header( - kawa.storage.space(), - &FrameHeader { - payload_len: 0, - frame_type: FrameType::Settings, - flags: 0, - stream_id: 0, - }, - ) { + match serializer::gen_settings(kawa.storage.space(), &self.local_settings) { Ok((_, size)) => kawa.storage.fill(size), Err(e) => { - println!("could not serialize HeaderFrame: {e:?}"); + println!("could not serialize SettingsFrame: {e:?}"); return self.force_disconnect(); } }; @@ -594,7 +602,7 @@ impl ConnectionH2 { return self.goaway(H2Error::ProtocolError); } }, - Frame::Priority(priority) => (), + Frame::Priority(priority) => self.prioriser.push_priority(priority), Frame::RstStream(rst_stream) => { println_!( "RstStream({} -> {})", @@ -608,15 +616,18 @@ impl ConnectionH2 { return MuxResult::Continue; } for setting in settings.settings { - match setting.identifier { + #[rustfmt::skip] + let _ = match setting.identifier { 1 => self.peer_settings.settings_header_table_size = setting.value, 2 => self.peer_settings.settings_enable_push = setting.value == 1, 3 => self.peer_settings.settings_max_concurrent_streams = setting.value, 4 => self.peer_settings.settings_initial_window_size = setting.value, 5 => self.peer_settings.settings_max_frame_size = setting.value, 6 => self.peer_settings.settings_max_header_list_size = setting.value, + 8 => self.peer_settings.settings_enable_connect_protocol = setting.value == 1, + 9 => self.peer_settings.settings_no_rfc7540_priorities = setting.value == 1, other => println!("unknown setting_id: {other}, we MUST ignore this"), - } + }; } println_!("{:#?}", self.peer_settings); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 41f3ab1e6..4d2a1d774 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -34,6 +34,8 @@ use crate::{ pub use crate::protocol::mux::{h1::ConnectionH1, h2::ConnectionH2}; +use self::h2::Prioriser; + #[macro_export] macro_rules! println_ { ($($t:expr),*) => { @@ -208,23 +210,24 @@ impl Connection { .upgrade() .and_then(|pool| pool.borrow_mut().checkout())?; Some(Connection::H2(ConnectionH2 { - socket: front_stream, + decoder: hpack::Decoder::new(), + encoder: hpack::Encoder::new(), + expect_read: Some((H2StreamId::Zero, 24 + 9)), + expect_write: None, + last_stream_id: 0, + local_settings: H2Settings::default(), + peer_settings: H2Settings::default(), position: Position::Server, + prioriser: Prioriser::new(), readiness: Readiness { interest: Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - streams: HashMap::new(), + socket: front_stream, state: H2State::ClientPreface, - expect_read: Some((H2StreamId::Zero, 24 + 9)), - expect_write: None, - local_settings: H2Settings::default(), - peer_settings: H2Settings::default(), - zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), + streams: HashMap::new(), window: 1 << 16, - decoder: hpack::Decoder::new(), - encoder: hpack::Encoder::new(), - last_stream_id: 0, + zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), })) } pub fn new_h2_client( @@ -236,23 +239,24 @@ impl Connection { .upgrade() .and_then(|pool| pool.borrow_mut().checkout())?; Some(Connection::H2(ConnectionH2 { - socket: front_stream, + decoder: hpack::Decoder::new(), + encoder: hpack::Encoder::new(), + expect_read: None, + expect_write: None, + last_stream_id: 0, + local_settings: H2Settings::default(), + peer_settings: H2Settings::default(), position: Position::Client(BackendStatus::Connecting(cluster_id)), + prioriser: Prioriser::new(), readiness: Readiness { interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - streams: HashMap::new(), + socket: front_stream, state: H2State::ClientPreface, - expect_read: None, - expect_write: None, - local_settings: H2Settings::default(), - peer_settings: H2Settings::default(), - zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), + streams: HashMap::new(), window: 1 << 16, - decoder: hpack::Decoder::new(), - encoder: hpack::Encoder::new(), - last_stream_id: 0, + zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), })) } diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index 05071aa0e..b0285bc06 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -391,7 +391,6 @@ pub fn headers_frame<'a>( stream_id: header.stream_id, stream_dependency, weight, - // header_block_fragment, header_block_fragment: Slice::new(input, header_block_fragment), end_stream: header.flags & 0x1 != 0, end_headers: header.flags & 0x4 != 0, diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index cb047ca3e..20b03571a 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -46,6 +46,8 @@ pub fn handle_header( scheme = val; } else if compare_no_case(&k, b"cookie") { todo!("cookies should be split in pairs"); + } else if compare_no_case(&k, b"priority") { + unimplemented!(); } else { if compare_no_case(&k, b"content-length") { let length = From ed56c8b04e597782d72345b3c9c33188bd8dcf85 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 29 Sep 2023 15:18:25 +0200 Subject: [PATCH 28/44] Introduce timeouts in Mux State Signed-off-by: Eloi DEMOLIS --- lib/src/http.rs | 168 ++++++++++++++---------------------- lib/src/https.rs | 128 +++++++++++++++------------ lib/src/protocol/mux/h1.rs | 4 +- lib/src/protocol/mux/h2.rs | 2 + lib/src/protocol/mux/mod.rs | 135 ++++++++++++++++++++++++++--- 5 files changed, 272 insertions(+), 165 deletions(-) diff --git a/lib/src/http.rs b/lib/src/http.rs index b09b30d10..73da438c9 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -38,7 +38,7 @@ use crate::{ }, mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, - Http, Pipe, SessionState, + Pipe, SessionState, }, router::{Route, Router}, server::{ListenToken, SessionManager}, @@ -123,15 +123,21 @@ impl HttpSession { gauge_add!("protocol.http", 1); let session_address = sock.peer_addr().ok(); + let frontend = mux::Connection::new_h1_server(sock, container_frontend_timeout); + let router = mux::Router::new( + configured_backend_timeout, + configured_connect_timeout, + listener.clone(), + ); let mut context = mux::Context::new(pool.clone()); context .create_stream(request_id, 1 << 16) .ok_or(AcceptError::BufferCapacityReached)?; - let frontend = mux::Connection::new_h1_server(sock); HttpStateMachine::Mux(Mux { + configured_frontend_timeout, frontend_token: token, frontend, - router: mux::Router::new(listener.clone()), + router, public_address, peer_address: session_address, sticky_name: sticky_name.clone(), @@ -204,130 +210,91 @@ impl HttpSession { .map(|add| (add.destination(), add.source())) { Some((Some(public_address), Some(session_address))) => { - let mut http = Http::new( - self.answers.clone(), + let frontend = mux::Connection::new_h1_server( + expect.frontend, + expect.container_frontend_timeout, + ); + let router = mux::Router::new( self.configured_backend_timeout, self.configured_connect_timeout, - self.configured_frontend_timeout, - expect.container_frontend_timeout, - expect.frontend, - expect.frontend_token, self.listener.clone(), - self.pool.clone(), - Protocol::HTTP, + ); + let mut context = mux::Context::new(self.pool.clone()); + context.create_stream(expect.request_id, 1 << 16)?; + let mut mux = Mux { + configured_frontend_timeout: self.configured_frontend_timeout, + frontend_token: self.frontend_token, + frontend, + router, public_address, - expect.request_id, - Some(session_address), - self.sticky_name.clone(), - ) - .ok()?; - http.frontend_readiness.event = expect.frontend_readiness.event; + peer_address: Some(session_address), + sticky_name: self.sticky_name.clone(), + context, + }; + mux.frontend.readiness_mut().event = expect.frontend_readiness.event; gauge_add!("protocol.proxy.expect", -1); gauge_add!("protocol.http", 1); - unimplemented!(); - // Some(HttpStateMachine::Http(http)) + Some(HttpStateMachine::Mux(mux)) } _ => None, } } - fn upgrade_http(&mut self, http: Http) -> Option { - debug!("http switching to ws"); - let front_token = self.frontend_token; - let back_token = match http.backend_token { - Some(back_token) => back_token, - None => { - warn!( - "Could not upgrade http request on cluster '{:?}' ({:?}) using backend '{:?}' into websocket for request '{}'", - http.context.cluster_id, self.frontend_token, http.context.backend_id, http.context.id - ); - return None; - } - }; - let ws_context = http.context.websocket_context(); - - let mut container_frontend_timeout = http.container_frontend_timeout; - let mut container_backend_timeout = http.container_backend_timeout; - container_frontend_timeout.reset(); - container_backend_timeout.reset(); - - let backend_buffer = if let ResponseStream::BackendAnswer(kawa) = http.response_stream { - kawa.storage.buffer - } else { - return None; - }; - - let mut pipe = Pipe::new( - backend_buffer, - http.context.backend_id, - http.backend_socket, - http.backend, - Some(container_backend_timeout), - Some(container_frontend_timeout), - http.context.cluster_id, - http.request_stream.storage.buffer, - front_token, - http.frontend_socket, - self.listener.clone(), - Protocol::HTTP, - http.context.id, - http.context.session_address, - ws_context, - ); - - pipe.frontend_readiness.event = http.frontend_readiness.event; - pipe.backend_readiness.event = http.backend_readiness.event; - pipe.set_back_token(back_token); - - gauge_add!("protocol.http", -1); - gauge_add!("protocol.ws", 1); - gauge_add!("http.active_requests", -1); - gauge_add!("websocket.active_requests", 1); - Some(HttpStateMachine::WebSocket(pipe)) - } - fn upgrade_mux(&mut self, mut mux: Mux) -> Option { debug!("mux switching to ws"); let stream = mux.context.streams.pop().unwrap(); - let (frontend_readiness, frontend_socket) = match mux.frontend { - mux::Connection::H1(mux::ConnectionH1 { - readiness, socket, .. - }) => (readiness, socket), - // only h1<->h1 connections can upgrade to websocket - mux::Connection::H2(_) => unreachable!(), - }; + let (frontend_readiness, frontend_socket, mut container_frontend_timeout) = + match mux.frontend { + mux::Connection::H1(mux::ConnectionH1 { + readiness, + socket, + timeout_container, + .. + }) => (readiness, socket, timeout_container), + mux::Connection::H2(_) => { + error!("Only h1<->h1 connections can upgrade to websocket"); + return None; + } + }; - let mux::StreamState::Linked(back_token) = stream.state else { unreachable!() }; - let backend = mux.router.backends.remove(&back_token).unwrap(); - let (cluster_id, backend_readiness, backend_socket) = match backend { - mux::Connection::H1(mux::ConnectionH1 { - position: mux::Position::Client(mux::BackendStatus::Connected(cluster_id)), - readiness, - socket, - .. - }) => (cluster_id, readiness, socket), - // the backend disconnected just after upgrade, abort - mux::Connection::H1(_) => return None, - // only h1<->h1 connections can upgrade to websocket - mux::Connection::H2(_) => unreachable!(), + let mux::StreamState::Linked(back_token) = stream.state else { + error!("Upgrading stream should be linked to a backend"); + return None; }; + let backend = mux.router.backends.remove(&back_token).unwrap(); + let (cluster_id, backend_readiness, backend_socket, mut container_backend_timeout) = + match backend { + mux::Connection::H1(mux::ConnectionH1 { + position: mux::Position::Client(mux::BackendStatus::Connected(cluster_id)), + readiness, + socket, + timeout_container, + .. + }) => (cluster_id, readiness, socket, timeout_container), + mux::Connection::H1(_) => { + error!("The backend disconnected just after upgrade, abort"); + return None; + } + mux::Connection::H2(_) => { + error!("Only h1<->h1 connections can upgrade to websocket"); + return None; + } + }; let ws_context = stream.context.websocket_context(); - // let mut container_frontend_timeout = http.container_frontend_timeout; - // let mut container_backend_timeout = http.container_backend_timeout; - // container_frontend_timeout.reset(); - // container_backend_timeout.reset(); + container_frontend_timeout.reset(); + container_backend_timeout.reset(); let mut pipe = Pipe::new( stream.back.storage.buffer, None, Some(backend_socket), None, - None, - None, + Some(container_backend_timeout), + Some(container_frontend_timeout), Some(cluster_id), stream.front.storage.buffer, self.frontend_token, @@ -369,7 +336,6 @@ impl ProxySession for HttpSession { // Restore gauges match self.state.marker() { StateMarker::Expect => gauge_add!("protocol.proxy.expect", -1), - // StateMarker::Http => gauge_add!("protocol.http", -1), StateMarker::Mux => gauge_add!("protocol.http", -1), StateMarker::WebSocket => { gauge_add!("protocol.ws", -1); diff --git a/lib/src/https.rs b/lib/src/https.rs index 4ef48d1ab..3bf7569a4 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -53,7 +53,6 @@ use crate::{ backends::BackendMap, pool::Pool, protocol::{ - h2::Http2, http::{ answers::HttpAnswers, parser::{hostname_and_port, Method}, @@ -62,7 +61,7 @@ use crate::{ mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, rustls::TlsHandshake, - Http, Pipe, SessionState, + Pipe, SessionState, }, router::{Route, Router}, server::{ListenToken, SessionManager}, @@ -93,9 +92,9 @@ StateMachineBuilder! { Expect(ExpectProxyProtocol, ServerConnection), Handshake(TlsHandshake), Mux(Mux), - Http(Http), + // Http(Http), WebSocket(Pipe), - Http2(Http2) -> todo!("H2"), + // Http2(Http2) -> todo!("H2"), } } @@ -190,9 +189,9 @@ impl HttpsSession { let new_state = match self.state.take() { HttpsStateMachine::Expect(expect, ssl) => self.upgrade_expect(expect, ssl), HttpsStateMachine::Handshake(handshake) => self.upgrade_handshake(handshake), - HttpsStateMachine::Http(http) => self.upgrade_http(http), - HttpsStateMachine::Mux(_) => unimplemented!(), - HttpsStateMachine::Http2(_) => self.upgrade_http2(), + // HttpsStateMachine::Http(http) => self.upgrade_http(http), + HttpsStateMachine::Mux(mux) => self.upgrade_mux(mux), + // HttpsStateMachine::Http2(_) => self.upgrade_http2(), HttpsStateMachine::WebSocket(wss) => self.upgrade_websocket(wss), HttpsStateMachine::FailedUpgrade(_) => unreachable!(), }; @@ -289,74 +288,105 @@ impl HttpsSession { gauge_add!("protocol.tls.handshake", -1); + let router = mux::Router::new( + self.configured_backend_timeout, + self.configured_connect_timeout, + self.listener.clone(), + ); let mut context = mux::Context::new(self.pool.clone()); let mut frontend = match alpn { AlpnProtocol::Http11 => { context.create_stream(handshake.request_id, 1 << 16)?; - mux::Connection::new_h1_server(front_stream) + mux::Connection::new_h1_server(front_stream, handshake.container_frontend_timeout) } - AlpnProtocol::H2 => mux::Connection::new_h2_server(front_stream, self.pool.clone())?, + AlpnProtocol::H2 => mux::Connection::new_h2_server( + front_stream, + self.pool.clone(), + handshake.container_frontend_timeout, + )?, }; frontend.readiness_mut().event = handshake.frontend_readiness.event; gauge_add!("protocol.https", 1); Some(HttpsStateMachine::Mux(Mux { + configured_frontend_timeout: self.configured_frontend_timeout, frontend_token: self.frontend_token, frontend, context, - router: mux::Router::new(self.listener.clone()), + router, public_address: self.public_address, peer_address: self.peer_address, sticky_name: self.sticky_name.clone(), })) } - fn upgrade_http(&self, http: Http) -> Option { - debug!("https switching to wss"); - let front_token = self.frontend_token; - let back_token = match http.backend_token { - Some(back_token) => back_token, - None => { - warn!( - "Could not upgrade https request on cluster '{:?}' ({:?}) using backend '{:?}' into secure websocket for request '{}'", - http.context.cluster_id, self.frontend_token, http.context.backend_id, http.context.id - ); - return None; - } - }; - let ws_context = http.context.websocket_context(); + fn upgrade_mux(&self, mut mux: Mux) -> Option { + debug!("mux switching to wss"); + let stream = mux.context.streams.pop().unwrap(); - let mut container_frontend_timeout = http.container_frontend_timeout; - let mut container_backend_timeout = http.container_backend_timeout; - container_frontend_timeout.reset(); - container_backend_timeout.reset(); + let (frontend_readiness, frontend_socket, mut container_frontend_timeout) = + match mux.frontend { + mux::Connection::H1(mux::ConnectionH1 { + readiness, + socket, + timeout_container, + .. + }) => (readiness, socket, timeout_container), + mux::Connection::H2(_) => { + error!("Only h1<->h1 connections can upgrade to websocket"); + return None; + } + }; - let backend_buffer = if let ResponseStream::BackendAnswer(kawa) = http.response_stream { - kawa.storage.buffer - } else { + let mux::StreamState::Linked(back_token) = stream.state else { + error!("Upgrading stream should be linked to a backend"); return None; }; + let backend = mux.router.backends.remove(&back_token).unwrap(); + let (cluster_id, backend_readiness, backend_socket, mut container_backend_timeout) = + match backend { + mux::Connection::H1(mux::ConnectionH1 { + position: mux::Position::Client(mux::BackendStatus::Connected(cluster_id)), + readiness, + socket, + timeout_container, + .. + }) => (cluster_id, readiness, socket, timeout_container), + mux::Connection::H1(_) => { + error!("The backend disconnected just after upgrade, abort"); + return None; + } + mux::Connection::H2(_) => { + error!("Only h1<->h1 connections can upgrade to websocket"); + return None; + } + }; + + let ws_context = stream.context.websocket_context(); + + container_frontend_timeout.reset(); + container_backend_timeout.reset(); let mut pipe = Pipe::new( - backend_buffer, - http.context.backend_id, - http.backend_socket, - http.backend, + stream.back.storage.buffer, + None, + Some(backend_socket), + None, Some(container_backend_timeout), Some(container_frontend_timeout), - http.context.cluster_id, - http.request_stream.storage.buffer, - front_token, - http.frontend_socket, + Some(cluster_id), + stream.front.storage.buffer, + self.frontend_token, + frontend_socket, self.listener.clone(), - Protocol::HTTP, - http.context.id, - http.context.session_address, + Protocol::HTTPS, + stream.context.id, + stream.context.session_address, ws_context, ); - pipe.frontend_readiness.event = http.frontend_readiness.event; - pipe.backend_readiness.event = http.backend_readiness.event; + pipe.frontend_readiness.event = frontend_readiness.event; + pipe.backend_readiness.event = backend_readiness.event; pipe.set_back_token(back_token); gauge_add!("protocol.https", -1); @@ -366,10 +396,6 @@ impl HttpsSession { Some(HttpsStateMachine::WebSocket(pipe)) } - fn upgrade_http2(&self) -> Option { - todo!() - } - fn upgrade_websocket( &self, wss: Pipe, @@ -393,23 +419,19 @@ impl ProxySession for HttpsSession { match self.state.marker() { StateMarker::Expect => gauge_add!("protocol.proxy.expect", -1), StateMarker::Handshake => gauge_add!("protocol.tls.handshake", -1), - StateMarker::Http => gauge_add!("protocol.https", -1), StateMarker::Mux => gauge_add!("protocol.https", -1), StateMarker::WebSocket => { gauge_add!("protocol.wss", -1); gauge_add!("websocket.active_requests", -1); } - StateMarker::Http2 => gauge_add!("protocol.http2", -1), } if self.state.failed() { match self.state.marker() { StateMarker::Expect => incr!("https.upgrade.expect.failed"), StateMarker::Handshake => incr!("https.upgrade.handshake.failed"), - StateMarker::Http => incr!("https.upgrade.http.failed"), - StateMarker::WebSocket => incr!("https.upgrade.wss.failed"), - StateMarker::Http2 => incr!("https.upgrade.http2.failed"), StateMarker::Mux => incr!("https.upgrade.mux.failed"), + StateMarker::WebSocket => incr!("https.upgrade.wss.failed"), } return; } diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 33c806e31..753cb3517 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -8,16 +8,18 @@ use crate::{ Position, StreamState, }, socket::SocketHandler, + timer::TimeoutContainer, Readiness, }; pub struct ConnectionH1 { pub position: Position, pub readiness: Readiness, + pub requests: usize, pub socket: Front, /// note: a Server H1 will always reference stream 0, but a client can reference any stream pub stream: GlobalStreamId, - pub requests: usize, + pub timeout_container: TimeoutContainer, } impl std::fmt::Debug for ConnectionH1 { diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 51e87acb8..a2977d43c 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -13,6 +13,7 @@ use crate::{ GlobalStreamId, MuxResult, Position, StreamId, StreamState, }, socket::SocketHandler, + timer::TimeoutContainer, Readiness, }; @@ -94,6 +95,7 @@ pub struct ConnectionH2 { pub socket: Front, pub state: H2State, pub streams: HashMap, + pub timeout_container: TimeoutContainer, pub window: u32, pub zero: GenericHttpStream, } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 4d2a1d774..6bce67202 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -4,6 +4,7 @@ use std::{ io::Write, net::SocketAddr, rc::{Rc, Weak}, + time::Duration, }; use mio::{net::TcpStream, Interest, Token}; @@ -28,6 +29,7 @@ use crate::{ router::Route, server::CONN_RETRIES, socket::{SocketHandler, SocketResult}, + timer::TimeoutContainer, BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, RetrieveClusterError, SessionIsToBeClosed, SessionMetrics, SessionResult, StateResult, }; @@ -177,19 +179,27 @@ pub enum Connection { } impl Connection { - pub fn new_h1_server(front_stream: Front) -> Connection { + pub fn new_h1_server( + front_stream: Front, + timeout_container: TimeoutContainer, + ) -> Connection { Connection::H1(ConnectionH1 { - socket: front_stream, position: Position::Server, readiness: Readiness { interest: Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - stream: 0, requests: 0, + socket: front_stream, + stream: 0, + timeout_container, }) } - pub fn new_h1_client(front_stream: Front, cluster_id: String) -> Connection { + pub fn new_h1_client( + front_stream: Front, + cluster_id: String, + timeout_container: TimeoutContainer, + ) -> Connection { Connection::H1(ConnectionH1 { socket: front_stream, position: Position::Client(BackendStatus::Connecting(cluster_id)), @@ -199,12 +209,14 @@ impl Connection { }, stream: 0, requests: 0, + timeout_container, }) } pub fn new_h2_server( front_stream: Front, pool: Weak>, + timeout_container: TimeoutContainer, ) -> Option> { let buffer = pool .upgrade() @@ -226,6 +238,7 @@ impl Connection { socket: front_stream, state: H2State::ClientPreface, streams: HashMap::new(), + timeout_container, window: 1 << 16, zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), })) @@ -234,6 +247,7 @@ impl Connection { front_stream: Front, cluster_id: String, pool: Weak>, + timeout_container: TimeoutContainer, ) -> Option> { let buffer = pool .upgrade() @@ -255,6 +269,7 @@ impl Connection { socket: front_stream, state: H2State::ClientPreface, streams: HashMap::new(), + timeout_container, window: 1 << 16, zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), })) @@ -284,6 +299,12 @@ impl Connection { Connection::H2(c) => &mut c.position, } } + pub fn timeout_container(&mut self) -> &mut TimeoutContainer { + match self { + Connection::H1(c) => &mut c.timeout_container, + Connection::H2(c) => &mut c.timeout_container, + } + } pub fn socket(&self) -> &TcpStream { match self { Connection::H1(c) => c.socket.socket_ref(), @@ -596,15 +617,23 @@ impl Context { } pub struct Router { - pub listener: Rc>, pub backends: HashMap>, + pub configured_backend_timeout: Duration, + pub configured_connect_timeout: Duration, + pub listener: Rc>, } impl Router { - pub fn new(listener: Rc>) -> Self { + pub fn new( + configured_backend_timeout: Duration, + configured_connect_timeout: Duration, + listener: Rc>, + ) -> Self { Self { - listener, backends: HashMap::new(), + configured_backend_timeout, + configured_connect_timeout, + listener, } } @@ -717,13 +746,19 @@ impl Router { error!("error registering back socket({:?}): {:?}", socket, e); } + let timeout_container = TimeoutContainer::new(self.configured_connect_timeout, token); let connection = if h2 { - match Connection::new_h2_client(socket, cluster_id, context.pool.clone()) { + match Connection::new_h2_client( + socket, + cluster_id, + context.pool.clone(), + timeout_container, + ) { Some(connection) => connection, None => return Err(BackendConnectionError::MaxBuffers), } } else { - Connection::new_h1_client(socket, cluster_id) + Connection::new_h1_client(socket, cluster_id, timeout_container) }; self.backends.insert(token, connection); token @@ -844,6 +879,7 @@ impl Router { } pub struct Mux { + pub configured_frontend_timeout: Duration, pub frontend_token: Token, pub frontend: Connection, pub router: Router, @@ -1035,11 +1071,90 @@ impl SessionState for Mux { fn timeout(&mut self, token: Token, _metrics: &mut SessionMetrics) -> StateResult { println_!("MuxState::timeout({token:?})"); - StateResult::CloseSession + let front_is_h2 = match self.frontend { + Connection::H1(_) => false, + Connection::H2(_) => true, + }; + let mut is_to_be_closed = true; + if self.frontend_token == token { + self.frontend.timeout_container().triggered(); + let front_readiness = self.frontend.readiness_mut(); + for stream in &mut self.context.streams { + match stream.state { + StreamState::Idle => { + // In h1 an Idle stream is always the first request, so we can send a 408 + // In h2 an Idle stream doesn't necessarily hold a request yet, + // in most cases it was just reserved, so we can just ignore them. + if !front_is_h2 { + set_default_answer(stream, front_readiness, 408); + is_to_be_closed = false; + } + } + StreamState::Link => { + // This is an unusual case, as we have both a complete request and no + // available backend yet. For now, we answer with 503 + set_default_answer(stream, front_readiness, 503); + is_to_be_closed = false; + } + StreamState::Linked(_) => { + // A stream Linked to a backend is waiting for the response, not the answer. + // For streaming or malformed requests, it is possible that the request is not + // terminated at this point. For now, we do nothing and + is_to_be_closed = false; + } + StreamState::Unlinked => { + // A stream Unlinked already has a response and its backend closed. + // In case it hasn't finished proxying we wait. Otherwise it is a stream + // kept alive for a new request, which can be killed. + if !stream.back.is_completed() { + is_to_be_closed = false; + } + } + StreamState::Recycle => { + // A recycled stream is an h2 stream which doesn't hold a request anymore. + // We can ignore it. + } + } + } + } else if let Some(backend) = self.router.backends.get_mut(&token) { + backend.timeout_container().triggered(); + let front_readiness = self.frontend.readiness_mut(); + for stream_id in 0..self.context.streams.len() { + let stream = &mut self.context.streams[stream_id]; + if let StreamState::Linked(back_token) = stream.state { + if token == back_token { + // This stream is linked to the backend that timedout. + if stream.back.is_terminated() || stream.back.is_error() { + // Nothing to do, simply wait for the remaining bytes to be proxied + if !stream.back.is_completed() { + is_to_be_closed = false; + } + } else if stream.back.is_initial() { + // The response has not started yet + set_default_answer(stream, front_readiness, 504); + is_to_be_closed = false; + } else { + forcefully_terminate_answer(stream, front_readiness); + is_to_be_closed = false; + } + backend.end_stream(0, &mut self.context); + } + } + } + } + if is_to_be_closed { + StateResult::CloseSession + } else { + StateResult::Continue + } } fn cancel_timeouts(&mut self) { println_!("MuxState::cancel_timeouts"); + self.frontend.timeout_container().cancel(); + for backend in self.router.backends.values_mut() { + backend.timeout_container().cancel(); + } } fn print_state(&self, context: &str) { From eb46c0061713c81494b526fab0be52778db5a91d Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 29 Sep 2023 17:50:33 +0200 Subject: [PATCH 29/44] Make gRPC work through Mux! - Fix readiness (set opposite endpoint as writable, after a read) - Fix timeouts (with force_disconnect) - Fix flags and phase handling for h2 headers - Add support for h2 trailers in pkawa Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/h1.rs | 22 +++++------- lib/src/protocol/mux/h2.rs | 46 ++++++++++++------------- lib/src/protocol/mux/mod.rs | 64 +++++++++++++++++++++++++++-------- lib/src/protocol/mux/pkawa.rs | 58 ++++++++++++++++++++++--------- 4 files changed, 120 insertions(+), 70 deletions(-) diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 753cb3517..189f39b5e 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -70,27 +70,21 @@ impl ConnectionH1 { self.readiness.interest.remove(Ready::READABLE); } if kawa.is_main_phase() { + if let StreamState::Linked(token) = stream.state { + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } match self.position { - Position::Client(_) => { - let StreamState::Linked(token) = stream.state else { unreachable!() }; - endpoint - .readiness_mut(token) - .interest - .insert(Ready::WRITABLE) - } Position::Server => { - if let StreamState::Linked(token) = stream.state { - endpoint - .readiness_mut(token) - .interest - .insert(Ready::WRITABLE) - } if was_initial { self.requests += 1; println_!("REQUESTS: {}", self.requests); stream.state = StreamState::Link } } + Position::Client(_) => {} } }; MuxResult::Continue @@ -177,7 +171,7 @@ impl ConnectionH1 { MuxResult::Continue } - fn force_disconnect(&mut self) -> MuxResult { + pub fn force_disconnect(&mut self) -> MuxResult { match self.position { Position::Client(_) => { self.position = Position::Client(BackendStatus::Disconnecting); diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index a2977d43c..15ca91c8f 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -535,9 +535,6 @@ impl ConnectionH2 { let kawa = stream.rbuffer(&self.position); slice.start += kawa.storage.head as u32; kawa.storage.head += slice.len(); - let buffer = kawa.storage.buffer(); - let payload = slice.data(buffer); - println_!("{:?}", unsafe { from_utf8_unchecked(payload) }); kawa.push_block(kawa::Block::Chunk(kawa::Chunk { data: kawa::Store::Slice(slice), })); @@ -550,16 +547,12 @@ impl ConnectionH2 { })); kawa.parsing_phase = kawa::ParsingPhase::Terminated; } - match self.position { - Position::Client(_) => { - let StreamState::Linked(token) = stream.state else { unreachable!() }; - endpoint - .readiness_mut(token) - .interest - .insert(Ready::WRITABLE) - } - Position::Server => {} - }; + if let StreamState::Linked(token) = stream.state { + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } } Frame::Headers(headers) => { if !headers.end_headers { @@ -572,6 +565,7 @@ impl ConnectionH2 { let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); let stream = &mut context.streams[global_stream_id]; let parts = &mut stream.split(&self.position); + let was_initial = parts.rbuffer.is_initial(); pkawa::handle_header( parts.rbuffer, buffer, @@ -580,16 +574,18 @@ impl ConnectionH2 { parts.context, ); debug_kawa(parts.rbuffer); - match self.position { - Position::Client(_) => { - let StreamState::Linked(token) = stream.state else { unreachable!() }; - endpoint - .readiness_mut(token) - .interest - .insert(Ready::WRITABLE) - } - Position::Server => stream.state = StreamState::Link, - }; + if let StreamState::Linked(token) = stream.state { + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } + if was_initial { + match self.position { + Position::Server => stream.state = StreamState::Link, + Position::Client(_) => {} + }; + } } Frame::PushPromise(push_promise) => match self.position { Position::Client(_) => { @@ -673,12 +669,12 @@ impl ConnectionH2 { } } } - Frame::Continuation(_) => todo!(), + Frame::Continuation(_) => unreachable!(), } MuxResult::Continue } - fn force_disconnect(&mut self) -> MuxResult { + pub fn force_disconnect(&mut self) -> MuxResult { self.state = H2State::Error; match self.position { Position::Client(_) => { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 6bce67202..ffaf4e818 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -207,7 +207,7 @@ impl Connection { interest: Ready::WRITABLE | Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, - stream: 0, + stream: usize::MAX - 1, requests: 0, timeout_container, }) @@ -311,6 +311,12 @@ impl Connection { Connection::H2(c) => c.socket.socket_ref(), } } + fn force_disconnect(&mut self) -> MuxResult { + match self { + Connection::H1(c) => c.force_disconnect(), + Connection::H2(c) => c.force_disconnect(), + } + } fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where E: Endpoint, @@ -1075,8 +1081,10 @@ impl SessionState for Mux { Connection::H1(_) => false, Connection::H2(_) => true, }; - let mut is_to_be_closed = true; + let mut should_close = true; + let mut should_write = false; if self.frontend_token == token { + println_!("MuxState::timeout_frontend({:#?})", self.frontend); self.frontend.timeout_container().triggered(); let front_readiness = self.frontend.readiness_mut(); for stream in &mut self.context.streams { @@ -1087,27 +1095,27 @@ impl SessionState for Mux { // in most cases it was just reserved, so we can just ignore them. if !front_is_h2 { set_default_answer(stream, front_readiness, 408); - is_to_be_closed = false; + should_write = true; } } StreamState::Link => { // This is an unusual case, as we have both a complete request and no // available backend yet. For now, we answer with 503 set_default_answer(stream, front_readiness, 503); - is_to_be_closed = false; + should_write = true; } StreamState::Linked(_) => { // A stream Linked to a backend is waiting for the response, not the answer. // For streaming or malformed requests, it is possible that the request is not // terminated at this point. For now, we do nothing and - is_to_be_closed = false; + should_close = false; } StreamState::Unlinked => { // A stream Unlinked already has a response and its backend closed. // In case it hasn't finished proxying we wait. Otherwise it is a stream // kept alive for a new request, which can be killed. if !stream.back.is_completed() { - is_to_be_closed = false; + should_close = false; } } StreamState::Recycle => { @@ -1117,6 +1125,7 @@ impl SessionState for Mux { } } } else if let Some(backend) = self.router.backends.get_mut(&token) { + println_!("MuxState::timeout_backend({:#?})", backend); backend.timeout_container().triggered(); let front_readiness = self.frontend.readiness_mut(); for stream_id in 0..self.context.streams.len() { @@ -1125,24 +1134,40 @@ impl SessionState for Mux { if token == back_token { // This stream is linked to the backend that timedout. if stream.back.is_terminated() || stream.back.is_error() { + println!( + "Stream terminated or in error, do nothing, just wait a bit more" + ); // Nothing to do, simply wait for the remaining bytes to be proxied if !stream.back.is_completed() { - is_to_be_closed = false; + should_close = false; } } else if stream.back.is_initial() { // The response has not started yet + println!("Stream still waiting for response, send 504"); set_default_answer(stream, front_readiness, 504); - is_to_be_closed = false; + should_write = true; } else { + println!("Stream waiting for end of response, forcefully terminate it"); forcefully_terminate_answer(stream, front_readiness); - is_to_be_closed = false; + should_write = true; } - backend.end_stream(0, &mut self.context); + backend.end_stream(stream_id, &mut self.context); + backend.force_disconnect(); } } } } - if is_to_be_closed { + if should_write { + return match self + .frontend + .writable(&mut self.context, EndpointClient(&mut self.router)) + { + MuxResult::Continue => StateResult::Continue, + MuxResult::Upgrade => StateResult::Upgrade, + MuxResult::CloseSession => StateResult::CloseSession, + }; + } + if should_close { StateResult::CloseSession } else { StateResult::Continue @@ -1162,11 +1187,19 @@ impl SessionState for Mux { "\ {} Session(Mux) \tFrontend: -\t\ttoken: {:?}\treadiness: {:?}", +\t\ttoken: {:?}\treadiness: {:?} +\tBackend(s):", context, self.frontend_token, self.frontend.readiness() ); + for (backend_token, backend) in &self.router.backends { + error!( + "\t\ttoken: {:?}\treadiness: {:?}", + backend_token, + backend.readiness() + ) + } } fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) { @@ -1184,9 +1217,10 @@ impl SessionState for Mux { let out = kawa.as_io_slice(); let mut writer = std::io::BufWriter::new(Vec::new()); let amount = writer.write_vectored(&out).unwrap(); - println_!("amount: {amount}\n{}", unsafe { - std::str::from_utf8_unchecked(writer.buffer()) - }); + println_!( + "amount: {amount}\n{}", + String::from_utf8_lossy(writer.buffer()) + ); } } } diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index 20b03571a..fbb49e5da 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -18,6 +18,9 @@ pub fn handle_header( ) where C: ParserCallbacks, { + if !kawa.is_initial() { + return handle_trailer(kawa, input, end_stream, decoder); + } kawa.push_block(Block::StatusLine); kawa.detached.status_line = match kawa.kind { Kind::Request => { @@ -126,6 +129,7 @@ pub fn handle_header( if end_stream { if let BodySize::Empty = kawa.body_size { + kawa.body_size = BodySize::Length(0); kawa.push_block(Block::Header(Pair { key: Store::Static(b"Content-Length"), val: Store::Static(b"0"), @@ -134,22 +138,12 @@ pub fn handle_header( } kawa.push_block(Block::Flags(Flags { - end_body: false, + end_body: end_stream, end_chunk: false, end_header: true, - end_stream: false, + end_stream, })); - if end_stream { - kawa.push_block(Block::Flags(Flags { - end_body: true, - end_chunk: false, - end_header: false, - end_stream: true, - })); - kawa.body_size = BodySize::Length(0); - } - if kawa.parsing_phase == ParsingPhase::Terminated { return; } @@ -158,9 +152,41 @@ pub fn handle_header( BodySize::Chunked => ParsingPhase::Chunks { first: true }, BodySize::Length(0) => ParsingPhase::Terminated, BodySize::Length(_) => ParsingPhase::Body, - BodySize::Empty => { - println!("HTTP is just the worst..."); - ParsingPhase::Body - } + BodySize::Empty => ParsingPhase::Chunks { first: true }, }; } + +pub fn handle_trailer( + kawa: &mut GenericHttpStream, + input: &[u8], + end_stream: bool, + decoder: &mut hpack::Decoder, +) { + decoder + .decode_with_cb(input, |k, v| { + let start = kawa.storage.end as u32; + kawa.storage.write_all(&k).unwrap(); + kawa.storage.write_all(&v).unwrap(); + let len_key = k.len() as u32; + let len_val = v.len() as u32; + let key = Store::Slice(Slice { + start, + len: len_key, + }); + let val = Store::Slice(Slice { + start: start + len_key, + len: len_val, + }); + kawa.push_block(Block::Header(Pair { key, val })); + }) + .unwrap(); + + assert!(end_stream); + kawa.push_block(Block::Flags(Flags { + end_body: end_stream, + end_chunk: false, + end_header: true, + end_stream, + })); + kawa.parsing_phase = ParsingPhase::Terminated; +} From a5fedf7711e5c1ab178d539be56550e9ba6d9d5f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 18 Oct 2023 12:21:26 +0200 Subject: [PATCH 30/44] Properly deregister backends from slab and mio Signed-off-by: Eloi DEMOLIS --- e2e/src/mock/async_backend.rs | 2 +- e2e/src/mock/client.rs | 2 +- e2e/src/mock/sync_backend.rs | 2 +- lib/src/protocol/mux/h1.rs | 10 ++- lib/src/protocol/mux/h2.rs | 24 +++++-- lib/src/protocol/mux/mod.rs | 121 +++++++++++++++++++++++++--------- lib/src/protocol/mux/pkawa.rs | 2 +- 7 files changed, 119 insertions(+), 44 deletions(-) diff --git a/e2e/src/mock/async_backend.rs b/e2e/src/mock/async_backend.rs index 56c452b62..b0ff7d25c 100644 --- a/e2e/src/mock/async_backend.rs +++ b/e2e/src/mock/async_backend.rs @@ -35,7 +35,7 @@ impl BackendHandle { let name = name.into(); let (stop_tx, mut stop_rx) = mpsc::channel::<()>(1); let (mut aggregator_tx, aggregator_rx) = mpsc::channel::(1); - let listener = TcpListener::bind(address).expect("could not bind"); + let listener = TcpListener::bind(address).expect(&format!("could not bind on: {address}")); let mut clients = Vec::new(); let thread_name = name.to_owned(); diff --git a/e2e/src/mock/client.rs b/e2e/src/mock/client.rs index da0e1f242..6b2bc324b 100644 --- a/e2e/src/mock/client.rs +++ b/e2e/src/mock/client.rs @@ -39,7 +39,7 @@ impl Client { /// Establish a TCP connection with its address, /// register the yielded TCP stream, apply timeouts pub fn connect(&mut self) { - let stream = TcpStream::connect(self.address).expect("could not connect"); + let stream = TcpStream::connect(self.address).expect(&format!("could not connect to: {}", self.address)); stream .set_read_timeout(Some(Duration::from_millis(100))) .expect("could not set read timeout"); diff --git a/e2e/src/mock/sync_backend.rs b/e2e/src/mock/sync_backend.rs index b97d58384..65f9f6638 100644 --- a/e2e/src/mock/sync_backend.rs +++ b/e2e/src/mock/sync_backend.rs @@ -44,7 +44,7 @@ impl Backend { /// Binds itself to its address, stores the yielded TCP listener pub fn connect(&mut self) { - let listener = TcpListener::bind(self.address).expect("could not bind"); + let listener = TcpListener::bind(self.address).expect(&format!("could not bind on: {}", self.address)); let timeout = Duration::from_millis(100); let timeout = libc::timeval { tv_sec: 0, diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 189f39b5e..0ed80a03f 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -39,6 +39,7 @@ impl ConnectionH1 { E: Endpoint, { println_!("======= MUX H1 READABLE {:?}", self.position); + self.timeout_container.reset(); let stream = &mut context.streams[self.stream]; let parts = stream.split(&self.position); let kawa = parts.rbuffer; @@ -67,6 +68,7 @@ impl ConnectionH1 { return MuxResult::Continue; } if kawa.is_terminated() { + self.timeout_container.cancel(); self.readiness.interest.remove(Ready::READABLE); } if kawa.is_main_phase() { @@ -95,6 +97,7 @@ impl ConnectionH1 { E: Endpoint, { println_!("======= MUX H1 WRITABLE {:?}", self.position); + self.timeout_container.reset(); let stream = &mut context.streams[self.stream]; let kawa = stream.wbuffer(&self.position); kawa.prepare(&mut kawa::h1::BlockConverter); @@ -121,12 +124,12 @@ impl ConnectionH1 { match kawa.detached.status_line { kawa::StatusLine::Response { code: 101, .. } => { println!("============== HANDLE UPGRADE!"); - // unimplemented!(); return MuxResult::Upgrade; } kawa::StatusLine::Response { code: 100, .. } => { println!("============== HANDLE CONTINUE!"); // after a 100 continue, we expect the client to continue with its request + self.timeout_container.reset(); self.readiness.interest.insert(Ready::READABLE); kawa.clear(); return MuxResult::Continue; @@ -134,7 +137,7 @@ impl ConnectionH1 { kawa::StatusLine::Response { code: 103, .. } => { println!("============== HANDLE EARLY HINT!"); if let StreamState::Linked(token) = stream.state { - // after a 103 early hints, we expect the server to send its response + // after a 103 early hints, we expect the backend to send its response endpoint .readiness_mut(token) .interest @@ -149,6 +152,7 @@ impl ConnectionH1 { } let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); if stream.context.keep_alive_frontend { + self.timeout_container.reset(); println!("{old_state:?} {:?}", self.readiness); if let StreamState::Linked(token) = old_state { println!("{:?}", endpoint.readiness(token)); @@ -160,7 +164,7 @@ impl ConnectionH1 { stream.back.clear(); stream.back.storage.clear(); stream.front.clear(); - // do not clear stream.front.storage because of H1 pipelining + // do not stream.front.storage.clear() because of H1 pipelining stream.attempts = 0; } else { return MuxResult::CloseSession; diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 15ca91c8f..1f40274dc 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, str::from_utf8_unchecked}; +use std::collections::HashMap; use rusty_ulid::Ulid; use sozu_command::ready::Ready; @@ -60,7 +60,7 @@ impl Default for H2Settings { Self { settings_header_table_size: 4096, settings_enable_push: true, - settings_max_concurrent_streams: 256, + settings_max_concurrent_streams: 100, settings_initial_window_size: (1 << 16) - 1, settings_max_frame_size: 1 << 14, settings_max_header_list_size: u32::MAX, @@ -128,6 +128,7 @@ impl ConnectionH2 { E: Endpoint, { println_!("======= MUX H2 READABLE {:?}", self.position); + self.timeout_container.reset(); let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect_read { let kawa = match stream_id { H2StreamId::Zero => &mut self.zero, @@ -269,7 +270,7 @@ impl ConnectionH2 { self.expect_read = Some((stream_id, header.payload_len as usize)); self.state = H2State::Frame(header); } - Err(e) => panic!("stream error: {:?}", error_nom_to_h2(e)), + Err(_) => return self.goaway(H2Error::ProtocolError), }; } (H2State::ContinuationHeader(headers), _) => { @@ -287,7 +288,7 @@ impl ConnectionH2 { headers.header_block_fragment.len += header.payload_len; self.state = H2State::ContinuationFrame(headers); } - Err(e) => panic!("stream error: {:?}", error_nom_to_h2(e)), + Err(_) => return self.goaway(H2Error::ProtocolError), }; } (H2State::Frame(header), _) => { @@ -325,6 +326,7 @@ impl ConnectionH2 { E: Endpoint, { println_!("======= MUX H2 WRITABLE {:?}", self.position); + self.timeout_container.reset(); if let Some(H2StreamId::Zero) = self.expect_write { let kawa = &mut self.zero; println_!("{:?}", kawa.storage.data()); @@ -607,7 +609,19 @@ impl ConnectionH2 { rst_stream.error_code, error_code_to_str(rst_stream.error_code) ); - self.streams.remove(&rst_stream.stream_id); + if let Some(stream_id) = self.streams.remove(&rst_stream.stream_id) { + let stream = &mut context.streams[stream_id]; + if let StreamState::Linked(token) = stream.state { + endpoint.end_stream(token, stream_id, context); + } + let stream = &mut context.streams[stream_id]; + match self.position { + Position::Client(_) => {} + Position::Server => { + stream.state = StreamState::Recycle; + } + } + } } Frame::Settings(settings) => { if settings.ack { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index ffaf4e818..8f6d9dad3 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -1,8 +1,8 @@ use std::{ cell::RefCell, collections::HashMap, - io::Write, - net::SocketAddr, + io::{ErrorKind, Write}, + net::{Shutdown, SocketAddr}, rc::{Rc, Weak}, time::Duration, }; @@ -47,7 +47,7 @@ macro_rules! println_ { }; } fn debug_kawa(_kawa: &GenericHttpStream) { - kawa::debug_kawa(_kawa); + // kawa::debug_kawa(_kawa); } /// Generic Http representation using the Kawa crate using the Checkout of Sozu as buffer @@ -299,18 +299,24 @@ impl Connection { Connection::H2(c) => &mut c.position, } } - pub fn timeout_container(&mut self) -> &mut TimeoutContainer { - match self { - Connection::H1(c) => &mut c.timeout_container, - Connection::H2(c) => &mut c.timeout_container, - } - } pub fn socket(&self) -> &TcpStream { match self { Connection::H1(c) => c.socket.socket_ref(), Connection::H2(c) => c.socket.socket_ref(), } } + pub fn socket_mut(&mut self) -> &mut TcpStream { + match self { + Connection::H1(c) => c.socket.socket_mut(), + Connection::H2(c) => c.socket.socket_mut(), + } + } + pub fn timeout_container(&mut self) -> &mut TimeoutContainer { + match self { + Connection::H1(c) => &mut c.timeout_container, + Connection::H2(c) => &mut c.timeout_container, + } + } fn force_disconnect(&mut self) -> MuxResult { match self { Connection::H1(c) => c.force_disconnect(), @@ -951,6 +957,9 @@ impl SessionState for Mux { *position = Position::Client(BackendStatus::Connected( std::mem::take(cluster_id), )); + backend + .timeout_container() + .set_duration(self.router.configured_backend_timeout); } _ => {} } @@ -981,7 +990,29 @@ impl SessionState for Mux { } if !dead_backends.is_empty() { for token in &dead_backends { - self.router.backends.remove(token); + let proxy_borrow = proxy.borrow(); + if let Some(mut backend) = self.router.backends.remove(token) { + backend.timeout_container().cancel(); + let socket = backend.socket_mut(); + if let Err(e) = proxy_borrow.deregister_socket(socket) { + error!("error deregistering back socket({:?}): {:?}", socket, e); + } + if let Err(e) = socket.shutdown(Shutdown::Both) { + if e.kind() != ErrorKind::NotConnected { + error!( + "error shutting down back socket({:?}): {:?}", + socket, e + ); + } + } + } else { + error!("session {:?} has no backend!", token); + } + if !proxy_borrow.remove_session(*token) { + error!("session {:?} was already removed!", token); + } else { + println!("SUCCESS: session {token:?} was removed!"); + } } println_!("FRONTEND: {:#?}", self.frontend); println_!("BACKENDS: {:#?}", self.router.backends); @@ -1013,10 +1044,15 @@ impl SessionState for Mux { } let context = &mut self.context; - let front_readiness = self.frontend.readiness_mut(); let mut dirty = false; for stream_id in 0..context.streams.len() { if context.streams[stream_id].state == StreamState::Link { + // Before the first request triggers a stream Link, the frontend timeout is set + // to a shorter request_timeout, here we switch to the longer nominal timeout + self.frontend + .timeout_container() + .set_duration(self.configured_frontend_timeout); + let front_readiness = self.frontend.readiness_mut(); dirty = true; match self.router.connect( stream_id, @@ -1105,9 +1141,9 @@ impl SessionState for Mux { should_write = true; } StreamState::Linked(_) => { - // A stream Linked to a backend is waiting for the response, not the answer. + // A stream Linked to a backend is waiting for the response, not the request. // For streaming or malformed requests, it is possible that the request is not - // terminated at this point. For now, we do nothing and + // terminated at this point. For now, we do nothing should_close = false; } StreamState::Unlinked => { @@ -1202,27 +1238,48 @@ impl SessionState for Mux { } } - fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) { - let s = match &mut self.frontend { - Connection::H1(c) => &mut c.socket, - Connection::H2(c) => &mut c.socket, - }; - let mut b = [0; 1024]; - let (size, status) = s.socket_read(&mut b); - println_!("{size} {status:?} {:?}", &b[..size]); - for stream in &mut self.context.streams { - for kawa in [&mut stream.front, &mut stream.back] { - debug_kawa(kawa); - kawa.prepare(&mut kawa::h1::BlockConverter); - let out = kawa.as_io_slice(); - let mut writer = std::io::BufWriter::new(Vec::new()); - let amount = writer.write_vectored(&out).unwrap(); - println_!( - "amount: {amount}\n{}", - String::from_utf8_lossy(writer.buffer()) - ); + fn close(&mut self, proxy: Rc>, _metrics: &mut SessionMetrics) { + println_!("FRONTEND: {:#?}", self.frontend); + println_!("BACKENDS: {:#?}", self.router.backends); + + for (token, backend) in &mut self.router.backends { + let proxy_borrow = proxy.borrow(); + backend.timeout_container().cancel(); + let socket = backend.socket_mut(); + if let Err(e) = proxy_borrow.deregister_socket(socket) { + error!("error deregistering back socket({:?}): {:?}", socket, e); + } + if let Err(e) = socket.shutdown(Shutdown::Both) { + if e.kind() != ErrorKind::NotConnected { + error!("error shutting down back socket({:?}): {:?}", socket, e); + } + } + if !proxy_borrow.remove_session(*token) { + error!("session {:?} was already removed!", token); + } else { + println!("SUCCESS: session {token:?} was removed!"); } } + // let s = match &mut self.frontend { + // Connection::H1(c) => &mut c.socket, + // Connection::H2(c) => &mut c.socket, + // }; + // let mut b = [0; 1024]; + // let (size, status) = s.socket_read(&mut b); + // println_!("{size} {status:?} {:?}", &b[..size]); + // for stream in &mut self.context.streams { + // for kawa in [&mut stream.front, &mut stream.back] { + // debug_kawa(kawa); + // kawa.prepare(&mut kawa::h1::BlockConverter); + // let out = kawa.as_io_slice(); + // let mut writer = std::io::BufWriter::new(Vec::new()); + // let amount = writer.write_vectored(&out).unwrap(); + // println_!( + // "amount: {amount}\n{}", + // String::from_utf8_lossy(writer.buffer()) + // ); + // } + // } } fn shutting_down(&mut self) -> SessionIsToBeClosed { diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index fbb49e5da..d00a430ef 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -117,7 +117,7 @@ pub fn handle_header( version: Version::V20, code, status, - reason: Store::Static(b"Default"), + reason: Store::Static(b"FromH2"), } } }; From e64cf6ba87525f25017d69773d592609a44bb9ac Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 22 Apr 2024 14:13:35 +0200 Subject: [PATCH 31/44] Remove debugging println Signed-off-by: Eloi DEMOLIS --- bin/config.toml | 3 ++- lib/src/https.rs | 12 ++++++------ lib/src/protocol/mux/h1.rs | 8 ++++---- lib/src/protocol/mux/mod.rs | 10 +++++----- lib/src/router/mod.rs | 2 +- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/bin/config.toml b/bin/config.toml index a37fbaf76..c144e520f 100644 --- a/bin/config.toml +++ b/bin/config.toml @@ -324,7 +324,8 @@ frontends = [ # - weight: weight used by the load balancing algorithm # - sticky-id: sticky session identifier backends = [ - { address = "127.0.0.1:1026", backend_id = "the-backend-to-my-app" } + { address = "127.0.0.1:1026", backend_id = "back26" }, + { address = "127.0.0.1:1027", backend_id = "back27" } ] # this is an example of a routing configuration for the TCP proxy diff --git a/lib/src/https.rs b/lib/src/https.rs index 3bf7569a4..086296178 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -272,7 +272,7 @@ impl HttpsSession { // Some client don't fill in the ALPN protocol, in this case we default to Http/1.1 None => AlpnProtocol::Http11, }; - println!("ALPN: {alpn:?}"); + // println!("ALPN: {alpn:?}"); if let Some(version) = handshake.session.protocol_version() { incr!(rustls_version_str(version)); @@ -480,23 +480,23 @@ impl ProxySession for HttpsSession { token, super::ready_to_string(events) ); - println!("EVENT: {token:?}->{events:?}"); + // println!("EVENT: {token:?}->{events:?}"); self.last_event = Instant::now(); self.metrics.wait_start(); self.state.update_readiness(token, events); } fn ready(&mut self, session: Rc>) -> SessionIsToBeClosed { - let start = std::time::Instant::now(); - println!("READY {start:?}"); + // let start = std::time::Instant::now(); + // println!("READY {start:?}"); self.metrics.service_start(); let session_result = self.state .ready(session.clone(), self.proxy.clone(), &mut self.metrics); - let end = std::time::Instant::now(); - println!("READY END {end:?} -> {:?}", end.duration_since(start)); + // let end = std::time::Instant::now(); + // println!("READY END {end:?} -> {:?}", end.duration_since(start)); let to_be_closed = match session_result { SessionResult::Close => true, diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 0ed80a03f..9fb52aa86 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -49,7 +49,7 @@ impl ConnectionH1 { return MuxResult::Continue; } - let was_initial = kawa.is_initial(); + let was_main_phase = kawa.is_main_phase(); kawa::h1::parse(kawa, parts.context); debug_kawa(kawa); if kawa.is_error() { @@ -80,7 +80,7 @@ impl ConnectionH1 { } match self.position { Position::Server => { - if was_initial { + if !was_main_phase { self.requests += 1; println_!("REQUESTS: {}", self.requests); stream.state = StreamState::Link @@ -153,9 +153,9 @@ impl ConnectionH1 { let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); if stream.context.keep_alive_frontend { self.timeout_container.reset(); - println!("{old_state:?} {:?}", self.readiness); + // println!("{old_state:?} {:?}", self.readiness); if let StreamState::Linked(token) = old_state { - println!("{:?}", endpoint.readiness(token)); + // println!("{:?}", endpoint.readiness(token)); endpoint.end_stream(token, self.stream, context); } self.readiness.interest.insert(Ready::READABLE); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 8f6d9dad3..e048af372 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -1,7 +1,7 @@ use std::{ cell::RefCell, collections::HashMap, - io::{ErrorKind, Write}, + io::ErrorKind, net::{Shutdown, SocketAddr}, rc::{Rc, Weak}, time::Duration, @@ -42,7 +42,7 @@ use self::h2::Prioriser; macro_rules! println_ { ($($t:expr),*) => { // print!("{}:{} ", file!(), line!()); - println!($($t),*) + // println!($($t),*) // $(let _ = &$t;)* }; } @@ -942,7 +942,7 @@ impl SessionState for Mux { let mut dead_backends = Vec::new(); for (token, backend) in self.router.backends.iter_mut() { let readiness = backend.readiness_mut(); - println!("{token:?} -> {readiness:?}"); + // println!("{token:?} -> {readiness:?}"); let dead = readiness.filter_interest().is_hup() || readiness.filter_interest().is_error(); if dead { @@ -1011,7 +1011,7 @@ impl SessionState for Mux { if !proxy_borrow.remove_session(*token) { error!("session {:?} was already removed!", token); } else { - println!("SUCCESS: session {token:?} was removed!"); + // println!("SUCCESS: session {token:?} was removed!"); } } println_!("FRONTEND: {:#?}", self.frontend); @@ -1257,7 +1257,7 @@ impl SessionState for Mux { if !proxy_borrow.remove_session(*token) { error!("session {:?} was already removed!", token); } else { - println!("SUCCESS: session {token:?} was removed!"); + // println!("SUCCESS: session {token:?} was removed!"); } } // let s = match &mut self.frontend { diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index 06c5b453d..b50b76d1c 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -936,7 +936,7 @@ mod tests { ), Ok(Route::Cluster { id: "examplewildcard".to_string(), - h2: true + h2: false }) ); assert_eq!( From 58d90d9a12b74d6bab9bc865a89dde26624a0188 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 2 May 2024 10:40:14 +0200 Subject: [PATCH 32/44] Making Mux working again after rebase Signed-off-by: Eloi DEMOLIS --- e2e/src/http_utils/mod.rs | 12 ++++++---- e2e/src/tests/tests.rs | 4 ++-- lib/src/protocol/mux/h2.rs | 2 +- lib/src/protocol/mux/mod.rs | 45 +++++++++++++++++++------------------ lib/src/socket.rs | 4 ++-- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/e2e/src/http_utils/mod.rs b/e2e/src/http_utils/mod.rs index 144136162..997e9fce4 100644 --- a/e2e/src/http_utils/mod.rs +++ b/e2e/src/http_utils/mod.rs @@ -26,10 +26,14 @@ pub fn http_request, S2: Into, S3: Into, S4: In pub fn immutable_answer(status: u16) -> String { match status { - 400 => String::from("HTTP/1.1 400 Bad Request\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), - 404 => String::from("HTTP/1.1 404 Not Found\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), - 502 => String::from("HTTP/1.1 502 Bad Gateway\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), - 503 => String::from("HTTP/1.1 503 Service Unavailable\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), + // 400 => String::from("HTTP/1.1 400 Bad Request\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), + 400 => String::from("HTTP/1.1 400 Sozu Default Answer\r\nCache-Control: no-cache\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"), + // 404 => String::from("HTTP/1.1 404 Not Found\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), + 404 => String::from("HTTP/1.1 404 Sozu Default Answer\r\nCache-Control: no-cache\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"), + // 502 => String::from("HTTP/1.1 502 Bad Gateway\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), + 502 => String::from("HTTP/1.1 502 Sozu Default Answer\r\nCache-Control: no-cache\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"), + // 503 => String::from("HTTP/1.1 503 Service Unavailable\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n"), + 503 => String::from("HTTP/1.1 503 Sozu Default Answer\r\nCache-Control: no-cache\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"), _ => unimplemented!() } } diff --git a/e2e/src/tests/tests.rs b/e2e/src/tests/tests.rs index 34e72e32c..5680b57d5 100644 --- a/e2e/src/tests/tests.rs +++ b/e2e/src/tests/tests.rs @@ -788,7 +788,7 @@ fn try_http_behaviors() -> State { let response = client.receive(); println!("request: {request:?}"); println!("response: {response:?}"); - assert_eq!(response, Some(immutable_answer(503))); + assert_eq!(response, Some(immutable_answer(502))); assert_eq!(client.receive(), None); worker.send_proxy_request_type(RequestType::RemoveBackend(RemoveBackend { @@ -988,7 +988,7 @@ fn try_https_redirect() -> State { client.connect(); client.send(); let answer = client.receive(); - let expected_answer = format!("{answer_301_prefix}https://example.com/redirected?true\r\n\r\n"); + let expected_answer = format!("{answer_301_prefix}https://example.com/redirected?true\r\nContent-Length: 0\r\n\r\n"); assert_eq!(answer, Some(expected_answer)); State::Success diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 1f40274dc..a7ed67d8a 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -59,7 +59,7 @@ impl Default for H2Settings { fn default() -> Self { Self { settings_header_table_size: 4096, - settings_enable_push: true, + settings_enable_push: false, settings_max_concurrent_streams: 100, settings_initial_window_size: (1 << 16) - 1, settings_max_frame_size: 1 << 14, diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index e048af372..f5c25e8f5 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -41,8 +41,8 @@ use self::h2::Prioriser; #[macro_export] macro_rules! println_ { ($($t:expr),*) => { - // print!("{}:{} ", file!(), line!()); - // println!($($t),*) + print!("{}:{} ", file!(), line!()); + println!($($t),*) // $(let _ = &$t;)* }; } @@ -1260,26 +1260,27 @@ impl SessionState for Mux { // println!("SUCCESS: session {token:?} was removed!"); } } - // let s = match &mut self.frontend { - // Connection::H1(c) => &mut c.socket, - // Connection::H2(c) => &mut c.socket, - // }; - // let mut b = [0; 1024]; - // let (size, status) = s.socket_read(&mut b); - // println_!("{size} {status:?} {:?}", &b[..size]); - // for stream in &mut self.context.streams { - // for kawa in [&mut stream.front, &mut stream.back] { - // debug_kawa(kawa); - // kawa.prepare(&mut kawa::h1::BlockConverter); - // let out = kawa.as_io_slice(); - // let mut writer = std::io::BufWriter::new(Vec::new()); - // let amount = writer.write_vectored(&out).unwrap(); - // println_!( - // "amount: {amount}\n{}", - // String::from_utf8_lossy(writer.buffer()) - // ); - // } - // } + use std::io::Write; + let s = match &mut self.frontend { + Connection::H1(c) => &mut c.socket, + Connection::H2(c) => &mut c.socket, + }; + let mut b = [0; 1024]; + let (size, status) = s.socket_read(&mut b); + println_!("{size} {status:?} {:?}", &b[..size]); + for stream in &mut self.context.streams { + for kawa in [&mut stream.front, &mut stream.back] { + debug_kawa(kawa); + kawa.prepare(&mut kawa::h1::BlockConverter); + let out = kawa.as_io_slice(); + let mut writer = std::io::BufWriter::new(Vec::new()); + let amount = writer.write_vectored(&out).unwrap(); + println_!( + "amount: {amount}\n{}", + String::from_utf8_lossy(writer.buffer()) + ); + } + } } fn shutting_down(&mut self) -> SessionIsToBeClosed { diff --git a/lib/src/socket.rs b/lib/src/socket.rs index e3c9cdd4e..f19cbf9fb 100644 --- a/lib/src/socket.rs +++ b/lib/src/socket.rs @@ -264,8 +264,8 @@ impl SocketHandler for FrontRustls { (size, SocketResult::Error) } else if is_closed { (size, SocketResult::Closed) - } else if !can_read { - (size, SocketResult::WouldBlock) + // } else if !can_read { + // (size, SocketResult::WouldBlock) } else { (size, SocketResult::Continue) } From 001b792a83a70ef33373c96762856925aa8ad303 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 7 May 2024 15:12:40 +0200 Subject: [PATCH 33/44] Flag h2 at config level not frontend level Signed-off-by: Eloi DEMOLIS --- bin/src/ctl/request_builder.rs | 2 - command/src/command.proto | 2 +- command/src/config.rs | 12 +-- command/src/request.rs | 1 - command/src/response.rs | 2 - lib/src/http.rs | 26 ++----- lib/src/https.rs | 42 +++-------- lib/src/protocol/kawa_h1/mod.rs | 2 +- lib/src/protocol/mux/mod.rs | 20 +++-- lib/src/router/mod.rs | 127 +++++++------------------------- 10 files changed, 61 insertions(+), 175 deletions(-) diff --git a/bin/src/ctl/request_builder.rs b/bin/src/ctl/request_builder.rs index 028dac079..0f477cb49 100644 --- a/bin/src/ctl/request_builder.rs +++ b/bin/src/ctl/request_builder.rs @@ -251,7 +251,6 @@ impl CommandManager { Some(tags) => tags, None => BTreeMap::new(), }, - h2: h2.unwrap_or(false), }) .into(), ), @@ -301,7 +300,6 @@ impl CommandManager { Some(tags) => tags, None => BTreeMap::new(), }, - h2: h2.unwrap_or(false), }) .into(), ), diff --git a/command/src/command.proto b/command/src/command.proto index d4aebe8c6..9e49f012e 100644 --- a/command/src/command.proto +++ b/command/src/command.proto @@ -248,7 +248,6 @@ message RequestHttpFrontend { required RulePosition position = 6 [default = TREE]; // custom tags to identify the frontend in the access logs map tags = 7; - required bool h2 = 8; } message RequestTcpFrontend { @@ -383,6 +382,7 @@ message Cluster { required LoadBalancingAlgorithms load_balancing = 5 [default = ROUND_ROBIN]; optional string answer_503 = 6; optional LoadMetric load_metric = 7; + required bool http2 = 8; } enum LoadBalancingAlgorithms { diff --git a/command/src/config.rs b/command/src/config.rs index ab2b1389e..4bff8c00e 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -684,7 +684,6 @@ pub struct FileClusterFrontendConfig { #[serde(default = "default_rule_position")] pub position: RulePosition, pub tags: Option>, - pub h2: Option, } impl FileClusterFrontendConfig { @@ -770,7 +769,6 @@ impl FileClusterFrontendConfig { path, method: self.method.clone(), tags: self.tags.clone(), - h2: self.h2.unwrap_or(false), }) } } @@ -787,6 +785,7 @@ pub enum ListenerProtocol { #[serde(deny_unknown_fields, rename_all = "lowercase")] pub enum FileClusterProtocolConfig { Http, + Http2, Tcp, } @@ -876,7 +875,7 @@ impl FileClusterConfig { load_metric: self.load_metric, })) } - FileClusterProtocolConfig::Http => { + FileClusterProtocolConfig::Http | FileClusterProtocolConfig::Http2 => { let mut frontends = Vec::new(); for frontend in self.frontends { let http_frontend = frontend.to_http_front(cluster_id)?; @@ -894,6 +893,7 @@ impl FileClusterConfig { Ok(ClusterConfig::Http(HttpClusterConfig { cluster_id: cluster_id.to_string(), + http2: self.protocol == FileClusterProtocolConfig::Http2, frontends, backends: self.backends, sticky_session: self.sticky_session.unwrap_or(false), @@ -922,7 +922,6 @@ pub struct HttpFrontendConfig { #[serde(default)] pub position: RulePosition, pub tags: Option>, - pub h2: bool, } impl HttpFrontendConfig { @@ -962,7 +961,6 @@ impl HttpFrontendConfig { path: self.path.clone(), method: self.method.clone(), position: self.position.into(), - h2: self.h2, tags, }) .into(), @@ -977,7 +975,6 @@ impl HttpFrontendConfig { path: self.path.clone(), method: self.method.clone(), position: self.position.into(), - h2: self.h2, tags, }) .into(), @@ -992,6 +989,7 @@ impl HttpFrontendConfig { #[serde(deny_unknown_fields)] pub struct HttpClusterConfig { pub cluster_id: String, + pub http2: bool, pub frontends: Vec, pub backends: Vec, pub sticky_session: bool, @@ -1007,6 +1005,7 @@ impl HttpClusterConfig { cluster_id: self.cluster_id.clone(), sticky_session: self.sticky_session, https_redirect: self.https_redirect, + http2: self.http2, proxy_protocol: None, load_balancing: self.load_balancing as i32, answer_503: self.answer_503.clone(), @@ -1066,6 +1065,7 @@ impl TcpClusterConfig { cluster_id: self.cluster_id.clone(), sticky_session: false, https_redirect: false, + http2: false, proxy_protocol: self.proxy_protocol.map(|s| s as i32), load_balancing: self.load_balancing as i32, load_metric: self.load_metric.map(|s| s as i32), diff --git a/command/src/request.rs b/command/src/request.rs index 496140ce2..f43e7c621 100644 --- a/command/src/request.rs +++ b/command/src/request.rs @@ -173,7 +173,6 @@ impl RequestHttpFrontend { } })?, tags: Some(self.tags), - h2: self.h2, }) } } diff --git a/command/src/response.rs b/command/src/response.rs index 9b1b87c10..341dbc7ae 100644 --- a/command/src/response.rs +++ b/command/src/response.rs @@ -39,7 +39,6 @@ pub struct HttpFrontend { #[serde(default)] pub position: RulePosition, pub tags: Option>, - pub h2: bool, } impl From for RequestHttpFrontend { @@ -55,7 +54,6 @@ impl From for RequestHttpFrontend { path: val.path, method: val.method, position: val.position.into(), - h2: val.h2, tags, } } diff --git a/lib/src/http.rs b/lib/src/http.rs index 73da438c9..a9cc414eb 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -520,7 +520,7 @@ impl L7ListenerHandler for HttpListener { let now = Instant::now(); - if let Route::Cluster { id: cluster, .. } = &route { + if let Route::Cluster(cluster) = &route { time!("frontend_matching_time", cluster, (now - start).as_millis()); } @@ -1374,7 +1374,6 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id1), tags: None, - h2: false, }) .expect("Could not add http frontend"); fronts @@ -1386,7 +1385,6 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id2), tags: None, - h2: false, }) .expect("Could not add http frontend"); fronts @@ -1398,7 +1396,6 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id3), tags: None, - h2: false, }) .expect("Could not add http frontend"); fronts @@ -1410,7 +1407,6 @@ mod tests { position: RulePosition::Tree, cluster_id: Some("cluster_1".to_owned()), tags: None, - h2: false, }) .expect("Could not add http frontend"); @@ -1440,31 +1436,19 @@ mod tests { let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get); assert_eq!( frontend1.expect("should find frontend"), - Route::Cluster { - id: "cluster_1".to_string(), - h2: false - } + Route::Cluster("cluster_1".to_string()) ); assert_eq!( frontend2.expect("should find frontend"), - Route::Cluster { - id: "cluster_1".to_string(), - h2: false - } + Route::Cluster("cluster_1".to_string()) ); assert_eq!( frontend3.expect("should find frontend"), - Route::Cluster { - id: "cluster_2".to_string(), - h2: false - } + Route::Cluster("cluster_2".to_string()) ); assert_eq!( frontend4.expect("should find frontend"), - Route::Cluster { - id: "cluster_3".to_string(), - h2: false - } + Route::Cluster("cluster_3".to_string()) ); assert!(frontend5.is_err()); } diff --git a/lib/src/https.rs b/lib/src/https.rs index 086296178..d9ef7beb1 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -606,7 +606,7 @@ impl L7ListenerHandler for HttpsListener { let now = Instant::now(); - if let Route::Cluster { id: cluster, .. } = &route { + if let Route::Cluster(cluster) = &route { time!("frontend_matching_time", cluster, (now - start).as_millis()); } @@ -1557,37 +1557,25 @@ mod tests { "lolcatho.st".as_bytes(), &PathRule::Prefix(uri1), &MethodRule::new(None), - &Route::Cluster { - id: cluster_id1.clone(), - h2: false - } + &Route::Cluster(cluster_id1.clone()) )); assert!(fronts.add_tree_rule( "lolcatho.st".as_bytes(), &PathRule::Prefix(uri2), &MethodRule::new(None), - &Route::Cluster { - id: cluster_id2, - h2: false - } + &Route::Cluster(cluster_id2) )); assert!(fronts.add_tree_rule( "lolcatho.st".as_bytes(), &PathRule::Prefix(uri3), &MethodRule::new(None), - &Route::Cluster { - id: cluster_id3, - h2: false - } + &Route::Cluster(cluster_id3) )); assert!(fronts.add_tree_rule( "other.domain".as_bytes(), &PathRule::Prefix("test".to_string()), &MethodRule::new(None), - &Route::Cluster { - id: cluster_id1, - h2: false - } + &Route::Cluster(cluster_id1) )); let address = SocketAddress::new_v4(127, 0, 0, 1, 1032); @@ -1628,37 +1616,25 @@ mod tests { let frontend1 = listener.frontend_from_request("lolcatho.st", "/", &Method::Get); assert_eq!( frontend1.expect("should find a frontend"), - Route::Cluster { - id: "cluster_1".to_string(), - h2: false - } + Route::Cluster("cluster_1".to_string()) ); println!("TEST {}", line!()); let frontend2 = listener.frontend_from_request("lolcatho.st", "/test", &Method::Get); assert_eq!( frontend2.expect("should find a frontend"), - Route::Cluster { - id: "cluster_1".to_string(), - h2: false - } + Route::Cluster("cluster_1".to_string()) ); println!("TEST {}", line!()); let frontend3 = listener.frontend_from_request("lolcatho.st", "/yolo/test", &Method::Get); assert_eq!( frontend3.expect("should find a frontend"), - Route::Cluster { - id: "cluster_2".to_string(), - h2: false - } + Route::Cluster("cluster_2".to_string()) ); println!("TEST {}", line!()); let frontend4 = listener.frontend_from_request("lolcatho.st", "/yolo/swag", &Method::Get); assert_eq!( frontend4.expect("should find a frontend"), - Route::Cluster { - id: "cluster_3".to_string(), - h2: false - } + Route::Cluster("cluster_3".to_string()) ); println!("TEST {}", line!()); let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get); diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 375a6d203..8a23267f8 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -1219,7 +1219,7 @@ impl Http id, + Route::Cluster(id) => id, Route::Deny => { self.set_answer(DefaultAnswer::Answer401 {}); return Err(RetrieveClusterError::UnauthorizedRoute); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index f5c25e8f5..79aa8cc37 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -184,13 +184,13 @@ impl Connection { timeout_container: TimeoutContainer, ) -> Connection { Connection::H1(ConnectionH1 { + socket: front_stream, position: Position::Server, readiness: Readiness { interest: Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, }, requests: 0, - socket: front_stream, stream: 0, timeout_container, }) @@ -667,16 +667,22 @@ impl Router { stream.attempts += 1; let stream_context = &mut stream.context; - let (cluster_id, h2) = self + let cluster_id = self .route_from_request(stream_context, proxy.clone()) .map_err(BackendConnectionError::RetrieveClusterError)?; - let (frontend_should_stick, frontend_should_redirect_https) = proxy + let (frontend_should_stick, frontend_should_redirect_https, h2) = proxy .borrow() .clusters() .get(&cluster_id) - .map(|cluster| (cluster.sticky_session, cluster.https_redirect)) - .unwrap_or((false, false)); + .map(|cluster| { + ( + cluster.sticky_session, + cluster.https_redirect, + cluster.http2, + ) + }) + .unwrap_or((false, false, false)); if frontend_should_redirect_https && matches!(proxy.borrow().kind(), ListenerType::Http) { return Err(BackendConnectionError::RetrieveClusterError( @@ -790,7 +796,7 @@ impl Router { &mut self, context: &mut HttpContext, _proxy: Rc>, - ) -> Result<(String, bool), RetrieveClusterError> { + ) -> Result { let (host, uri, method) = match context.extract_route() { Ok(tuple) => tuple, Err(cluster_error) => { @@ -815,7 +821,7 @@ impl Router { }; let cluster_id = match route { - Route::Cluster { id, h2 } => (id, h2), + Route::Cluster(id) => id, Route::Deny => { println!("Route::Deny"); // self.set_answer(DefaultAnswerStatus::Answer401, None); diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index b50b76d1c..91e1fdaf0 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -133,10 +133,7 @@ impl Router { let method_rule = MethodRule::new(front.method.clone()); let route = match &front.cluster_id { - Some(cluster_id) => Route::Cluster { - id: cluster_id.clone(), - h2: front.h2, - }, + Some(cluster_id) => Route::Cluster(cluster_id.clone()), None => Route::Deny, }; @@ -600,7 +597,7 @@ pub enum Route { /// send a 401 default answer Deny, /// the cluster to which the frontend belongs - Cluster { id: ClusterId, h2: bool }, + Cluster(ClusterId), } #[cfg(test)] @@ -719,42 +716,27 @@ mod tests { b"*.sozu.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "base".to_string(), - h2: false - } + &Route::Cluster("base".to_string()) )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::Cluster { - id: "base".to_string(), - h2: false - }) + Ok(Route::Cluster("base".to_string())) ); assert!(router.add_tree_rule( b"*.sozu.io", &PathRule::Prefix("/api".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "api".to_string(), - h2: false - } + &Route::Cluster("api".to_string()) )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/ap", &Method::Get), - Ok(Route::Cluster { - id: "base".to_string(), - h2: false - }) + Ok(Route::Cluster("base".to_string())) ); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::Cluster { - id: "api".to_string(), - h2: false - }) + Ok(Route::Cluster("api".to_string())) ); } @@ -773,42 +755,27 @@ mod tests { b"*.sozu.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "base".to_string(), - h2: false - } + &Route::Cluster("base".to_string()) )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::Cluster { - id: "base".to_string(), - h2: false - }) + Ok(Route::Cluster("base".to_string())) ); assert!(router.add_tree_rule( b"api.sozu.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "api".to_string(), - h2: false - } + &Route::Cluster("api".to_string()) )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/api", &Method::Get), - Ok(Route::Cluster { - id: "base".to_string(), - h2: false - }) + Ok(Route::Cluster("base".to_string())) ); assert_eq!( router.lookup("api.sozu.io", "/api", &Method::Get), - Ok(Route::Cluster { - id: "api".to_string(), - h2: false - }) + Ok(Route::Cluster("api".to_string())) ); } @@ -820,35 +787,23 @@ mod tests { b"www./.*/.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "base".to_string(), - h2: false - } + &Route::Cluster("base".to_string()) )); println!("{:#?}", router.tree); assert!(router.add_tree_rule( b"www.doc./.*/.io", &PathRule::Prefix("".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "doc".to_string(), - h2: false - } + &Route::Cluster("doc".to_string()) )); println!("{:#?}", router.tree); assert_eq!( router.lookup("www.sozu.io", "/", &Method::Get), - Ok(Route::Cluster { - id: "base".to_string(), - h2: false - }) + Ok(Route::Cluster("base".to_string())) ); assert_eq!( router.lookup("www.doc.sozu.io", "/", &Method::Get), - Ok(Route::Cluster { - id: "doc".to_string(), - h2: false - }) + Ok(Route::Cluster("doc".to_string())) ); assert!(router.remove_tree_rule( b"www./.*/.io", @@ -859,10 +814,7 @@ mod tests { assert!(router.lookup("www.sozu.io", "/", &Method::Get).is_err()); assert_eq!( router.lookup("www.doc.sozu.io", "/", &Method::Get), - Ok(Route::Cluster { - id: "doc".to_string(), - h2: false - }) + Ok(Route::Cluster("doc".to_string())) ); } @@ -874,45 +826,30 @@ mod tests { &"*".parse::().unwrap(), &PathRule::Prefix("/.well-known/acme-challenge".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "acme".to_string(), - h2: false - } + &Route::Cluster("acme".to_string()) )); assert!(router.add_tree_rule( "www.example.com".as_bytes(), &PathRule::Prefix("/".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "example".to_string(), - h2: false - } + &Route::Cluster("example".to_string()) )); assert!(router.add_tree_rule( "*.test.example.com".as_bytes(), &PathRule::Regex(Regex::new("/hello[A-Z]+/").unwrap()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "examplewildcard".to_string(), - h2: false - } + &Route::Cluster("examplewildcard".to_string()) )); assert!(router.add_tree_rule( "/test[0-9]/.example.com".as_bytes(), &PathRule::Prefix("/".to_string()), &MethodRule::new(Some("GET".to_string())), - &Route::Cluster { - id: "exampleregex".to_string(), - h2: false - } + &Route::Cluster("exampleregex".to_string()) )); assert_eq!( router.lookup("www.example.com", "/helloA", &Method::new(&b"GET"[..])), - Ok(Route::Cluster { - id: "example".to_string(), - h2: false - }) + Ok(Route::Cluster("example".to_string())) ); assert_eq!( router.lookup( @@ -920,10 +857,7 @@ mod tests { "/.well-known/acme-challenge", &Method::new(&b"GET"[..]) ), - Ok(Route::Cluster { - id: "acme".to_string(), - h2: false - }) + Ok(Route::Cluster("acme".to_string())) ); assert!(router .lookup("www.test.example.com", "/", &Method::new(&b"GET"[..])) @@ -934,10 +868,7 @@ mod tests { "/helloAB/", &Method::new(&b"GET"[..]) ), - Ok(Route::Cluster { - id: "examplewildcard".to_string(), - h2: false - }) + Ok(Route::Cluster("examplewildcard".to_string())) ); assert_eq!( router.lookup( @@ -945,17 +876,11 @@ mod tests { "/helloAB/", &Method::new(&b"GET"[..]) ), - Ok(Route::Cluster { - id: "examplewildcard".to_string(), - h2: false - }) + Ok(Route::Cluster("examplewildcard".to_string())) ); assert_eq!( router.lookup("test1.example.com", "/helloAB/", &Method::new(&b"GET"[..])), - Ok(Route::Cluster { - id: "exampleregex".to_string(), - h2: false - }) + Ok(Route::Cluster("exampleregex".to_string())) ); } } From f1f9afc440275a5066918e56754efdbc25be58d1 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 13 May 2024 12:05:06 +0200 Subject: [PATCH 34/44] Use static dispatch for ProxyL7 and L7ListenerHandler rebase: - log_access macror has an on_failure - H2BlockConverter::call() returns true Signed-off-by: Eloi DEMOLIS --- lib/src/http.rs | 36 +-- lib/src/https.rs | 28 ++- lib/src/lib.rs | 24 +- lib/src/protocol/kawa_h1/editor.rs | 33 ++- lib/src/protocol/kawa_h1/mod.rs | 34 +-- lib/src/protocol/mod.rs | 6 +- lib/src/protocol/mux/converter.rs | 7 +- lib/src/protocol/mux/h1.rs | 31 ++- lib/src/protocol/mux/h2.rs | 63 ++++- lib/src/protocol/mux/mod.rs | 280 ++++++++++++++-------- lib/src/protocol/pipe.rs | 8 +- lib/src/protocol/proxy_protocol/expect.rs | 18 +- lib/src/protocol/rustls.rs | 6 +- lib/src/tcp.rs | 16 +- 14 files changed, 402 insertions(+), 188 deletions(-) diff --git a/lib/src/http.rs b/lib/src/http.rs index a9cc414eb..584118671 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -34,9 +34,8 @@ use crate::{ http::{ answers::HttpAnswers, parser::{hostname_and_port, Method}, - ResponseStream, }, - mux::{self, Mux}, + mux::{self, Mux, MuxClear}, proxy_protocol::expect::ExpectProxyProtocol, Pipe, SessionState, }, @@ -64,7 +63,7 @@ StateMachineBuilder! { enum HttpStateMachine impl SessionState { Expect(ExpectProxyProtocol), // Http(Http), - Mux(Mux), + Mux(MuxClear), WebSocket(Pipe), } } @@ -124,12 +123,8 @@ impl HttpSession { let session_address = sock.peer_addr().ok(); let frontend = mux::Connection::new_h1_server(sock, container_frontend_timeout); - let router = mux::Router::new( - configured_backend_timeout, - configured_connect_timeout, - listener.clone(), - ); - let mut context = mux::Context::new(pool.clone()); + let router = mux::Router::new(configured_backend_timeout, configured_connect_timeout); + let mut context = mux::Context::new(pool.clone(), listener.clone()); context .create_stream(request_id, 1 << 16) .ok_or(AcceptError::BufferCapacityReached)?; @@ -217,9 +212,8 @@ impl HttpSession { let router = mux::Router::new( self.configured_backend_timeout, self.configured_connect_timeout, - self.listener.clone(), ); - let mut context = mux::Context::new(self.pool.clone()); + let mut context = mux::Context::new(self.pool.clone(), self.listener.clone()); context.create_stream(expect.request_id, 1 << 16)?; let mut mux = Mux { configured_frontend_timeout: self.configured_frontend_timeout, @@ -241,7 +235,7 @@ impl HttpSession { } } - fn upgrade_mux(&mut self, mut mux: Mux) -> Option { + fn upgrade_mux(&mut self, mut mux: MuxClear) -> Option { debug!("mux switching to ws"); let stream = mux.context.streams.pop().unwrap(); @@ -453,10 +447,22 @@ pub struct HttpListener { } impl ListenerHandler for HttpListener { - fn get_addr(&self) -> &SocketAddr { + fn protocol(&self) -> Protocol { + Protocol::HTTP + } + + fn address(&self) -> &SocketAddr { &self.address } + fn public_address(&self) -> SocketAddr { + self.config + .public_address + .as_ref() + .map(|addr| addr.clone().into()) + .unwrap_or(self.address.clone()) + } + fn get_tags(&self, key: &str) -> Option<&CachedTags> { self.tags.get(key) } @@ -470,11 +476,11 @@ impl ListenerHandler for HttpListener { } impl L7ListenerHandler for HttpListener { - fn get_sticky_name(&self) -> &str { + fn sticky_name(&self) -> &str { &self.config.sticky_name } - fn get_connect_timeout(&self) -> u32 { + fn connect_timeout(&self) -> u32 { self.config.connect_timeout } diff --git a/lib/src/https.rs b/lib/src/https.rs index d9ef7beb1..cd565630b 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -56,9 +56,8 @@ use crate::{ http::{ answers::HttpAnswers, parser::{hostname_and_port, Method}, - ResponseStream, }, - mux::{self, Mux}, + mux::{self, Mux, MuxTls}, proxy_protocol::expect::ExpectProxyProtocol, rustls::TlsHandshake, Pipe, SessionState, @@ -91,7 +90,7 @@ StateMachineBuilder! { enum HttpsStateMachine impl SessionState { Expect(ExpectProxyProtocol, ServerConnection), Handshake(TlsHandshake), - Mux(Mux), + Mux(MuxTls), // Http(Http), WebSocket(Pipe), // Http2(Http2) -> todo!("H2"), @@ -291,9 +290,8 @@ impl HttpsSession { let router = mux::Router::new( self.configured_backend_timeout, self.configured_connect_timeout, - self.listener.clone(), ); - let mut context = mux::Context::new(self.pool.clone()); + let mut context = mux::Context::new(self.pool.clone(), self.listener.clone()); let mut frontend = match alpn { AlpnProtocol::Http11 => { context.create_stream(handshake.request_id, 1 << 16)?; @@ -320,7 +318,7 @@ impl HttpsSession { })) } - fn upgrade_mux(&self, mut mux: Mux) -> Option { + fn upgrade_mux(&self, mut mux: MuxTls) -> Option { debug!("mux switching to wss"); let stream = mux.context.streams.pop().unwrap(); @@ -546,10 +544,22 @@ pub struct HttpsListener { } impl ListenerHandler for HttpsListener { - fn get_addr(&self) -> &StdSocketAddr { + fn protocol(&self) -> Protocol { + Protocol::HTTPS + } + + fn address(&self) -> &StdSocketAddr { &self.address } + fn public_address(&self) -> StdSocketAddr { + self.config + .public_address + .as_ref() + .map(|addr| addr.clone().into()) + .unwrap_or(self.address.clone()) + } + fn get_tags(&self, key: &str) -> Option<&CachedTags> { self.tags.get(key) } @@ -563,11 +573,11 @@ impl ListenerHandler for HttpsListener { } impl L7ListenerHandler for HttpsListener { - fn get_sticky_name(&self) -> &str { + fn sticky_name(&self) -> &str { &self.config.sticky_name } - fn get_connect_timeout(&self) -> u32 { + fn connect_timeout(&self) -> u32 { self.config.connect_timeout } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 8bfbce847..3250f3961 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -466,8 +466,8 @@ macro_rules! StateMachineBuilder { } macro_rules! _fn_impl { - ($function:ident(&$d($mut:ident)?, self $d(,$arg_name:ident: $arg_type:ty)*) $d(-> $ret:ty)? $d(| $marker:tt => $fail:expr)?) => { - fn $function(&$d($mut)? self $d(,$arg_name: $arg_type)*) $d(-> $ret)? { + ($function:ident$d([$d($bounds:tt)*])?(&$d($mut:ident)?, self $d(,$arg_name:ident: $arg_type:ty)*) $d(-> $ret:ty)? $d(| $marker:tt => $fail:expr)?) => { + fn $function$d(<$d($bounds)*>)?(&$d($mut)? self $d(,$arg_name: $arg_type)*) $d(-> $ret)? { match self { $($state_name::$variant_name(_state, ..) => $crate::fallback!({$($override)?} _state.$function($d($arg_name),*)),)+ $state_name::FailedUpgrade($crate::fallback!({$d($marker)?} _)) => $crate::fallback!({$d($fail)?} unreachable!()) @@ -503,28 +503,32 @@ macro_rules! StateMachineBuilder { $crate::branch!{ if $($trait)? == SessionState { impl SessionState for $state_name { - _fn_impl!{ready(&mut, self, session: Rc>, proxy: Rc>, metrics: &mut SessionMetrics) -> SessionResult} + _fn_impl!{ready[P: L7Proxy](&mut, self, session: Rc>, proxy: Rc>, metrics: &mut SessionMetrics) -> SessionResult} _fn_impl!{update_readiness(&mut, self, token: Token, events: Ready)} _fn_impl!{timeout(&mut, self, token: Token, metrics: &mut SessionMetrics) -> StateResult} _fn_impl!{cancel_timeouts(&mut, self)} _fn_impl!{print_state(&, self, context: &str) | marker => error!("{} Session(FailedUpgrade({:?}))", context, marker)} - _fn_impl!{close(&mut, self, proxy: Rc>, metrics: &mut SessionMetrics) | _ => {}} + _fn_impl!{close[P: L7Proxy](&mut, self, proxy: Rc>, metrics: &mut SessionMetrics) | _ => {}} _fn_impl!{shutting_down(&mut, self) -> SessionIsToBeClosed | _ => true} } } else {} } }; - ($($tt:tt)+) => { - StateMachineBuilder!{($) $($tt)+} + ($(#[$($state_macros:tt)*])* enum $($tt:tt)+) => { + StateMachineBuilder!{($) $(#[$($state_macros)*])* enum $($tt)+} } } pub trait ListenerHandler { - fn get_addr(&self) -> &SocketAddr; + fn protocol(&self) -> Protocol; + + fn address(&self) -> &SocketAddr; + + fn public_address(&self) -> SocketAddr; fn get_tags(&self, key: &str) -> Option<&CachedTags>; - fn get_concatenated_tags(&self, key: &str) -> Option<&str> { + fn concatenated_tags(&self, key: &str) -> Option<&str> { self.get_tags(key).map(|tags| tags.concatenated.as_str()) } @@ -542,9 +546,9 @@ pub enum FrontendFromRequestError { } pub trait L7ListenerHandler { - fn get_sticky_name(&self) -> &str; + fn sticky_name(&self) -> &str; - fn get_connect_timeout(&self) -> u32; + fn connect_timeout(&self) -> u32; /// retrieve a frontend by parsing a request's hostname, uri and method fn frontend_from_request( diff --git a/lib/src/protocol/kawa_h1/editor.rs b/lib/src/protocol/kawa_h1/editor.rs index f3b2b24f4..aacb242df 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -45,8 +45,9 @@ pub struct HttpContext { pub closing: bool, /// the value of the custom header, named "Sozu-Id", that Kawa should write (request and response) pub id: Ulid, - pub backend_id: Option, pub cluster_id: Option, + pub backend_id: Option, + pub backend_address: Option, /// the value of the protocol Kawa should write in the Forwarded headers of the request pub protocol: Protocol, /// the value of the public address Kawa should write in the Forwarded headers of the request @@ -70,6 +71,36 @@ impl kawa::h1::ParserCallbacks for HttpContext { } impl HttpContext { + pub fn new( + id: Ulid, + protocol: Protocol, + public_address: SocketAddr, + session_address: Option, + sticky_name: String, + ) -> Self { + Self { + id, + protocol, + public_address, + session_address, + sticky_name, + + cluster_id: None, + backend_id: None, + backend_address: None, + keep_alive_backend: true, + keep_alive_frontend: true, + sticky_session_found: None, + method: None, + authority: None, + path: None, + status: None, + reason: None, + user_agent: None, + closing: false, + sticky_session: None, + } + } /// Callback for request: /// /// - edit headers (connection, forwarded, sticky cookie, sozu-id) diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 8a23267f8..fc88a2c54 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -30,7 +30,6 @@ use crate::{ editor::HttpContext, parser::Method, }, - pipe::WebSocketContext, SessionState, }, retry::RetryPolicy, @@ -237,8 +236,9 @@ impl Http Http>, metrics: &mut SessionMetrics) { + fn close_backend(&mut self, proxy: Rc>, metrics: &mut SessionMetrics) { self.container_backend_timeout.cancel(); debug!( "{}\tPROXY [{}->{}] CLOSED BACKEND", @@ -1189,9 +1189,9 @@ impl Http( &mut self, - proxy: Rc>, + proxy: Rc>, ) -> Result { let (host, uri, method) = match self.context.extract_route() { Ok(tuple) => tuple, @@ -1244,11 +1244,11 @@ impl Http( &mut self, cluster_id: &str, frontend_should_stick: bool, - proxy: Rc>, + proxy: Rc>, metrics: &mut SessionMetrics, ) -> Result { let (backend, conn) = self @@ -1269,7 +1269,7 @@ impl Http Http( &self, frontend_should_stick: bool, sticky_session: Option<&str>, cluster_id: &str, - proxy: Rc>, + proxy: Rc>, ) -> Result<(Rc>, TcpStream), BackendError> { match (frontend_should_stick, sticky_session) { (true, Some(sticky_session)) => proxy @@ -1309,10 +1309,10 @@ impl Http( &mut self, session_rc: Rc>, - proxy: Rc>, + proxy: Rc>, metrics: &mut SessionMetrics, ) -> Result { let old_cluster_id = self.context.cluster_id.clone(); @@ -1609,10 +1609,10 @@ impl Http( &mut self, session: Rc>, - proxy: Rc>, + proxy: Rc>, metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; @@ -1816,10 +1816,10 @@ impl Http SessionState for Http { - fn ready( + fn ready( &mut self, session: Rc>, - proxy: Rc>, + proxy: Rc>, metrics: &mut SessionMetrics, ) -> SessionResult { let session_result = self.ready_inner(session, proxy, metrics); @@ -1850,7 +1850,7 @@ impl SessionState } } - fn close(&mut self, proxy: Rc>, metrics: &mut SessionMetrics) { + fn close(&mut self, proxy: Rc>, metrics: &mut SessionMetrics) { self.close_backend(proxy, metrics); //if the state was initial, the connection was already reset diff --git a/lib/src/protocol/mod.rs b/lib/src/protocol/mod.rs index 88dde18cb..618939c9f 100644 --- a/lib/src/protocol/mod.rs +++ b/lib/src/protocol/mod.rs @@ -24,17 +24,17 @@ pub trait SessionState { /// if a session received an event or can still execute, the event loop will /// call this method. Its result indicates if it can still execute or if the /// session can be closed - fn ready( + fn ready( &mut self, session: Rc>, - proxy: Rc>, + proxy: Rc>, metrics: &mut SessionMetrics, ) -> SessionResult; /// if the event loop got an event for a token associated with the session, /// it will call this method fn update_readiness(&mut self, token: Token, events: Ready); /// close the state - fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) {} + fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) {} /// if a timeout associated with the session triggers, the event loop will /// call this method with the timeout's token fn timeout(&mut self, token: Token, metrics: &mut SessionMetrics) -> StateResult; diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 44b3cc726..92636e2f1 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -17,7 +17,7 @@ pub struct H2BlockConverter<'a> { } impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { - fn call(&mut self, block: Block, kawa: &mut Kawa) { + fn call(&mut self, block: Block, kawa: &mut Kawa) -> bool { let buffer = kawa.storage.buffer(); match block { Block::StatusLine => match kawa.detached.status_line.pop() { @@ -49,7 +49,7 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { }, Block::Cookies => { if kawa.detached.jar.is_empty() { - return; + return true; } for cookie in kawa .detached @@ -83,7 +83,7 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { || compare_no_case(key, b"upgrade") { println!("Elided H2 header: {}", unsafe { from_utf8_unchecked(key) }); - return; + return true; } } self.encoder @@ -164,6 +164,7 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { } } } + true } fn finalize(&mut self, _kawa: &mut Kawa) { assert!(self.out.is_empty()); diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 9fb52aa86..3fb4bdff6 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -9,7 +9,7 @@ use crate::{ }, socket::SocketHandler, timer::TimeoutContainer, - Readiness, + L7ListenerHandler, ListenerHandler, Readiness, }; pub struct ConnectionH1 { @@ -34,9 +34,10 @@ impl std::fmt::Debug for ConnectionH1 { } impl ConnectionH1 { - pub fn readable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult + pub fn readable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { println_!("======= MUX H1 READABLE {:?}", self.position); self.timeout_container.reset(); @@ -55,7 +56,9 @@ impl ConnectionH1 { if kawa.is_error() { match self.position { Position::Client(_) => { - let StreamState::Linked(token) = stream.state else { unreachable!() }; + let StreamState::Linked(token) = stream.state else { + unreachable!() + }; let global_stream_id = self.stream; self.readiness.interest.remove(Ready::ALL); self.end_stream(global_stream_id, context); @@ -92,9 +95,10 @@ impl ConnectionH1 { MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult + pub fn writable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { println_!("======= MUX H1 WRITABLE {:?}", self.position); self.timeout_container.reset(); @@ -150,6 +154,8 @@ impl ConnectionH1 { } _ => {} } + // ACCESS LOG + stream.generate_access_log(false, Some(String::from("H1")), context.listener.clone()); let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); if stream.context.keep_alive_frontend { self.timeout_container.reset(); @@ -186,9 +192,10 @@ impl ConnectionH1 { } } - pub fn close(&mut self, context: &mut Context, mut endpoint: E) + pub fn close(&mut self, context: &mut Context, mut endpoint: E) where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { match self.position { Position::Client(BackendStatus::KeepAlive(_)) @@ -201,11 +208,16 @@ impl ConnectionH1 { Position::Server => unreachable!(), } // reconnection is handled by the server - let StreamState::Linked(token) = context.streams[self.stream].state else {unreachable!()}; + let StreamState::Linked(token) = context.streams[self.stream].state else { + unreachable!() + }; endpoint.end_stream(token, self.stream, context) } - pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { assert_eq!(stream, self.stream); let stream = &mut context.streams[stream]; let stream_context = &mut stream.context; @@ -250,7 +262,10 @@ impl ConnectionH1 { } } - pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { println_!("start H1 stream {stream} {:?}", self.readiness); self.readiness.interest.insert(Ready::ALL); self.stream = stream; diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index a7ed67d8a..742066129 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -14,7 +14,7 @@ use crate::{ }, socket::SocketHandler, timer::TimeoutContainer, - Readiness, + L7ListenerHandler, ListenerHandler, Readiness, }; #[inline(always)] @@ -123,9 +123,10 @@ pub enum H2StreamId { } impl ConnectionH2 { - pub fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + pub fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { println_!("======= MUX H2 READABLE {:?}", self.position); self.timeout_container.reset(); @@ -321,9 +322,10 @@ impl ConnectionH2 { MuxResult::Continue } - pub fn writable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult + pub fn writable(&mut self, context: &mut Context, mut endpoint: E) -> MuxResult where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { println_!("======= MUX H2 WRITABLE {:?}", self.position); self.timeout_container.reset(); @@ -408,6 +410,12 @@ impl ConnectionH2 { Position::Server => { // mark stream as reusable println_!("Recycle stream: {global_stream_id}"); + // ACCESS LOG + stream.generate_access_log( + false, + Some(String::from("H2::SplitFrame")), + context.listener.clone(), + ); let state = std::mem::replace(&mut stream.state, StreamState::Recycle); if let StreamState::Linked(token) = state { @@ -453,6 +461,12 @@ impl ConnectionH2 { Position::Server => { // mark stream as reusable println_!("Recycle stream: {global_stream_id}"); + // ACCESS LOG + stream.generate_access_log( + false, + Some(String::from("H2::WholeFrame")), + context.listener.clone(), + ); let state = std::mem::replace(&mut stream.state, StreamState::Recycle); if let StreamState::Linked(token) = state { @@ -497,11 +511,14 @@ impl ConnectionH2 { } } - pub fn create_stream( + pub fn create_stream( &mut self, stream_id: StreamId, - context: &mut Context, - ) -> Option { + context: &mut Context, + ) -> Option + where + L: ListenerHandler + L7ListenerHandler, + { let global_stream_id = context.create_stream( Ulid::generate(), self.peer_settings.settings_initial_window_size, @@ -521,9 +538,15 @@ impl ConnectionH2 { } } - fn handle_frame(&mut self, frame: Frame, context: &mut Context, mut endpoint: E) -> MuxResult + fn handle_frame( + &mut self, + frame: Frame, + context: &mut Context, + mut endpoint: E, + ) -> MuxResult where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { println_!("{frame:#?}"); match frame { @@ -618,6 +641,15 @@ impl ConnectionH2 { match self.position { Position::Client(_) => {} Position::Server => { + // This is a special case, normally, all stream are terminated by the server + // when the last byte of the response is written. Here, the reset is requested + // on the server endpoint and immediately terminates, shortcutting the other path + // ACCESS LOG + stream.generate_access_log( + true, + Some(String::from("H2::ResetFrame")), + context.listener.clone(), + ); stream.state = StreamState::Recycle; } } @@ -700,9 +732,10 @@ impl ConnectionH2 { } } - pub fn close(&mut self, context: &mut Context, mut endpoint: E) + pub fn close(&mut self, context: &mut Context, mut endpoint: E) where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { match self.position { Position::Client(BackendStatus::Connected(_)) @@ -714,12 +747,17 @@ impl ConnectionH2 { // reconnection is handled by the server for each stream separately for global_stream_id in self.streams.values() { println_!("end stream: {global_stream_id}"); - let StreamState::Linked(token) = context.streams[*global_stream_id].state else { unreachable!() }; + let StreamState::Linked(token) = context.streams[*global_stream_id].state else { + unreachable!() + }; endpoint.end_stream(token, *global_stream_id, context) } } - pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { let stream_context = &mut context.streams[stream].context; println_!("end H2 stream {stream}: {stream_context:#?}"); match self.position { @@ -764,7 +802,10 @@ impl ConnectionH2 { } } - pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + pub fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { println_!("start new H2 stream {stream} {:?}", self.readiness); let stream_id = self.new_stream_id(); self.streams.insert(stream_id, stream); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 79aa8cc37..a3bed190e 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -9,7 +9,7 @@ use std::{ use mio::{net::TcpStream, Interest, Token}; use rusty_ulid::Ulid; -use sozu_command::{proto::command::ListenerType, ready::Ready}; +use sozu_command::{logging::EndpointRecord, proto::command::ListenerType, ready::Ready}; mod converter; mod h1; @@ -20,24 +20,25 @@ mod serializer; use crate::{ backends::{Backend, BackendError}, + http::HttpListener, + https::HttpsListener, pool::{Checkout, Pool}, protocol::{ http::editor::HttpContext, - mux::h2::{H2Settings, H2State, H2StreamId}, + mux::h2::{H2Settings, H2State, H2StreamId, Prioriser}, SessionState, }, router::Route, server::CONN_RETRIES, - socket::{SocketHandler, SocketResult}, + socket::{FrontRustls, SocketHandler, SocketResult}, timer::TimeoutContainer, - BackendConnectionError, L7ListenerHandler, L7Proxy, ProxySession, Readiness, - RetrieveClusterError, SessionIsToBeClosed, SessionMetrics, SessionResult, StateResult, + BackendConnectionError, L7ListenerHandler, L7Proxy, ListenerHandler, Protocol, ProxySession, + Readiness, RetrieveClusterError, SessionIsToBeClosed, SessionMetrics, SessionResult, + StateResult, }; pub use crate::protocol::mux::{h1::ConnectionH1, h2::ConnectionH2}; -use self::h2::Prioriser; - #[macro_export] macro_rules! println_ { ($($t:expr),*) => { @@ -54,6 +55,8 @@ fn debug_kawa(_kawa: &GenericHttpStream) { type GenericHttpStream = kawa::Kawa; type StreamId = u32; type GlobalStreamId = usize; +pub type MuxClear = Mux; +pub type MuxTls = Mux; pub fn fill_default_301_answer(kawa: &mut kawa::Kawa, host: &str, uri: &str) { kawa.detached.status_line = kawa::StatusLine::Response { @@ -117,6 +120,7 @@ fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) } else { fill_default_answer(kawa, code); } + stream.context.status = Some(code); stream.state = StreamState::Unlinked; readiness.interest.insert(Ready::WRITABLE); } @@ -165,11 +169,21 @@ pub trait Endpoint { /// If end_stream is called on a server it means the stream was BROKEN, the client was most likely disconnected or encountered an error /// it is for the server to decide if the stream can be retried or an error should be sent. It should be GUARANTEED that all bytes from /// the backend were read. However it is almost certain that all bytes were not already sent to the client. - fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context); + fn end_stream( + &mut self, + token: Token, + stream: GlobalStreamId, + context: &mut Context, + ); /// If start_stream is called on a client it means the stream should be attached to this endpoint, /// the stream might be recovering from a disconnection, in any case at this point its response MUST be empty. /// If the start_stream is called on a H2 server it means the stream is a server push and its request MUST be empty. - fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context); + fn start_stream( + &mut self, + token: Token, + stream: GlobalStreamId, + context: &mut Context, + ); } #[derive(Debug)] @@ -323,18 +337,21 @@ impl Connection { Connection::H2(c) => c.force_disconnect(), } } - fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + + fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { match self { Connection::H1(c) => c.readable(context, endpoint), Connection::H2(c) => c.readable(context, endpoint), } } - fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult + fn writable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { match self { Connection::H1(c) => c.writable(context, endpoint), @@ -342,9 +359,10 @@ impl Connection { } } - fn close(&mut self, context: &mut Context, endpoint: E) + fn close(&mut self, context: &mut Context, endpoint: E) where E: Endpoint, + L: ListenerHandler + L7ListenerHandler, { match self { Connection::H1(c) => c.close(context, endpoint), @@ -352,14 +370,20 @@ impl Connection { } } - fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { match self { Connection::H1(c) => c.end_stream(stream, context), Connection::H2(c) => c.end_stream(stream, context), } } - fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) { + fn start_stream(&mut self, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { match self { Connection::H1(c) => c.start_stream(stream, context), Connection::H2(c) => c.start_stream(stream, context), @@ -380,13 +404,19 @@ impl<'a, Front: SocketHandler> Endpoint for EndpointServer<'a, Front> { self.0.readiness_mut() } - fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { // this may be used to forward H2<->H2 RstStream // or to handle backend hup self.0.end_stream(stream, context); } - fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { // this may be used to forward H2<->H2 PushPromise todo!() } @@ -399,7 +429,10 @@ impl<'a> Endpoint for EndpointClient<'a> { self.0.backends.get_mut(&token).unwrap().readiness_mut() } - fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { self.0 .backends .get_mut(&token) @@ -407,7 +440,10 @@ impl<'a> Endpoint for EndpointClient<'a> { .end_stream(stream, context); } - fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) { + fn start_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) + where + L: ListenerHandler + L7ListenerHandler, + { self.0 .backends .get_mut(&token) @@ -522,31 +558,8 @@ pub struct StreamParts<'a> { pub context: &'a mut HttpContext, } -fn temporary_http_context(request_id: Ulid) -> HttpContext { - HttpContext { - backend_id: None, - cluster_id: None, - keep_alive_backend: true, - keep_alive_frontend: true, - sticky_session_found: None, - method: None, - authority: None, - path: None, - status: None, - reason: None, - user_agent: None, - closing: false, - id: request_id, - protocol: crate::Protocol::HTTPS, - public_address: "0.0.0.0:80".parse().unwrap(), - session_address: None, - sticky_name: "SOZUBALANCEID".to_owned(), - sticky_session: None, - } -} - impl Stream { - pub fn new(pool: Weak>, request_id: Ulid, window: u32) -> Option { + pub fn new(pool: Weak>, context: HttpContext, window: u32) -> Option { let (front_buffer, back_buffer) = match pool.upgrade() { Some(pool) => { let mut pool = pool.borrow_mut(); @@ -563,7 +576,7 @@ impl Stream { window: window as i32, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), - context: temporary_http_context(request_id), + context, }) } pub fn split(&mut self, position: &Position) -> StreamParts<'_> { @@ -592,22 +605,90 @@ impl Stream { Position::Server => &mut self.back, } } + pub fn generate_access_log( + &mut self, + error: bool, + message: Option, + listener: Rc>, + ) where + L: ListenerHandler + L7ListenerHandler, + { + let protocol = match self.context.protocol { + Protocol::HTTP => "http", + Protocol::HTTPS => "https", + _ => unreachable!(), + }; + + let endpoint = EndpointRecord::Http { + method: self.context.method.as_deref(), + authority: self.context.authority.as_deref(), + path: self.context.path.as_deref(), + reason: self.context.reason.as_deref(), + status: self.context.status, + }; + + let listener = listener.borrow(); + let tags = self.context.authority.as_deref().and_then(|host| { + let hostname = match host.split_once(':') { + None => host, + Some((hostname, _)) => hostname, + }; + listener.get_tags(hostname) + }); + + log_access! { + error, + on_failure: { incr!("unsent-access-logs") }, + message: message.as_deref(), + context: self.context.log_context(), + session_address: self.context.session_address, + backend_address: self.context.backend_address, + protocol, + endpoint, + tags, + client_rtt: None, //socket_rtt(self.front_socket()), + server_rtt: None, //self.backend_socket.as_ref().and_then(socket_rtt), + service_time: Duration::from_micros(0), //metrics.service_time(), + response_time: Some(Duration::from_micros(0)), //metrics.response_time(), + request_time: Duration::from_micros(0), // metrics.request_time(), + bytes_in: 0, //metrics.bin, + bytes_out: 0, //metrics.bout, + user_agent: self.context.user_agent.as_deref(), + }; + } } -pub struct Context { +pub struct Context { pub streams: Vec, pub pool: Weak>, + pub listener: Rc>, } -impl Context { +impl Context { + pub fn new(pool: Weak>, listener: Rc>) -> Self { + Self { + streams: Vec::new(), + pool, + listener, + } + } + pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option { + let listener = self.listener.borrow(); + let http_context = HttpContext::new( + request_id, + listener.protocol(), + listener.public_address(), + None, // TODO: how??? + listener.sticky_name().to_string(), + ); for (stream_id, stream) in self.streams.iter_mut().enumerate() { if stream.state == StreamState::Recycle { println_!("Reuse stream: {stream_id}"); stream.state = StreamState::Idle; stream.attempts = 0; stream.window = window as i32; - stream.context = temporary_http_context(request_id); + stream.context = http_context; stream.back.clear(); stream.back.storage.clear(); stream.front.clear(); @@ -616,45 +697,32 @@ impl Context { } } self.streams - .push(Stream::new(self.pool.clone(), request_id, window)?); + .push(Stream::new(self.pool.clone(), http_context, window)?); Some(self.streams.len() - 1) } - - pub fn new(pool: Weak>) -> Context { - Self { - streams: Vec::new(), - pool, - } - } } pub struct Router { pub backends: HashMap>, pub configured_backend_timeout: Duration, pub configured_connect_timeout: Duration, - pub listener: Rc>, } impl Router { - pub fn new( - configured_backend_timeout: Duration, - configured_connect_timeout: Duration, - listener: Rc>, - ) -> Self { + pub fn new(configured_backend_timeout: Duration, configured_connect_timeout: Duration) -> Self { Self { backends: HashMap::new(), configured_backend_timeout, configured_connect_timeout, - listener, } } - fn connect( + fn connect( &mut self, stream_id: GlobalStreamId, - context: &mut Context, + context: &mut Context, session: Rc>, - proxy: Rc>, + proxy: Rc>, metrics: &mut SessionMetrics, ) -> Result<(), BackendConnectionError> { let stream = &mut context.streams[stream_id]; @@ -662,14 +730,17 @@ impl Router { // with concurrent connections on a single endpoint assert!(matches!(stream.state, StreamState::Link)); if stream.attempts >= CONN_RETRIES { - return Err(BackendConnectionError::MaxConnectionRetries(None)); + return Err(BackendConnectionError::MaxConnectionRetries( + stream.context.cluster_id.clone(), + )); } stream.attempts += 1; let stream_context = &mut stream.context; let cluster_id = self - .route_from_request(stream_context, proxy.clone()) + .route_from_request(stream_context, &context.listener) .map_err(BackendConnectionError::RetrieveClusterError)?; + stream_context.cluster_id = Some(cluster_id.clone()); let (frontend_should_stick, frontend_should_redirect_https, h2) = proxy .borrow() @@ -690,6 +761,19 @@ impl Router { )); } + /* + Current h2 connecting strategy: + - look at every backend connection + - reuse the first connected backend that belongs to the same cluster + - or, reuse the last connecting backend that belonds to the same cluster + - if no backend is to reuse, ask the router for a socket to the "next in line" backend for that cluster + + We may want to change to: + - ask the router the name of the "next in line" backend for that cluster + - if we already have a backend connected to this name, reuse it + - if not, create a new socket to it + */ + let mut reuse_token = None; // let mut priority = 0; let mut reuse_connecting = true; @@ -699,22 +783,22 @@ impl Router { unreachable!("Backend connection behaves like a server") } (_, _, Position::Client(BackendStatus::Disconnecting)) => {} + (true, false, Position::Client(BackendStatus::Connecting(_))) => {} - (true, _, Position::Client(BackendStatus::Connected(old_cluster_id))) => { - if *old_cluster_id == cluster_id { + (true, _, Position::Client(BackendStatus::Connected(other_cluster_id))) => { + if *other_cluster_id == cluster_id { reuse_token = Some(*token); reuse_connecting = false; break; } } - (true, true, Position::Client(BackendStatus::Connecting(old_cluster_id))) => { - if *old_cluster_id == cluster_id { + (true, true, Position::Client(BackendStatus::Connecting(other_cluster_id))) => { + if *other_cluster_id == cluster_id { reuse_token = Some(*token) } } - (true, false, Position::Client(BackendStatus::Connecting(_))) => {} - (true, _, Position::Client(BackendStatus::KeepAlive(old_cluster_id))) => { - if *old_cluster_id == cluster_id { + (true, _, Position::Client(BackendStatus::KeepAlive(other_cluster_id))) => { + if *other_cluster_id == cluster_id { unreachable!("ConnectionH2 behaves like H1") } } @@ -742,6 +826,7 @@ impl Router { frontend_should_stick, stream_context, proxy.clone(), + &context.listener, metrics, )?; @@ -792,10 +877,10 @@ impl Router { Ok(()) } - fn route_from_request( + fn route_from_request( &mut self, context: &mut HttpContext, - _proxy: Rc>, + listener: &Rc>, ) -> Result { let (host, uri, method) = match context.extract_route() { Ok(tuple) => tuple, @@ -806,10 +891,7 @@ impl Router { } }; - let route_result = self - .listener - .borrow() - .frontend_from_request(host, uri, method); + let route_result = listener.borrow().frontend_from_request(host, uri, method); let route = match route_result { Ok(route) => route, @@ -832,12 +914,13 @@ impl Router { Ok(cluster_id) } - pub fn backend_from_request( + pub fn backend_from_request( &mut self, cluster_id: &str, frontend_should_stick: bool, context: &mut HttpContext, - proxy: Rc>, + proxy: Rc>, + listener: &Rc>, _metrics: &mut SessionMetrics, ) -> Result { let (backend, conn) = self @@ -855,7 +938,7 @@ impl Router { if frontend_should_stick { // update sticky name in case it changed I guess? - context.sticky_name = self.listener.borrow().get_sticky_name().to_string(); + context.sticky_name = listener.borrow().sticky_name().to_string(); context.sticky_session = Some( backend @@ -870,16 +953,18 @@ impl Router { // metrics.backend_start(); // self.set_backend_id(backend.borrow().backend_id.clone()); // self.backend = Some(backend); + context.backend_id = Some(backend.borrow().backend_id.clone()); + context.backend_address = Some(backend.borrow().address); Ok(conn) } - fn get_backend_for_sticky_session( + fn get_backend_for_sticky_session( &self, cluster_id: &str, frontend_should_stick: bool, sticky_session: Option<&str>, - proxy: Rc>, + proxy: Rc>, ) -> Result<(Rc>, TcpStream), BackendError> { match (frontend_should_stick, sticky_session) { (true, Some(sticky_session)) => proxy @@ -896,7 +981,7 @@ impl Router { } } -pub struct Mux { +pub struct Mux { pub configured_frontend_timeout: Duration, pub frontend_token: Token, pub frontend: Connection, @@ -904,20 +989,22 @@ pub struct Mux { pub public_address: SocketAddr, pub peer_address: Option, pub sticky_name: String, - pub context: Context, + pub context: Context, } -impl Mux { +impl Mux { pub fn front_socket(&self) -> &TcpStream { self.frontend.socket() } } -impl SessionState for Mux { - fn ready( +impl SessionState + for Mux +{ + fn ready( &mut self, session: Rc>, - proxy: Rc>, + proxy: Rc>, metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; @@ -1174,7 +1261,7 @@ impl SessionState for Mux { let stream = &mut self.context.streams[stream_id]; if let StreamState::Linked(back_token) = stream.state { if token == back_token { - // This stream is linked to the backend that timedout. + // This stream is linked to the backend that timedout if stream.back.is_terminated() || stream.back.is_error() { println!( "Stream terminated or in error, do nothing, just wait a bit more" @@ -1183,7 +1270,7 @@ impl SessionState for Mux { if !stream.back.is_completed() { should_close = false; } - } else if stream.back.is_initial() { + } else if !stream.back.consumed { // The response has not started yet println!("Stream still waiting for response, send 504"); set_default_answer(stream, front_readiness, 504); @@ -1194,10 +1281,13 @@ impl SessionState for Mux { should_write = true; } backend.end_stream(stream_id, &mut self.context); - backend.force_disconnect(); + // backend.force_disconnect(); } } } + } else { + // Session received a timeout for an unknown token, ignore it + return StateResult::Continue; } if should_write { return match self @@ -1244,7 +1334,7 @@ impl SessionState for Mux { } } - fn close(&mut self, proxy: Rc>, _metrics: &mut SessionMetrics) { + fn close(&mut self, proxy: Rc>, _metrics: &mut SessionMetrics) { println_!("FRONTEND: {:#?}", self.frontend); println_!("BACKENDS: {:#?}", self.router.backends); @@ -1273,7 +1363,7 @@ impl SessionState for Mux { }; let mut b = [0; 1024]; let (size, status) = s.socket_read(&mut b); - println_!("{size} {status:?} {:?}", &b[..size]); + println_!("socket: {size} {status:?} {:?}", &b[..size]); for stream in &mut self.context.streams { for kawa in [&mut stream.front, &mut stream.back] { debug_kawa(kawa); diff --git a/lib/src/protocol/pipe.rs b/lib/src/protocol/pipe.rs index d6ad6283c..e83735379 100644 --- a/lib/src/protocol/pipe.rs +++ b/lib/src/protocol/pipe.rs @@ -243,7 +243,7 @@ impl Pipe { backend_address: self.get_backend_address(), protocol: self.protocol_string(), endpoint, - tags: listener.get_tags(&listener.get_addr().to_string()), + tags: listener.get_tags(&listener.address().to_string()), client_rtt: socket_rtt(self.front_socket()), server_rtt: self.backend_socket.as_ref().and_then(socket_rtt), service_time: metrics.service_time(), @@ -661,10 +661,10 @@ impl Pipe { } impl SessionState for Pipe { - fn ready( + fn ready( &mut self, _session: Rc>, - _proxy: Rc>, + _proxy: Rc>, metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; @@ -794,7 +794,7 @@ impl SessionState for Pipe { self.container_backend_timeout.as_mut().map(|t| t.cancel()); } - fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) { + fn close(&mut self, _proxy: Rc>, _metrics: &mut SessionMetrics) { if let Some(backend) = self.backend.as_mut() { let mut backend = backend.borrow_mut(); backend.active_requests = backend.active_requests.saturating_sub(1); diff --git a/lib/src/protocol/proxy_protocol/expect.rs b/lib/src/protocol/proxy_protocol/expect.rs index 031d1cdf9..59386da2b 100644 --- a/lib/src/protocol/proxy_protocol/expect.rs +++ b/lib/src/protocol/proxy_protocol/expect.rs @@ -15,7 +15,7 @@ use crate::{ sozu_command::ready::Ready, tcp::TcpListener, timer::TimeoutContainer, - Protocol, Readiness, SessionMetrics, StateResult, + L7Proxy, Protocol, Readiness, SessionMetrics, StateResult, }; use super::{header::ProxyAddr, parser::parse_v2_header}; @@ -204,13 +204,20 @@ impl ExpectProxyProtocol { backend_id: None, } } + + fn print_state(&self, context: &str) { + error!( + "{} Session(Expect)\n\tFrontend:\n\t\ttoken: {:?}\treadiness: {:?}", + context, self.frontend_token, self.frontend_readiness + ); + } } impl SessionState for ExpectProxyProtocol { - fn ready( + fn ready( &mut self, _session: Rc>, - _proxy: Rc>, + _proxy: Rc>, metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; @@ -292,10 +299,7 @@ impl SessionState for ExpectProxyProtocol { } fn print_state(&self, context: &str) { - error!( - "{} Session(Expect)\n\tFrontend:\n\t\ttoken: {:?}\treadiness: {:?}", - context, self.frontend_token, self.frontend_readiness - ); + self.print_state(context) } } diff --git a/lib/src/protocol/rustls.rs b/lib/src/protocol/rustls.rs index a6116188a..e8f1c4c65 100644 --- a/lib/src/protocol/rustls.rs +++ b/lib/src/protocol/rustls.rs @@ -6,7 +6,7 @@ use rusty_ulid::Ulid; use sozu_command::{config::MAX_LOOP_ITERATIONS, logging::LogContext}; use crate::{ - protocol::SessionState, timer::TimeoutContainer, Readiness, Ready, SessionMetrics, + protocol::SessionState, timer::TimeoutContainer, L7Proxy, Readiness, Ready, SessionMetrics, SessionResult, StateResult, }; @@ -221,10 +221,10 @@ impl TlsHandshake { } impl SessionState for TlsHandshake { - fn ready( + fn ready( &mut self, _session: Rc>, - _proxy: Rc>, + _proxy: Rc>, _metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; diff --git a/lib/src/tcp.rs b/lib/src/tcp.rs index 9005ed9fe..453267e96 100644 --- a/lib/src/tcp.rs +++ b/lib/src/tcp.rs @@ -221,7 +221,7 @@ impl TcpSession { backend_address: None, protocol: "TCP", endpoint: EndpointRecord::Tcp, - tags: listener.get_tags(&listener.get_addr().to_string()), + tags: listener.get_tags(&listener.address().to_string()), client_rtt: socket_rtt(self.state.front_socket()), server_rtt: None, user_agent: None, @@ -1098,10 +1098,22 @@ pub struct TcpListener { } impl ListenerHandler for TcpListener { - fn get_addr(&self) -> &SocketAddr { + fn protocol(&self) -> Protocol { + Protocol::TCP + } + + fn address(&self) -> &SocketAddr { &self.address } + fn public_address(&self) -> SocketAddr { + self.config + .public_address + .as_ref() + .map(|addr| addr.clone().into()) + .unwrap_or(self.address.clone()) + } + fn get_tags(&self, key: &str) -> Option<&CachedTags> { self.tags.get(key) } From de22a00cec7163a5adb461b571ceae5ed36626bd Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 13 May 2024 12:06:52 +0200 Subject: [PATCH 35/44] Move session_address, public_address and sticky_name around Signed-off-by: Eloi DEMOLIS --- lib/src/http.rs | 24 ++++++++++++------------ lib/src/https.rs | 10 ++++++---- lib/src/protocol/kawa_h1/editor.rs | 4 ++-- lib/src/protocol/mux/mod.rs | 18 ++++++++++++------ 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/lib/src/http.rs b/lib/src/http.rs index 584118671..abd081111 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -83,7 +83,6 @@ pub struct HttpSession { pool: Weak>, proxy: Rc>, state: HttpStateMachine, - sticky_name: String, has_been_closed: bool, } @@ -101,7 +100,6 @@ impl HttpSession { proxy: Rc>, public_address: SocketAddr, sock: TcpStream, - sticky_name: String, token: Token, wait_time: Duration, ) -> Result { @@ -124,7 +122,12 @@ impl HttpSession { let frontend = mux::Connection::new_h1_server(sock, container_frontend_timeout); let router = mux::Router::new(configured_backend_timeout, configured_connect_timeout); - let mut context = mux::Context::new(pool.clone(), listener.clone()); + let mut context = mux::Context::new( + pool.clone(), + listener.clone(), + session_address, + public_address, + ); context .create_stream(request_id, 1 << 16) .ok_or(AcceptError::BufferCapacityReached)?; @@ -133,9 +136,6 @@ impl HttpSession { frontend_token: token, frontend, router, - public_address, - peer_address: session_address, - sticky_name: sticky_name.clone(), context, }) // HttpStateMachine::Http(Http::new( @@ -170,7 +170,6 @@ impl HttpSession { pool, proxy, state, - sticky_name, }) } @@ -213,16 +212,18 @@ impl HttpSession { self.configured_backend_timeout, self.configured_connect_timeout, ); - let mut context = mux::Context::new(self.pool.clone(), self.listener.clone()); + let mut context = mux::Context::new( + self.pool.clone(), + self.listener.clone(), + Some(session_address), + public_address, + ); context.create_stream(expect.request_id, 1 << 16)?; let mut mux = Mux { configured_frontend_timeout: self.configured_frontend_timeout, frontend_token: self.frontend_token, frontend, router, - public_address, - peer_address: Some(session_address), - sticky_name: self.sticky_name.clone(), context, }; mux.frontend.readiness_mut().event = expect.frontend_readiness.event; @@ -981,7 +982,6 @@ impl ProxyConfiguration for HttpProxy { proxy, public_address, frontend_sock, - owned.config.sticky_name.clone(), session_token, wait_time, )?; diff --git a/lib/src/https.rs b/lib/src/https.rs index cd565630b..1d57e2169 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -291,7 +291,12 @@ impl HttpsSession { self.configured_backend_timeout, self.configured_connect_timeout, ); - let mut context = mux::Context::new(self.pool.clone(), self.listener.clone()); + let mut context = mux::Context::new( + self.pool.clone(), + self.listener.clone(), + self.peer_address, + self.public_address, + ); let mut frontend = match alpn { AlpnProtocol::Http11 => { context.create_stream(handshake.request_id, 1 << 16)?; @@ -312,9 +317,6 @@ impl HttpsSession { frontend, context, router, - public_address: self.public_address, - peer_address: self.peer_address, - sticky_name: self.sticky_name.clone(), })) } diff --git a/lib/src/protocol/kawa_h1/editor.rs b/lib/src/protocol/kawa_h1/editor.rs index aacb242df..9aca0dc0a 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -74,16 +74,16 @@ impl HttpContext { pub fn new( id: Ulid, protocol: Protocol, + sticky_name: String, public_address: SocketAddr, session_address: Option, - sticky_name: String, ) -> Self { Self { id, protocol, + sticky_name, public_address, session_address, - sticky_name, cluster_id: None, backend_id: None, diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index a3bed190e..969ce4a1f 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -662,14 +662,23 @@ pub struct Context { pub streams: Vec, pub pool: Weak>, pub listener: Rc>, + pub session_address: Option, + pub public_address: SocketAddr, } impl Context { - pub fn new(pool: Weak>, listener: Rc>) -> Self { + pub fn new( + pool: Weak>, + listener: Rc>, + session_address: Option, + public_address: SocketAddr, + ) -> Self { Self { streams: Vec::new(), pool, listener, + session_address, + public_address, } } @@ -678,9 +687,9 @@ impl Context { let http_context = HttpContext::new( request_id, listener.protocol(), - listener.public_address(), - None, // TODO: how??? listener.sticky_name().to_string(), + self.public_address, + self.session_address, ); for (stream_id, stream) in self.streams.iter_mut().enumerate() { if stream.state == StreamState::Recycle { @@ -986,9 +995,6 @@ pub struct Mux { pub frontend_token: Token, pub frontend: Connection, pub router: Router, - pub public_address: SocketAddr, - pub peer_address: Option, - pub sticky_name: String, pub context: Context, } From bc27e25103075d94bb1a4c76a301734b424f7897 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 22 May 2024 22:49:18 +0200 Subject: [PATCH 36/44] h2spec ETA: 113/147 Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 2 +- lib/src/protocol/mux/h2.rs | 239 +++++++++++++++++++++-------- lib/src/protocol/mux/mod.rs | 48 ++++-- lib/src/protocol/mux/parser.rs | 201 +++++++++++++++--------- lib/src/protocol/mux/pkawa.rs | 159 +++++++++++-------- lib/src/protocol/mux/serializer.rs | 10 +- 6 files changed, 444 insertions(+), 215 deletions(-) diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 92636e2f1..0677cb54e 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -124,7 +124,7 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { .. }) => { if end_header { - let payload = std::mem::replace(&mut self.out, Vec::new()); + let payload = std::mem::take(&mut self.out); let mut header = [0; 9]; let flags = if end_stream { 1 } else { 0 } | if end_header { 4 } else { 0 }; gen_frame_header( diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 742066129..91647d9bb 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -7,7 +7,10 @@ use crate::{ println_, protocol::mux::{ converter, debug_kawa, forcefully_terminate_answer, - parser::{self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error, Headers}, + parser::{ + self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error, Headers, ParserError, + ParserErrorKind, + }, pkawa, serializer, set_default_answer, update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, Endpoint, GenericHttpStream, GlobalStreamId, MuxResult, Position, StreamId, StreamState, @@ -18,10 +21,14 @@ use crate::{ }; #[inline(always)] -fn error_nom_to_h2(error: nom::Err) -> H2Error { +fn error_nom_to_h2(error: nom::Err) -> H2Error { match error { - nom::Err::Error(parser::Error { - error: parser::InnerError::H2(e), + nom::Err::Error(parser::ParserError { + kind: parser::ParserErrorKind::H2(e), + .. + }) => return e, + nom::Err::Failure(parser::ParserError { + kind: parser::ParserErrorKind::H2(e), .. }) => return e, _ => return H2Error::ProtocolError, @@ -39,6 +46,7 @@ pub enum H2State { ContinuationFrame(Headers), GoAway, Error, + Discard, } #[derive(Debug)] @@ -76,8 +84,8 @@ impl Prioriser { pub fn new() -> Self { Self {} } - pub fn push_priority(&mut self, priority: parser::Priority) { - println!("DEPRECATED: {priority:?}"); + pub fn push_priority(&mut self, stream_id: StreamId, priority: parser::PriorityPart) { + println!("PRIORITY REQUEST FOR {stream_id}: {priority:?}"); } } @@ -96,7 +104,7 @@ pub struct ConnectionH2 { pub state: H2State, pub streams: HashMap, pub timeout_container: TimeoutContainer, - pub window: u32, + pub window: i32, pub zero: GenericHttpStream, } impl std::fmt::Debug for ConnectionH2 { @@ -116,7 +124,7 @@ impl std::fmt::Debug for ConnectionH2 { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum H2StreamId { Zero, Other(StreamId, GlobalStreamId), @@ -139,6 +147,10 @@ impl ConnectionH2 { }; println_!("{:?}({stream_id:?}, {amount})", self.state); if amount > 0 { + if amount > kawa.storage.available_space() { + self.readiness.interest.remove(Ready::READABLE); + return MuxResult::Continue; + } let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); kawa.storage.fill(size); if update_readiness_after_read(size, status, &mut self.readiness) { @@ -148,6 +160,25 @@ impl ConnectionH2 { self.expect_read = None; } else { self.expect_read = Some((stream_id, amount - size)); + match (&self.state, &self.position) { + (H2State::ClientPreface, Position::Server) => { + let i = kawa.storage.data(); + if !b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".starts_with(i) { + println_!("EARLY INVALID PREFACE: {i:?}"); + return self.force_disconnect(); + } + } + // ( + // H2State::Frame(FrameHeader { + // payload_len, + // frame_type: FrameType::Data, + // flags, + // stream_id, + // }), + // _, + // ) => {} + _ => {} + } return MuxResult::Continue; } } @@ -165,16 +196,23 @@ impl ConnectionH2 { | (H2State::ServerSettings, Position::Server) | (H2State::ClientPreface, Position::Client(_)) | (H2State::ClientSettings, Position::Client(_)) => unreachable!( - "Unexpected combination: (Writable, {:?}, {:?})", + "Unexpected combination: (Readable, {:?}, {:?})", self.state, self.position ), + (H2State::Discard, _) => { + let i = kawa.storage.data(); + println_!("DISCARDING: {i:?}"); + kawa.storage.clear(); + self.state = H2State::Header; + self.expect_read = Some((H2StreamId::Zero, 9)); + } (H2State::ClientPreface, Position::Server) => { let i = kawa.storage.data(); let i = match parser::preface(i) { Ok((i, _)) => i, Err(_) => return self.force_disconnect(), }; - match parser::frame_header(i) { + match parser::frame_header(i, self.local_settings.settings_max_frame_size) { Ok(( _, FrameHeader { @@ -223,7 +261,7 @@ impl ConnectionH2 { } (H2State::ServerSettings, Position::Client(_)) => { let i = kawa.storage.data(); - match parser::frame_header(i) { + match parser::frame_header(i, self.local_settings.settings_max_frame_size) { Ok(( _, header @ FrameHeader { @@ -243,74 +281,120 @@ impl ConnectionH2 { (H2State::Header, _) => { let i = kawa.storage.data(); println_!(" header: {i:?}"); - match parser::frame_header(i) { + match parser::frame_header(i, self.local_settings.settings_max_frame_size) { Ok((_, header)) => { println_!("{header:#?}"); kawa.storage.clear(); let stream_id = header.stream_id; - let stream_id = - if stream_id == 0 || header.frame_type == FrameType::RstStream { - H2StreamId::Zero + let read_stream = if stream_id == 0 { + H2StreamId::Zero + } else if let Some(global_stream_id) = self.streams.get(&stream_id) { + let stream = &context.streams[*global_stream_id]; + println_!( + "REQUESTING EXISTING STREAM {stream_id}: {}/{:?}", + stream.received_end_of_stream, + stream.state + ); + if stream.received_end_of_stream || !stream.state.is_open() { + return self.goaway(H2Error::StreamClosed); + } + if header.frame_type == FrameType::Data { + H2StreamId::Other(stream_id, *global_stream_id) } else { - let global_stream_id = - if let Some(global_stream_id) = self.streams.get(&stream_id) { - *global_stream_id - } else { - match self.create_stream(stream_id, context) { - Some(global_stream_id) => global_stream_id, - None => return self.goaway(H2Error::InternalError), - } - }; - if header.frame_type == FrameType::Data { - H2StreamId::Other(stream_id, global_stream_id) - } else { - H2StreamId::Zero + H2StreamId::Zero + } + } else { + if header.frame_type == FrameType::Headers + && self.position.is_server() + && stream_id % 2 == 1 + && stream_id >= self.last_stream_id + { + if context.streams.len() + >= self.local_settings.settings_max_concurrent_streams as usize + { + return self.goaway(H2Error::RefusedStream); + } + match self.create_stream(stream_id, context) { + Some(_) => {} + None => return self.goaway(H2Error::InternalError), } - }; + } else if header.frame_type != FrameType::Priority { + println_!( + "ONLY HEADERS AND PRIORITY CAN BE RECEIVED ON IDLE/CLOSED STREAMS" + ); + return self.goaway(H2Error::ProtocolError); + } + H2StreamId::Zero + }; println_!("{} {stream_id:?} {:#?}", header.stream_id, self.streams); - self.expect_read = Some((stream_id, header.payload_len as usize)); + self.expect_read = Some((read_stream, header.payload_len as usize)); self.state = H2State::Frame(header); } - Err(_) => return self.goaway(H2Error::ProtocolError), + Err(nom::Err::Failure(ParserError { + kind: ParserErrorKind::UnknownFrame(skip), + .. + })) => { + self.expect_read = Some((H2StreamId::Zero, skip as usize)); + self.state = H2State::Discard; + } + Err(error) => { + let error = error_nom_to_h2(error); + return self.goaway(error); + } }; } (H2State::ContinuationHeader(headers), _) => { - let i = kawa.storage.data(); + let i = kawa.storage.unparsed_data(); println_!(" continuation header: {i:?}"); - match parser::frame_header(i) { - Ok((_, header)) => { - println_!("{header:#?}"); + match parser::frame_header(i, self.local_settings.settings_max_frame_size) { + Ok(( + _, + FrameHeader { + payload_len, + frame_type: FrameType::Continuation, + flags, + stream_id, + }, + )) => { + // println_!("{header:#?}"); kawa.storage.end -= 9; - let stream_id = header.stream_id; assert_eq!(stream_id, headers.stream_id); - self.expect_read = Some((H2StreamId::Zero, header.payload_len as usize)); + self.expect_read = Some((H2StreamId::Zero, payload_len as usize)); let mut headers = headers.clone(); - headers.end_headers = header.flags & 0x4 != 0; - headers.header_block_fragment.len += header.payload_len; + headers.end_headers = flags & 0x4 != 0; + headers.header_block_fragment.len += payload_len; self.state = H2State::ContinuationFrame(headers); } - Err(_) => return self.goaway(H2Error::ProtocolError), + Err(error) => { + let error = error_nom_to_h2(error); + return self.goaway(error); + } + _ => return self.goaway(H2Error::ProtocolError), }; } (H2State::Frame(header), _) => { - let i = kawa.storage.data(); + let i = kawa.storage.unparsed_data(); println_!(" data: {i:?}"); - let frame = match parser::frame_body( - i, - header, - self.local_settings.settings_max_frame_size, - ) { + let frame = match parser::frame_body(i, header) { Ok((_, frame)) => frame, - Err(e) => panic!("stream error: {:?}", error_nom_to_h2(e)), + Err(error) => { + let error = error_nom_to_h2(error); + return self.goaway(error); + } }; if let H2StreamId::Zero = stream_id { - kawa.storage.clear(); + if header.frame_type == FrameType::Headers { + kawa.storage.head = kawa.storage.end; + } else { + kawa.storage.end = kawa.storage.head; + } } self.state = H2State::Header; self.expect_read = Some((H2StreamId::Zero, 9)); return self.handle_frame(frame, context, endpoint); } (H2State::ContinuationFrame(headers), _) => { + kawa.storage.head = kawa.storage.end; let i = kawa.storage.data(); println_!(" data: {i:?}"); let headers = headers.clone(); @@ -346,10 +430,11 @@ impl ConnectionH2 { } match (&self.state, &self.position) { (H2State::Error, _) + | (H2State::Discard, _) | (H2State::ClientPreface, Position::Server) | (H2State::ClientSettings, Position::Server) | (H2State::ServerSettings, Position::Client(_)) => unreachable!( - "Unexpected combination: (Readable, {:?}, {:?})", + "Unexpected combination: (Writable, {:?}, {:?})", self.state, self.position ), (H2State::GoAway, _) => self.force_disconnect(), @@ -392,13 +477,22 @@ impl ConnectionH2 { | (H2State::ContinuationHeader(_), _) => { let mut dead_streams = Vec::new(); - if let Some(H2StreamId::Other(stream_id, global_stream_id)) = self.expect_write { + if let Some(write_stream @ H2StreamId::Other(stream_id, global_stream_id)) = + self.expect_write + { let stream = &mut context.streams[global_stream_id]; let kawa = stream.wbuffer(&self.position); while !kawa.out.is_empty() { let bufs = kawa.as_io_slice(); let (size, status) = self.socket.socket_write_vectored(&bufs); kawa.consume(size); + if let Some((read_stream, amount)) = self.expect_read { + if write_stream == read_stream + && kawa.storage.available_space() >= amount + { + self.readiness.interest.insert(Ready::READABLE); + } + } if update_readiness_after_write(size, status, &mut self.readiness) { return MuxResult::Continue; } @@ -455,6 +549,7 @@ impl ConnectionH2 { break 'outer; } } + self.expect_write = None; if (kawa.is_terminated() || kawa.is_error()) && kawa.is_completed() { match self.position { Position::Client(_) => {} @@ -493,8 +588,8 @@ impl ConnectionH2 { pub fn goaway(&mut self, error: H2Error) -> MuxResult { self.state = H2State::Error; self.expect_read = None; - self.expect_write = Some(H2StreamId::Zero); let kawa = &mut self.zero; + kawa.storage.clear(); match serializer::gen_goaway(kawa.storage.space(), self.last_stream_id, error) { Ok((_, size)) => { @@ -523,9 +618,7 @@ impl ConnectionH2 { Ulid::generate(), self.peer_settings.settings_initial_window_size, )?; - if stream_id > self.last_stream_id { - self.last_stream_id = stream_id & !1; - } + self.last_stream_id = (stream_id + 2) & !1; self.streams.insert(stream_id, global_stream_id); Some(global_stream_id) } @@ -533,8 +626,8 @@ impl ConnectionH2 { pub fn new_stream_id(&mut self) -> StreamId { self.last_stream_id += 2; match self.position { - Position::Client(_) => self.last_stream_id + 1, - Position::Server => self.last_stream_id, + Position::Client(_) => self.last_stream_id - 1, + Position::Server => self.last_stream_id - 2, } } @@ -571,6 +664,7 @@ impl ConnectionH2 { end_stream: true, })); kawa.parsing_phase = kawa::ParsingPhase::Terminated; + stream.received_end_of_stream = true; } if let StreamState::Linked(token) = stream.state { endpoint @@ -581,24 +675,34 @@ impl ConnectionH2 { } Frame::Headers(headers) => { if !headers.end_headers { + // self.zero.storage.head = self.zero.storage.end; + println!("FRAGMENT: {:?}", self.zero.storage.data()); self.state = H2State::ContinuationHeader(headers); return MuxResult::Continue; } // can this fail? - let global_stream_id = *self.streams.get(&headers.stream_id).unwrap(); + let stream_id = headers.stream_id; + let global_stream_id = *self.streams.get(&stream_id).unwrap(); let kawa = &mut self.zero; let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); let stream = &mut context.streams[global_stream_id]; let parts = &mut stream.split(&self.position); let was_initial = parts.rbuffer.is_initial(); pkawa::handle_header( + &mut self.decoder, + &mut self.prioriser, + stream_id, parts.rbuffer, buffer, headers.end_stream, - &mut self.decoder, parts.context, ); + kawa.storage.clear(); + if parts.rbuffer.is_error() { + return self.goaway(H2Error::CompressionError); + } debug_kawa(parts.rbuffer); + stream.received_end_of_stream |= headers.end_stream; if let StreamState::Linked(token) = stream.state { endpoint .readiness_mut(token) @@ -625,7 +729,9 @@ impl ConnectionH2 { return self.goaway(H2Error::ProtocolError); } }, - Frame::Priority(priority) => self.prioriser.push_priority(priority), + Frame::Priority(priority) => self + .prioriser + .push_priority(priority.stream_id, priority.inner), Frame::RstStream(rst_stream) => { println_!( "RstStream({} -> {})", @@ -704,15 +810,22 @@ impl ConnectionH2 { goaway.error_code, error_code_to_str(goaway.error_code) ); - return self.goaway(H2Error::NoError); + // return self.goaway(H2Error::NoError); } Frame::WindowUpdate(update) => { - if update.stream_id == 0 { - self.window += update.increment; + let window = if update.stream_id == 0 { + &mut self.window } else { if let Some(global_stream_id) = self.streams.get(&update.stream_id) { - context.streams[*global_stream_id].window += update.increment as i32; + &mut context.streams[*global_stream_id].window + } else { + unreachable!() } + }; + if update.increment as i32 > i32::MAX - *window { + return self.goaway(H2Error::FlowControlError); + } else { + *window += update.increment as i32; } } Frame::Continuation(_) => unreachable!(), diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 969ce4a1f..ad4861011 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -147,6 +147,21 @@ pub enum Position { Server, } +impl Position { + fn is_server(&self) -> bool { + match self { + Position::Client(_) => false, + Position::Server => true, + } + } + fn is_client(&self) -> bool { + match self { + Position::Client(_) => true, + Position::Server => false, + } + } +} + #[derive(Debug)] pub enum BackendStatus { Connecting(String), @@ -419,6 +434,7 @@ impl<'a, Front: SocketHandler> Endpoint for EndpointServer<'a, Front> { { // this may be used to forward H2<->H2 PushPromise todo!() + // self.0.start_stream(stream, context); } } impl<'a> Endpoint for EndpointClient<'a> { @@ -540,11 +556,21 @@ pub enum StreamState { Recycle, } +impl StreamState { + fn is_open(&self) -> bool { + match self { + StreamState::Idle | StreamState::Recycle => false, + _ => true, + } + } +} + pub struct Stream { // pub request_id: Ulid, pub window: i32, pub attempts: u8, pub state: StreamState, + pub received_end_of_stream: bool, pub front: GenericHttpStream, pub back: GenericHttpStream, pub context: HttpContext, @@ -574,6 +600,7 @@ impl Stream { state: StreamState::Idle, attempts: 0, window: window as i32, + received_end_of_stream: false, front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), context, @@ -1188,6 +1215,7 @@ impl {} BE::RetrieveClusterError(_) => unreachable!(), + // TCP specific error BE::NotFound(_) => unreachable!(), } } @@ -1203,6 +1231,7 @@ impl &mut c.socket, Connection::H2(c) => &mut c.socket, @@ -1372,15 +1400,15 @@ impl &'static str { } #[derive(Clone, Debug, PartialEq)] -pub struct Error<'a> { +pub struct ParserError<'a> { pub input: &'a [u8], - pub error: InnerError, + pub kind: ParserErrorKind, } #[derive(Clone, Debug, PartialEq)] -pub enum InnerError { +pub enum ParserErrorKind { Nom(ErrorKind), H2(H2Error), + UnknownFrame(u32), } #[derive(Clone, Debug, PartialEq)] @@ -98,39 +99,39 @@ pub enum H2Error { HTTP11Required, } -impl<'a> Error<'a> { - pub fn new(input: &'a [u8], error: InnerError) -> Error<'a> { - Error { input, error } +impl<'a> ParserError<'a> { + pub fn new(input: &'a [u8], error: ParserErrorKind) -> ParserError<'a> { + ParserError { input, kind: error } } - pub fn new_h2(input: &'a [u8], error: H2Error) -> Error<'a> { - Error { + pub fn new_h2(input: &'a [u8], error: H2Error) -> ParserError<'a> { + ParserError { input, - error: InnerError::H2(error), + kind: ParserErrorKind::H2(error), } } } -impl<'a> ParseError<&'a [u8]> for Error<'a> { +impl<'a> ParseError<&'a [u8]> for ParserError<'a> { fn from_error_kind(input: &'a [u8], kind: ErrorKind) -> Self { - Error { + ParserError { input, - error: InnerError::Nom(kind), + kind: ParserErrorKind::Nom(kind), } } fn append(input: &'a [u8], kind: ErrorKind, other: Self) -> Self { - Error { + ParserError { input, - error: InnerError::Nom(kind), + kind: ParserErrorKind::Nom(kind), } } } -impl<'a> From<(&'a [u8], ErrorKind)> for Error<'a> { +impl<'a> From<(&'a [u8], ErrorKind)> for ParserError<'a> { fn from((input, kind): (&'a [u8], ErrorKind)) -> Self { - Error { + ParserError { input, - error: InnerError::Nom(kind), + kind: ParserErrorKind::Nom(kind), } } } @@ -155,11 +156,40 @@ pub fn preface(i: &[u8]) -> IResult<&[u8], &[u8]> { ); */ -pub fn frame_header(input: &[u8]) -> IResult<&[u8], FrameHeader, Error> { +pub fn frame_header(input: &[u8], max_frame_size: u32) -> IResult<&[u8], FrameHeader, ParserError> { let (i, payload_len) = be_u24(input)?; - let (i, frame_type) = map_opt(be_u8, convert_frame_type)(i)?; + if payload_len > max_frame_size { + return Err(Err::Failure(ParserError::new_h2( + i, + H2Error::FrameSizeError, + ))); + } + + let (i, t) = be_u8(i)?; + let Some(frame_type) = convert_frame_type(t) else { + return Err(Err::Failure(ParserError::new( + i, + ParserErrorKind::UnknownFrame(payload_len), + ))); + }; let (i, flags) = be_u8(i)?; let (i, stream_id) = be_u32(i)?; + let stream_id = stream_id & 0x7FFFFFFF; + + let valid_stream_id = match frame_type { + FrameType::Data + | FrameType::Headers + | FrameType::Priority + | FrameType::RstStream + | FrameType::PushPromise + | FrameType::Continuation => stream_id != 0, + FrameType::Settings | FrameType::Ping | FrameType::GoAway => stream_id == 0, + FrameType::WindowUpdate => true, + }; + if !valid_stream_id { + println!("invalid stream_id: {stream_id}"); + return Err(Err::Failure(ParserError::new_h2(i, H2Error::ProtocolError))); + } Ok(( i, @@ -234,39 +264,25 @@ impl Frame { pub fn frame_body<'a>( i: &'a [u8], header: &FrameHeader, - max_frame_size: u32, -) -> IResult<&'a [u8], Frame, Error<'a>> { - if header.payload_len > max_frame_size { - return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); - } - - let valid_stream_id = match header.frame_type { - FrameType::Data - | FrameType::Headers - | FrameType::Priority - | FrameType::RstStream - | FrameType::PushPromise - | FrameType::Continuation => header.stream_id != 0, - FrameType::Settings | FrameType::Ping | FrameType::GoAway => header.stream_id == 0, - FrameType::WindowUpdate => true, - }; - - if !valid_stream_id { - return Err(Err::Failure(Error::new_h2(i, H2Error::ProtocolError))); - } - +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let f = match header.frame_type { FrameType::Data => data_frame(i, header)?, FrameType::Headers => headers_frame(i, header)?, FrameType::Priority => { if header.payload_len != 5 { - return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); + return Err(Err::Failure(ParserError::new_h2( + i, + H2Error::FrameSizeError, + ))); } priority_frame(i, header)? } FrameType::RstStream => { if header.payload_len != 4 { - return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); + return Err(Err::Failure(ParserError::new_h2( + i, + H2Error::FrameSizeError, + ))); } rst_stream_frame(i, header)? } @@ -274,20 +290,29 @@ pub fn frame_body<'a>( FrameType::Continuation => continuation_frame(i, header)?, FrameType::Settings => { if header.payload_len % 6 != 0 { - return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); + return Err(Err::Failure(ParserError::new_h2( + i, + H2Error::FrameSizeError, + ))); } settings_frame(i, header)? } FrameType::Ping => { if header.payload_len != 8 { - return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); + return Err(Err::Failure(ParserError::new_h2( + i, + H2Error::FrameSizeError, + ))); } ping_frame(i, header)? } FrameType::GoAway => goaway_frame(i, header)?, FrameType::WindowUpdate => { if header.payload_len != 4 { - return Err(Err::Failure(Error::new_h2(i, H2Error::FrameSizeError))); + return Err(Err::Failure(ParserError::new_h2( + i, + H2Error::FrameSizeError, + ))); } window_update_frame(i, header)? } @@ -306,8 +331,9 @@ pub struct Data { pub fn data_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (remaining, i) = take(header.payload_len)(input)?; + println!("{i:?}"); let (i, pad_length) = if header.flags & 0x8 != 0 { let (i, pad_length) = be_u8(i)?; @@ -317,7 +343,10 @@ pub fn data_frame<'a>( }; if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { - return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); + return Err(Err::Failure(ParserError::new_h2( + input, + H2Error::ProtocolError, + ))); } let (_, payload) = take(i.len() - pad_length.unwrap_or(0) as usize)(i)?; @@ -335,13 +364,11 @@ pub fn data_frame<'a>( #[derive(Clone, Debug)] pub struct Headers { pub stream_id: u32, - pub stream_dependency: Option, - pub weight: Option, + pub priority: Option, pub header_block_fragment: Slice, // pub header_block_fragment: &'a [u8], pub end_stream: bool, pub end_headers: bool, - pub priority: bool, } #[derive(Clone, Debug, PartialEq)] @@ -350,7 +377,7 @@ pub struct StreamDependency { pub stream_id: u32, } -fn stream_dependency(i: &[u8]) -> IResult<&[u8], StreamDependency, Error<'_>> { +fn stream_dependency(i: &[u8]) -> IResult<&[u8], StreamDependency, ParserError<'_>> { let (i, stream) = map(be_u32, |i| StreamDependency { exclusive: i & 0x8000 != 0, stream_id: i & 0x7FFFFFFF, @@ -361,7 +388,7 @@ fn stream_dependency(i: &[u8]) -> IResult<&[u8], StreamDependency, Error<'_>> { pub fn headers_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (remaining, i) = take(header.payload_len)(input)?; let (i, pad_length) = if header.flags & 0x8 != 0 { @@ -371,16 +398,25 @@ pub fn headers_frame<'a>( (i, None) }; - let (i, stream_dependency, weight) = if header.flags & 0x20 != 0 { + let (i, priority) = if header.flags & 0x20 != 0 { let (i, stream_dependency) = stream_dependency(i)?; let (i, weight) = be_u8(i)?; - (i, Some(stream_dependency), Some(weight)) + ( + i, + Some(PriorityPart::Rfc7540 { + stream_dependency, + weight, + }), + ) } else { - (i, None, None) + (i, None) }; if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { - return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); + return Err(Err::Failure(ParserError::new_h2( + input, + H2Error::ProtocolError, + ))); } let (_, header_block_fragment) = take(i.len() - pad_length.unwrap_or(0) as usize)(i)?; @@ -389,35 +425,46 @@ pub fn headers_frame<'a>( remaining, Frame::Headers(Headers { stream_id: header.stream_id, - stream_dependency, - weight, + priority, header_block_fragment: Slice::new(input, header_block_fragment), end_stream: header.flags & 0x1 != 0, end_headers: header.flags & 0x4 != 0, - priority: header.flags & 0x20 != 0, }), )) } +#[derive(Clone, Debug, PartialEq)] +pub enum PriorityPart { + Rfc7540 { + stream_dependency: StreamDependency, + weight: u8, + }, + Rfc9218 { + urgency: u8, // should be between 0 and 7 inclusive + incremental: bool, + }, +} + #[derive(Clone, Debug, PartialEq)] pub struct Priority { pub stream_id: u32, - pub stream_dependency: StreamDependency, - pub weight: u8, + pub inner: PriorityPart, } pub fn priority_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (i, stream_dependency) = stream_dependency(input)?; let (i, weight) = be_u8(i)?; Ok(( i, Frame::Priority(Priority { - stream_dependency, stream_id: header.stream_id, - weight, + inner: PriorityPart::Rfc7540 { + stream_dependency, + weight, + }, }), )) } @@ -431,7 +478,7 @@ pub struct RstStream { pub fn rst_stream_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (i, error_code) = be_u32(input)?; Ok(( i, @@ -457,7 +504,7 @@ pub struct Setting { pub fn settings_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (i, data) = take(header.payload_len)(input)?; let (_, settings) = many0(map( @@ -485,7 +532,7 @@ pub struct PushPromise { pub fn push_promise_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (remaining, i) = take(header.payload_len)(input)?; let (i, pad_length) = if header.flags & 0x8 != 0 { @@ -496,7 +543,10 @@ pub fn push_promise_frame<'a>( }; if pad_length.is_some() && i.len() <= pad_length.unwrap() as usize { - return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); + return Err(Err::Failure(ParserError::new_h2( + input, + H2Error::ProtocolError, + ))); } let (i, promised_stream_id) = be_u32(i)?; @@ -521,7 +571,7 @@ pub struct Ping { pub fn ping_frame<'a>( input: &'a [u8], _header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (i, data) = take(8usize)(input)?; let mut p = Ping { payload: [0; 8] }; @@ -540,7 +590,7 @@ pub struct GoAway { pub fn goaway_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (remaining, i) = take(header.payload_len)(input)?; let (i, last_stream_id) = be_u32(i)?; let (additional_debug_data, error_code) = be_u32(i)?; @@ -563,13 +613,16 @@ pub struct WindowUpdate { pub fn window_update_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (i, increment) = be_u32(input)?; let increment = increment & 0x7FFFFFFF; //FIXME: if stream id is 0, trat it as connection error? if increment == 0 { - return Err(Err::Failure(Error::new_h2(input, H2Error::ProtocolError))); + return Err(Err::Failure(ParserError::new_h2( + input, + H2Error::ProtocolError, + ))); } Ok(( @@ -591,7 +644,7 @@ pub struct Continuation { pub fn continuation_frame<'a>( input: &'a [u8], header: &FrameHeader, -) -> IResult<&'a [u8], Frame, Error<'a>> { +) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (i, header_block_fragment) = take(header.payload_len)(input)?; Ok(( i, diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index d00a430ef..5846fa36f 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -5,15 +5,21 @@ use kawa::{ Store, Version, }; -use crate::{pool::Checkout, protocol::http::parser::compare_no_case}; - -use super::GenericHttpStream; +use crate::{ + pool::Checkout, + protocol::{ + http::parser::compare_no_case, + mux::{h2::Prioriser, parser::PriorityPart, GenericHttpStream, StreamId}, + }, +}; pub fn handle_header( + decoder: &mut hpack::Decoder, + prioriser: &mut Prioriser, + stream_id: StreamId, kawa: &mut GenericHttpStream, input: &[u8], end_stream: bool, - decoder: &mut hpack::Decoder, callbacks: &mut C, ) where C: ParserCallbacks, @@ -28,44 +34,56 @@ pub fn handle_header( let mut authority = Store::Empty; let mut path = Store::Empty; let mut scheme = Store::Empty; - decoder - .decode_with_cb(input, |k, v| { - let start = kawa.storage.end as u32; - kawa.storage.write_all(&v).unwrap(); - let len_key = k.len() as u32; - let len_val = v.len() as u32; - let val = Store::Slice(Slice { - start, - len: len_val, - }); + let decode_status = decoder.decode_with_cb(input, |k, v| { + let start = kawa.storage.end as u32; + kawa.storage.write_all(&v).unwrap(); + let len_key = k.len() as u32; + let len_val = v.len() as u32; + let val = Store::Slice(Slice { + start, + len: len_val, + }); - if compare_no_case(&k, b":method") { - method = val; - } else if compare_no_case(&k, b":authority") { - authority = val; - } else if compare_no_case(&k, b":path") { - path = val; - } else if compare_no_case(&k, b":scheme") { - scheme = val; - } else if compare_no_case(&k, b"cookie") { - todo!("cookies should be split in pairs"); - } else if compare_no_case(&k, b"priority") { - unimplemented!(); - } else { - if compare_no_case(&k, b"content-length") { - let length = - unsafe { from_utf8_unchecked(&v).parse::().unwrap() }; - kawa.body_size = BodySize::Length(length); - } - kawa.storage.write_all(&k).unwrap(); - let key = Store::Slice(Slice { - start: start + len_val, - len: len_key, - }); - kawa.push_block(Block::Header(Pair { key, val })); + if compare_no_case(&k, b":method") { + method = val; + } else if compare_no_case(&k, b":scheme") { + scheme = val; + } else if compare_no_case(&k, b":path") { + path = val; + } else if compare_no_case(&k, b":authority") { + authority = val; + } else if compare_no_case(&k, b"cookie---") { + todo!("cookies should be split in pairs"); + } else if compare_no_case(&k, b"priority") { + todo!("decode priority"); + prioriser.push_priority( + stream_id, + PriorityPart::Rfc9218 { + urgency: todo!(), + incremental: todo!(), + }, + ) + } else { + if compare_no_case(&k, b"content-length") { + let length = unsafe { from_utf8_unchecked(&v).parse::().unwrap() }; + kawa.body_size = BodySize::Length(length); } - }) - .unwrap(); + kawa.storage.write_all(&k).unwrap(); + let key = Store::Slice(Slice { + start: start + len_val, + len: len_key, + }); + kawa.push_block(Block::Header(Pair { key, val })); + } + }); + if let Err(error) = decode_status { + println!("INVALID FRAGMENT: {error:?}"); + kawa.parsing_phase.error("Invalid header fragment".into()); + } + if method.is_empty() || authority.is_empty() || path.is_empty() { + println!("MISSING PSEUDO HEADERS"); + kawa.parsing_phase.error("Missing pseudo headers".into()); + } // uri is only used by H1 statusline, in most cases it only consists of the path // a better algorithm should be used though // let buffer = kawa.storage.data(); @@ -89,30 +107,36 @@ pub fn handle_header( Kind::Response => { let mut code = 0; let mut status = Store::Empty; - decoder - .decode_with_cb(input, |k, v| { - let start = kawa.storage.end as u32; - kawa.storage.write_all(&v).unwrap(); - let len_key = k.len() as u32; - let len_val = v.len() as u32; - let val = Store::Slice(Slice { - start, - len: len_val, - }); + let decode_status = decoder.decode_with_cb(input, |k, v| { + let start = kawa.storage.end as u32; + kawa.storage.write_all(&v).unwrap(); + let len_key = k.len() as u32; + let len_val = v.len() as u32; + let val = Store::Slice(Slice { + start, + len: len_val, + }); - if compare_no_case(&k, b":status") { - status = val; - code = unsafe { from_utf8_unchecked(&v).parse::().ok().unwrap() } - } else { - kawa.storage.write_all(&k).unwrap(); - let key = Store::Slice(Slice { - start: start + len_val, - len: len_key, - }); - kawa.push_block(Block::Header(Pair { key, val })); - } - }) - .unwrap(); + if compare_no_case(&k, b":status") { + status = val; + code = unsafe { from_utf8_unchecked(&v).parse::().ok().unwrap() } + } else { + kawa.storage.write_all(&k).unwrap(); + let key = Store::Slice(Slice { + start: start + len_val, + len: len_key, + }); + kawa.push_block(Block::Header(Pair { key, val })); + } + }); + if let Err(error) = decode_status { + println!("INVALID FRAGMENT: {error:?}"); + kawa.parsing_phase.error("Invalid header fragment".into()); + } + if status.is_empty() { + println!("MISSING PSEUDO HEADERS"); + kawa.parsing_phase.error("Missing pseudo headers".into()); + } StatusLine::Response { version: Version::V20, code, @@ -124,7 +148,14 @@ pub fn handle_header( // everything has been parsed kawa.storage.head = kawa.storage.end; + println!( + "index: {}/{}/{}", + kawa.storage.start, kawa.storage.head, kawa.storage.end + ); + if kawa.is_error() { + return; + } callbacks.on_headers(kawa); if end_stream { @@ -181,7 +212,7 @@ pub fn handle_trailer( }) .unwrap(); - assert!(end_stream); + // assert!(end_stream); kawa.push_block(Block::Flags(Flags { end_body: end_stream, end_chunk: false, diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs index fa94ead4d..e7eb39127 100644 --- a/lib/src/protocol/mux/serializer.rs +++ b/lib/src/protocol/mux/serializer.rs @@ -13,7 +13,7 @@ use super::{ pub const H2_PRI: &str = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; pub const SETTINGS_ACKNOWLEDGEMENT: [u8; 9] = [0, 0, 0, 4, 1, 0, 0, 0, 0]; -pub const PING_ACKNOWLEDGEMENT_HEADER: [u8; 9] = [0, 0, 0, 6, 1, 0, 0, 0, 0]; +pub const PING_ACKNOWLEDGEMENT_HEADER: [u8; 9] = [0, 0, 8, 6, 1, 0, 0, 0, 0]; pub fn gen_frame_header<'a, 'b>( buf: &'a mut [u8], @@ -68,7 +68,7 @@ pub fn gen_settings<'a>( gen_frame_header( buf, &FrameHeader { - payload_len: 6 * 6, + payload_len: 6 * 8, frame_type: FrameType::Settings, flags: 0, stream_id: 0, @@ -89,6 +89,10 @@ pub fn gen_settings<'a>( be_u32(settings.settings_max_frame_size), be_u16(6), be_u32(settings.settings_max_header_list_size), + be_u16(8), + be_u32(settings.settings_enable_connect_protocol as u32), + be_u16(9), + be_u32(settings.settings_no_rfc7540_priorities as u32), )), buf, ) @@ -123,7 +127,7 @@ pub fn gen_goaway<'a>( gen_frame_header( buf, &FrameHeader { - payload_len: 4, + payload_len: 8, frame_type: FrameType::GoAway, flags: 0, stream_id: 0, From 9bc8157579117bb45ec77cb27cfd3ad09528b4e9 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 23 May 2024 14:59:09 +0200 Subject: [PATCH 37/44] h2spec ETA: 135/147 - implement reset_stream (in a very bad way) - change forcefully_terminate_stream to be quicker - more strict h2 parsing note: it bacomes apparent that: - streams lack the RFC definition of state - remultiplexing lacks a frame queue - priorizer may have to be shared among connections of a session Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 68 +++++++----- lib/src/protocol/mux/h1.rs | 9 +- lib/src/protocol/mux/h2.rs | 170 +++++++++++++++++++++++------ lib/src/protocol/mux/mod.rs | 31 ++++-- lib/src/protocol/mux/parser.rs | 28 ++++- lib/src/protocol/mux/pkawa.rs | 163 ++++++++++++++++++--------- lib/src/protocol/mux/serializer.rs | 2 +- 7 files changed, 348 insertions(+), 123 deletions(-) diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 0677cb54e..05f3ebe62 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -1,13 +1,17 @@ use std::str::from_utf8_unchecked; -use kawa::{AsBuffer, Block, BlockConverter, Chunk, Flags, Kawa, Pair, StatusLine, Store}; - -use crate::protocol::http::parser::compare_no_case; +use kawa::{ + AsBuffer, Block, BlockConverter, Chunk, Flags, Kawa, Pair, ParsingErrorKind, ParsingPhase, + StatusLine, Store, +}; -use super::{ - parser::{FrameHeader, FrameType, H2Error}, - serializer::{gen_frame_header, gen_rst_stream}, - StreamId, +use crate::protocol::{ + http::parser::compare_no_case, + mux::{ + parser::{str_to_error_code, FrameHeader, FrameType, H2Error}, + serializer::{gen_frame_header, gen_rst_stream}, + StreamId, + }, }; pub struct H2BlockConverter<'a> { @@ -17,6 +21,26 @@ pub struct H2BlockConverter<'a> { } impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { + fn initialize(&mut self, kawa: &mut Kawa) { + // This is very ugly... we may add a h2 variant in kawa::ParsingErrorKind + match kawa.parsing_phase { + ParsingPhase::Error { + kind: ParsingErrorKind::Processing { message }, + .. + } => { + let error = str_to_error_code(message); + let mut frame = [0; 13]; + gen_rst_stream(&mut frame, self.stream_id, error).unwrap(); + kawa.push_out(Store::from_slice(&frame)); + } + ParsingPhase::Error { .. } => { + let mut frame = [0; 13]; + gen_rst_stream(&mut frame, self.stream_id, H2Error::InternalError).unwrap(); + kawa.push_out(Store::from_slice(&frame)); + } + _ => {} + } + } fn call(&mut self, block: Block, kawa: &mut Kawa) -> bool { let buffer = kawa.storage.buffer(); match block { @@ -140,24 +164,18 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { kawa.push_out(Store::from_slice(&header)); kawa.push_out(Store::Alloc(payload.into_boxed_slice(), 0)); } else if end_stream { - if kawa.is_error() { - let mut frame = [0; 13]; - gen_rst_stream(&mut frame, self.stream_id, H2Error::InternalError).unwrap(); - kawa.push_out(Store::from_slice(&frame)); - } else { - let mut header = [0; 9]; - gen_frame_header( - &mut header, - &FrameHeader { - payload_len: 0, - frame_type: FrameType::Data, - flags: 1, - stream_id: self.stream_id, - }, - ) - .unwrap(); - kawa.push_out(Store::from_slice(&header)); - } + let mut header = [0; 9]; + gen_frame_header( + &mut header, + &FrameHeader { + payload_len: 0, + frame_type: FrameType::Data, + flags: 1, + stream_id: self.stream_id, + }, + ) + .unwrap(); + kawa.push_out(Store::from_slice(&header)); } if end_header || end_stream { kawa.push_delimiter() diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 3fb4bdff6..81d1637e9 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -3,9 +3,7 @@ use sozu_command::ready::Ready; use crate::{ println_, protocol::mux::{ - debug_kawa, forcefully_terminate_answer, set_default_answer, update_readiness_after_read, - update_readiness_after_write, BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, - Position, StreamState, + debug_kawa, forcefully_terminate_answer, parser::H2Error, set_default_answer, update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, Position, StreamState }, socket::SocketHandler, timer::TimeoutContainer, @@ -226,6 +224,9 @@ impl ConnectionH1 { Position::Client(BackendStatus::Connected(cluster_id)) | Position::Client(BackendStatus::Connecting(cluster_id)) => { self.stream = usize::MAX; + // keep alive should probably be used only if the http context is fully reset + // in case end_stream occurs due to an error the connection state is probably + // unrecoverable and should be terminated if stream_context.keep_alive_backend { self.position = Position::Client(BackendStatus::KeepAlive(std::mem::take(cluster_id))) @@ -241,7 +242,7 @@ impl ConnectionH1 { // if the answer is not terminated we send an RstStream to properly clean the stream // if it is terminated, we finish the transfer, the backend is not necessary anymore if !stream.back.is_terminated() { - forcefully_terminate_answer(stream, &mut self.readiness); + forcefully_terminate_answer(stream, &mut self.readiness, H2Error::InternalError); } else { stream.state = StreamState::Unlinked; self.readiness.interest.insert(Ready::WRITABLE); diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 91647d9bb..5000b5294 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -9,7 +9,7 @@ use crate::{ converter, debug_kawa, forcefully_terminate_answer, parser::{ self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error, Headers, ParserError, - ParserErrorKind, + ParserErrorKind, StreamDependency, WindowUpdate, }, pkawa, serializer, set_default_answer, update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, Endpoint, GenericHttpStream, @@ -84,8 +84,25 @@ impl Prioriser { pub fn new() -> Self { Self {} } - pub fn push_priority(&mut self, stream_id: StreamId, priority: parser::PriorityPart) { - println!("PRIORITY REQUEST FOR {stream_id}: {priority:?}"); + pub fn push_priority(&mut self, stream_id: StreamId, priority: parser::PriorityPart) -> bool { + println_!("PRIORITY REQUEST FOR {stream_id}: {priority:?}"); + match priority { + parser::PriorityPart::Rfc7540 { + stream_dependency, + weight, + } => { + if stream_dependency.stream_id == stream_id { + println_!("STREAM CAN'T DEPEND ON ITSELF"); + true + } else { + false + } + } + parser::PriorityPart::Rfc9218 { + urgency, + incremental, + } => false, + } } } @@ -289,13 +306,18 @@ impl ConnectionH2 { let read_stream = if stream_id == 0 { H2StreamId::Zero } else if let Some(global_stream_id) = self.streams.get(&stream_id) { + let allowed_on_half_closed = header.frame_type + == FrameType::WindowUpdate + || header.frame_type == FrameType::Priority; let stream = &context.streams[*global_stream_id]; println_!( "REQUESTING EXISTING STREAM {stream_id}: {}/{:?}", stream.received_end_of_stream, stream.state ); - if stream.received_end_of_stream || !stream.state.is_open() { + if !allowed_on_half_closed + && (stream.received_end_of_stream || !stream.state.is_open()) + { return self.goaway(H2Error::StreamClosed); } if header.frame_type == FrameType::Data { @@ -318,7 +340,9 @@ impl ConnectionH2 { Some(_) => {} None => return self.goaway(H2Error::InternalError), } - } else if header.frame_type != FrameType::Priority { + } else if header.frame_type != FrameType::Priority + && header.frame_type != FrameType::WindowUpdate + { println_!( "ONLY HEADERS AND PRIORITY CAN BE RECEIVED ON IDLE/CLOSED STREAMS" ); @@ -555,7 +579,7 @@ impl ConnectionH2 { Position::Client(_) => {} Position::Server => { // mark stream as reusable - println_!("Recycle stream: {global_stream_id}"); + println_!("Recycle1 stream: {global_stream_id}"); // ACCESS LOG stream.generate_access_log( false, @@ -683,12 +707,25 @@ impl ConnectionH2 { // can this fail? let stream_id = headers.stream_id; let global_stream_id = *self.streams.get(&stream_id).unwrap(); + + if let Some(priority) = &headers.priority { + if self.prioriser.push_priority(stream_id, priority.clone()) { + self.reset_stream( + global_stream_id, + context, + endpoint, + H2Error::ProtocolError, + ); + return MuxResult::Continue; + } + } + let kawa = &mut self.zero; let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); let stream = &mut context.streams[global_stream_id]; let parts = &mut stream.split(&self.position); let was_initial = parts.rbuffer.is_initial(); - pkawa::handle_header( + let status = pkawa::handle_header( &mut self.decoder, &mut self.prioriser, stream_id, @@ -698,8 +735,12 @@ impl ConnectionH2 { parts.context, ); kawa.storage.clear(); - if parts.rbuffer.is_error() { - return self.goaway(H2Error::CompressionError); + if let Err((error, global)) = status { + if global { + return self.goaway(error); + } else { + return self.reset_stream(global_stream_id, context, endpoint, error); + } } debug_kawa(parts.rbuffer); stream.received_end_of_stream |= headers.end_stream; @@ -729,9 +770,23 @@ impl ConnectionH2 { return self.goaway(H2Error::ProtocolError); } }, - Frame::Priority(priority) => self - .prioriser - .push_priority(priority.stream_id, priority.inner), + Frame::Priority(priority) => { + if self + .prioriser + .push_priority(priority.stream_id, priority.inner) + { + if let Some(global_stream_id) = self.streams.get(&priority.stream_id) { + return self.reset_stream( + *global_stream_id, + context, + endpoint, + H2Error::ProtocolError, + ); + } else { + return self.goaway(H2Error::ProtocolError); + } + } + } Frame::RstStream(rst_stream) => { println_!( "RstStream({} -> {})", @@ -766,18 +821,23 @@ impl ConnectionH2 { return MuxResult::Continue; } for setting in settings.settings { + let v = setting.value; + let mut is_error = false; #[rustfmt::skip] let _ = match setting.identifier { - 1 => self.peer_settings.settings_header_table_size = setting.value, - 2 => self.peer_settings.settings_enable_push = setting.value == 1, - 3 => self.peer_settings.settings_max_concurrent_streams = setting.value, - 4 => self.peer_settings.settings_initial_window_size = setting.value, - 5 => self.peer_settings.settings_max_frame_size = setting.value, - 6 => self.peer_settings.settings_max_header_list_size = setting.value, - 8 => self.peer_settings.settings_enable_connect_protocol = setting.value == 1, - 9 => self.peer_settings.settings_no_rfc7540_priorities = setting.value == 1, + 1 => { self.peer_settings.settings_header_table_size = v }, + 2 => { self.peer_settings.settings_enable_push = v == 1; is_error |= v > 1 }, + 3 => { self.peer_settings.settings_max_concurrent_streams = v }, + 4 => { self.peer_settings.settings_initial_window_size = v; is_error |= v >= 1<<31 }, + 5 => { self.peer_settings.settings_max_frame_size = v; is_error |= v >= 1<<24 || v < 1<<14 }, + 6 => { self.peer_settings.settings_max_header_list_size = v }, + 8 => { self.peer_settings.settings_enable_connect_protocol = v == 1; is_error |= v > 1 }, + 9 => { self.peer_settings.settings_no_rfc7540_priorities = v == 1; is_error |= v > 1 }, other => println!("unknown setting_id: {other}, we MUST ignore this"), }; + if is_error { + return self.goaway(H2Error::ProtocolError); + } } println_!("{:#?}", self.peer_settings); @@ -792,6 +852,9 @@ impl ConnectionH2 { self.expect_write = Some(H2StreamId::Zero); } Frame::Ping(ping) => { + if ping.ack { + return MuxResult::Continue; + } let kawa = &mut self.zero; match serializer::gen_ping_acknolegment(kawa.storage.space(), &ping.payload) { Ok((_, size)) => kawa.storage.fill(size), @@ -812,21 +875,36 @@ impl ConnectionH2 { ); // return self.goaway(H2Error::NoError); } - Frame::WindowUpdate(update) => { - let window = if update.stream_id == 0 { - &mut self.window + Frame::WindowUpdate(WindowUpdate { + stream_id, + increment, + }) => { + let increment = increment as i32; + if stream_id == 0 { + if increment > i32::MAX - self.window { + return self.goaway(H2Error::FlowControlError); + } else { + self.window += increment; + } } else { - if let Some(global_stream_id) = self.streams.get(&update.stream_id) { - &mut context.streams[*global_stream_id].window + if let Some(global_stream_id) = self.streams.get(&stream_id) { + let stream = &mut context.streams[*global_stream_id]; + if increment > i32::MAX - stream.window { + return self.reset_stream( + *global_stream_id, + context, + endpoint, + H2Error::FlowControlError, + ); + } else { + stream.window += increment; + } } else { - unreachable!() + println_!( + "Ignoring window update on closed stream {stream_id}: {increment}" + ); } }; - if update.increment as i32 > i32::MAX - *window { - return self.goaway(H2Error::FlowControlError); - } else { - *window += update.increment as i32; - } } Frame::Continuation(_) => unreachable!(), } @@ -867,6 +945,27 @@ impl ConnectionH2 { } } + pub fn reset_stream( + &mut self, + stream_id: GlobalStreamId, + context: &mut Context, + mut endpoint: E, + error: H2Error, + ) -> MuxResult + where + E: Endpoint, + L: ListenerHandler + L7ListenerHandler, + { + let stream = &mut context.streams[stream_id]; + println_!("reset H2 stream {stream_id}: {:#?}", stream.context); + let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); + forcefully_terminate_answer(stream, &mut self.readiness, error); + if let StreamState::Linked(token) = old_state { + endpoint.end_stream(token, stream_id, context); + } + MuxResult::Continue + } + pub fn end_stream(&mut self, stream: GlobalStreamId, context: &mut Context) where L: ListenerHandler + L7ListenerHandler, @@ -878,6 +977,9 @@ impl ConnectionH2 { for (stream_id, global_stream_id) in &self.streams { if *global_stream_id == stream { let id = *stream_id; + // if the stream is not in a closed state we should probably send an + // RST_STREAM frame here. We also need to handle frames coming from + // the backend on this stream after it was closed self.streams.remove(&id); return; } @@ -893,7 +995,11 @@ impl ConnectionH2 { // if the answer is not terminated we send an RstStream to properly clean the stream // if it is terminated, we finish the transfer, the backend is not necessary anymore if !stream.back.is_terminated() { - forcefully_terminate_answer(stream, &mut self.readiness); + forcefully_terminate_answer( + stream, + &mut self.readiness, + H2Error::InternalError, + ); } else { stream.state = StreamState::Unlinked; self.readiness.interest.insert(Ready::WRITABLE); diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index ad4861011..193a93f51 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -37,7 +37,11 @@ use crate::{ StateResult, }; -pub use crate::protocol::mux::{h1::ConnectionH1, h2::ConnectionH2}; +pub use crate::protocol::mux::{ + h1::ConnectionH1, + h2::ConnectionH2, + parser::{error_code_to_str, H2Error}, +}; #[macro_export] macro_rules! println_ { @@ -127,15 +131,18 @@ fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) /// Forcefully terminates a kawa message by setting the "end_stream" flag and setting the parsing_phase to Error. /// An H2 converter will produce an RstStream frame. -fn forcefully_terminate_answer(stream: &mut Stream, readiness: &mut Readiness) { +fn forcefully_terminate_answer(stream: &mut Stream, readiness: &mut Readiness, error: H2Error) { let kawa = &mut stream.back; - kawa.push_block(kawa::Block::Flags(kawa::Flags { - end_body: false, - end_chunk: false, - end_header: false, - end_stream: true, - })); - kawa.parsing_phase.error("Termination".into()); + kawa.out.clear(); + kawa.blocks.clear(); + // kawa.push_block(kawa::Block::Flags(kawa::Flags { + // end_body: false, + // end_chunk: false, + // end_header: false, + // end_stream: true, + // })); + kawa.parsing_phase + .error(error_code_to_str(error as u32).into()); debug_kawa(kawa); stream.state = StreamState::Unlinked; readiness.interest.insert(Ready::WRITABLE); @@ -1312,7 +1319,11 @@ impl &'static str { } } +pub fn str_to_error_code(str: &str) -> H2Error { + match str { + "NO_ERROR" => H2Error::NoError, + "PROTOCOL_ERROR" => H2Error::ProtocolError, + "INTERNAL_ERROR" => H2Error::InternalError, + "FLOW_CONTROL_ERROR" => H2Error::FlowControlError, + "SETTINGS_TIMEOUT" => H2Error::SettingsTimeout, + "STREAM_CLOSED" => H2Error::StreamClosed, + "FRAME_SIZE_ERROR" => H2Error::FrameSizeError, + "REFUSED_STREAM" => H2Error::RefusedStream, + "CANCEL" => H2Error::Cancel, + "COMPRESSION_ERROR" => H2Error::CompressionError, + "CONNECT_ERROR" => H2Error::ConnectError, + "ENHANCE_YOUR_CALM" => H2Error::EnhanceYourCalm, + "INADEQUATE_SECURITY" => H2Error::InadequateSecurity, + "HTTP_1_1_REQUIRED" => H2Error::HTTP11Required, + _ => H2Error::InternalError, + } +} + #[derive(Clone, Debug, PartialEq)] pub struct ParserError<'a> { pub input: &'a [u8], @@ -566,15 +586,19 @@ pub fn push_promise_frame<'a>( #[derive(Clone, Debug, PartialEq)] pub struct Ping { pub payload: [u8; 8], + pub ack: bool, } pub fn ping_frame<'a>( input: &'a [u8], - _header: &FrameHeader, + header: &FrameHeader, ) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (i, data) = take(8usize)(input)?; - let mut p = Ping { payload: [0; 8] }; + let mut p = Ping { + payload: [0; 8], + ack: header.flags & 1 != 0, + }; p.payload[..8].copy_from_slice(&data[..8]); Ok((i, Frame::Ping(p))) diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index 5846fa36f..012c16ddb 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -1,4 +1,4 @@ -use std::{io::Write, str::from_utf8_unchecked}; +use std::{io::Write, str::from_utf8}; use kawa::{ h1::ParserCallbacks, repr::Slice, Block, BodySize, Flags, Kind, Pair, ParsingPhase, StatusLine, @@ -9,10 +9,28 @@ use crate::{ pool::Checkout, protocol::{ http::parser::compare_no_case, - mux::{h2::Prioriser, parser::PriorityPart, GenericHttpStream, StreamId}, + mux::{ + h2::Prioriser, + parser::{H2Error, PriorityPart}, + GenericHttpStream, StreamId, + }, }, }; +trait AdHocStore { + fn len(&self) -> usize; +} +impl AdHocStore for Store { + fn len(&self) -> usize { + match self { + Store::Empty => 0, + Store::Slice(slice) | Store::Detached(slice) => slice.len(), + Store::Static(s) => s.len(), + Store::Alloc(a, i) => a.len() - *i as usize, + } + } +} + pub fn handle_header( decoder: &mut hpack::Decoder, prioriser: &mut Prioriser, @@ -21,7 +39,8 @@ pub fn handle_header( input: &[u8], end_stream: bool, callbacks: &mut C, -) where +) -> Result<(), (H2Error, bool)> +where C: ParserCallbacks, { if !kawa.is_initial() { @@ -34,6 +53,8 @@ pub fn handle_header( let mut authority = Store::Empty; let mut path = Store::Empty; let mut scheme = Store::Empty; + let mut invalid_headers = false; + let mut regular_headers = false; let decode_status = decoder.decode_with_cb(input, |k, v| { let start = kawa.storage.end as u32; kawa.storage.write_all(&v).unwrap(); @@ -45,28 +66,49 @@ pub fn handle_header( }); if compare_no_case(&k, b":method") { + if !method.is_empty() || regular_headers { + invalid_headers = true; + } method = val; } else if compare_no_case(&k, b":scheme") { + if !scheme.is_empty() || regular_headers { + invalid_headers = true; + } scheme = val; } else if compare_no_case(&k, b":path") { + if !path.is_empty() || regular_headers { + invalid_headers = true; + } path = val; } else if compare_no_case(&k, b":authority") { + if !authority.is_empty() || regular_headers { + invalid_headers = true; + } authority = val; + } else if k.starts_with(b":") { + invalid_headers = true; } else if compare_no_case(&k, b"cookie---") { + regular_headers = true; todo!("cookies should be split in pairs"); - } else if compare_no_case(&k, b"priority") { - todo!("decode priority"); - prioriser.push_priority( - stream_id, - PriorityPart::Rfc9218 { - urgency: todo!(), - incremental: todo!(), - }, - ) } else { + regular_headers = true; if compare_no_case(&k, b"content-length") { - let length = unsafe { from_utf8_unchecked(&v).parse::().unwrap() }; - kawa.body_size = BodySize::Length(length); + if let Some(length) = + from_utf8(&v).ok().and_then(|v| v.parse::().ok()) + { + kawa.body_size = BodySize::Length(length); + } else { + invalid_headers = true; + } + } else if compare_no_case(&k, b"priority") { + todo!("decode priority"); + prioriser.push_priority( + stream_id, + PriorityPart::Rfc9218 { + urgency: todo!(), + incremental: todo!(), + }, + ); } kawa.storage.write_all(&k).unwrap(); let key = Store::Slice(Slice { @@ -78,11 +120,16 @@ pub fn handle_header( }); if let Err(error) = decode_status { println!("INVALID FRAGMENT: {error:?}"); - kawa.parsing_phase.error("Invalid header fragment".into()); + return Err((H2Error::CompressionError, true)); } - if method.is_empty() || authority.is_empty() || path.is_empty() { - println!("MISSING PSEUDO HEADERS"); - kawa.parsing_phase.error("Missing pseudo headers".into()); + if invalid_headers + || method.len() == 0 + || authority.len() == 0 + || path.len() == 0 + || scheme.len() == 0 + { + println!("INVALID HEADERS"); + return Err((H2Error::ProtocolError, false)); } // uri is only used by H1 statusline, in most cases it only consists of the path // a better algorithm should be used though @@ -107,6 +154,8 @@ pub fn handle_header( Kind::Response => { let mut code = 0; let mut status = Store::Empty; + let mut invalid_headers = false; + let mut regular_headers = false; let decode_status = decoder.decode_with_cb(input, |k, v| { let start = kawa.storage.end as u32; kawa.storage.write_all(&v).unwrap(); @@ -118,9 +167,21 @@ pub fn handle_header( }); if compare_no_case(&k, b":status") { + if !status.is_empty() || regular_headers { + invalid_headers = true; + } status = val; - code = unsafe { from_utf8_unchecked(&v).parse::().ok().unwrap() } + if let Some(parsed_code) = + from_utf8(&v).ok().and_then(|v| v.parse::().ok()) + { + code = parsed_code; + } else { + invalid_headers = true; + } + } else if k.starts_with(b":") { + invalid_headers = true; } else { + regular_headers = true; kawa.storage.write_all(&k).unwrap(); let key = Store::Slice(Slice { start: start + len_val, @@ -131,11 +192,11 @@ pub fn handle_header( }); if let Err(error) = decode_status { println!("INVALID FRAGMENT: {error:?}"); - kawa.parsing_phase.error("Invalid header fragment".into()); + return Err((H2Error::CompressionError, true)); } - if status.is_empty() { - println!("MISSING PSEUDO HEADERS"); - kawa.parsing_phase.error("Missing pseudo headers".into()); + if invalid_headers || status.len() == 0 { + println!("INVALID HEADERS"); + return Err((H2Error::ProtocolError, false)); } StatusLine::Response { version: Version::V20, @@ -153,9 +214,6 @@ pub fn handle_header( kawa.storage.start, kawa.storage.head, kawa.storage.end ); - if kawa.is_error() { - return; - } callbacks.on_headers(kawa); if end_stream { @@ -176,7 +234,7 @@ pub fn handle_header( })); if kawa.parsing_phase == ParsingPhase::Terminated { - return; + return Ok(()); } kawa.parsing_phase = match kawa.body_size { @@ -185,6 +243,7 @@ pub fn handle_header( BodySize::Length(_) => ParsingPhase::Body, BodySize::Empty => ParsingPhase::Chunks { first: true }, }; + Ok(()) } pub fn handle_trailer( @@ -192,32 +251,38 @@ pub fn handle_trailer( input: &[u8], end_stream: bool, decoder: &mut hpack::Decoder, -) { - decoder - .decode_with_cb(input, |k, v| { - let start = kawa.storage.end as u32; - kawa.storage.write_all(&k).unwrap(); - kawa.storage.write_all(&v).unwrap(); - let len_key = k.len() as u32; - let len_val = v.len() as u32; - let key = Store::Slice(Slice { - start, - len: len_key, - }); - let val = Store::Slice(Slice { - start: start + len_key, - len: len_val, - }); - kawa.push_block(Block::Header(Pair { key, val })); - }) - .unwrap(); +) -> Result<(), (H2Error, bool)> { + if !end_stream { + return Err((H2Error::ProtocolError, false)); + } + let decode_status = decoder.decode_with_cb(input, |k, v| { + let start = kawa.storage.end as u32; + kawa.storage.write_all(&k).unwrap(); + kawa.storage.write_all(&v).unwrap(); + let len_key = k.len() as u32; + let len_val = v.len() as u32; + let key = Store::Slice(Slice { + start, + len: len_key, + }); + let val = Store::Slice(Slice { + start: start + len_key, + len: len_val, + }); + kawa.push_block(Block::Header(Pair { key, val })); + }); + + if let Err(error) = decode_status { + println!("INVALID FRAGMENT: {error:?}"); + return Err((H2Error::CompressionError, true)); + } - // assert!(end_stream); kawa.push_block(Block::Flags(Flags { - end_body: end_stream, + end_body: false, end_chunk: false, end_header: true, - end_stream, + end_stream: true, })); kawa.parsing_phase = ParsingPhase::Terminated; + Ok(()) } diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs index e7eb39127..18c53720d 100644 --- a/lib/src/protocol/mux/serializer.rs +++ b/lib/src/protocol/mux/serializer.rs @@ -6,7 +6,7 @@ use cookie_factory::{ GenError, }; -use super::{ +use crate::protocol::mux::{ h2::H2Settings, parser::{FrameHeader, FrameType, H2Error}, }; From 85fe0df4b6dd59bfbf823625c78ea66e2eea4202 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 30 May 2024 12:44:40 +0200 Subject: [PATCH 38/44] h2spec ETA: 140/147 - h2 converter takes into account the window of the converted stream - update windows upon receiving a new initial_window setting - track negative windows Signed-off-by: Eloi DEMOLIS --- lib/Cargo.toml | 2 +- lib/src/protocol/mux/converter.rs | 23 ++++++--- lib/src/protocol/mux/h2.rs | 80 +++++++++++++++++++++---------- lib/src/protocol/mux/mod.rs | 7 ++- lib/src/protocol/mux/pkawa.rs | 14 ------ 5 files changed, 79 insertions(+), 47 deletions(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 13c196f42..80e5e4de8 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -34,7 +34,7 @@ hdrhistogram = "^7.5.4" hex = "^0.4.3" hpack = "^0.3.0" idna = "^0.5.0" -kawa = { version = "^0.6.6", default-features = false } +kawa = { version = "^0.6.7", default-features = false } libc = "^0.2.155" memchr = "^2.7.2" mio = { version = "^1.0.0", features = ["os-poll", "os-ext", "net"] } diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 05f3ebe62..27b081342 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -15,6 +15,7 @@ use crate::protocol::{ }; pub struct H2BlockConverter<'a> { + pub window: i32, pub stream_id: StreamId, pub encoder: &'a mut hpack::Encoder<'static>, pub out: Vec, @@ -122,12 +123,21 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { } Block::Chunk(Chunk { data }) => { let mut header = [0; 9]; - let payload_len = match &data { - Store::Empty => 0, - Store::Detached(s) | Store::Slice(s) => s.len, - Store::Static(s) => s.len() as u32, - Store::Alloc(a, i) => a.len() as u32 - i, + let payload_len = data.len(); + let (data, payload_len, can_continue) = if self.window >= payload_len as i32 { + // the window is wide enought to send the entire chunk + (data, payload_len as u32, true) + } else if self.window > 0 { + // we split the chunk to fit in the window + let (before, after) = data.split(self.window as usize); + kawa.blocks.push_front(Block::Chunk(Chunk { data: after })); + (before, self.window as u32, false) + } else { + // the window can't take any more bytes, return the chunk to the blocks + kawa.blocks.push_front(Block::Chunk(Chunk { data })); + return false; }; + self.window -= payload_len as i32; gen_frame_header( &mut header, &FrameHeader { @@ -140,7 +150,8 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { .unwrap(); kawa.push_out(Store::from_slice(&header)); kawa.push_out(data); - kawa.push_delimiter() + kawa.push_delimiter(); + return can_continue; } Block::Flags(Flags { end_header, diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 5000b5294..160dfa132 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{cmp::min, collections::HashMap}; use rusty_ulid::Ulid; use sozu_command::ready::Ready; @@ -9,7 +9,7 @@ use crate::{ converter, debug_kawa, forcefully_terminate_answer, parser::{ self, error_code_to_str, Frame, FrameHeader, FrameType, H2Error, Headers, ParserError, - ParserErrorKind, StreamDependency, WindowUpdate, + ParserErrorKind, WindowUpdate, }, pkawa, serializer, set_default_answer, update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, Endpoint, GenericHttpStream, @@ -127,13 +127,13 @@ pub struct ConnectionH2 { impl std::fmt::Debug for ConnectionH2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ConnectionH2") - .field("expect", &self.expect_read) .field("position", &self.position) + .field("state", &self.state) + .field("expect", &self.expect_read) .field("readiness", &self.readiness) .field("local_settings", &self.local_settings) .field("peer_settings", &self.peer_settings) .field("socket", &self.socket.socket_ref()) - .field("state", &self.state) .field("streams", &self.streams) .field("zero", &self.zero.storage.meter(20)) .field("window", &self.window) @@ -185,15 +185,6 @@ impl ConnectionH2 { return self.force_disconnect(); } } - // ( - // H2State::Frame(FrameHeader { - // payload_len, - // frame_type: FrameType::Data, - // flags, - // stream_id, - // }), - // _, - // ) => {} _ => {} } return MuxResult::Continue; @@ -340,9 +331,7 @@ impl ConnectionH2 { Some(_) => {} None => return self.goaway(H2Error::InternalError), } - } else if header.frame_type != FrameType::Priority - && header.frame_type != FrameType::WindowUpdate - { + } else if header.frame_type != FrameType::Priority { println_!( "ONLY HEADERS AND PRIORITY CAN BE RECEIVED ON IDLE/CLOSED STREAMS" ); @@ -546,6 +535,7 @@ impl ConnectionH2 { } let mut converter = converter::H2BlockConverter { + window: 0, stream_id: 0, encoder: &mut self.encoder, out: Vec::new(), @@ -557,10 +547,16 @@ impl ConnectionH2 { 'outer: for stream_id in priorities { let global_stream_id = *self.streams.get(stream_id).unwrap(); let stream = &mut context.streams[global_stream_id]; - let kawa = stream.wbuffer(&self.position); + let parts = stream.split(&self.position); + let kawa = parts.wbuffer; if kawa.is_main_phase() || kawa.is_error() { + let window = min(*parts.window, self.window); + converter.window = window; converter.stream_id = *stream_id; kawa.prepare(&mut converter); + let consumed = window - converter.window; + *parts.window -= consumed; + self.window -= consumed; debug_kawa(kawa); } while !kawa.out.is_empty() { @@ -828,7 +824,7 @@ impl ConnectionH2 { 1 => { self.peer_settings.settings_header_table_size = v }, 2 => { self.peer_settings.settings_enable_push = v == 1; is_error |= v > 1 }, 3 => { self.peer_settings.settings_max_concurrent_streams = v }, - 4 => { self.peer_settings.settings_initial_window_size = v; is_error |= v >= 1<<31 }, + 4 => { is_error |= self.update_initial_window_size(v, context) }, 5 => { self.peer_settings.settings_max_frame_size = v; is_error |= v >= 1<<24 || v < 1<<14 }, 6 => { self.peer_settings.settings_max_header_list_size = v }, 8 => { self.peer_settings.settings_enable_connect_protocol = v == 1; is_error |= v > 1 }, @@ -881,23 +877,29 @@ impl ConnectionH2 { }) => { let increment = increment as i32; if stream_id == 0 { - if increment > i32::MAX - self.window { - return self.goaway(H2Error::FlowControlError); + if let Some(window) = self.window.checked_add(increment) { + if self.window <= 0 && window > 0 { + self.readiness.interest.insert(Ready::WRITABLE); + } + self.window = window; } else { - self.window += increment; + return self.goaway(H2Error::FlowControlError); } } else { if let Some(global_stream_id) = self.streams.get(&stream_id) { let stream = &mut context.streams[*global_stream_id]; - if increment > i32::MAX - stream.window { + if let Some(window) = stream.window.checked_add(increment) { + if stream.window <= 0 && window > 0 { + self.readiness.interest.insert(Ready::WRITABLE); + } + stream.window = window; + } else { return self.reset_stream( *global_stream_id, context, endpoint, H2Error::FlowControlError, ); - } else { - stream.window += increment; } } else { println_!( @@ -911,6 +913,36 @@ impl ConnectionH2 { MuxResult::Continue } + fn update_initial_window_size(&mut self, value: u32, context: &mut Context) -> bool + where + L: ListenerHandler + L7ListenerHandler, + { + if value >= 1 << 31 { + return true; + } + let delta = value as i32 - self.peer_settings.settings_initial_window_size as i32; + println!( + "INITIAL_WINDOW_SIZE: {} -> {} => {}", + self.peer_settings.settings_initial_window_size, value, delta + ); + let mut open_window = false; + for (i, stream) in context.streams.iter_mut().enumerate() { + println!( + " - stream_{i}: {} -> {}", + stream.window, + stream.window + delta + ); + open_window |= stream.window <= 0 && stream.window + delta > 0; + stream.window += delta; + } + println_!("UPDATE INIT WINDOW: {open_window} {:?}", self.readiness); + if open_window { + self.readiness.interest.insert(Ready::WRITABLE); + } + self.peer_settings.settings_initial_window_size = value; + false + } + pub fn force_disconnect(&mut self) -> MuxResult { self.state = H2State::Error; match self.position { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 193a93f51..55c329ff9 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -275,7 +275,7 @@ impl Connection { state: H2State::ClientPreface, streams: HashMap::new(), timeout_container, - window: 1 << 16, + window: (1 << 16) - 1, zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), })) } @@ -306,7 +306,7 @@ impl Connection { state: H2State::ClientPreface, streams: HashMap::new(), timeout_container, - window: 1 << 16, + window: (1 << 16) - 1, zero: kawa::Kawa::new(kawa::Kind::Request, kawa::Buffer::new(buffer)), })) } @@ -586,6 +586,7 @@ pub struct Stream { /// This struct allows to mutably borrow the read and write buffers (dependant on the position) /// as well as the context of a Stream at the same time pub struct StreamParts<'a> { + pub window: &'a mut i32, pub rbuffer: &'a mut GenericHttpStream, pub wbuffer: &'a mut GenericHttpStream, pub context: &'a mut HttpContext, @@ -616,11 +617,13 @@ impl Stream { pub fn split(&mut self, position: &Position) -> StreamParts<'_> { match position { Position::Client(_) => StreamParts { + window: &mut self.window, rbuffer: &mut self.back, wbuffer: &mut self.front, context: &mut self.context, }, Position::Server => StreamParts { + window: &mut self.window, rbuffer: &mut self.front, wbuffer: &mut self.back, context: &mut self.context, diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index 012c16ddb..f94cf21e8 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -17,20 +17,6 @@ use crate::{ }, }; -trait AdHocStore { - fn len(&self) -> usize; -} -impl AdHocStore for Store { - fn len(&self) -> usize { - match self { - Store::Empty => 0, - Store::Slice(slice) | Store::Detached(slice) => slice.len(), - Store::Static(s) => s.len(), - Store::Alloc(a, i) => a.len() - *i as usize, - } - } -} - pub fn handle_header( decoder: &mut hpack::Decoder, prioriser: &mut Prioriser, From c3366981eff4fa245d984ff436c7d24a1901f786 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 12 Jun 2024 15:57:37 +0200 Subject: [PATCH 39/44] Update metrics, backend status and events Signed-off-by: Eloi DEMOLIS --- command/src/proto/display.rs | 2 +- lib/src/backends.rs | 3 +- lib/src/http.rs | 13 +- lib/src/https.rs | 13 +- lib/src/lib.rs | 2 +- lib/src/protocol/mux/h1.rs | 94 ++++++---- lib/src/protocol/mux/h2.rs | 99 ++++++++--- lib/src/protocol/mux/mod.rs | 332 ++++++++++++++++++++++++++--------- 8 files changed, 403 insertions(+), 155 deletions(-) diff --git a/command/src/proto/display.rs b/command/src/proto/display.rs index 75f34e69c..63112211c 100644 --- a/command/src/proto/display.rs +++ b/command/src/proto/display.rs @@ -152,7 +152,7 @@ impl Response { } impl ResponseContent { - fn display(&self, json: bool) -> Result<(), DisplayError> { + pub fn display(&self, json: bool) -> Result<(), DisplayError> { let content_type = match &self.content_type { Some(content_type) => content_type, None => return Ok(println!("No content")), diff --git a/lib/src/backends.rs b/lib/src/backends.rs index 2b3d1e087..be8ab5ac7 100644 --- a/lib/src/backends.rs +++ b/lib/src/backends.rs @@ -297,7 +297,8 @@ impl BackendMap { })?; self.available = true; - Ok((next_backend.clone(), tcp_stream)) + drop(borrowed_backend); + Ok((next_backend, tcp_stream)) } pub fn backend_from_sticky_session( diff --git a/lib/src/http.rs b/lib/src/http.rs index abd081111..63fea8fb4 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -259,15 +259,16 @@ impl HttpSession { return None; }; let backend = mux.router.backends.remove(&back_token).unwrap(); - let (cluster_id, backend_readiness, backend_socket, mut container_backend_timeout) = + let (cluster_id, backend, backend_readiness, backend_socket, mut container_backend_timeout) = match backend { mux::Connection::H1(mux::ConnectionH1 { - position: mux::Position::Client(mux::BackendStatus::Connected(cluster_id)), + position: + mux::Position::Client(cluster_id, backend, mux::BackendStatus::Connected), readiness, socket, timeout_container, .. - }) => (cluster_id, readiness, socket, timeout_container), + }) => (cluster_id, backend, readiness, socket, timeout_container), mux::Connection::H1(_) => { error!("The backend disconnected just after upgrade, abort"); return None; @@ -283,11 +284,12 @@ impl HttpSession { container_frontend_timeout.reset(); container_backend_timeout.reset(); + let backend_id = backend.borrow().backend_id.clone(); let mut pipe = Pipe::new( stream.back.storage.buffer, - None, + Some(backend_id), Some(backend_socket), - None, + Some(backend), Some(container_backend_timeout), Some(container_frontend_timeout), Some(cluster_id), @@ -307,7 +309,6 @@ impl HttpSession { gauge_add!("protocol.http", -1); gauge_add!("protocol.ws", 1); - gauge_add!("http.active_requests", -1); gauge_add!("websocket.active_requests", 1); Some(HttpStateMachine::WebSocket(pipe)) } diff --git a/lib/src/https.rs b/lib/src/https.rs index 1d57e2169..f8a134b8b 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -343,15 +343,16 @@ impl HttpsSession { return None; }; let backend = mux.router.backends.remove(&back_token).unwrap(); - let (cluster_id, backend_readiness, backend_socket, mut container_backend_timeout) = + let (cluster_id, backend, backend_readiness, backend_socket, mut container_backend_timeout) = match backend { mux::Connection::H1(mux::ConnectionH1 { - position: mux::Position::Client(mux::BackendStatus::Connected(cluster_id)), + position: + mux::Position::Client(cluster_id, backend, mux::BackendStatus::Connected), readiness, socket, timeout_container, .. - }) => (cluster_id, readiness, socket, timeout_container), + }) => (cluster_id, backend, readiness, socket, timeout_container), mux::Connection::H1(_) => { error!("The backend disconnected just after upgrade, abort"); return None; @@ -367,11 +368,12 @@ impl HttpsSession { container_frontend_timeout.reset(); container_backend_timeout.reset(); + let backend_id = backend.borrow().backend_id.clone(); let mut pipe = Pipe::new( stream.back.storage.buffer, - None, + Some(backend_id), Some(backend_socket), - None, + Some(backend), Some(container_backend_timeout), Some(container_frontend_timeout), Some(cluster_id), @@ -391,7 +393,6 @@ impl HttpsSession { gauge_add!("protocol.https", -1); gauge_add!("protocol.wss", 1); - gauge_add!("http.active_requests", -1); gauge_add!("websocket.active_requests", 1); Some(HttpsStateMachine::WebSocket(pipe)) } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 3250f3961..7d565afdd 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -583,7 +583,7 @@ pub enum BackendConnectAction { pub enum BackendConnectionError { #[error("Not found: {0:?}")] NotFound(ObjectKind), - #[error("Too many connections on cluster {0:?}")] + #[error("Too many failed attemps on cluster {0:?}")] MaxConnectionRetries(Option), #[error("the sessions slab has reached maximum capacity")] MaxSessionsMemory, diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 81d1637e9..c0a93840c 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -3,7 +3,9 @@ use sozu_command::ready::Ready; use crate::{ println_, protocol::mux::{ - debug_kawa, forcefully_terminate_answer, parser::H2Error, set_default_answer, update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, Endpoint, GlobalStreamId, MuxResult, Position, StreamState + debug_kawa, forcefully_terminate_answer, parser::H2Error, set_default_answer, + update_readiness_after_read, update_readiness_after_write, BackendStatus, Context, + Endpoint, GlobalStreamId, MuxResult, Position, StreamState, }, socket::SocketHandler, timer::TimeoutContainer, @@ -44,6 +46,16 @@ impl ConnectionH1 { let kawa = parts.rbuffer; let (size, status) = self.socket.socket_read(kawa.storage.space()); kawa.storage.fill(size); + match self.position { + Position::Client(..) => { + count!("back_bytes_in", size as i64); + parts.metrics.backend_bin += size; + } + Position::Server => { + count!("bytes_in", size as i64); + parts.metrics.bin += size; + } + } if update_readiness_after_read(size, status, &mut self.readiness) { return MuxResult::Continue; } @@ -53,7 +65,7 @@ impl ConnectionH1 { debug_kawa(kawa); if kawa.is_error() { match self.position { - Position::Client(_) => { + Position::Client(..) => { let StreamState::Linked(token) = stream.state else { unreachable!() }; @@ -79,15 +91,11 @@ impl ConnectionH1 { .interest .insert(Ready::WRITABLE) } - match self.position { - Position::Server => { - if !was_main_phase { - self.requests += 1; - println_!("REQUESTS: {}", self.requests); - stream.state = StreamState::Link - } - } - Position::Client(_) => {} + if !was_main_phase && self.position.is_server() { + self.requests += 1; + println_!("REQUESTS: {}", self.requests); + gauge_add!("http.active_requests", 1); + stream.state = StreamState::Link } }; MuxResult::Continue @@ -101,7 +109,8 @@ impl ConnectionH1 { println_!("======= MUX H1 WRITABLE {:?}", self.position); self.timeout_container.reset(); let stream = &mut context.streams[self.stream]; - let kawa = stream.wbuffer(&self.position); + let parts = stream.split(&self.position); + let kawa = parts.wbuffer; kawa.prepare(&mut kawa::h1::BlockConverter); debug_kawa(kawa); let bufs = kawa.as_io_slice(); @@ -111,13 +120,23 @@ impl ConnectionH1 { } let (size, status) = self.socket.socket_write_vectored(&bufs); kawa.consume(size); + match self.position { + Position::Client(..) => { + count!("back_bytes_out", size as i64); + parts.metrics.backend_bout += size; + } + Position::Server => { + count!("bytes_out", size as i64); + parts.metrics.bout += size; + } + } if update_readiness_after_write(size, status, &mut self.readiness) { return MuxResult::Continue; } if kawa.is_terminated() && kawa.is_completed() { match self.position { - Position::Client(_) => self.readiness.interest.insert(Ready::READABLE), + Position::Client(..) => self.readiness.interest.insert(Ready::READABLE), Position::Server => { if stream.context.closing { return MuxResult::CloseSession; @@ -153,7 +172,12 @@ impl ConnectionH1 { _ => {} } // ACCESS LOG - stream.generate_access_log(false, Some(String::from("H1")), context.listener.clone()); + stream.generate_access_log( + false, + Some(String::from("H1")), + context.listener.clone(), + ); + stream.metrics.reset(); let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); if stream.context.keep_alive_frontend { self.timeout_container.reset(); @@ -180,9 +204,9 @@ impl ConnectionH1 { } pub fn force_disconnect(&mut self) -> MuxResult { - match self.position { - Position::Client(_) => { - self.position = Position::Client(BackendStatus::Disconnecting); + match &mut self.position { + Position::Client(_, _, status) => { + *status = BackendStatus::Disconnecting; self.readiness.event = Ready::HUP; MuxResult::Continue } @@ -196,13 +220,13 @@ impl ConnectionH1 { L: ListenerHandler + L7ListenerHandler, { match self.position { - Position::Client(BackendStatus::KeepAlive(_)) - | Position::Client(BackendStatus::Disconnecting) => { + Position::Client(_, _, BackendStatus::KeepAlive) + | Position::Client(_, _, BackendStatus::Disconnecting) => { println_!("close detached client ConnectionH1"); return; } - Position::Client(BackendStatus::Connecting(_)) - | Position::Client(BackendStatus::Connected(_)) => {} + Position::Client(_, _, BackendStatus::Connecting(_)) + | Position::Client(_, _, BackendStatus::Connected) => {} Position::Server => unreachable!(), } // reconnection is handled by the server @@ -221,28 +245,34 @@ impl ConnectionH1 { let stream_context = &mut stream.context; println_!("end H1 stream {}: {stream_context:#?}", self.stream); match &mut self.position { - Position::Client(BackendStatus::Connected(cluster_id)) - | Position::Client(BackendStatus::Connecting(cluster_id)) => { + Position::Client(_, _, BackendStatus::Connecting(_)) => { + self.stream = usize::MAX; + self.force_disconnect(); + } + Position::Client(_, _, status @ BackendStatus::Connected) => { self.stream = usize::MAX; // keep alive should probably be used only if the http context is fully reset // in case end_stream occurs due to an error the connection state is probably // unrecoverable and should be terminated if stream_context.keep_alive_backend { - self.position = - Position::Client(BackendStatus::KeepAlive(std::mem::take(cluster_id))) + *status = BackendStatus::KeepAlive; } else { self.force_disconnect(); } } - Position::Client(BackendStatus::KeepAlive(_)) - | Position::Client(BackendStatus::Disconnecting) => unreachable!(), + Position::Client(_, _, BackendStatus::KeepAlive) + | Position::Client(_, _, BackendStatus::Disconnecting) => unreachable!(), Position::Server => match (stream.front.consumed, stream.back.is_main_phase()) { (true, true) => { // we have a "forwardable" answer from the back // if the answer is not terminated we send an RstStream to properly clean the stream // if it is terminated, we finish the transfer, the backend is not necessary anymore if !stream.back.is_terminated() { - forcefully_terminate_answer(stream, &mut self.readiness, H2Error::InternalError); + forcefully_terminate_answer( + stream, + &mut self.readiness, + H2Error::InternalError, + ); } else { stream.state = StreamState::Unlinked; self.readiness.interest.insert(Ready::WRITABLE); @@ -271,11 +301,11 @@ impl ConnectionH1 { self.readiness.interest.insert(Ready::ALL); self.stream = stream; match &mut self.position { - Position::Client(BackendStatus::KeepAlive(cluster_id)) => { - self.position = - Position::Client(BackendStatus::Connecting(std::mem::take(cluster_id))) + Position::Client(_, _, status @ BackendStatus::KeepAlive) => { + *status = BackendStatus::Connected; } - Position::Client(_) => {} + Position::Client(_, _, BackendStatus::Disconnecting) => unreachable!(), + Position::Client(_, _, _) => {} Position::Server => unreachable!(), } } diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 160dfa132..2987af681 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -159,7 +159,9 @@ impl ConnectionH2 { let kawa = match stream_id { H2StreamId::Zero => &mut self.zero, H2StreamId::Other(stream_id, global_stream_id) => { - context.streams[global_stream_id].rbuffer(&self.position) + context.streams[global_stream_id] + .split(&self.position) + .rbuffer } }; println_!("{:?}({stream_id:?}, {amount})", self.state); @@ -170,6 +172,14 @@ impl ConnectionH2 { } let (size, status) = self.socket.socket_read(&mut kawa.storage.space()[..amount]); kawa.storage.fill(size); + match self.position { + Position::Client(..) => { + count!("back_bytes_in", size as i64); + } + Position::Server => { + count!("bytes_in", size as i64); + } + } if update_readiness_after_read(size, status, &mut self.readiness) { return MuxResult::Continue; } else { @@ -202,8 +212,8 @@ impl ConnectionH2 { (H2State::Error, _) | (H2State::GoAway, _) | (H2State::ServerSettings, Position::Server) - | (H2State::ClientPreface, Position::Client(_)) - | (H2State::ClientSettings, Position::Client(_)) => unreachable!( + | (H2State::ClientPreface, Position::Client(..)) + | (H2State::ClientSettings, Position::Client(..)) => unreachable!( "Unexpected combination: (Readable, {:?}, {:?})", self.state, self.position ), @@ -267,7 +277,7 @@ impl ConnectionH2 { self.expect_write = Some(H2StreamId::Zero); return self.handle_frame(settings, context, endpoint); } - (H2State::ServerSettings, Position::Client(_)) => { + (H2State::ServerSettings, Position::Client(..)) => { let i = kawa.storage.data(); match parser::frame_header(i, self.local_settings.settings_max_frame_size) { Ok(( @@ -432,6 +442,14 @@ impl ConnectionH2 { while !kawa.storage.is_empty() { let (size, status) = self.socket.socket_write(kawa.storage.data()); kawa.storage.consume(size); + match self.position { + Position::Client(..) => { + count!("back_bytes_out", size as i64); + } + Position::Server => { + count!("bytes_out", size as i64); + } + } if update_readiness_after_write(size, status, &mut self.readiness) { return MuxResult::Continue; } @@ -446,12 +464,12 @@ impl ConnectionH2 { | (H2State::Discard, _) | (H2State::ClientPreface, Position::Server) | (H2State::ClientSettings, Position::Server) - | (H2State::ServerSettings, Position::Client(_)) => unreachable!( + | (H2State::ServerSettings, Position::Client(..)) => unreachable!( "Unexpected combination: (Writable, {:?}, {:?})", self.state, self.position ), (H2State::GoAway, _) => self.force_disconnect(), - (H2State::ClientPreface, Position::Client(_)) => { + (H2State::ClientPreface, Position::Client(..)) => { println_!("Preparing preface and settings"); let pri = serializer::H2_PRI.as_bytes(); let kawa = &mut self.zero; @@ -470,7 +488,7 @@ impl ConnectionH2 { self.expect_write = Some(H2StreamId::Zero); MuxResult::Continue } - (H2State::ClientSettings, Position::Client(_)) => { + (H2State::ClientSettings, Position::Client(..)) => { println_!("Sent preface and settings"); self.state = H2State::ServerSettings; self.readiness.interest.remove(Ready::WRITABLE); @@ -494,11 +512,22 @@ impl ConnectionH2 { self.expect_write { let stream = &mut context.streams[global_stream_id]; - let kawa = stream.wbuffer(&self.position); + let parts = stream.split(&self.position); + let kawa = parts.wbuffer; while !kawa.out.is_empty() { let bufs = kawa.as_io_slice(); let (size, status) = self.socket.socket_write_vectored(&bufs); kawa.consume(size); + match self.position { + Position::Client(..) => { + count!("back_bytes_out", size as i64); + parts.metrics.backend_bout += size; + } + Position::Server => { + count!("bytes_out", size as i64); + parts.metrics.bout += size; + } + } if let Some((read_stream, amount)) = self.expect_read { if write_stream == read_stream && kawa.storage.available_space() >= amount @@ -513,7 +542,7 @@ impl ConnectionH2 { self.expect_write = None; if (kawa.is_terminated() || kawa.is_error()) && kawa.is_completed() { match self.position { - Position::Client(_) => {} + Position::Client(..) => {} Position::Server => { // mark stream as reusable println_!("Recycle stream: {global_stream_id}"); @@ -563,6 +592,16 @@ impl ConnectionH2 { let bufs = kawa.as_io_slice(); let (size, status) = self.socket.socket_write_vectored(&bufs); kawa.consume(size); + match self.position { + Position::Client(..) => { + count!("back_bytes_out", size as i64); + parts.metrics.backend_bout += size; + } + Position::Server => { + count!("bytes_out", size as i64); + parts.metrics.bout += size; + } + } if update_readiness_after_write(size, status, &mut self.readiness) { self.expect_write = Some(H2StreamId::Other(*stream_id, global_stream_id)); @@ -572,7 +611,7 @@ impl ConnectionH2 { self.expect_write = None; if (kawa.is_terminated() || kawa.is_error()) && kawa.is_completed() { match self.position { - Position::Client(_) => {} + Position::Client(..) => {} Position::Server => { // mark stream as reusable println_!("Recycle1 stream: {global_stream_id}"); @@ -646,7 +685,7 @@ impl ConnectionH2 { pub fn new_stream_id(&mut self) -> StreamId { self.last_stream_id += 2; match self.position { - Position::Client(_) => self.last_stream_id - 1, + Position::Client(..) => self.last_stream_id - 1, Position::Server => self.last_stream_id - 2, } } @@ -670,7 +709,12 @@ impl ConnectionH2 { None => panic!("stream error"), }; let stream = &mut context.streams[global_stream_id]; - let kawa = stream.rbuffer(&self.position); + let parts = stream.split(&self.position); + let kawa = parts.rbuffer; + match self.position { + Position::Client(..) => parts.metrics.backend_bin += slice.len(), + Position::Server => parts.metrics.bin += slice.len(), + } slice.start += kawa.storage.head as u32; kawa.storage.head += slice.len(); kawa.push_block(kawa::Block::Chunk(kawa::Chunk { @@ -720,6 +764,10 @@ impl ConnectionH2 { let buffer = headers.header_block_fragment.data(kawa.storage.buffer()); let stream = &mut context.streams[global_stream_id]; let parts = &mut stream.split(&self.position); + match self.position { + Position::Client(..) => parts.metrics.backend_bin += buffer.len(), + Position::Server => parts.metrics.bin += buffer.len(), + } let was_initial = parts.rbuffer.is_initial(); let status = pkawa::handle_header( &mut self.decoder, @@ -746,15 +794,14 @@ impl ConnectionH2 { .interest .insert(Ready::WRITABLE) } - if was_initial { - match self.position { - Position::Server => stream.state = StreamState::Link, - Position::Client(_) => {} - }; + // was_initial prevents trailers from triggering connection + if was_initial && self.position.is_server() { + gauge_add!("http.active_requests", 1); + stream.state = StreamState::Link; } } Frame::PushPromise(push_promise) => match self.position { - Position::Client(_) => { + Position::Client(..) => { if self.local_settings.settings_enable_push { todo!("forward the push") } else { @@ -796,7 +843,7 @@ impl ConnectionH2 { } let stream = &mut context.streams[stream_id]; match self.position { - Position::Client(_) => {} + Position::Client(..) => {} Position::Server => { // This is a special case, normally, all stream are terminated by the server // when the last byte of the response is written. Here, the reset is requested @@ -945,9 +992,9 @@ impl ConnectionH2 { pub fn force_disconnect(&mut self) -> MuxResult { self.state = H2State::Error; - match self.position { - Position::Client(_) => { - self.position = Position::Client(BackendStatus::Disconnecting); + match &mut self.position { + Position::Client(_, _, status) => { + *status = BackendStatus::Disconnecting; self.readiness.event = Ready::HUP; MuxResult::Continue } @@ -961,10 +1008,8 @@ impl ConnectionH2 { L: ListenerHandler + L7ListenerHandler, { match self.position { - Position::Client(BackendStatus::Connected(_)) - | Position::Client(BackendStatus::Connecting(_)) - | Position::Client(BackendStatus::Disconnecting) => {} - Position::Client(BackendStatus::KeepAlive(_)) => unreachable!(), + Position::Client(_, _, BackendStatus::KeepAlive) => unreachable!(), + Position::Client(..) => {} Position::Server => unreachable!(), } // reconnection is handled by the server for each stream separately @@ -1005,7 +1050,7 @@ impl ConnectionH2 { let stream_context = &mut context.streams[stream].context; println_!("end H2 stream {stream}: {stream_context:#?}"); match self.position { - Position::Client(_) => { + Position::Client(..) => { for (stream_id, global_stream_id) in &self.streams { if *global_stream_id == stream { let id = *stream_id; diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 55c329ff9..90f3f9d44 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -1,15 +1,20 @@ use std::{ cell::RefCell, collections::HashMap, + fmt::Debug, io::ErrorKind, net::{Shutdown, SocketAddr}, rc::{Rc, Weak}, - time::Duration, + time::{Duration, Instant}, }; use mio::{net::TcpStream, Interest, Token}; use rusty_ulid::Ulid; -use sozu_command::{logging::EndpointRecord, proto::command::ListenerType, ready::Ready}; +use sozu_command::{ + logging::EndpointRecord, + proto::command::{Event, EventKind, ListenerType}, + ready::Ready, +}; mod converter; mod h1; @@ -28,8 +33,9 @@ use crate::{ mux::h2::{H2Settings, H2State, H2StreamId, Prioriser}, SessionState, }, + retry::RetryPolicy, router::Route, - server::CONN_RETRIES, + server::{push_event, CONN_RETRIES}, socket::{FrontRustls, SocketHandler, SocketResult}, timer::TimeoutContainer, BackendConnectionError, L7ListenerHandler, L7Proxy, ListenerHandler, Protocol, ProxySession, @@ -148,22 +154,34 @@ fn forcefully_terminate_answer(stream: &mut Stream, readiness: &mut Readiness, e readiness.interest.insert(Ready::WRITABLE); } -#[derive(Debug)] pub enum Position { - Client(BackendStatus), + Client(String, Rc>, BackendStatus), Server, } +impl Debug for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Client(cluster_id, _, status) => f + .debug_tuple("Client") + .field(cluster_id) + .field(status) + .finish(), + Self::Server => write!(f, "Server"), + } + } +} + impl Position { fn is_server(&self) -> bool { match self { - Position::Client(_) => false, + Position::Client(..) => false, Position::Server => true, } } fn is_client(&self) -> bool { match self { - Position::Client(_) => true, + Position::Client(..) => true, Position::Server => false, } } @@ -171,9 +189,9 @@ impl Position { #[derive(Debug)] pub enum BackendStatus { - Connecting(String), - Connected(String), - KeepAlive(String), + Connecting(Instant), + Connected, + KeepAlive, Disconnecting, } @@ -234,11 +252,16 @@ impl Connection { pub fn new_h1_client( front_stream: Front, cluster_id: String, + backend: Rc>, timeout_container: TimeoutContainer, ) -> Connection { Connection::H1(ConnectionH1 { socket: front_stream, - position: Position::Client(BackendStatus::Connecting(cluster_id)), + position: Position::Client( + cluster_id, + backend, + BackendStatus::Connecting(Instant::now()), + ), readiness: Readiness { interest: Ready::WRITABLE | Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, @@ -282,6 +305,7 @@ impl Connection { pub fn new_h2_client( front_stream: Front, cluster_id: String, + backend: Rc>, pool: Weak>, timeout_container: TimeoutContainer, ) -> Option> { @@ -296,7 +320,11 @@ impl Connection { last_stream_id: 0, local_settings: H2Settings::default(), peer_settings: H2Settings::default(), - position: Position::Client(BackendStatus::Connecting(cluster_id)), + position: Position::Client( + cluster_id, + backend, + BackendStatus::Connecting(Instant::now()), + ), prioriser: Prioriser::new(), readiness: Readiness { interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, @@ -386,6 +414,21 @@ impl Connection { E: Endpoint, L: ListenerHandler + L7ListenerHandler, { + match self.position() { + Position::Client(cluster_id, backend, _) => { + let mut backend_borrow = backend.borrow_mut(); + backend_borrow.dec_connections(); + gauge_add!("backend.connections", -1); + gauge_add!( + "connections_per_backend", + -1, + Some(cluster_id), + Some(&backend_borrow.backend_id) + ); + println_!("--------------- CONNECTION CLOSE: {backend_borrow:#?}"); + } + Position::Server => todo!(), + } match self { Connection::H1(c) => c.close(context, endpoint), Connection::H2(c) => c.close(context, endpoint), @@ -396,6 +439,14 @@ impl Connection { where L: ListenerHandler + L7ListenerHandler, { + match self.position() { + Position::Client(_, backend, BackendStatus::Connected) => { + let mut backend_borrow = backend.borrow_mut(); + backend_borrow.active_requests = backend_borrow.active_requests.saturating_sub(1); + println_!("--------------- CONNECTION END STREAM: {backend_borrow:#?}"); + } + _ => {} + } match self { Connection::H1(c) => c.end_stream(stream, context), Connection::H2(c) => c.end_stream(stream, context), @@ -406,6 +457,14 @@ impl Connection { where L: ListenerHandler + L7ListenerHandler, { + match self.position() { + Position::Client(_, backend, BackendStatus::Connected) => { + let mut backend_borrow = backend.borrow_mut(); + backend_borrow.active_requests += 1; + println_!("--------------- CONNECTION START STREAM: {backend_borrow:#?}"); + } + _ => {} + } match self { Connection::H1(c) => c.start_stream(stream, context), Connection::H2(c) => c.start_stream(stream, context), @@ -581,15 +640,17 @@ pub struct Stream { pub front: GenericHttpStream, pub back: GenericHttpStream, pub context: HttpContext, + pub metrics: SessionMetrics, } /// This struct allows to mutably borrow the read and write buffers (dependant on the position) -/// as well as the context of a Stream at the same time +/// as well as the context and metrics of a Stream at the same time pub struct StreamParts<'a> { pub window: &'a mut i32, pub rbuffer: &'a mut GenericHttpStream, pub wbuffer: &'a mut GenericHttpStream, pub context: &'a mut HttpContext, + pub metrics: &'a mut SessionMetrics, } impl Stream { @@ -612,36 +673,27 @@ impl Stream { front: GenericHttpStream::new(kawa::Kind::Request, kawa::Buffer::new(front_buffer)), back: GenericHttpStream::new(kawa::Kind::Response, kawa::Buffer::new(back_buffer)), context, + metrics: SessionMetrics::new(None), // FIXME }) } pub fn split(&mut self, position: &Position) -> StreamParts<'_> { match position { - Position::Client(_) => StreamParts { + Position::Client(..) => StreamParts { window: &mut self.window, rbuffer: &mut self.back, wbuffer: &mut self.front, context: &mut self.context, + metrics: &mut self.metrics, }, Position::Server => StreamParts { window: &mut self.window, rbuffer: &mut self.front, wbuffer: &mut self.back, context: &mut self.context, + metrics: &mut self.metrics, }, } } - pub fn rbuffer(&mut self, position: &Position) -> &mut GenericHttpStream { - match position { - Position::Client(_) => &mut self.back, - Position::Server => &mut self.front, - } - } - pub fn wbuffer(&mut self, position: &Position) -> &mut GenericHttpStream { - match position { - Position::Client(_) => &mut self.front, - Position::Server => &mut self.back, - } - } pub fn generate_access_log( &mut self, error: bool, @@ -650,6 +702,7 @@ impl Stream { ) where L: ListenerHandler + L7ListenerHandler, { + gauge_add!("http.active_requests", -1); let protocol = match self.context.protocol { Protocol::HTTP => "http", Protocol::HTTPS => "https", @@ -685,11 +738,11 @@ impl Stream { tags, client_rtt: None, //socket_rtt(self.front_socket()), server_rtt: None, //self.backend_socket.as_ref().and_then(socket_rtt), - service_time: Duration::from_micros(0), //metrics.service_time(), - response_time: Some(Duration::from_micros(0)), //metrics.response_time(), - request_time: Duration::from_micros(0), // metrics.request_time(), - bytes_in: 0, //metrics.bin, - bytes_out: 0, //metrics.bout, + service_time: self.metrics.service_time(), + response_time: self.metrics.backend_response_time(), + request_time: self.metrics.request_time(), + bytes_in: self.metrics.bin, + bytes_out: self.metrics.bout, user_agent: self.context.user_agent.as_deref(), }; } @@ -769,7 +822,6 @@ impl Router { context: &mut Context, session: Rc>, proxy: Rc>, - metrics: &mut SessionMetrics, ) -> Result<(), BackendConnectionError> { let stream = &mut context.streams[stream_id]; // when reused, a stream should be detached from its old connection, if not we could end @@ -828,28 +880,32 @@ impl Router { (_, _, Position::Server) => { unreachable!("Backend connection behaves like a server") } - (_, _, Position::Client(BackendStatus::Disconnecting)) => {} - (true, false, Position::Client(BackendStatus::Connecting(_))) => {} + (_, _, Position::Client(_, _, BackendStatus::Disconnecting)) => {} + (true, false, Position::Client(_, _, BackendStatus::Connecting(_))) => {} - (true, _, Position::Client(BackendStatus::Connected(other_cluster_id))) => { + (true, _, Position::Client(other_cluster_id, _, BackendStatus::Connected)) => { if *other_cluster_id == cluster_id { reuse_token = Some(*token); reuse_connecting = false; break; } } - (true, true, Position::Client(BackendStatus::Connecting(other_cluster_id))) => { + ( + true, + true, + Position::Client(other_cluster_id, _, BackendStatus::Connecting(_)), + ) => { if *other_cluster_id == cluster_id { reuse_token = Some(*token) } } - (true, _, Position::Client(BackendStatus::KeepAlive(other_cluster_id))) => { + (true, _, Position::Client(other_cluster_id, _, BackendStatus::KeepAlive)) => { if *other_cluster_id == cluster_id { unreachable!("ConnectionH2 behaves like H1") } } - (false, _, Position::Client(BackendStatus::KeepAlive(old_cluster_id))) => { + (false, _, Position::Client(old_cluster_id, _, BackendStatus::KeepAlive)) => { if *old_cluster_id == cluster_id { reuse_token = Some(*token); reuse_connecting = false; @@ -857,24 +913,33 @@ impl Router { } } // can't bundle H1 streams together - (false, _, Position::Client(BackendStatus::Connected(_))) - | (false, _, Position::Client(BackendStatus::Connecting(_))) => {} + (false, _, Position::Client(_, _, BackendStatus::Connected)) + | (false, _, Position::Client(_, _, BackendStatus::Connecting(_))) => {} } } println_!("connect: {cluster_id} (stick={frontend_should_stick}, h2={h2}) -> (reuse={reuse_token:?})"); let token = if let Some(token) = reuse_token { println_!("reused backend: {:#?}", self.backends.get(&token).unwrap()); + stream.metrics.backend_start(); + stream.metrics.backend_connected(); token } else { - let mut socket = self.backend_from_request( + let (mut socket, backend) = self.backend_from_request( &cluster_id, frontend_should_stick, stream_context, proxy.clone(), &context.listener, - metrics, )?; + stream.metrics.backend_start(); + gauge_add!("backend.connections", 1); + gauge_add!( + "connections_per_backend", + 1, + Some(&cluster_id), + Some(&backend.borrow().backend_id) + ); if let Err(e) = socket.set_nodelay(true) { error!( @@ -882,8 +947,6 @@ impl Router { socket, e ); } - // self.backend_readiness.interest = Ready::WRITABLE | Ready::HUP | Ready::ERROR; - // self.backend_connection_status = BackendConnectionStatus::Connecting(Instant::now()); let token = proxy.borrow().add_session(session); @@ -900,6 +963,7 @@ impl Router { match Connection::new_h2_client( socket, cluster_id, + backend, context.pool.clone(), timeout_container, ) { @@ -907,7 +971,7 @@ impl Router { None => return Err(BackendConnectionError::MaxBuffers), } } else { - Connection::new_h1_client(socket, cluster_id, timeout_container) + Connection::new_h1_client(socket, cluster_id, backend, timeout_container) }; self.backends.insert(token, connection); token @@ -967,8 +1031,7 @@ impl Router { context: &mut HttpContext, proxy: Rc>, listener: &Rc>, - _metrics: &mut SessionMetrics, - ) -> Result { + ) -> Result<(TcpStream, Rc>), BackendConnectionError> { let (backend, conn) = self .get_backend_for_sticky_session( cluster_id, @@ -995,14 +1058,10 @@ impl Router { ); } - // metrics.backend_id = Some(backend.borrow().backend_id.clone()); - // metrics.backend_start(); - // self.set_backend_id(backend.borrow().backend_id.clone()); - // self.backend = Some(backend); context.backend_id = Some(backend.borrow().backend_id.clone()); context.backend_address = Some(backend.borrow().address); - Ok(conn) + Ok((conn, backend)) } fn get_backend_for_sticky_session( @@ -1076,8 +1135,8 @@ impl {readiness:?}"); let dead = readiness.filter_interest().is_hup() || readiness.filter_interest().is_error(); @@ -1086,50 +1145,139 @@ impl { - *position = Position::Client(BackendStatus::Connected( + Position::Client( + cluster_id, + backend, + BackendStatus::Connecting(start), + ) => { + let mut backend_borrow = backend.borrow_mut(); + if backend_borrow.retry_policy.is_down() { + info!( + "backend server {} at {} is up", + backend_borrow.backend_id, backend_borrow.address + ); + incr!( + "backend.up", + Some(cluster_id), + Some(&backend_borrow.backend_id) + ); + push_event(Event { + kind: EventKind::BackendUp as i32, + backend_id: Some(backend_borrow.backend_id.to_owned()), + address: Some(backend_borrow.address.into()), + cluster_id: Some(cluster_id.to_owned()), + }); + } + + //successful connection, reset failure counter + backend_borrow.failures = 0; + backend_borrow.set_connection_time(start.elapsed()); + backend_borrow.retry_policy.succeed(); + + for stream in &mut context.streams { + match stream.state { + StreamState::Linked(back_token) if back_token == *token => { + stream.metrics.backend_connected(); + backend_borrow.active_requests += 1; + } + _ => {} + } + } + println_!( + "--------------- CONNECTION SUCCESS: {backend_borrow:#?}" + ); + drop(backend_borrow); + *position = Position::Client( std::mem::take(cluster_id), - )); - backend + backend.clone(), + BackendStatus::Connected, + ); + client .timeout_container() .set_duration(self.router.configured_backend_timeout); } - _ => {} + Position::Client(..) => {} + Position::Server => unreachable!(), } - match backend.writable(context, EndpointServer(&mut self.frontend)) { + match client.writable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => {} MuxResult::Upgrade => unreachable!(), // only frontend can upgrade MuxResult::CloseSession => return SessionResult::Close, } } - if backend.readiness().filter_interest().is_readable() { - match backend.readable(context, EndpointServer(&mut self.frontend)) { + if client.readiness().filter_interest().is_readable() { + match client.readable(context, EndpointServer(&mut self.frontend)) { MuxResult::Continue => {} MuxResult::Upgrade => unreachable!(), // only frontend can upgrade MuxResult::CloseSession => return SessionResult::Close, } } - if dead && !backend.readiness().filter_interest().is_readable() { - println_!("Closing {:#?}", backend); - backend.close(context, EndpointServer(&mut self.frontend)); + if dead && !client.readiness().filter_interest().is_readable() { + println_!("Closing {:#?}", client); + match client.position() { + Position::Client(cluster_id, backend, BackendStatus::Connecting(_)) => { + let mut backend_borrow = backend.borrow_mut(); + backend_borrow.failures += 1; + + let already_unavailable = backend_borrow.retry_policy.is_down(); + backend_borrow.retry_policy.fail(); + incr!( + "backend.connections.error", + Some(cluster_id), + Some(&backend_borrow.backend_id) + ); + if !already_unavailable && backend_borrow.retry_policy.is_down() { + error!( + "backend server {} at {} is down", + backend_borrow.backend_id, backend_borrow.address + ); + incr!( + "backend.down", + Some(cluster_id), + Some(&backend_borrow.backend_id) + ); + push_event(Event { + kind: EventKind::BackendDown as i32, + backend_id: Some(backend_borrow.backend_id.to_owned()), + address: Some(backend_borrow.address.into()), + cluster_id: Some(cluster_id.to_owned()), + }); + } + println_!("--------------- CONNECTION FAIL: {backend_borrow:#?}"); + } + Position::Client(_, backend, _) => { + let mut backend_borrow = backend.borrow_mut(); + for stream in &mut context.streams { + match stream.state { + StreamState::Linked(back_token) if back_token == *token => { + backend_borrow.active_requests = + backend_borrow.active_requests.saturating_sub(1); + } + _ => {} + } + } + } + Position::Server => unreachable!(), + } + client.close(context, EndpointServer(&mut self.frontend)); dead_backends.push(*token); } - if !backend.readiness().filter_interest().is_empty() { + if !client.readiness().filter_interest().is_empty() { all_backends_readiness_are_empty = false; } } if !dead_backends.is_empty() { for token in &dead_backends { let proxy_borrow = proxy.borrow(); - if let Some(mut backend) = self.router.backends.remove(token) { - backend.timeout_container().cancel(); - let socket = backend.socket_mut(); + if let Some(mut client) = self.router.backends.remove(token) { + client.timeout_container().cancel(); + let socket = client.socket_mut(); if let Err(e) = proxy_borrow.deregister_socket(socket) { error!("error deregistering back socket({:?}): {:?}", socket, e); } @@ -1190,13 +1338,10 @@ impl {} Err(error) => { println_!("Connection error: {error}"); @@ -1387,10 +1532,10 @@ impl { + let mut backend_borrow = backend.borrow_mut(); + backend_borrow.dec_connections(); + gauge_add!("backend.connections", -1); + gauge_add!( + "connections_per_backend", + -1, + Some(cluster_id), + Some(&backend_borrow.backend_id) + ); + for stream in &mut self.context.streams { + match stream.state { + StreamState::Linked(back_token) if back_token == *token => { + backend_borrow.active_requests = + backend_borrow.active_requests.saturating_sub(1); + } + _ => {} + } + } + println_!("--------------- CONNECTION(SESSION) CLOSED: {backend_borrow:#?}"); + } + Position::Server => unreachable!(), + } } let s = match &mut self.frontend { Connection::H1(c) => &mut c.socket, From 3d0430685493d4834606e8ff279f223662e46898 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 20 Jun 2024 18:04:00 +0200 Subject: [PATCH 40/44] Performance and stability improvements - remove kawa delimiters (it overly fragments the writes and slows h2 tremendously) - check rustls buffers before the socket (to reduce syscalls) - ignore empty data frames with end stream on close stream (all the stream management should be revised honestly) - only count the active streams when checking if opening a new one would overflow the max concurrent allowed (again... stream management = bad) - log the precise reason of any goaway - properly reset metrics - display total time and backend response time in access logs (will soon changed when rebase on main) Signed-off-by: Eloi DEMOLIS --- lib/src/protocol/mux/converter.rs | 4 +- lib/src/protocol/mux/h1.rs | 15 ++-- lib/src/protocol/mux/h2.rs | 118 +++++++++++++++++++----------- lib/src/protocol/mux/mod.rs | 45 +++++++----- lib/src/protocol/mux/parser.rs | 5 +- lib/src/protocol/mux/pkawa.rs | 34 +++++++-- lib/src/socket.rs | 50 ++++++------- 7 files changed, 166 insertions(+), 105 deletions(-) diff --git a/lib/src/protocol/mux/converter.rs b/lib/src/protocol/mux/converter.rs index 27b081342..feb1a81df 100644 --- a/lib/src/protocol/mux/converter.rs +++ b/lib/src/protocol/mux/converter.rs @@ -150,7 +150,7 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { .unwrap(); kawa.push_out(Store::from_slice(&header)); kawa.push_out(data); - kawa.push_delimiter(); + // kawa.push_delimiter(); return can_continue; } Block::Flags(Flags { @@ -189,7 +189,7 @@ impl<'a, T: AsBuffer> BlockConverter for H2BlockConverter<'a> { kawa.push_out(Store::from_slice(&header)); } if end_header || end_stream { - kawa.push_delimiter() + // kawa.push_delimiter() } } } diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index c0a93840c..d0f14bea4 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -1,3 +1,5 @@ +use std::time::Instant; + use sozu_command::ready::Ready; use crate::{ @@ -42,6 +44,9 @@ impl ConnectionH1 { println_!("======= MUX H1 READABLE {:?}", self.position); self.timeout_container.reset(); let stream = &mut context.streams[self.stream]; + if stream.metrics.start.is_none() { + stream.metrics.start = Some(Instant::now()); + } let parts = stream.split(&self.position); let kawa = parts.rbuffer; let (size, status) = self.socket.socket_read(kawa.storage.space()); @@ -144,11 +149,11 @@ impl ConnectionH1 { let kawa = &mut stream.back; match kawa.detached.status_line { kawa::StatusLine::Response { code: 101, .. } => { - println!("============== HANDLE UPGRADE!"); + debug!("============== HANDLE UPGRADE!"); return MuxResult::Upgrade; } kawa::StatusLine::Response { code: 100, .. } => { - println!("============== HANDLE CONTINUE!"); + debug!("============== HANDLE CONTINUE!"); // after a 100 continue, we expect the client to continue with its request self.timeout_container.reset(); self.readiness.interest.insert(Ready::READABLE); @@ -156,7 +161,7 @@ impl ConnectionH1 { return MuxResult::Continue; } kawa::StatusLine::Response { code: 103, .. } => { - println!("============== HANDLE EARLY HINT!"); + debug!("============== HANDLE EARLY HINT!"); if let StreamState::Linked(token) = stream.state { // after a 103 early hints, we expect the backend to send its response endpoint @@ -181,9 +186,7 @@ impl ConnectionH1 { let old_state = std::mem::replace(&mut stream.state, StreamState::Unlinked); if stream.context.keep_alive_frontend { self.timeout_container.reset(); - // println!("{old_state:?} {:?}", self.readiness); if let StreamState::Linked(token) = old_state { - // println!("{:?}", endpoint.readiness(token)); endpoint.end_stream(token, self.stream, context); } self.readiness.interest.insert(Ready::READABLE); @@ -285,7 +288,7 @@ impl ConnectionH1 { } (false, false) => { // we do not have an answer, but the request is untouched so we can retry - println!("H1 RECONNECT"); + debug!("H1 RECONNECT"); stream.state = StreamState::Link; } (false, true) => unreachable!(), diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 2987af681..4ca6c2947 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -92,7 +92,7 @@ impl Prioriser { weight, } => { if stream_dependency.stream_id == stream_id { - println_!("STREAM CAN'T DEPEND ON ITSELF"); + error!("STREAM CAN'T DEPEND ON ITSELF"); true } else { false @@ -148,6 +148,10 @@ pub enum H2StreamId { } impl ConnectionH2 { + fn expect_header(&mut self) { + self.state = H2State::Header; + self.expect_read = Some((H2StreamId::Zero, 9)); + } pub fn readable(&mut self, context: &mut Context, endpoint: E) -> MuxResult where E: Endpoint, @@ -221,8 +225,7 @@ impl ConnectionH2 { let i = kawa.storage.data(); println_!("DISCARDING: {i:?}"); kawa.storage.clear(); - self.state = H2State::Header; - self.expect_read = Some((H2StreamId::Zero, 9)); + self.expect_header(); } (H2State::ClientPreface, Position::Server) => { let i = kawa.storage.data(); @@ -267,8 +270,8 @@ impl ConnectionH2 { let kawa = &mut self.zero; match serializer::gen_settings(kawa.storage.space(), &self.local_settings) { Ok((_, size)) => kawa.storage.fill(size), - Err(e) => { - println!("could not serialize SettingsFrame: {e:?}"); + Err(error) => { + error!("Could not serialize SettingsFrame: {:?}", error); return self.force_disconnect(); } }; @@ -309,7 +312,8 @@ impl ConnectionH2 { } else if let Some(global_stream_id) = self.streams.get(&stream_id) { let allowed_on_half_closed = header.frame_type == FrameType::WindowUpdate - || header.frame_type == FrameType::Priority; + || header.frame_type == FrameType::Priority + || header.frame_type == FrameType::RstStream; let stream = &context.streams[*global_stream_id]; println_!( "REQUESTING EXISTING STREAM {stream_id}: {}/{:?}", @@ -319,6 +323,10 @@ impl ConnectionH2 { if !allowed_on_half_closed && (stream.received_end_of_stream || !stream.state.is_open()) { + error!( + "CANNOT RECEIVE {:?} ON THIS STREAM {:?}", + header.frame_type, stream.state + ); return self.goaway(H2Error::StreamClosed); } if header.frame_type == FrameType::Data { @@ -332,19 +340,43 @@ impl ConnectionH2 { && stream_id % 2 == 1 && stream_id >= self.last_stream_id { - if context.streams.len() + if context.active_len() >= self.local_settings.settings_max_concurrent_streams as usize { + error!( + "MAX CONCURRENT STREAMS: {} {} {}", + self.local_settings.settings_max_concurrent_streams, + context.active_len(), + context.streams.len() + ); return self.goaway(H2Error::RefusedStream); } match self.create_stream(stream_id, context) { Some(_) => {} - None => return self.goaway(H2Error::InternalError), + None => { + error!("COULD NOT CREATE NEW STREAM"); + return self.goaway(H2Error::InternalError); + } } } else if header.frame_type != FrameType::Priority { - println_!( - "ONLY HEADERS AND PRIORITY CAN BE RECEIVED ON IDLE/CLOSED STREAMS" + error!( + "CANNOT RECEIVE {:?} FRAME ON IDLE/CLOSED STREAMS", + header.frame_type ); + if header.frame_type == FrameType::Data + && header.payload_len == 0 + && header.flags == 1 + { + error!( + "SKIPPED DATA: {} {} {} {}", + stream_id, + self.last_stream_id, + header.flags, + header.payload_len + ); + self.expect_header(); + return MuxResult::Continue; + } return self.goaway(H2Error::ProtocolError); } H2StreamId::Zero @@ -362,6 +394,7 @@ impl ConnectionH2 { } Err(error) => { let error = error_nom_to_h2(error); + error!("COULD NOT PARSE FRAME HEADER"); return self.goaway(error); } }; @@ -390,9 +423,13 @@ impl ConnectionH2 { } Err(error) => { let error = error_nom_to_h2(error); + error!("COULD NOT PARSE CONTINUATION HEADER"); return self.goaway(error); } - _ => return self.goaway(H2Error::ProtocolError), + other => { + error!("UNEXPECTED {:?} WHILE PARSING CONTINUATION HEADER", other); + return self.goaway(H2Error::ProtocolError); + } }; } (H2State::Frame(header), _) => { @@ -402,6 +439,7 @@ impl ConnectionH2 { Ok((_, frame)) => frame, Err(error) => { let error = error_nom_to_h2(error); + error!("COULD NOT PARSE FRAME BODY"); return self.goaway(error); } }; @@ -412,8 +450,7 @@ impl ConnectionH2 { kawa.storage.end = kawa.storage.head; } } - self.state = H2State::Header; - self.expect_read = Some((H2StreamId::Zero, 9)); + self.expect_header(); return self.handle_frame(frame, context, endpoint); } (H2State::ContinuationFrame(headers), _) => { @@ -421,8 +458,7 @@ impl ConnectionH2 { let i = kawa.storage.data(); println_!(" data: {i:?}"); let headers = headers.clone(); - self.state = H2State::Header; - self.expect_read = Some((H2StreamId::Zero, 9)); + self.expect_header(); return self.handle_frame(Frame::Headers(headers), context, endpoint); } } @@ -478,8 +514,8 @@ impl ConnectionH2 { kawa.storage.fill(pri.len()); match serializer::gen_settings(kawa.storage.space(), &self.local_settings) { Ok((_, size)) => kawa.storage.fill(size), - Err(e) => { - println!("could not serialize SettingsFrame: {e:?}"); + Err(error) => { + error!("Could not serialize SettingsFrame: {:?}", error); return self.force_disconnect(); } }; @@ -491,14 +527,13 @@ impl ConnectionH2 { (H2State::ClientSettings, Position::Client(..)) => { println_!("Sent preface and settings"); self.state = H2State::ServerSettings; - self.readiness.interest.remove(Ready::WRITABLE); self.expect_read = Some((H2StreamId::Zero, 9)); + self.readiness.interest.remove(Ready::WRITABLE); MuxResult::Continue } (H2State::ServerSettings, Position::Server) => { - self.state = H2State::Header; + self.expect_header(); self.readiness.interest.remove(Ready::WRITABLE); - self.expect_read = Some((H2StreamId::Zero, 9)); MuxResult::Continue } // Proxying states @@ -649,6 +684,7 @@ impl ConnectionH2 { self.expect_read = None; let kawa = &mut self.zero; kawa.storage.clear(); + error!("//////////////GOAWAY: {:?}", error); match serializer::gen_goaway(kawa.storage.space(), self.last_stream_id, error) { Ok((_, size)) => { @@ -658,8 +694,8 @@ impl ConnectionH2 { self.readiness.interest = Ready::WRITABLE | Ready::HUP | Ready::ERROR; MuxResult::Continue } - Err(e) => { - println!("could not serialize GoAwayFrame: {e:?}"); + Err(error) => { + error!("Could not serialize GoAwayFrame: {:?}", error); self.force_disconnect() } } @@ -704,10 +740,8 @@ impl ConnectionH2 { match frame { Frame::Data(data) => { let mut slice = data.payload; - let global_stream_id = match self.streams.get(&data.stream_id) { - Some(global_stream_id) => *global_stream_id, - None => panic!("stream error"), - }; + // can this fail? + let global_stream_id = *self.streams.get(&data.stream_id).unwrap(); let stream = &mut context.streams[global_stream_id]; let parts = stream.split(&self.position); let kawa = parts.rbuffer; @@ -739,8 +773,7 @@ impl ConnectionH2 { } Frame::Headers(headers) => { if !headers.end_headers { - // self.zero.storage.head = self.zero.storage.end; - println!("FRAGMENT: {:?}", self.zero.storage.data()); + debug!("FRAGMENT: {:?}", self.zero.storage.data()); self.state = H2State::ContinuationHeader(headers); return MuxResult::Continue; } @@ -781,6 +814,7 @@ impl ConnectionH2 { kawa.storage.clear(); if let Err((error, global)) = status { if global { + error!("GOT GLOBAL ERROR WHILE PROCESSING HEADERS"); return self.goaway(error); } else { return self.reset_stream(global_stream_id, context, endpoint, error); @@ -805,11 +839,12 @@ impl ConnectionH2 { if self.local_settings.settings_enable_push { todo!("forward the push") } else { + error!("DID NOT ALLOW PUSH"); return self.goaway(H2Error::ProtocolError); } } Position::Server => { - println_!("A client should not push promises"); + error!("INVALID PUSH FROM CLIENT"); return self.goaway(H2Error::ProtocolError); } }, @@ -826,6 +861,7 @@ impl ConnectionH2 { H2Error::ProtocolError, ); } else { + error!("INVALID PRIORITY RECEIVED ON INVALID STREAM"); return self.goaway(H2Error::ProtocolError); } } @@ -876,9 +912,10 @@ impl ConnectionH2 { 6 => { self.peer_settings.settings_max_header_list_size = v }, 8 => { self.peer_settings.settings_enable_connect_protocol = v == 1; is_error |= v > 1 }, 9 => { self.peer_settings.settings_no_rfc7540_priorities = v == 1; is_error |= v > 1 }, - other => println!("unknown setting_id: {other}, we MUST ignore this"), + other => warn!("Unknown setting_id: {}, we MUST ignore this", other), }; if is_error { + error!("INVALID SETTING"); return self.goaway(H2Error::ProtocolError); } } @@ -901,8 +938,8 @@ impl ConnectionH2 { let kawa = &mut self.zero; match serializer::gen_ping_acknolegment(kawa.storage.space(), &ping.payload) { Ok((_, size)) => kawa.storage.fill(size), - Err(e) => { - println!("could not serialize PingFrame: {e:?}"); + Err(error) => { + error!("Could not serialize PingFrame: {:?}", error); return self.force_disconnect(); } }; @@ -930,6 +967,7 @@ impl ConnectionH2 { } self.window = window; } else { + error!("INVALID WINDOW INCREMENT"); return self.goaway(H2Error::FlowControlError); } } else { @@ -968,21 +1006,15 @@ impl ConnectionH2 { return true; } let delta = value as i32 - self.peer_settings.settings_initial_window_size as i32; - println!( - "INITIAL_WINDOW_SIZE: {} -> {} => {}", - self.peer_settings.settings_initial_window_size, value, delta - ); let mut open_window = false; for (i, stream) in context.streams.iter_mut().enumerate() { - println!( - " - stream_{i}: {} -> {}", - stream.window, - stream.window + delta - ); open_window |= stream.window <= 0 && stream.window + delta > 0; stream.window += delta; } - println_!("UPDATE INIT WINDOW: {open_window} {:?}", self.readiness); + println_!( + "UPDATE INIT WINDOW: {delta} {open_window} {:?}", + self.readiness + ); if open_window { self.readiness.interest.insert(Ready::WRITABLE); } @@ -1090,7 +1122,7 @@ impl ConnectionH2 { } (false, false) => { // we do not have an answer, but the request is untouched so we can retry - println!("H2 RECONNECT"); + debug!("H2 RECONNECT"); stream.state = StreamState::Link } } diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 90f3f9d44..6dce6aad1 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -52,9 +52,9 @@ pub use crate::protocol::mux::{ #[macro_export] macro_rules! println_ { ($($t:expr),*) => { - print!("{}:{} ", file!(), line!()); - println!($($t),*) - // $(let _ = &$t;)* + // print!("{}:{} ", file!(), line!()); + // println!($($t),*) + $(let _ = &$t;)* }; } fn debug_kawa(_kawa: &GenericHttpStream) { @@ -772,6 +772,13 @@ impl Context { } } + pub fn active_len(&self) -> usize { + self.streams + .iter() + .filter(|s| !matches!(s.state, StreamState::Recycle)) + .count() + } + pub fn create_stream(&mut self, request_id: Ulid, window: u32) -> Option { let listener = self.listener.borrow(); let http_context = HttpContext::new( @@ -786,12 +793,15 @@ impl Context { println_!("Reuse stream: {stream_id}"); stream.state = StreamState::Idle; stream.attempts = 0; + stream.received_end_of_stream = false; stream.window = window as i32; stream.context = http_context; stream.back.clear(); stream.back.storage.clear(); stream.front.clear(); stream.front.storage.clear(); + stream.metrics.reset(); + stream.metrics.start = Some(Instant::now()); return Some(stream_id); } } @@ -921,8 +931,8 @@ impl Router { let token = if let Some(token) = reuse_token { println_!("reused backend: {:#?}", self.backends.get(&token).unwrap()); - stream.metrics.backend_start(); - stream.metrics.backend_connected(); + // stream.metrics.backend_start(); + // stream.metrics.backend_connected(); token } else { let (mut socket, backend) = self.backend_from_request( @@ -997,7 +1007,7 @@ impl Router { Err(cluster_error) => { // we are past kawa parsing if it succeeded this can't fail // if the request was malformed it was caught by kawa and we sent a 400 - panic!("{cluster_error}"); + unreachable!("{cluster_error}"); } }; @@ -1006,7 +1016,7 @@ impl Router { let route = match route_result { Ok(route) => route, Err(frontend_error) => { - println!("{}", frontend_error); + println_!("{}", frontend_error); // self.set_answer(DefaultAnswerStatus::Answer404, None); return Err(RetrieveClusterError::RetrieveFrontend(frontend_error)); } @@ -1015,7 +1025,7 @@ impl Router { let cluster_id = match route { Route::Cluster(id) => id, Route::Deny => { - println!("Route::Deny"); + println_!("Route::Deny"); // self.set_answer(DefaultAnswerStatus::Answer401, None); return Err(RetrieveClusterError::UnauthorizedRoute); } @@ -1040,7 +1050,7 @@ impl Router { proxy, ) .map_err(|backend_error| { - println!("{backend_error}"); + println_!("{backend_error}"); // self.set_answer(DefaultAnswerStatus::Answer503, None); BackendConnectionError::Backend(backend_error) })?; @@ -1137,7 +1147,6 @@ impl {readiness:?}"); let dead = readiness.filter_interest().is_hup() || readiness.filter_interest().is_error(); if dead { @@ -1294,8 +1303,6 @@ impl(&mut self, proxy: Rc>, _metrics: &mut SessionMetrics) { + println!("MUX CLOSE"); println_!("FRONTEND: {:#?}", self.frontend); println_!("BACKENDS: {:#?}", self.router.backends); @@ -1546,8 +1556,6 @@ impl unreachable!(), } } + return; let s = match &mut self.frontend { Connection::H1(c) => &mut c.socket, Connection::H2(c) => &mut c.socket, diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index 95ca0a5db..d138be2e5 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -207,7 +207,7 @@ pub fn frame_header(input: &[u8], max_frame_size: u32) -> IResult<&[u8], FrameHe FrameType::WindowUpdate => true, }; if !valid_stream_id { - println!("invalid stream_id: {stream_id}"); + error!("invalid stream_id: {}", stream_id); return Err(Err::Failure(ParserError::new_h2(i, H2Error::ProtocolError))); } @@ -223,7 +223,7 @@ pub fn frame_header(input: &[u8], max_frame_size: u32) -> IResult<&[u8], FrameHe } fn convert_frame_type(t: u8) -> Option { - info!("got frame type: {}", t); + debug!("got frame type: {}", t); match t { 0 => Some(FrameType::Data), 1 => Some(FrameType::Headers), @@ -353,7 +353,6 @@ pub fn data_frame<'a>( header: &FrameHeader, ) -> IResult<&'a [u8], Frame, ParserError<'a>> { let (remaining, i) = take(header.payload_len)(input)?; - println!("{i:?}"); let (i, pad_length) = if header.flags & 0x8 != 0 { let (i, pad_length) = be_u8(i)?; diff --git a/lib/src/protocol/mux/pkawa.rs b/lib/src/protocol/mux/pkawa.rs index f94cf21e8..6cbf9dc3b 100644 --- a/lib/src/protocol/mux/pkawa.rs +++ b/lib/src/protocol/mux/pkawa.rs @@ -33,6 +33,21 @@ where return handle_trailer(kawa, input, end_stream, decoder); } kawa.push_block(Block::StatusLine); + // kawa.detached.status_line = match kawa.kind { + // Kind::Request => StatusLine::Request { + // version: Version::V20, + // method: Store::Static(b"GET"), + // uri: Store::Static(b"/"), + // authority: Store::Static(b"lolcatho.st:8443"), + // path: Store::Static(b"/"), + // }, + // Kind::Response => StatusLine::Response { + // version: Version::V20, + // code: 200, + // status: Store::Static(b"200"), + // reason: Store::Static(b"FromH2"), + // }, + // }; kawa.detached.status_line = match kawa.kind { Kind::Request => { let mut method = Store::Empty; @@ -87,12 +102,15 @@ where invalid_headers = true; } } else if compare_no_case(&k, b"priority") { - todo!("decode priority"); + // todo!("decode priority"); + warn!("DECODE PRIORITY: {}", unsafe { + std::str::from_utf8_unchecked(v.as_ref()) + }); prioriser.push_priority( stream_id, PriorityPart::Rfc9218 { - urgency: todo!(), - incremental: todo!(), + urgency: 0, + incremental: false, }, ); } @@ -105,7 +123,7 @@ where } }); if let Err(error) = decode_status { - println!("INVALID FRAGMENT: {error:?}"); + error!("INVALID FRAGMENT: {:?}", error); return Err((H2Error::CompressionError, true)); } if invalid_headers @@ -114,7 +132,7 @@ where || path.len() == 0 || scheme.len() == 0 { - println!("INVALID HEADERS"); + error!("INVALID HEADERS"); return Err((H2Error::ProtocolError, false)); } // uri is only used by H1 statusline, in most cases it only consists of the path @@ -177,11 +195,11 @@ where } }); if let Err(error) = decode_status { - println!("INVALID FRAGMENT: {error:?}"); + error!("INVALID FRAGMENT: {:?}", error); return Err((H2Error::CompressionError, true)); } if invalid_headers || status.len() == 0 { - println!("INVALID HEADERS"); + error!("INVALID HEADERS"); return Err((H2Error::ProtocolError, false)); } StatusLine::Response { @@ -195,7 +213,7 @@ where // everything has been parsed kawa.storage.head = kawa.storage.end; - println!( + debug!( "index: {}/{}/{}", kawa.storage.start, kawa.storage.head, kawa.storage.end ); diff --git a/lib/src/socket.rs b/lib/src/socket.rs index f19cbf9fb..44d3f614d 100644 --- a/lib/src/socket.rs +++ b/lib/src/socket.rs @@ -193,6 +193,31 @@ impl SocketHandler for FrontRustls { incr!("rustls.read.infinite_loop.error"); } + while !self.session.wants_read() { + match self.session.reader().read(&mut buf[size..]) { + Ok(0) => break, + Ok(sz) => { + size += sz; + } + Err(e) => match e.kind() { + ErrorKind::WouldBlock => { + break; + } + ErrorKind::ConnectionReset + | ErrorKind::ConnectionAborted + | ErrorKind::BrokenPipe => { + is_closed = true; + break; + } + _ => { + error!("could not read data from TLS stream: {:?}", e); + is_error = true; + break; + } + }, + } + } + if size == buf.len() { break; } @@ -233,31 +258,6 @@ impl SocketHandler for FrontRustls { is_error = true; break; } - - while !self.session.wants_read() { - match self.session.reader().read(&mut buf[size..]) { - Ok(0) => break, - Ok(sz) => { - size += sz; - } - Err(e) => match e.kind() { - ErrorKind::WouldBlock => { - break; - } - ErrorKind::ConnectionReset - | ErrorKind::ConnectionAborted - | ErrorKind::BrokenPipe => { - is_closed = true; - break; - } - _ => { - error!("could not read data from TLS stream: {:?}", e); - is_error = true; - break; - } - }, - } - } } if is_error { From 8a2a71272268b704aa65b8c1bac9ae717ae0a46c Mon Sep 17 00:00:00 2001 From: Emmanuel Bosquet Date: Fri, 19 Jul 2024 10:21:50 +0200 Subject: [PATCH 41/44] release: v1.1.0-rc.0 --- Cargo.lock | 34 +++++++++++++++++----------------- bin/Cargo.toml | 6 +++--- command/Cargo.toml | 2 +- e2e/Cargo.toml | 4 ++-- lib/Cargo.toml | 4 ++-- os-build/archlinux/PKGBUILD | 2 +- os-build/linux-rpm/sozu.spec | 4 ++++ 7 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98707607a..96bd674ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,9 +134,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-rs" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a47f2fb521b70c11ce7369a6c5fa4bd6af7e5d62ec06303875bafe7c6ba245" +checksum = "4ae74d9bd0a7530e8afd1770739ad34b36838829d6ad61818f9230f683f5ad77" dependencies = [ "aws-lc-sys", "mirai-annotations", @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2927c7af777b460b7ccd95f8b67acd7b4c04ec8896bf0c8e80ba30523cffc057" +checksum = "2e89b6941c2d1a7045538884d6e760ccfffdf8e1ffc2613d8efa74305e1f3752" dependencies = [ "bindgen", "cc", @@ -1599,9 +1599,9 @@ dependencies = [ [[package]] name = "sdd" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb0dde0ccd15e337a3cf738a9a38115c6d8e74795d074e73973dad3d229a897" +checksum = "85f05a494052771fc5bd0619742363b5e24e5ad72ab3111ec2e27925b8edc5f3" [[package]] name = "serde" @@ -1712,7 +1712,7 @@ dependencies = [ [[package]] name = "sozu" -version = "1.0.4" +version = "1.1.0-rc.0" dependencies = [ "clap", "jemallocator", @@ -1735,7 +1735,7 @@ dependencies = [ [[package]] name = "sozu-command-lib" -version = "1.0.4" +version = "1.1.0-rc.0" dependencies = [ "hex", "libc", @@ -1779,7 +1779,7 @@ dependencies = [ [[package]] name = "sozu-lib" -version = "1.0.4" +version = "1.1.0-rc.0" dependencies = [ "anyhow", "cookie-factory", @@ -1898,18 +1898,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -2001,9 +2001,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" dependencies = [ "serde", "serde_spanned", @@ -2022,9 +2022,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.15" +version = "0.22.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" dependencies = [ "indexmap", "serde", diff --git a/bin/Cargo.toml b/bin/Cargo.toml index fa504acba..7cc116c4e 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu" homepage = "https://sozu.io" -version = "1.0.4" +version = "1.1.0-rc.0" license = "AGPL-3.0" authors = [ "Geoffroy Couprie ", @@ -34,8 +34,8 @@ tempfile = "^3.10.1" termion = "^4.0.0" thiserror = "^1.0.61" -sozu-command-lib = { path = "../command", version = "^1.0.4" } -sozu-lib = { path = "../lib", version = "^1.0.4" } +sozu-command-lib = { path = "../command", version = "^1.1.0-rc.0" } +sozu-lib = { path = "../lib", version = "1.1.0-rc.0" } [target.'cfg(target_os="linux")'.dependencies] num_cpus = "^1.16.0" diff --git a/command/Cargo.toml b/command/Cargo.toml index 471223d45..6cb60dd8d 100644 --- a/command/Cargo.toml +++ b/command/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu-command-lib" homepage = "https://sozu.io" -version = "1.0.4" +version = "1.1.0-rc.0" license = "LGPL-3.0" authors = [ "Geoffroy Couprie ", diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index eb90b5cd2..64533f7ef 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -14,5 +14,5 @@ rustls = { version = "^0.21.10", features = ["dangerous_configuration"] } time = "^0.3.36" tokio = { version = "1.37.0", features = ["net", "rt-multi-thread"] } -sozu-command-lib = { path = "../command", version = "^1.0.4" } -sozu-lib = { path = "../lib", version = "^1.0.4" } +sozu-command-lib = { path = "../command", version = "^1.1.0-rc.0" } +sozu-lib = { path = "../lib", version = "^1.1.0-rc.0" } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 80e5e4de8..355dd94c8 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu-lib" homepage = "https://sozu.io" -version = "1.0.4" +version = "1.1.0-rc.0" license = "AGPL-3.0" authors = [ "Clément Delafargue ", @@ -52,7 +52,7 @@ thiserror = "^1.0.61" time = "^0.3.36" once_cell = "1.19.0" -sozu-command-lib = { path = "../command", version = "^1.0.4" } +sozu-command-lib = { path = "../command", version = "1.1.0-rc.0" } [dev-dependencies] quickcheck = "^1.0.3" diff --git a/os-build/archlinux/PKGBUILD b/os-build/archlinux/PKGBUILD index 1e232d8e7..df4299fb1 100644 --- a/os-build/archlinux/PKGBUILD +++ b/os-build/archlinux/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Jan-Erik Rediger pkgname=sozu-git -pkgver=1.0.4 +pkgver=1.1.0-rc.0 pkgrel=1 pkgdesc="HTTP reverse proxy, configurable at runtime, fast and safe, built in Rust" arch=('i686' 'x86_64') diff --git a/os-build/linux-rpm/sozu.spec b/os-build/linux-rpm/sozu.spec index c833926d2..7d849ad97 100755 --- a/os-build/linux-rpm/sozu.spec +++ b/os-build/linux-rpm/sozu.spec @@ -6,7 +6,11 @@ Summary: A lightweight, fast, always-up reverse proxy server. Name: sozu +<<<<<<< HEAD Version: 1.0.4 +======= +Version: 1.1.0-rc.0 +>>>>>>> a85689c1 (release: v1.1.0-rc.0) Release: 1%{?dist} Epoch: 1 License: AGPL-3.0 From 6507da34e45914620a2d049b8e6375ef57e62126 Mon Sep 17 00:00:00 2001 From: Emmanuel Bosquet Date: Thu, 25 Jul 2024 14:42:51 +0200 Subject: [PATCH 42/44] release: v1.1.0-rc.1 --- Cargo.lock | 6 +++--- bin/Cargo.toml | 6 +++--- command/Cargo.toml | 2 +- e2e/Cargo.toml | 4 ++-- lib/Cargo.toml | 4 ++-- os-build/archlinux/PKGBUILD | 2 +- os-build/linux-rpm/sozu.spec | 6 +----- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96bd674ff..b3fadcb12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1712,7 +1712,7 @@ dependencies = [ [[package]] name = "sozu" -version = "1.1.0-rc.0" +version = "1.1.0-rc.1" dependencies = [ "clap", "jemallocator", @@ -1735,7 +1735,7 @@ dependencies = [ [[package]] name = "sozu-command-lib" -version = "1.1.0-rc.0" +version = "1.1.0-rc.1" dependencies = [ "hex", "libc", @@ -1779,7 +1779,7 @@ dependencies = [ [[package]] name = "sozu-lib" -version = "1.1.0-rc.0" +version = "1.1.0-rc.1" dependencies = [ "anyhow", "cookie-factory", diff --git a/bin/Cargo.toml b/bin/Cargo.toml index 7cc116c4e..f13b22860 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu" homepage = "https://sozu.io" -version = "1.1.0-rc.0" +version = "1.1.0-rc.1" license = "AGPL-3.0" authors = [ "Geoffroy Couprie ", @@ -34,8 +34,8 @@ tempfile = "^3.10.1" termion = "^4.0.0" thiserror = "^1.0.61" -sozu-command-lib = { path = "../command", version = "^1.1.0-rc.0" } -sozu-lib = { path = "../lib", version = "1.1.0-rc.0" } +sozu-command-lib = { path = "../command", version = "^1.1.0-rc.1" } +sozu-lib = { path = "../lib", version = "1.1.0-rc.1" } [target.'cfg(target_os="linux")'.dependencies] num_cpus = "^1.16.0" diff --git a/command/Cargo.toml b/command/Cargo.toml index 6cb60dd8d..4968d49d6 100644 --- a/command/Cargo.toml +++ b/command/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu-command-lib" homepage = "https://sozu.io" -version = "1.1.0-rc.0" +version = "1.1.0-rc.1" license = "LGPL-3.0" authors = [ "Geoffroy Couprie ", diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index 64533f7ef..4f9e7fe84 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -14,5 +14,5 @@ rustls = { version = "^0.21.10", features = ["dangerous_configuration"] } time = "^0.3.36" tokio = { version = "1.37.0", features = ["net", "rt-multi-thread"] } -sozu-command-lib = { path = "../command", version = "^1.1.0-rc.0" } -sozu-lib = { path = "../lib", version = "^1.1.0-rc.0" } +sozu-command-lib = { path = "../command", version = "^1.1.0-rc.1" } +sozu-lib = { path = "../lib", version = "^1.1.0-rc.1" } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 355dd94c8..bdeb5b400 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu-lib" homepage = "https://sozu.io" -version = "1.1.0-rc.0" +version = "1.1.0-rc.1" license = "AGPL-3.0" authors = [ "Clément Delafargue ", @@ -52,7 +52,7 @@ thiserror = "^1.0.61" time = "^0.3.36" once_cell = "1.19.0" -sozu-command-lib = { path = "../command", version = "1.1.0-rc.0" } +sozu-command-lib = { path = "../command", version = "1.1.0-rc.1" } [dev-dependencies] quickcheck = "^1.0.3" diff --git a/os-build/archlinux/PKGBUILD b/os-build/archlinux/PKGBUILD index df4299fb1..66ce7d94b 100644 --- a/os-build/archlinux/PKGBUILD +++ b/os-build/archlinux/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Jan-Erik Rediger pkgname=sozu-git -pkgver=1.1.0-rc.0 +pkgver=1.1.0-rc.1 pkgrel=1 pkgdesc="HTTP reverse proxy, configurable at runtime, fast and safe, built in Rust" arch=('i686' 'x86_64') diff --git a/os-build/linux-rpm/sozu.spec b/os-build/linux-rpm/sozu.spec index 7d849ad97..d2851483c 100755 --- a/os-build/linux-rpm/sozu.spec +++ b/os-build/linux-rpm/sozu.spec @@ -6,11 +6,7 @@ Summary: A lightweight, fast, always-up reverse proxy server. Name: sozu -<<<<<<< HEAD -Version: 1.0.4 -======= -Version: 1.1.0-rc.0 ->>>>>>> a85689c1 (release: v1.1.0-rc.0) +Version: 1.1.0-rc.1 Release: 1%{?dist} Epoch: 1 License: AGPL-3.0 From f7af2c4dd81d1ee3f801afc4232e31b105fa22df Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 16 Oct 2024 11:53:15 +0200 Subject: [PATCH 43/44] Various enhancements for test and release - Add logs and access logs for informationals - Add metrics - Fix panic on malformed and Http/1.0 requests - Some clippy fixes Signed-off-by: Eloi DEMOLIS --- bin/src/cli.rs | 12 ++- bin/src/ctl/request_builder.rs | 7 +- lib/src/http.rs | 13 ++- lib/src/https.rs | 19 +++-- lib/src/protocol/mux/h1.rs | 57 +++++++++++-- lib/src/protocol/mux/h2.rs | 106 +++++++++++------------ lib/src/protocol/mux/mod.rs | 131 +++++++++++++++++++---------- lib/src/protocol/mux/parser.rs | 2 +- lib/src/protocol/mux/serializer.rs | 16 ++-- 9 files changed, 234 insertions(+), 129 deletions(-) diff --git a/bin/src/cli.rs b/bin/src/cli.rs index ea4e8a96e..74355d9bc 100644 --- a/bin/src/cli.rs +++ b/bin/src/cli.rs @@ -180,7 +180,10 @@ pub enum SubCmd { #[clap(subcommand)] cmd: ConfigCmd, }, - #[clap(name = "events", about = "receive sozu events about the status of backends")] + #[clap( + name = "events", + about = "receive sozu events about the status of backends" + )] Events, } @@ -296,6 +299,11 @@ pub enum ClusterCmd { help = "Configures the load balancing policy. Possible values are 'roundrobin', 'random' or 'leastconnections'" )] load_balancing_policy: LoadBalancingAlgorithms, + #[clap( + long = "http2", + help = "the backends of this cluster use http2 prio-knowledge" + )] + h2: bool, }, } @@ -422,8 +430,6 @@ pub enum HttpFrontendCmd { method: Option, #[clap(long = "tags", help = "Specify tag (key-value pair) to apply on front-end (example: 'key=value, other-key=other-value')", value_parser = parse_tags)] tags: Option>, - #[clap(help = "the frontend uses http2 with prio-knowledge")] - h2: Option, }, #[clap(name = "remove")] Remove { diff --git a/bin/src/ctl/request_builder.rs b/bin/src/ctl/request_builder.rs index 0f477cb49..2d8acea14 100644 --- a/bin/src/ctl/request_builder.rs +++ b/bin/src/ctl/request_builder.rs @@ -150,6 +150,7 @@ impl CommandManager { send_proxy, expect_proxy, load_balancing_policy, + h2, } => { let proxy_protocol = match (send_proxy, expect_proxy) { (true, true) => Some(ProxyProtocolConfig::RelayHeader), @@ -164,7 +165,9 @@ impl CommandManager { https_redirect, proxy_protocol: proxy_protocol.map(|pp| pp as i32), load_balancing: load_balancing_policy as i32, - ..Default::default() + http2: h2, + load_metric: None, + answer_503: None, }) .into(), ) @@ -238,7 +241,6 @@ impl CommandManager { method, cluster_id: route, tags, - h2, } => self.send_request( RequestType::AddHttpFrontend(RequestHttpFrontend { cluster_id: route.into(), @@ -287,7 +289,6 @@ impl CommandManager { method, cluster_id: route, tags, - h2, } => self.send_request( RequestType::AddHttpsFrontend(RequestHttpFrontend { cluster_id: route.into(), diff --git a/lib/src/http.rs b/lib/src/http.rs index 63fea8fb4..c4e7c4903 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -218,7 +218,10 @@ impl HttpSession { Some(session_address), public_address, ); - context.create_stream(expect.request_id, 1 << 16)?; + if context.create_stream(expect.request_id, 1 << 16).is_none() { + error!("HTTP expect upgrade failed: could not create stream"); + return None; + } let mut mux = Mux { configured_frontend_timeout: self.configured_frontend_timeout, frontend_token: self.frontend_token, @@ -232,7 +235,13 @@ impl HttpSession { gauge_add!("protocol.http", 1); Some(HttpStateMachine::Mux(mux)) } - _ => None, + _ => { + warn!( + "HTTP expect upgrade failed: bad header {:?}", + expect.addresses + ); + None + } } } diff --git a/lib/src/https.rs b/lib/src/https.rs index f8a134b8b..49a8980da 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -248,7 +248,10 @@ impl HttpsSession { if !expect.container_frontend_timeout.cancel() { error!("failed to cancel request timeout on expect upgrade phase for 'expect proxy protocol with AF_UNSPEC address'"); } - + warn!( + "HTTP expect upgrade failed: bad header {:?}", + expect.addresses + ); None } @@ -299,14 +302,18 @@ impl HttpsSession { ); let mut frontend = match alpn { AlpnProtocol::Http11 => { + incr!("http.alpn.http11"); context.create_stream(handshake.request_id, 1 << 16)?; mux::Connection::new_h1_server(front_stream, handshake.container_frontend_timeout) } - AlpnProtocol::H2 => mux::Connection::new_h2_server( - front_stream, - self.pool.clone(), - handshake.container_frontend_timeout, - )?, + AlpnProtocol::H2 => { + incr!("http.alpn.h2"); + mux::Connection::new_h2_server( + front_stream, + self.pool.clone(), + handshake.container_frontend_timeout, + )? + } }; frontend.readiness_mut().event = handshake.frontend_readiness.event; diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index d0f14bea4..ec2b54502 100644 --- a/lib/src/protocol/mux/h1.rs +++ b/lib/src/protocol/mux/h1.rs @@ -90,18 +90,41 @@ impl ConnectionH1 { self.readiness.interest.remove(Ready::READABLE); } if kawa.is_main_phase() { - if let StreamState::Linked(token) = stream.state { - endpoint - .readiness_mut(token) - .interest - .insert(Ready::WRITABLE) - } if !was_main_phase && self.position.is_server() { + if parts.context.method.is_none() + || parts.context.authority.is_none() + || parts.context.path.is_none() + { + if let kawa::StatusLine::Request { + version: kawa::Version::V10, + .. + } = kawa.detached.status_line + { + error!( + "Unexpected malformed request: HTTP/1.0 from {:?} with {:?} {:?} {:?}", + parts.context.session_address, + parts.context.method, + parts.context.authority, + parts.context.path + ); + } else { + error!("Unexpected malformed request"); + kawa::debug_kawa(kawa); + } + set_default_answer(stream, &mut self.readiness, 400); + return MuxResult::Continue; + } self.requests += 1; println_!("REQUESTS: {}", self.requests); gauge_add!("http.active_requests", 1); stream.state = StreamState::Link } + if let StreamState::Linked(token) = stream.state { + endpoint + .readiness_mut(token) + .interest + .insert(Ready::WRITABLE) + } }; MuxResult::Continue } @@ -150,6 +173,11 @@ impl ConnectionH1 { match kawa.detached.status_line { kawa::StatusLine::Response { code: 101, .. } => { debug!("============== HANDLE UPGRADE!"); + stream.generate_access_log( + false, + Some(String::from("H1::Upgrade")), + context.listener.clone(), + ); return MuxResult::Upgrade; } kawa::StatusLine::Response { code: 100, .. } => { @@ -158,6 +186,11 @@ impl ConnectionH1 { self.timeout_container.reset(); self.readiness.interest.insert(Ready::READABLE); kawa.clear(); + stream.generate_access_log( + false, + Some(String::from("H1::Continue")), + context.listener.clone(), + ); return MuxResult::Continue; } kawa::StatusLine::Response { code: 103, .. } => { @@ -169,14 +202,24 @@ impl ConnectionH1 { .interest .insert(Ready::READABLE); kawa.clear(); + stream.generate_access_log( + false, + Some(String::from("H1::EarlyHint+Error")), + context.listener.clone(), + ); return MuxResult::Continue; } else { + stream.generate_access_log( + false, + Some(String::from("H1::EarlyHint")), + context.listener.clone(), + ); return MuxResult::CloseSession; } } _ => {} } - // ACCESS LOG + incr!("http.e2e.http11"); stream.generate_access_log( false, Some(String::from("H1")), diff --git a/lib/src/protocol/mux/h2.rs b/lib/src/protocol/mux/h2.rs index 4ca6c2947..90f6dd19c 100644 --- a/lib/src/protocol/mux/h2.rs +++ b/lib/src/protocol/mux/h2.rs @@ -26,12 +26,12 @@ fn error_nom_to_h2(error: nom::Err) -> H2Error { nom::Err::Error(parser::ParserError { kind: parser::ParserErrorKind::H2(e), .. - }) => return e, + }) => e, nom::Err::Failure(parser::ParserError { kind: parser::ParserErrorKind::H2(e), .. - }) => return e, - _ => return H2Error::ProtocolError, + }) => e, + _ => H2Error::ProtocolError, } } @@ -78,12 +78,10 @@ impl Default for H2Settings { } } +#[derive(Default)] pub struct Prioriser {} impl Prioriser { - pub fn new() -> Self { - Self {} - } pub fn push_priority(&mut self, stream_id: StreamId, priority: parser::PriorityPart) -> bool { println_!("PRIORITY REQUEST FOR {stream_id}: {priority:?}"); match priority { @@ -162,7 +160,7 @@ impl ConnectionH2 { let (stream_id, kawa) = if let Some((stream_id, amount)) = self.expect_read { let kawa = match stream_id { H2StreamId::Zero => &mut self.zero, - H2StreamId::Other(stream_id, global_stream_id) => { + H2StreamId::Other(_, global_stream_id) => { context.streams[global_stream_id] .split(&self.position) .rbuffer @@ -186,23 +184,21 @@ impl ConnectionH2 { } if update_readiness_after_read(size, status, &mut self.readiness) { return MuxResult::Continue; + } else if size == amount { + self.expect_read = None; } else { - if size == amount { - self.expect_read = None; - } else { - self.expect_read = Some((stream_id, amount - size)); - match (&self.state, &self.position) { - (H2State::ClientPreface, Position::Server) => { - let i = kawa.storage.data(); - if !b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".starts_with(i) { - println_!("EARLY INVALID PREFACE: {i:?}"); - return self.force_disconnect(); - } + self.expect_read = Some((stream_id, amount - size)); + match (&self.state, &self.position) { + (H2State::ClientPreface, Position::Server) => { + let i = kawa.storage.data(); + if !b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".starts_with(i) { + println_!("EARLY INVALID PREFACE: {i:?}"); + return self.force_disconnect(); } - _ => {} } - return MuxResult::Continue; + _ => {} } + return MuxResult::Continue; } } else { self.expect_read = None; @@ -359,24 +355,24 @@ impl ConnectionH2 { } } } else if header.frame_type != FrameType::Priority { - error!( - "CANNOT RECEIVE {:?} FRAME ON IDLE/CLOSED STREAMS", - header.frame_type - ); if header.frame_type == FrameType::Data && header.payload_len == 0 && header.flags == 1 { - error!( - "SKIPPED DATA: {} {} {} {}", - stream_id, - self.last_stream_id, - header.flags, - header.payload_len - ); + // error!( + // "SKIPPED DATA: {} {} {} {}", + // stream_id, + // self.last_stream_id, + // header.flags, + // header.payload_len + // ); self.expect_header(); return MuxResult::Continue; } + error!( + "CANNOT RECEIVE {:?} FRAME ON IDLE/CLOSED STREAMS", + header.frame_type + ); return self.goaway(H2Error::ProtocolError); } H2StreamId::Zero @@ -456,7 +452,7 @@ impl ConnectionH2 { (H2State::ContinuationFrame(headers), _) => { kawa.storage.head = kawa.storage.end; let i = kawa.storage.data(); - println_!(" data: {i:?}"); + println_!(" data: {:?}", i); let headers = headers.clone(); self.expect_header(); return self.handle_frame(Frame::Headers(headers), context, endpoint); @@ -581,7 +577,7 @@ impl ConnectionH2 { Position::Server => { // mark stream as reusable println_!("Recycle stream: {global_stream_id}"); - // ACCESS LOG + incr!("http.e2e.h2"); stream.generate_access_log( false, Some(String::from("H2::SplitFrame")), @@ -648,9 +644,12 @@ impl ConnectionH2 { match self.position { Position::Client(..) => {} Position::Server => { + // Handle 1xx, this code should probably be merged with the h2 SplitFrame case and h1 nominal case + // to avoid code duplication + // mark stream as reusable println_!("Recycle1 stream: {global_stream_id}"); - // ACCESS LOG + incr!("http.e2e.h2"); stream.generate_access_log( false, Some(String::from("H2::WholeFrame")), @@ -834,7 +833,7 @@ impl ConnectionH2 { stream.state = StreamState::Link; } } - Frame::PushPromise(push_promise) => match self.position { + Frame::PushPromise(_push_promise) => match self.position { Position::Client(..) => { if self.local_settings.settings_enable_push { todo!("forward the push") @@ -884,7 +883,6 @@ impl ConnectionH2 { // This is a special case, normally, all stream are terminated by the server // when the last byte of the response is written. Here, the reset is requested // on the server endpoint and immediately terminates, shortcutting the other path - // ACCESS LOG stream.generate_access_log( true, Some(String::from("H2::ResetFrame")), @@ -903,7 +901,7 @@ impl ConnectionH2 { let v = setting.value; let mut is_error = false; #[rustfmt::skip] - let _ = match setting.identifier { + match setting.identifier { 1 => { self.peer_settings.settings_header_table_size = v }, 2 => { self.peer_settings.settings_enable_push = v == 1; is_error |= v > 1 }, 3 => { self.peer_settings.settings_max_concurrent_streams = v }, @@ -970,27 +968,23 @@ impl ConnectionH2 { error!("INVALID WINDOW INCREMENT"); return self.goaway(H2Error::FlowControlError); } - } else { - if let Some(global_stream_id) = self.streams.get(&stream_id) { - let stream = &mut context.streams[*global_stream_id]; - if let Some(window) = stream.window.checked_add(increment) { - if stream.window <= 0 && window > 0 { - self.readiness.interest.insert(Ready::WRITABLE); - } - stream.window = window; - } else { - return self.reset_stream( - *global_stream_id, - context, - endpoint, - H2Error::FlowControlError, - ); + } else if let Some(global_stream_id) = self.streams.get(&stream_id) { + let stream = &mut context.streams[*global_stream_id]; + if let Some(window) = stream.window.checked_add(increment) { + if stream.window <= 0 && window > 0 { + self.readiness.interest.insert(Ready::WRITABLE); } + stream.window = window; } else { - println_!( - "Ignoring window update on closed stream {stream_id}: {increment}" + return self.reset_stream( + *global_stream_id, + context, + endpoint, + H2Error::FlowControlError, ); } + } else { + println_!("Ignoring window update on closed stream {stream_id}: {increment}"); }; } Frame::Continuation(_) => unreachable!(), @@ -1007,7 +1001,7 @@ impl ConnectionH2 { } let delta = value as i32 - self.peer_settings.settings_initial_window_size as i32; let mut open_window = false; - for (i, stream) in context.streams.iter_mut().enumerate() { + for stream in context.streams.iter_mut() { open_window |= stream.window <= 0 && stream.window + delta > 0; stream.window += delta; } @@ -1080,7 +1074,7 @@ impl ConnectionH2 { L: ListenerHandler + L7ListenerHandler, { let stream_context = &mut context.streams[stream].context; - println_!("end H2 stream {stream}: {stream_context:#?}"); + println_!("end H2 stream {}: {:#?}", stream, stream_context); match self.position { Position::Client(..) => { for (stream_id, global_stream_id) in &self.streams { diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 6dce6aad1..377ee1a27 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -120,17 +120,39 @@ pub fn terminate_default_answer(kawa: &mut kawa::Kawa, clo /// Replace the content of the kawa message with a default Sozu answer for a given status code fn set_default_answer(stream: &mut Stream, readiness: &mut Readiness, code: u16) { + let context = &mut stream.context; let kawa = &mut stream.back; kawa.clear(); kawa.storage.clear(); + let key = match code { + 301 => "http.301.redirection", + 400 => "http.400.errors", + 401 => "http.401.errors", + 404 => "http.404.errors", + 408 => "http.408.errors", + 413 => "http.413.errors", + 502 => "http.502.errors", + 503 => "http.503.errors", + 504 => "http.504.errors", + 507 => "http.507.errors", + _ => "http.other.errors", + }; + // if context.cluster_id.is_some() { + // incr!(key); + // } + incr!( + key, + context.cluster_id.as_deref(), + context.backend_id.as_deref() + ); if code == 301 { - let host = stream.context.authority.as_deref().unwrap(); - let uri = stream.context.path.as_deref().unwrap(); + let host = context.authority.as_deref().unwrap(); + let uri = context.path.as_deref().unwrap(); fill_default_301_answer(kawa, host, uri); } else { fill_default_answer(kawa, code); } - stream.context.status = Some(code); + context.status = Some(code); stream.state = StreamState::Unlinked; readiness.interest.insert(Ready::WRITABLE); } @@ -172,6 +194,7 @@ impl Debug for Position { } } +#[allow(dead_code)] impl Position { fn is_server(&self) -> bool { match self { @@ -289,7 +312,7 @@ impl Connection { local_settings: H2Settings::default(), peer_settings: H2Settings::default(), position: Position::Server, - prioriser: Prioriser::new(), + prioriser: Prioriser::default(), readiness: Readiness { interest: Ready::READABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, @@ -325,7 +348,7 @@ impl Connection { backend, BackendStatus::Connecting(Instant::now()), ), - prioriser: Prioriser::new(), + prioriser: Prioriser::default(), readiness: Readiness { interest: Ready::WRITABLE | Ready::HUP | Ready::ERROR, event: Ready::EMPTY, @@ -439,13 +462,10 @@ impl Connection { where L: ListenerHandler + L7ListenerHandler, { - match self.position() { - Position::Client(_, backend, BackendStatus::Connected) => { - let mut backend_borrow = backend.borrow_mut(); - backend_borrow.active_requests = backend_borrow.active_requests.saturating_sub(1); - println_!("--------------- CONNECTION END STREAM: {backend_borrow:#?}"); - } - _ => {} + if let Position::Client(_, backend, BackendStatus::Connected) = self.position() { + let mut backend_borrow = backend.borrow_mut(); + backend_borrow.active_requests = backend_borrow.active_requests.saturating_sub(1); + println_!("--------------- CONNECTION END STREAM: {backend_borrow:#?}"); } match self { Connection::H1(c) => c.end_stream(stream, context), @@ -457,13 +477,10 @@ impl Connection { where L: ListenerHandler + L7ListenerHandler, { - match self.position() { - Position::Client(_, backend, BackendStatus::Connected) => { - let mut backend_borrow = backend.borrow_mut(); - backend_borrow.active_requests += 1; - println_!("--------------- CONNECTION START STREAM: {backend_borrow:#?}"); - } - _ => {} + if let Position::Client(_, backend, BackendStatus::Connected) = self.position() { + let mut backend_borrow = backend.borrow_mut(); + backend_borrow.active_requests += 1; + println_!("--------------- CONNECTION START STREAM: {backend_borrow:#?}"); } match self { Connection::H1(c) => c.start_stream(stream, context), @@ -485,7 +502,7 @@ impl<'a, Front: SocketHandler> Endpoint for EndpointServer<'a, Front> { self.0.readiness_mut() } - fn end_stream(&mut self, token: Token, stream: GlobalStreamId, context: &mut Context) + fn end_stream(&mut self, _token: Token, stream: GlobalStreamId, context: &mut Context) where L: ListenerHandler + L7ListenerHandler, { @@ -702,23 +719,46 @@ impl Stream { ) where L: ListenerHandler + L7ListenerHandler, { + let context = &self.context; gauge_add!("http.active_requests", -1); - let protocol = match self.context.protocol { + let protocol = match context.protocol { Protocol::HTTP => "http", Protocol::HTTPS => "https", _ => unreachable!(), }; + // Save the HTTP status code of the backend response + let key = if let Some(status) = context.status { + match status { + 100..=199 => "http.status.1xx", + 200..=299 => "http.status.2xx", + 300..=399 => "http.status.3xx", + 400..=499 => "http.status.4xx", + 500..=599 => "http.status.5xx", + _ => "http.status.other", + } + } else { + "http.status.none" + }; + // if context.cluster_id.is_some() { + // incr!(key); + // } + incr!( + key, + context.cluster_id.as_deref(), + context.backend_id.as_deref() + ); + let endpoint = EndpointRecord::Http { - method: self.context.method.as_deref(), - authority: self.context.authority.as_deref(), - path: self.context.path.as_deref(), - reason: self.context.reason.as_deref(), - status: self.context.status, + method: context.method.as_deref(), + authority: context.authority.as_deref(), + path: context.path.as_deref(), + reason: context.reason.as_deref(), + status: context.status, }; let listener = listener.borrow(); - let tags = self.context.authority.as_deref().and_then(|host| { + let tags = context.authority.as_deref().and_then(|host| { let hostname = match host.split_once(':') { None => host, Some((hostname, _)) => hostname, @@ -730,9 +770,9 @@ impl Stream { error, on_failure: { incr!("unsent-access-logs") }, message: message.as_deref(), - context: self.context.log_context(), - session_address: self.context.session_address, - backend_address: self.context.backend_address, + context: context.log_context(), + session_address: context.session_address, + backend_address: context.backend_address, protocol, endpoint, tags, @@ -743,7 +783,7 @@ impl Stream { request_time: self.metrics.request_time(), bytes_in: self.metrics.bin, bytes_out: self.metrics.bout, - user_agent: self.context.user_agent.as_deref(), + user_agent: context.user_agent.as_deref(), }; } } @@ -1007,6 +1047,10 @@ impl Router { Err(cluster_error) => { // we are past kawa parsing if it succeeded this can't fail // if the request was malformed it was caught by kawa and we sent a 400 + error!( + "Malformed request in connect (should be caught at parsing) {:?}", + context + ); unreachable!("{cluster_error}"); } }; @@ -1117,7 +1161,7 @@ impl>, proxy: Rc>, - metrics: &mut SessionMetrics, + _metrics: &mut SessionMetrics, ) -> SessionResult { let mut counter = 0; let max_loop_iterations = 100000; @@ -1127,7 +1171,7 @@ impl(&mut self, proxy: Rc>, _metrics: &mut SessionMetrics) { - println!("MUX CLOSE"); + debug!("MUX CLOSE"); println_!("FRONTEND: {:#?}", self.frontend); println_!("BACKENDS: {:#?}", self.router.backends); @@ -1583,7 +1627,7 @@ impl unreachable!(), } } - return; + /* let s = match &mut self.frontend { Connection::H1(c) => &mut c.socket, Connection::H2(c) => &mut c.socket, @@ -1594,16 +1638,17 @@ impl SessionIsToBeClosed { diff --git a/lib/src/protocol/mux/parser.rs b/lib/src/protocol/mux/parser.rs index d138be2e5..10a2256fa 100644 --- a/lib/src/protocol/mux/parser.rs +++ b/lib/src/protocol/mux/parser.rs @@ -139,7 +139,7 @@ impl<'a> ParseError<&'a [u8]> for ParserError<'a> { } } - fn append(input: &'a [u8], kind: ErrorKind, other: Self) -> Self { + fn append(input: &'a [u8], kind: ErrorKind, _other: Self) -> Self { ParserError { input, kind: ParserErrorKind::Nom(kind), diff --git a/lib/src/protocol/mux/serializer.rs b/lib/src/protocol/mux/serializer.rs index 18c53720d..28e0643a0 100644 --- a/lib/src/protocol/mux/serializer.rs +++ b/lib/src/protocol/mux/serializer.rs @@ -15,9 +15,9 @@ pub const H2_PRI: &str = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; pub const SETTINGS_ACKNOWLEDGEMENT: [u8; 9] = [0, 0, 0, 4, 1, 0, 0, 0, 0]; pub const PING_ACKNOWLEDGEMENT_HEADER: [u8; 9] = [0, 0, 8, 6, 1, 0, 0, 0, 0]; -pub fn gen_frame_header<'a, 'b>( +pub fn gen_frame_header<'a>( buf: &'a mut [u8], - frame: &'b FrameHeader, + frame: &FrameHeader, ) -> Result<(&'a mut [u8], usize), GenError> { let serializer = tuple(( be_u24(frame.payload_len), @@ -100,11 +100,11 @@ pub fn gen_settings<'a>( }) } -pub fn gen_rst_stream<'a>( - buf: &'a mut [u8], +pub fn gen_rst_stream( + buf: &mut [u8], stream_id: u32, error_code: H2Error, -) -> Result<(&'a mut [u8], usize), GenError> { +) -> Result<(&mut [u8], usize), GenError> { gen_frame_header( buf, &FrameHeader { @@ -119,11 +119,11 @@ pub fn gen_rst_stream<'a>( }) } -pub fn gen_goaway<'a>( - buf: &'a mut [u8], +pub fn gen_goaway( + buf: &mut [u8], last_stream_id: u32, error_code: H2Error, -) -> Result<(&'a mut [u8], usize), GenError> { +) -> Result<(&mut [u8], usize), GenError> { gen_frame_header( buf, &FrameHeader { From 087f631dbc212131f890273ceee7399a423a0e12 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 29 Oct 2024 09:56:53 +0100 Subject: [PATCH 44/44] release: v1.1.0-rc.2 Signed-off-by: Eloi DEMOLIS --- Cargo.lock | 6 +++--- bin/Cargo.toml | 6 +++--- command/Cargo.toml | 2 +- e2e/Cargo.toml | 4 ++-- lib/Cargo.toml | 4 ++-- os-build/archlinux/PKGBUILD | 2 +- os-build/linux-rpm/sozu.spec | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3fadcb12..d3d8c34d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1712,7 +1712,7 @@ dependencies = [ [[package]] name = "sozu" -version = "1.1.0-rc.1" +version = "1.1.0-rc.2" dependencies = [ "clap", "jemallocator", @@ -1735,7 +1735,7 @@ dependencies = [ [[package]] name = "sozu-command-lib" -version = "1.1.0-rc.1" +version = "1.1.0-rc.2" dependencies = [ "hex", "libc", @@ -1779,7 +1779,7 @@ dependencies = [ [[package]] name = "sozu-lib" -version = "1.1.0-rc.1" +version = "1.1.0-rc.2" dependencies = [ "anyhow", "cookie-factory", diff --git a/bin/Cargo.toml b/bin/Cargo.toml index f13b22860..0a950a8bc 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu" homepage = "https://sozu.io" -version = "1.1.0-rc.1" +version = "1.1.0-rc.2" license = "AGPL-3.0" authors = [ "Geoffroy Couprie ", @@ -34,8 +34,8 @@ tempfile = "^3.10.1" termion = "^4.0.0" thiserror = "^1.0.61" -sozu-command-lib = { path = "../command", version = "^1.1.0-rc.1" } -sozu-lib = { path = "../lib", version = "1.1.0-rc.1" } +sozu-command-lib = { path = "../command", version = "^1.1.0-rc.2" } +sozu-lib = { path = "../lib", version = "1.1.0-rc.2" } [target.'cfg(target_os="linux")'.dependencies] num_cpus = "^1.16.0" diff --git a/command/Cargo.toml b/command/Cargo.toml index 4968d49d6..e010351ab 100644 --- a/command/Cargo.toml +++ b/command/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu-command-lib" homepage = "https://sozu.io" -version = "1.1.0-rc.1" +version = "1.1.0-rc.2" license = "LGPL-3.0" authors = [ "Geoffroy Couprie ", diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index 4f9e7fe84..bc2fc9389 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -14,5 +14,5 @@ rustls = { version = "^0.21.10", features = ["dangerous_configuration"] } time = "^0.3.36" tokio = { version = "1.37.0", features = ["net", "rt-multi-thread"] } -sozu-command-lib = { path = "../command", version = "^1.1.0-rc.1" } -sozu-lib = { path = "../lib", version = "^1.1.0-rc.1" } +sozu-command-lib = { path = "../command", version = "^1.1.0-rc.2" } +sozu-lib = { path = "../lib", version = "^1.1.0-rc.2" } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index bdeb5b400..252363946 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/sozu-proxy/sozu" readme = "README.md" documentation = "https://docs.rs/sozu-lib" homepage = "https://sozu.io" -version = "1.1.0-rc.1" +version = "1.1.0-rc.2" license = "AGPL-3.0" authors = [ "Clément Delafargue ", @@ -52,7 +52,7 @@ thiserror = "^1.0.61" time = "^0.3.36" once_cell = "1.19.0" -sozu-command-lib = { path = "../command", version = "1.1.0-rc.1" } +sozu-command-lib = { path = "../command", version = "1.1.0-rc.2" } [dev-dependencies] quickcheck = "^1.0.3" diff --git a/os-build/archlinux/PKGBUILD b/os-build/archlinux/PKGBUILD index 66ce7d94b..c2fa5f13b 100644 --- a/os-build/archlinux/PKGBUILD +++ b/os-build/archlinux/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Jan-Erik Rediger pkgname=sozu-git -pkgver=1.1.0-rc.1 +pkgver=1.1.0-rc.2 pkgrel=1 pkgdesc="HTTP reverse proxy, configurable at runtime, fast and safe, built in Rust" arch=('i686' 'x86_64') diff --git a/os-build/linux-rpm/sozu.spec b/os-build/linux-rpm/sozu.spec index d2851483c..52c3e4016 100755 --- a/os-build/linux-rpm/sozu.spec +++ b/os-build/linux-rpm/sozu.spec @@ -6,7 +6,7 @@ Summary: A lightweight, fast, always-up reverse proxy server. Name: sozu -Version: 1.1.0-rc.1 +Version: 1.1.0-rc.2 Release: 1%{?dist} Epoch: 1 License: AGPL-3.0