Skip to main content

netconf_rust/
config.rs

1use std::path::PathBuf;
2use std::time::Duration;
3
4use crate::codec::CodecConfig;
5
6/// Host key verification mode, mirroring OpenSSH's `StrictHostKeyChecking`.
7#[derive(Debug, Clone, Default)]
8pub enum HostKeyVerification {
9    /// Reject connections to hosts not already in `known_hosts`.
10    Strict,
11    /// Accept and learn unknown host keys; reject changed keys (OpenSSH default).
12    #[default]
13    AcceptNew,
14    /// Accept all host keys without verification (insecure).
15    Disabled,
16}
17
18/// SSH transport settings exposed to users.
19///
20/// These map to the corresponding fields in `russh::client::Config`.
21/// Fields left as `None` use the russh defaults.
22#[derive(Debug, Clone)]
23pub(crate) struct SshConfig {
24    /// Time after which the connection is garbage-collected.
25    pub inactivity_timeout: Option<Duration>,
26    /// If nothing is received from the server for this amount of time, send a keepalive message.
27    pub keepalive_interval: Option<Duration>,
28    /// If this many keepalives have been sent without reply, close the connection.
29    pub keepalive_max: Option<usize>,
30    /// Enable `TCP_NODELAY` (disable Nagle's algorithm).
31    pub nodelay: Option<bool>,
32    /// SSH flow-control window size.
33    pub window_size: Option<u32>,
34    /// Maximum SSH packet size.
35    pub maximum_packet_size: Option<u32>,
36    /// Host key verification mode.
37    pub host_key_verification: HostKeyVerification,
38    /// Custom path to `known_hosts` file. `None` uses `~/.ssh/known_hosts`.
39    pub known_hosts_path: Option<PathBuf>,
40}
41
42#[derive(Debug, Clone)]
43pub(crate) struct JumphostConfig {
44    pub host: String,
45    pub port: u16,
46    pub username: String,
47    pub password: String,
48}
49
50impl Default for SshConfig {
51    fn default() -> Self {
52        Self {
53            inactivity_timeout: None,
54            keepalive_interval: Some(Duration::from_secs(10)),
55            keepalive_max: Some(3),
56            nodelay: None,
57            window_size: None,
58            maximum_packet_size: None,
59            host_key_verification: HostKeyVerification::AcceptNew,
60            known_hosts_path: None,
61        }
62    }
63}
64
65/// Configuration for a NETCONF session.
66///
67/// Use [`Config::builder()`] for a fluent API, or [`Config::default()`]
68/// for sensible defaults.
69///
70/// # Example
71///
72/// ```
73/// use std::time::Duration;
74/// use netconf_rust::Config;
75///
76/// let config = Config::builder()
77///     .hello_timeout(Duration::from_secs(10))
78///     .connect_timeout(Duration::from_secs(5))
79///     .nodelay(true)
80///     .build();
81/// ```
82#[derive(Debug, Clone)]
83pub struct Config {
84    pub(crate) codec: CodecConfig,
85    pub(crate) connect_timeout: Option<Duration>,
86    pub(crate) hello_timeout: Option<Duration>,
87    pub(crate) rpc_timeout: Option<Duration>,
88    pub(crate) ssh: SshConfig,
89    pub(crate) jumphost: Option<JumphostConfig>,
90}
91
92impl Default for Config {
93    fn default() -> Self {
94        Self {
95            codec: CodecConfig::default(),
96            connect_timeout: None,
97            hello_timeout: Some(Duration::from_secs(30)),
98            rpc_timeout: None,
99            ssh: SshConfig::default(),
100            jumphost: None,
101        }
102    }
103}
104
105impl Config {
106    pub fn builder() -> ConfigBuilder {
107        ConfigBuilder {
108            config: Config::default(),
109        }
110    }
111
112    /// Maximum message size limit, or `None` for unlimited.
113    pub fn max_message_size(&self) -> Option<usize> {
114        self.codec.max_message_size
115    }
116
117    /// Timeout for the TCP/SSH connect phase.
118    pub fn connect_timeout(&self) -> Option<Duration> {
119        self.connect_timeout
120    }
121
122    /// Timeout for the NETCONF hello exchange.
123    pub fn hello_timeout(&self) -> Option<Duration> {
124        self.hello_timeout
125    }
126
127    /// Timeout for individual RPC operations.
128    pub fn rpc_timeout(&self) -> Option<Duration> {
129        self.rpc_timeout
130    }
131}
132
133#[derive(Debug, Clone)]
134pub struct ConfigBuilder {
135    config: Config,
136}
137
138impl ConfigBuilder {
139    /// Set the maximum NETCONF message size. `None` means unlimited.
140    pub fn max_message_size(mut self, size: usize) -> Self {
141        self.config.codec.max_message_size = Some(size);
142        self
143    }
144
145    /// Set the timeout for the TCP/SSH connect phase.
146    ///
147    /// When set, [`Session::connect_with_config`](crate::Session::connect_with_config)
148    /// will fail with [`TransportError::Timeout`](crate::error::TransportError::Timeout)
149    /// if the SSH connection is not established within this duration.
150    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
151        self.config.connect_timeout = Some(timeout);
152        self
153    }
154
155    /// Set the timeout for the NETCONF hello exchange.
156    ///
157    /// Some vendors (e.g. Nokia SR OS) can silently stall during the hello
158    /// exchange — for example, if the client hello contains an invalid
159    /// namespace, the server waits indefinitely for a valid hello without
160    /// returning an error. Without a timeout, the connection hangs forever.
161    ///
162    /// When set, the hello exchange will fail with
163    /// [`TransportError::Timeout`](crate::error::TransportError::Timeout)
164    /// if the server does not complete the hello within this duration.
165    pub fn hello_timeout(mut self, timeout: Duration) -> Self {
166        self.config.hello_timeout = Some(timeout);
167        self
168    }
169
170    /// Set the timeout for individual RPC operations (send + wait for reply).
171    ///
172    /// When set, [`RpcFuture::response()`](crate::RpcFuture::response) will fail
173    /// with [`TransportError::Timeout`](crate::error::TransportError::Timeout)
174    /// if the server does not reply within this duration.
175    pub fn rpc_timeout(mut self, timeout: Duration) -> Self {
176        self.config.rpc_timeout = Some(timeout);
177        self
178    }
179
180    /// Set the SSH inactivity timeout (garbage-collect idle connections).
181    pub fn inactivity_timeout(mut self, timeout: Duration) -> Self {
182        self.config.ssh.inactivity_timeout = Some(timeout);
183        self
184    }
185
186    /// Set the SSH keepalive interval (detect dead peers).
187    pub fn keepalive_interval(mut self, interval: Duration) -> Self {
188        self.config.ssh.keepalive_interval = Some(interval);
189        self
190    }
191
192    /// Set the maximum number of missed keepalives before disconnect.
193    pub fn keepalive_max(mut self, max: usize) -> Self {
194        self.config.ssh.keepalive_max = Some(max);
195        self
196    }
197
198    /// Enable or disable `TCP_NODELAY` on the SSH socket.
199    pub fn nodelay(mut self, nodelay: bool) -> Self {
200        self.config.ssh.nodelay = Some(nodelay);
201        self
202    }
203
204    /// Set the SSH flow-control window size.
205    pub fn window_size(mut self, size: u32) -> Self {
206        self.config.ssh.window_size = Some(size);
207        self
208    }
209
210    /// Set the maximum SSH packet size.
211    pub fn maximum_packet_size(mut self, size: u32) -> Self {
212        self.config.ssh.maximum_packet_size = Some(size);
213        self
214    }
215
216    /// Set the host key verification mode.
217    pub fn host_key_verification(mut self, mode: HostKeyVerification) -> Self {
218        self.config.ssh.host_key_verification = mode;
219        self
220    }
221
222    /// Set a custom path to the `known_hosts` file.
223    pub fn known_hosts_path(mut self, path: impl Into<PathBuf>) -> Self {
224        self.config.ssh.known_hosts_path = Some(path.into());
225        self
226    }
227
228    /// Disable host key verification entirely (insecure — use only for testing).
229    pub fn danger_disable_host_key_verification(mut self) -> Self {
230        self.config.ssh.host_key_verification = HostKeyVerification::Disabled;
231        self
232    }
233
234    /// Route the connection through an SSH jumphost (bastion host).
235    ///
236    /// The library will first SSH into the jumphost, open a `direct-tcpip`
237    /// channel to the target device, then establish the NETCONF SSH session
238    /// over that tunnel. The jumphost inherits all SSH settings
239    /// (host key verification, known_hosts, keepalive, etc.) from this config.
240    pub fn jumphost(
241        mut self,
242        host: impl Into<String>,
243        port: u16,
244        username: impl Into<String>,
245        password: impl Into<String>,
246    ) -> Self {
247        self.config.jumphost = Some(JumphostConfig {
248            host: host.into(),
249            port,
250            username: username.into(),
251            password: password.into(),
252        });
253        self
254    }
255
256    pub fn build(self) -> Config {
257        self.config
258    }
259}