1pub mod actions;
2pub mod codec;
3use crate::llm::actions::{executor::ExecutionResult, protocol_trait::ActionResult};
4use crate::logging::emit::Log;
5use crate::protocol::{Event, SpawnContext};
6use crate::server::{
7 accept_bounded::{accept_bounded, ConnectionLimiter, DEFAULT_MAX_CONNECTIONS},
8 connection::ConnectionId,
9};
10use crate::state::{
11 server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo},
12 AccessLogOwner,
13};
14use anyhow::{bail, ensure, Context, Result};
15use codec::*;
16use serde_json::{json, Value};
17use std::{future::Future, net::SocketAddr, pin::Pin, sync::Arc, time::Duration};
18use tokio::{
19 net::{
20 tcp::{OwnedReadHalf, OwnedWriteHalf},
21 TcpListener, TcpStream,
22 },
23 time::Instant,
24};
25
26pub struct Config {
27 pub identity: Identity,
28 pub io_timeout: Duration,
29 pub handler_timeout: Duration,
30 pub watchdog_interval: Duration,
31 pub llm_fallback: bool,
32}
33impl Config {
34 pub fn from_params(p: &crate::protocol::StartupParams) -> Result<Self> {
35 Ok(Self {
36 identity: actions::identity(p)?,
37 io_timeout: timeout(
38 p.get_optional_u64("io_timeout_seconds")?,
39 DEFAULT_IO_SECONDS,
40 )?,
41 handler_timeout: timeout(
42 p.get_optional_u64("handler_timeout_seconds")?,
43 DEFAULT_HANDLER_SECONDS,
44 )?,
45 watchdog_interval: timeout(
46 p.get_optional_u64("watchdog_interval_seconds")?,
47 DEFAULT_WATCHDOG_SECONDS,
48 )?,
49 llm_fallback: p
50 .get_optional_bool("llm_fallback")?
51 .unwrap_or(DEFAULT_LLM_FALLBACK),
52 })
53 }
54}
55pub type Reader = Pin<Box<dyn Future<Output = (Result<Packet>, OwnedReadHalf)> + Send>>;
56pub fn reader(mut half: OwnedReadHalf, deadline: Duration) -> Reader {
57 Box::pin(async move {
58 let packet = read_packet(&mut half, deadline).await;
59 (packet, half)
60 })
61}
62pub struct DiameterServer;
63impl DiameterServer {
64 pub async fn spawn(mut ctx: SpawnContext) -> Result<SocketAddr> {
65 let cfg = Arc::new(Config::from_params(
66 ctx.startup_params
67 .as_ref()
68 .context("Diameter identity parameters required")?,
69 )?);
70 ctx.startup_params = None;
71 let listener =
72 TcpListener::bind(ctx.socket_addr().context("Diameter TCP bind address")?).await?;
73 let local = listener.local_addr()?;
74 let sid = ctx.server_id;
75 Log::new(Some(&ctx.status_tx))
76 .info(format!("Diameter experimental NASREQ listening on {local}"));
77 ctx.state
78 .clone()
79 .spawn_server_task(sid, async move {
80 let limiter = ConnectionLimiter::new(DEFAULT_MAX_CONNECTIONS);
81 loop {
82 let (socket, peer, permit) = match accept_bounded(
83 &listener,
84 &limiter,
85 b"",
86 "DIAMETER",
87 Some(&ctx.status_tx),
88 )
89 .await
90 {
91 Ok(v) => v,
92 Err(_) => break,
93 };
94 let cid = ConnectionId::new(ctx.state.get_next_unified_id().await);
95 let now = crate::utils::clock::Instant::now();
96 ctx.state
97 .add_connection_to_server(
98 sid,
99 ConnectionState {
100 id: cid,
101 remote_addr: peer,
102 local_addr: local,
103 bytes_sent: 0,
104 bytes_received: 0,
105 packets_sent: 0,
106 packets_received: 0,
107 last_activity: now,
108 status: ConnectionStatus::Active,
109 status_changed_at: now,
110 protocol_info: ProtocolConnectionInfo::empty(),
111 },
112 )
113 .await;
114 let child = ctx.clone();
115 let config = cfg.clone();
116 ctx.state
117 .spawn_server_task(sid, async move {
118 let _permit = permit;
119 if session(&child, cid, socket, peer, &config).await.is_err() {
120 decision(&child, cid, "fail_closed_peer_session");
121 }
122 child
123 .state
124 .update_connection_status(sid, cid, ConnectionStatus::Closed)
125 .await;
126 child.state.remove_connection_from_server(sid, cid).await;
127 let _ = child.status_tx.send("__UPDATE_UI__".into());
128 })
129 .await;
130 }
131 })
132 .await;
133 Ok(local)
134 }
135}
136fn decision(ctx: &SpawnContext, id: ConnectionId, tag: &str) {
137 Log::new(Some(&ctx.status_tx)).info(format!("Diameter connection={id} decision={tag}"));
138}
139async fn send(
140 ctx: &SpawnContext,
141 id: ConnectionId,
142 w: &mut OwnedWriteHalf,
143 p: &Packet,
144) -> Result<()> {
145 let n = write_packet(w, p).await?;
146 ctx.state
147 .update_connection_stats(ctx.server_id, id, None, Some(n as u64), None, Some(1))
148 .await;
149 Ok(())
150}
151async fn received(ctx: &SpawnContext, id: ConnectionId, p: &Packet) {
152 ctx.state
153 .update_connection_stats(
154 ctx.server_id,
155 id,
156 Some(p.encode().map(|b| b.len()).unwrap_or(0) as u64),
157 None,
158 Some(1),
159 None,
160 )
161 .await;
162}
163pub fn control(p: &Packet, peer: &Identity) -> Result<()> {
164 ensure!(
165 p.application == 0 && p.flags & 0x40 == 0 && p.flags & 0x20 == 0,
166 "Diameter peer-control header"
167 );
168 peer.same_peer(p)?;
169 let allowed = if p.command == DPR {
170 &[HOST, REALM, ORIGIN_STATE, DISCONNECT_CAUSE, RESULT][..]
171 } else {
172 &[HOST, REALM, ORIGIN_STATE, RESULT][..]
173 };
174 ensure!(
175 p.unsupported_mandatory(allowed).is_none(),
176 "Diameter unsupported mandatory peer-control AVP"
177 );
178 if let Some(a) = p.optional(ORIGIN_STATE)? {
179 a.integer()?;
180 }
181 if p.is_request() && p.command == DPR {
182 ensure!(p.num(DISCONNECT_CAUSE)? <= 2, "Diameter Disconnect-Cause");
183 }
184 if !p.is_request() {
185 ensure!(p.num(RESULT)? == 2001, "Diameter peer-control rejected");
186 }
187 Ok(())
188}
189pub fn chosen_reply(result: ExecutionResult) -> Result<Option<Reply>> {
192 let mut invalid = !result.failures.is_empty() || result.raw_actions.len() > 32;
193 for v in result.raw_actions {
194 invalid |= !within_json_budget(&v);
195 crate::utils::json_budget::drop_iteratively(v);
196 }
197 let mut pending = result.protocol_results;
198 let mut found: Option<Value> = None;
199 let mut seen = 0usize;
200 while let Some(result) = pending.pop() {
201 seen = seen.saturating_add(1);
202 invalid |= seen > 64;
203 match result {
204 ActionResult::Multiple(v) => pending.extend(v),
205 ActionResult::Custom { name, data } => {
206 if name == "respond_diameter_aa" && found.is_none() && within_json_budget(&data) {
207 found = Some(data);
208 } else {
209 invalid = true;
210 crate::utils::json_budget::drop_iteratively(data);
211 }
212 }
213 ActionResult::NoAction => {}
214 _ => invalid = true,
215 }
216 }
217 if invalid {
218 if let Some(v) = found {
219 crate::utils::json_budget::drop_iteratively(v);
220 }
221 bail!("Diameter handler failure/type/count/budget");
222 }
223 found
224 .map(|v| {
225 let reply: Reply = serde_json::from_value(v)?;
226 reply.validate()?;
227 Ok(reply)
228 })
229 .transpose()
230}
231type Handler = Pin<Box<dyn Future<Output = Result<Option<Reply>>> + Send>>;
232fn handler(ctx: SpawnContext, cid: ConnectionId, event: Event, cfg: Arc<Config>) -> Handler {
233 Box::pin(async move {
234 let configured = ctx
235 .state
236 .get_event_handler_config(ctx.server_id)
237 .await
238 .is_some_and(|c| c.find_handler(event.id()).is_some());
239 if !configured && !cfg.llm_fallback {
240 let event_id = event.id().to_owned();
241 ctx.state
242 .record_access_log(
243 AccessLogOwner::Server(ctx.server_id.as_u32()),
244 "DIAMETER",
245 Some(cid.as_u32()),
246 &event_id,
247 event.data,
248 vec![],
249 )
250 .await;
251 return Ok(None);
252 }
253 let result = tokio::time::timeout(
254 cfg.handler_timeout,
255 crate::llm::action_helper::call_llm(
256 &ctx.llm_client,
257 &ctx.state,
258 ctx.server_id,
259 Some(cid),
260 &event,
261 &actions::DiameterProtocol,
262 ),
263 )
264 .await
265 .context("Diameter AAA handler deadline")??;
266 chosen_reply(result)
267 })
268}
269async fn refuse(
270 ctx: &SpawnContext,
271 cid: ConnectionId,
272 w: &mut OwnedWriteHalf,
273 p: &Packet,
274 id: &Identity,
275 code: u32,
276 failed: Option<&Avp>,
277 tag: &str,
278) -> Result<()> {
279 decision(ctx, cid, tag);
280 send(ctx, cid, w, &error_answer(p, id, code, failed)?).await
281}
282async fn session(
283 ctx: &SpawnContext,
284 cid: ConnectionId,
285 socket: TcpStream,
286 source: SocketAddr,
287 cfg: &Arc<Config>,
288) -> Result<()> {
289 let local_ip = socket.local_addr()?.ip();
290 let (mut half, mut w) = socket.into_split();
291 let first = read_packet(&mut half, cfg.io_timeout).await?;
292 received(ctx, cid, &first).await;
293 ensure!(
294 first.is_request() && first.command == CER,
295 "Diameter CER required first"
296 );
297 let peer = match capabilities(&first) {
298 Ok(peer) => peer,
299 Err(_) => {
300 let failed = first.unsupported_mandatory(BASE_ALLOWED);
301 let security = first.avps.iter().any(|a| {
302 a.code == SECURITY && a.vendor.is_none() && a.integer().ok().is_some_and(|v| v != 0)
303 });
304 let code =
305 if failed.is_some() {
306 5001
307 } else if security {
308 5017
309 } else if !first.avps.iter().any(|a| {
310 a.code == AUTH_APP && a.vendor.is_none() && a.integer().ok() == Some(1)
311 }) {
312 5010
313 } else {
314 5012
315 };
316 decision(ctx, cid, "fail_closed_capabilities");
317 let mut answer = error_answer(&first, &cfg.identity, code, failed)?;
318 answer.avps.retain(|a| ![HOST, REALM].contains(&a.code));
319 capability_fields(&mut answer, &cfg.identity, local_ip);
320 send(ctx, cid, &mut w, &answer).await?;
321 return Ok(());
322 }
323 };
324 let mut cea = first.answer(2001);
325 capability_fields(&mut cea, &cfg.identity, local_ip);
326 send(ctx, cid, &mut w, &cea).await?;
327 decision(ctx, cid, "peer_capabilities_accepted");
328 let mut reading = reader(half, cfg.io_timeout);
329 let mut current: Option<(Packet, Handler)> = None;
330 let mut watchdog: Option<(Packet, Instant)> = None;
331 let mut next_watchdog = Instant::now() + cfg.watchdog_interval;
332 loop {
333 tokio::select! {
334 (result,half)=&mut reading=>{
335 let p = result?;
336 received(ctx, cid, &p).await;
337 reading = reader(half, cfg.io_timeout);
338 next_watchdog = Instant::now() + cfg.watchdog_interval;
339 if !p.is_request() {
340 let Some((request, _)) = watchdog.take() else {
341 bail!("Diameter unexpected answer");
342 };
343 ensure!(p.matches(&request), "Diameter watchdog correlation");
344 control(&p, &peer)?;
345 continue;
346 }
347 match p.command {
348 DWR | DPR => {
349 control(&p, &peer)?;
350 let mut answer = p.answer(2001);
351 answer.origin(&cfg.identity);
352 send(ctx, cid, &mut w, &answer).await?;
353 if p.command == DPR {
354 decision(ctx, cid, "peer_disconnect_accepted");
355 break;
356 }
357 }
358 CER => {
359 refuse(
360 ctx,
361 cid,
362 &mut w,
363 &p,
364 &cfg.identity,
365 5012,
366 None,
367 "fail_closed_repeated_capabilities",
368 )
369 .await?;
370 break;
371 }
372 AA if p.application == 1 => {
373 if let Some(failed) = p.unsupported_mandatory(REQUEST_ALLOWED) {
374 refuse(
375 ctx,
376 cid,
377 &mut w,
378 &p,
379 &cfg.identity,
380 5001,
381 Some(failed),
382 "fail_closed_mandatory_avp",
383 )
384 .await?;
385 continue;
386 }
387 let (request, session) = match Request::from_packet(&p, &cfg.identity, &peer) {
388 Ok(v) => v,
389 Err(_) => {
390 refuse(
391 ctx,
392 cid,
393 &mut w,
394 &p,
395 &cfg.identity,
396 5012,
397 None,
398 "fail_closed_request_semantics",
399 )
400 .await?;
401 continue;
402 }
403 };
404 if current.is_some() {
405 refuse(
406 ctx,
407 cid,
408 &mut w,
409 &p,
410 &cfg.identity,
411 3004,
412 None,
413 "fail_closed_handler_capacity",
414 )
415 .await?;
416 continue;
417 }
418 let mut data = serde_json::to_value(request)?;
419 data["session_id"] = json!(session);
420 data["origin_host"] = json!(peer.host);
421 data["origin_realm"] = json!(peer.realm);
422 data["source_addr"] = json!(source.to_string());
423 let event = Event::new(&actions::AA_EVENT, json!({"request":data}));
424 current = Some((p, handler(ctx.clone(), cid, event, cfg.clone())));
425 }
426 _ => {
427 let code = if p.application != 0 && p.application != 1 {
428 3007
429 } else {
430 3001
431 };
432 refuse(
433 ctx,
434 cid,
435 &mut w,
436 &p,
437 &cfg.identity,
438 code,
439 None,
440 "fail_closed_unsupported_command_application",
441 )
442 .await?;
443 }
444 }
445 }
446 result=async{current.as_mut().unwrap().1.as_mut().await},if current.is_some()=>{
447 let (request, _) = current.take().unwrap();
448 match result {
449 Ok(reply) => {
450 let reply = reply.unwrap_or_default();
451 decision(
452 ctx,
453 cid,
454 match reply.verdict {
455 Verdict::Accept => "handler_accept",
456 Verdict::Reject => "handler_reject_or_no_action",
457 Verdict::Error => "handler_error",
458 },
459 );
460 let answer = reply.packet(&request, &cfg.identity)?;
461 send(ctx, cid, &mut w, &answer).await?;
462 }
463 Err(error) => {
464 let code = match crate::utils::WireFailure::classify(&error) {
465 crate::utils::WireFailure::Overloaded => 3004,
466 crate::utils::WireFailure::Unavailable => 5012,
467 };
468 refuse(
469 ctx,
470 cid,
471 &mut w,
472 &request,
473 &cfg.identity,
474 code,
475 None,
476 "fail_closed_handler_failure",
477 )
478 .await?;
479 }
480 }
481 next_watchdog = Instant::now() + cfg.watchdog_interval;
482 }
483 _=tokio::time::sleep_until(next_watchdog),if watchdog.is_none()=>{
484 let mut request = Packet::request(DWR, 0)?;
485 request.origin(&cfg.identity);
486 send(ctx, cid, &mut w, &request).await?;
487 watchdog = Some((request, Instant::now() + cfg.io_timeout));
488 }
489 _=tokio::time::sleep_until(watchdog.as_ref().map(|v|v.1).unwrap_or_else(Instant::now)),if watchdog.is_some()=>{
490 decision(ctx, cid, "fail_closed_watchdog_deadline");
491 bail!("Diameter watchdog answer deadline");
492 }
493 }
494 }
495 Ok(())
496}