From 6164f7b59726b2ccaeebc040eaaf44b5e0792d9d Mon Sep 17 00:00:00 2001 From: moznion Date: Sun, 22 Nov 2020 21:15:21 +0900 Subject: [PATCH] Add server --- Cargo.lock | 258 ++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + src/attributes.rs | 2 +- src/code.rs | 2 +- src/lib.rs | 7 ++ src/packet.rs | 7 +- src/request.rs | 31 +++++ src/request_handler.rs | 7 ++ src/secret_provider.rs | 13 +++ src/server.rs | 132 +++++++++++++++++++++ 10 files changed, 457 insertions(+), 5 deletions(-) create mode 100644 src/request.rs create mode 100644 src/request_handler.rs create mode 100644 src/secret_provider.rs create mode 100644 src/server.rs diff --git a/Cargo.lock b/Cargo.lock index 2f7d369..ce14063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,12 +6,30 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bytes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16" + [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "chrono" version = "0.4.19" @@ -25,6 +43,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags", +] + [[package]] name = "derivative" version = "2.1.1" @@ -36,29 +63,115 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-core" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" + [[package]] name = "getrandom" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if 0.1.10", +] + [[package]] name = "md5" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "mio" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +dependencies = [ + "socket2", + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -78,6 +191,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_enum" version = "0.5.1" @@ -100,6 +223,38 @@ dependencies = [ "syn", ] +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -138,9 +293,12 @@ name = "radius-rs" version = "0.1.0" dependencies = [ "chrono", + "log", "md5", "num_enum", "rand", + "thiserror", + "tokio", ] [[package]] @@ -184,12 +342,57 @@ dependencies = [ "rand_core", ] +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +[[package]] +name = "signal-hook-registry" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" + +[[package]] +name = "socket2" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "syn" version = "1.0.48" @@ -201,6 +404,26 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "thiserror" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.1.44" @@ -212,6 +435,39 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfe2523e6fa84ddf5e688151d4e5fddc51678de9752c6512a24714c23818d61" +dependencies = [ + "autocfg", + "bytes", + "futures-core", + "lazy_static", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d30fdbb5dc2d8f91049691aa1a9d4d4ae422a21c334ce8936e5886d30c5c45" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.5.7" diff --git a/Cargo.toml b/Cargo.toml index 619ecfe..d2c84c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ md5 = "0.7.0" chrono = "0.4" rand = "0.7.3" num_enum = "0.5.1" +tokio = { version = "0.3.4", features = ["full"] } +log = "0.4.11" +thiserror = "1.0" diff --git a/src/attributes.rs b/src/attributes.rs index b077c07..5201c37 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -14,7 +14,7 @@ pub struct AVP { pub struct Attributes(pub(crate) Vec); impl Attributes { - pub fn parse_attributes(bs: &Vec) -> Result { + pub(crate) fn parse_attributes(bs: &Vec) -> Result { let mut i = 0; let mut attrs = Vec::new(); diff --git a/src/code.rs b/src/code.rs index 6f25881..1cb0279 100644 --- a/src/code.rs +++ b/src/code.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use num_enum::TryFromPrimitive; -#[derive(Debug, Clone, PartialEq, TryFromPrimitive)] +#[derive(Debug, Copy, Clone, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum Code { AccessRequest = 1, diff --git a/src/lib.rs b/src/lib.rs index 50fb29a..2982981 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,11 @@ +#[macro_use] +extern crate log; + pub mod code; pub mod attribute; pub mod attributes; pub mod packet; +pub mod server; +pub mod secret_provider; +pub mod request_handler; +pub mod request; diff --git a/src/packet.rs b/src/packet.rs index 5b16d78..49067e2 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,5 +1,4 @@ use std::convert::TryInto; -use std::io::Write; use rand::Rng; @@ -30,6 +29,10 @@ impl Packet { } } + pub(crate) fn get_identifier(&self) -> u8 { + self.identifier + } + pub fn parse(bs: &Vec, secret: &Vec) -> Result { if bs.len() < 20 { return Err("radius packet doesn't have enough length of bytes; that has to be at least 20 bytes".to_owned()); @@ -129,7 +132,7 @@ impl Packet { ].concat()).to_vec().eq(&response[4..20].to_vec()) } - pub fn is_authentic_request(request: Vec, secret: Vec) -> bool { + pub fn is_authentic_request(request: &Vec, secret: &Vec) -> bool { if request.len() < 20 || secret.len() == 0 { return false; } diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..b36dbf6 --- /dev/null +++ b/src/request.rs @@ -0,0 +1,31 @@ +use std::net::SocketAddr; + +use crate::packet::Packet; + +pub struct Request { + local_addr: SocketAddr, + remote_addr: SocketAddr, + packet: Packet, +} + +impl Request { + pub(crate) fn new(local_addr: SocketAddr, remote_addr: SocketAddr, packet: Packet) -> Self { + Self { + local_addr, + remote_addr, + packet, + } + } + + pub fn get_local_addr(&self) -> SocketAddr { + self.local_addr + } + + pub fn get_remote_addr(&self) -> SocketAddr { + self.remote_addr + } + + pub fn get_packet(&self) -> &Packet { + &self.packet + } +} diff --git a/src/request_handler.rs b/src/request_handler.rs new file mode 100644 index 0000000..bf1aab3 --- /dev/null +++ b/src/request_handler.rs @@ -0,0 +1,7 @@ +use tokio::net::UdpSocket; + +use crate::request::Request; + +pub trait RequestHandler: Sync { + fn handle_radius_request(&self, conn: &UdpSocket, request: &Request); +} diff --git a/src/secret_provider.rs b/src/secret_provider.rs new file mode 100644 index 0000000..8ff87c8 --- /dev/null +++ b/src/secret_provider.rs @@ -0,0 +1,13 @@ +use std::net::SocketAddr; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SecretProviderError { + #[error("failed to fetch a secret value => `{0}`")] + FailedFetching(String) +} + +pub trait SecretProvider: Sync { + fn fetch_secret(&self, remote_addr: SocketAddr) -> Result, SecretProviderError>; +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..6ea10df --- /dev/null +++ b/src/server.rs @@ -0,0 +1,132 @@ +use std::borrow::Borrow; +use std::collections::HashSet; +use std::io; +use std::net::SocketAddr; +use std::sync::{Arc, RwLock}; + +use tokio::net::UdpSocket; + +use crate::packet::Packet; +use crate::request::Request; +use crate::request_handler::RequestHandler; +use crate::secret_provider::SecretProvider; + +pub struct Server { + address: String, + buf_size: u8, + skip_authenticity_validation: bool, + request_handler: &'static T, + secret_provider: &'static U, +} + +impl Server { + pub fn new(host: &str, port: u16, buf_size: u8, skip_authenticity_validation: bool, request_handler: &'static T, secret_provider: &'static U) -> Self { + Self { + address: format!("{}:{}", host, port), + buf_size, + skip_authenticity_validation, + request_handler, + secret_provider, + } + } + + pub async fn run(&'static self) -> Result<(), io::Error> { + let mut buf = vec![0, self.buf_size]; + + let conn_arc = Arc::new(UdpSocket::bind(&self.address).await?); + let undergoing_requests_lock_arc = Arc::new(RwLock::new(HashSet::new())); + + loop { + let conn = conn_arc.clone(); + + let (size, remote_addr) = conn.recv_from(&mut buf).await?; + let request_data = buf[..size].to_vec(); + + let local_addr = match conn.local_addr() { + Ok(addr) => addr, + Err(e) => { + error!("failed to get a local address from from a connection; {}", e); + continue; + } + }; + + let undergoing_requests_lock = undergoing_requests_lock_arc.clone(); + + tokio::spawn(async move { + Self::process_request( + conn, + &request_data, + local_addr, + remote_addr, + undergoing_requests_lock, + self.request_handler, + self.secret_provider, + self.skip_authenticity_validation, + ).await; + }); + } + } + + async fn process_request( + conn: Arc, + request_data: &Vec, + local_addr: SocketAddr, + remote_addr: SocketAddr, + undergoing_requests_lock: Arc>>, + request_handler: &T, + secret_provider: &U, + skip_authenticity_validation: bool, + ) { + let secret: Vec = match secret_provider.fetch_secret(remote_addr) { + Ok(secret) => secret, + Err(e) => { + error!("failed to fetch secret binary vector from the secret provider; {}", e); + return; + } + }; + if secret.len() <= 0 { + error!("empty secret returned from secret source; empty secret is prohibited"); + return; + } + + if !skip_authenticity_validation && !Packet::is_authentic_request(request_data, &secret) { + info!("packet validation failed; bad secret"); + return; + } + + let packet = match Packet::parse(request_data, &secret) { + Ok(packet) => packet, + Err(e) => { + error!("failed to parse given request data to pack into the RADIUS packet; {}", e); + debug!("failed request data => {:?}", request_data); + return; + } + }; + + let key = RequestKey { + ip: remote_addr.to_string(), + identifier: packet.get_identifier(), + }; + let key_for_remove = key.clone(); + + { + let mut undergoing_requests = undergoing_requests_lock.write().unwrap(); + if undergoing_requests.contains(&key) { + return; + } + undergoing_requests.insert(key); + } + + request_handler.handle_radius_request(conn.borrow(), &Request::new(local_addr, remote_addr, packet)); + + let mut undergoing_requests = undergoing_requests_lock.write().unwrap(); + undergoing_requests.remove(&key_for_remove); + } +} + +#[derive(PartialEq, Eq, Hash, Clone)] +struct RequestKey { + ip: String, + identifier: u8, +} +