mirror of
https://codeberg.org/YGGverse/psocks.git
synced 2026-03-31 08:25:27 +00:00
grand refactory to multiple list-based control api
This commit is contained in:
parent
827cb182f2
commit
b93c1e8481
15 changed files with 271 additions and 282 deletions
68
Cargo.lock
generated
68
Cargo.lock
generated
|
|
@ -455,7 +455,7 @@ dependencies = [
|
||||||
"atomic 0.6.1",
|
"atomic 0.6.1",
|
||||||
"pear",
|
"pear",
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml 0.8.23",
|
||||||
"uncased",
|
"uncased",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
@ -1438,6 +1438,8 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"structopt",
|
"structopt",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml 1.1.0+spec-1.1.0",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1979,6 +1981,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
@ -2361,11 +2372,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned 0.6.9",
|
||||||
"toml_datetime",
|
"toml_datetime 0.6.11",
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "1.1.0+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde_core",
|
||||||
|
"serde_spanned 1.1.0",
|
||||||
|
"toml_datetime 1.1.0+spec-1.1.0",
|
||||||
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
|
"winnow 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
|
|
@ -2375,6 +2401,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "1.1.0+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.27"
|
version = "0.22.27"
|
||||||
|
|
@ -2383,10 +2418,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned 0.6.9",
|
||||||
"toml_datetime",
|
"toml_datetime 0.6.11",
|
||||||
"toml_write",
|
"toml_write",
|
||||||
"winnow",
|
"winnow 0.7.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.1.0+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
|
||||||
|
dependencies = [
|
||||||
|
"winnow 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2395,6 +2439,12 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_writer"
|
||||||
|
version = "1.1.0+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -3126,6 +3176,12 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,5 @@ rocket = { version = "0.5.1", features = ["json"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
structopt = "0.3.26"
|
structopt = "0.3.26"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
toml = "1.1.0"
|
||||||
|
url = "2.5.8"
|
||||||
|
|
|
||||||
19
README.md
19
README.md
|
|
@ -16,19 +16,13 @@ Filtering asynchronous SOCKS5 (TCP/UDP) proxy server based on [fast-socks5](http
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
RUST_LOG=trace cargo run -- --allow=http://localhost/allow.txt \
|
RUST_LOG=trace cargo run -- -c=/path/to/config.toml no-auth
|
||||||
--allow=/path/to/allow.txt \
|
|
||||||
no-auth
|
|
||||||
```
|
```
|
||||||
* set `socks5://127.0.0.1:1080` proxy in your application
|
* set `socks5://127.0.0.1:1080` proxy in your application
|
||||||
* use http://127.0.0.1:8010 for API:
|
* use http://127.0.0.1:8010 for API:
|
||||||
* `/api/allow/<domain.com>` - add rule to the current session
|
* `/api/totals` - blocking summary
|
||||||
* `/api/block/<domain.com>` - delete rule from the current session
|
* `/api/list/enable/<ID>` - enable all parsed rules of given list ID (`[list.ID]` in your config)
|
||||||
* `/api/rules` - return active rules (from server memory)
|
* `/api/list/disable/<ID>` - disable all parsed rules of given list ID (`[list.ID]` in your config)
|
||||||
* `/api/lists` - get parsed lists with its ID
|
|
||||||
* `/api/list/<ID>` - get all parsed rules for list ID (see `/api/lists`)
|
|
||||||
* `/api/list/enable/<ID>` - enable all parsed rules of given list ID (see `/api/lists`)
|
|
||||||
* `/api/list/disable/<ID>` - disable all parsed rules of given list ID (see `/api/lists`)
|
|
||||||
|
|
||||||
### Allow list example
|
### Allow list example
|
||||||
|
|
||||||
|
|
@ -53,6 +47,7 @@ git clone https://codeberg.org/postscriptum/psocks.git
|
||||||
cd psocks
|
cd psocks
|
||||||
cargo build --release --locked
|
cargo build --release --locked
|
||||||
sudo install target/release/psocks /usr/local/bin
|
sudo install target/release/psocks /usr/local/bin
|
||||||
|
sudo cp example/config.toml /etc/psocks.toml
|
||||||
sudo useradd -s /usr/sbin/nologin -Mr psocks
|
sudo useradd -s /usr/sbin/nologin -Mr psocks
|
||||||
sudo mkdir /var/log/psocks && sudo chown psocks:psocks /var/log/psocks
|
sudo mkdir /var/log/psocks && sudo chown psocks:psocks /var/log/psocks
|
||||||
```
|
```
|
||||||
|
|
@ -68,9 +63,7 @@ Wants=network-online.target
|
||||||
User=psocks
|
User=psocks
|
||||||
Group=psocks
|
Group=psocks
|
||||||
|
|
||||||
ExecStart=/usr/local/bin/psocks \
|
ExecStart=/usr/local/bin/psocks -c=/etc/psocks.toml no-auth
|
||||||
-a=http://localhost/allow.txt \
|
|
||||||
no-auth
|
|
||||||
|
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
||||||
|
|
|
||||||
13
src/config.rs
Normal file
13
src/config.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct List {
|
||||||
|
pub is_enabled: bool,
|
||||||
|
pub source: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub list: HashMap<String, List>,
|
||||||
|
}
|
||||||
11
src/example/config.toml
Normal file
11
src/example/config.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[list.google]
|
||||||
|
is_enabled = true
|
||||||
|
source = "https://codeberg.org/postscriptum/psocks-list/src/branch/main/allow/google.txt"
|
||||||
|
|
||||||
|
[list.github]
|
||||||
|
is_enabled = false
|
||||||
|
source = "https://codeberg.org/postscriptum/psocks-list/src/branch/main/allow/github.txt"
|
||||||
|
|
||||||
|
#[[list.common]]
|
||||||
|
#is_enabled = false
|
||||||
|
#source = "/path/to/common.txt"
|
||||||
6
src/example/list/github.txt
Normal file
6
src/example/list/github.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Github list example
|
||||||
|
|
||||||
|
.github.com
|
||||||
|
.github.io
|
||||||
|
.githubassets.com
|
||||||
|
.githubusercontent.com
|
||||||
13
src/example/list/google.txt
Normal file
13
src/example/list/google.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Google list example
|
||||||
|
|
||||||
|
.gmail.com
|
||||||
|
.google
|
||||||
|
.google.ch
|
||||||
|
.google.com
|
||||||
|
.google.com.ua
|
||||||
|
.google.dev
|
||||||
|
.googlevideo.com
|
||||||
|
.gstatic.com
|
||||||
|
.withgoogle.com
|
||||||
|
.youtube.com
|
||||||
|
.ytimg.com
|
||||||
119
src/main.rs
119
src/main.rs
|
|
@ -1,8 +1,10 @@
|
||||||
|
mod config;
|
||||||
mod opt;
|
mod opt;
|
||||||
mod rules;
|
mod rules;
|
||||||
mod stats;
|
mod stats;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use config::Config;
|
||||||
use fast_socks5::{
|
use fast_socks5::{
|
||||||
ReplyError, Result, Socks5Command, SocksError,
|
ReplyError, Result, Socks5Command, SocksError,
|
||||||
server::{DnsResolveHelper as _, Socks5ServerProtocol, run_tcp_proxy, run_udp_proxy},
|
server::{DnsResolveHelper as _, Socks5ServerProtocol, run_tcp_proxy, run_udp_proxy},
|
||||||
|
|
@ -14,7 +16,7 @@ use rules::Rules;
|
||||||
use stats::{Snap, Total};
|
use stats::{Snap, Total};
|
||||||
use std::{future::Future, sync::Arc, time::Instant};
|
use std::{future::Future, sync::Arc, time::Instant};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tokio::{net::TcpListener, task};
|
use tokio::{net::TcpListener, sync::RwLock, task};
|
||||||
|
|
||||||
#[rocket::get("/")]
|
#[rocket::get("/")]
|
||||||
async fn index(totals: &State<Arc<Total>>, startup_time: &State<Instant>) -> Json<Snap> {
|
async fn index(totals: &State<Arc<Total>>, startup_time: &State<Instant>) -> Json<Snap> {
|
||||||
|
|
@ -26,79 +28,20 @@ async fn api_totals(totals: &State<Arc<Total>>, startup_time: &State<Instant>) -
|
||||||
Json(totals.inner().snap(startup_time.elapsed().as_secs()))
|
Json(totals.inner().snap(startup_time.elapsed().as_secs()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::get("/api/allow/<rule>")]
|
#[rocket::get("/api/list/enable/<alias>")]
|
||||||
async fn api_allow(
|
|
||||||
rule: &str,
|
|
||||||
rules: &State<Arc<Rules>>,
|
|
||||||
totals: &State<Arc<Total>>,
|
|
||||||
) -> Result<Json<bool>, Status> {
|
|
||||||
let result = rules.allow(rule).await;
|
|
||||||
totals.set_entries(rules.total(true).await); // @TODO separate active/inactive totals
|
|
||||||
info!("Delete `{rule}` from the in-memory rules (operation status: {result:?})");
|
|
||||||
Ok(Json(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::get("/api/block/<rule>")]
|
|
||||||
async fn api_block(
|
|
||||||
rule: &str,
|
|
||||||
rules: &State<Arc<Rules>>,
|
|
||||||
totals: &State<Arc<Total>>,
|
|
||||||
) -> Result<Json<()>, Status> {
|
|
||||||
let result = rules.block(rule).await;
|
|
||||||
totals.set_entries(rules.total(true).await); // @TODO separate active/inactive totals
|
|
||||||
info!("Add `{rule}` to the in-memory rules (operation status: {result:?})");
|
|
||||||
Ok(Json(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::get("/api/rules")]
|
|
||||||
async fn api_rules(rules: &State<Arc<Rules>>) -> Result<Json<Vec<String>>, Status> {
|
|
||||||
let active = rules.active().await;
|
|
||||||
debug!("Get rules (total: {})", active.len());
|
|
||||||
Ok(Json(active))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::get("/api/lists")]
|
|
||||||
async fn api_lists(rules: &State<Arc<Rules>>) -> Result<Json<Vec<rules::ListEntry>>, Status> {
|
|
||||||
let lists = rules.lists().await;
|
|
||||||
debug!("Get lists index (total: {})", lists.len());
|
|
||||||
Ok(Json(lists))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::get("/api/list/<id>")]
|
|
||||||
async fn api_list(
|
|
||||||
id: usize,
|
|
||||||
rules: &State<Arc<Rules>>,
|
|
||||||
) -> Result<Json<Option<rules::List>>, Status> {
|
|
||||||
let list = rules.list(&id).await;
|
|
||||||
debug!(
|
|
||||||
"Get list #{id} rules (total: {:?})",
|
|
||||||
list.as_ref().map(|l| l.items.len())
|
|
||||||
);
|
|
||||||
Ok(Json(list))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::get("/api/list/enable/<id>")]
|
|
||||||
async fn api_list_enable(
|
async fn api_list_enable(
|
||||||
id: usize,
|
alias: &str,
|
||||||
rules: &State<Arc<Rules>>,
|
rules: &State<Arc<RwLock<Rules>>>,
|
||||||
totals: &State<Arc<Total>>,
|
) -> Result<Json<bool>, Status> {
|
||||||
) -> Result<Json<Option<()>>, Status> {
|
Ok(Json(rules.write().await.set_status(alias, true)))
|
||||||
let affected = rules.enable(&id, true).await;
|
|
||||||
totals.set_entries(rules.total(true).await); // @TODO separate active/inactive totals
|
|
||||||
info!("Enabled {affected:?} rules from the active rule set");
|
|
||||||
Ok(Json(affected)) // @TODO handle empty result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::get("/api/list/disable/<id>")]
|
#[rocket::get("/api/list/disable/<alias>")]
|
||||||
async fn api_list_disable(
|
async fn api_list_disable(
|
||||||
id: usize,
|
alias: &str,
|
||||||
rules: &State<Arc<Rules>>,
|
rules: &State<Arc<RwLock<Rules>>>,
|
||||||
totals: &State<Arc<Total>>,
|
) -> Result<Json<bool>, Status> {
|
||||||
) -> Result<Json<Option<()>>, Status> {
|
Ok(Json(rules.write().await.set_status(alias, false)))
|
||||||
let affected = rules.enable(&id, false).await;
|
|
||||||
totals.set_entries(rules.total(true).await); // @TODO separate active/inactive totals
|
|
||||||
info!("Disabled {affected:?} rules from the active rule set");
|
|
||||||
Ok(Json(affected)) // @TODO handle empty result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::launch]
|
#[rocket::launch]
|
||||||
|
|
@ -106,10 +49,20 @@ async fn rocket() -> _ {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let opt: &'static Opt = Box::leak(Box::new(Opt::from_args()));
|
let opt: &'static Opt = Box::leak(Box::new(Opt::from_args()));
|
||||||
|
let config: Config = toml::from_str(&std::fs::read_to_string(&opt.config).unwrap()).unwrap();
|
||||||
let rules = Arc::new(Rules::from_opt(&opt.allow_list).await.unwrap());
|
let totals = Arc::new(Total::default());
|
||||||
|
let rules = Arc::new(RwLock::new({
|
||||||
let totals = Arc::new(Total::with_rules(rules.total(true).await)); // @TODO separate active/inactive totals
|
let mut rules = Rules::new();
|
||||||
|
for (alias, rule) in config.list {
|
||||||
|
assert!(
|
||||||
|
rules
|
||||||
|
.push(&alias, &rule.source, rule.is_enabled)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rules
|
||||||
|
}));
|
||||||
|
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let socks_rules = rules.clone();
|
let socks_rules = rules.clone();
|
||||||
|
|
@ -132,23 +85,13 @@ async fn rocket() -> _ {
|
||||||
.manage(Instant::now())
|
.manage(Instant::now())
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
rocket::routes![
|
rocket::routes![index, api_totals, api_list_enable, api_list_disable],
|
||||||
index,
|
|
||||||
api_totals,
|
|
||||||
api_allow,
|
|
||||||
api_block,
|
|
||||||
api_rules,
|
|
||||||
api_lists,
|
|
||||||
api_list,
|
|
||||||
api_list_enable,
|
|
||||||
api_list_disable
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_socks_server(
|
async fn spawn_socks_server(
|
||||||
opt: &'static Opt,
|
opt: &'static Opt,
|
||||||
rules: Arc<Rules>,
|
rules: Arc<RwLock<Rules>>,
|
||||||
totals: Arc<Total>,
|
totals: Arc<Total>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if opt.allow_udp && opt.public_addr.is_none() {
|
if opt.allow_udp && opt.public_addr.is_none() {
|
||||||
|
|
@ -179,7 +122,7 @@ async fn spawn_socks_server(
|
||||||
async fn serve_socks5(
|
async fn serve_socks5(
|
||||||
opt: &Opt,
|
opt: &Opt,
|
||||||
socket: tokio::net::TcpStream,
|
socket: tokio::net::TcpStream,
|
||||||
rules: Arc<Rules>,
|
rules: Arc<RwLock<Rules>>,
|
||||||
totals: Arc<Total>,
|
totals: Arc<Total>,
|
||||||
) -> Result<(), SocksError> {
|
) -> Result<(), SocksError> {
|
||||||
totals.increase_request();
|
totals.increase_request();
|
||||||
|
|
@ -201,7 +144,7 @@ async fn serve_socks5(
|
||||||
|
|
||||||
let (host, _) = request.2.clone().into_string_and_port();
|
let (host, _) = request.2.clone().into_string_and_port();
|
||||||
|
|
||||||
if !rules.any(&host).await {
|
if !rules.read().await.any(&host) {
|
||||||
totals.increase_blocked();
|
totals.increase_blocked();
|
||||||
info!("Blocked connection attempt to: {host}");
|
info!("Blocked connection attempt to: {host}");
|
||||||
request
|
request
|
||||||
|
|
|
||||||
10
src/opt.rs
10
src/opt.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{net::SocketAddr, num::ParseFloatError, time::Duration};
|
use std::{net::SocketAddr, num::ParseFloatError, path::PathBuf, time::Duration};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
/// # How to use it:
|
/// # How to use it:
|
||||||
|
|
@ -45,11 +45,9 @@ pub struct Opt {
|
||||||
#[structopt(short = "U", long)]
|
#[structopt(short = "U", long)]
|
||||||
pub allow_udp: bool,
|
pub allow_udp: bool,
|
||||||
|
|
||||||
/// Allow list:
|
/// Path to config file
|
||||||
/// * local filename
|
#[structopt(short = "c", long)]
|
||||||
/// * remote URL
|
pub config: PathBuf,
|
||||||
#[structopt(short = "a", long)]
|
|
||||||
pub allow_list: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Choose the authentication type
|
/// Choose the authentication type
|
||||||
|
|
|
||||||
175
src/rules.rs
175
src/rules.rs
|
|
@ -1,162 +1,39 @@
|
||||||
mod item;
|
mod list;
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::Result;
|
||||||
use item::Item;
|
use list::List;
|
||||||
use log::*;
|
use std::collections::HashMap;
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use tokio::{fs, sync::RwLock};
|
|
||||||
|
|
||||||
pub struct Rules(RwLock<HashMap<usize, List>>);
|
pub struct Rules(HashMap<String, List>);
|
||||||
|
|
||||||
impl Rules {
|
impl Rules {
|
||||||
pub async fn from_opt(list: &[String]) -> Result<Self> {
|
pub fn new() -> Self {
|
||||||
let mut index = HashMap::with_capacity(list.len());
|
Self(HashMap::new())
|
||||||
assert!(
|
|
||||||
index
|
|
||||||
.insert(
|
|
||||||
0,
|
|
||||||
List {
|
|
||||||
alias: "session".into(),
|
|
||||||
items: HashSet::new(),
|
|
||||||
status: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.is_none()
|
|
||||||
);
|
|
||||||
for (i, l) in list.iter().enumerate() {
|
|
||||||
let mut items = HashSet::new();
|
|
||||||
for line in if l.contains("://") {
|
|
||||||
let response = reqwest::get(l).await?;
|
|
||||||
let status = response.status();
|
|
||||||
if status.is_success() {
|
|
||||||
response.text().await?
|
|
||||||
} else {
|
|
||||||
bail!("Could not receive remote list `{l}`: `{status}`")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fs::read_to_string(l).await?
|
|
||||||
}
|
|
||||||
.lines()
|
|
||||||
{
|
|
||||||
if line.starts_with("/") || line.starts_with("#") || line.is_empty() {
|
|
||||||
continue; // skip comments
|
|
||||||
}
|
|
||||||
if !items.insert(Item::from_line(line)) {
|
|
||||||
warn!("List `{l}` contains duplicated entry: `{line}`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(
|
|
||||||
index
|
|
||||||
.insert(
|
|
||||||
i + 1,
|
|
||||||
List {
|
|
||||||
alias: l.clone(),
|
|
||||||
items,
|
|
||||||
status: true // @TODO implement config file
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.is_none()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Ok(Self(RwLock::new(index)))
|
|
||||||
}
|
}
|
||||||
/// Check if rule is exist in the (allow) index
|
pub async fn push(&mut self, list_alias: &str, src: &str, status: bool) -> Result<bool> {
|
||||||
pub async fn any(&self, value: &str) -> bool {
|
Ok(self
|
||||||
self.0.read().await.values().any(|list| list.any(value))
|
|
||||||
}
|
|
||||||
/// Get total rules from the current session
|
|
||||||
pub async fn total(&self, status: bool) -> u64 {
|
|
||||||
self.0
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.values()
|
|
||||||
.filter(|list| list.status == status)
|
|
||||||
.map(|list| list.total())
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
/// Allow given `rule`(in the session index)
|
|
||||||
/// * return `false` if the `rule` is exist
|
|
||||||
pub async fn allow(&self, rule: &str) -> bool {
|
|
||||||
self.0
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.get_mut(&0)
|
|
||||||
.unwrap()
|
|
||||||
.items
|
|
||||||
.insert(Item::from_line(rule))
|
|
||||||
}
|
|
||||||
/// Block given `rule` (in the session index)
|
|
||||||
pub async fn block(&self, rule: &str) {
|
|
||||||
self.0
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.get_mut(&0)
|
|
||||||
.unwrap()
|
|
||||||
.items
|
|
||||||
.retain(|item| rule == item.as_str())
|
|
||||||
}
|
|
||||||
/// Return active rules
|
|
||||||
pub async fn active(&self) -> Vec<String> {
|
|
||||||
let mut rules: Vec<String> = self
|
|
||||||
.0
|
.0
|
||||||
.read()
|
.insert(list_alias.into(), List::init(src, status).await?)
|
||||||
.await
|
.is_none())
|
||||||
.values()
|
|
||||||
.filter(|list| list.status)
|
|
||||||
.flat_map(|list| list.items.iter().map(|item| item.to_string()))
|
|
||||||
.collect();
|
|
||||||
rules.sort(); // HashSet does not keep the order
|
|
||||||
rules
|
|
||||||
}
|
|
||||||
/// Return list references
|
|
||||||
pub async fn lists(&self) -> Vec<ListEntry> {
|
|
||||||
let this = self.0.read().await;
|
|
||||||
let mut list = Vec::with_capacity(this.len());
|
|
||||||
for l in this.iter() {
|
|
||||||
list.push(ListEntry {
|
|
||||||
id: *l.0,
|
|
||||||
alias: l.1.alias.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
list
|
|
||||||
}
|
|
||||||
/// Return original list references
|
|
||||||
pub async fn list(&self, id: &usize) -> Option<List> {
|
|
||||||
self.0.read().await.get(id).cloned()
|
|
||||||
}
|
}
|
||||||
/// Change rule set status by list ID
|
/// Change rule set status by list ID
|
||||||
pub async fn enable(&self, list_id: &usize, status: bool) -> Option<()> {
|
pub fn set_status(&mut self, list_alias: &str, status: bool) -> bool {
|
||||||
self.0
|
self.0
|
||||||
.write()
|
.get_mut(list_alias)
|
||||||
.await
|
.map(|list| list.set_status(status))
|
||||||
.get_mut(list_id)
|
.is_some()
|
||||||
.map(|this| this.status = status)
|
|
||||||
}
|
}
|
||||||
}
|
/// Check if rule is exist in the index
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
pub struct ListEntry {
|
|
||||||
pub id: usize,
|
|
||||||
pub alias: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, Clone)]
|
|
||||||
pub struct List {
|
|
||||||
pub alias: String,
|
|
||||||
pub items: HashSet<Item>,
|
|
||||||
pub status: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl List {
|
|
||||||
/// Check if rule is exist in the items index
|
|
||||||
pub fn any(&self, value: &str) -> bool {
|
pub fn any(&self, value: &str) -> bool {
|
||||||
self.items.iter().any(|item| match item {
|
self.0.values().any(|list| list.contains(value))
|
||||||
Item::Exact(v) => v == value,
|
|
||||||
Item::Ending(v) => value.ends_with(v),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Get total rules in list
|
|
||||||
pub fn total(&self) -> u64 {
|
|
||||||
self.items.len() as u64
|
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
/// Get total rules in session by `status`
|
||||||
|
pub fn total(&self, status: bool) -> u64 {
|
||||||
|
self.0
|
||||||
|
.values()
|
||||||
|
.filter(|list| status == list.is_enabled())
|
||||||
|
.map(|list| list.total())
|
||||||
|
.sum()
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
src/rules/list.rs
Normal file
44
src/rules/list.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
mod rule;
|
||||||
|
mod source;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::warn;
|
||||||
|
use rule::Rule;
|
||||||
|
use source::Source;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub struct List {
|
||||||
|
pub is_enabled: bool,
|
||||||
|
//pub source: Source,
|
||||||
|
pub rules: HashSet<Rule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl List {
|
||||||
|
pub async fn init(src: &str, is_enabled: bool) -> Result<Self> {
|
||||||
|
let mut rules = HashSet::new();
|
||||||
|
let source = Source::from_str(src)?;
|
||||||
|
if is_enabled {
|
||||||
|
for line in source.get().await?.lines() {
|
||||||
|
if line.starts_with("/") || line.starts_with("#") || line.is_empty() {
|
||||||
|
continue; // skip comments
|
||||||
|
}
|
||||||
|
if !rules.insert(Rule::from_line(line)) {
|
||||||
|
warn!("List `{src}` contains duplicated entry: `{line}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
rules,
|
||||||
|
//source,
|
||||||
|
is_enabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Change rule set status by list ID
|
||||||
|
pub fn set_status(&mut self, is_enabled: bool) {
|
||||||
|
self.is_enabled = is_enabled;
|
||||||
|
}
|
||||||
|
/// Check if rule is exist in the items index
|
||||||
|
pub fn contains(&self, value: &str) -> bool {
|
||||||
|
self.is_enabled && self.rules.iter().any(|rule| rule.contains(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, serde::Serialize)]
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
pub enum Item {
|
pub enum Rule {
|
||||||
Ending(String),
|
Ending(String),
|
||||||
Exact(String),
|
Exact(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Rule {
|
||||||
pub fn from_line(rule: &str) -> Self {
|
pub fn from_line(rule: &str) -> Self {
|
||||||
if let Some(item) = rule.strip_prefix(".") {
|
if let Some(item) = rule.strip_prefix(".") {
|
||||||
debug!("Init `{rule}` rule");
|
debug!("Init `{rule}` rule");
|
||||||
|
|
@ -16,14 +16,20 @@ impl Item {
|
||||||
Self::Exact(rule.to_string())
|
Self::Exact(rule.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn as_str(&self) -> &str {
|
/*pub fn as_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Item::Ending(s) | Item::Exact(s) => s,
|
Self::Ending(s) | Self::Exact(s) => s,
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
pub fn contains(&self, value: &str) -> bool {
|
||||||
|
match self {
|
||||||
|
Rule::Exact(v) => v == value,
|
||||||
|
Rule::Ending(v) => value.ends_with(v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Item {
|
impl std::fmt::Display for Rule {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Ending(s) => write!(f, ".{}", s),
|
Self::Ending(s) => write!(f, ".{}", s),
|
||||||
40
src/rules/list/source.rs
Normal file
40
src/rules/list/source.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
use anyhow::{Result, bail};
|
||||||
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub enum Source {
|
||||||
|
Path(PathBuf),
|
||||||
|
Url(Url),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Source {
|
||||||
|
pub fn from_str(source: &str) -> Result<Self> {
|
||||||
|
Ok(if source.contains("://") {
|
||||||
|
Self::Url(Url::from_str(source)?)
|
||||||
|
} else {
|
||||||
|
Self::Path(PathBuf::from_str(source)?.canonicalize()?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub async fn get(&self) -> Result<String> {
|
||||||
|
Ok(match self {
|
||||||
|
Source::Path(path) => tokio::fs::read_to_string(path).await?,
|
||||||
|
Source::Url(url) => {
|
||||||
|
let request = url.as_str();
|
||||||
|
let response = reqwest::get(request).await?;
|
||||||
|
let status = response.status();
|
||||||
|
if status.is_success() {
|
||||||
|
response.text().await?
|
||||||
|
} else {
|
||||||
|
bail!("Could not receive remote list `{request}`: `{status}`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Source {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Source::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/stats.rs
11
src/stats.rs
|
|
@ -6,29 +6,18 @@ pub use snap::Snap;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Total {
|
pub struct Total {
|
||||||
entries: AtomicU64,
|
|
||||||
blocked: AtomicU64,
|
blocked: AtomicU64,
|
||||||
request: AtomicU64,
|
request: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Total {
|
impl Total {
|
||||||
pub fn with_rules(entries: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
entries: entries.into(),
|
|
||||||
..Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn snap(&self, seconds_from_startup: u64) -> Snap {
|
pub fn snap(&self, seconds_from_startup: u64) -> Snap {
|
||||||
Snap::shot(
|
Snap::shot(
|
||||||
self.entries.load(Ordering::Relaxed),
|
|
||||||
self.request.load(Ordering::Relaxed),
|
self.request.load(Ordering::Relaxed),
|
||||||
self.blocked.load(Ordering::Relaxed),
|
self.blocked.load(Ordering::Relaxed),
|
||||||
seconds_from_startup,
|
seconds_from_startup,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pub fn set_entries(&self, value: u64) {
|
|
||||||
self.entries.store(value, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
pub fn increase_blocked(&self) {
|
pub fn increase_blocked(&self) {
|
||||||
self.blocked.fetch_add(1, Ordering::Relaxed);
|
self.blocked.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,20 +40,18 @@ pub struct Request {
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct Snap {
|
pub struct Snap {
|
||||||
rules: u64,
|
|
||||||
request: Request,
|
request: Request,
|
||||||
up: Up,
|
up: Up,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Snap {
|
impl Snap {
|
||||||
pub fn shot(rules: u64, request: u64, blocked: u64, seconds_from_startup: u64) -> Self {
|
pub fn shot(request: u64, blocked: u64, seconds_from_startup: u64) -> Self {
|
||||||
let blocked_percent = if request > 0 {
|
let blocked_percent = if request > 0 {
|
||||||
blocked as f32 * 100.0 / request as f32
|
blocked as f32 * 100.0 / request as f32
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
rules,
|
|
||||||
request: Request {
|
request: Request {
|
||||||
total: request,
|
total: request,
|
||||||
allowed: Sum {
|
allowed: Sum {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue