1use std::{
51 collections::{BTreeMap, HashMap, HashSet},
52 env, fmt,
53 fs::{File, create_dir_all, metadata},
54 io::{ErrorKind, Read},
55 net::SocketAddr,
56 ops::Range,
57 path::PathBuf,
58};
59
60use crate::{
61 ObjectKind,
62 certificate::split_certificate_chain,
63 logging::AccessLogFormat,
64 proto::command::{
65 ActivateListener, AddBackend, AddCertificate, CertificateAndKey, Cluster,
66 CustomHttpAnswers, Header, HeaderPosition, HealthCheckConfig, HstsConfig,
67 HttpListenerConfig, HttpsListenerConfig, ListenerType, LoadBalancingAlgorithms,
68 LoadBalancingParams, LoadMetric, MetricDetail, MetricsConfiguration, PathRule,
69 ProtobufAccessLogFormat, ProxyProtocolConfig, RedirectPolicy, RedirectScheme, Request,
70 RequestHttpFrontend, RequestTcpFrontend, RulePosition, ServerConfig, ServerMetricsConfig,
71 SocketAddress, TcpListenerConfig, TlsVersion, WorkerRequest, request::RequestType,
72 },
73};
74
75pub const DEFAULT_CIPHER_LIST: [&str; 9] = [
83 "TLS13_AES_256_GCM_SHA384",
85 "TLS13_AES_128_GCM_SHA256",
86 "TLS13_CHACHA20_POLY1305_SHA256",
87 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
89 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
90 "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
91 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
92 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
93 "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
94];
95
96pub const DEFAULT_SIGNATURE_ALGORITHMS: [&str; 9] = [
97 "ECDSA+SHA256",
98 "ECDSA+SHA384",
99 "ECDSA+SHA512",
100 "RSA+SHA256",
101 "RSA+SHA384",
102 "RSA+SHA512",
103 "RSA-PSS+SHA256",
104 "RSA-PSS+SHA384",
105 "RSA-PSS+SHA512",
106];
107
108pub const DEFAULT_GROUPS_LIST: [&str; 4] = ["X25519MLKEM768", "x25519", "P-256", "P-384"];
109
110pub const DEFAULT_ALPN_PROTOCOLS: [&str; 2] = ["h2", "http/1.1"];
113
114pub const DEFAULT_FRONT_TIMEOUT: u32 = 60;
116
117pub const DEFAULT_BACK_TIMEOUT: u32 = 30;
119
120pub const DEFAULT_CONNECT_TIMEOUT: u32 = 3;
122
123pub const DEFAULT_REQUEST_TIMEOUT: u32 = 10;
125
126pub const DEFAULT_WORKER_TIMEOUT: u32 = 10;
128
129pub const DEFAULT_STICKY_NAME: &str = "SOZUBALANCEID";
131
132pub const DEFAULT_ZOMBIE_CHECK_INTERVAL: u32 = 1_800;
134
135pub const DEFAULT_ACCEPT_QUEUE_TIMEOUT: u32 = 60;
137
138pub const DEFAULT_HSTS_MAX_AGE: u32 = 31_536_000;
145
146pub const DEFAULT_EVICT_ON_QUEUE_FULL: bool = false;
152
153pub const DEFAULT_WORKER_COUNT: u16 = 2;
155
156pub const DEFAULT_WORKER_AUTOMATIC_RESTART: bool = true;
158
159pub const DEFAULT_AUTOMATIC_STATE_SAVE: bool = false;
161
162pub const DEFAULT_MIN_BUFFERS: u64 = 1;
164
165pub const DEFAULT_MAX_BUFFERS: u64 = 1_000;
167
168pub const DEFAULT_BUFFER_SIZE: u64 = 16_393;
170
171pub const H2_MIN_BUFFER_SIZE: u64 = 16_393;
181
182pub const DEFAULT_MAX_CONNECTIONS: usize = 10_000;
184
185pub const DEFAULT_COMMAND_BUFFER_SIZE: u64 = 1_000_000;
187
188pub const DEFAULT_MAX_COMMAND_BUFFER_SIZE: u64 = 2_000_000;
190
191pub const DEFAULT_DISABLE_CLUSTER_METRICS: bool = false;
193
194pub const MAX_LOOP_ITERATIONS: usize = 100000;
195
196pub const DEFAULT_SEND_TLS_13_TICKETS: u64 = 4;
201
202pub const DEFAULT_LOG_TARGET: &str = "stdout";
204
205pub const DEFAULT_MAX_CONNECTIONS_PER_IP: u64 = 0;
210
211pub const DEFAULT_RETRY_AFTER: u32 = 60;
217
218#[derive(Debug)]
219pub enum IncompatibilityKind {
220 PublicAddress,
221 ProxyProtocol,
222}
223
224#[derive(Debug)]
225pub enum MissingKind {
226 Field(String),
227 Protocol,
228 SavedState,
229}
230
231#[derive(thiserror::Error, Debug)]
232pub enum ConfigError {
233 #[error("env path not found: {0}")]
234 Env(String),
235 #[error("Could not open file {path_to_open}: {io_error}")]
236 FileOpen {
237 path_to_open: String,
238 io_error: std::io::Error,
239 },
240 #[error("Could not read file {path_to_read}: {io_error}")]
241 FileRead {
242 path_to_read: String,
243 io_error: std::io::Error,
244 },
245 #[error(
246 "the field {kind:?} of {object:?} with id or address {id} is incompatible with the rest of the options"
247 )]
248 Incompatible {
249 kind: IncompatibilityKind,
250 object: ObjectKind,
251 id: String,
252 },
253 #[error("Invalid '{0}' field for a TCP frontend")]
254 InvalidFrontendConfig(String),
255 #[error("invalid path {0:?}")]
256 InvalidPath(PathBuf),
257 #[error("listening address {0:?} is already used in the configuration")]
258 ListenerAddressAlreadyInUse(SocketAddr),
259 #[error("missing {0:?}")]
260 Missing(MissingKind),
261 #[error("could not get parent directory for file {0}")]
262 NoFileParent(String),
263 #[error("Could not get the path of the saved state")]
264 SaveStatePath(String),
265 #[error("Can not determine path to sozu socket: {0}")]
266 SocketPathError(String),
267 #[error("toml decoding error: {0}")]
268 DeserializeToml(String),
269 #[error("Can not set this frontend on a {0:?} listener")]
270 WrongFrontendProtocol(ListenerProtocol),
271 #[error("Can not build a {expected:?} listener from a {found:?} config")]
272 WrongListenerProtocol {
273 expected: ListenerProtocol,
274 found: Option<ListenerProtocol>,
275 },
276 #[error("Invalid ALPN protocol '{0}'. Valid values: \"h2\", \"http/1.1\"")]
277 InvalidAlpnProtocol(String),
278 #[error(
284 "disable_http11 = true is incompatible with alpn_protocols containing \"http/1.1\" \
285 on listener {address}. The proxy would advertise http/1.1 then refuse every \
286 connection that negotiates it. Drop \"http/1.1\" from alpn_protocols or unset \
287 disable_http11."
288 )]
289 DisableHttp11WithHttp11Alpn { address: String },
290 #[error(
297 "buffer_size = {buffer_size} is below the H2 minimum of {minimum} but \
298 {listeners} HTTPS listener(s) advertise H2 ALPN. The H2 mux deadlocks \
299 on full-size frames with smaller buffers. Raise buffer_size to >= {minimum} \
300 or remove \"h2\" from those listeners' alpn_protocols."
301 )]
302 BufferSizeTooSmallForH2 {
303 buffer_size: u64,
304 minimum: u64,
305 listeners: usize,
306 },
307 #[error(
311 "invalid redirect policy '{0}'. Valid values: \"forward\", \"permanent\", \"unauthorized\""
312 )]
313 InvalidRedirectPolicy(String),
314 #[error(
318 "invalid redirect scheme '{0}'. Valid values: \"use-same\", \"use-http\", \"use-https\""
319 )]
320 InvalidRedirectScheme(String),
321 #[error(
325 "invalid header position '{position}' at headers[{index}]. Valid values: \"request\", \"response\", \"both\""
326 )]
327 InvalidHeaderPosition { index: usize, position: String },
328 #[error(
335 "invalid header bytes in {field} at headers[{index}]: control characters \
336 (NUL / CR / LF / other C0) are forbidden in header keys and values"
337 )]
338 InvalidHeaderBytes { index: usize, field: &'static str },
339 #[error("invalid HSTS config at {0}: `enabled` is required when an [hsts] block is present")]
345 HstsEnabledRequired(String),
346 #[error(
351 "invalid HSTS config at {0}: HSTS is only valid on HTTPS listeners and frontends \
352 (RFC 6797 §7.2 forbids the header over plaintext HTTP)"
353 )]
354 HstsOnPlainHttp(String),
355}
356
357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
359#[serde(deny_unknown_fields)]
360pub struct ListenerBuilder {
361 pub address: SocketAddr,
362 pub protocol: Option<ListenerProtocol>,
363 pub public_address: Option<SocketAddr>,
364 pub answer_301: Option<String>,
365 pub answer_400: Option<String>,
366 pub answer_401: Option<String>,
367 pub answer_404: Option<String>,
368 pub answer_408: Option<String>,
369 pub answer_413: Option<String>,
370 pub answer_421: Option<String>,
373 pub answer_502: Option<String>,
374 pub answer_503: Option<String>,
375 pub answer_504: Option<String>,
376 pub answer_507: Option<String>,
377 pub answer_429: Option<String>,
382 pub tls_versions: Option<Vec<TlsVersion>>,
383 pub cipher_list: Option<Vec<String>>,
384 pub cipher_suites: Option<Vec<String>>,
385 pub groups_list: Option<Vec<String>>,
386 pub expect_proxy: Option<bool>,
387 #[serde(default = "default_sticky_name")]
388 pub sticky_name: String,
389 pub certificate: Option<String>,
390 pub certificate_chain: Option<String>,
391 pub key: Option<String>,
392 pub front_timeout: Option<u32>,
394 pub back_timeout: Option<u32>,
396 pub connect_timeout: Option<u32>,
398 pub request_timeout: Option<u32>,
400 pub config: Option<Config>,
402 pub send_tls13_tickets: Option<u64>,
406 pub alpn_protocols: Option<Vec<String>>,
409 pub h2_max_rst_stream_per_window: Option<u32>,
411 pub h2_max_ping_per_window: Option<u32>,
413 pub h2_max_settings_per_window: Option<u32>,
415 pub h2_max_empty_data_per_window: Option<u32>,
417 pub h2_max_window_update_stream0_per_window: Option<u32>,
421 pub sozu_id_header: Option<String>,
425 pub h2_max_continuation_frames: Option<u32>,
427 pub h2_max_glitch_count: Option<u32>,
429 pub h2_initial_connection_window: Option<u32>,
431 pub h2_max_concurrent_streams: Option<u32>,
433 pub h2_stream_shrink_ratio: Option<u32>,
435 pub h2_max_rst_stream_lifetime: Option<u64>,
438 pub h2_max_rst_stream_abusive_lifetime: Option<u64>,
441 pub h2_max_rst_stream_emitted_lifetime: Option<u64>,
445 pub h2_max_header_list_size: Option<u32>,
449 pub h2_max_header_table_size: Option<u32>,
453 pub h2_stream_idle_timeout_seconds: Option<u32>,
457 pub h2_graceful_shutdown_deadline_seconds: Option<u32>,
462 pub strict_sni_binding: Option<bool>,
468 pub disable_http11: Option<bool>,
473 pub elide_x_real_ip: Option<bool>,
477 pub send_x_real_ip: Option<bool>,
482 pub answers: Option<BTreeMap<String, String>>,
497 pub hsts: Option<FileHstsConfig>,
503}
504
505pub fn default_sticky_name() -> String {
506 DEFAULT_STICKY_NAME.to_string()
507}
508
509impl ListenerBuilder {
510 pub fn new_http(address: SocketAddress) -> ListenerBuilder {
513 Self::new(address, ListenerProtocol::Http)
514 }
515
516 pub fn new_tcp(address: SocketAddress) -> ListenerBuilder {
519 Self::new(address, ListenerProtocol::Tcp)
520 }
521
522 pub fn new_https(address: SocketAddress) -> ListenerBuilder {
525 Self::new(address, ListenerProtocol::Https)
526 }
527
528 fn new(address: SocketAddress, protocol: ListenerProtocol) -> ListenerBuilder {
530 ListenerBuilder {
531 address: address.into(),
532 answer_301: None,
533 answer_401: None,
534 answer_400: None,
535 answer_404: None,
536 answer_408: None,
537 answer_413: None,
538 answer_421: None,
539 answer_502: None,
540 answer_503: None,
541 answer_504: None,
542 answer_507: None,
543 answer_429: None,
544 back_timeout: None,
545 certificate_chain: None,
546 certificate: None,
547 cipher_list: None,
548 cipher_suites: None,
549 groups_list: None,
550 config: None,
551 connect_timeout: None,
552 expect_proxy: None,
553 front_timeout: None,
554 key: None,
555 protocol: Some(protocol),
556 public_address: None,
557 request_timeout: None,
558 send_tls13_tickets: None,
559 sticky_name: DEFAULT_STICKY_NAME.to_string(),
560 tls_versions: None,
561 alpn_protocols: None,
562 h2_max_rst_stream_per_window: None,
563 h2_max_ping_per_window: None,
564 h2_max_settings_per_window: None,
565 h2_max_empty_data_per_window: None,
566 h2_max_window_update_stream0_per_window: None,
567 sozu_id_header: None,
568 h2_max_continuation_frames: None,
569 h2_max_glitch_count: None,
570 h2_initial_connection_window: None,
571 h2_max_concurrent_streams: None,
572 h2_stream_shrink_ratio: None,
573 h2_max_rst_stream_lifetime: None,
574 h2_max_rst_stream_abusive_lifetime: None,
575 h2_max_rst_stream_emitted_lifetime: None,
576 h2_max_header_list_size: None,
577 h2_max_header_table_size: None,
578 h2_stream_idle_timeout_seconds: None,
579 h2_graceful_shutdown_deadline_seconds: None,
580 strict_sni_binding: None,
581 disable_http11: None,
582 elide_x_real_ip: None,
583 send_x_real_ip: None,
584 answers: None,
585 hsts: None,
586 }
587 }
588
589 pub fn with_public_address(&mut self, public_address: Option<SocketAddr>) -> &mut Self {
590 if let Some(address) = public_address {
591 self.public_address = Some(address);
592 }
593 self
594 }
595
596 pub fn with_answer_404_path<S>(&mut self, answer_404_path: Option<S>) -> &mut Self
597 where
598 S: ToString,
599 {
600 if let Some(path) = answer_404_path {
601 self.answer_404 = Some(path.to_string());
602 }
603 self
604 }
605
606 pub fn with_answer_503_path<S>(&mut self, answer_503_path: Option<S>) -> &mut Self
607 where
608 S: ToString,
609 {
610 if let Some(path) = answer_503_path {
611 self.answer_503 = Some(path.to_string());
612 }
613 self
614 }
615
616 pub fn with_tls_versions(&mut self, tls_versions: Vec<TlsVersion>) -> &mut Self {
617 self.tls_versions = Some(tls_versions);
618 self
619 }
620
621 pub fn with_cipher_list(&mut self, cipher_list: Option<Vec<String>>) -> &mut Self {
622 self.cipher_list = cipher_list;
623 self
624 }
625
626 pub fn with_cipher_suites(&mut self, cipher_suites: Option<Vec<String>>) -> &mut Self {
627 self.cipher_suites = cipher_suites;
628 self
629 }
630
631 pub fn with_alpn_protocols(&mut self, alpn_protocols: Option<Vec<String>>) -> &mut Self {
632 self.alpn_protocols = alpn_protocols;
633 self
634 }
635
636 pub fn with_elide_x_real_ip(&mut self, elide_x_real_ip: bool) -> &mut Self {
639 self.elide_x_real_ip = Some(elide_x_real_ip);
640 self
641 }
642
643 pub fn with_send_x_real_ip(&mut self, send_x_real_ip: bool) -> &mut Self {
647 self.send_x_real_ip = Some(send_x_real_ip);
648 self
649 }
650
651 pub fn with_expect_proxy(&mut self, expect_proxy: bool) -> &mut Self {
652 self.expect_proxy = Some(expect_proxy);
653 self
654 }
655
656 pub fn with_sticky_name<S>(&mut self, sticky_name: Option<S>) -> &mut Self
657 where
658 S: ToString,
659 {
660 if let Some(name) = sticky_name {
661 self.sticky_name = name.to_string();
662 }
663 self
664 }
665
666 pub fn with_certificate<S>(&mut self, certificate: S) -> &mut Self
667 where
668 S: ToString,
669 {
670 self.certificate = Some(certificate.to_string());
671 self
672 }
673
674 pub fn with_certificate_chain(&mut self, certificate_chain: String) -> &mut Self {
675 self.certificate = Some(certificate_chain);
676 self
677 }
678
679 pub fn with_key<S>(&mut self, key: String) -> &mut Self
680 where
681 S: ToString,
682 {
683 self.key = Some(key);
684 self
685 }
686
687 pub fn with_front_timeout(&mut self, front_timeout: Option<u32>) -> &mut Self {
688 self.front_timeout = front_timeout;
689 self
690 }
691
692 pub fn with_back_timeout(&mut self, back_timeout: Option<u32>) -> &mut Self {
693 self.back_timeout = back_timeout;
694 self
695 }
696
697 pub fn with_connect_timeout(&mut self, connect_timeout: Option<u32>) -> &mut Self {
698 self.connect_timeout = connect_timeout;
699 self
700 }
701
702 pub fn with_request_timeout(&mut self, request_timeout: Option<u32>) -> &mut Self {
703 self.request_timeout = request_timeout;
704 self
705 }
706
707 pub fn with_answer<S, P>(&mut self, code: S, path: P) -> &mut Self
712 where
713 S: ToString,
714 P: ToString,
715 {
716 self.answers
717 .get_or_insert_with(BTreeMap::new)
718 .insert(code.to_string(), path.to_string());
719 self
720 }
721
722 pub fn with_answers(&mut self, answers: BTreeMap<String, String>) -> &mut Self {
725 self.answers = Some(answers);
726 self
727 }
728
729 fn get_http_answers(&self) -> Result<Option<CustomHttpAnswers>, ConfigError> {
731 let http_answers = CustomHttpAnswers {
732 answer_301: read_http_answer_file(&self.answer_301)?,
733 answer_400: read_http_answer_file(&self.answer_400)?,
734 answer_401: read_http_answer_file(&self.answer_401)?,
735 answer_404: read_http_answer_file(&self.answer_404)?,
736 answer_408: read_http_answer_file(&self.answer_408)?,
737 answer_413: read_http_answer_file(&self.answer_413)?,
738 answer_421: read_http_answer_file(&self.answer_421)?,
739 answer_502: read_http_answer_file(&self.answer_502)?,
740 answer_503: read_http_answer_file(&self.answer_503)?,
741 answer_504: read_http_answer_file(&self.answer_504)?,
742 answer_507: read_http_answer_file(&self.answer_507)?,
743 answer_429: read_http_answer_file(&self.answer_429)?,
744 };
745 Ok(Some(http_answers))
746 }
747
748 fn get_listener_answers(&self) -> Result<BTreeMap<String, String>, ConfigError> {
756 let mut out = BTreeMap::new();
757
758 macro_rules! merge_legacy {
762 ($code:literal, $field:ident) => {
763 if let Some(body) = read_http_answer_file(&self.$field)? {
764 out.insert($code.to_owned(), body);
765 }
766 };
767 }
768 merge_legacy!("301", answer_301);
769 merge_legacy!("400", answer_400);
770 merge_legacy!("401", answer_401);
771 merge_legacy!("404", answer_404);
772 merge_legacy!("408", answer_408);
773 merge_legacy!("413", answer_413);
774 merge_legacy!("421", answer_421);
775 merge_legacy!("502", answer_502);
776 merge_legacy!("503", answer_503);
777 merge_legacy!("504", answer_504);
778 merge_legacy!("507", answer_507);
779 merge_legacy!("429", answer_429);
780
781 if let Some(map) = &self.answers {
782 let loaded = load_answers(map)?;
783 out.extend(loaded);
784 }
785 Ok(out)
786 }
787
788 fn assign_config_timeouts(&mut self, config: &Config) {
790 self.front_timeout = Some(self.front_timeout.unwrap_or(config.front_timeout));
791 self.back_timeout = Some(self.back_timeout.unwrap_or(config.back_timeout));
792 self.connect_timeout = Some(self.connect_timeout.unwrap_or(config.connect_timeout));
793 self.request_timeout = Some(self.request_timeout.unwrap_or(config.request_timeout));
794 }
795
796 pub fn to_http(&mut self, config: Option<&Config>) -> Result<HttpListenerConfig, ConfigError> {
798 if self.protocol != Some(ListenerProtocol::Http) {
799 return Err(ConfigError::WrongListenerProtocol {
800 expected: ListenerProtocol::Http,
801 found: self.protocol.to_owned(),
802 });
803 }
804
805 if self.hsts.is_some() {
811 return Err(ConfigError::HstsOnPlainHttp(format!(
812 "HTTP listener {}",
813 self.address
814 )));
815 }
816
817 if let Some(config) = config {
818 self.assign_config_timeouts(config);
819 }
820
821 let http_answers = self.get_http_answers()?;
822 let answers = self.get_listener_answers()?;
823
824 let configuration = HttpListenerConfig {
825 address: self.address.into(),
826 public_address: self.public_address.map(|a| a.into()),
827 expect_proxy: self.expect_proxy.unwrap_or(false),
828 sticky_name: self.sticky_name.clone(),
829 front_timeout: self.front_timeout.unwrap_or(DEFAULT_FRONT_TIMEOUT),
830 back_timeout: self.back_timeout.unwrap_or(DEFAULT_BACK_TIMEOUT),
831 connect_timeout: self.connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT),
832 request_timeout: self.request_timeout.unwrap_or(DEFAULT_REQUEST_TIMEOUT),
833 http_answers,
834 answers,
835 h2_max_rst_stream_per_window: self.h2_max_rst_stream_per_window,
836 h2_max_ping_per_window: self.h2_max_ping_per_window,
837 h2_max_settings_per_window: self.h2_max_settings_per_window,
838 h2_max_empty_data_per_window: self.h2_max_empty_data_per_window,
839 h2_max_window_update_stream0_per_window: self.h2_max_window_update_stream0_per_window,
840 h2_max_continuation_frames: self.h2_max_continuation_frames,
841 h2_max_glitch_count: self.h2_max_glitch_count,
842 h2_initial_connection_window: self.h2_initial_connection_window,
843 h2_max_concurrent_streams: self.h2_max_concurrent_streams,
844 h2_stream_shrink_ratio: self.h2_stream_shrink_ratio,
845 h2_max_rst_stream_lifetime: self.h2_max_rst_stream_lifetime,
846 h2_max_rst_stream_abusive_lifetime: self.h2_max_rst_stream_abusive_lifetime,
847 h2_max_rst_stream_emitted_lifetime: self.h2_max_rst_stream_emitted_lifetime,
848 h2_max_header_list_size: self.h2_max_header_list_size,
849 h2_max_header_table_size: self.h2_max_header_table_size,
850 h2_stream_idle_timeout_seconds: self.h2_stream_idle_timeout_seconds,
851 h2_graceful_shutdown_deadline_seconds: self.h2_graceful_shutdown_deadline_seconds,
852 sozu_id_header: self.sozu_id_header.clone(),
853 elide_x_real_ip: Some(self.elide_x_real_ip.unwrap_or(false)),
854 send_x_real_ip: Some(self.send_x_real_ip.unwrap_or(false)),
855 ..Default::default()
856 };
857
858 Ok(configuration)
859 }
860
861 pub fn to_tls(&mut self, config: Option<&Config>) -> Result<HttpsListenerConfig, ConfigError> {
863 if self.protocol != Some(ListenerProtocol::Https) {
864 return Err(ConfigError::WrongListenerProtocol {
865 expected: ListenerProtocol::Https,
866 found: self.protocol.to_owned(),
867 });
868 }
869
870 let default_cipher_list = DEFAULT_CIPHER_LIST.into_iter().map(String::from).collect();
871
872 let cipher_list = self.cipher_list.clone().unwrap_or(default_cipher_list);
873
874 let cipher_suites = self
875 .cipher_suites
876 .clone()
877 .unwrap_or_else(|| DEFAULT_CIPHER_LIST.into_iter().map(String::from).collect());
878
879 let signature_algorithms: Vec<String> = DEFAULT_SIGNATURE_ALGORITHMS
880 .into_iter()
881 .map(String::from)
882 .collect();
883
884 let groups_list = self
885 .groups_list
886 .clone()
887 .unwrap_or_else(|| DEFAULT_GROUPS_LIST.into_iter().map(String::from).collect());
888
889 let alpn_protocols: Vec<String> = match &self.alpn_protocols {
890 Some(protos) if !protos.is_empty() => {
891 for proto in protos {
892 match proto.as_str() {
893 "h2" | "http/1.1" => {}
894 other => return Err(ConfigError::InvalidAlpnProtocol(other.to_owned())),
895 }
896 }
897 if self.disable_http11.unwrap_or(false) && protos.iter().any(|p| p == "http/1.1") {
902 return Err(ConfigError::DisableHttp11WithHttp11Alpn {
903 address: self.address.to_string(),
904 });
905 }
906 if !protos.iter().any(|p| p == "http/1.1") {
907 warn!(
908 "ALPN protocols do not include 'http/1.1'. Clients without H2 support will fail TLS negotiation."
909 );
910 }
911 let mut seen = std::collections::HashSet::new();
913 protos
914 .iter()
915 .filter(|p| seen.insert(p.as_str()))
916 .cloned()
917 .collect()
918 }
919 _ => {
920 if self.disable_http11.unwrap_or(false)
924 && DEFAULT_ALPN_PROTOCOLS.contains(&"http/1.1")
925 {
926 return Err(ConfigError::DisableHttp11WithHttp11Alpn {
927 address: self.address.to_string(),
928 });
929 }
930 DEFAULT_ALPN_PROTOCOLS
931 .iter()
932 .map(|s| s.to_string())
933 .collect()
934 }
935 };
936
937 let versions = match self.tls_versions {
938 None => vec![TlsVersion::TlsV12 as i32, TlsVersion::TlsV13 as i32],
939 Some(ref v) => v.iter().map(|v| *v as i32).collect(),
940 };
941
942 let key = self.key.as_ref().and_then(|path| {
943 Config::load_file(path)
944 .map_err(|e| {
945 error!("cannot load key at path '{}': {:?}", path, e);
946 e
947 })
948 .ok()
949 });
950 let certificate = self.certificate.as_ref().and_then(|path| {
951 Config::load_file(path)
952 .map_err(|e| {
953 error!("cannot load certificate at path '{}': {:?}", path, e);
954 e
955 })
956 .ok()
957 });
958 let certificate_chain = self
959 .certificate_chain
960 .as_ref()
961 .and_then(|path| {
962 Config::load_file(path)
963 .map_err(|e| {
964 error!("cannot load certificate chain at path '{}': {:?}", path, e);
965 e
966 })
967 .ok()
968 })
969 .map(split_certificate_chain)
970 .unwrap_or_default();
971
972 let http_answers = self.get_http_answers()?;
973 let answers = self.get_listener_answers()?;
974
975 if let Some(config) = config {
976 self.assign_config_timeouts(config);
977 }
978
979 let https_listener_config = HttpsListenerConfig {
980 address: self.address.into(),
981 sticky_name: self.sticky_name.clone(),
982 public_address: self.public_address.map(|a| a.into()),
983 cipher_list,
984 versions,
985 expect_proxy: self.expect_proxy.unwrap_or(false),
986 key,
987 certificate,
988 certificate_chain,
989 front_timeout: self.front_timeout.unwrap_or(DEFAULT_FRONT_TIMEOUT),
990 back_timeout: self.back_timeout.unwrap_or(DEFAULT_BACK_TIMEOUT),
991 connect_timeout: self.connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT),
992 request_timeout: self.request_timeout.unwrap_or(DEFAULT_REQUEST_TIMEOUT),
993 cipher_suites,
994 signature_algorithms,
995 groups_list,
996 active: false,
997 send_tls13_tickets: self
998 .send_tls13_tickets
999 .unwrap_or(DEFAULT_SEND_TLS_13_TICKETS),
1000 http_answers,
1001 answers,
1002 alpn_protocols,
1003 h2_max_rst_stream_per_window: self.h2_max_rst_stream_per_window,
1004 h2_max_ping_per_window: self.h2_max_ping_per_window,
1005 h2_max_settings_per_window: self.h2_max_settings_per_window,
1006 h2_max_empty_data_per_window: self.h2_max_empty_data_per_window,
1007 h2_max_window_update_stream0_per_window: self.h2_max_window_update_stream0_per_window,
1008 h2_max_continuation_frames: self.h2_max_continuation_frames,
1009 h2_max_glitch_count: self.h2_max_glitch_count,
1010 h2_initial_connection_window: self.h2_initial_connection_window,
1011 h2_max_concurrent_streams: self.h2_max_concurrent_streams,
1012 h2_stream_shrink_ratio: self.h2_stream_shrink_ratio,
1013 h2_max_rst_stream_lifetime: self.h2_max_rst_stream_lifetime,
1014 h2_max_rst_stream_abusive_lifetime: self.h2_max_rst_stream_abusive_lifetime,
1015 h2_max_rst_stream_emitted_lifetime: self.h2_max_rst_stream_emitted_lifetime,
1016 h2_max_header_list_size: self.h2_max_header_list_size,
1017 h2_max_header_table_size: self.h2_max_header_table_size,
1018 strict_sni_binding: self.strict_sni_binding,
1019 disable_http11: self.disable_http11,
1020 h2_stream_idle_timeout_seconds: self.h2_stream_idle_timeout_seconds,
1021 h2_graceful_shutdown_deadline_seconds: self.h2_graceful_shutdown_deadline_seconds,
1022 sozu_id_header: self.sozu_id_header.clone(),
1023 elide_x_real_ip: Some(self.elide_x_real_ip.unwrap_or(false)),
1024 send_x_real_ip: Some(self.send_x_real_ip.unwrap_or(false)),
1025 hsts: match self.hsts.as_ref() {
1026 Some(h) => Some(h.to_proto("listener")?),
1027 None => None,
1028 },
1029 };
1030
1031 Ok(https_listener_config)
1032 }
1033
1034 pub fn to_tcp(&mut self, config: Option<&Config>) -> Result<TcpListenerConfig, ConfigError> {
1036 if self.protocol != Some(ListenerProtocol::Tcp) {
1037 return Err(ConfigError::WrongListenerProtocol {
1038 expected: ListenerProtocol::Tcp,
1039 found: self.protocol.to_owned(),
1040 });
1041 }
1042
1043 if let Some(config) = config {
1044 self.assign_config_timeouts(config);
1045 }
1046
1047 Ok(TcpListenerConfig {
1048 address: self.address.into(),
1049 public_address: self.public_address.map(|a| a.into()),
1050 expect_proxy: self.expect_proxy.unwrap_or(false),
1051 front_timeout: self.front_timeout.unwrap_or(DEFAULT_FRONT_TIMEOUT),
1052 back_timeout: self.back_timeout.unwrap_or(DEFAULT_BACK_TIMEOUT),
1053 connect_timeout: self.connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT),
1054 active: false,
1055 })
1056 }
1057}
1058
1059fn read_http_answer_file(path: &Option<String>) -> Result<Option<String>, ConfigError> {
1061 match path {
1062 Some(path) => {
1063 let mut content = String::new();
1064 let mut file = File::open(path).map_err(|io_error| ConfigError::FileOpen {
1065 path_to_open: path.to_owned(),
1066 io_error,
1067 })?;
1068
1069 file.read_to_string(&mut content)
1070 .map_err(|io_error| ConfigError::FileRead {
1071 path_to_read: path.to_owned(),
1072 io_error,
1073 })?;
1074
1075 Ok(Some(content))
1076 }
1077 None => Ok(None),
1078 }
1079}
1080
1081pub fn resolve_answer_source(value: &str) -> Result<String, ConfigError> {
1104 if let Some(path) = value.strip_prefix("file://") {
1105 let mut content = String::new();
1106 let mut file = File::open(path).map_err(|io_error| ConfigError::FileOpen {
1107 path_to_open: path.to_owned(),
1108 io_error,
1109 })?;
1110 file.read_to_string(&mut content)
1111 .map_err(|io_error| ConfigError::FileRead {
1112 path_to_read: path.to_owned(),
1113 io_error,
1114 })?;
1115 return Ok(content);
1116 }
1117 Ok(value.to_owned())
1118}
1119
1120pub fn load_answers(
1137 answers: &BTreeMap<String, String>,
1138) -> Result<BTreeMap<String, String>, ConfigError> {
1139 let mut out = BTreeMap::new();
1140 for (code, value) in answers {
1141 if value.is_empty() {
1142 continue;
1143 }
1144 out.insert(code.to_owned(), resolve_answer_source(value)?);
1145 }
1146 Ok(out)
1147}
1148
1149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
1162#[serde(rename_all = "lowercase")]
1163pub enum MetricDetailLevel {
1164 Process,
1165 Frontend,
1166 Cluster,
1167 Backend,
1168}
1169
1170impl Default for MetricDetailLevel {
1171 fn default() -> Self {
1172 Self::Cluster
1175 }
1176}
1177
1178impl From<MetricDetailLevel> for MetricDetail {
1179 fn from(level: MetricDetailLevel) -> Self {
1180 match level {
1181 MetricDetailLevel::Process => MetricDetail::DetailProcess,
1182 MetricDetailLevel::Frontend => MetricDetail::DetailFrontend,
1183 MetricDetailLevel::Cluster => MetricDetail::DetailCluster,
1184 MetricDetailLevel::Backend => MetricDetail::DetailBackend,
1185 }
1186 }
1187}
1188
1189impl From<MetricDetail> for MetricDetailLevel {
1190 fn from(detail: MetricDetail) -> Self {
1194 match detail {
1195 MetricDetail::DetailProcess => MetricDetailLevel::Process,
1196 MetricDetail::DetailFrontend => MetricDetailLevel::Frontend,
1197 MetricDetail::DetailCluster => MetricDetailLevel::Cluster,
1198 MetricDetail::DetailBackend => MetricDetailLevel::Backend,
1199 }
1200 }
1201}
1202
1203#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1204#[serde(deny_unknown_fields)]
1205pub struct MetricsConfig {
1206 pub address: SocketAddr,
1207 #[serde(default)]
1208 pub tagged_metrics: bool,
1209 #[serde(default)]
1210 pub prefix: Option<String>,
1211 #[serde(default)]
1214 pub detail: MetricDetailLevel,
1215}
1216
1217#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1218#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1219#[serde(deny_unknown_fields)]
1220pub enum PathRuleType {
1221 Prefix,
1222 Regex,
1223 Equals,
1224}
1225
1226#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1227#[serde(deny_unknown_fields)]
1228pub struct FileClusterFrontendConfig {
1229 pub address: SocketAddr,
1230 pub hostname: Option<String>,
1231 pub path: Option<String>,
1233 pub path_type: Option<PathRuleType>,
1235 pub method: Option<String>,
1236 pub certificate: Option<String>,
1237 pub key: Option<String>,
1238 pub certificate_chain: Option<String>,
1239 #[serde(default)]
1240 pub tls_versions: Vec<TlsVersion>,
1241 #[serde(default)]
1242 pub position: RulePosition,
1243 pub tags: Option<BTreeMap<String, String>>,
1244 pub redirect: Option<String>,
1249 pub redirect_scheme: Option<String>,
1253 pub redirect_template: Option<String>,
1257 pub rewrite_host: Option<String>,
1260 pub rewrite_path: Option<String>,
1262 pub rewrite_port: Option<u32>,
1264 pub required_auth: Option<bool>,
1268 pub headers: Option<Vec<HeaderEditConfig>>,
1272 pub hsts: Option<FileHstsConfig>,
1277}
1278
1279#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1286#[serde(deny_unknown_fields)]
1287pub struct HeaderEditConfig {
1288 pub position: String,
1289 pub key: String,
1290 pub value: String,
1291}
1292
1293#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1304#[serde(deny_unknown_fields)]
1305pub struct FileHstsConfig {
1306 pub enabled: Option<bool>,
1309 pub max_age: Option<u32>,
1314 pub include_subdomains: Option<bool>,
1316 pub preload: Option<bool>,
1319 pub force_replace_backend: Option<bool>,
1327}
1328
1329impl FileHstsConfig {
1330 pub fn to_proto(&self, scope: &str) -> Result<HstsConfig, ConfigError> {
1348 let enabled = match self.enabled {
1349 Some(v) => v,
1350 None => return Err(ConfigError::HstsEnabledRequired(scope.to_owned())),
1351 };
1352
1353 let max_age = match (enabled, self.max_age) {
1354 (true, None) => Some(DEFAULT_HSTS_MAX_AGE),
1355 (_, m) => m,
1356 };
1357
1358 if let Some(value) = max_age
1359 && value > 0
1360 && value < 86_400
1361 {
1362 warn!(
1363 "HSTS max_age = {}s on {} is below 1 day — this is almost certainly a \
1364 misconfiguration. RFC 6797 §11.4 reserves max_age = 0 as the explicit kill \
1365 switch.",
1366 value, scope
1367 );
1368 }
1369
1370 let include_subdomains = self.include_subdomains;
1371 let preload = self.preload;
1372
1373 if matches!(preload, Some(true)) {
1374 let max_age_value = max_age.unwrap_or(0);
1375 if max_age_value < DEFAULT_HSTS_MAX_AGE {
1376 warn!(
1377 "HSTS preload = true on {} with max_age = {}s; the Chrome HSTS preload \
1378 list requires max_age >= {} (https://hstspreload.org/).",
1379 scope, max_age_value, DEFAULT_HSTS_MAX_AGE
1380 );
1381 }
1382 if include_subdomains != Some(true) {
1383 warn!(
1384 "HSTS preload = true on {} without include_subdomains = true; the Chrome \
1385 HSTS preload list requires includeSubDomains \
1386 (https://hstspreload.org/).",
1387 scope
1388 );
1389 }
1390 }
1391
1392 Ok(HstsConfig {
1393 enabled: Some(enabled),
1394 max_age,
1395 include_subdomains,
1396 preload,
1397 force_replace_backend: self.force_replace_backend,
1398 })
1399 }
1400}
1401
1402impl FileClusterFrontendConfig {
1403 pub fn to_tcp_front(&self) -> Result<TcpFrontendConfig, ConfigError> {
1404 if self.hostname.is_some() {
1405 return Err(ConfigError::InvalidFrontendConfig("hostname".to_string()));
1406 }
1407 if self.path.is_some() {
1408 return Err(ConfigError::InvalidFrontendConfig(
1409 "path_prefix".to_string(),
1410 ));
1411 }
1412 if self.certificate.is_some() {
1413 return Err(ConfigError::InvalidFrontendConfig(
1414 "certificate".to_string(),
1415 ));
1416 }
1417 if self.hostname.is_some() {
1418 return Err(ConfigError::InvalidFrontendConfig("hostname".to_string()));
1419 }
1420 if self.certificate_chain.is_some() {
1421 return Err(ConfigError::InvalidFrontendConfig(
1422 "certificate_chain".to_string(),
1423 ));
1424 }
1425
1426 Ok(TcpFrontendConfig {
1427 address: self.address,
1428 tags: self.tags.clone(),
1429 })
1430 }
1431
1432 pub fn to_http_front(&self, _cluster_id: &str) -> Result<HttpFrontendConfig, ConfigError> {
1433 let hostname = match &self.hostname {
1434 Some(hostname) => hostname.to_owned(),
1435 None => {
1436 return Err(ConfigError::Missing(MissingKind::Field(
1437 "hostname".to_string(),
1438 )));
1439 }
1440 };
1441
1442 let key_opt = match self.key.as_ref() {
1443 None => None,
1444 Some(path) => {
1445 let key = Config::load_file(path)?;
1446 Some(key)
1447 }
1448 };
1449
1450 let certificate_opt = match self.certificate.as_ref() {
1451 None => None,
1452 Some(path) => {
1453 let certificate = Config::load_file(path)?;
1454 Some(certificate)
1455 }
1456 };
1457
1458 let certificate_chain = match self.certificate_chain.as_ref() {
1459 None => None,
1460 Some(path) => {
1461 let certificate_chain = Config::load_file(path)?;
1462 Some(split_certificate_chain(certificate_chain))
1463 }
1464 };
1465
1466 let path = match (self.path.as_ref(), self.path_type.as_ref()) {
1467 (None, _) => PathRule::prefix("".to_string()),
1468 (Some(s), Some(PathRuleType::Prefix)) => PathRule::prefix(s.to_string()),
1469 (Some(s), Some(PathRuleType::Regex)) => PathRule::regex(s.to_string()),
1470 (Some(s), Some(PathRuleType::Equals)) => PathRule::equals(s.to_string()),
1471 (Some(s), None) => PathRule::prefix(s.clone()),
1472 };
1473
1474 let redirect = match self.redirect.as_deref() {
1475 Some(v) => Some(parse_redirect_policy(v)?),
1476 None => None,
1477 };
1478 let redirect_scheme = match self.redirect_scheme.as_deref() {
1479 Some(v) => Some(parse_redirect_scheme(v)?),
1480 None => None,
1481 };
1482
1483 let headers = match self.headers.as_ref() {
1484 Some(entries) => {
1485 let mut out = Vec::with_capacity(entries.len());
1486 for (index, entry) in entries.iter().enumerate() {
1487 out.push(parse_header_edit(index, entry)?);
1488 }
1489 out
1490 }
1491 None => Vec::new(),
1492 };
1493
1494 let frontend_serves_https = key_opt.is_some() && certificate_opt.is_some();
1501 let hsts = match self.hsts.as_ref() {
1502 Some(h) => {
1503 if !frontend_serves_https {
1504 return Err(ConfigError::HstsOnPlainHttp(format!(
1505 "frontend {_cluster_id}/{hostname}"
1506 )));
1507 }
1508 Some(h.to_proto(&format!("frontend {_cluster_id}/{hostname}"))?)
1509 }
1510 None => None,
1511 };
1512
1513 Ok(HttpFrontendConfig {
1514 address: self.address,
1515 hostname,
1516 certificate: certificate_opt,
1517 key: key_opt,
1518 certificate_chain,
1519 tls_versions: self.tls_versions.clone(),
1520 position: self.position,
1521 path,
1522 method: self.method.clone(),
1523 tags: self.tags.clone(),
1524 redirect,
1525 redirect_scheme,
1526 redirect_template: self.redirect_template.clone(),
1527 rewrite_host: self.rewrite_host.clone(),
1528 rewrite_path: self.rewrite_path.clone(),
1529 rewrite_port: self.rewrite_port,
1530 required_auth: self.required_auth,
1531 headers,
1532 hsts,
1533 })
1534 }
1535}
1536
1537pub(crate) fn parse_redirect_policy(value: &str) -> Result<RedirectPolicy, ConfigError> {
1539 match value.to_ascii_lowercase().as_str() {
1540 "forward" => Ok(RedirectPolicy::Forward),
1541 "permanent" => Ok(RedirectPolicy::Permanent),
1542 "unauthorized" => Ok(RedirectPolicy::Unauthorized),
1543 _ => Err(ConfigError::InvalidRedirectPolicy(value.to_owned())),
1544 }
1545}
1546
1547pub(crate) fn parse_redirect_scheme(value: &str) -> Result<RedirectScheme, ConfigError> {
1549 match value.to_ascii_lowercase().as_str() {
1550 "use-same" | "use_same" => Ok(RedirectScheme::UseSame),
1551 "use-http" | "use_http" => Ok(RedirectScheme::UseHttp),
1552 "use-https" | "use_https" => Ok(RedirectScheme::UseHttps),
1553 _ => Err(ConfigError::InvalidRedirectScheme(value.to_owned())),
1554 }
1555}
1556
1557pub(crate) fn parse_header_edit(
1564 index: usize,
1565 entry: &HeaderEditConfig,
1566) -> Result<Header, ConfigError> {
1567 let position = match entry.position.to_ascii_lowercase().as_str() {
1568 "request" => HeaderPosition::Request,
1569 "response" => HeaderPosition::Response,
1570 "both" => HeaderPosition::Both,
1571 _ => {
1572 return Err(ConfigError::InvalidHeaderPosition {
1573 index,
1574 position: entry.position.clone(),
1575 });
1576 }
1577 };
1578 if !header_name_is_valid_token(entry.key.as_bytes()) {
1579 return Err(ConfigError::InvalidHeaderBytes {
1580 index,
1581 field: "key",
1582 });
1583 }
1584 if header_value_contains_forbidden_controls(entry.value.as_bytes()) {
1585 return Err(ConfigError::InvalidHeaderBytes {
1586 index,
1587 field: "value",
1588 });
1589 }
1590 Ok(Header {
1591 position: position as i32,
1592 key: entry.key.clone(),
1593 val: entry.value.clone(),
1594 })
1595}
1596
1597pub(crate) fn header_name_is_valid_token(bytes: &[u8]) -> bool {
1605 if bytes.is_empty() {
1606 return false;
1607 }
1608 bytes.iter().all(|&b| is_tchar(b))
1609}
1610
1611fn is_tchar(b: u8) -> bool {
1614 b.is_ascii_alphanumeric()
1615 || matches!(
1616 b,
1617 b'!' | b'#'
1618 | b'$'
1619 | b'%'
1620 | b'&'
1621 | b'\''
1622 | b'*'
1623 | b'+'
1624 | b'-'
1625 | b'.'
1626 | b'^'
1627 | b'_'
1628 | b'`'
1629 | b'|'
1630 | b'~'
1631 )
1632}
1633
1634pub(crate) fn header_value_contains_forbidden_controls(bytes: &[u8]) -> bool {
1642 bytes
1643 .iter()
1644 .any(|&b| matches!(b, 0x00..=0x08 | 0x0A..=0x1F | 0x7F))
1645}
1646
1647#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1648#[serde(deny_unknown_fields, rename_all = "lowercase")]
1649pub enum ListenerProtocol {
1650 Http,
1651 Https,
1652 Tcp,
1653}
1654
1655#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1656#[serde(deny_unknown_fields, rename_all = "lowercase")]
1657pub enum FileClusterProtocolConfig {
1658 Http,
1659 Tcp,
1660}
1661
1662fn default_health_check_interval() -> u32 {
1663 10
1664}
1665fn default_health_check_timeout() -> u32 {
1666 5
1667}
1668fn default_health_check_threshold() -> u32 {
1669 3
1670}
1671
1672#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1673#[serde(deny_unknown_fields)]
1674pub struct FileHealthCheckConfig {
1675 pub uri: String,
1676 #[serde(default = "default_health_check_interval")]
1677 pub interval: u32,
1678 #[serde(default = "default_health_check_timeout")]
1679 pub timeout: u32,
1680 #[serde(default = "default_health_check_threshold")]
1681 pub healthy_threshold: u32,
1682 #[serde(default = "default_health_check_threshold")]
1683 pub unhealthy_threshold: u32,
1684 #[serde(default)]
1685 pub expected_status: u32,
1686}
1687
1688impl FileHealthCheckConfig {
1689 pub fn to_proto(&self) -> HealthCheckConfig {
1690 HealthCheckConfig {
1691 uri: self.uri.to_owned(),
1692 interval: self.interval,
1693 timeout: self.timeout,
1694 healthy_threshold: self.healthy_threshold,
1695 unhealthy_threshold: self.unhealthy_threshold,
1696 expected_status: self.expected_status,
1697 }
1698 }
1699}
1700
1701pub fn validate_health_check_config(cfg: &HealthCheckConfig) -> Result<(), &'static str> {
1712 if cfg.interval == 0 {
1713 return Err("health check interval must be > 0");
1714 }
1715 if cfg.timeout == 0 {
1716 return Err("health check timeout must be > 0");
1717 }
1718 if cfg.healthy_threshold == 0 {
1719 return Err("health check healthy_threshold must be > 0");
1720 }
1721 if cfg.unhealthy_threshold == 0 {
1722 return Err("health check unhealthy_threshold must be > 0");
1723 }
1724 if !cfg.uri.starts_with('/') {
1725 return Err("health check URI must start with '/'");
1726 }
1727 if cfg
1728 .uri
1729 .bytes()
1730 .any(|b| b == b'\r' || b == b'\n' || b == 0 || (b < 0x20 && b != b'\t'))
1731 {
1732 return Err("health check URI must not contain CR, LF, NUL, or other C0 control bytes");
1733 }
1734 Ok(())
1735}
1736
1737#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1738#[serde(deny_unknown_fields)]
1739pub struct FileClusterConfig {
1740 pub frontends: Vec<FileClusterFrontendConfig>,
1741 pub backends: Vec<BackendConfig>,
1742 pub protocol: FileClusterProtocolConfig,
1743 pub sticky_session: Option<bool>,
1744 pub https_redirect: Option<bool>,
1745 #[serde(default)]
1746 pub send_proxy: Option<bool>,
1747 #[serde(default)]
1748 pub load_balancing: LoadBalancingAlgorithms,
1749 pub answer_503: Option<String>,
1750 #[serde(default)]
1751 pub load_metric: Option<LoadMetric>,
1752 pub http2: Option<bool>,
1755 pub answers: Option<BTreeMap<String, String>>,
1766 pub https_redirect_port: Option<u32>,
1771 pub authorized_hashes: Option<Vec<String>>,
1776 pub www_authenticate: Option<String>,
1780 pub max_connections_per_ip: Option<u64>,
1787 pub retry_after: Option<u32>,
1793 #[serde(default)]
1797 pub health_check: Option<FileHealthCheckConfig>,
1798}
1799
1800#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1801#[serde(deny_unknown_fields)]
1802pub struct BackendConfig {
1803 pub address: SocketAddr,
1804 pub weight: Option<u8>,
1805 pub sticky_id: Option<String>,
1806 pub backup: Option<bool>,
1807 pub backend_id: Option<String>,
1808}
1809
1810impl FileClusterConfig {
1811 pub fn to_cluster_config(
1812 self,
1813 cluster_id: &str,
1814 expect_proxy: &HashSet<SocketAddr>,
1815 ) -> Result<ClusterConfig, ConfigError> {
1816 match self.protocol {
1817 FileClusterProtocolConfig::Tcp => {
1818 let mut has_expect_proxy = None;
1819 let mut frontends = Vec::new();
1820 for f in self.frontends {
1821 if expect_proxy.contains(&f.address) {
1822 match has_expect_proxy {
1823 Some(true) => {}
1824 Some(false) => {
1825 return Err(ConfigError::Incompatible {
1826 object: ObjectKind::Cluster,
1827 id: cluster_id.to_owned(),
1828 kind: IncompatibilityKind::ProxyProtocol,
1829 });
1830 }
1831 None => has_expect_proxy = Some(true),
1832 }
1833 } else {
1834 match has_expect_proxy {
1835 Some(false) => {}
1836 Some(true) => {
1837 return Err(ConfigError::Incompatible {
1838 object: ObjectKind::Cluster,
1839 id: cluster_id.to_owned(),
1840 kind: IncompatibilityKind::ProxyProtocol,
1841 });
1842 }
1843 None => has_expect_proxy = Some(false),
1844 }
1845 }
1846 let tcp_frontend = f.to_tcp_front()?;
1847 frontends.push(tcp_frontend);
1848 }
1849
1850 let send_proxy = self.send_proxy.unwrap_or(false);
1851 let expect_proxy = has_expect_proxy.unwrap_or(false);
1852 let proxy_protocol = match (send_proxy, expect_proxy) {
1853 (true, true) => Some(ProxyProtocolConfig::RelayHeader),
1854 (true, false) => Some(ProxyProtocolConfig::SendHeader),
1855 (false, true) => Some(ProxyProtocolConfig::ExpectHeader),
1856 _ => None,
1857 };
1858
1859 let answers = match self.answers.as_ref() {
1860 Some(map) => load_answers(map)?,
1861 None => BTreeMap::new(),
1862 };
1863
1864 Ok(ClusterConfig::Tcp(TcpClusterConfig {
1865 cluster_id: cluster_id.to_string(),
1866 frontends,
1867 backends: self.backends,
1868 proxy_protocol,
1869 load_balancing: self.load_balancing,
1870 load_metric: self.load_metric,
1871 answers,
1872 https_redirect_port: self.https_redirect_port,
1873 authorized_hashes: self.authorized_hashes.unwrap_or_default(),
1874 www_authenticate: self.www_authenticate,
1875 max_connections_per_ip: self.max_connections_per_ip,
1876 retry_after: self.retry_after,
1877 health_check: self.health_check.as_ref().map(|hc| hc.to_proto()),
1878 }))
1879 }
1880 FileClusterProtocolConfig::Http => {
1881 let mut frontends = Vec::new();
1882 for frontend in self.frontends {
1883 let http_frontend = frontend.to_http_front(cluster_id)?;
1884 frontends.push(http_frontend);
1885 }
1886
1887 let answer_503 = self.answer_503.as_ref().and_then(|path| {
1888 Config::load_file(path)
1889 .map_err(|e| {
1890 error!("cannot load 503 error page at path '{}': {:?}", path, e);
1891 e
1892 })
1893 .ok()
1894 });
1895
1896 let answers = match self.answers.as_ref() {
1897 Some(map) => load_answers(map)?,
1898 None => BTreeMap::new(),
1899 };
1900
1901 Ok(ClusterConfig::Http(HttpClusterConfig {
1902 cluster_id: cluster_id.to_string(),
1903 frontends,
1904 backends: self.backends,
1905 sticky_session: self.sticky_session.unwrap_or(false),
1906 https_redirect: self.https_redirect.unwrap_or(false),
1907 load_balancing: self.load_balancing,
1908 load_metric: self.load_metric,
1909 answer_503,
1910 http2: self.http2,
1911 answers,
1912 https_redirect_port: self.https_redirect_port,
1913 authorized_hashes: self.authorized_hashes.unwrap_or_default(),
1914 www_authenticate: self.www_authenticate,
1915 max_connections_per_ip: self.max_connections_per_ip,
1916 retry_after: self.retry_after,
1917 health_check: self.health_check.as_ref().map(|hc| hc.to_proto()),
1918 }))
1919 }
1920 }
1921 }
1922}
1923
1924#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1925#[serde(deny_unknown_fields)]
1926pub struct HttpFrontendConfig {
1927 pub address: SocketAddr,
1928 pub hostname: String,
1929 pub path: PathRule,
1930 pub method: Option<String>,
1931 pub certificate: Option<String>,
1932 pub key: Option<String>,
1933 pub certificate_chain: Option<Vec<String>>,
1934 #[serde(default)]
1935 pub tls_versions: Vec<TlsVersion>,
1936 #[serde(default)]
1937 pub position: RulePosition,
1938 pub tags: Option<BTreeMap<String, String>>,
1939 #[serde(default)]
1941 pub redirect: Option<RedirectPolicy>,
1942 #[serde(default)]
1944 pub redirect_scheme: Option<RedirectScheme>,
1945 #[serde(default)]
1946 pub redirect_template: Option<String>,
1947 #[serde(default)]
1948 pub rewrite_host: Option<String>,
1949 #[serde(default)]
1950 pub rewrite_path: Option<String>,
1951 #[serde(default)]
1952 pub rewrite_port: Option<u32>,
1953 #[serde(default)]
1954 pub required_auth: Option<bool>,
1955 #[serde(default)]
1958 pub headers: Vec<Header>,
1959 #[serde(default)]
1962 pub hsts: Option<HstsConfig>,
1963}
1964
1965impl HttpFrontendConfig {
1966 pub fn generate_requests(&self, cluster_id: &str) -> Vec<Request> {
1967 let mut v = Vec::new();
1968
1969 let tags = self.tags.clone().unwrap_or_default();
1970
1971 if self.key.is_some() && self.certificate.is_some() {
1972 v.push(
1973 RequestType::AddCertificate(AddCertificate {
1974 address: self.address.into(),
1975 certificate: CertificateAndKey {
1976 key: self.key.clone().unwrap(),
1977 certificate: self.certificate.clone().unwrap(),
1978 certificate_chain: self.certificate_chain.clone().unwrap_or_default(),
1979 versions: self.tls_versions.iter().map(|v| *v as i32).collect(),
1980 names: vec![],
1985 },
1986 expired_at: None,
1987 })
1988 .into(),
1989 );
1990
1991 v.push(
1992 RequestType::AddHttpsFrontend(RequestHttpFrontend {
1993 cluster_id: Some(cluster_id.to_string()),
1994 address: self.address.into(),
1995 hostname: self.hostname.clone(),
1996 path: self.path.clone(),
1997 method: self.method.clone(),
1998 position: self.position.into(),
1999 tags,
2000 redirect: self.redirect.map(|r| r as i32),
2001 required_auth: self.required_auth,
2002 redirect_scheme: self.redirect_scheme.map(|s| s as i32),
2003 redirect_template: self.redirect_template.clone(),
2004 rewrite_host: self.rewrite_host.clone(),
2005 rewrite_path: self.rewrite_path.clone(),
2006 rewrite_port: self.rewrite_port,
2007 headers: self.headers.clone(),
2008 hsts: self.hsts,
2009 })
2010 .into(),
2011 );
2012 } else {
2013 v.push(
2015 RequestType::AddHttpFrontend(RequestHttpFrontend {
2016 cluster_id: Some(cluster_id.to_string()),
2017 address: self.address.into(),
2018 hostname: self.hostname.clone(),
2019 path: self.path.clone(),
2020 method: self.method.clone(),
2021 position: self.position.into(),
2022 tags,
2023 redirect: self.redirect.map(|r| r as i32),
2024 required_auth: self.required_auth,
2025 redirect_scheme: self.redirect_scheme.map(|s| s as i32),
2026 redirect_template: self.redirect_template.clone(),
2027 rewrite_host: self.rewrite_host.clone(),
2028 rewrite_path: self.rewrite_path.clone(),
2029 rewrite_port: self.rewrite_port,
2030 headers: self.headers.clone(),
2031 hsts: self.hsts,
2032 })
2033 .into(),
2034 );
2035 }
2036
2037 v
2038 }
2039}
2040
2041#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
2042#[serde(deny_unknown_fields)]
2043pub struct HttpClusterConfig {
2044 pub cluster_id: String,
2045 pub frontends: Vec<HttpFrontendConfig>,
2046 pub backends: Vec<BackendConfig>,
2047 pub sticky_session: bool,
2048 pub https_redirect: bool,
2049 pub load_balancing: LoadBalancingAlgorithms,
2050 pub load_metric: Option<LoadMetric>,
2051 pub answer_503: Option<String>,
2052 pub http2: Option<bool>,
2053 #[serde(default)]
2056 pub answers: BTreeMap<String, String>,
2057 #[serde(default)]
2058 pub https_redirect_port: Option<u32>,
2059 #[serde(default)]
2060 pub authorized_hashes: Vec<String>,
2061 #[serde(default)]
2062 pub www_authenticate: Option<String>,
2063 #[serde(default)]
2066 pub max_connections_per_ip: Option<u64>,
2067 #[serde(default)]
2070 pub retry_after: Option<u32>,
2071 #[serde(default)]
2075 pub health_check: Option<HealthCheckConfig>,
2076}
2077
2078impl HttpClusterConfig {
2079 pub fn generate_requests(&self) -> Result<Vec<Request>, ConfigError> {
2080 let mut v = vec![
2081 RequestType::AddCluster(Cluster {
2082 cluster_id: self.cluster_id.clone(),
2083 sticky_session: self.sticky_session,
2084 https_redirect: self.https_redirect,
2085 proxy_protocol: None,
2086 load_balancing: self.load_balancing as i32,
2087 answer_503: self.answer_503.clone(),
2088 load_metric: self.load_metric.map(|s| s as i32),
2089 http2: self.http2,
2090 answers: self.answers.clone(),
2091 https_redirect_port: self.https_redirect_port,
2092 authorized_hashes: self.authorized_hashes.clone(),
2093 www_authenticate: self.www_authenticate.clone(),
2094 max_connections_per_ip: self.max_connections_per_ip,
2095 retry_after: self.retry_after,
2096 health_check: self.health_check.clone(),
2097 })
2098 .into(),
2099 ];
2100
2101 for frontend in &self.frontends {
2102 let mut orders = frontend.generate_requests(&self.cluster_id);
2103 v.append(&mut orders);
2104 }
2105
2106 for (backend_count, backend) in self.backends.iter().enumerate() {
2107 let load_balancing_parameters = Some(LoadBalancingParams {
2108 weight: backend.weight.unwrap_or(100) as i32,
2109 });
2110
2111 v.push(
2112 RequestType::AddBackend(AddBackend {
2113 cluster_id: self.cluster_id.clone(),
2114 backend_id: backend.backend_id.clone().unwrap_or_else(|| {
2115 format!("{}-{}-{}", self.cluster_id, backend_count, backend.address)
2116 }),
2117 address: backend.address.into(),
2118 load_balancing_parameters,
2119 sticky_id: backend.sticky_id.clone(),
2120 backup: backend.backup,
2121 })
2122 .into(),
2123 );
2124 }
2125
2126 Ok(v)
2127 }
2128}
2129
2130#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
2131pub struct TcpFrontendConfig {
2132 pub address: SocketAddr,
2133 pub tags: Option<BTreeMap<String, String>>,
2134}
2135
2136#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
2137pub struct TcpClusterConfig {
2138 pub cluster_id: String,
2139 pub frontends: Vec<TcpFrontendConfig>,
2140 pub backends: Vec<BackendConfig>,
2141 #[serde(default)]
2142 pub proxy_protocol: Option<ProxyProtocolConfig>,
2143 pub load_balancing: LoadBalancingAlgorithms,
2144 pub load_metric: Option<LoadMetric>,
2145 #[serde(default)]
2149 pub answers: BTreeMap<String, String>,
2150 #[serde(default)]
2151 pub https_redirect_port: Option<u32>,
2152 #[serde(default)]
2153 pub authorized_hashes: Vec<String>,
2154 #[serde(default)]
2155 pub www_authenticate: Option<String>,
2156 #[serde(default)]
2159 pub max_connections_per_ip: Option<u64>,
2160 #[serde(default)]
2164 pub retry_after: Option<u32>,
2165 #[serde(default)]
2169 pub health_check: Option<HealthCheckConfig>,
2170}
2171
2172impl TcpClusterConfig {
2173 pub fn generate_requests(&self) -> Result<Vec<Request>, ConfigError> {
2174 let mut v = vec![
2175 RequestType::AddCluster(Cluster {
2176 cluster_id: self.cluster_id.clone(),
2177 sticky_session: false,
2178 https_redirect: false,
2179 proxy_protocol: self.proxy_protocol.map(|s| s as i32),
2180 load_balancing: self.load_balancing as i32,
2181 load_metric: self.load_metric.map(|s| s as i32),
2182 answer_503: None,
2183 http2: None,
2184 answers: self.answers.clone(),
2185 https_redirect_port: self.https_redirect_port,
2186 authorized_hashes: self.authorized_hashes.clone(),
2187 www_authenticate: self.www_authenticate.clone(),
2188 max_connections_per_ip: self.max_connections_per_ip,
2189 retry_after: self.retry_after,
2190 health_check: self.health_check.clone(),
2191 })
2192 .into(),
2193 ];
2194
2195 for frontend in &self.frontends {
2196 v.push(
2197 RequestType::AddTcpFrontend(RequestTcpFrontend {
2198 cluster_id: self.cluster_id.clone(),
2199 address: frontend.address.into(),
2200 tags: frontend.tags.clone().unwrap_or(BTreeMap::new()),
2201 })
2202 .into(),
2203 );
2204 }
2205
2206 for (backend_count, backend) in self.backends.iter().enumerate() {
2207 let load_balancing_parameters = Some(LoadBalancingParams {
2208 weight: backend.weight.unwrap_or(100) as i32,
2209 });
2210
2211 v.push(
2212 RequestType::AddBackend(AddBackend {
2213 cluster_id: self.cluster_id.clone(),
2214 backend_id: backend.backend_id.clone().unwrap_or_else(|| {
2215 format!("{}-{}-{}", self.cluster_id, backend_count, backend.address)
2216 }),
2217 address: backend.address.into(),
2218 load_balancing_parameters,
2219 sticky_id: backend.sticky_id.clone(),
2220 backup: backend.backup,
2221 })
2222 .into(),
2223 );
2224 }
2225
2226 Ok(v)
2227 }
2228}
2229
2230#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
2231pub enum ClusterConfig {
2232 Http(HttpClusterConfig),
2233 Tcp(TcpClusterConfig),
2234}
2235
2236impl ClusterConfig {
2237 pub fn generate_requests(&self) -> Result<Vec<Request>, ConfigError> {
2238 match *self {
2239 ClusterConfig::Http(ref http) => http.generate_requests(),
2240 ClusterConfig::Tcp(ref tcp) => tcp.generate_requests(),
2241 }
2242 }
2243}
2244
2245#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default, Deserialize)]
2247pub struct FileConfig {
2248 pub command_socket: Option<String>,
2249 pub command_buffer_size: Option<u64>,
2250 pub max_command_buffer_size: Option<u64>,
2251 pub max_connections: Option<usize>,
2252 pub min_buffers: Option<u64>,
2253 pub max_buffers: Option<u64>,
2254 pub buffer_size: Option<u64>,
2255 #[serde(default)]
2259 pub slab_entries_per_connection: Option<u64>,
2260 #[serde(default)]
2270 pub basic_auth_max_credential_bytes: Option<u64>,
2271 #[serde(default)]
2279 pub max_connections_per_ip: Option<u64>,
2280 #[serde(default)]
2286 pub retry_after: Option<u32>,
2287 #[serde(default)]
2296 pub splice_pipe_capacity_bytes: Option<u64>,
2297 #[serde(default)]
2304 pub command_allowed_uids: Option<Vec<u32>>,
2305 pub saved_state: Option<String>,
2306 #[serde(default)]
2307 pub automatic_state_save: Option<bool>,
2308 pub log_level: Option<String>,
2309 pub log_target: Option<String>,
2310 #[serde(default)]
2311 pub log_colored: bool,
2312 #[serde(default)]
2320 pub audit_logs_target: Option<String>,
2321 #[serde(default)]
2326 pub audit_logs_json_target: Option<String>,
2327 #[serde(default)]
2328 pub access_logs_target: Option<String>,
2329 #[serde(default)]
2330 pub access_logs_format: Option<AccessLogFormat>,
2331 #[serde(default)]
2332 pub access_logs_colored: Option<bool>,
2333 pub worker_count: Option<u16>,
2334 pub worker_automatic_restart: Option<bool>,
2335 pub metrics: Option<MetricsConfig>,
2336 pub disable_cluster_metrics: Option<bool>,
2337 pub listeners: Option<Vec<ListenerBuilder>>,
2338 pub clusters: Option<HashMap<String, FileClusterConfig>>,
2339 pub handle_process_affinity: Option<bool>,
2340 pub ctl_command_timeout: Option<u64>,
2341 pub pid_file_path: Option<String>,
2342 pub activate_listeners: Option<bool>,
2343 #[serde(default)]
2344 pub front_timeout: Option<u32>,
2345 #[serde(default)]
2346 pub back_timeout: Option<u32>,
2347 #[serde(default)]
2348 pub connect_timeout: Option<u32>,
2349 #[serde(default)]
2350 pub zombie_check_interval: Option<u32>,
2351 #[serde(default)]
2352 pub accept_queue_timeout: Option<u32>,
2353 #[serde(default)]
2354 pub evict_on_queue_full: Option<bool>,
2355 #[serde(default)]
2356 pub request_timeout: Option<u32>,
2357 #[serde(default)]
2358 pub worker_timeout: Option<u32>,
2359}
2360
2361impl FileConfig {
2362 pub fn load_from_path(path: &str) -> Result<FileConfig, ConfigError> {
2363 let data = Config::load_file(path)?;
2364
2365 let config: FileConfig = match toml::from_str(&data) {
2366 Ok(config) => config,
2367 Err(e) => {
2368 display_toml_error(&data, &e);
2369 return Err(ConfigError::DeserializeToml(e.to_string()));
2370 }
2371 };
2372
2373 let mut reserved_address: HashSet<SocketAddr> = HashSet::new();
2374
2375 if let Some(listeners) = config.listeners.as_ref() {
2376 for listener in listeners.iter() {
2377 if reserved_address.contains(&listener.address) {
2378 return Err(ConfigError::ListenerAddressAlreadyInUse(listener.address));
2379 }
2380 reserved_address.insert(listener.address);
2381 }
2382 }
2383
2384 Ok(config)
2406 }
2407}
2408
2409pub struct ConfigBuilder {
2411 file: FileConfig,
2412 known_addresses: HashMap<SocketAddr, ListenerProtocol>,
2413 expect_proxy_addresses: HashSet<SocketAddr>,
2414 built: Config,
2415}
2416
2417impl ConfigBuilder {
2418 pub fn new<S>(file_config: FileConfig, config_path: S) -> Self
2422 where
2423 S: ToString,
2424 {
2425 let built = Config {
2426 accept_queue_timeout: file_config
2427 .accept_queue_timeout
2428 .unwrap_or(DEFAULT_ACCEPT_QUEUE_TIMEOUT),
2429 evict_on_queue_full: file_config
2430 .evict_on_queue_full
2431 .unwrap_or(DEFAULT_EVICT_ON_QUEUE_FULL),
2432 activate_listeners: file_config.activate_listeners.unwrap_or(true),
2433 automatic_state_save: file_config
2434 .automatic_state_save
2435 .unwrap_or(DEFAULT_AUTOMATIC_STATE_SAVE),
2436 back_timeout: file_config.back_timeout.unwrap_or(DEFAULT_BACK_TIMEOUT),
2437 buffer_size: file_config.buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE),
2438 command_buffer_size: file_config
2439 .command_buffer_size
2440 .unwrap_or(DEFAULT_COMMAND_BUFFER_SIZE),
2441 config_path: config_path.to_string(),
2442 connect_timeout: file_config
2443 .connect_timeout
2444 .unwrap_or(DEFAULT_CONNECT_TIMEOUT),
2445 ctl_command_timeout: file_config.ctl_command_timeout.unwrap_or(1_000),
2446 front_timeout: file_config.front_timeout.unwrap_or(DEFAULT_FRONT_TIMEOUT),
2447 handle_process_affinity: file_config.handle_process_affinity.unwrap_or(false),
2448 access_logs_target: file_config.access_logs_target.clone(),
2449 audit_logs_target: file_config.audit_logs_target.clone(),
2450 audit_logs_json_target: file_config.audit_logs_json_target.clone(),
2451 access_logs_format: file_config.access_logs_format.clone(),
2452 access_logs_colored: file_config.access_logs_colored,
2453 log_level: file_config
2454 .log_level
2455 .clone()
2456 .unwrap_or_else(|| String::from("info")),
2457 log_target: file_config
2458 .log_target
2459 .clone()
2460 .unwrap_or_else(|| String::from("stdout")),
2461 log_colored: file_config.log_colored,
2462 max_buffers: file_config.max_buffers.unwrap_or(DEFAULT_MAX_BUFFERS),
2463 max_command_buffer_size: file_config
2464 .max_command_buffer_size
2465 .unwrap_or(DEFAULT_MAX_COMMAND_BUFFER_SIZE),
2466 max_connections: file_config
2467 .max_connections
2468 .unwrap_or(DEFAULT_MAX_CONNECTIONS),
2469 metrics: file_config.metrics.clone(),
2470 disable_cluster_metrics: file_config
2471 .disable_cluster_metrics
2472 .unwrap_or(DEFAULT_DISABLE_CLUSTER_METRICS),
2473 min_buffers: std::cmp::min(
2474 file_config.min_buffers.unwrap_or(DEFAULT_MIN_BUFFERS),
2475 file_config.max_buffers.unwrap_or(DEFAULT_MAX_BUFFERS),
2476 ),
2477 pid_file_path: file_config.pid_file_path.clone(),
2478 request_timeout: file_config
2479 .request_timeout
2480 .unwrap_or(DEFAULT_REQUEST_TIMEOUT),
2481 saved_state: file_config.saved_state.clone(),
2482 worker_automatic_restart: file_config
2483 .worker_automatic_restart
2484 .unwrap_or(DEFAULT_WORKER_AUTOMATIC_RESTART),
2485 worker_count: file_config.worker_count.unwrap_or(DEFAULT_WORKER_COUNT),
2486 zombie_check_interval: file_config
2487 .zombie_check_interval
2488 .unwrap_or(DEFAULT_ZOMBIE_CHECK_INTERVAL),
2489 worker_timeout: file_config.worker_timeout.unwrap_or(DEFAULT_WORKER_TIMEOUT),
2490 slab_entries_per_connection: file_config.slab_entries_per_connection.map(|n| {
2491 n.clamp(
2492 ServerConfig::MIN_SLAB_ENTRIES_PER_CONNECTION,
2493 ServerConfig::MAX_SLAB_ENTRIES_PER_CONNECTION,
2494 )
2495 }),
2496 command_allowed_uids: file_config.command_allowed_uids.clone(),
2497 basic_auth_max_credential_bytes: file_config.basic_auth_max_credential_bytes,
2498 max_connections_per_ip: file_config
2499 .max_connections_per_ip
2500 .unwrap_or(DEFAULT_MAX_CONNECTIONS_PER_IP),
2501 retry_after: file_config.retry_after.unwrap_or(DEFAULT_RETRY_AFTER),
2502 splice_pipe_capacity_bytes: file_config.splice_pipe_capacity_bytes,
2503 ..Default::default()
2504 };
2505
2506 Self {
2507 file: file_config,
2508 known_addresses: HashMap::new(),
2509 expect_proxy_addresses: HashSet::new(),
2510 built,
2511 }
2512 }
2513
2514 fn push_tls_listener(&mut self, mut listener: ListenerBuilder) -> Result<(), ConfigError> {
2515 let listener = listener.to_tls(Some(&self.built))?;
2516 self.built.https_listeners.push(listener);
2517 Ok(())
2518 }
2519
2520 fn push_http_listener(&mut self, mut listener: ListenerBuilder) -> Result<(), ConfigError> {
2521 let listener = listener.to_http(Some(&self.built))?;
2522 self.built.http_listeners.push(listener);
2523 Ok(())
2524 }
2525
2526 fn push_tcp_listener(&mut self, mut listener: ListenerBuilder) -> Result<(), ConfigError> {
2527 let listener = listener.to_tcp(Some(&self.built))?;
2528 self.built.tcp_listeners.push(listener);
2529 Ok(())
2530 }
2531
2532 fn populate_listeners(&mut self, listeners: Vec<ListenerBuilder>) -> Result<(), ConfigError> {
2533 for listener in listeners.iter() {
2534 if self.known_addresses.contains_key(&listener.address) {
2535 return Err(ConfigError::ListenerAddressAlreadyInUse(listener.address));
2536 }
2537
2538 let protocol = listener
2539 .protocol
2540 .ok_or(ConfigError::Missing(MissingKind::Protocol))?;
2541
2542 self.known_addresses.insert(listener.address, protocol);
2543 if listener.expect_proxy == Some(true) {
2544 self.expect_proxy_addresses.insert(listener.address);
2545 }
2546
2547 if listener.public_address.is_some() && listener.expect_proxy == Some(true) {
2548 return Err(ConfigError::Incompatible {
2549 object: ObjectKind::Listener,
2550 id: listener.address.to_string(),
2551 kind: IncompatibilityKind::PublicAddress,
2552 });
2553 }
2554
2555 match protocol {
2556 ListenerProtocol::Https => self.push_tls_listener(listener.clone())?,
2557 ListenerProtocol::Http => self.push_http_listener(listener.clone())?,
2558 ListenerProtocol::Tcp => self.push_tcp_listener(listener.clone())?,
2559 }
2560 }
2561 Ok(())
2562 }
2563
2564 fn populate_clusters(
2565 &mut self,
2566 mut file_cluster_configs: HashMap<String, FileClusterConfig>,
2567 ) -> Result<(), ConfigError> {
2568 for (id, file_cluster_config) in file_cluster_configs.drain() {
2569 let mut cluster_config =
2570 file_cluster_config.to_cluster_config(id.as_str(), &self.expect_proxy_addresses)?;
2571
2572 match cluster_config {
2573 ClusterConfig::Http(ref mut http) => {
2574 for frontend in http.frontends.iter_mut() {
2575 match self.known_addresses.get(&frontend.address) {
2576 Some(ListenerProtocol::Tcp) => {
2577 return Err(ConfigError::WrongFrontendProtocol(
2578 ListenerProtocol::Tcp,
2579 ));
2580 }
2581 Some(ListenerProtocol::Http) => {
2582 if frontend.certificate.is_some() {
2583 return Err(ConfigError::WrongFrontendProtocol(
2584 ListenerProtocol::Http,
2585 ));
2586 }
2587 }
2588 Some(ListenerProtocol::Https) => {
2589 if frontend.certificate.is_none() {
2590 if let Some(https_listener) =
2591 self.built.https_listeners.iter().find(|listener| {
2592 listener.address == frontend.address.into()
2593 && listener.certificate.is_some()
2594 })
2595 {
2596 frontend
2598 .certificate
2599 .clone_from(&https_listener.certificate);
2600 frontend.certificate_chain =
2601 Some(https_listener.certificate_chain.clone());
2602 frontend.key.clone_from(&https_listener.key);
2603 }
2604 if frontend.certificate.is_none() {
2605 debug!("known addresses: {:?}", self.known_addresses);
2606 debug!("frontend: {:?}", frontend);
2607 return Err(ConfigError::WrongFrontendProtocol(
2608 ListenerProtocol::Https,
2609 ));
2610 }
2611 }
2612 }
2613 None => {
2614 let file_listener_protocol = if frontend.certificate.is_some() {
2616 self.push_tls_listener(ListenerBuilder::new(
2617 frontend.address.into(),
2618 ListenerProtocol::Https,
2619 ))?;
2620
2621 ListenerProtocol::Https
2622 } else {
2623 self.push_http_listener(ListenerBuilder::new(
2624 frontend.address.into(),
2625 ListenerProtocol::Http,
2626 ))?;
2627
2628 ListenerProtocol::Http
2629 };
2630 self.known_addresses
2631 .insert(frontend.address, file_listener_protocol);
2632 }
2633 }
2634 }
2635 }
2636 ClusterConfig::Tcp(ref tcp) => {
2637 for frontend in &tcp.frontends {
2639 match self.known_addresses.get(&frontend.address) {
2640 Some(ListenerProtocol::Http) | Some(ListenerProtocol::Https) => {
2641 return Err(ConfigError::WrongFrontendProtocol(
2642 ListenerProtocol::Http,
2643 ));
2644 }
2645 Some(ListenerProtocol::Tcp) => {}
2646 None => {
2647 self.push_tcp_listener(ListenerBuilder::new(
2649 frontend.address.into(),
2650 ListenerProtocol::Tcp,
2651 ))?;
2652 self.known_addresses
2653 .insert(frontend.address, ListenerProtocol::Tcp);
2654 }
2655 }
2656 }
2657 }
2658 }
2659
2660 self.built.clusters.insert(id, cluster_config);
2661 }
2662 Ok(())
2663 }
2664
2665 pub fn into_config(&mut self) -> Result<Config, ConfigError> {
2667 if let Some(listeners) = &self.file.listeners {
2668 self.populate_listeners(listeners.clone())?;
2669 }
2670
2671 if let Some(file_cluster_configs) = &self.file.clusters {
2672 self.populate_clusters(file_cluster_configs.clone())?;
2673 }
2674
2675 let h2_listeners = self
2684 .built
2685 .https_listeners
2686 .iter()
2687 .filter(|l| l.alpn_protocols.iter().any(|p| p == "h2"))
2688 .count();
2689 if h2_listeners > 0 && self.built.buffer_size < H2_MIN_BUFFER_SIZE {
2690 return Err(ConfigError::BufferSizeTooSmallForH2 {
2691 buffer_size: self.built.buffer_size,
2692 minimum: H2_MIN_BUFFER_SIZE,
2693 listeners: h2_listeners,
2694 });
2695 }
2696
2697 if let Some(cap) = self.built.basic_auth_max_credential_bytes {
2707 let third = self.built.buffer_size / 3;
2708 if cap >= third {
2709 warn!(
2710 "basic_auth_max_credential_bytes = {} is >= buffer_size / 3 ({}); \
2711 a hostile peer can pin ~33% of the per-frontend buffer per failed auth \
2712 attempt. Consider lowering basic_auth_max_credential_bytes (typical \
2713 credentials are <100 bytes) or raising buffer_size.",
2714 cap, third
2715 );
2716 }
2717 }
2718
2719 if self.built.evict_on_queue_full && self.built.max_connections < 100 {
2726 let pct = 100usize.div_ceil(self.built.max_connections);
2727 warn!(
2728 "evict_on_queue_full enabled with max_connections = {}; the eviction batch \
2729 clamps to 1, equivalent to ~{}% of capacity per cap event (the knob is \
2730 documented as 1%). Confirm this is intended.",
2731 self.built.max_connections, pct
2732 );
2733 }
2734
2735 let command_socket_path = self.file.command_socket.clone().unwrap_or({
2736 let mut path = env::current_dir().map_err(|e| ConfigError::Env(e.to_string()))?;
2737 path.push("sozu.sock");
2738 let verified_path = path
2739 .to_str()
2740 .ok_or(ConfigError::InvalidPath(path.clone()))?;
2741 verified_path.to_owned()
2742 });
2743
2744 if let (None, Some(true)) = (&self.file.saved_state, &self.file.automatic_state_save) {
2745 return Err(ConfigError::Missing(MissingKind::SavedState));
2746 }
2747
2748 Ok(Config {
2749 command_socket: command_socket_path,
2750 ..self.built.clone()
2751 })
2752 }
2753}
2754
2755#[derive(Clone, PartialEq, Eq, Serialize, Default, Deserialize)]
2759pub struct Config {
2760 pub config_path: String,
2761 pub command_socket: String,
2762 pub command_buffer_size: u64,
2763 pub max_command_buffer_size: u64,
2764 pub max_connections: usize,
2765 pub min_buffers: u64,
2766 pub max_buffers: u64,
2767 pub buffer_size: u64,
2768 pub saved_state: Option<String>,
2769 #[serde(default)]
2770 pub automatic_state_save: bool,
2771 pub log_level: String,
2772 pub log_target: String,
2773 pub log_colored: bool,
2774 #[serde(default)]
2777 pub audit_logs_target: Option<String>,
2778 #[serde(default)]
2781 pub audit_logs_json_target: Option<String>,
2782 #[serde(default)]
2783 pub access_logs_target: Option<String>,
2784 pub access_logs_format: Option<AccessLogFormat>,
2785 pub access_logs_colored: Option<bool>,
2786 pub worker_count: u16,
2787 pub worker_automatic_restart: bool,
2788 pub metrics: Option<MetricsConfig>,
2789 #[serde(default = "default_disable_cluster_metrics")]
2790 pub disable_cluster_metrics: bool,
2791 pub http_listeners: Vec<HttpListenerConfig>,
2792 pub https_listeners: Vec<HttpsListenerConfig>,
2793 pub tcp_listeners: Vec<TcpListenerConfig>,
2794 pub clusters: HashMap<String, ClusterConfig>,
2795 pub handle_process_affinity: bool,
2796 pub ctl_command_timeout: u64,
2797 pub pid_file_path: Option<String>,
2798 pub activate_listeners: bool,
2799 #[serde(default = "default_front_timeout")]
2800 pub front_timeout: u32,
2801 #[serde(default = "default_back_timeout")]
2802 pub back_timeout: u32,
2803 #[serde(default = "default_connect_timeout")]
2804 pub connect_timeout: u32,
2805 #[serde(default = "default_zombie_check_interval")]
2806 pub zombie_check_interval: u32,
2807 #[serde(default = "default_accept_queue_timeout")]
2808 pub accept_queue_timeout: u32,
2809 #[serde(default = "default_evict_on_queue_full")]
2810 pub evict_on_queue_full: bool,
2811 #[serde(default = "default_request_timeout")]
2812 pub request_timeout: u32,
2813 #[serde(default = "default_worker_timeout")]
2814 pub worker_timeout: u32,
2815 #[serde(default)]
2822 pub slab_entries_per_connection: Option<u64>,
2823 #[serde(default)]
2828 pub command_allowed_uids: Option<Vec<u32>>,
2829 #[serde(default)]
2834 pub basic_auth_max_credential_bytes: Option<u64>,
2835 #[serde(default = "default_max_connections_per_ip")]
2840 pub max_connections_per_ip: u64,
2841 #[serde(default = "default_retry_after")]
2844 pub retry_after: u32,
2845 #[serde(default)]
2852 pub splice_pipe_capacity_bytes: Option<u64>,
2853}
2854
2855fn default_front_timeout() -> u32 {
2856 DEFAULT_FRONT_TIMEOUT
2857}
2858
2859fn default_back_timeout() -> u32 {
2860 DEFAULT_BACK_TIMEOUT
2861}
2862
2863fn default_connect_timeout() -> u32 {
2864 DEFAULT_CONNECT_TIMEOUT
2865}
2866
2867fn default_request_timeout() -> u32 {
2868 DEFAULT_REQUEST_TIMEOUT
2869}
2870
2871fn default_zombie_check_interval() -> u32 {
2872 DEFAULT_ZOMBIE_CHECK_INTERVAL
2873}
2874
2875fn default_accept_queue_timeout() -> u32 {
2876 DEFAULT_ACCEPT_QUEUE_TIMEOUT
2877}
2878
2879fn default_evict_on_queue_full() -> bool {
2880 DEFAULT_EVICT_ON_QUEUE_FULL
2881}
2882
2883fn default_disable_cluster_metrics() -> bool {
2884 DEFAULT_DISABLE_CLUSTER_METRICS
2885}
2886
2887fn default_worker_timeout() -> u32 {
2888 DEFAULT_WORKER_TIMEOUT
2889}
2890
2891fn default_max_connections_per_ip() -> u64 {
2892 DEFAULT_MAX_CONNECTIONS_PER_IP
2893}
2894
2895fn default_retry_after() -> u32 {
2896 DEFAULT_RETRY_AFTER
2897}
2898
2899impl Config {
2900 pub fn load_from_path(path: &str) -> Result<Config, ConfigError> {
2902 let file_config = FileConfig::load_from_path(path)?;
2903
2904 let mut config = ConfigBuilder::new(file_config, path).into_config()?;
2905
2906 config.saved_state = config.saved_state_path()?;
2908
2909 Ok(config)
2910 }
2911
2912 pub fn generate_config_messages(&self) -> Result<Vec<WorkerRequest>, ConfigError> {
2914 let mut v = Vec::new();
2915 let mut count = 0u8;
2916
2917 for listener in &self.http_listeners {
2918 v.push(WorkerRequest {
2919 id: format!("CONFIG-{count}"),
2920 content: RequestType::AddHttpListener(listener.clone()).into(),
2921 });
2922 count += 1;
2923 }
2924
2925 for listener in &self.https_listeners {
2926 v.push(WorkerRequest {
2927 id: format!("CONFIG-{count}"),
2928 content: RequestType::AddHttpsListener(listener.clone()).into(),
2929 });
2930 count += 1;
2931 }
2932
2933 for listener in &self.tcp_listeners {
2934 v.push(WorkerRequest {
2935 id: format!("CONFIG-{count}"),
2936 content: RequestType::AddTcpListener(*listener).into(),
2937 });
2938 count += 1;
2939 }
2940
2941 for cluster in self.clusters.values() {
2942 let mut orders = cluster.generate_requests()?;
2943 for content in orders.drain(..) {
2944 v.push(WorkerRequest {
2945 id: format!("CONFIG-{count}"),
2946 content,
2947 });
2948 count += 1;
2949 }
2950 }
2951
2952 if self.activate_listeners {
2953 for listener in &self.http_listeners {
2954 v.push(WorkerRequest {
2955 id: format!("CONFIG-{count}"),
2956 content: RequestType::ActivateListener(ActivateListener {
2957 address: listener.address,
2958 proxy: ListenerType::Http.into(),
2959 from_scm: false,
2960 })
2961 .into(),
2962 });
2963 count += 1;
2964 }
2965
2966 for listener in &self.https_listeners {
2967 v.push(WorkerRequest {
2968 id: format!("CONFIG-{count}"),
2969 content: RequestType::ActivateListener(ActivateListener {
2970 address: listener.address,
2971 proxy: ListenerType::Https.into(),
2972 from_scm: false,
2973 })
2974 .into(),
2975 });
2976 count += 1;
2977 }
2978
2979 for listener in &self.tcp_listeners {
2980 v.push(WorkerRequest {
2981 id: format!("CONFIG-{count}"),
2982 content: RequestType::ActivateListener(ActivateListener {
2983 address: listener.address,
2984 proxy: ListenerType::Tcp.into(),
2985 from_scm: false,
2986 })
2987 .into(),
2988 });
2989 count += 1;
2990 }
2991 }
2992
2993 if self.disable_cluster_metrics {
2994 v.push(WorkerRequest {
2995 id: format!("CONFIG-{count}"),
2996 content: RequestType::ConfigureMetrics(MetricsConfiguration::Disabled.into())
2997 .into(),
2998 });
2999 }
3001
3002 Ok(v)
3003 }
3004
3005 pub fn command_socket_path(&self) -> Result<String, ConfigError> {
3007 let config_path_buf = PathBuf::from(self.config_path.clone());
3008 let mut config_dir = config_path_buf
3009 .parent()
3010 .ok_or(ConfigError::NoFileParent(
3011 config_path_buf.to_string_lossy().to_string(),
3012 ))?
3013 .to_path_buf();
3014
3015 let socket_path = PathBuf::from(self.command_socket.clone());
3016
3017 let mut socket_parent_dir = match socket_path.parent() {
3018 None => config_dir,
3021 Some(path) => {
3022 config_dir.push(path);
3024 config_dir.canonicalize().map_err(|io_error| {
3026 ConfigError::SocketPathError(format!(
3027 "Could not canonicalize path {config_dir:?}: {io_error}"
3028 ))
3029 })?
3030 }
3031 };
3032
3033 let socket_name = socket_path
3034 .file_name()
3035 .ok_or(ConfigError::SocketPathError(format!(
3036 "could not get command socket file name from {socket_path:?}"
3037 )))?;
3038
3039 socket_parent_dir.push(socket_name);
3041
3042 let command_socket_path = socket_parent_dir
3043 .to_str()
3044 .ok_or(ConfigError::SocketPathError(format!(
3045 "Invalid socket path {socket_parent_dir:?}"
3046 )))?
3047 .to_string();
3048
3049 Ok(command_socket_path)
3050 }
3051
3052 fn saved_state_path(&self) -> Result<Option<String>, ConfigError> {
3054 let path = match self.saved_state.as_ref() {
3055 Some(path) => path,
3056 None => return Ok(None),
3057 };
3058
3059 debug!("saved_stated path in the config: {}", path);
3060 let config_path = PathBuf::from(self.config_path.clone());
3061
3062 debug!("Config path buffer: {:?}", config_path);
3063 let config_dir = config_path
3064 .parent()
3065 .ok_or(ConfigError::SaveStatePath(format!(
3066 "Could get parent directory of config file {config_path:?}"
3067 )))?;
3068
3069 debug!("Config folder: {:?}", config_dir);
3070 if !config_dir.exists() {
3071 create_dir_all(config_dir).map_err(|io_error| {
3072 ConfigError::SaveStatePath(format!(
3073 "failed to create state parent directory '{config_dir:?}': {io_error}"
3074 ))
3075 })?;
3076 }
3077
3078 let mut saved_state_path_raw = config_dir.to_path_buf();
3079 saved_state_path_raw.push(path);
3080 debug!(
3081 "Looking for saved state on the path {:?}",
3082 saved_state_path_raw
3083 );
3084
3085 match metadata(path) {
3086 Err(err) if matches!(err.kind(), ErrorKind::NotFound) => {
3087 info!("Create an empty state file at '{}'", path);
3088 File::create(path).map_err(|io_error| {
3089 ConfigError::SaveStatePath(format!(
3090 "failed to create state file '{path:?}': {io_error}"
3091 ))
3092 })?;
3093 }
3094 _ => {}
3095 }
3096
3097 saved_state_path_raw.canonicalize().map_err(|io_error| {
3098 ConfigError::SaveStatePath(format!(
3099 "could not get saved state path from config file input {path:?}: {io_error}"
3100 ))
3101 })?;
3102
3103 let stringified_path = saved_state_path_raw
3104 .to_str()
3105 .ok_or(ConfigError::SaveStatePath(format!(
3106 "Invalid path {saved_state_path_raw:?}"
3107 )))?
3108 .to_string();
3109
3110 Ok(Some(stringified_path))
3111 }
3112
3113 pub fn load_file(path: &str) -> Result<String, ConfigError> {
3115 std::fs::read_to_string(path).map_err(|io_error| ConfigError::FileRead {
3116 path_to_read: path.to_owned(),
3117 io_error,
3118 })
3119 }
3120
3121 pub fn load_file_bytes(path: &str) -> Result<Vec<u8>, ConfigError> {
3123 std::fs::read(path).map_err(|io_error| ConfigError::FileRead {
3124 path_to_read: path.to_owned(),
3125 io_error,
3126 })
3127 }
3128}
3129
3130impl fmt::Debug for Config {
3131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3132 f.debug_struct("Config")
3133 .field("config_path", &self.config_path)
3134 .field("command_socket", &self.command_socket)
3135 .field("command_buffer_size", &self.command_buffer_size)
3136 .field("max_command_buffer_size", &self.max_command_buffer_size)
3137 .field("max_connections", &self.max_connections)
3138 .field("min_buffers", &self.min_buffers)
3139 .field("max_buffers", &self.max_buffers)
3140 .field("buffer_size", &self.buffer_size)
3141 .field("saved_state", &self.saved_state)
3142 .field("automatic_state_save", &self.automatic_state_save)
3143 .field("log_level", &self.log_level)
3144 .field("log_target", &self.log_target)
3145 .field("access_logs_target", &self.access_logs_target)
3146 .field("audit_logs_target", &self.audit_logs_target)
3147 .field("audit_logs_json_target", &self.audit_logs_json_target)
3148 .field("access_logs_format", &self.access_logs_format)
3149 .field("worker_count", &self.worker_count)
3150 .field("worker_automatic_restart", &self.worker_automatic_restart)
3151 .field("metrics", &self.metrics)
3152 .field("disable_cluster_metrics", &self.disable_cluster_metrics)
3153 .field("handle_process_affinity", &self.handle_process_affinity)
3154 .field("ctl_command_timeout", &self.ctl_command_timeout)
3155 .field("pid_file_path", &self.pid_file_path)
3156 .field("activate_listeners", &self.activate_listeners)
3157 .field("front_timeout", &self.front_timeout)
3158 .field("back_timeout", &self.back_timeout)
3159 .field("connect_timeout", &self.connect_timeout)
3160 .field("zombie_check_interval", &self.zombie_check_interval)
3161 .field("accept_queue_timeout", &self.accept_queue_timeout)
3162 .field("evict_on_queue_full", &self.evict_on_queue_full)
3163 .field("request_timeout", &self.request_timeout)
3164 .field("worker_timeout", &self.worker_timeout)
3165 .finish()
3166 }
3167}
3168
3169fn display_toml_error(file: &str, error: &toml::de::Error) {
3170 println!("error parsing the configuration file '{file}': {error}");
3171 if let Some(Range { start, end }) = error.span() {
3172 print!("error parsing the configuration file '{file}' at position: {start}, {end}");
3173 }
3174}
3175
3176impl ServerConfig {
3177 pub const DEFAULT_SLAB_ENTRIES_PER_CONNECTION: u64 = 4;
3184 pub const MIN_SLAB_ENTRIES_PER_CONNECTION: u64 = 2;
3187 pub const MAX_SLAB_ENTRIES_PER_CONNECTION: u64 = 32;
3190
3191 pub fn effective_slab_entries_per_connection(&self) -> u64 {
3194 match self.slab_entries_per_connection {
3195 Some(0) | None => Self::DEFAULT_SLAB_ENTRIES_PER_CONNECTION,
3196 Some(n) => n.clamp(
3197 Self::MIN_SLAB_ENTRIES_PER_CONNECTION,
3198 Self::MAX_SLAB_ENTRIES_PER_CONNECTION,
3199 ),
3200 }
3201 }
3202
3203 pub fn slab_capacity(&self) -> u64 {
3210 10 + self.effective_slab_entries_per_connection() * self.max_connections
3211 }
3212}
3213
3214impl From<&Config> for ServerConfig {
3216 fn from(config: &Config) -> Self {
3217 let metrics = config.metrics.clone().map(|m| ServerMetricsConfig {
3218 address: m.address.to_string(),
3219 tagged_metrics: m.tagged_metrics,
3220 prefix: m.prefix,
3221 detail: Some(MetricDetail::from(m.detail) as i32),
3222 });
3223 Self {
3224 max_connections: config.max_connections as u64,
3225 front_timeout: config.front_timeout,
3226 back_timeout: config.back_timeout,
3227 connect_timeout: config.connect_timeout,
3228 zombie_check_interval: config.zombie_check_interval,
3229 accept_queue_timeout: config.accept_queue_timeout,
3230 min_buffers: config.min_buffers,
3231 max_buffers: config.max_buffers,
3232 buffer_size: config.buffer_size,
3233 log_level: config.log_level.clone(),
3234 log_target: config.log_target.clone(),
3235 access_logs_target: config.access_logs_target.clone(),
3236 audit_logs_target: config.audit_logs_target.clone(),
3237 audit_logs_json_target: config.audit_logs_json_target.clone(),
3238 command_buffer_size: config.command_buffer_size,
3239 max_command_buffer_size: config.max_command_buffer_size,
3240 metrics,
3241 access_log_format: ProtobufAccessLogFormat::from(&config.access_logs_format) as i32,
3242 log_colored: config.log_colored,
3243 slab_entries_per_connection: config.slab_entries_per_connection,
3244 basic_auth_max_credential_bytes: config.basic_auth_max_credential_bytes,
3245 evict_on_queue_full: Some(config.evict_on_queue_full),
3246 max_connections_per_ip: Some(config.max_connections_per_ip),
3247 retry_after: Some(config.retry_after),
3248 splice_pipe_capacity_bytes: config.splice_pipe_capacity_bytes,
3249 }
3250 }
3251}
3252
3253#[cfg(test)]
3254mod tests {
3255 use toml::to_string;
3256
3257 use super::*;
3258
3259 #[test]
3260 fn hsts_to_proto_enabled_substitutes_default_max_age() {
3261 let cfg = FileHstsConfig {
3262 enabled: Some(true),
3263 max_age: None,
3264 include_subdomains: None,
3265 preload: None,
3266 force_replace_backend: None,
3267 };
3268 let proto = cfg.to_proto("test").expect("should validate");
3269 assert_eq!(proto.enabled, Some(true));
3270 assert_eq!(proto.max_age, Some(DEFAULT_HSTS_MAX_AGE));
3271 }
3272
3273 #[test]
3274 fn hsts_to_proto_explicit_max_age_kept() {
3275 let cfg = FileHstsConfig {
3276 enabled: Some(true),
3277 max_age: Some(63_072_000),
3278 include_subdomains: Some(true),
3279 preload: Some(true),
3280 force_replace_backend: None,
3281 };
3282 let proto = cfg.to_proto("test").expect("should validate");
3283 assert_eq!(proto.max_age, Some(63_072_000));
3284 assert_eq!(proto.include_subdomains, Some(true));
3285 assert_eq!(proto.preload, Some(true));
3286 }
3287
3288 #[test]
3289 fn hsts_to_proto_disabled_keeps_zero_intent() {
3290 let cfg = FileHstsConfig {
3294 enabled: Some(false),
3295 max_age: None,
3296 include_subdomains: None,
3297 preload: None,
3298 force_replace_backend: None,
3299 };
3300 let proto = cfg.to_proto("test").expect("should validate");
3301 assert_eq!(proto.enabled, Some(false));
3302 }
3303
3304 #[test]
3305 fn hsts_to_proto_kill_switch_max_age_zero_allowed() {
3306 let cfg = FileHstsConfig {
3310 enabled: Some(true),
3311 max_age: Some(0),
3312 include_subdomains: None,
3313 preload: None,
3314 force_replace_backend: None,
3315 };
3316 let proto = cfg.to_proto("test").expect("kill-switch must validate");
3317 assert_eq!(proto.max_age, Some(0));
3318 }
3319
3320 #[test]
3321 fn hsts_to_proto_missing_enabled_errors() {
3322 let cfg = FileHstsConfig {
3323 enabled: None,
3324 max_age: Some(31_536_000),
3325 include_subdomains: None,
3326 preload: None,
3327 force_replace_backend: None,
3328 };
3329 match cfg.to_proto("test").unwrap_err() {
3330 ConfigError::HstsEnabledRequired(scope) => assert_eq!(scope, "test"),
3331 other => panic!("expected HstsEnabledRequired, got {other:?}"),
3332 }
3333 }
3334
3335 #[test]
3336 fn hsts_rejected_on_http_listener() {
3337 let mut listener = ListenerBuilder::new(
3342 SocketAddress::new_v4(127, 0, 0, 1, 8080),
3343 ListenerProtocol::Http,
3344 );
3345 listener.hsts = Some(FileHstsConfig {
3346 enabled: Some(true),
3347 max_age: Some(31_536_000),
3348 include_subdomains: None,
3349 preload: None,
3350 force_replace_backend: None,
3351 });
3352 match listener.to_http(None).unwrap_err() {
3353 ConfigError::HstsOnPlainHttp(scope) => assert!(
3354 scope.contains("HTTP listener"),
3355 "expected scope to mention 'HTTP listener', got {scope:?}"
3356 ),
3357 other => panic!("expected HstsOnPlainHttp, got {other:?}"),
3358 }
3359 }
3360
3361 #[test]
3362 fn hsts_rejected_on_http_frontend() {
3363 let frontend = FileClusterFrontendConfig {
3369 address: "127.0.0.1:8080".parse().unwrap(),
3370 hostname: Some("example.com".to_owned()),
3371 path: None,
3372 path_type: None,
3373 method: None,
3374 certificate: None,
3375 key: None,
3376 certificate_chain: None,
3377 tls_versions: vec![],
3378 position: RulePosition::Tree,
3379 tags: None,
3380 redirect: None,
3381 redirect_scheme: None,
3382 redirect_template: None,
3383 rewrite_host: None,
3384 rewrite_path: None,
3385 rewrite_port: None,
3386 required_auth: None,
3387 headers: None,
3388 hsts: Some(FileHstsConfig {
3389 enabled: Some(true),
3390 max_age: Some(31_536_000),
3391 include_subdomains: None,
3392 preload: None,
3393 force_replace_backend: None,
3394 }),
3395 };
3396 match frontend.to_http_front("api").unwrap_err() {
3397 ConfigError::HstsOnPlainHttp(scope) => {
3398 assert!(
3399 scope.contains("api") && scope.contains("example.com"),
3400 "expected scope to mention 'api' and 'example.com', got {scope:?}"
3401 );
3402 }
3403 other => panic!("expected HstsOnPlainHttp, got {other:?}"),
3404 }
3405 }
3406
3407 #[test]
3408 fn serialize() {
3409 let http = ListenerBuilder::new(
3410 SocketAddress::new_v4(127, 0, 0, 1, 8080),
3411 ListenerProtocol::Http,
3412 )
3413 .with_answer_404_path(Some("404.html"))
3414 .to_owned();
3415 println!("http: {:?}", to_string(&http));
3416
3417 let https = ListenerBuilder::new(
3418 SocketAddress::new_v4(127, 0, 0, 1, 8443),
3419 ListenerProtocol::Https,
3420 )
3421 .with_answer_404_path(Some("404.html"))
3422 .to_owned();
3423 println!("https: {:?}", to_string(&https));
3424
3425 let listeners = vec![http, https];
3426 let config = FileConfig {
3427 command_socket: Some(String::from("./command_folder/sock")),
3428 worker_count: Some(2),
3429 worker_automatic_restart: Some(true),
3430 max_connections: Some(500),
3431 min_buffers: Some(1),
3432 max_buffers: Some(500),
3433 buffer_size: Some(16393),
3434 metrics: Some(MetricsConfig {
3435 address: "127.0.0.1:8125".parse().unwrap(),
3436 tagged_metrics: false,
3437 prefix: Some(String::from("sozu-metrics")),
3438 detail: MetricDetailLevel::default(),
3439 }),
3440 listeners: Some(listeners),
3441 ..Default::default()
3442 };
3443
3444 println!("config: {:?}", to_string(&config));
3445 let encoded = to_string(&config).unwrap();
3446 println!("conf:\n{encoded}");
3447 }
3448
3449 #[test]
3450 fn parse() {
3451 let path = "assets/config.toml";
3452 let config = Config::load_from_path(path).unwrap_or_else(|load_error| {
3453 panic!("Cannot load config from path {path}: {load_error:?}")
3454 });
3455 println!("config: {config:#?}");
3456 }
3458
3459 #[test]
3460 fn multiple_listeners_preserve_per_address_expect_proxy() {
3461 let toml_content = r#"
3462 command_socket = "/tmp/sozu_test.sock"
3463 worker_count = 1
3464
3465 [[listeners]]
3466 protocol = "http"
3467 address = "172.16.20.1:80"
3468 expect_proxy = true
3469
3470 [[listeners]]
3471 protocol = "http"
3472 address = "10.22.0.1:80"
3473 expect_proxy = false
3474
3475 [[listeners]]
3476 protocol = "https"
3477 address = "192.168.1.1:443"
3478 expect_proxy = true
3479
3480 [[listeners]]
3481 protocol = "https"
3482 address = "192.168.2.1:443"
3483 expect_proxy = false
3484 "#;
3485
3486 let file_config: FileConfig =
3487 toml::from_str(toml_content).expect("Could not parse TOML config");
3488
3489 let listeners = file_config.listeners.as_ref().expect("No listeners found");
3490 assert_eq!(listeners.len(), 4);
3491
3492 let config = ConfigBuilder::new(file_config, "/tmp/test_config.toml")
3493 .into_config()
3494 .expect("Could not build config");
3495
3496 assert_eq!(config.http_listeners.len(), 2);
3497 assert_eq!(config.https_listeners.len(), 2);
3498
3499 let http_proxy = config
3501 .http_listeners
3502 .iter()
3503 .find(|l| SocketAddr::from(l.address) == "172.16.20.1:80".parse().unwrap())
3504 .expect("Listener on 172.16.20.1:80 not found");
3505 let http_direct = config
3506 .http_listeners
3507 .iter()
3508 .find(|l| SocketAddr::from(l.address) == "10.22.0.1:80".parse().unwrap())
3509 .expect("Listener on 10.22.0.1:80 not found");
3510
3511 assert!(http_proxy.expect_proxy);
3512 assert!(!http_direct.expect_proxy);
3513
3514 let https_proxy = config
3516 .https_listeners
3517 .iter()
3518 .find(|l| SocketAddr::from(l.address) == "192.168.1.1:443".parse().unwrap())
3519 .expect("Listener on 192.168.1.1:443 not found");
3520 let https_direct = config
3521 .https_listeners
3522 .iter()
3523 .find(|l| SocketAddr::from(l.address) == "192.168.2.1:443".parse().unwrap())
3524 .expect("Listener on 192.168.2.1:443 not found");
3525
3526 assert!(https_proxy.expect_proxy);
3527 assert!(!https_direct.expect_proxy);
3528 }
3529
3530 #[test]
3531 fn multiple_listeners_generate_correct_worker_requests() {
3532 let toml_content = r#"
3533 command_socket = "/tmp/sozu_test.sock"
3534 worker_count = 1
3535 activate_listeners = true
3536
3537 [[listeners]]
3538 protocol = "http"
3539 address = "172.16.20.1:80"
3540 expect_proxy = true
3541
3542 [[listeners]]
3543 protocol = "http"
3544 address = "10.22.0.1:80"
3545 expect_proxy = false
3546 "#;
3547
3548 let file_config: FileConfig =
3549 toml::from_str(toml_content).expect("Could not parse TOML config");
3550
3551 let config = ConfigBuilder::new(file_config, "/tmp/test_config.toml")
3552 .into_config()
3553 .expect("Could not build config");
3554
3555 let messages = config
3556 .generate_config_messages()
3557 .expect("Could not generate config messages");
3558
3559 let add_listener_count = messages
3560 .iter()
3561 .filter(|m| {
3562 matches!(
3563 m.content.request_type,
3564 Some(RequestType::AddHttpListener(_))
3565 )
3566 })
3567 .count();
3568
3569 let activate_listener_count = messages
3570 .iter()
3571 .filter(|m| {
3572 matches!(
3573 m.content.request_type,
3574 Some(RequestType::ActivateListener(ActivateListener {
3575 proxy,
3576 ..
3577 })) if proxy == ListenerType::Http as i32
3578 )
3579 })
3580 .count();
3581
3582 assert_eq!(add_listener_count, 2);
3583 assert_eq!(activate_listener_count, 2);
3584 }
3585
3586 #[test]
3587 fn duplicate_listener_address_rejected() {
3588 let toml_content = r#"
3589 command_socket = "/tmp/sozu_test.sock"
3590 worker_count = 1
3591
3592 [[listeners]]
3593 protocol = "http"
3594 address = "0.0.0.0:80"
3595
3596 [[listeners]]
3597 protocol = "http"
3598 address = "0.0.0.0:80"
3599 "#;
3600
3601 let file_config: FileConfig =
3602 toml::from_str(toml_content).expect("Could not parse TOML config");
3603
3604 let result = ConfigBuilder::new(file_config, "/tmp/test_config.toml").into_config();
3605
3606 assert!(
3607 result.is_err(),
3608 "Should reject duplicate listener addresses"
3609 );
3610 }
3611
3612 #[test]
3613 fn buffer_size_below_h2_minimum_rejected() {
3614 let toml_content = r#"
3616 command_socket = "/tmp/sozu_test.sock"
3617 worker_count = 1
3618 buffer_size = 8192
3619
3620 [[listeners]]
3621 protocol = "https"
3622 address = "127.0.0.1:8443"
3623 "#;
3624 let file_config: FileConfig =
3625 toml::from_str(toml_content).expect("Could not parse TOML config");
3626 let result = ConfigBuilder::new(file_config, "/tmp/test_config.toml").into_config();
3627 match result {
3628 Err(ConfigError::BufferSizeTooSmallForH2 {
3629 buffer_size: 8192,
3630 minimum: 16_393,
3631 listeners: 1,
3632 }) => {}
3633 other => panic!("expected BufferSizeTooSmallForH2, got {other:?}"),
3634 }
3635 }
3636
3637 #[test]
3638 fn buffer_size_below_h2_minimum_accepted_when_no_h2_listener() {
3639 let toml_content = r#"
3641 command_socket = "/tmp/sozu_test.sock"
3642 worker_count = 1
3643 buffer_size = 8192
3644
3645 [[listeners]]
3646 protocol = "https"
3647 address = "127.0.0.1:8443"
3648 alpn_protocols = ["http/1.1"]
3649 "#;
3650 let file_config: FileConfig =
3651 toml::from_str(toml_content).expect("Could not parse TOML config");
3652 let result = ConfigBuilder::new(file_config, "/tmp/test_config.toml").into_config();
3653 assert!(
3654 result.is_ok(),
3655 "non-H2 HTTPS listener with sub-16393 buffer should be accepted: {result:?}"
3656 );
3657 }
3658
3659 #[test]
3660 fn buffer_size_at_h2_minimum_accepted() {
3661 let toml_content = r#"
3662 command_socket = "/tmp/sozu_test.sock"
3663 worker_count = 1
3664 buffer_size = 16393
3665
3666 [[listeners]]
3667 protocol = "https"
3668 address = "127.0.0.1:8443"
3669 "#;
3670 let file_config: FileConfig =
3671 toml::from_str(toml_content).expect("Could not parse TOML config");
3672 let result = ConfigBuilder::new(file_config, "/tmp/test_config.toml").into_config();
3673 assert!(
3674 result.is_ok(),
3675 "buffer_size at the H2 minimum should be accepted: {result:?}"
3676 );
3677 }
3678
3679 #[test]
3680 fn alpn_protocols_default() {
3681 let mut builder = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 8443));
3682 let config = builder.to_tls(None).expect("to_tls should succeed");
3683 assert_eq!(config.alpn_protocols, vec!["h2", "http/1.1"]);
3684 }
3685
3686 #[test]
3687 fn alpn_protocols_custom() {
3688 let mut builder = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 8443));
3689 builder.with_alpn_protocols(Some(vec!["http/1.1".to_owned()]));
3690 let config = builder.to_tls(None).expect("to_tls should succeed");
3691 assert_eq!(config.alpn_protocols, vec!["http/1.1"]);
3692 }
3693
3694 #[test]
3695 fn alpn_protocols_invalid_rejected() {
3696 let mut builder = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 8443));
3697 builder.with_alpn_protocols(Some(vec!["h3".to_owned()]));
3698 let result = builder.to_tls(None);
3699 assert!(result.is_err());
3700 let err = result.unwrap_err();
3701 assert!(
3702 err.to_string().contains("h3"),
3703 "error should mention the invalid protocol: {err}"
3704 );
3705 }
3706
3707 #[test]
3708 fn alpn_protocols_empty_uses_default() {
3709 let mut builder = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 8443));
3710 builder.with_alpn_protocols(Some(vec![]));
3711 let config = builder.to_tls(None).expect("to_tls should succeed");
3712 assert_eq!(config.alpn_protocols, vec!["h2", "http/1.1"]);
3713 }
3714
3715 #[test]
3716 fn alpn_protocols_deduplicated() {
3717 let mut builder = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 8443));
3718 builder.with_alpn_protocols(Some(vec![
3719 "h2".to_owned(),
3720 "h2".to_owned(),
3721 "http/1.1".to_owned(),
3722 ]));
3723 let config = builder.to_tls(None).expect("to_tls should succeed");
3724 assert_eq!(config.alpn_protocols, vec!["h2", "http/1.1"]);
3725 }
3726
3727 #[test]
3728 fn alpn_protocols_order_preserved() {
3729 let mut builder = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 8443));
3730 builder.with_alpn_protocols(Some(vec!["http/1.1".to_owned(), "h2".to_owned()]));
3731 let config = builder.to_tls(None).expect("to_tls should succeed");
3732 assert_eq!(config.alpn_protocols, vec!["http/1.1", "h2"]);
3733 }
3734
3735 #[test]
3741 fn parse_header_edit_rejects_crlf_in_value() {
3742 let entry = HeaderEditConfig {
3743 position: "request".to_owned(),
3744 key: "X-Test".to_owned(),
3745 value: "value\r\nEvil-Header: stolen".to_owned(),
3746 };
3747 let err = parse_header_edit(0, &entry).expect_err("CRLF in value must be rejected");
3748 match err {
3749 ConfigError::InvalidHeaderBytes { index, field } => {
3750 assert_eq!(index, 0);
3751 assert_eq!(field, "value");
3752 }
3753 other => panic!("expected InvalidHeaderBytes, got {other:?}"),
3754 }
3755 }
3756
3757 #[test]
3758 fn parse_header_edit_rejects_lf_in_key() {
3759 let entry = HeaderEditConfig {
3760 position: "response".to_owned(),
3761 key: "X-\nTest".to_owned(),
3762 value: "ok".to_owned(),
3763 };
3764 let err = parse_header_edit(2, &entry).expect_err("LF in key must be rejected");
3765 match err {
3766 ConfigError::InvalidHeaderBytes { index, field } => {
3767 assert_eq!(index, 2);
3768 assert_eq!(field, "key");
3769 }
3770 other => panic!("expected InvalidHeaderBytes, got {other:?}"),
3771 }
3772 }
3773
3774 #[test]
3775 fn parse_header_edit_rejects_nul() {
3776 let entry = HeaderEditConfig {
3777 position: "both".to_owned(),
3778 key: "X-Test".to_owned(),
3779 value: "with\0nul".to_owned(),
3780 };
3781 assert!(matches!(
3782 parse_header_edit(0, &entry),
3783 Err(ConfigError::InvalidHeaderBytes { .. })
3784 ));
3785 }
3786
3787 #[test]
3793 fn parse_header_edit_accepts_tab_in_value() {
3794 let entry = HeaderEditConfig {
3795 position: "request".to_owned(),
3796 key: "X-Test".to_owned(),
3797 value: "with\ttab".to_owned(),
3798 };
3799 let header = parse_header_edit(0, &entry).expect("tab in value must be accepted");
3800 assert_eq!(header.val, "with\ttab");
3801 }
3802
3803 #[test]
3810 fn parse_header_edit_rejects_tab_in_key() {
3811 let entry = HeaderEditConfig {
3812 position: "request".to_owned(),
3813 key: "Host\t".to_owned(),
3814 value: "ok".to_owned(),
3815 };
3816 let err = parse_header_edit(0, &entry).expect_err("HTAB in key must be rejected");
3817 match err {
3818 ConfigError::InvalidHeaderBytes { field, .. } => assert_eq!(field, "key"),
3819 other => panic!("expected InvalidHeaderBytes{{field=\"key\"}}, got {other:?}"),
3820 }
3821 }
3822
3823 #[test]
3824 fn parse_header_edit_rejects_space_in_key() {
3825 let entry = HeaderEditConfig {
3826 position: "request".to_owned(),
3827 key: "X Test".to_owned(),
3828 value: "ok".to_owned(),
3829 };
3830 let err = parse_header_edit(0, &entry).expect_err("SP in key must be rejected");
3831 assert!(matches!(err, ConfigError::InvalidHeaderBytes { .. }));
3832 }
3833
3834 #[test]
3835 fn parse_header_edit_rejects_empty_key() {
3836 let entry = HeaderEditConfig {
3837 position: "request".to_owned(),
3838 key: String::new(),
3839 value: "ok".to_owned(),
3840 };
3841 let err = parse_header_edit(0, &entry).expect_err("empty key must be rejected");
3842 assert!(matches!(
3843 err,
3844 ConfigError::InvalidHeaderBytes { field: "key", .. }
3845 ));
3846 }
3847
3848 #[test]
3849 fn parse_header_edit_accepts_clean_value() {
3850 let entry = HeaderEditConfig {
3851 position: "request".to_owned(),
3852 key: "X-Tenant".to_owned(),
3853 value: "alpha".to_owned(),
3854 };
3855 let header = parse_header_edit(0, &entry).expect("clean value must be accepted");
3856 assert_eq!(header.key, "X-Tenant");
3857 assert_eq!(header.val, "alpha");
3858 }
3859
3860 #[test]
3864 fn resolve_answer_source_bare_string_is_literal() {
3865 let body = resolve_answer_source("HTTP/1.1 503 Service Unavailable\r\n\r\nbusy")
3866 .expect("bare-string source must resolve");
3867 assert_eq!(body, "HTTP/1.1 503 Service Unavailable\r\n\r\nbusy");
3868 }
3869
3870 #[test]
3871 fn resolve_answer_source_empty_string_is_legitimate() {
3872 let body = resolve_answer_source("").expect("empty source must resolve");
3873 assert_eq!(body, "");
3874 }
3875
3876 #[test]
3881 fn resolve_answer_source_file_scheme_missing_file_errors() {
3882 let err = resolve_answer_source("file:///nonexistent/sozu-test/never.http")
3883 .expect_err("missing path must error");
3884 assert!(matches!(err, ConfigError::FileOpen { .. }));
3885 }
3886
3887 #[test]
3890 fn resolve_answer_source_file_scheme_empty_path_errors() {
3891 let err = resolve_answer_source("file://").expect_err("empty path must error");
3892 assert!(matches!(err, ConfigError::FileOpen { .. }));
3893 }
3894}