abol_core/lib.rs
1pub mod attribute;
2pub mod packet;
3use crate::packet::{Packet, PacketParseError};
4use core::fmt;
5use std::{
6 convert::TryFrom,
7 fmt::{Display, Formatter},
8 net::IpAddr,
9};
10/// Represents a RADIUS request received by the server.
11///
12/// Contains metadata about the client connection and the parsed RADIUS packet.
13pub struct Request {
14 /// Local socket address (IP:port) of the server that received the request.
15 pub local_addr: String,
16 /// Remote socket address (IP:port) of the client sending the request.
17 pub remote_addr: String,
18 /// Parsed RADIUS packet for this request.
19 pub packet: Packet,
20}
21/// Represents a RADIUS response to be sent back to the client.
22///
23/// Wraps a `Packet` that will be transmitted as the response.
24pub struct Response {
25 /// RADIUS packet that will be sent as a response.
26 pub packet: Packet,
27}
28
29/// RADIUS packet codes as defined in [RFC 2865](https://datatracker.ietf.org/doc/html/rfc2865).
30///
31/// Each variant corresponds to the `Code` field in the RADIUS header, indicating
32/// the type of request or response.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Code {
35 /// Access-Request (1): Used by a client to request authentication.
36 AccessRequest = 1,
37 /// Access-Accept (2): Server response granting access.
38 AccessAccept = 2,
39 /// Access-Reject (3): Server response denying access.
40 AccessReject = 3,
41 /// Accounting-Request (4): Used by a client to send accounting data.
42 AccountingRequest = 4,
43 /// Accounting-Response (5): Server acknowledgment of an Accounting-Request.
44 AccountingResponse = 5,
45 /// Access-Challenge (11): Server requests additional information.
46 AccessChallenge = 11,
47 /// Status-Server (12): Experimental code for server status.
48 StatusServer = 12,
49 /// Status-Client (13): Experimental code for client status.
50 StatusClient = 13,
51 /// Disconnect-Request (40): Used in CoA to terminate a session.
52 DisconnectRequest = 40,
53 /// Disconnect-ACK (41): Acknowledgment of Disconnect-Request.
54 DisconnectAck = 41,
55 /// Disconnect-NAK (42): Negative acknowledgment of Disconnect-Request.
56 DisconnectNak = 42,
57 /// CoA-Request (43): Change-of-Authorization request.
58 CoARequest = 43,
59 /// CoA-ACK (44): Acknowledgment of CoA request.
60 CoAACK = 44,
61 /// CoA-NAK (45): Negative acknowledgment of CoA request.
62 CoANAK = 45,
63 /// Reserved (255): Reserved/experimental codes not standardized.
64 Reserved = 255,
65}
66
67impl Display for Code {
68 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69 let text = match self {
70 Code::AccessRequest => "Access-Request",
71 Code::AccessAccept => "Access-Accept",
72 Code::AccessReject => "Access-Reject",
73 Code::AccountingRequest => "Accounting-Request",
74 Code::AccountingResponse => "Accounting-Response",
75 Code::AccessChallenge => "Access-Challenge",
76 Code::StatusServer => "Status-Server",
77 Code::StatusClient => "Status-Client",
78 Code::DisconnectRequest => "Disconnect-Request",
79 Code::DisconnectAck => "Disconnect-ACK",
80 Code::DisconnectNak => "Disconnect-NAK",
81 Code::CoARequest => "CoA-Request",
82 Code::CoAACK => "CoA-ACK",
83 Code::CoANAK => "CoA-NAK",
84 Code::Reserved => "Reserved",
85 };
86
87 write!(f, "{}", text)
88 }
89}
90
91impl TryFrom<u8> for Code {
92 type Error = PacketParseError;
93 fn try_from(value: u8) -> Result<Self, Self::Error> {
94 match value {
95 1 => Ok(Code::AccessRequest),
96 2 => Ok(Code::AccessAccept),
97 3 => Ok(Code::AccessReject),
98 4 => Ok(Code::AccountingRequest),
99 5 => Ok(Code::AccountingResponse),
100 11 => Ok(Code::AccessChallenge),
101 12 => Ok(Code::StatusServer),
102 13 => Ok(Code::StatusClient),
103 40 => Ok(Code::DisconnectRequest),
104 41 => Ok(Code::DisconnectAck),
105 42 => Ok(Code::DisconnectNak),
106 43 => Ok(Code::CoARequest),
107 44 => Ok(Code::CoAACK),
108 45 => Ok(Code::CoANAK),
109 255 => Ok(Code::Reserved),
110 _ => Err(PacketParseError::InvalidLength(value as usize)),
111 }
112 }
113}
114/// Represents an IP network prefix using Classless Inter-Domain Routing (CIDR) notation.
115///
116/// This struct is used to define a range of IP addresses (a subnet) by combining
117/// a base IP address with a routing prefix length.
118///
119/// # Example
120/// ```rust
121/// # use std::error::Error;
122/// # use std::net::IpAddr;
123/// # use abol_core::Cidr;
124/// # fn main() -> Result<(), Box<dyn Error>> {
125/// let network = Cidr {
126/// ip: "192.168.1.0".parse()?,
127/// prefix: 24,
128/// };
129/// # Ok(())
130/// # }
131/// ```
132#[derive(Debug, Clone, PartialEq, Eq, Hash)]
133pub struct Cidr {
134 /// The base IP address of the network.
135 pub ip: IpAddr,
136
137 /// The routing prefix length (the number of leading bits in the subnet mask).
138 ///
139 /// For IPv4, this should be in the range 0..=32.
140 /// For IPv6, this should be in the range 0..=128.
141 pub prefix: u8,
142}
143
144impl Cidr {
145 pub fn contains(&self, other: &IpAddr) -> bool {
146 match (self.ip, other) {
147 (IpAddr::V4(net), IpAddr::V4(ip)) => {
148 let mask = u32::MAX.checked_shl(32 - self.prefix as u32).unwrap_or(0);
149 u32::from(net) & mask == u32::from(*ip) & mask
150 }
151 (IpAddr::V6(net), IpAddr::V6(ip)) => {
152 let mask = u128::MAX.checked_shl(128 - self.prefix as u32).unwrap_or(0);
153 u128::from(net) & mask == u128::from(*ip) & mask
154 }
155 _ => false,
156 }
157 }
158}