aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-02-11 21:45:16 +0100
committermetamuffin <metamuffin@disroot.org>2023-02-11 21:45:16 +0100
commit1284ac8ac8ab0797b908fd9cc8db8b682bc4373f (patch)
tree060ebed333d4127663e9f1c21db514a3486a6b58 /src
downloadgnix-1284ac8ac8ab0797b908fd9cc8db8b682bc4373f.tar
gnix-1284ac8ac8ab0797b908fd9cc8db8b682bc4373f.tar.bz2
gnix-1284ac8ac8ab0797b908fd9cc8db8b682bc4373f.tar.zst
works
Diffstat (limited to 'src')
-rw-r--r--src/config.rs34
-rw-r--r--src/main.rs139
2 files changed, 173 insertions, 0 deletions
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<HttpConfig>,
+ pub https: Option<HttpsConfig>,
+ pub hosts: HashMap<String, HostConfig>,
+}
+
+#[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<Config> {
+ 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<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()
+}