Skip to main content

netget/client/radsec/
actions.rs

1//! The RadSec client offers the model exactly the RADIUS client's vocabulary — the same
2//! requests and the same reply events. Only the transport differs: one TLS connection, with no
3//! retransmission (RFC 6614 §2.5).
4use crate::client::radius::RadiusClientProtocol;
5use crate::llm::actions::{
6    client_trait::{Client, ClientActionResult},
7    protocol_trait::Protocol,
8    ActionDefinition, ParameterDefinition, StartupExamples,
9};
10use crate::protocol::{ConnectContext, EventType};
11use crate::state::app_state::AppState;
12use anyhow::Result;
13use serde_json::{json, Value};
14
15/// How long a request waits for its reply by default.
16pub const DEFAULT_TIMEOUT_MS: u64 = 10_000;
17
18#[derive(Default)]
19pub struct RadsecClientProtocol;
20impl RadsecClientProtocol {
21    pub fn new() -> Self {
22        Self
23    }
24}
25
26impl Protocol for RadsecClientProtocol {
27    fn protocol_name(&self) -> &'static str {
28        "RadSec"
29    }
30    fn stack_name(&self) -> &'static str {
31        "ETH>IP>TCP>TLS>RadSec"
32    }
33    fn keywords(&self) -> Vec<&'static str> {
34        vec!["radsec", "radius over tls", "rfc 6614"]
35    }
36    fn description(&self) -> &'static str {
37        "RadSec client: a NAS sending RADIUS authentication and accounting over TLS (RFC 6614)"
38    }
39    fn get_async_actions(&self, state: &AppState) -> Vec<ActionDefinition> {
40        RadiusClientProtocol::new().get_async_actions(state)
41    }
42    fn get_sync_actions(&self) -> Vec<ActionDefinition> {
43        RadiusClientProtocol::new().get_sync_actions()
44    }
45    fn get_event_types(&self) -> Vec<EventType> {
46        RadiusClientProtocol::new().get_event_types()
47    }
48    fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
49        let mut p = crate::server::radsec::actions::tls_parameters(false);
50        p.extend([
51            ParameterDefinition {
52                name: "server_name".into(),
53                type_hint: "string".into(),
54                description: "Name the server's certificate must carry; defaults to the host of remote_addr".into(),
55                required: false,
56                example: json!("radsec.example.net"),
57                default: None,
58            },
59            ParameterDefinition {
60                name: "timeout_ms".into(),
61                type_hint: "number".into(),
62                description: "Milliseconds a request waits for its reply before it is reported unanswered (100..=60000)".into(),
63                required: false,
64                example: json!(5000),
65                default: Some(json!(DEFAULT_TIMEOUT_MS)),
66            },
67        ]);
68        p
69    }
70    fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
71        use crate::protocol::metadata::*;
72        ProtocolMetadataV2::builder()
73            .state(DevelopmentState::Experimental)
74            .privilege_requirement(PrivilegeRequirement::None)
75            .implementation("The RADIUS client's transport over a tokio-rustls stream: packets framed by their Length field, the server verified against ca_file, a client certificate when given; every reply verified (Response Authenticator, Message-Authenticator) exactly as over UDP")
76            .llm_control("The same as the RADIUS client: who to authenticate and how, what to account, and what to do with each reply")
77            .e2e_testing("tests/client/radsec: NetGet's own RadSec server; FreeRADIUS with a TLS listener, and radsecproxy in front of FreeRADIUS, as independent servers")
78            .notes("One connection carries authentication and accounting. Nothing is retransmitted; a request waits timeout_ms and is then reported as a timeout. A connection that closes ends the client.")
79            .max_inbound_bytes(crate::server::radius::packet::MAX_PACKET_LEN)
80            .build()
81    }
82    fn example_prompt(&self) -> &'static str {
83        "Authenticate alice with password wonderland against the RadSec server at radsec.example.net:2083"
84    }
85    fn get_startup_examples(&self) -> StartupExamples {
86        let base = json!({"type":"open_client","protocol":"radsec","remote_addr":"127.0.0.1:2083",
87            "startup_params":{"ca_file":"/etc/netget/radsec-ca.pem","certificate_file":"/etc/netget/radsec-client.pem","private_key_file":"/etc/netget/radsec-client.key","server_name":"localhost"},
88            "instruction":"Authenticate alice with password wonderland"});
89        let mut static_example = base.clone();
90        static_example["event_handlers"] = json!([{"event_pattern":"radius_connected","handler":{"type":"static","actions":[
91            {"type":"radius_access_request","user_name":"alice","password":"wonderland"}]}}]);
92        let mut scripted = base.clone();
93        scripted["event_handlers"] = json!([{"event_pattern":"radius_access_accept","handler":{"type":"script","language":"python",
94            "code":"respond([{'type': 'disconnect'}])"}}]);
95        StartupExamples::new(base, scripted, static_example)
96    }
97    fn group_name(&self) -> &'static str {
98        "Application"
99    }
100}
101
102impl Client for RadsecClientProtocol {
103    fn connect(
104        &self,
105        ctx: ConnectContext,
106    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<std::net::SocketAddr>> + Send>>
107    {
108        Box::pin(super::connect(ctx))
109    }
110    fn execute_action(&self, v: Value) -> Result<ClientActionResult> {
111        RadiusClientProtocol::new().execute_action(v)
112    }
113}