From 68d8611837086146b3d00b086d22da48887cebc3 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 10 Aug 2023 10:19:29 +0200 Subject: [PATCH 01/31] Add ALPN field to listener config, change listener builder Signed-off-by: Eloi DEMOLIS --- command/src/command.proto | 6 ++ command/src/config.rs | 118 ++++++++++++++++++++------------------ lib/src/https.rs | 41 ++++++------- 3 files changed, 89 insertions(+), 76 deletions(-) diff --git a/command/src/command.proto b/command/src/command.proto index 408bde6e0..ec252f7c8 100644 --- a/command/src/command.proto +++ b/command/src/command.proto @@ -161,6 +161,7 @@ message HttpsListenerConfig { // The tickets allow the client to resume a session. This protects the client // agains session tracking. Defaults to 4. required uint64 send_tls13_tickets = 20; + repeated AlpnProtocol alpn = 21; } // details of an TCP listener @@ -339,6 +340,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 efe1cdc26..313839eab 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -62,7 +62,7 @@ use toml; use crate::{ certificate::split_certificate_chain, proto::command::{ - request::RequestType, ActivateListener, AddBackend, AddCertificate, CertificateAndKey, + request::RequestType, ActivateListener, AddBackend, AddCertificate, AlpnProtocol, CertificateAndKey, Cluster, HttpListenerConfig, HttpsListenerConfig, ListenerType, LoadBalancingAlgorithms, LoadBalancingParams, LoadMetric, MetricsConfiguration, PathRule, ProxyProtocolConfig, Request, RequestHttpFrontend, RequestTcpFrontend, RulePosition, TcpListenerConfig, @@ -72,8 +72,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] = [ @@ -236,6 +238,7 @@ pub enum ConfigError { pub struct ListenerBuilder { pub address: String, pub protocol: Option, + pub alpn: Option>, pub public_address: Option, /// path to the 404 html file pub answer_404: Option, @@ -311,7 +314,7 @@ 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 where S: ToString, { @@ -321,7 +324,7 @@ impl ListenerBuilder { 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, { @@ -331,7 +334,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, { @@ -341,27 +344,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, { @@ -371,7 +374,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, { @@ -379,12 +382,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, { @@ -392,22 +395,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 } @@ -432,29 +435,28 @@ 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, }); } - if let Some(config) = config { - self.assign_config_timeouts(config); - } + let _address = self.parse_address()?; + let _public_address = self.parse_public_address()?; let (answer_404, answer_503) = self.get_404_503_answers()?; - let _address = self.parse_address()?; - - let _public_address = self.parse_public_address()?; + if let Some(config) = config { + self.assign_config_timeouts(config); + } let configuration = HttpListenerConfig { - address: self.address.clone(), - public_address: self.public_address.clone(), + address: self.address, + public_address: self.public_address, 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), @@ -468,34 +470,51 @@ 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, }); } + let _address = self.parse_address()?; + let _public_address = self.parse_public_address()?; + + let (answer_404, answer_503) = self.get_404_503_answers()?; + + if let Some(config) = config { + self.assign_config_timeouts(config); + } + + 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], @@ -532,23 +551,10 @@ impl ListenerBuilder { .map(split_certificate_chain) .unwrap_or_else(Vec::new); - let (answer_404, answer_503) = self - .get_404_503_answers() - //.with_context(|| "Could not get 404 and 503 answers from file system") - ?; - - let _address = self.parse_address()?; - - let _public_address = self.parse_public_address()?; - - if let Some(config) = config { - self.assign_config_timeouts(config); - } - let https_listener_config = HttpsListenerConfig { - address: self.address.clone(), - sticky_name: self.sticky_name.clone(), - public_address: self.public_address.clone(), + address: self.address, + sticky_name: self.sticky_name, + public_address: self.public_address, cipher_list, versions, expect_proxy: self.expect_proxy.unwrap_or(false), @@ -568,22 +574,22 @@ impl ListenerBuilder { send_tls13_tickets: self .send_tls13_tickets .unwrap_or(DEFAULT_SEND_TLS_13_TICKETS), + alpn, }; Ok(https_listener_config) } /// 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, }); } let _address = self.parse_address()?; - let _public_address = self.parse_public_address()?; if let Some(config) = config { @@ -591,8 +597,8 @@ impl ListenerBuilder { } Ok(TcpListenerConfig { - address: self.address.clone(), - public_address: self.public_address.clone(), + address: self.address, + public_address: self.public_address, expect_proxy: self.expect_proxy.unwrap_or(false), front_timeout: self.front_timeout.unwrap_or(DEFAULT_FRONT_TIMEOUT), back_timeout: self.back_timeout.unwrap_or(DEFAULT_BACK_TIMEOUT), @@ -1285,7 +1291,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 da3364857..5f1f83fbb 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -33,10 +33,10 @@ use sozu_command::{ config::DEFAULT_CIPHER_SUITES, logging, proto::command::{ - request::RequestType, response_content::ContentType, AddCertificate, CertificateSummary, - CertificatesByAddress, Cluster, HttpsListenerConfig, ListOfCertificatesByAddress, - ListenerType, RemoveCertificate, RemoveListener, ReplaceCertificate, RequestHttpFrontend, - ResponseContent, TlsVersion, + request::RequestType, response_content::ContentType, AddCertificate, AlpnProtocol, + CertificateSummary, CertificatesByAddress, Cluster, HttpsListenerConfig, + ListOfCertificatesByAddress, ListenerType, RemoveCertificate, RemoveListener, + ReplaceCertificate, RequestHttpFrontend, ResponseContent, TlsVersion, }, ready::Ready, request::WorkerRequest, @@ -69,9 +69,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, @@ -95,11 +92,6 @@ StateMachineBuilder! { } } -pub enum AlpnProtocols { - H2, - Http11, -} - pub struct HttpsSession { answers: Rc>, configured_backend_timeout: Duration, @@ -270,14 +262,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() { @@ -294,7 +286,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, @@ -318,7 +310,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, @@ -760,11 +752,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::from_i32(*protocol) { + Some(AlpnProtocol::Http11) => Some("http/1.1"), + Some(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 ef17772d01e625d2309ba8c2c91e5d935ae91102 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 11 Aug 2023 15:18:42 +0200 Subject: [PATCH 02/31] Add h2 flag to frontends and in Router Signed-off-by: Eloi DEMOLIS --- bin/src/cli.rs | 2 + bin/src/ctl/command.rs | 2 +- bin/src/ctl/display.rs | 6 +- 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 +++++++++++++++++++++++++------- lib/src/server.rs | 5 +- 15 files changed, 198 insertions(+), 60 deletions(-) diff --git a/bin/src/cli.rs b/bin/src/cli.rs index 454dc9dbd..c529b9ac1 100644 --- a/bin/src/cli.rs +++ b/bin/src/cli.rs @@ -454,6 +454,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/command.rs b/bin/src/ctl/command.rs index d291b1e3f..a96aea86b 100644 --- a/bin/src/ctl/command.rs +++ b/bin/src/ctl/command.rs @@ -434,7 +434,7 @@ impl CommandManager { if let Some(response_content) = response.content { let certs = match response_content.content_type { Some(ContentType::CertificatesWithFingerprints(certs)) => certs.certs, - _ => bail!(format!("Wrong response content {:?}", response_content)), + _ => bail!(format!("Wrong response content {response_content:?}")), }; if certs.is_empty() { bail!("No certificates match your request."); diff --git a/bin/src/ctl/display.rs b/bin/src/ctl/display.rs index 78a8eb66a..8250a8fb4 100644 --- a/bin/src/ctl/display.rs +++ b/bin/src/ctl/display.rs @@ -611,7 +611,7 @@ pub fn print_cluster_responses( clusters_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS); let mut header = vec![cell!("cluster id")]; for worker_id in worker_responses.map.keys() { - header.push(cell!(format!("worker {}", worker_id))); + header.push(cell!(format!("worker {worker_id}"))); } header.push(cell!("desynchronized")); clusters_table.add_row(Row::new(header)); @@ -659,14 +659,14 @@ pub fn print_certificates_by_worker( } for (worker_id, response_content) in response_contents.iter() { - println!("Worker {}", worker_id); + println!("Worker {worker_id}"); match &response_content.content_type { Some(ContentType::CertificatesByAddress(list)) => { for certs in list.certificates.iter() { println!("\t{}:", certs.address); for summary in certs.certificate_summaries.iter() { - println!("\t\t{}", summary); + println!("\t\t{summary}"); } println!(); diff --git a/bin/src/ctl/request_builder.rs b/bin/src/ctl/request_builder.rs index ba9706dd0..c1b38d537 100644 --- a/bin/src/ctl/request_builder.rs +++ b/bin/src/ctl/request_builder.rs @@ -202,6 +202,7 @@ impl CommandManager { method, cluster_id: route, tags, + h2, } => self.send_request( RequestType::AddHttpFrontend(RequestHttpFrontend { cluster_id: route.into(), @@ -214,6 +215,7 @@ impl CommandManager { Some(tags) => tags, None => BTreeMap::new(), }, + h2: h2.unwrap_or(false), }) .into(), ), @@ -250,6 +252,7 @@ impl CommandManager { method, cluster_id: route, tags, + h2, } => self.send_request( RequestType::AddHttpsFrontend(RequestHttpFrontend { cluster_id: route.into(), @@ -262,6 +265,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 ec252f7c8..d9fad822b 100644 --- a/command/src/command.proto +++ b/command/src/command.proto @@ -222,6 +222,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 313839eab..03783ab63 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -685,6 +685,7 @@ pub struct FileClusterFrontendConfig { #[serde(default)] pub position: RulePosition, pub tags: Option>, + pub h2: Option, } impl FileClusterFrontendConfig { @@ -770,6 +771,7 @@ impl FileClusterFrontendConfig { path, method: self.method.clone(), tags: self.tags.clone(), + h2: self.h2.unwrap_or(false), }) } } @@ -795,6 +797,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)] @@ -920,6 +923,7 @@ pub struct HttpFrontendConfig { #[serde(default)] pub position: RulePosition, pub tags: Option>, + pub h2: bool, } impl HttpFrontendConfig { @@ -955,6 +959,7 @@ impl HttpFrontendConfig { path: self.path.clone(), method: self.method.clone(), position: self.position.into(), + h2: self.h2, tags, }) .into(), @@ -969,6 +974,7 @@ impl HttpFrontendConfig { path: self.path.clone(), method: self.method.clone(), position: self.position.into(), + h2: self.h2, tags, }) .into(), @@ -1297,13 +1303,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 fd2e2dad7..96918ef49 100644 --- a/command/src/proto/display.rs +++ b/command/src/proto/display.rs @@ -32,9 +32,9 @@ impl Display for CertificateSummary { impl Display for QueryCertificatesFilters { fn fmt(&self, f: &mut Formatter<'_>) -> std::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 c8350b23b..84ecd8dec 100644 --- a/command/src/request.rs +++ b/command/src/request.rs @@ -210,6 +210,7 @@ impl RequestHttpFrontend { } })?, tags: Some(self.tags), + h2: self.h2, }) } } diff --git a/command/src/response.rs b/command/src/response.rs index eb6ed6992..3bd83acae 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 04af19958..033bd863b 100644 --- a/command/src/state.rs +++ b/command/src/state.rs @@ -478,7 +478,7 @@ impl ConfigState { if tcp_frontends.contains(&tcp_frontend) { return Err(StateError::Exists { kind: ObjectKind::TcpFrontend, - id: format!("{:?}", tcp_frontend), + id: format!("{tcp_frontend:?}"), }); } @@ -497,7 +497,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 f00fa9de4..786748008 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -461,7 +461,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, @@ -671,7 +671,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:?}"), }); } @@ -694,7 +694,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:?}"), }); } @@ -1467,6 +1467,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id1), tags: None, + h2: false, }) .expect("Could not add http frontend"); fronts @@ -1478,6 +1479,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id2), tags: None, + h2: false, }) .expect("Could not add http frontend"); fronts @@ -1489,6 +1491,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some(cluster_id3), tags: None, + h2: false, }) .expect("Could not add http frontend"); fronts @@ -1500,6 +1503,7 @@ mod tests { position: RulePosition::Tree, cluster_id: Some("cluster_1".to_owned()), tags: None, + h2: false, }) .expect("Could not add http frontend"); @@ -1531,19 +1535,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 5f1f83fbb..6c1c2b7cd 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -579,7 +579,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, @@ -873,7 +873,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:?}"), }); } @@ -896,7 +896,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:?}"), }); } @@ -1620,25 +1620,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: StdSocketAddr = FromStr::from_str("127.0.0.1:1032") @@ -1680,25 +1692,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 d1a42bf1b..2a394931c 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -1124,8 +1124,8 @@ impl Http cluster_id, + let (cluster_id, _) = match route { + Route::Cluster { id, h2 } => (id, h2), Route::Deny => { self.set_answer(DefaultAnswerStatus::Answer401, None); return Err(RetrieveClusterError::UnauthorizedRoute); diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index 1001c72a8..7cd066c76 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -134,7 +134,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, }; @@ -162,7 +165,7 @@ impl Router { } }; if !success { - return Err(RouterError::AddRoute(format!("{:?}", front))); + return Err(RouterError::AddRoute(format!("{front:?}"))); } Ok(()) } @@ -197,7 +200,7 @@ impl Router { } }; if !remove_success { - return Err(RouterError::RemoveRoute(format!("{:?}", front))); + return Err(RouterError::RemoveRoute(format!("{front:?}"))); } Ok(()) } @@ -598,7 +601,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)] @@ -717,27 +720,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 + }) ); } @@ -756,27 +774,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 + }) ); } @@ -788,23 +821,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", @@ -815,7 +860,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 + }) ); } @@ -827,30 +875,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( @@ -858,7 +921,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"[..])) @@ -869,11 +935,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 + }) ); } } diff --git a/lib/src/server.rs b/lib/src/server.rs index 0461dbe41..0a9926e1a 100644 --- a/lib/src/server.rs +++ b/lib/src/server.rs @@ -1305,8 +1305,7 @@ impl Server { None => { let error = format!( - "Couldn't deactivate HTTPS listener at address {:?}", - address + "Couldn't deactivate HTTPS listener at address {address:?}", ); error!("{}", error); return WorkerResponse::error(req_id, error); @@ -1345,7 +1344,7 @@ impl Server { Some((token, listener)) => (token, listener), None => { let error = - format!("Couldn't deactivate TCP listener at address {:?}", address); + format!("Couldn't deactivate TCP listener at address {address:?}"); error!("{}", error); return WorkerResponse::error(req_id, error); } From 4806fdbd1301a4768bdf320a6f5fcdbb01010148 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 14 Aug 2023 20:32:33 +0200 Subject: [PATCH 03/31] 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 6c1c2b7cd..2eebd96fe 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -54,6 +54,7 @@ use crate::{ answers::HttpAnswers, parser::{hostname_and_port, Method}, }, + mux::Mux, proxy_protocol::expect::ExpectProxyProtocol, rustls::TlsHandshake, Http, Pipe, SessionState, @@ -65,8 +66,8 @@ use crate::{ tls::{CertifiedKeyWrapper, MutexWrappedCertificateResolver, ResolveCertificate}, 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)] @@ -86,6 +87,7 @@ StateMachineBuilder! { enum HttpsStateMachine impl SessionState { Expect(ExpectProxyProtocol, ServerConnection), Handshake(TlsHandshake), + Mux(Mux), Http(Http), WebSocket(Pipe), Http2(Http2) -> todo!("H2"), @@ -184,6 +186,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!(), @@ -271,6 +274,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)); @@ -285,6 +289,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( @@ -396,6 +444,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); @@ -464,6 +513,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 afbe0adad5e23b64c7fb31575b8b1bfe4b5d315f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 16 Aug 2023 14:38:49 +0200 Subject: [PATCH 04/31] 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 2eebd96fe..26a513980 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -318,6 +318,7 @@ impl HttpsSession { }, streams: HashMap::new(), state: mux::H2State::ClientPreface, + expect: Some((0, 24 + 9)), }), }; let mut mux = Mux { @@ -459,6 +460,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 9d92d8963037fb33bde1510a4bb2419caf05c60f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 18 Aug 2023 00:01:46 +0200 Subject: [PATCH 05/31] 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 26a513980..9aba5821a 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -299,40 +299,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 00295553fde7d59e119e870fc930b069a3398c48 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 21 Aug 2023 13:03:42 +0200 Subject: [PATCH 06/31] 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 9aba5821a..ad065bd4b 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -66,8 +66,8 @@ use crate::{ tls::{CertifiedKeyWrapper, MutexWrappedCertificateResolver, ResolveCertificate}, 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)] @@ -186,7 +186,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!(), @@ -250,12 +250,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()); @@ -289,79 +283,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 2a394931c..f1a427e39 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -1124,8 +1124,8 @@ impl Http (id, h2), + let cluster_id = match route { + Route::Cluster { id, .. } => id, Route::Deny => { self.set_answer(DefaultAnswerStatus::Answer401, None); 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 46fbe9bc7764a9fd358494a2324ad44ca5738576 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 22 Aug 2023 11:32:22 +0200 Subject: [PATCH 07/31] 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 e018789f4cb0498df72c178d745eba37e7474056 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 22 Aug 2023 14:51:33 +0200 Subject: [PATCH 08/31] 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 46ccc207c17a1470fd2cd3c1adf60af5f876ee27 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 22 Aug 2023 16:20:48 +0200 Subject: [PATCH 09/31] 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 d3fadeb2906aa2b647b7dbfa64afa2e0c4e6e7e7 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 23 Aug 2023 18:56:40 +0200 Subject: [PATCH 10/31] 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 | 14 +- lib/src/protocol/kawa_h1/mod.rs | 23 +-- lib/src/protocol/mux/h1.rs | 17 +- lib/src/protocol/mux/h2.rs | 70 ++++++--- lib/src/protocol/mux/mod.rs | 245 +++++++++++++++++++++++++---- lib/src/protocol/mux/pkawa.rs | 60 +++++-- 7 files changed, 344 insertions(+), 92 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index ad065bd4b..060e6d9bf 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -293,9 +293,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(), @@ -532,7 +534,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 177a86c74..e96aa1e62 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, }; /// This is the container used to store and use information about the session from within a Kawa parser callback @@ -337,4 +337,16 @@ impl HttpContext { val: kawa::Store::from_string(self.id.to_string()), })); } + + // -> 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)) + } } diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index f1a427e39..035d54a59 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -1078,32 +1078,11 @@ impl 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)) - } - 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(DefaultAnswerStatus::Answer400, None); 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..9f08195b8 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, @@ -244,11 +253,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 +430,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 +457,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 +492,7 @@ impl SessionState for Mux { MuxResult::CloseSession => return SessionResult::Close, MuxResult::Close(_) => todo!(), MuxResult::Connect(_) => unreachable!(), - } + } dirty = true; } } @@ -324,10 +507,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 +534,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 +568,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 1bf6741a13a8f6c2c4a56aef571aa60b3a62892d Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 24 Aug 2023 14:17:55 +0200 Subject: [PATCH 11/31] 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 060e6d9bf..a877ac5c3 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -285,15 +285,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(), @@ -301,9 +307,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 9f08195b8..425ba476a 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)), @@ -225,31 +209,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(), - }) + } } } @@ -267,7 +273,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 @@ -317,7 +323,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, @@ -360,7 +366,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( @@ -567,7 +573,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 29b1df4e103b53301c4cc6e86f9ed7a61d0bc27f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 25 Aug 2023 00:27:57 +0200 Subject: [PATCH 12/31] 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 425ba476a..246568cf3 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, @@ -459,7 +490,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!(), @@ -483,7 +514,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!(), @@ -493,7 +524,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!(), @@ -504,7 +535,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 f44ebdd23295d60dfb5e059ba259acf574f1f3b2 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 25 Aug 2023 11:50:27 +0200 Subject: [PATCH 13/31] 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 246568cf3..b009768c0 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 { @@ -304,7 +336,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 @@ -346,6 +379,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(()) @@ -490,7 +524,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!(), @@ -535,7 +572,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 9c33967ad36ef2f523221b0b73bea35073d5a176 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 25 Aug 2023 18:00:02 +0200 Subject: [PATCH 14/31] 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..89d6e8d2a --- /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::new_vec(&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::new_vec(&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::new_vec(&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 b009768c0..25dd1ea00 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(), })) } @@ -303,7 +306,6 @@ impl Stream { pub struct Context { pub streams: Vec, pub pool: Weak>, - pub decoder: hpack::Decoder<'static>, } impl Context { @@ -317,7 +319,6 @@ impl Context { Self { streams: Vec::new(), pool, - decoder: hpack::Decoder::new(), } } } @@ -379,7 +380,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(()) @@ -608,7 +609,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 08132d0d0b919258e0f14795ccf0930805b2334e Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 28 Aug 2023 09:19:00 +0200 Subject: [PATCH 15/31] 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 89d6e8d2a..d81770f7e 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 25dd1ea00..c5988c458 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 0c50d57cd923524ffe05bd56c5a1f860c26f3971 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 28 Aug 2023 18:23:02 +0200 Subject: [PATCH 16/31] 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 d81770f7e..1e1f8b4f3 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 c5988c458..9234080a8 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(), })) } @@ -590,7 +592,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 a81659458de30e5ec50a29e20833bfc13bbc9c63 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/31] 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 0491cbd138a34f1f253c680c8c6658f6c1f605a7 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 30 Aug 2023 13:21:07 +0200 Subject: [PATCH 18/31] 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 e96aa1e62..eef6595fe 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -36,7 +36,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 9234080a8..b67276092 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 { @@ -258,8 +333,8 @@ 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: HttpContext { - keep_alive_backend: false, - keep_alive_frontend: false, + keep_alive_backend: true, + keep_alive_frontend: true, sticky_session_found: None, method: None, authority: None, @@ -277,9 +352,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, @@ -291,15 +366,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, } } @@ -340,13 +415,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() @@ -355,44 +430,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) => { @@ -417,7 +543,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); @@ -552,8 +678,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, @@ -563,7 +709,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, @@ -573,6 +719,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 @@ -587,15 +740,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 9440fb054..47fb1fbf9 100644 --- a/lib/src/socket.rs +++ b/lib/src/socket.rs @@ -171,6 +171,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 e4f341d6350d0a84b0158a28a748d966291b4e59 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 31 Aug 2023 15:05:49 +0200 Subject: [PATCH 19/31] 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 03783ab63..395199744 100644 --- a/command/src/config.rs +++ b/command/src/config.rs @@ -667,6 +667,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 { @@ -682,7 +687,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 acfc8c5696debbca848dd637542100144fff590b Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 31 Aug 2023 23:24:01 +0200 Subject: [PATCH 20/31] 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 | 71 ++++++++++++++++++++---------- lib/src/protocol/mux/pkawa.rs | 18 +++++--- 4 files changed, 121 insertions(+), 59 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 b67276092..f1006b2f8 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,27 @@ pub struct StreamParts<'a> { pub context: &'a mut HttpContext, } +fn temporary_http_context(request_id: Ulid) -> HttpContext { + HttpContext { + 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,28 +352,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 { - 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<'_> { @@ -387,6 +395,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) @@ -470,6 +492,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( @@ -499,7 +522,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 e150a90392af70439505fee63509f7c3c3c725cb Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 4 Sep 2023 18:07:00 +0200 Subject: [PATCH 21/31] 2 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 | 12 ++ lib/src/protocol/kawa_h1/mod.rs | 5 +- 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, 221 insertions(+), 117 deletions(-) diff --git a/lib/src/protocol/kawa_h1/editor.rs b/lib/src/protocol/kawa_h1/editor.rs index eef6595fe..7c10fdc4a 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -349,4 +349,16 @@ impl HttpContext { Ok((given_authority, given_path, given_method)) } + + pub fn reset(&mut self) { + self.keep_alive_backend = true; + self.sticky_session_found = None; + self.method = None; + self.authority = None; + self.path = None; + self.status = None; + self.reason = None; + self.user_agent = None; + self.id = Ulid::generate(); + } } diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 035d54a59..64a9aba6a 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -219,10 +219,7 @@ impl Http { 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 f1006b2f8..6b8e656eb 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, @@ -709,11 +759,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); } @@ -776,6 +822,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 47fb1fbf9..057798492 100644 --- a/lib/src/socket.rs +++ b/lib/src/socket.rs @@ -196,7 +196,7 @@ impl SocketHandler for FrontRustls { break; } - if !can_read | is_error | is_closed { + if !can_read || is_error || is_closed { break; } From 10927cdc35693f09ac372f1b42d554c1f24be354 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 6 Sep 2023 10:51:41 +0200 Subject: [PATCH 22/31] 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 a877ac5c3..49da7b8cc 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -442,19 +442,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 18706cd6b..5c98c1ed4 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -620,6 +620,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 6b8e656eb..693cd144f 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 => { @@ -447,7 +462,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(); @@ -507,7 +522,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))) => { @@ -523,8 +540,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))) => { @@ -539,10 +558,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( @@ -573,7 +592,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) }; @@ -599,7 +621,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); } @@ -613,18 +637,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); } }; @@ -647,9 +671,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 { @@ -709,6 +733,10 @@ impl Mux { pub fn front_socket(&self) -> &TcpStream { self.frontend.socket() } + + fn goaway(&mut self, e: H2Error) { + todo!() + } } impl SessionState for Mux { @@ -725,17 +753,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( @@ -747,7 +777,7 @@ impl SessionState for Mux { ) { Ok(_) => (), Err(error) => { - println!("{error}"); + println_!("{error}"); } } } @@ -755,11 +785,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); } @@ -775,7 +806,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!(), } @@ -785,7 +819,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!(), } @@ -796,17 +833,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!(), } @@ -822,7 +860,6 @@ impl SessionState for Mux { if counter == max_loop_iterations { incr!("http.infinite_loop.error"); - panic!(); return SessionResult::Close; } @@ -838,12 +875,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) { @@ -864,15 +901,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 9441e12549b3c03e5a19e37b33357ba2852ead7f Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Sat, 9 Sep 2023 21:31:52 +0200 Subject: [PATCH 23/31] 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 1e1f8b4f3..2e03ff987 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::new_vec(&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::new_vec(&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::new_vec(&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::new_vec(&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 693cd144f..fa4c8be37 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::Error; + 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, @@ -417,9 +484,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), @@ -461,16 +528,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); } } @@ -504,7 +571,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()) @@ -604,7 +676,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) @@ -624,8 +696,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); } }; @@ -755,112 +825,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 a8992c81dd9de35a996dbf5299ad18c76a31f534 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 12 Sep 2023 17:59:14 +0200 Subject: [PATCH 24/31] 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 5c98c1ed4..c2bad6f1c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -515,9 +515,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 2e03ff987..6e1c95e3e 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 fa4c8be37..3e15a4c14 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::Error; 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 { @@ -803,10 +808,6 @@ impl Mux { pub fn front_socket(&self) -> &TcpStream { self.frontend.socket() } - - fn goaway(&mut self, e: H2Error) { - todo!() - } } impl SessionState for Mux { @@ -833,8 +834,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, } } @@ -851,31 +852,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, } } @@ -903,8 +898,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, } } @@ -937,24 +932,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 90c70b3135a1738a7e44e80dd92e84a4c2d484f1 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Tue, 19 Sep 2023 12:12:10 +0200 Subject: [PATCH 25/31] 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 --- Cargo.lock | 1 + e2e/Cargo.toml | 1 + e2e/src/http_utils/mod.rs | 24 +++-- e2e/src/tests/tests.rs | 33 ++++--- lib/src/http.rs | 126 ++++++++++++++++++----- lib/src/https.rs | 12 +-- lib/src/lib.rs | 2 + lib/src/protocol/kawa_h1/editor.rs | 13 +++ lib/src/protocol/kawa_h1/mod.rs | 14 --- lib/src/protocol/mux/h1.rs | 84 +++++++++++++--- lib/src/protocol/mux/mod.rs | 154 ++++++++++++++++++++++------- 11 files changed, 347 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc443ac8c..e9c045d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1951,6 +1951,7 @@ dependencies = [ "futures", "hyper", "hyper-rustls", + "kawa", "libc", "mio", "rustls", diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index e590a2678..e26c6f632 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -5,6 +5,7 @@ rust-version = "1.70.0" edition = "2021" [dependencies] +kawa = "0.6.3" futures = "^0.3.28" hyper = { version = "^0.14.27", features = ["client", "http1"] } hyper-rustls = { version = "^0.24.1", default-features = false, features = ["webpki-tokio", "http1", "tls12", "logging"] } diff --git a/e2e/src/http_utils/mod.rs b/e2e/src/http_utils/mod.rs index 7ca7a5bbd..f0afbc6bf 100644 --- a/e2e/src/http_utils/mod.rs +++ b/e2e/src/http_utils/mod.rs @@ -24,14 +24,20 @@ pub fn http_request, S2: Into, S3: Into, S4: In ) } -// the default value for the 404 error, as provided in the command lib, -// used as default for listeners -pub fn default_404_answer() -> String { - String::from(include_str!("../../../command/assets/404.html")) -} +use std::io::Write; +use kawa; -// the default value for the 503 error, as provided in the command lib, -// used as default for listeners -pub fn default_503_answer() -> String { - String::from(include_str!("../../../command/assets/503.html")) +/// 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 4672257b8..4123e8ec4 100644 --- a/e2e/src/tests/tests.rs +++ b/e2e/src/tests/tests.rs @@ -17,7 +17,7 @@ use sozu_command_lib::{ }; use crate::{ - http_utils::{default_404_answer, default_503_answer, http_ok_response, http_request}, + http_utils::{default_answer, http_ok_response, http_request}, mock::{ aggregator::SimpleAggregator, async_backend::BackendHandle as AsyncBackend, @@ -672,7 +672,7 @@ fn try_http_behaviors() -> State { let response = client.receive(); println!("response: {response:?}"); - assert_eq!(response, Some(default_404_answer())); + assert_eq!(response, Some(default_answer(404))); assert_eq!(client.receive(), None); worker.send_proxy_request_type(RequestType::AddHttpFrontend(RequestHttpFrontend { @@ -687,7 +687,7 @@ fn try_http_behaviors() -> State { let response = client.receive(); println!("response: {response:?}"); - assert_eq!(response, Some(default_503_answer())); + assert_eq!(response, Some(default_answer(503))); assert_eq!(client.receive(), None); let back_address = create_local_address(); @@ -705,12 +705,9 @@ fn try_http_behaviors() -> State { client.connect(); client.send(); - let expected_response = String::from( - "HTTP/1.1 400 Bad Request\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n", - ); let response = client.receive(); println!("response: {response:?}"); - assert_eq!(response, Some(expected_response)); + assert_eq!(response, Some(default_answer(400))); assert_eq!(client.receive(), None); let mut backend = SyncBackend::new("backend", back_address, "TEST\r\n\r\n"); @@ -724,13 +721,10 @@ fn try_http_behaviors() -> State { let request = backend.receive(0); backend.send(0); - let expected_response = String::from( - "HTTP/1.1 502 Bad Gateway\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n", - ); let response = client.receive(); println!("request: {request:?}"); println!("response: {response:?}"); - assert_eq!(response, Some(expected_response)); + assert_eq!(response, Some(default_answer(502))); assert_eq!(client.receive(), None); info!("expecting 200"); @@ -782,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(); @@ -793,7 +788,7 @@ fn try_http_behaviors() -> State { let response = client.receive(); println!("request: {request:?}"); println!("response: {response:?}"); - assert_eq!(response, Some(default_503_answer())); + assert_eq!(response, Some(default_answer(502))); assert_eq!(client.receive(), None); worker.send_proxy_request_type(RequestType::RemoveBackend(RemoveBackend { @@ -1200,7 +1195,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 @@ -1213,7 +1210,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) @@ -1226,7 +1225,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 786748008..293bfcb9e 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -39,6 +39,7 @@ use crate::{ answers::HttpAnswers, parser::{hostname_and_port, Method}, }, + mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, Http, Pipe, SessionState, }, @@ -66,7 +67,8 @@ StateMachineBuilder! { /// 3. WebSocket (passthrough) enum HttpStateMachine impl SessionState { Expect(ExpectProxyProtocol), - Http(Http), + // Http(Http), + Mux(Mux), WebSocket(Pipe), } } @@ -125,22 +127,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)); @@ -164,7 +180,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!(), @@ -212,7 +229,8 @@ impl HttpSession { gauge_add!("protocol.proxy.expect", -1); gauge_add!("protocol.http", 1); - Some(HttpStateMachine::Http(http)) + unimplemented!(); + // Some(HttpStateMachine::Http(http)) } _ => None, } @@ -222,7 +240,7 @@ impl HttpSession { debug!("http switching to ws"); let front_token = self.frontend_token; let back_token = unwrap_msg!(http.backend_token); - let ws_context = http.websocket_context(); + 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; @@ -258,6 +276,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, + Some(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"); @@ -277,7 +358,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); @@ -287,7 +369,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; @@ -1395,7 +1477,7 @@ mod tests { ); println!("http client write: {w:?}"); - let expected_answer = "HTTP/1.1 301 Moved Permanently\r\nContent-Length: 0\r\nLocation: https://localhost/redirected?true\r\n\r\n"; + let expected_answer = "HTTP/1.1 301 Moved Permanently\r\nLocation: https://localhost/redirected?true\r\nContent-Length: 0\r\n\r\n"; let mut buffer = [0; 4096]; let mut index = 0; loop { diff --git a/lib/src/https.rs b/lib/src/https.rs index 49da7b8cc..00899ddea 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -54,7 +54,7 @@ use crate::{ answers::HttpAnswers, parser::{hostname_and_port, Method}, }, - mux::Mux, + mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, rustls::TlsHandshake, Http, Pipe, SessionState, @@ -87,7 +87,7 @@ StateMachineBuilder! { enum HttpsStateMachine impl SessionState { Expect(ExpectProxyProtocol, ServerConnection), Handshake(TlsHandshake), - Mux(Mux), + Mux(Mux), Http(Http), WebSocket(Pipe), Http2(Http2) -> todo!("H2"), @@ -284,7 +284,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 => { @@ -300,10 +299,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(), @@ -314,7 +310,7 @@ impl HttpsSession { debug!("https switching to wss"); let front_token = self.frontend_token; let back_token = unwrap_msg!(http.backend_token); - let ws_context = http.websocket_context(); + 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; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index c2bad6f1c..55041ec4b 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -639,6 +639,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 7c10fdc4a..67abd464a 100644 --- a/lib/src/protocol/kawa_h1/editor.rs +++ b/lib/src/protocol/kawa_h1/editor.rs @@ -6,6 +6,7 @@ use std::{ use rusty_ulid::Ulid; use crate::{ + logs::Endpoint, pool::Checkout, protocol::http::{parser::compare_no_case, GenericHttpStream, Method}, Protocol, RetrieveClusterError, @@ -350,6 +351,18 @@ impl HttpContext { Ok((given_authority, given_path, given_method)) } + /// Format the context of the websocket into a loggable String + pub fn websocket_context(&self) -> String { + Endpoint::Http { + method: self.method.as_ref(), + authority: self.authority.as_deref(), + path: self.path.as_deref(), + status: self.status, + reason: self.reason.as_deref(), + } + .to_string() + } + pub fn reset(&mut self) { self.keep_alive_backend = true; self.sticky_session_found = None; diff --git a/lib/src/protocol/kawa_h1/mod.rs b/lib/src/protocol/kawa_h1/mod.rs index 64a9aba6a..4ac3dc9ff 100644 --- a/lib/src/protocol/kawa_h1/mod.rs +++ b/lib/src/protocol/kawa_h1/mod.rs @@ -755,20 +755,6 @@ impl Http String { - format!( - "{}", - Endpoint::Http { - method: self.context.method.as_ref(), - authority: self.context.authority.as_deref(), - path: self.context.path.as_deref(), - status: self.context.status, - reason: self.context.reason.as_deref(), - } - ) - } - pub fn log_request(&self, metrics: &SessionMetrics, 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 3e15a4c14..7b153e27c 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::Error; + 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, } @@ -560,11 +590,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, @@ -587,12 +624,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; @@ -794,9 +837,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, @@ -804,13 +847,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>, @@ -836,6 +879,7 @@ impl SessionState for Mux { { MuxResult::Continue => {} MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Upgrade => return SessionResult::Upgrade, } } @@ -844,6 +888,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 { @@ -863,6 +908,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, } } @@ -870,6 +916,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, } } @@ -888,7 +935,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); } @@ -900,6 +947,7 @@ impl SessionState for Mux { { MuxResult::Continue => {} MuxResult::CloseSession => return SessionResult::Close, + MuxResult::Upgrade => return SessionResult::Upgrade, } } @@ -951,6 +999,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!(), @@ -996,6 +1047,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, @@ -1017,4 +1069,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 64c72254ed7c04c6ea3747343f218735b834c3cd Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Mon, 25 Sep 2023 11:42:07 +0200 Subject: [PATCH 26/31] 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 6e1c95e3e..aafcda8bf 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 6a8caf6be9a0e10225677c6a5b76320308b85a0c Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 27 Sep 2023 14:54:28 +0200 Subject: [PATCH 27/31] 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 7b153e27c..096e4de8d 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 6d7acc2dbf6bfb9da736248562fa37a9cace3219 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 29 Sep 2023 15:18:25 +0200 Subject: [PATCH 28/31] Introduce timeouts in Mux State Signed-off-by: Eloi DEMOLIS --- lib/src/http.rs | 191 ++++++++++++++++++++---------------- lib/src/https.rs | 160 ++++++++++++++++++++++-------- lib/src/protocol/mux/h1.rs | 4 +- lib/src/protocol/mux/h2.rs | 2 + lib/src/protocol/mux/mod.rs | 135 +++++++++++++++++++++++-- 5 files changed, 356 insertions(+), 136 deletions(-) diff --git a/lib/src/http.rs b/lib/src/http.rs index 293bfcb9e..fead82726 100644 --- a/lib/src/http.rs +++ b/lib/src/http.rs @@ -41,13 +41,12 @@ use crate::{ }, mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, - Http, Pipe, SessionState, + Pipe, SessionState, }, router::{Route, Router}, server::{ListenSession, ListenToken, ProxyChannel, Server, SessionManager}, socket::server_bind, timer::TimeoutContainer, - util::UnwrapLog, AcceptError, CachedTags, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerError, ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, SessionIsToBeClosed, SessionMetrics, SessionResult, StateMachineBuilder, StateResult, @@ -127,15 +126,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(), @@ -208,115 +213,131 @@ 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 = unwrap_msg!(http.backend_token); - 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 mut pipe = Pipe::new( - http.response_stream.storage.buffer, - http.backend_id, - http.backend_socket, - http.backend, - Some(container_backend_timeout), - Some(container_frontend_timeout), - http.cluster_id, - http.request_stream.storage.buffer, - front_token, - http.frontend_socket, - self.listener.clone(), - Protocol::HTTP, - http.context.id, - http.context.session_address, - Some(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_http(&mut self, http: Http) -> Option { + // debug!("http switching to ws"); + // let front_token = self.frontend_token; + // let back_token = unwrap_msg!(http.backend_token); + // 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 mut pipe = Pipe::new( + // http.response_stream.storage.buffer, + // http.backend_id, + // http.backend_socket, + // http.backend, + // Some(container_backend_timeout), + // Some(container_frontend_timeout), + // http.cluster_id, + // http.request_stream.storage.buffer, + // front_token, + // http.frontend_socket, + // self.listener.clone(), + // Protocol::HTTP, + // http.context.id, + // http.context.session_address, + // Some(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, diff --git a/lib/src/https.rs b/lib/src/https.rs index 00899ddea..76de96c60 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -49,7 +49,6 @@ use crate::{ backends::BackendMap, pool::Pool, protocol::{ - h2::Http2, http::{ answers::HttpAnswers, parser::{hostname_and_port, Method}, @@ -57,7 +56,7 @@ use crate::{ mux::{self, Mux}, proxy_protocol::expect::ExpectProxyProtocol, rustls::TlsHandshake, - Http, Pipe, SessionState, + Pipe, SessionState, }, router::{Route, Router}, server::{ListenSession, ListenToken, ProxyChannel, Server, SessionManager, SessionToken}, @@ -88,9 +87,9 @@ StateMachineBuilder! { Expect(ExpectProxyProtocol, ServerConnection), Handshake(TlsHandshake), Mux(Mux), - Http(Http), + // Http(Http), WebSocket(Pipe), - Http2(Http2) -> todo!("H2"), + // Http2(Http2) -> todo!("H2"), } } @@ -185,9 +184,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!(), }; @@ -284,59 +283,145 @@ 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 = unwrap_msg!(http.backend_token); - let ws_context = http.context.websocket_context(); + // fn upgrade_http(&self, http: Http) -> Option { + // debug!("https switching to wss"); + // let front_token = self.frontend_token; + // let back_token = unwrap_msg!(http.backend_token); + // 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 mut pipe = Pipe::new( + // http.response_stream.storage.buffer, + // http.backend_id, + // http.backend_socket, + // http.backend, + // Some(container_backend_timeout), + // Some(container_frontend_timeout), + // http.cluster_id, + // http.request_stream.storage.buffer, + // front_token, + // http.frontend_socket, + // self.listener.clone(), + // Protocol::HTTP, + // http.context.id, + // http.context.session_address, + // Some(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.https", -1); + // gauge_add!("protocol.wss", 1); + // gauge_add!("http.active_requests", -1); + // gauge_add!("websocket.active_requests", 1); + // Some(HttpsStateMachine::WebSocket(pipe)) + // } + + fn upgrade_mux(&self, mut mux: Mux) -> Option { + debug!("mux switching to wss"); + let stream = mux.context.streams.pop().unwrap(); + + 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 { + 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(); let mut pipe = Pipe::new( - http.response_stream.storage.buffer, - http.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.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, Some(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); @@ -346,10 +431,6 @@ impl HttpsSession { Some(HttpsStateMachine::WebSocket(pipe)) } - fn upgrade_http2(&self) -> Option { - todo!() - } - fn upgrade_websocket( &self, wss: Pipe, @@ -373,13 +454,12 @@ 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::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), + } // StateMarker::Http2 => gauge_add!("protocol.http2", -1), } if self.state.failed() { @@ -740,9 +820,9 @@ impl HttpsListener { let protocols = config .alpn .iter() - .filter_map(|protocol| match AlpnProtocol::from_i32(*protocol) { - Some(AlpnProtocol::Http11) => Some("http/1.1"), - Some(AlpnProtocol::H2) => Some("h2"), + .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 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 096e4de8d..9f2292f69 100644 --- a/lib/src/protocol/mux/mod.rs +++ b/lib/src/protocol/mux/mod.rs @@ -9,6 +9,7 @@ use std::{ use mio::{net::TcpStream, Interest, Token}; use rusty_ulid::Ulid; use sozu_command::{proto::command::ListenerType, ready::Ready}; +use time::Duration; mod converter; mod h1; @@ -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(), @@ -594,15 +615,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, } } @@ -715,13 +744,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 @@ -842,6 +877,7 @@ impl Router { } pub struct Mux { + pub configured_frontend_timeout: Duration, pub frontend_token: Token, pub frontend: Connection, pub router: Router, @@ -1033,11 +1069,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 a46ab63be872a6a85258729940688188ed7b1dca Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Fri, 29 Sep 2023 17:50:33 +0200 Subject: [PATCH 29/31] 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 --- e2e/src/http_utils/mod.rs | 2 +- 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 ++++++++++++++++++++++--------- 5 files changed, 121 insertions(+), 71 deletions(-) diff --git a/e2e/src/http_utils/mod.rs b/e2e/src/http_utils/mod.rs index f0afbc6bf..fe91efcb5 100644 --- a/e2e/src/http_utils/mod.rs +++ b/e2e/src/http_utils/mod.rs @@ -24,8 +24,8 @@ pub fn http_request, S2: Into, S3: Into, S4: In ) } -use std::io::Write; use kawa; +use std::io::Write; /// the default kawa answer for the error code provided, converted to HTTP/1.1 pub fn default_answer(code: u16) -> String { 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 9f2292f69..b2c625478 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, @@ -1073,8 +1079,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 { @@ -1085,27 +1093,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 => { @@ -1115,6 +1123,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() { @@ -1123,24 +1132,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 @@ -1160,11 +1185,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) { @@ -1182,9 +1215,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 7edc897d6f002d3af3badef7df3d6508b87b9e4d Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 18 Oct 2023 12:21:26 +0200 Subject: [PATCH 30/31] 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 2a1adae60..4b89bcc81 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 14712bb7f..ecd770149 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 b2c625478..618f2c86a 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}, }; @@ -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(), @@ -949,6 +955,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); } _ => {} } @@ -979,7 +988,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); @@ -1011,10 +1042,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, @@ -1103,9 +1139,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 => { @@ -1200,27 +1236,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 4436540ee9e77d35f461ded66013568e21f55129 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Wed, 29 Nov 2023 18:03:26 +0100 Subject: [PATCH 31/31] tmp Signed-off-by: Eloi DEMOLIS --- lib/src/https.rs | 4 +--- lib/src/protocol/mux/h1.rs | 4 ++-- lib/src/protocol/mux/mod.rs | 2 +- lib/src/router/mod.rs | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/src/https.rs b/lib/src/https.rs index 76de96c60..98e4ea99e 100644 --- a/lib/src/https.rs +++ b/lib/src/https.rs @@ -466,10 +466,8 @@ impl ProxySession for HttpsSession { 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::Mux => incr!("https.upgrade.http.failed"), } return; } diff --git a/lib/src/protocol/mux/h1.rs b/lib/src/protocol/mux/h1.rs index 0ed80a03f..822bcaa36 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 diff --git a/lib/src/protocol/mux/mod.rs b/lib/src/protocol/mux/mod.rs index 618f2c86a..48c9afa64 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}, }; diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index 7cd066c76..9a8fd594a 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -937,7 +937,7 @@ mod tests { ), Ok(Route::Cluster { id: "examplewildcard".to_string(), - h2: true + h2: false }) ); assert_eq!(