mirror of
https://github.com/YCCDSZXH/proxy-checker-rs.git
synced 2024-11-15 19:22:25 +08:00
v0.1.1
This commit is contained in:
parent
f5c4c335fc
commit
b781e95ed6
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/server.key
|
||||
/server.crt
|
1202
Cargo.lock
generated
Normal file
1202
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "proxy_checker"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
rustls = { version = "0.23.12"}
|
||||
tracing = "0.1.40"
|
||||
libc = "0.2.155"
|
||||
anyhow = "1.0.86"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tokio-rustls = "0.26.0"
|
||||
rustls-pemfile = "2.1.3"
|
||||
http = "1.1.0"
|
||||
h2 = "0.4.5"
|
||||
bytes = "1.7.1"
|
||||
serde = "1.0.208"
|
||||
serde_json = "1.0.125"
|
||||
clap = { version = "4.5.15", features = ["derive"] }
|
16
src/args.rs
Normal file
16
src/args.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[arg(short, long, default_value_t = String::from("0.0.0.0:8443"))]
|
||||
pub addr: String,
|
||||
#[arg(long, default_value_t = String::from("./server.crt"))]
|
||||
pub cert: String,
|
||||
#[arg(long, default_value_t = String::from("./server.key"))]
|
||||
pub key: String,
|
||||
#[arg(short, long, default_value_t = String::from("info"))]
|
||||
pub log_level: String,
|
||||
#[arg(short, long, default_value_t = 10)]
|
||||
pub duration: i32,
|
||||
}
|
102
src/http2.rs
Normal file
102
src/http2.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use crate::tcp::get_tcp_rtt;
|
||||
use crate::ARGS;
|
||||
use anyhow::Result;
|
||||
use bytes::Bytes;
|
||||
use h2::server::SendResponse;
|
||||
use h2::{Ping, PingPong, RecvStream};
|
||||
use http::header::{ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE};
|
||||
use http::Request;
|
||||
use serde_json::json;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
#[tracing::instrument(fields(uri = %request.uri().path(), ip = %addr.ip()), skip_all)]
|
||||
pub async fn handle(
|
||||
request: &mut Request<RecvStream>,
|
||||
respond: &mut SendResponse<Bytes>,
|
||||
raw_fd: i32,
|
||||
addr: SocketAddr,
|
||||
ping_pong: Arc<Mutex<PingPong>>,
|
||||
) -> Result<()> {
|
||||
if request.uri().path() != "/" {
|
||||
error!(?request, "not found");
|
||||
let response = http::Response::builder()
|
||||
.status(http::StatusCode::NOT_FOUND)
|
||||
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
||||
.body(())?;
|
||||
let _ = respond.send_response(response, true)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let header_frame = http::Response::builder()
|
||||
.status(http::StatusCode::OK)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
||||
.body(())?;
|
||||
let mut send = respond.send_response(header_frame, false)?;
|
||||
|
||||
let tls_rtts = tls_rtt(ping_pong).await?;
|
||||
let (tls_rtt, _std_dev, anomalies) = detect_anomalies(&tls_rtts, 0.5);
|
||||
let tcp_rtt = get_tcp_rtt(raw_fd);
|
||||
|
||||
// TODO: type convert
|
||||
let is_proxy = tls_rtt - tcp_rtt as f64 > ARGS.duration as f64 * 1000.0;
|
||||
|
||||
info!(?is_proxy, ?tcp_rtt, ?tls_rtt, ?anomalies, "result");
|
||||
|
||||
Ok(send.send_data(
|
||||
json!({
|
||||
"is_proxy": is_proxy,
|
||||
"ip": addr.ip().to_string(),
|
||||
})
|
||||
.to_string()
|
||||
.into(),
|
||||
true,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn tls_rtt(ping_pong: Arc<Mutex<PingPong>>) -> Result<Vec<i32>> {
|
||||
let mut ping = ping_pong.lock().await;
|
||||
let mut tls_rtts = vec![];
|
||||
for index in 0..10 {
|
||||
let instant = tokio::time::Instant::now();
|
||||
ping.ping(Ping::opaque()).await?;
|
||||
let duration = instant.elapsed().as_micros();
|
||||
debug!(?duration, %index, "ping");
|
||||
tls_rtts.push(duration as i32);
|
||||
}
|
||||
Ok(tls_rtts)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
fn detect_anomalies(data: &[i32], threshold: f64) -> (f64, f64, Vec<i32>) {
|
||||
let sum: i32 = data.iter().sum();
|
||||
let mean = sum as f64 / data.len() as f64;
|
||||
|
||||
let variance = calculate_variance(data, mean);
|
||||
let std_dev = variance.sqrt();
|
||||
|
||||
let anomalies: Vec<i32> = data
|
||||
.iter()
|
||||
.filter(|&&x| (x as f64 - mean).abs() > threshold * std_dev)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let non_anomalies_count = data.len() - anomalies.len();
|
||||
let adjusted_mean = if non_anomalies_count > 0 {
|
||||
(sum - anomalies.iter().sum::<i32>()) as f64 / non_anomalies_count as f64
|
||||
} else {
|
||||
mean
|
||||
};
|
||||
|
||||
info!(?data, ?adjusted_mean, ?std_dev, ?anomalies);
|
||||
|
||||
(adjusted_mean, std_dev, anomalies)
|
||||
}
|
||||
|
||||
fn calculate_variance(data: &[i32], mean: f64) -> f64 {
|
||||
data.iter().map(|&x| (x as f64 - mean).powi(2)).sum::<f64>() / data.len() as f64
|
||||
}
|
94
src/main.rs
Normal file
94
src/main.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
||||
use rustls::ServerConfig;
|
||||
use std::io::BufReader;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use tokio::net::TcpListener;
|
||||
use tracing::{debug, info, trace};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
mod args;
|
||||
mod http2;
|
||||
mod tcp;
|
||||
mod tls;
|
||||
|
||||
pub static ARGS: LazyLock<args::Args> = LazyLock::new(args::Args::parse);
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::Registry::default()
|
||||
.with(
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(ARGS.log_level.parse().unwrap())
|
||||
.from_env_lossy(),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer().pretty())
|
||||
.init();
|
||||
|
||||
let listener = TcpListener::bind(&ARGS.addr).await?;
|
||||
info!("listening on {}", ARGS.addr);
|
||||
debug!("listening on {}", ARGS.addr);
|
||||
trace!("listening on {}", ARGS.addr);
|
||||
println!("listening on {}", ARGS.addr);
|
||||
|
||||
let server_config = load_tls().expect(
|
||||
r#"
|
||||
load tls certificate fail,
|
||||
run:
|
||||
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout server.key -out server.crt
|
||||
|
||||
generate one
|
||||
"#,
|
||||
);
|
||||
let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config));
|
||||
|
||||
loop {
|
||||
if let Ok((socket, addr)) = listener.accept().await {
|
||||
info!(name = "Accept TCP connection from", addr = %addr.ip());
|
||||
|
||||
let tls_acceptor = tls_acceptor.clone();
|
||||
tokio::spawn(tcp::handle(socket, tls_acceptor, addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_tls() -> Result<ServerConfig> {
|
||||
info!("load key and cert");
|
||||
let cert = load_certs(&ARGS.cert)?;
|
||||
let key = load_private_key(&ARGS.key)?;
|
||||
let mut config = rustls::ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(cert, key)?;
|
||||
config.alpn_protocols = vec![b"h2".to_vec()];
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn load_certs(filename: &str) -> Result<Vec<CertificateDer<'static>>> {
|
||||
let certfile = std::fs::File::open(filename)?;
|
||||
let mut reader = BufReader::new(certfile);
|
||||
Ok(rustls_pemfile::certs(&mut reader)
|
||||
.map(|result| result.expect("parse cert error"))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn load_private_key(filename: &str) -> Result<PrivateKeyDer<'static>> {
|
||||
let keyfile = std::fs::File::open(filename).expect("cannot open private key file");
|
||||
let mut reader = BufReader::new(keyfile);
|
||||
|
||||
Ok(
|
||||
match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
|
||||
Some(rustls_pemfile::Item::Pkcs1Key(key)) => key.into(),
|
||||
Some(rustls_pemfile::Item::Pkcs8Key(key)) => key.into(),
|
||||
Some(rustls_pemfile::Item::Sec1Key(key)) => key.into(),
|
||||
_ => {
|
||||
panic!(
|
||||
"no keys found in {:?} (encrypted keys not supported)",
|
||||
filename
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
119
src/tcp.rs
Normal file
119
src/tcp.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use libc::{self, __u16, __u32, __u64, __u8};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
use tracing::{error, info};
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn handle(conn: TcpStream, acceptor: TlsAcceptor, addr: SocketAddr) {
|
||||
let socket = match acceptor.accept(conn).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to perform TLS handshake");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
info!(name = "Accept TLS connection", addr = %addr.ip());
|
||||
|
||||
if let Err(err) = super::tls::handle(socket, addr).await {
|
||||
error!(error = ?err, "error while handle tls connection");
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct TcpInfo {
|
||||
pub tcpi_state: __u8,
|
||||
pub tcpi_ca_state: __u8,
|
||||
pub tcpi_retransmits: __u8,
|
||||
pub tcpi_probes: __u8,
|
||||
pub tcpi_backoff: __u8,
|
||||
pub tcpi_options: __u8,
|
||||
pub tcpi_snd_wscale_rcv_wscale: __u8,
|
||||
pub tcpi_delivery_rate_app_limited_fastopen_client_fail: __u8,
|
||||
|
||||
pub tcpi_rto: __u32,
|
||||
pub tcpi_ato: __u32,
|
||||
pub tcpi_snd_mss: __u32,
|
||||
pub tcpi_rcv_mss: __u32,
|
||||
|
||||
pub tcpi_unacked: __u32,
|
||||
pub tcpi_sacked: __u32,
|
||||
pub tcpi_lost: __u32,
|
||||
pub tcpi_retrans: __u32,
|
||||
pub tcpi_fackets: __u32,
|
||||
|
||||
pub tcpi_last_data_sent: __u32,
|
||||
pub tcpi_last_ack_sent: __u32,
|
||||
pub tcpi_last_data_recv: __u32,
|
||||
pub tcpi_last_ack_recv: __u32,
|
||||
|
||||
pub tcpi_pmtu: __u32,
|
||||
pub tcpi_rcv_ssthresh: __u32,
|
||||
pub tcpi_rtt: __u32,
|
||||
pub tcpi_rttvar: __u32,
|
||||
pub tcpi_snd_ssthresh: __u32,
|
||||
pub tcpi_snd_cwnd: __u32,
|
||||
pub tcpi_advmss: __u32,
|
||||
pub tcpi_reordering: __u32,
|
||||
|
||||
pub tcpi_rcv_rtt: __u32,
|
||||
pub tcpi_rcv_space: __u32,
|
||||
|
||||
pub tcpi_total_retrans: __u32,
|
||||
|
||||
pub tcpi_pacing_rate: __u64,
|
||||
pub tcpi_max_pacing_rate: __u64,
|
||||
pub tcpi_bytes_acked: __u64,
|
||||
pub tcpi_bytes_received: __u64,
|
||||
pub tcpi_segs_out: __u32,
|
||||
pub tcpi_segs_in: __u32,
|
||||
|
||||
pub tcpi_notsent_bytes: __u32,
|
||||
pub tcpi_min_rtt: __u32,
|
||||
pub tcpi_data_segs_in: __u32,
|
||||
pub tcpi_data_segs_out: __u32,
|
||||
|
||||
pub tcpi_delivery_rate: __u64,
|
||||
|
||||
pub tcpi_busy_time: __u64,
|
||||
pub tcpi_rwnd_limited: __u64,
|
||||
pub tcpi_sndbuf_limited: __u64,
|
||||
|
||||
pub tcpi_delivered: __u32,
|
||||
pub tcpi_delivered_ce: __u32,
|
||||
|
||||
pub tcpi_bytes_sent: __u64,
|
||||
pub tcpi_bytes_retrans: __u64,
|
||||
pub tcpi_dsack_dups: __u32,
|
||||
pub tcpi_reord_seen: __u32,
|
||||
|
||||
pub tcpi_rcv_ooopack: __u32,
|
||||
|
||||
pub tcpi_snd_wnd: __u32,
|
||||
pub tcpi_rcv_wnd: __u32,
|
||||
|
||||
pub tcpi_rehash: __u32,
|
||||
|
||||
pub tcpi_total_rto: __u16,
|
||||
pub tcpi_total_rto_recoveries: __u16,
|
||||
pub tcpi_total_rto_time: __u32,
|
||||
}
|
||||
|
||||
pub fn get_tcp_rtt(raw_fd: i32) -> u32 {
|
||||
let mut tcp_info = TcpInfo::default();
|
||||
let mut tcp_info_len = std::mem::size_of::<TcpInfo>() as u32;
|
||||
|
||||
let _ret = unsafe {
|
||||
libc::getsockopt(
|
||||
raw_fd,
|
||||
libc::IPPROTO_TCP,
|
||||
libc::TCP_INFO,
|
||||
&mut tcp_info as *mut _ as *mut libc::c_void,
|
||||
&mut tcp_info_len,
|
||||
)
|
||||
};
|
||||
|
||||
tcp_info.tcpi_rtt
|
||||
}
|
37
src/tls.rs
Normal file
37
src/tls.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use anyhow::{Context, Result};
|
||||
use std::net::SocketAddr;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_rustls::server::TlsStream;
|
||||
use tracing::{error, info, Instrument};
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn handle(conn: TlsStream<TcpStream>, addr: SocketAddr) -> Result<()> {
|
||||
let raw_fd = conn.get_ref().0.as_raw_fd();
|
||||
let mut connection = h2::server::handshake(conn).await?;
|
||||
info!("H2 connection bound");
|
||||
let ping_pong = Arc::new(Mutex::new(
|
||||
connection.ping_pong().context("ping pong error")?,
|
||||
));
|
||||
|
||||
while let Some(result) = connection.accept().await {
|
||||
let (mut request, mut respond) = result?;
|
||||
let ping_pong = ping_pong.clone();
|
||||
// TODO: spawn a ping task
|
||||
tokio::spawn(
|
||||
async move {
|
||||
if let Err(e) =
|
||||
super::http2::handle(&mut request, &mut respond, raw_fd, addr, ping_pong).await
|
||||
{
|
||||
error!("error while handling request: {}", e);
|
||||
}
|
||||
}
|
||||
.in_current_span(),
|
||||
);
|
||||
}
|
||||
|
||||
connection.graceful_shutdown();
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user