nix_netconfig/systemd/
mod.rs

1extern 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
15//FIXME: Handle unwrap() - try not to use in library
16
17pub struct IPv4Network {
18    pub ip: Ipv4Addr,
19    pub prefix: u8
20}
21
22//FIXME: This is probably wrong, but at least its descriptive
23pub 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    //FIXME: Add the name of the file in the expect clause
80    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        //FIXME: This regex will only work on the first address
129        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        //FIXME: This regex will only work on the first address
139        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        //FIXME: Validate the inputs
184        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}