1use crate::api_objects::{
3 Error, MetaData, Result, SclName, SclObject, SeparationContext, TransitionError,
4};
5use serde::{Deserialize, Serialize};
6use std::net::Ipv4Addr;
7
8#[cfg(feature = "openapi")]
9use schemars::JsonSchema;
10
11#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
13#[cfg_attr(feature = "openapi", derive(JsonSchema))]
14#[serde(rename_all = "camelCase")]
15pub struct Router {
16 #[serde(flatten)]
17 pub metadata: MetaData,
18
19 pub separation_context: SclName,
21
22 pub internal_ip_netmask: Ipv4Addr,
26
27 pub internal_ip: Ipv4Addr,
32
33 pub external_ip: Ipv4Addr,
37
38 #[serde(default)]
43 pub status: RouterStatus,
44
45 #[serde(default)]
50 pub forwarded_tcp_ports: Vec<ForwardedPort>,
51
52 #[serde(default)]
57 pub forwarded_udp_ports: Vec<ForwardedPort>,
58}
59
60#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
61#[cfg_attr(feature = "openapi", derive(JsonSchema))]
62#[serde(rename_all = "camelCase")]
63pub struct ForwardedPort {
64 pub src_port: u16,
65 pub dst_ip: Ipv4Addr,
66 pub dst_port: u16,
67}
68
69#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
70#[cfg_attr(feature = "openapi", derive(JsonSchema))]
71#[serde(rename_all = "camelCase")]
72pub enum RouterStatus {
73 #[default]
75 Pending,
76
77 Assigned(AssignedDeviceNames),
90}
91
92#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
93#[cfg_attr(feature = "openapi", derive(JsonSchema))]
94#[serde(rename_all = "camelCase")]
95pub struct AssignedDeviceNames {
96 pub veth_name_in_sc_netns: String,
98
99 pub veth_name_in_gateway_netns: String,
101}
102
103impl SclObject for Router {
104 const PREFIX: &'static str = "/routers";
106
107 fn name(&self) -> &SclName {
108 &self.metadata.name
109 }
110
111 fn separation_context(&self) -> Option<&SclName> {
112 Some(&self.separation_context)
113 }
114
115 fn metadata(&self) -> &MetaData {
116 &self.metadata
117 }
118
119 fn metadata_mut(&mut self) -> &mut MetaData {
120 &mut self.metadata
121 }
122
123 fn referenced_db_keys(&self) -> Vec<String> {
124 vec![SeparationContext::db_key(
125 self.separation_context.as_str(),
126 None,
127 )]
128 }
129
130 fn validate_fields_before_create(&self) -> Result<()> {
131 self.metadata.validate_fields_before_create()?;
132
133 if self.status != RouterStatus::default() {
134 return Err(Error::IllegalInitialValue(
135 "Illegal initial router status!".to_string(),
136 ));
137 }
138
139 Ok(())
142 }
143
144 fn validate_fields_before_update(
145 current_db_state: &Self,
146 proposed_new_state: &Self,
147 ) -> Result<()> {
148 MetaData::validate_fields_before_regular_update(
149 ¤t_db_state.metadata,
150 &proposed_new_state.metadata,
151 )?;
152
153 validate_status_transition(¤t_db_state.status, &proposed_new_state.status)?;
154
155 if current_db_state.external_ip != proposed_new_state.external_ip
157 || current_db_state.internal_ip != proposed_new_state.internal_ip
158 || current_db_state.internal_ip_netmask != proposed_new_state.internal_ip_netmask
159 || current_db_state.forwarded_tcp_ports != proposed_new_state.forwarded_tcp_ports
160 || current_db_state.forwarded_udp_ports != proposed_new_state.forwarded_udp_ports
161 || current_db_state.separation_context != proposed_new_state.separation_context
162 {
163 Err(TransitionError::Other("Only status field may be changed!".to_string()).into())
164 } else {
165 Ok(())
166 }
167 }
168}
169
170fn validate_status_transition(old_state: &RouterStatus, new_state: &RouterStatus) -> Result<()> {
172 use RouterStatus::*;
174 let err = Err(TransitionError::Other("Invalid status transition!".to_string()).into());
175 match (old_state, new_state) {
176 (Assigned(a), Assigned(b)) if a != b => return err,
177 (Assigned(_), Pending) => return err,
178 _ => (),
179 };
180
181 if let Assigned(names) = new_state {
183 if names.veth_name_in_sc_netns.is_empty() || names.veth_name_in_gateway_netns.is_empty() {
184 return Err(TransitionError::Other(
185 "Assigned device names must not be empty".to_string(),
186 )
187 .into());
188 }
189 }
190
191 Ok(())
192}
193
194#[cfg(test)]
195mod test {
196 use crate::api_objects::router::{validate_status_transition, AssignedDeviceNames};
197 use crate::api_objects::test::{
198 detect_invalid_metadata_create_mutations, detect_invalid_metadata_update_mutations,
199 detect_invalid_update_mutations, parse_json_dir,
200 };
201 use crate::api_objects::{ForwardedPort, MetaData, Router, RouterStatus, SclName, SclObject};
202 use std::net::Ipv4Addr;
203 use std::path::Path;
204
205 #[test]
206 fn parse_sample_json() {
207 parse_json_dir::<Router>(Path::new("../test/sample_json/routers"));
208 }
209
210 fn example_router() -> Router {
211 Router {
212 external_ip: "127.0.0.1".parse().unwrap(),
213 internal_ip: "127.0.0.1".parse().unwrap(),
214 internal_ip_netmask: "255.255.255.0".parse().unwrap(),
215 metadata: MetaData::new(SclName::try_from("example").unwrap()),
216 separation_context: SclName::try_from("example").unwrap(),
217 status: Default::default(),
218 forwarded_tcp_ports: Vec::new(),
219 forwarded_udp_ports: Vec::new(),
220 }
221 }
222
223 #[test]
224 fn validate_fields_before_create() {
225 let mut router = example_router();
226 assert!(router.validate_fields_before_create().is_ok());
227 router.status = RouterStatus::Assigned(AssignedDeviceNames {
228 veth_name_in_sc_netns: "sc-veth".into(),
229 veth_name_in_gateway_netns: "gateway-veth".into(),
230 });
231 assert!(router.validate_fields_before_create().is_err());
232 }
233
234 #[test]
235 fn test_validate_status_transition() {
236 use RouterStatus::*;
237
238 let assigned_status = |a: &str, b: &str| {
239 Assigned(AssignedDeviceNames {
240 veth_name_in_sc_netns: a.to_string(),
241 veth_name_in_gateway_netns: b.to_string(),
242 })
243 };
244
245 let bad_transitions = vec![
246 (Pending, assigned_status("", "")),
247 (assigned_status("a", "a"), Pending),
248 (assigned_status("a", "a"), assigned_status("a", "b")),
249 (assigned_status("a", "a"), assigned_status("b", "a")),
250 ];
251
252 for (old_state, new_state) in bad_transitions {
253 assert!(validate_status_transition(&old_state, &new_state).is_err());
254 }
255
256 let good_transitions = vec![
257 (Pending, Pending),
258 (Pending, assigned_status("a", "a")),
259 (assigned_status("a", "a"), assigned_status("a", "a")),
260 ];
261
262 for (old_state, new_state) in good_transitions {
263 assert!(validate_status_transition(&old_state, &new_state).is_ok());
264 }
265 }
266
267 #[test]
268 fn validate_fields_before_update() {
269 let mut current = example_router();
270 let proposed = example_router();
271 assert!(Router::validate_fields_before_update(¤t, &proposed).is_ok());
272
273 current.status = RouterStatus::Assigned(AssignedDeviceNames {
274 veth_name_in_sc_netns: "sc-veth".into(),
275 veth_name_in_gateway_netns: "gateway-veth".into(),
276 });
277
278 #[allow(clippy::type_complexity)]
279 let invalid_mutations: Vec<Box<dyn Fn(&mut Router)>> = vec![
280 Box::new(|t| t.separation_context = SclName::try_from("different").unwrap()),
281 Box::new(|t| t.internal_ip = "127.0.0.2".parse().unwrap()),
282 Box::new(|t| t.external_ip = "127.0.0.2".parse().unwrap()),
283 Box::new(|t| t.internal_ip_netmask = "127.0.0.2".parse().unwrap()),
284 Box::new(|t| t.status = RouterStatus::Pending),
285 Box::new(|t| {
286 t.forwarded_tcp_ports = vec![ForwardedPort {
287 src_port: 8008,
288 dst_ip: Ipv4Addr::new(192, 168, 30, 10),
289 dst_port: 22,
290 }]
291 }),
292 Box::new(|t| {
293 t.forwarded_udp_ports = vec![ForwardedPort {
294 src_port: 7000,
295 dst_ip: Ipv4Addr::new(192, 168, 30, 10),
296 dst_port: 22,
297 }]
298 }),
299 ];
300
301 detect_invalid_update_mutations(¤t, &proposed, invalid_mutations);
302 }
303
304 #[test]
305 fn detect_invalid_metadata_mutations() {
306 let t = example_router();
307 detect_invalid_metadata_create_mutations(&t);
308 detect_invalid_metadata_update_mutations(&t);
309 }
310}