Compare commits

...

2 commits

Author SHA1 Message Date
f38ed373b8 Better error management 2025-04-12 21:41:09 +02:00
f08543b533 Disable keepalive 2025-04-12 21:04:10 +02:00
6 changed files with 276 additions and 52 deletions

215
Cargo.lock generated
View file

@ -83,7 +83,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
@ -119,6 +119,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "colored"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.52.0",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -146,6 +156,15 @@ dependencies = [
"libc",
]
[[package]]
name = "deranged"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
[[package]]
name = "digest"
version = "0.11.0-pre.10"
@ -210,6 +229,12 @@ dependencies = [
"typenum",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "keccak"
version = "0.2.0-pre.0"
@ -219,12 +244,24 @@ dependencies = [
"cpufeatures",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
@ -237,6 +274,7 @@ version = "0.1.0"
dependencies = [
"argp",
"base64",
"log",
"rand",
"realm_io",
"realm_syscall",
@ -244,6 +282,7 @@ dependencies = [
"saphyr",
"sha2",
"sha3",
"simple_logger",
"static_cell",
"subtle",
"tokio",
@ -266,7 +305,22 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
@ -296,6 +350,12 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -443,6 +503,26 @@ dependencies = [
"hashlink",
]
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha2"
version = "0.11.0-pre.5"
@ -464,6 +544,18 @@ dependencies = [
"keccak",
]
[[package]]
name = "simple_logger"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb"
dependencies = [
"colored",
"log",
"time",
"windows-sys 0.48.0",
]
[[package]]
name = "socket2"
version = "0.5.8"
@ -471,7 +563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -500,6 +592,39 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tokio"
version = "1.44.1"
@ -513,7 +638,7 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -563,13 +688,37 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
@ -578,28 +727,46 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@ -612,24 +779,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"

View file

@ -10,6 +10,7 @@ license = "AGPL-3.0-only"
[dependencies]
argp = "0.4.0"
base64 = "0.22.1"
log = "0.4.27"
rand = "0.8.5"
realm_io = { version = "0.5" }
realm_syscall = "0.1"
@ -18,6 +19,7 @@ saphyr = { version = "0.0.3", default-features = false }
sha2 = { version = "0.11.0-pre.5", default-features = false, features = ["std"] }
# TODO test feature asm on ARM
sha3 = { version = "0.11.0-pre.5", default-features = false, features = ["std"] }
simple_logger = "5.0.0"
static_cell = { version = "2.1.0", features = ["nightly"] }
subtle = { version = "2.6.1", default-features = false, features = ["const-generics", "nightly", "std"] }
tokio = { version = "1", features = ["io-util", "macros", "rt", "rt-multi-thread", "time"] }

View file

@ -2,6 +2,10 @@
Mesozoa is a small animal living between a reverse-proxy and a server, protecting the server from crawlers by forcing the browser to run proof of work.
It inspects request's HTTP header and passes the socket to the server directly (zero-copy).
[Try it online.](https://git.txmn.tk/tuxmain/mesozoa/commits/branch/main) (remove the cookie `mesozoa-proof` or change User-Agent to renew the experience)
## Why?
Why not Anubis? Because it provides no build instructions and only supports Docker.
@ -12,14 +16,40 @@ And because it looked like a fun little project.
## Install
### Build
[Install rustup](https://rustup.rs) and a nightly Rust toolchain.
cargo build --release
Must be used behind a reverse proxy providing `X-Forwarded-For`.
### Run
./target/release/mesozoa -c example-config.yaml
### Apache config
Note that the reverse-proxy must provide the HTTP header `X-Forwarded-For`.
Add this to your virtual host:
```
ProxyPreserveHost On
ProxyRequests Off
ProxyTimeout 600
<Proxy "http://127.0.0.1:8504">
ProxySet keepalive=Off
</Proxy>
<Location />
ProxyPass http://127.0.0.1:8504/
</Location>
```
**Note on keepalive**: When keepalive is On, connections between Apache and server are re-used, even for requests from different clients.
This increases server performance as it reduces connection overhead, but prevents Mesozoa from intercepting HTTP headers.
Hence we have to disable keepalive around Mesozoa. This does not prevent using keepalive between Apache and client.
## Challenge protocol
### Challenge generation

View file

@ -17,7 +17,7 @@ challenge-timeout: 3600
# Action applied when no policy matches
# (see below for legal values)
default-action: challenge
default-action: allow
# Policy groups are evaluated in order.
# The first matching group stops evaluation.

View file

@ -7,4 +7,8 @@ pub struct Cli {
/// Path to config file
#[argp(option, short = 'c')]
pub config: String,
/// Log level (error|warn|info|debug|trace)
#[argp(option, short = 'l', default = "String::from(\"info\")")]
pub log: String,
}

View file

@ -8,9 +8,9 @@ use http::HeaderLineIterator;
use policy::CompiledPolicies;
use rand::Rng;
use realm_syscall::socket2::TcpKeepalive;
// use realm_syscall::socket2::TcpKeepalive;
use regex::bytes::Regex;
use std::{net::SocketAddr, time::Duration};
use std::{net::SocketAddr, str::FromStr, time::Duration};
use tokio::{
io::{AsyncWriteExt, ReadBuf},
net::{TcpSocket, TcpStream},
@ -37,6 +37,12 @@ macro_rules! mk_static {
async fn main() {
let cli: cli::Cli = argp::parse_args_or_exit(argp::DEFAULT);
simple_logger::init_with_level(log::Level::from_str(&cli.log).unwrap_or_else(|_| {
log::error!("Invalid log level option. Fallback to default.");
log::Level::Info
}))
.unwrap();
let config = &*mk_static!(config::Config, config::Config::from_file(&cli.config));
let mut rng = rand::thread_rng();
@ -100,14 +106,14 @@ async fn main() {
.await
.is_err()
{
// Peek timeout
return;
log::debug!("Peek timeout");
return Ok(());
}
let mut header_line_iter = HeaderLineIterator::new(&buf);
let Some(first_line) = header_line_iter.next() else {
// Not HTTP, or too long line
return;
log::debug!("Not HTTP, or too long line");
return Ok(());
};
let mut action = config.default_action;
@ -189,64 +195,55 @@ async fn main() {
&challenge_mac,
);
client_stream.writable().await.unwrap();
client_stream
.write_all(response_begin.as_bytes())
.await
.unwrap();
client_stream.writable().await?;
client_stream.write_all(response_begin.as_bytes()).await?;
if !valid_challenge {
client_stream
.write_all(b"set-cookie: mesozoa-challenge=")
.await
.unwrap();
client_stream
.write_all(challenge_cookie.as_bytes())
.await
.unwrap();
client_stream.write_all(b"; domain=").await.unwrap();
client_stream
.write_all(config.domain.as_bytes())
.await
.unwrap();
.await?;
client_stream.write_all(challenge_cookie.as_bytes()).await?;
client_stream.write_all(b"; domain=").await?;
client_stream.write_all(config.domain.as_bytes()).await?;
client_stream
.write_all(b"; path=/; max-age=3600; samesite=strict\r\n")
.await
.unwrap();
.await?;
}
client_stream.write_all(b"\r\n").await.unwrap();
client_stream
.write_all(CHALLENGE_BODY.as_bytes())
.await
.unwrap();
client_stream.write_all(b"\r\n").await?;
client_stream.write_all(CHALLENGE_BODY.as_bytes()).await?;
}
}
}
std::io::Result::Ok(())
});
}
}
async fn do_proxy(pass_addr: SocketAddr, mut client_stream: TcpStream) {
let keepalive_dur = Duration::from_secs(15);
let mut keepalive = TcpKeepalive::new().with_time(keepalive_dur);
keepalive = TcpKeepalive::with_interval(keepalive, keepalive_dur);
keepalive = TcpKeepalive::with_retries(keepalive, 3);
// let keepalive_dur = Duration::from_secs(15);
// let mut keepalive = TcpKeepalive::new().with_time(keepalive_dur);
// keepalive = TcpKeepalive::with_interval(keepalive, keepalive_dur);
// keepalive = TcpKeepalive::with_retries(keepalive, 3);
let pass_socket = realm_syscall::new_tcp_socket(&pass_addr).unwrap();
let Ok(pass_socket) = realm_syscall::new_tcp_socket(&pass_addr) else {
return;
};
pass_socket.set_reuse_address(true).ok();
pass_socket.set_tcp_keepalive(&keepalive).ok();
// pass_socket.set_tcp_keepalive(&keepalive).ok();
let pass_socket = TcpSocket::from_std_stream(pass_socket.into());
let mut pass_stream = pass_socket.connect(pass_addr).await.unwrap();
let Ok(mut pass_stream) = pass_socket.connect(pass_addr).await else {
return;
};
match realm_io::bidi_zero_copy(&mut client_stream, &mut pass_stream).await {
Ok(_) => {}
Err(ref e) if e.kind() == tokio::io::ErrorKind::InvalidInput => {
realm_io::bidi_copy(&mut client_stream, &mut pass_stream)
.await
.unwrap();
.ok();
}
Err(e) => panic!("err {}", e),
Err(e) => log::error!("Error passing socket: {e:?}"),
}
}