Skip to main content

lager/nets/
router.rs

1//! Router nets (MikroTik RouterOS): network-condition control for
2//! connectivity testing — toggling interfaces, changing SSIDs/passwords,
3//! blocking traffic.
4//!
5//! The common read/control actions get typed methods; the long tail of
6//! RouterOS configuration (security profiles, DHCP tuning, bandwidth limits,
7//! firewall rules, access lists, raw REST calls) is reachable through the
8//! generic [`Router::command`] escape hatch with the same action names the
9//! box handler dispatches on.
10
11use serde_json::Value;
12
13use super::net_handle;
14pub use crate::wire::RouterSystemInfo;
15
16pub(crate) mod ops {
17    use std::time::Duration;
18
19    use serde_json::{json, Value};
20
21    use crate::error::Result;
22    use crate::wire::{net_command, unit, value_as, CommandResponse, Op, RouterSystemInfo, Timeout};
23
24    // Router saved-net records may carry the role string "router" or the
25    // legacy alias "mikrotik" (both dispatch to the same box handler). The
26    // box verifies a supplied role hint against the saved string exactly, so
27    // sending "router" would 404 on a net saved as "mikrotik". Omit the hint
28    // and let the box resolve the role from saved_nets.json.
29    const ROLE: Option<&str> = None;
30
31    /// RouterOS REST calls are quick, but reboot/reset actions and busy
32    /// routers can lag; give every action a comfortable budget.
33    fn budget() -> Timeout {
34        Timeout::After(Duration::from_secs(60))
35    }
36
37    fn value_raw(resp: CommandResponse) -> Result<Value> {
38        Ok(resp.value.unwrap_or(Value::Null))
39    }
40
41    pub(crate) fn connect(name: &str) -> Op<Value> {
42        Op {
43            req: net_command(name, ROLE, "connect", json!({}), budget()),
44            parse: value_raw,
45        }
46    }
47
48    pub(crate) fn system_info(name: &str) -> Op<RouterSystemInfo> {
49        Op {
50            req: net_command(name, ROLE, "system_info", json!({}), budget()),
51            parse: value_as::<RouterSystemInfo>,
52        }
53    }
54
55    pub(crate) fn interfaces(name: &str) -> Op<Value> {
56        Op {
57            req: net_command(name, ROLE, "interfaces", json!({}), budget()),
58            parse: value_raw,
59        }
60    }
61
62    pub(crate) fn wireless_interfaces(name: &str) -> Op<Value> {
63        Op {
64            req: net_command(name, ROLE, "wireless_interfaces", json!({}), budget()),
65            parse: value_raw,
66        }
67    }
68
69    pub(crate) fn wireless_clients(name: &str) -> Op<Value> {
70        Op {
71            req: net_command(name, ROLE, "wireless_clients", json!({}), budget()),
72            parse: value_raw,
73        }
74    }
75
76    pub(crate) fn dhcp_leases(name: &str) -> Op<Value> {
77        Op {
78            req: net_command(name, ROLE, "dhcp_leases", json!({}), budget()),
79            parse: value_raw,
80        }
81    }
82
83    pub(crate) fn reboot(name: &str) -> Op<()> {
84        Op {
85            req: net_command(name, ROLE, "reboot", json!({}), budget()),
86            parse: unit,
87        }
88    }
89
90    pub(crate) fn enable_interface(name: &str, interface: &str) -> Op<()> {
91        Op {
92            req: net_command(
93                name,
94                ROLE,
95                "enable_interface",
96                json!({ "interface": interface }),
97                budget(),
98            ),
99            parse: unit,
100        }
101    }
102
103    pub(crate) fn disable_interface(name: &str, interface: &str) -> Op<()> {
104        Op {
105            req: net_command(
106                name,
107                ROLE,
108                "disable_interface",
109                json!({ "interface": interface }),
110                budget(),
111            ),
112            parse: unit,
113        }
114    }
115
116    pub(crate) fn set_wireless_ssid(name: &str, interface: &str, ssid: &str) -> Op<Value> {
117        Op {
118            req: net_command(
119                name,
120                ROLE,
121                "set_wireless_ssid",
122                json!({ "interface": interface, "ssid": ssid }),
123                budget(),
124            ),
125            parse: value_raw,
126        }
127    }
128
129    pub(crate) fn block_internet(name: &str) -> Op<()> {
130        Op {
131            req: net_command(name, ROLE, "block_internet", json!({}), budget()),
132            parse: unit,
133        }
134    }
135
136    pub(crate) fn remove_firewall_rules(name: &str) -> Op<()> {
137        Op {
138            req: net_command(name, ROLE, "remove_firewall_rules", json!({}), budget()),
139            parse: unit,
140        }
141    }
142
143    pub(crate) fn command(name: &str, action: &str, params: Value) -> Op<Value> {
144        Op {
145            req: net_command(name, ROLE, action, params, budget()),
146            parse: value_raw,
147        }
148    }
149}
150
151net_handle! {
152    /// Handle for a router net.
153    sync: Router,
154    async: AsyncRouter,
155    methods: {
156        /// Verify connectivity; returns identity/version/board/uptime.
157        fn connect() -> Value = ops::connect;
158        /// System resource information.
159        fn system_info() -> RouterSystemInfo = ops::system_info;
160        /// All network interfaces (raw RouterOS records).
161        fn interfaces() -> Value = ops::interfaces;
162        /// Wireless interfaces (raw RouterOS records).
163        fn wireless_interfaces() -> Value = ops::wireless_interfaces;
164        /// Currently associated wireless clients.
165        fn wireless_clients() -> Value = ops::wireless_clients;
166        /// Active DHCP leases.
167        fn dhcp_leases() -> Value = ops::dhcp_leases;
168        /// Reboot the router.
169        fn reboot() -> () = ops::reboot;
170        /// Enable a network interface by name.
171        fn enable_interface(interface: &str) -> () = ops::enable_interface;
172        /// Disable a network interface by name.
173        fn disable_interface(interface: &str) -> () = ops::disable_interface;
174        /// Change the SSID broadcast by a wireless interface.
175        fn set_wireless_ssid(interface: &str, ssid: &str) -> Value = ops::set_wireless_ssid;
176        /// Drop all forwarded traffic (simulate internet outage).
177        fn block_internet() -> () = ops::block_internet;
178        /// Remove every Lager-added firewall rule (undo `block_*`).
179        fn remove_firewall_rules() -> () = ops::remove_firewall_rules;
180        /// Run any other router action by name with raw JSON params
181        /// (security profiles, DHCP config, bandwidth limits, firewall
182        /// rules, access lists, `run` for raw RouterOS REST paths, ...).
183        fn command(action: &str, params: Value) -> Value = ops::command;
184    }
185}