This commit is contained in:
moznion
2020-12-07 23:28:38 +09:00
parent 93b951eff8
commit 3e6701b0b1
9 changed files with 79 additions and 13 deletions

View File

@@ -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 (<moznion@gmail.com>)

View File

@@ -32,6 +32,7 @@ pub enum ClientError {
SocketTimeoutError(),
}
/// A basic implementation of the RADIUS client.
pub struct Client {
connection_timeout: Option<Duration>,
socket_timeout: Option<Duration>,
@@ -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<Duration>, socket_timeout: Option<Duration>) -> 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<Packet, ClientError> {
// TODO retransmission
let local_addr: SocketAddr = if remote_addr.is_ipv4() {
"0.0.0.0:0"
} else {

View File

@@ -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<T, E>: '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<T, E>;
}

View File

@@ -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<Vec<u8>, SecretProviderError>;
}

View File

@@ -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<X, E: Debug, T: RequestHandler<X, E>, U: SecretProvider>(
host: &str,
port: u16,

View File

@@ -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<Self, AVPError> {
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<Self, AVPError> {
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<Utc>) -> 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<u32, AVPError> {
const U32_SIZE: usize = std::mem::size_of::<u32>();
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<u16, AVPError> {
const U16_SIZE: usize = std::mem::size_of::<u16>();
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<String, AVPError> {
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<Tag>), 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<u8> {
self.value.to_vec()
}
/// (This method is for dictionary developers) encode an AVP into Ipv4 value.
pub fn encode_ipv4(&self) -> Result<Ipv4Addr, AVPError> {
const IPV4_SIZE: usize = std::mem::size_of::<Ipv4Addr>();
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<Vec<u8>, 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<Ipv6Addr, AVPError> {
const IPV6_SIZE: usize = std::mem::size_of::<Ipv6Addr>();
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<Vec<u8>, 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<DateTime<Utc>, AVPError> {
const U32_SIZE: usize = std::mem::size_of::<u32>();
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],

View File

@@ -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::<Vec<u8>>();
@@ -64,6 +66,7 @@ impl Packet {
&self.authenticator
}
/// This decodes bytes into a Packet.
pub fn decode(bs: &[u8], secret: &[u8]) -> Result<Self, PacketError> {
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<Vec<u8>, 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<AVP>) {
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)
}

View File

@@ -2,6 +2,7 @@ use std::net::SocketAddr;
use crate::packet::Packet;
/// RADIUS request object.
pub struct Request {
local_addr: SocketAddr,
remote_addr: SocketAddr,

View File

@@ -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,