diff --git a/README.md b/README.md index 133b82a..e0d53f7 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,17 @@ A async/await native implementation of the RADIUS server and client for Rust. ## Description -**THIS PROJECT IS UNDER DEVELOPMENT STATUS. IT WOULD CHANGE WITHOUT NOTICES.** - This RADIUS server and client implementation use [tokio](https://tokio.rs/) to support asynchronous operations natively. This implementation satisfies basic functions that are described in [RFC2865](https://tools.ietf.org/html/rfc2865). +## Usage + +Simple example implementations are here: + +- [server](./examples/server.rs) +- [client](./examples/client.rs) + +## Supported Dictionaries + This supports the following RFC dictionaries at the moment: - [RFC2865](https://tools.ietf.org/html/rfc2865) @@ -35,13 +42,6 @@ This supports the following RFC dictionaries at the moment: - [RFC7055](https://tools.ietf.org/html/rfc7055) - [RFC7155](https://tools.ietf.org/html/rfc7155) -## Usage - -Simple example implementations are here: - -- [server](./examples/server.rs) -- [client](./examples/client.rs) - ## Roadmap - retransmission feature on the client @@ -59,9 +59,8 @@ Simple example implementations are here: ## Note -Original implementation and design of this are based on [layeh/radius](https://github.com/layeh/radius). +The original implementation and design of this are inspired by [layeh/radius](https://github.com/layeh/radius). ## Author moznion () - diff --git a/radius-client/src/client.rs b/radius-client/src/client.rs index 050025b..12d4ed1 100644 --- a/radius-client/src/client.rs +++ b/radius-client/src/client.rs @@ -32,6 +32,7 @@ pub enum ClientError { SocketTimeoutError(), } +/// A basic implementation of the RADIUS client. pub struct Client { connection_timeout: Option, socket_timeout: Option, @@ -40,6 +41,14 @@ pub struct Client { impl Client { const MAX_DATAGRAM_SIZE: usize = 65507; + /// A constructor for a client. + /// + /// # Arguments + /// + /// * `connection_timeout` - A duration of connection timeout. If the connection is not established in time, the `ConnectionTimeoutError` occurs. + /// If this value is `None`, it never timed-out. + /// * `socket_timeout` - A duration of socket timeout. If the response is not returned in time, the `SocketTimeoutError` occurs. + /// If this value is `None`, it never timed-out. pub fn new(connection_timeout: Option, socket_timeout: Option) -> Self { Client { connection_timeout, @@ -47,13 +56,14 @@ impl Client { } } + /// This method sends a packet to the destination. + /// + /// This method doesn't support auto retransmission when something failed, so if you need such a feature you have to implement that. pub async fn send_packet( &self, remote_addr: &SocketAddr, request_packet: &Packet, ) -> Result { - // TODO retransmission - let local_addr: SocketAddr = if remote_addr.is_ipv4() { "0.0.0.0:0" } else { diff --git a/radius-server/src/request_handler.rs b/radius-server/src/request_handler.rs index 86434a6..1e1ddf2 100644 --- a/radius-server/src/request_handler.rs +++ b/radius-server/src/request_handler.rs @@ -3,7 +3,15 @@ use tokio::net::UdpSocket; use radius::request::Request; +/// RequestHandler is a handler for the received RADIUS request. #[async_trait] pub trait RequestHandler: 'static + Sync + Send { + /// This method has to implement the core feature of the server application what you need. + /// + /// # Arguments + /// + /// * conn - This connection is associated with the remote requester. In the most situations, + /// you have to send a response through this connection object. + /// * request - This is a request object that comes from the remote requester. async fn handle_radius_request(&self, conn: &UdpSocket, request: &Request) -> Result; } diff --git a/radius-server/src/secret_provider.rs b/radius-server/src/secret_provider.rs index a769f65..c292b6c 100644 --- a/radius-server/src/secret_provider.rs +++ b/radius-server/src/secret_provider.rs @@ -8,6 +8,9 @@ pub enum SecretProviderError { FailedFetching(String), } +/// SecretProvider is a provider for secret value. pub trait SecretProvider: 'static + Sync + Send { + /// This method has to implement the generator of the secret value to verify the request of + /// `Accounting-Response`, `Accounting-Response` and `CoA-Request`. fn fetch_secret(&self, remote_addr: SocketAddr) -> Result, SecretProviderError>; } diff --git a/radius-server/src/server.rs b/radius-server/src/server.rs index 1743e5f..f042999 100644 --- a/radius-server/src/server.rs +++ b/radius-server/src/server.rs @@ -13,9 +13,11 @@ use radius::packet::Packet; use radius::request::Request; use std::fmt::Debug; +/// A basic implementation of the RADIUS server. pub struct Server {} impl Server { + /// Start listening a UDP socket to process the RAIDUS requests. pub async fn run, U: SecretProvider>( host: &str, port: u16, diff --git a/radius/src/avp.rs b/radius/src/avp.rs index 8367f25..67db50e 100644 --- a/radius/src/avp.rs +++ b/radius/src/avp.rs @@ -34,6 +34,7 @@ pub type AVPType = u8; pub const TYPE_INVALID: AVPType = 255; +/// This struct represents a attribute-value pair. #[derive(Debug, Clone, PartialEq)] pub struct AVP { pub(crate) typ: AVPType, @@ -41,6 +42,7 @@ pub struct AVP { } impl AVP { + /// (This method is for dictionary developers) make an AVP from a u32 value. pub fn from_u32(typ: AVPType, value: u32) -> Self { AVP { typ, @@ -48,6 +50,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a u16 value. pub fn from_u16(typ: AVPType, value: u16) -> Self { AVP { typ, @@ -55,6 +58,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a tagged u32 value. pub fn from_tagged_u32(typ: AVPType, tag: Option<&Tag>, value: u32) -> Self { let tag = match tag { None => &Tag { @@ -69,6 +73,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a string value. pub fn from_string(typ: AVPType, value: &str) -> Self { AVP { typ, @@ -76,6 +81,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a tagged string value. pub fn from_tagged_string(typ: AVPType, tag: Option<&Tag>, value: &str) -> Self { match tag { None => AVP { @@ -89,6 +95,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from bytes. pub fn from_bytes(typ: AVPType, value: &[u8]) -> Self { AVP { typ, @@ -96,6 +103,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a IPv4 value. pub fn from_ipv4(typ: AVPType, value: &Ipv4Addr) -> Self { AVP { typ, @@ -103,6 +111,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a IPv4-prefix value. pub fn from_ipv4_prefix(typ: AVPType, prefix: &[u8]) -> Result { let prefix_len = prefix.len(); if prefix_len != 4 { @@ -115,6 +124,7 @@ impl AVP { }) } + /// (This method is for dictionary developers) make an AVP from a IPv6 value. pub fn from_ipv6(typ: AVPType, value: &Ipv6Addr) -> Self { AVP { typ, @@ -122,6 +132,7 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a IPv6-prefix value. pub fn from_ipv6_prefix(typ: AVPType, prefix: &[u8]) -> Result { let prefix_len = prefix.len(); if prefix_len > 16 { @@ -134,6 +145,8 @@ impl AVP { }) } + /// (This method is for dictionary developers) make an AVP from a user-password value. + /// see also: https://tools.ietf.org/html/rfc2865#section-5.2 pub fn from_user_password( typ: AVPType, plain_text: &[u8], @@ -197,6 +210,7 @@ impl AVP { Ok(AVP { typ, value: enc }) } + /// (This method is for dictionary developers) make an AVP from a date value. pub fn from_date(typ: AVPType, dt: &DateTime) -> Self { AVP { typ, @@ -204,6 +218,8 @@ impl AVP { } } + /// (This method is for dictionary developers) make an AVP from a tunne-password value. + /// see also: https://tools.ietf.org/html/rfc2868#section-3.5 pub fn from_tunnel_password( typ: AVPType, tag: Option<&Tag>, @@ -293,6 +309,7 @@ impl AVP { Ok(AVP { typ, value: enc }) } + /// (This method is for dictionary developers) encode an AVP into a u32 value. pub fn encode_u32(&self) -> Result { const U32_SIZE: usize = std::mem::size_of::(); if self.value.len() != U32_SIZE { @@ -306,6 +323,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into a u16 value. pub fn encode_u16(&self) -> Result { const U16_SIZE: usize = std::mem::size_of::(); if self.value.len() != U16_SIZE { @@ -319,6 +337,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into a tag and u32 value. pub fn encode_tagged_u32(&self) -> Result<(u32, Tag), AVPError> { if self.value.is_empty() { return Err(AVPError::InvalidAttributeLengthError(self.value.len())); @@ -346,6 +365,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into a string value. pub fn encode_string(&self) -> Result { match String::from_utf8(self.value.to_vec()) { Ok(str) => Ok(str), @@ -353,6 +373,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into a tag and string value. pub fn encode_tagged_string(&self) -> Result<(String, Option), AVPError> { let string_vec = self.value.to_vec(); if string_vec.is_empty() { @@ -388,10 +409,12 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into bytes. pub fn encode_bytes(&self) -> Vec { self.value.to_vec() } + /// (This method is for dictionary developers) encode an AVP into Ipv4 value. pub fn encode_ipv4(&self) -> Result { const IPV4_SIZE: usize = std::mem::size_of::(); if self.value.len() != IPV4_SIZE { @@ -405,6 +428,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into Ipv4-prefix value. pub fn encode_ipv4_prefix(&self) -> Result, AVPError> { match self.value.len() == 6 { true => Ok(self.value[2..].to_owned()), @@ -412,6 +436,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into Ipv6 value. pub fn encode_ipv6(&self) -> Result { const IPV6_SIZE: usize = std::mem::size_of::(); if self.value.len() != IPV6_SIZE { @@ -425,6 +450,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into Ipv6-prefix value. pub fn encode_ipv6_prefix(&self) -> Result, AVPError> { match self.value.len() >= 2 { true => Ok(self.value[2..].to_owned()), @@ -432,6 +458,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into user-password value as bytes. pub fn encode_user_password( &self, secret: &[u8], @@ -475,6 +502,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into date value. pub fn encode_date(&self) -> Result, AVPError> { const U32_SIZE: usize = std::mem::size_of::(); if self.value.len() != U32_SIZE { @@ -491,6 +519,7 @@ impl AVP { } } + /// (This method is for dictionary developers) encode an AVP into a tunnel-password value as bytes. pub fn encode_tunnel_password( &self, secret: &[u8], diff --git a/radius/src/packet.rs b/radius/src/packet.rs index f3c73a4..9bc2ec0 100644 --- a/radius/src/packet.rs +++ b/radius/src/packet.rs @@ -26,6 +26,7 @@ pub enum PacketError { UnknownCodeError(String), } +/// This struct represents a packet of RADIUS for request and response. #[derive(Debug, Clone, PartialEq)] pub struct Packet { code: Code, @@ -36,6 +37,7 @@ pub struct Packet { } impl Packet { + /// Constructor for a Packet. pub fn new(code: Code, secret: &[u8]) -> Self { let mut rng = rand::thread_rng(); let authenticator = (0..16).map(|_| rng.gen()).collect::>(); @@ -64,6 +66,7 @@ impl Packet { &self.authenticator } + /// This decodes bytes into a Packet. pub fn decode(bs: &[u8], secret: &[u8]) -> Result { if bs.len() < RADIUS_PACKET_HEADER_LENGTH { return Err(PacketError::InsufficientPacketLengthError( @@ -93,6 +96,7 @@ impl Packet { }) } + /// This method makes a response packet according to self (i.e. request packet). pub fn make_response_packet(&self, code: Code) -> Self { Packet { code, @@ -103,6 +107,7 @@ impl Packet { } } + /// This method encodes the Packet into bytes. pub fn encode(&self) -> Result, PacketError> { let mut bs = match self.marshal_binary() { Ok(bs) => bs, @@ -181,6 +186,7 @@ impl Packet { Ok(bs) } + /// Returns whether the Packet is authentic response or not. pub fn is_authentic_response(response: &[u8], request: &[u8], secret: &[u8]) -> bool { if response.len() < RADIUS_PACKET_HEADER_LENGTH || request.len() < RADIUS_PACKET_HEADER_LENGTH @@ -202,6 +208,7 @@ impl Packet { .eq(&response[4..RADIUS_PACKET_HEADER_LENGTH].to_vec()) } + /// Returns whether the Packet is authentic request or not. pub fn is_authentic_request(request: &[u8], secret: &[u8]) -> bool { if request.len() < RADIUS_PACKET_HEADER_LENGTH || secret.is_empty() { return false; @@ -227,22 +234,27 @@ impl Packet { } } + /// Add an AVP to the list of AVPs. pub fn add(&mut self, avp: AVP) { self.attributes.add(avp); } + /// Add AVPs to the list of AVPs. pub fn extend(&mut self, avps: Vec) { self.attributes.extend(avps) } + /// Delete all of AVPs from the list according to given AVP type. pub fn delete(&mut self, typ: AVPType) { self.attributes.del(typ); } + /// Returns an AVP that matches at first with the given AVP type. If there are not any matched ones, this returns `None`. pub fn lookup(&self, typ: AVPType) -> Option<&AVP> { self.attributes.lookup(typ) } + /// Returns AVPs that match with the given AVP type. pub fn lookup_all(&self, typ: AVPType) -> Vec<&AVP> { self.attributes.lookup_all(typ) } diff --git a/radius/src/request.rs b/radius/src/request.rs index 0b68a18..999f336 100644 --- a/radius/src/request.rs +++ b/radius/src/request.rs @@ -2,6 +2,7 @@ use std::net::SocketAddr; use crate::packet::Packet; +/// RADIUS request object. pub struct Request { local_addr: SocketAddr, remote_addr: SocketAddr, diff --git a/radius/src/tag.rs b/radius/src/tag.rs index 38b13eb..7969a94 100644 --- a/radius/src/tag.rs +++ b/radius/src/tag.rs @@ -1,5 +1,7 @@ pub(crate) const UNUSED_TAG_VALUE: u8 = 0x00; +/// Tag represents a tag of a RADIUS value. +/// see also: http://www.ietf.org/rfc/rfc2868.html #[derive(Debug, PartialEq)] pub struct Tag { pub(crate) value: u8,