doc and better names
This commit is contained in:
parent
0356bb1329
commit
a884edf2f2
5 changed files with 320 additions and 143 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -252,18 +252,6 @@ dependencies = [
|
||||||
"vec_map",
|
"vec_map",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cli"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
"clipboard",
|
|
||||||
"pow_sha256",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard"
|
name = "clipboard"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -730,6 +718,18 @@ version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mcaptcha-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"clipboard",
|
||||||
|
"pow_sha256",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.4"
|
version = "2.3.4"
|
||||||
|
|
131
src/counter.rs
131
src/counter.rs
|
@ -1,49 +1,108 @@
|
||||||
|
/*
|
||||||
|
* mCaptcha - A proof of work based DoS protection system
|
||||||
|
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
//! MCaptcha actor module that manages defense levels
|
||||||
|
//!
|
||||||
|
//! ## Usage:
|
||||||
|
//! ```rust
|
||||||
|
//! use m_captcha::{message::Visitor,MCaptchaBuilder, LevelBuilder, DefenseBuilder};
|
||||||
|
//! // traits from actix needs to be in scope for starting actor
|
||||||
|
//! use actix::prelude::*;
|
||||||
|
//!
|
||||||
|
//! #[actix_rt::main]
|
||||||
|
//! async fn main() -> std::io::Result<()> {
|
||||||
|
//! // configure defense
|
||||||
|
//! let defense = DefenseBuilder::default()
|
||||||
|
//! // add as many levels as you see fit
|
||||||
|
//! .add_level(
|
||||||
|
//! LevelBuilder::default()
|
||||||
|
//! // visitor_threshold is the threshold/limit at which
|
||||||
|
//! // mCaptcha will adjust difficulty levels
|
||||||
|
//! // it is advisable to set small values for the first
|
||||||
|
//! // levels visitor_threshold and difficulty_factor
|
||||||
|
//! // as this will be the work that clients will be
|
||||||
|
//! // computing when there's no load
|
||||||
|
//! .visitor_threshold(50)
|
||||||
|
//! .difficulty_factor(500)
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap(),
|
||||||
|
//! )
|
||||||
|
//! .unwrap()
|
||||||
|
//! .add_level(
|
||||||
|
//! LevelBuilder::default()
|
||||||
|
//! .visitor_threshold(5000)
|
||||||
|
//! .difficulty_factor(50000)
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap(),
|
||||||
|
//! )
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! // create and start MCaptcha actor
|
||||||
|
//! let mcaptcha = MCaptchaBuilder::default()
|
||||||
|
//! .defense(defense)
|
||||||
|
//! // leaky bucket algorithm's emission interval
|
||||||
|
//! .duration(30)
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap()
|
||||||
|
//! .start();
|
||||||
|
//!
|
||||||
|
//! // increment count when user visits protected routes
|
||||||
|
//! mcaptcha.send(Visitor).await.unwrap();
|
||||||
|
//!
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
//use lazy_static::*;
|
|
||||||
|
|
||||||
use crate::levels::Defense;
|
use crate::defense::Defense;
|
||||||
//use crate::new_levels::Levels;
|
|
||||||
|
|
||||||
// TODO move this into config parameter
|
/// Message to increment the visitor count
|
||||||
// lazy_static! {
|
|
||||||
// pub static ref DURATION: Duration = Duration::new(POW_SESSION_DURATION, 0);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Add visitor message
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "u32")]
|
#[rtype(result = "u32")]
|
||||||
pub struct Visitor;
|
pub struct Visitor;
|
||||||
|
|
||||||
|
/// Message to decrement the visitor count
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
struct DeleteVisitor;
|
struct DeleteVisitor;
|
||||||
|
|
||||||
|
/// This struct represents the mCaptcha state and is used
|
||||||
|
/// to configure leaky-bucket lifetime and manage defense
|
||||||
#[derive(Builder)]
|
#[derive(Builder)]
|
||||||
pub struct Counter {
|
pub struct MCaptcha {
|
||||||
#[builder(default = "0", setter(skip))]
|
#[builder(default = "0", setter(skip))]
|
||||||
visitor_count: u32,
|
visitor_threshold: u32,
|
||||||
defense: Defense,
|
defense: Defense,
|
||||||
duration: u64,
|
duration: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Default for Counter {
|
impl MCaptcha {
|
||||||
// fn default() -> Self {
|
|
||||||
// Counter {
|
|
||||||
// visitor_count: 0,
|
|
||||||
// levels: Levels::default(),
|
|
||||||
// duration: 30,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl Counter {
|
|
||||||
/// incerment visiotr count by one
|
/// incerment visiotr count by one
|
||||||
pub fn add_visitor(&mut self) {
|
pub fn add_visitor(&mut self) {
|
||||||
self.visitor_count += 1;
|
self.visitor_threshold += 1;
|
||||||
if self.visitor_count > self.defense.visitor_threshold() {
|
if self.visitor_threshold > self.defense.visitor_threshold() {
|
||||||
self.defense.tighten_up();
|
self.defense.tighten_up();
|
||||||
} else {
|
} else {
|
||||||
self.defense.loosen_up();
|
self.defense.loosen_up();
|
||||||
|
@ -52,8 +111,8 @@ impl Counter {
|
||||||
|
|
||||||
/// deccerment visiotr count by one
|
/// deccerment visiotr count by one
|
||||||
pub fn decrement_visiotr(&mut self) {
|
pub fn decrement_visiotr(&mut self) {
|
||||||
if self.visitor_count > 0 {
|
if self.visitor_threshold > 0 {
|
||||||
self.visitor_count -= 1;
|
self.visitor_threshold -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +122,11 @@ impl Counter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for Counter {
|
impl Actor for MCaptcha {
|
||||||
type Context = Context<Self>;
|
type Context = Context<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<Visitor> for Counter {
|
impl Handler<Visitor> for MCaptcha {
|
||||||
type Result = u32;
|
type Result = u32;
|
||||||
fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, _: Visitor, ctx: &mut Self::Context) -> Self::Result {
|
||||||
use actix::clock::delay_for;
|
use actix::clock::delay_for;
|
||||||
|
@ -87,7 +146,7 @@ impl Handler<Visitor> for Counter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<DeleteVisitor> for Counter {
|
impl Handler<DeleteVisitor> for MCaptcha {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
self.decrement_visiotr();
|
self.decrement_visiotr();
|
||||||
|
@ -97,9 +156,9 @@ impl Handler<DeleteVisitor> for Counter {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::levels::*;
|
use crate::defense::*;
|
||||||
|
|
||||||
// constants foor testing
|
// constants for testing
|
||||||
// (visitor count, level)
|
// (visitor count, level)
|
||||||
const LEVEL_1: (u32, u32) = (50, 50);
|
const LEVEL_1: (u32, u32) = (50, 50);
|
||||||
const LEVEL_2: (u32, u32) = (500, 500);
|
const LEVEL_2: (u32, u32) = (500, 500);
|
||||||
|
@ -109,7 +168,7 @@ mod tests {
|
||||||
DefenseBuilder::default()
|
DefenseBuilder::default()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(LEVEL_1.0)
|
.visitor_threshold(LEVEL_1.0)
|
||||||
.difficulty_factor(LEVEL_1.1)
|
.difficulty_factor(LEVEL_1.1)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -118,7 +177,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(LEVEL_2.0)
|
.visitor_threshold(LEVEL_2.0)
|
||||||
.difficulty_factor(LEVEL_2.1)
|
.difficulty_factor(LEVEL_2.1)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -129,14 +188,14 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn race(addr: Addr<Counter>, count: (u32, u32)) {
|
async fn race(addr: Addr<MCaptcha>, count: (u32, u32)) {
|
||||||
for _ in 0..count.0 as usize - 1 {
|
for _ in 0..count.0 as usize - 1 {
|
||||||
let _ = addr.send(Visitor).await.unwrap();
|
let _ = addr.send(Visitor).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_counter() -> Counter {
|
fn get_counter() -> MCaptcha {
|
||||||
CounterBuilder::default()
|
MCaptchaBuilder::default()
|
||||||
.defense(get_defense())
|
.defense(get_defense())
|
||||||
.duration(DURATION)
|
.duration(DURATION)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -1,56 +1,76 @@
|
||||||
// //! ```rust
|
/*
|
||||||
// //! DefenseBuilder::default()
|
* mCaptcha - A proof of work based DoS protection system
|
||||||
// //! .add_level(
|
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
|
||||||
// //! LevelBuilder::default()
|
*
|
||||||
// //! .visitor_count(50)
|
* This program is free software: you can redistribute it and/or modify
|
||||||
// //! .difficulty_factor(50)
|
* it under the terms of the GNU Affero General Public License as
|
||||||
// //! .unwrap()
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
// //! .build()
|
* License, or (at your option) any later version.
|
||||||
// //! .unwrap(),
|
*
|
||||||
// //! )
|
* This program is distributed in the hope that it will be useful,
|
||||||
// //! .unwrap()
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// //! .add_level(
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// //! LevelBuilder::default()
|
* GNU Affero General Public License for more details.
|
||||||
// //! .visitor_count(500)
|
*
|
||||||
// //! .difficulty_factor(500)
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
// //! .unwrap()
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
// //! .build()
|
*/
|
||||||
// //! .unwrap(),
|
|
||||||
// //! )
|
//! Defense datatypes
|
||||||
// //! .unwrap()
|
//! ```rust
|
||||||
// //! .build()
|
//! use m_captcha::{LevelBuilder, DefenseBuilder};
|
||||||
// //! .unwrap();
|
//! DefenseBuilder::default()
|
||||||
// //! ```
|
//! .add_level(
|
||||||
|
//! LevelBuilder::default()
|
||||||
|
//! .visitor_threshold(50)
|
||||||
|
//! .difficulty_factor(50)
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap(),
|
||||||
|
//! )
|
||||||
|
//! .unwrap()
|
||||||
|
//! .add_level(
|
||||||
|
//! LevelBuilder::default()
|
||||||
|
//! .visitor_threshold(500)
|
||||||
|
//! .difficulty_factor(500)
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap(),
|
||||||
|
//! )
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//! ```
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
|
||||||
/// Level struct
|
/// Level struct that describes threshold-difficulty factor mapping
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct Level {
|
pub struct Level {
|
||||||
visitor_count: u32,
|
visitor_threshold: u32,
|
||||||
difficulty_factor: u32,
|
difficulty_factor: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Level {
|
impl Default for Level {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Level {
|
Level {
|
||||||
visitor_count: 0,
|
visitor_threshold: 0,
|
||||||
difficulty_factor: 0,
|
difficulty_factor: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set difficulty configuration
|
/// Bulder struct for [Level] to describe threshold-difficulty factor mapping
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct LevelBuilder {
|
pub struct LevelBuilder {
|
||||||
visitor_count: Option<u32>,
|
visitor_threshold: Option<u32>,
|
||||||
difficulty_factor: Option<u32>,
|
difficulty_factor: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LevelBuilder {
|
impl Default for LevelBuilder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
LevelBuilder {
|
LevelBuilder {
|
||||||
visitor_count: None,
|
visitor_threshold: None,
|
||||||
difficulty_factor: None,
|
difficulty_factor: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,14 +78,13 @@ impl Default for LevelBuilder {
|
||||||
|
|
||||||
impl LevelBuilder {
|
impl LevelBuilder {
|
||||||
/// set visitor count for level
|
/// set visitor count for level
|
||||||
pub fn visitor_count(&mut self, visitor_count: u32) -> &mut Self {
|
pub fn visitor_threshold(&mut self, visitor_threshold: u32) -> &mut Self {
|
||||||
self.visitor_count = Some(visitor_count);
|
self.visitor_threshold = Some(visitor_threshold);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set difficulty factor for level
|
/// set difficulty factor for level. difficulty_factor can't be zero because
|
||||||
/// difficulty_factor can't be zero because
|
/// Difficulty is calculated as:
|
||||||
/// Difficulty is calculated as
|
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// let difficulty_factor = 500;
|
/// let difficulty_factor = 500;
|
||||||
/// let difficulty = u128::max_value() - u128::max_value() / difficulty_factor;
|
/// let difficulty = u128::max_value() - u128::max_value() / difficulty_factor;
|
||||||
|
@ -80,21 +99,22 @@ impl LevelBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// build Level
|
/// build Level struct
|
||||||
pub fn build(&mut self) -> CaptchaResult<Level> {
|
pub fn build(&mut self) -> CaptchaResult<Level> {
|
||||||
if self.visitor_count.is_none() {
|
if self.visitor_threshold.is_none() {
|
||||||
Err(CaptchaError::SetVisitorCount)
|
Err(CaptchaError::SetVisitorCount)
|
||||||
} else if self.difficulty_factor.is_none() {
|
} else if self.difficulty_factor.is_none() {
|
||||||
Err(CaptchaError::SetDifficultyFactor)
|
Err(CaptchaError::SetDifficultyFactor)
|
||||||
} else {
|
} else {
|
||||||
Ok(Level {
|
Ok(Level {
|
||||||
difficulty_factor: self.difficulty_factor.unwrap(),
|
difficulty_factor: self.difficulty_factor.unwrap(),
|
||||||
visitor_count: self.visitor_count.unwrap(),
|
visitor_threshold: self.visitor_threshold.unwrap(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// struct describes all the different [Level]s at which an mCaptcha system operates
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Defense {
|
pub struct Defense {
|
||||||
levels: Vec<Level>,
|
levels: Vec<Level>,
|
||||||
|
@ -102,6 +122,7 @@ pub struct Defense {
|
||||||
current_visitor_threshold: usize,
|
current_visitor_threshold: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder struct for [Defense]
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct DefenseBuilder {
|
pub struct DefenseBuilder {
|
||||||
levels: Vec<Level>,
|
levels: Vec<Level>,
|
||||||
|
@ -114,9 +135,10 @@ impl Default for DefenseBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefenseBuilder {
|
impl DefenseBuilder {
|
||||||
|
/// add a level to [Defense]
|
||||||
pub fn add_level(&mut self, level: Level) -> CaptchaResult<&mut Self> {
|
pub fn add_level(&mut self, level: Level) -> CaptchaResult<&mut Self> {
|
||||||
for i in self.levels.iter() {
|
for i in self.levels.iter() {
|
||||||
if i.visitor_count == level.visitor_count {
|
if i.visitor_threshold == level.visitor_threshold {
|
||||||
return Err(CaptchaError::DuplicateVisitorCount);
|
return Err(CaptchaError::DuplicateVisitorCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,12 +146,11 @@ impl DefenseBuilder {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build [Defense]
|
||||||
pub fn build(&mut self) -> CaptchaResult<Defense> {
|
pub fn build(&mut self) -> CaptchaResult<Defense> {
|
||||||
if !self.levels.is_empty() {
|
if !self.levels.is_empty() {
|
||||||
// sort levels to arrange in ascending order
|
// sort levels to arrange in ascending order
|
||||||
self.levels.sort_by_key(|a| a.visitor_count);
|
self.levels.sort_by_key(|a| a.visitor_threshold);
|
||||||
|
|
||||||
// as visitor count increases, difficulty_factor too should increse
|
|
||||||
|
|
||||||
for level in self.levels.iter() {
|
for level in self.levels.iter() {
|
||||||
if level.difficulty_factor == 0 {
|
if level.difficulty_factor == 0 {
|
||||||
|
@ -137,6 +158,8 @@ impl DefenseBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// as visitor count increases, difficulty_factor too should increse
|
||||||
|
// if it decreses, an error must be thrown
|
||||||
for i in 0..self.levels.len() - 1 {
|
for i in 0..self.levels.len() - 1 {
|
||||||
if self.levels[i].difficulty_factor > self.levels[i + 1].difficulty_factor {
|
if self.levels[i].difficulty_factor > self.levels[i + 1].difficulty_factor {
|
||||||
return Err(CaptchaError::DecreaseingDifficultyFactor);
|
return Err(CaptchaError::DecreaseingDifficultyFactor);
|
||||||
|
@ -163,44 +186,45 @@ impl Default for Defense {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Defense {
|
impl Defense {
|
||||||
///! Difficulty is calculated as
|
///! Difficulty is calculated as:
|
||||||
///! ```rust
|
///! ```rust
|
||||||
///! let difficulty = u128::max_value() - u128::max_value() / difficulty_factor;
|
///! let difficulty = u128::max_value() - u128::max_value() / difficulty_factor;
|
||||||
///! ```
|
///! ```
|
||||||
///! the higher the `difficulty_factor`, the higher the difficulty.
|
///! The higher the `difficulty_factor`, the higher the difficulty.
|
||||||
|
|
||||||
/// get difficulty factor of current level of defense
|
/// Get difficulty factor of current level of defense
|
||||||
pub fn get_difficulty(&self) -> u32 {
|
pub fn get_difficulty(&self) -> u32 {
|
||||||
self.levels[self.current_visitor_threshold].difficulty_factor
|
self.levels[self.current_visitor_threshold].difficulty_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// tighten up defense. Increases defense level by a factor of one
|
/// tighten up defense. Increases defense level by a factor of one.
|
||||||
/// when defense is at max level, calling this method will have no effect
|
/// When defense is at max level, calling this method will have no effect
|
||||||
pub fn tighten_up(&mut self) {
|
pub fn tighten_up(&mut self) {
|
||||||
if self.current_visitor_threshold != self.levels.len() - 1 {
|
if self.current_visitor_threshold != self.levels.len() - 1 {
|
||||||
self.current_visitor_threshold += 1;
|
self.current_visitor_threshold += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// loosen up defense. Decreases defense level by a factor of one
|
/// Loosen up defense. Decreases defense level by a factor of one.
|
||||||
/// when defense is at the lowest level, calling this method will have no effect
|
/// When defense is at the lowest level, calling this method will have no effect.
|
||||||
pub fn loosen_up(&mut self) {
|
pub fn loosen_up(&mut self) {
|
||||||
if self.current_visitor_threshold != 0 {
|
if self.current_visitor_threshold != 0 {
|
||||||
self.current_visitor_threshold -= 1;
|
self.current_visitor_threshold -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set defense to maximum level
|
/// Set defense to maximum level
|
||||||
pub fn max_defense(&mut self) {
|
pub fn max_defense(&mut self) {
|
||||||
self.current_visitor_threshold = self.levels.len() - 1;
|
self.current_visitor_threshold = self.levels.len() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set defense to minimum level
|
/// Set defense to minimum level
|
||||||
pub fn min_defense(&mut self) {
|
pub fn min_defense(&mut self) {
|
||||||
self.current_visitor_threshold = 0;
|
self.current_visitor_threshold = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get current level's visitor threshold
|
||||||
pub fn visitor_threshold(&self) -> u32 {
|
pub fn visitor_threshold(&self) -> u32 {
|
||||||
self.levels[self.current_visitor_threshold].visitor_count
|
self.levels[self.current_visitor_threshold].visitor_threshold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,11 +237,11 @@ mod tests {
|
||||||
let level = LevelBuilder::default()
|
let level = LevelBuilder::default()
|
||||||
.difficulty_factor(1)
|
.difficulty_factor(1)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.visitor_count(0)
|
.visitor_threshold(0)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(level.visitor_count, 0);
|
assert_eq!(level.visitor_threshold, 0);
|
||||||
assert_eq!(level.difficulty_factor, 1);
|
assert_eq!(level.difficulty_factor, 1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -227,12 +251,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn defense_builder_duplicate_visitor_count() {
|
fn defense_builder_duplicate_visitor_threshold() {
|
||||||
let mut defense_builder = DefenseBuilder::default();
|
let mut defense_builder = DefenseBuilder::default();
|
||||||
let err = defense_builder
|
let err = defense_builder
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(50)
|
.visitor_threshold(50)
|
||||||
.difficulty_factor(50)
|
.difficulty_factor(50)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -241,7 +265,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(50)
|
.visitor_threshold(50)
|
||||||
.difficulty_factor(50)
|
.difficulty_factor(50)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -256,7 +280,7 @@ mod tests {
|
||||||
let err = defense_builder
|
let err = defense_builder
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(50)
|
.visitor_threshold(50)
|
||||||
.difficulty_factor(50)
|
.difficulty_factor(50)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -265,7 +289,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(500)
|
.visitor_threshold(500)
|
||||||
.difficulty_factor(10)
|
.difficulty_factor(10)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -280,7 +304,7 @@ mod tests {
|
||||||
DefenseBuilder::default()
|
DefenseBuilder::default()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(50)
|
.visitor_threshold(50)
|
||||||
.difficulty_factor(50)
|
.difficulty_factor(50)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -289,7 +313,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(500)
|
.visitor_threshold(500)
|
||||||
.difficulty_factor(5000)
|
.difficulty_factor(5000)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -298,7 +322,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(5000)
|
.visitor_threshold(5000)
|
||||||
.difficulty_factor(50000)
|
.difficulty_factor(50000)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -307,7 +331,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(50000)
|
.visitor_threshold(50000)
|
||||||
.difficulty_factor(500000)
|
.difficulty_factor(500000)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -316,7 +340,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.add_level(
|
.add_level(
|
||||||
LevelBuilder::default()
|
LevelBuilder::default()
|
||||||
.visitor_count(500000)
|
.visitor_threshold(500000)
|
||||||
.difficulty_factor(5000000)
|
.difficulty_factor(5000000)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
|
@ -395,28 +419,4 @@ mod tests {
|
||||||
defense.loosen_up();
|
defense.loosen_up();
|
||||||
assert_eq!(defense.get_difficulty(), 50);
|
assert_eq!(defense.get_difficulty(), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn threshold_works() {
|
|
||||||
// let mut level = Levels::default();
|
|
||||||
//
|
|
||||||
// assert_eq!(level.threshold(), Levels::One as usize);
|
|
||||||
// level.next();
|
|
||||||
// assert_eq!(level.threshold(), Levels::Two as usize);
|
|
||||||
// level.next();
|
|
||||||
// assert_eq!(level.threshold(), Levels::Three as usize);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[test]
|
|
||||||
// fn difficulty_works() {
|
|
||||||
// let mut level = Levels::default();
|
|
||||||
//
|
|
||||||
// assert_eq!(level.get_difficulty(), Levels::One as u32);
|
|
||||||
// level.next();
|
|
||||||
// assert_eq!(level.get_difficulty(), Levels::Two as u32);
|
|
||||||
// level.next();
|
|
||||||
// assert_eq!(level.get_difficulty(), 100_000);
|
|
||||||
// level.next();
|
|
||||||
// assert_eq!(level.get_difficulty(), 1_000_000);
|
|
||||||
// }
|
|
||||||
}
|
}
|
|
@ -1,18 +1,36 @@
|
||||||
//! Error datatypes
|
/*
|
||||||
|
* mCaptcha - A proof of work based DoS protection system
|
||||||
|
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Errors and Result module
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
|
|
||||||
/// Errors that can occur when using
|
/// Error datatype
|
||||||
#[derive(Debug, PartialEq, Display, Clone, Error)]
|
#[derive(Debug, PartialEq, Display, Clone, Error)]
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
pub enum CaptchaError {
|
pub enum CaptchaError {
|
||||||
/// when configuring m_captcha, [DefenseBuilder][crate::new_levels::DefenseBuilder] must be passed atleast
|
/// When configuring m_captcha, [DefenseBuilder][crate::defense::DefenseBuilder]
|
||||||
/// one `LevelConfig` if not this error will arise
|
/// must be passed atleast one `LevelConfig` if not this error will arise
|
||||||
#[display(fmt = "LevelBuilder should have atleaset one level configured")]
|
#[display(fmt = "LevelBuilder should have atleaset one level configured")]
|
||||||
LevelEmpty,
|
LevelEmpty,
|
||||||
|
|
||||||
/// Visitor count must be an integer
|
/// Visitor count must be a whole number(zero and above).
|
||||||
/// when configuring m_captcha, [LevelBuilder][crate::new_levels::LevelBuilder] difficulty_factor
|
/// When configuring m_captcha, [LevelBuilder][crate::defense::LevelBuilder].
|
||||||
/// must be set to greater than zero.
|
/// difficulty_factor must be set to greater than zero.
|
||||||
#[display(fmt = "difficulty factor must be greater than zero")]
|
#[display(fmt = "difficulty factor must be greater than zero")]
|
||||||
DifficultyFactorZero,
|
DifficultyFactorZero,
|
||||||
|
|
||||||
|
|
102
src/lib.rs
102
src/lib.rs
|
@ -1,3 +1,103 @@
|
||||||
|
/*
|
||||||
|
* mCaptcha - A proof of work based DoS protection system
|
||||||
|
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
//! mCaptcha is a proof of work based Denaial-of-Service attack protection system.
|
||||||
|
//! This is is a server library that you can embed in your services to protect your
|
||||||
|
//! servers.
|
||||||
|
//!
|
||||||
|
//! A commercial managed solution is in the works but I'd much rather prefer
|
||||||
|
//! folks host their own instances as it will make the more decentralized and free.
|
||||||
|
//!
|
||||||
|
//! In mCaptcha, defense is adjusted in discrete levels that depend on the
|
||||||
|
//! ammount of traffic that a service is experiencing. So users of this library are
|
||||||
|
//! requested to benchmark their target machines before configuring their mCaptcha
|
||||||
|
//! component.
|
||||||
|
//!
|
||||||
|
//! ## Terminology:
|
||||||
|
//! - Difficulty(Factor): Minimum ammount of work that a client must do to make a valid
|
||||||
|
//! request.
|
||||||
|
//! - [Defense]: A datatype that various visitor-difficulty mappigns
|
||||||
|
//! - [Visitor][crate::message::Visitor]: Smallest unit of traffic, usually a single request. The more you have, the busier
|
||||||
|
//! your service is. Determines mCaptcha defense defense
|
||||||
|
//! - Visitor threshold: The threshold at which [MCaptcha] will adjust defense defense
|
||||||
|
//!
|
||||||
|
//! ## Example:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use m_captcha::{LevelBuilder, DefenseBuilder, message::Visitor, MCaptchaBuilder};
|
||||||
|
//! // traits from actix needs to be in scope for starting actor
|
||||||
|
//! use actix::prelude::*;
|
||||||
|
//!
|
||||||
|
//! #[actix_rt::main]
|
||||||
|
//! async fn main() -> std::io::Result<()> {
|
||||||
|
//! // configure defense
|
||||||
|
//! let defense = DefenseBuilder::default()
|
||||||
|
//! // add as many defense as you see fit
|
||||||
|
//! .add_level(
|
||||||
|
//! LevelBuilder::default()
|
||||||
|
//! // visitor_threshold is the threshold/limit at which
|
||||||
|
//! // mCaptcha will adjust difficulty defense
|
||||||
|
//! // it is advisable to set small values for the first
|
||||||
|
//! // defense visitor_threshold and difficulty_factor
|
||||||
|
//! // as this will be the work that clients will be
|
||||||
|
//! // computing when there's no load
|
||||||
|
//! .visitor_threshold(50)
|
||||||
|
//! .difficulty_factor(500)
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap(),
|
||||||
|
//! )
|
||||||
|
//! .unwrap()
|
||||||
|
//! .add_level(
|
||||||
|
//! LevelBuilder::default()
|
||||||
|
//! .visitor_threshold(5000)
|
||||||
|
//! .difficulty_factor(50000)
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap(),
|
||||||
|
//! )
|
||||||
|
//! .unwrap()
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! // create and start MCaptcha actor
|
||||||
|
//! let mcaptcha = MCaptchaBuilder::default()
|
||||||
|
//! .defense(defense)
|
||||||
|
//! // leaky bucket algorithm's emission interval
|
||||||
|
//! .duration(30)
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap()
|
||||||
|
//! .start();
|
||||||
|
//!
|
||||||
|
//! // increment count when user visits protected routes
|
||||||
|
//! mcaptcha.send(Visitor).await.unwrap();
|
||||||
|
//!
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
pub mod counter;
|
pub mod counter;
|
||||||
|
pub mod defense;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod levels;
|
|
||||||
|
/// message datatypes to interact with [MCaptcha] actor
|
||||||
|
pub mod message {
|
||||||
|
pub use crate::counter::Visitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use counter::{MCaptcha, MCaptchaBuilder};
|
||||||
|
pub use defense::{Defense, DefenseBuilder, LevelBuilder};
|
||||||
|
|
Loading…
Reference in a new issue