add feature to specify default backend

This commit is contained in:
Jun Kurihara 2022-06-28 11:51:38 -04:00
commit 3491b80142
No known key found for this signature in database
GPG key ID: 48ADFD173ED22B03
9 changed files with 50 additions and 12 deletions

View file

@ -5,6 +5,7 @@ listen_ipv6 = false
max_concurrent_streams = 128 max_concurrent_streams = 128
max_clients = 512 max_clients = 512
default_app = 'localhost' # passing through 127.0.0.1
[apps] [apps]

View file

@ -15,6 +15,13 @@ max_clients = 512
listen_ipv6 = false listen_ipv6 = false
# App that serves all plaintext http request by referring to HOSTS or request header
# execpt for configured application.
# Note that this is only for http.
# Note that nothing is served for requests via https since secure channel cannot be
# established for unconfigured server_name, and they are always rejected by checking SNI.
default_app = 'another_localhost'
################################### ###################################
# Backend settings # # Backend settings #
################################### ###################################

View file

@ -12,6 +12,11 @@ use std::{
}; };
use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig}; use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig};
pub struct Backends {
pub apps: HashMap<String, Backend>, // TODO: hyper::uriで抜いたhostで引っ掛ける。Stringでいいのか
pub default_app: Option<String>, // for plaintext http
}
pub struct Backend { pub struct Backend {
pub app_name: String, pub app_name: String,
pub server_name: String, pub server_name: String,

View file

@ -7,7 +7,7 @@ use std::{collections::HashMap, sync::Mutex};
// #[cfg(feature = "tls")] // #[cfg(feature = "tls")]
use std::path::PathBuf; use std::path::PathBuf;
pub fn parse_opts(globals: &mut Globals, backends: &mut HashMap<String, Backend>) -> Result<()> { pub fn parse_opts(globals: &mut Globals, backends: &mut Backends) -> Result<()> {
let _ = include_str!("../../Cargo.toml"); let _ = include_str!("../../Cargo.toml");
let options = clap::command!().arg( let options = clap::command!().arg(
Arg::new("config_file") Arg::new("config_file")
@ -110,7 +110,7 @@ pub fn parse_opts(globals: &mut Globals, backends: &mut HashMap<String, Backend>
ensure!(app.reverse_proxy.is_some(), "Missing reverse_proxy"); ensure!(app.reverse_proxy.is_some(), "Missing reverse_proxy");
let reverse_proxy = get_reverse_proxy(app.reverse_proxy.as_ref().unwrap())?; let reverse_proxy = get_reverse_proxy(app.reverse_proxy.as_ref().unwrap())?;
backends.insert( backends.apps.insert(
server_name.to_owned(), server_name.to_owned(),
Backend { Backend {
app_name: app_name.to_owned(), app_name: app_name.to_owned(),
@ -125,6 +125,18 @@ pub fn parse_opts(globals: &mut Globals, backends: &mut HashMap<String, Backend>
); );
info!("Registering application: {} ({})", app_name, server_name); info!("Registering application: {} ({})", app_name, server_name);
} }
// default backend application for plaintext http requests
if let Some(d) = config.default_app {
if backends.apps.contains_key(&d) {
info!(
"Serving plaintext http for requests to unconfigured server_name: {}.",
d
);
}
backends.default_app = Some(d);
}
Ok(()) Ok(())
} }

View file

@ -10,6 +10,7 @@ pub struct ConfigToml {
pub max_concurrent_streams: Option<u32>, pub max_concurrent_streams: Option<u32>,
pub max_clients: Option<u32>, pub max_clients: Option<u32>,
pub apps: Option<Apps>, pub apps: Option<Apps>,
pub default_app: Option<String>,
} }
#[derive(Deserialize, Debug, Default)] #[derive(Deserialize, Debug, Default)]

View file

@ -10,7 +10,13 @@ mod log;
mod proxy; mod proxy;
use crate::{ use crate::{
backend::Backend, config::parse_opts, constants::*, error::*, globals::*, log::*, proxy::Proxy, backend::{Backend, Backends},
config::parse_opts,
constants::*,
error::*,
globals::*,
log::*,
proxy::Proxy,
}; };
use futures::future::select_all; use futures::future::select_all;
use hyper::Client; use hyper::Client;
@ -55,7 +61,10 @@ fn main() {
runtime_handle: runtime.handle().clone(), runtime_handle: runtime.handle().clone(),
}; };
let mut backends: HashMap<String, Backend> = HashMap::new(); let mut backends = Backends {
default_app: None,
apps: HashMap::<String, Backend>::new(),
};
let _ = parse_opts(&mut globals, &mut backends).expect("Invalid configuration"); let _ = parse_opts(&mut globals, &mut backends).expect("Invalid configuration");
@ -67,7 +76,7 @@ fn main() {
} }
// entrypoint creates and spawns tasks of proxy services // entrypoint creates and spawns tasks of proxy services
async fn entrypoint(globals: Arc<Globals>, backends: Arc<HashMap<String, Backend>>) -> Result<()> { async fn entrypoint(globals: Arc<Globals>, backends: Arc<Backends>) -> Result<()> {
let connector = TrustDnsResolver::default().into_rustls_webpki_https_connector(); let connector = TrustDnsResolver::default().into_rustls_webpki_https_connector();
let forwarder = Arc::new(Client::builder().build::<_, hyper::Body>(connector)); let forwarder = Arc::new(Client::builder().build::<_, hyper::Body>(connector));

View file

@ -39,8 +39,11 @@ where
} else { } else {
return http_error(StatusCode::SERVICE_UNAVAILABLE); return http_error(StatusCode::SERVICE_UNAVAILABLE);
}; };
let backend = if let Some(be) = self.backends.get(server_name.as_str()) { let backend = if let Some(be) = self.backends.apps.get(server_name.as_str()) {
be be
} else if let Some(default_be) = &self.backends.default_app {
debug!("Serving by default app: {}", default_be);
self.backends.apps.get(default_be).unwrap()
} else { } else {
return http_error(StatusCode::SERVICE_UNAVAILABLE); return http_error(StatusCode::SERVICE_UNAVAILABLE);
}; };

View file

@ -1,9 +1,9 @@
// use super::proxy_handler::handle_request; // use super::proxy_handler::handle_request;
use crate::{backend::Backend, error::*, globals::Globals, log::*}; use crate::{backend::Backends, error::*, globals::Globals, log::*};
use hyper::{ use hyper::{
client::connect::Connect, server::conn::Http, service::service_fn, Body, Client, Request, client::connect::Connect, server::conn::Http, service::service_fn, Body, Client, Request,
}; };
use std::{collections::HashMap, net::SocketAddr, sync::Arc}; use std::{net::SocketAddr, sync::Arc};
use tokio::{ use tokio::{
io::{AsyncRead, AsyncWrite}, io::{AsyncRead, AsyncWrite},
net::TcpListener, net::TcpListener,
@ -38,8 +38,8 @@ where
T: Connect + Clone + Sync + Send + 'static, T: Connect + Clone + Sync + Send + 'static,
{ {
pub listening_on: SocketAddr, pub listening_on: SocketAddr,
pub tls_enabled: bool, // TCP待受がTLSかどうか pub tls_enabled: bool, // TCP待受がTLSかどうか
pub backends: Arc<HashMap<String, Backend>>, // TODO: hyper::uriで抜いたhostで引っ掛ける。Stringでいいのか pub backends: Arc<Backends>,
pub forwarder: Arc<Client<T>>, pub forwarder: Arc<Client<T>>,
pub globals: Arc<Globals>, pub globals: Arc<Globals>,
} }

View file

@ -17,7 +17,7 @@ where
let cert_service = async { let cert_service = async {
info!("Start cert watch service for {}", self.listening_on); info!("Start cert watch service for {}", self.listening_on);
loop { loop {
for (server_name, backend) in self.backends.iter() { for (server_name, backend) in self.backends.apps.iter() {
if backend.tls_cert_key_path.is_some() && backend.tls_cert_path.is_some() { if backend.tls_cert_key_path.is_some() && backend.tls_cert_path.is_some() {
if let Err(_e) = backend.update_server_config().await { if let Err(_e) = backend.update_server_config().await {
warn!("Failed to update certs for {}", server_name); warn!("Failed to update certs for {}", server_name);
@ -53,7 +53,7 @@ where
info!("No SNI in ClientHello"); info!("No SNI in ClientHello");
continue; continue;
}; };
let backend_serve = if let Some(backend_serve) = self.backends.get(svn){ let backend_serve = if let Some(backend_serve) = self.backends.apps.get(svn){
backend_serve backend_serve
} else { } else {
info!("No configuration for the server name {} given in client_hello", svn); info!("No configuration for the server name {} given in client_hello", svn);