From 1284ac8ac8ab0797b908fd9cc8db8b682bc4373f Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sat, 11 Feb 2023 21:45:16 +0100 Subject: works --- src/config.rs | 34 ++++++++++++++ src/main.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/config.rs create mode 100644 src/main.rs (limited to 'src') diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..527e159 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,34 @@ +use std::{collections::HashMap, fs::read_to_string, net::SocketAddr, path::PathBuf}; + +use anyhow::Context; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + pub http: Option, + pub https: Option, + pub hosts: HashMap, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct HttpConfig { + pub bind: SocketAddr, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct HttpsConfig { + pub bind: SocketAddr, + pub tls_cert: PathBuf, + pub tls_key: PathBuf, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct HostConfig {} + +impl Config { + pub fn load() -> anyhow::Result { + let raw = read_to_string("config.toml").context("reading config file")?; + let config: Config = toml::from_str(&raw).context("parsing config")?; + Ok(config) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..583a069 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,139 @@ +pub mod config; + +use crate::config::Config; +use anyhow::{bail, Context, Result}; +use bytes::Bytes; +use http_body_util::{combinators::BoxBody, BodyExt, Empty}; +use hyper::{body::Incoming, server::conn::http1, service::service_fn, Method, Request, Response}; +use log::{debug, error, info}; +use std::{fs::File, io::BufReader, path::Path, sync::Arc}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::{TcpListener, TcpStream}, +}; +use tokio_rustls::TlsAcceptor; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env_logger::init_from_env("LOG"); + + let config = Arc::new(Config::load()?); + + tokio::select! { + x = serve_http(config.clone()) => x.context("serving http")?, + x = serve_https(config.clone()) => x.context("serving https")?, + }; + Ok(()) +} + +async fn serve_http(config: Arc) -> Result<()> { + let http_config = match &config.http { + Some(n) => n, + None => return Ok(()), + }; + let listener = TcpListener::bind(http_config.bind).await?; + info!("serving http"); + loop { + let (stream, addr) = listener.accept().await.context("accepting connection")?; + debug!("connection from {addr}"); + serve_stream(config.clone(), stream) + } +} +async fn serve_https(config: Arc) -> Result<()> { + let https_config = match &config.https { + Some(n) => n, + None => return Ok(()), + }; + let tls_config = { + let certs = load_certs(&https_config.tls_cert)?; + let key = load_private_key(&https_config.tls_key)?; + let mut cfg = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs, key)?; + cfg.alpn_protocols = vec![ + // b"h2".to_vec(), + b"http/1.1".to_vec(), + ]; + Arc::new(cfg) + }; + let listener = TcpListener::bind(https_config.bind).await?; + let tls_acceptor = TlsAcceptor::from(tls_config); + + info!("serving https"); + loop { + let (stream, addr) = listener.accept().await.context("accepting connection")?; + debug!("connection from {addr}"); + let stream = tls_acceptor.accept(stream).await.context("accepting tls")?; + serve_stream(config.clone(), stream) + } +} + +pub fn serve_stream( + config: Arc, + stream: T, +) { + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection(stream, service_fn(move |req| service(config.clone(), req))) + .await + { + error!("{:?}", err); + } + }); +} + +fn load_certs(path: &Path) -> anyhow::Result> { + let mut reader = BufReader::new(File::open(path).context("reading tls certs")?); + let certs = rustls_pemfile::certs(&mut reader).context("parsing tls certs")?; + Ok(certs.into_iter().map(rustls::Certificate).collect()) +} +fn load_private_key(path: &Path) -> anyhow::Result { + let mut reader = BufReader::new(File::open(path).context("reading tls private key")?); + let keys = + rustls_pemfile::pkcs8_private_keys(&mut reader).context("parsing tls private key")?; + if keys.len() != 1 { + bail!("expected a single private key, found {}", keys.len()) + } + Ok(rustls::PrivateKey(keys[0].clone())) +} + +async fn service( + config: Arc, + mut req: Request, +) -> Result>, hyper::Error> { + let uri_string = format!( + "http://127.0.0.1:8080{}", + req.uri() + .path_and_query() + .map(|x| x.as_str()) + .unwrap_or("/") + ); + let uri = uri_string.parse().unwrap(); + *req.uri_mut() = uri; + + let host = req.uri().host().expect("uri has no host"); + let port = req.uri().port_u16().unwrap_or(80); + let addr = format!("{}:{}", host, port); + + let client_stream = TcpStream::connect(addr).await.unwrap(); + + if req.method() == Method::CONNECT { + Ok(Response::new(empty())) + } else { + let (mut sender, conn) = hyper::client::conn::http1::handshake(client_stream).await?; + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + let resp = sender.send_request(req).await?; + Ok(resp.map(|b| b.boxed())) + } +} + +fn empty() -> BoxBody { + Empty::::new() + .map_err(|never| match never {}) + .boxed() +} -- cgit v1.2.3-70-g09d2