nix_netconfig/systemd/
mod.rs1extern crate regex;
2extern crate ini;
3
4use self::regex::Regex;
5use std::error::Error;
6use std::fs;
7use std::fs::File;
8use std::io::prelude::*;
9use std::io;
10use self::ini::Ini;
11use std::net::Ipv4Addr;
12use std::str::FromStr;
13use std::path::Path;
14
15pub struct IPv4Network {
18 pub ip: Ipv4Addr,
19 pub prefix: u8
20}
21
22pub type ValidationErrorString = &'static str;
24
25#[derive(Debug)]
26pub enum StaticIPError {
27 Io(io::Error),
28 Validation(ValidationErrorString),
29}
30
31pub trait NetworkConfigLoader {
32
33 fn new(path: &'static str) -> Self;
34
35 fn is_dhcp(&self) -> bool;
36
37 fn ip_address(&self) -> Option<IPv4Network>;
38
39 fn gateway(&self) -> Option<Ipv4Addr>;
40
41 fn get_settings_section(&self) -> Option<String>;
42
43 fn dns(&self) -> Vec<Ipv4Addr>;
44
45 fn enable_dhcp(&self) -> Result<(), io::Error>;
46
47 fn static_ip(&self, address: &str, gateway: &str) -> Result<(), StaticIPError>;
48
49}
50
51fn ip_from_match(address_match: Option<&str>, network_match: Option<&str> ) -> Option<IPv4Network> {
52
53 let network: u8 = network_match.unwrap_or("0").parse().unwrap_or(0);
54
55 match address_match {
56 Some(result) => {
57 match Ipv4Addr::from_str(result) {
58 Ok(address) => {
59 return Some(IPv4Network{ ip: address, prefix: network })
60 }
61 Err(_) => {
62 return None
63 }
64 }
65 },
66 None => None
67 }
68}
69
70fn read_to_string(path: &Path) -> Result<String,io::Error> {
71 let display = path.display();
72 let mut file = match File::open(path) {
73 Err(why) => return Err(why),
74 Ok(file) => file,
75 };
76
77 let mut s = String::new();
78
79 file.read_to_string(&mut s).ok().expect("Could not read file");
81
82 Ok(s)
83}
84
85pub struct SystemdNetworkConfig<'a> {
86 path: &'a Path
87}
88
89impl<'a> NetworkConfigLoader for SystemdNetworkConfig<'a> {
90
91 fn new(path: &'static str) -> SystemdNetworkConfig {
92 let default_path = "/etc/systemd/network/eth0.network";
93
94 if path == "" {
95 return SystemdNetworkConfig { path: Path::new(default_path) }
96 }
97
98 SystemdNetworkConfig { path: Path::new(path) }
99 }
100
101
102 fn is_dhcp(&self) -> bool {
103 let conf = Ini::load_from_file(self.path.to_str().expect("Could to parse path")).unwrap();
104
105 let section = conf.section(Some("Network".to_owned())).unwrap();
106
107 let dhcp = section.get("DHCP").map_or("", |s| &s[..]);
108
109 let re = Regex::new(r"(both|yes|ipv4|ipv6)").unwrap();
110 return re.is_match(dhcp)
111 }
112
113 fn get_settings_section(&self) -> Option<String> {
114 let content = read_to_string(self.path).unwrap_or("".to_string());
115 let re = Regex::new(r"(?P<settings_section>\[Network\][^\[\0]+)").unwrap();
116 return match re.captures(&content[..]) {
117 Some(matched) => {
118 let mut settings_section = String::new();
119 settings_section = settings_section + matched.name("settings_section").unwrap();
120 Some(settings_section)
121 },
122 None => None
123 };
124 }
125
126 fn ip_address(&self) -> Option<IPv4Network> {
127 let content = self.get_settings_section().unwrap_or(String::new());
128 let re = Regex::new(r"Address=(?P<address>[^/]+)\b/\b(?P<network>(?:3[0-2]|[12][0-9]|[1-9]))\b").unwrap();
130 return match re.captures(&content[..]) {
131 Some(matched) => ip_from_match(matched.name("address"), matched.name("network")),
132 None => { None }
133 };
134 }
135
136 fn gateway(&self) -> Option<Ipv4Addr> {
137 let content = self.get_settings_section().unwrap_or(String::new());
138 let re = Regex::new(r"Gateway=(?P<address>[\d.]+)").unwrap();
140 return match re.captures(&content[..]) {
141 Some(matched) => {
142 let ip_address = ip_from_match(matched.name("address"),Some("0"));
143 return Some(ip_address.unwrap().ip);
144 },
145 None => { None }
146 };
147 }
148
149 fn dns(&self) -> Vec<Ipv4Addr> {
150 let content = self.get_settings_section().unwrap_or(String::new());
151 let re = Regex::new(r"DNS=(?P<address>[\d.]+)").unwrap();
152
153 let dns_servers = re.captures_iter(&content[..]).filter_map(|cap| {
154
155 match ip_from_match(cap.name("address"),Some("0")) {
156 Some(ip_struct) => Some(ip_struct.ip),
157 None => None
158 }
159
160 }).collect::<Vec<Ipv4Addr>>();
161
162 dns_servers
163 }
164
165 fn enable_dhcp(&self) -> Result<(), io::Error> {
166 let mut conf = Ini::load_from_file(self.path.to_str().expect("Could to parse path")).unwrap();
167
168 conf.section_mut(Some("Network")).unwrap().insert("DHCP".into(), "both".into());
169 conf.delete_from(Some("Network".to_owned()), "Address");
170 conf.delete_from(Some("Network".to_owned()), "Gateway");
171
172 conf.write_to_file(self.path.to_str().expect("Could to parse path"))
173 }
174
175 fn static_ip(&self, address: &str, gateway: &str) -> Result<(), StaticIPError> {
176 let re_ip_with_cidr = Regex::new(r"^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:/\b(?P<network>(?:3[0-2]|[12][0-9]|[1-9]))\b)$").unwrap();
177 let re_ip = Regex::new(r"^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$").unwrap();
178
179 if !(re_ip_with_cidr.is_match(address) && re_ip.is_match(gateway)) {
180 return Err(StaticIPError::Validation("Validation failed"));
181 }
182
183 let mut conf = Ini::load_from_file(self.path.to_str().expect("Could to parse path")).unwrap();
185
186 conf.section_mut(Some("Network")).unwrap().insert("DHCP".into(), "no".into());
187 conf.section_mut(Some("Network")).unwrap().insert("Address".into(), address.into());
188 conf.section_mut(Some("Network")).unwrap().insert("Gateway".into(), gateway.into());
189
190 match conf.write_to_file(self.path.to_str().expect("Could to parse path")) {
191 Ok(()) => return Ok(()),
192 Err(err) => return Err(StaticIPError::Io(err))
193 }
194 }
195
196}
197
198struct UbuntuUpstartNetworkConfig<'a> {
199 path: &'a Path
200}
201
202impl<'a> NetworkConfigLoader for UbuntuUpstartNetworkConfig<'a> {
203
204 fn new(path: &'static str) -> UbuntuUpstartNetworkConfig {
205 let default_path = "/etc/network/interfaces";
206
207 if path == "" {
208 return UbuntuUpstartNetworkConfig { path: Path::new(default_path) }
209 }
210
211 UbuntuUpstartNetworkConfig { path: Path::new(path) }
212 }
213
214 fn is_dhcp(&self) -> bool {
215 unreachable!("Not implemented yet");
216 }
217
218 fn ip_address(&self) -> Option<IPv4Network> {
219 unreachable!("Not implemented yet");
220 }
221
222 fn gateway(&self) -> Option<Ipv4Addr> {
223 unreachable!("Not implemented yet");
224 }
225
226 fn get_settings_section(&self) -> Option<String> {
227 unreachable!("Not implemented yet");
228 }
229
230 fn dns(&self) -> Vec<Ipv4Addr> {
231 unreachable!("Not implemented yet");
232 }
233
234 fn enable_dhcp(&self) -> Result<(), io::Error>{
235 unreachable!("Not implemented yet");
236 }
237
238 fn static_ip(&self, address: &str, gateway: &str) -> Result<(), StaticIPError> {
239 unreachable!("Not implemented yet");
240 }
241
242}
243
244enum NetworkConfig<'a> {
245 Systemd(SystemdNetworkConfig<'a>),
246 Ubuntu(UbuntuUpstartNetworkConfig<'a>)
247}
248
249#[test]
250fn default_config_systemd() {
251 let config = SystemdNetworkConfig::new("");
252 assert!(config.path.to_str().unwrap() == "/etc/systemd/network/eth0.network");
253}
254
255#[test]
256fn default_config_ubuntu_upstart() {
257 let config = UbuntuUpstartNetworkConfig::new("");
258 assert!(config.path.to_str().unwrap() == "/etc/network/interfaces");
259}
260
261#[test]
262fn config_section_systemd() {
263 let config = SystemdNetworkConfig::new("./tests/eth0.network.dhcp");
264 let content = config.get_settings_section().unwrap_or(String::new());
265 assert!(content.contains("[Network]"));
266}
267
268#[test]
269fn no_config_section_systemd() {
270 let config = SystemdNetworkConfig::new("./tests/eth0.network.nonetwork");
271 let content = config.get_settings_section().unwrap_or(String::new());
272 assert!(!content.contains("[Network]"));
273}
274
275#[test]
276fn read_dhcp_config_systemd() {
277 let config = SystemdNetworkConfig::new("./tests/eth0.network.dhcp");
278 assert!(config.is_dhcp() == true);
279}
280
281#[test]
282fn read_non_dhcp_config_systemd() {
283 let config = SystemdNetworkConfig::new("./tests/eth0.network.static");
284 assert!(config.is_dhcp() == false);
285}
286
287#[test]
288fn read_static_config_systemd() {
289 let config = SystemdNetworkConfig::new("./tests/eth0.network.static");
290 let comparitor_ip = Ipv4Addr::from_str("192.168.1.3").unwrap();
291 let ip_address = config.ip_address().expect("No ip address found");
292 assert!(ip_address.ip == comparitor_ip);
293 assert!(ip_address.prefix == 24);
294
295 let comparitor_gw = Ipv4Addr::from_str("192.168.1.1").unwrap();
296 assert!(config.gateway().expect("No gateway address found") == comparitor_gw);
297}
298
299#[test]
300fn read_dns_config_systemd() {
301 let config = SystemdNetworkConfig::new("./tests/eth0.network.static_with_dns");
302 let dns_servers = config.dns();
303 assert!(dns_servers.len() == 4);
304}
305
306#[test]
307fn read_no_dns_config_systemd() {
308 let config = SystemdNetworkConfig::new("./tests/eth0.network.static");
309 let dns_servers = config.dns();
310 assert!(dns_servers.len() == 0);
311}
312
313#[test]
314fn enable_dhcp_systemd() {
315 fs::copy("./tests/eth0.network.static", "./tests/eth0.network.static.test").unwrap();
316
317 let config = SystemdNetworkConfig::new("./tests/eth0.network.static.test");
318 assert!(config.is_dhcp() == false);
319 config.enable_dhcp().unwrap();
320 assert!(config.is_dhcp() == true);
321}
322
323#[test]
324fn static_ip_systemd() {
325 fs::copy("./tests/eth0.network.dhcp", "./tests/eth0.network.dhcp.test").unwrap();
326
327 let config = SystemdNetworkConfig::new("./tests/eth0.network.dhcp.test");
328 assert!(config.is_dhcp() == true);
329 config.static_ip("192.168.1.3/24","192.168.1.1").unwrap();
330 assert!(config.is_dhcp() == false);
331
332 let comparitor_ip = Ipv4Addr::from_str("192.168.1.3").unwrap();
333 let ip_address = config.ip_address().expect("No ip address found");
334 assert!(ip_address.ip == comparitor_ip);
335 assert!(ip_address.prefix == 24);
336
337 let comparitor_gw = Ipv4Addr::from_str("192.168.1.1").unwrap();
338 assert!(config.gateway().expect("No gateway address found") == comparitor_gw);
339}
340
341#[test]
342fn static_ip_invalid_values_systemd() {
343 fs::copy("./tests/eth0.network.dhcp", "./tests/eth0.network.dhcp.validation").unwrap();
344
345 let config = SystemdNetworkConfig::new("./tests/eth0.network.dhcp.validation");
346 assert!(config.is_dhcp() == true);
347 assert!(config.static_ip("192.broken.1.3/24","192.168.1.1").is_err());
348 assert!(config.static_ip("192.300.1.3/24","192.168.1.1").is_err());
349 assert!(config.static_ip("192.300.1.3/24","192.168.1000.1").is_err());
350 assert!(config.static_ip("192.300.1.3/33","192.168.1.1").is_err());
351 assert!(config.static_ip("192.168.1.3/33","192.168.1.1").is_err());
352 assert!(config.static_ip("192.168.1.3","192.168.1.1").is_err());
353 assert!(config.static_ip("192.168.1.3/31","192.168.1.1").is_ok());
354
355}