radius_server/
packet.rs

1use std::str;
2
3#[derive(Debug,Clone)]
4pub struct RadiusPacket {
5    pub code: u8,
6    pub identifier: u8,
7    pub length: u16,
8    pub authenticator: [u8; 16],
9    pub attributes: Vec<RadiusAttribute>,
10}
11
12#[derive(Debug,Clone)]
13pub struct RadiusAttribute {
14    pub typ: u8,
15    pub len: u8,
16    pub value: Vec<u8>,
17}
18
19impl RadiusAttribute {
20    pub fn reply_message(msg: &str) -> Self {
21        let value = msg.as_bytes().to_vec();
22        let len = (value.len() + 2) as u8;
23
24        RadiusAttribute {
25            typ: 18,
26            len,
27            value,
28        }
29    }
30
31    pub fn user_name(name: &str) -> Self {
32        let value = name.as_bytes().to_vec();
33        let len = (value.len() + 2) as u8;
34
35        RadiusAttribute {
36            typ: 1,
37            len,
38            value,
39        }
40    }
41
42    pub fn vendor_specific(vendor_id: u32, payload: Vec<u8>) -> Self {
43        let mut value = Vec::new();
44        value.extend_from_slice(&vendor_id.to_be_bytes());
45        value.extend(payload);
46
47        RadiusAttribute {
48            typ: 26,
49            len: (2 + value.len()) as u8,
50            value,
51        }
52    }
53
54    pub fn wispr_bandwidth_max_up(bps: u32) -> Self {
55        let mut value = Vec::new();
56        value.extend_from_slice(&14122u32.to_be_bytes());
57        value.push(7);
58        value.push(6);
59        value.extend_from_slice(&bps.to_be_bytes());
60
61        RadiusAttribute {
62            typ: 26,
63            len: (2 + value.len()) as u8,
64            value,
65        }
66    }
67
68    pub fn wispr_bandwidth_max_down(bps: u32) -> Self {
69        let mut value = Vec::new();
70        value.extend_from_slice(&14122u32.to_be_bytes());
71        value.push(8);
72        value.push(6);
73        value.extend_from_slice(&bps.to_be_bytes());
74
75        RadiusAttribute {
76            typ: 26,
77            len: (2 + value.len()) as u8,
78            value,
79        }
80    }
81
82    pub fn session_timeout(seconds: u32) -> Self {
83        RadiusAttribute {
84            typ: 27,
85            len: 6,
86            value: seconds.to_be_bytes().to_vec(),
87        }
88    }
89
90    pub fn idle_timeout(seconds: u32) -> Self {
91        RadiusAttribute {
92            typ: 28,
93            len: 6,
94            value: seconds.to_be_bytes().to_vec(),
95        }
96    }
97}
98
99impl RadiusPacket {
100    pub fn from_bytes(buf: &[u8]) -> Result<Self, String> {
101        if buf.len() < 20 {
102            return Err("Packet too short".to_string());
103        }
104
105        let code = buf[0];
106        let identifier = buf[1];
107        let length = u16::from_be_bytes([buf[2], buf[3]]) as usize;
108
109        if buf.len() < length {
110            return Err(format!(
111                "Length mismatch: header says {}, but got {} bytes",
112                length,
113                buf.len()
114            ));
115        }
116
117        let authenticator: [u8; 16] = buf[4..20].try_into().unwrap();
118        let mut attributes = Vec::new();
119        let mut i = 20;
120
121        while i < length {
122            if i + 2 > length {
123                return Err("Truncated attribute header".to_string());
124            }
125
126            let typ = buf[i];
127            let len = buf[i + 1] as usize;
128
129            if len < 2 || i + len > length {
130                return Err(format!(
131                    "Invalid attribute length at index {}: len = {}",
132                    i, len
133                ));
134            }
135
136            let value = buf[i + 2..i + len].to_vec();
137            attributes.push(RadiusAttribute {
138                typ,
139                len: len as u8,
140                value,
141            });
142
143            i += len;
144        }
145
146        Ok(RadiusPacket {
147            code,
148            identifier,
149            length: length as u16,
150            authenticator,
151            attributes,
152        })
153    }
154
155    pub fn to_bytes(&self) -> Vec<u8> {
156        let mut buf = Vec::new();
157        buf.push(self.code);
158        buf.push(self.identifier);
159        buf.extend_from_slice(&[0x00, 0x00]);
160        buf.extend_from_slice(&self.authenticator);
161
162        for attr in &self.attributes {
163            buf.push(attr.typ);
164            buf.push(attr.len);
165            buf.extend_from_slice(&attr.value);
166        }
167
168        let length = buf.len() as u16;
169        buf[2] = (length >> 8) as u8;
170        buf[3] = length as u8;
171
172        buf
173    }
174
175    pub fn access_accept(identifier: u8, attributes: Vec<RadiusAttribute>) -> Self {
176        RadiusPacket {
177            code: 2,
178            identifier,
179            length: 0,
180            authenticator: [0u8; 16],
181            attributes,
182        }
183    }
184
185    pub fn access_reject(identifier: u8, msg: &str) -> Self {
186        RadiusPacket {
187            code: 3,
188            identifier,
189            length: 0,
190            authenticator: [0u8; 16],
191            attributes: vec![RadiusAttribute::reply_message(msg)],
192        }
193    }
194
195    pub fn access_challenge(identifier: u8, msg: &str) -> Self {
196        RadiusPacket {
197            code: 11,
198            identifier,
199            length: 0,
200            authenticator: [0u8; 16],
201            attributes: vec![RadiusAttribute::reply_message(msg)],
202        }
203    }
204
205    pub fn log(&self) {
206        let code_name = match self.code {
207            1 => "Access-Request",
208            2 => "Access-Accept",
209            3 => "Access-Reject",
210            _ => "Unknown",
211        };
212
213        println!("📨 RADIUS {} (id: {})", code_name, self.identifier);
214
215        for attr in &self.attributes {
216            let name = radius_type_name(attr.typ);
217            if attr.typ == 1 || attr.typ == 18 || attr.typ == 80 {
218                match str::from_utf8(&attr.value) {
219                    Ok(val) => println!("  • {}: {}", name, val.trim()),
220                    Err(_) => println!("  • {}: {:?}", name, attr.value),
221                }
222            } else if attr.typ == 26 {
223                println!("  • {}: {}", name, decode_vendor_specific(&attr.value));
224            } else {
225                println!("  • {}: {:?}", name, attr.value);
226            }
227        }
228    }
229      pub fn reply_accept(&self, attributes: Vec<RadiusAttribute>) -> RadiusPacket {
230        RadiusPacket {
231            code: 2,
232            identifier: self.identifier,
233            length: 0,
234            authenticator: [0; 16],
235            attributes,
236        }
237    }
238
239    pub fn reply_reject(&self, message: &str) -> RadiusPacket {
240        RadiusPacket {
241            code: 3,
242            identifier: self.identifier,
243            length: 0,
244            authenticator: [0; 16],
245            attributes: vec![RadiusAttribute::reply_message(message)],
246        }
247    }
248
249    pub fn reply_challenge(&self, message: &str) -> RadiusPacket {
250        RadiusPacket {
251            code: 11,
252            identifier: self.identifier,
253            length: 0,
254            authenticator: [0; 16],
255            attributes: vec![RadiusAttribute::reply_message(message)],
256        }
257    }
258
259    pub fn username(&self) -> Option<String> {
260        self.attributes
261            .iter()
262            .find(|a| a.typ == 1)
263            .and_then(|a| String::from_utf8(a.value.clone()).ok())
264    }
265}
266
267fn radius_type_name(typ: u8) -> &'static str {
268    match typ {
269        1 => "User-Name",
270        2 => "User-Password",
271        3 => "CHAP-Password",
272        4 => "NAS-IP-Address",
273        18 => "Reply-Message",
274        26 => "Vendor-Specific",
275        27 => "Session-Timeout",
276        28 => "Idle-Timeout",
277        80 => "Message-Authenticator",
278        _ => "Unknown",
279    }
280}
281
282fn decode_vendor_specific(value: &[u8]) -> String {
283    if value.len() < 6 {
284        return "Invalid VSA".to_string();
285    }
286
287    let vendor_id = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
288    let vendor_type = value.get(4).copied().unwrap_or(0);
289    let vendor_len = value.get(5).copied().unwrap_or(0) as usize;
290
291    let data_start = 6;
292    let data_end = data_start + (vendor_len.saturating_sub(2));
293    let payload = &value.get(data_start..data_end).unwrap_or(&[]);
294
295    format!("VendorID={}, Type={}, Data={:?}", vendor_id, vendor_type, payload)
296}