diff --git a/Cargo.toml b/Cargo.toml index b994a6b..a848e05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ members = [ # Internal "code-generator", "examples", + "e2e-test" ] diff --git a/e2e-test/Cargo.toml b/e2e-test/Cargo.toml new file mode 100644 index 0000000..9a4b941 --- /dev/null +++ b/e2e-test/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "e2e-test" +version = "0.1.0" +edition = "2018" +license-file = "../LICENSE" +publish = false + +[dependencies] +radius = { version = "0.1.0", path = "../radius" } +radius-client = { version = "0.1.0", path = "../radius-client" } +radius-server = { version = "0.1.0", path = "../radius-server" } +tokio = { version = "0.3.4", features = ["signal", "net"] } +async-trait = "0.1.42" diff --git a/e2e-test/src/lib.rs b/e2e-test/src/lib.rs new file mode 100644 index 0000000..5857229 --- /dev/null +++ b/e2e-test/src/lib.rs @@ -0,0 +1 @@ +mod test; diff --git a/e2e-test/src/test.rs b/e2e-test/src/test.rs new file mode 100644 index 0000000..fdef84d --- /dev/null +++ b/e2e-test/src/test.rs @@ -0,0 +1,166 @@ +use std::io; +use std::net::SocketAddr; +use std::time::Duration; + +use async_trait::async_trait; +use tokio::net::UdpSocket; +use tokio::time::sleep; + +use radius::code::Code; +use radius::request::Request; +use radius::rfc2865; +use radius_server::request_handler::RequestHandler; +use radius_server::secret_provider::{SecretProvider, SecretProviderError}; + +struct MyRequestHandler {} + +#[async_trait] +impl RequestHandler<(), io::Error> for MyRequestHandler { + async fn handle_radius_request( + &self, + conn: &UdpSocket, + req: &Request, + ) -> Result<(), io::Error> { + let req_packet = req.get_packet(); + let maybe_user_name_attr = rfc2865::lookup_user_name(req_packet); + let maybe_user_password_attr = rfc2865::lookup_user_password(req_packet); + + let user_name = maybe_user_name_attr.unwrap().unwrap(); + let user_password = String::from_utf8(maybe_user_password_attr.unwrap().unwrap()).unwrap(); + let code = if user_name == "admin" && user_password == "p@ssw0rd" { + Code::AccessAccept + } else { + Code::AccessReject + }; + + let mut resp_packet = req_packet.make_response_packet(code); + rfc2865::add_user_name(&mut resp_packet, user_name.as_str()); + conn.send_to(&resp_packet.encode().unwrap(), req.get_remote_addr()) + .await?; + Ok(()) + } +} + +struct LongTimeTakingHandler {} + +#[async_trait] +impl RequestHandler<(), io::Error> for LongTimeTakingHandler { + async fn handle_radius_request( + &self, + conn: &UdpSocket, + req: &Request, + ) -> Result<(), io::Error> { + sleep(Duration::from_secs(30)).await; + let req_packet = req.get_packet(); + let resp_packet = req_packet.make_response_packet(Code::AccessReject); + conn.send_to(&resp_packet.encode().unwrap(), req.get_remote_addr()) + .await?; + Ok(()) + } +} + +struct MySecretProvider {} + +impl SecretProvider for MySecretProvider { + fn fetch_secret(&self, _remote_addr: SocketAddr) -> Result, SecretProviderError> { + let bs = b"secret".to_vec(); + Ok(bs) + } +} + +#[cfg(test)] +mod tests { + use std::net::SocketAddr; + use std::time::Duration; + + use tokio::sync::oneshot; + + use radius::code::Code; + use radius::packet::Packet; + use radius::rfc2865; + use radius_client::client::{Client, ClientError}; + use radius_server::server::Server; + + use crate::test::{LongTimeTakingHandler, MyRequestHandler, MySecretProvider}; + + #[tokio::test] + async fn test_runner() { + test_access_request().await; + test_socket_timeout().await; + } + + async fn test_access_request() { + let (sender, receiver) = oneshot::channel::<()>(); + + let port = 1812; + + let server_proc = tokio::spawn(async move { + Server::run( + "0.0.0.0", + port, + 1500, + true, + MyRequestHandler {}, + MySecretProvider {}, + receiver, + ) + .await + .unwrap(); + }); + + let remote_addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); + let client = Client::new(None, None); + + let mut req_packet = Packet::new(Code::AccessRequest, &b"secret".to_vec()); + rfc2865::add_user_name(&mut req_packet, "admin"); + rfc2865::add_user_password(&mut req_packet, b"p@ssw0rd").unwrap(); + let res = client.send_packet(&remote_addr, &req_packet).await.unwrap(); + let maybe_user_name = rfc2865::lookup_user_name(&res); + let maybe_user_pass = rfc2865::lookup_user_password(&res); + assert_eq!(res.get_code(), Code::AccessAccept); + assert_eq!(maybe_user_name.is_some(), true); + assert_eq!(maybe_user_name.unwrap().unwrap(), "admin"); + assert_eq!(maybe_user_pass.is_none(), true); + + let mut req_packet = Packet::new(Code::AccessRequest, &b"secret".to_vec()); + rfc2865::add_user_name(&mut req_packet, "admin"); + rfc2865::add_user_password(&mut req_packet, b"INVALID-PASS").unwrap(); + let res = client.send_packet(&remote_addr, &req_packet).await.unwrap(); + assert_eq!(res.get_code(), Code::AccessReject); + + sender.send(()).unwrap(); + server_proc.await.unwrap(); + } + + async fn test_socket_timeout() { + let (sender, receiver) = oneshot::channel::<()>(); + + let port = 1812; + + let server_proc = tokio::spawn(async move { + Server::run( + "0.0.0.0", + port, + 1500, + true, + LongTimeTakingHandler {}, + MySecretProvider {}, + receiver, + ) + .await + .unwrap(); + }); + + let remote_addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); + let client = Client::new(None, Some(Duration::from_secs(0))); + + let mut req_packet = Packet::new(Code::AccessRequest, &b"secret".to_vec()); + rfc2865::add_user_name(&mut req_packet, "admin"); + rfc2865::add_user_password(&mut req_packet, b"p@ssw0rd").unwrap(); + let res = client.send_packet(&remote_addr, &req_packet).await; + assert_eq!(res.unwrap_err(), ClientError::SocketTimeoutError()); + + sender.send(()).unwrap(); + server_proc.await.unwrap(); + } +} diff --git a/radius-client/src/client.rs b/radius-client/src/client.rs index 12d4ed1..995d32a 100644 --- a/radius-client/src/client.rs +++ b/radius-client/src/client.rs @@ -12,7 +12,7 @@ use crate::client::ClientError::{ FailedReceivingResponse, FailedSendingPacket, FailedUdpSocketBinding, }; -#[derive(Error, Debug)] +#[derive(Error, PartialEq, Debug)] pub enum ClientError { #[error("failed to bind a UDP socket => `{0}`")] FailedUdpSocketBinding(String),