diff options
author | metamuffin <metamuffin@disroot.org> | 2023-02-11 21:45:16 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-02-11 21:45:16 +0100 |
commit | 1284ac8ac8ab0797b908fd9cc8db8b682bc4373f (patch) | |
tree | 060ebed333d4127663e9f1c21db514a3486a6b58 /src/main.rs | |
download | gnix-1284ac8ac8ab0797b908fd9cc8db8b682bc4373f.tar gnix-1284ac8ac8ab0797b908fd9cc8db8b682bc4373f.tar.bz2 gnix-1284ac8ac8ab0797b908fd9cc8db8b682bc4373f.tar.zst |
works
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 139 |
1 files changed, 139 insertions, 0 deletions
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<Config>) -> 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<Config>) -> 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<T: AsyncRead + AsyncWrite + Unpin + Send + 'static>( + config: Arc<Config>, + 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<Vec<rustls::Certificate>> { + 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<rustls::PrivateKey> { + 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<Config>, + mut req: Request<Incoming>, +) -> Result<hyper::Response<BoxBody<bytes::Bytes, hyper::Error>>, 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<Bytes, hyper::Error> { + Empty::<Bytes>::new() + .map_err(|never| match never {}) + .boxed() +} |