]>
Commit | Line | Data |
---|---|---|
2eefd9ae DM |
1 | use std::path::Path; |
2 | use std::process::Command; | |
f34d4401 | 3 | use std::collections::HashMap; |
1ec7f8a0 DM |
4 | |
5 | use anyhow::{Error, bail, format_err}; | |
f34d4401 | 6 | use lazy_static::lazy_static; |
1ec7f8a0 DM |
7 | use nix::sys::socket::{socket, AddressFamily, SockType, SockFlag}; |
8 | use nix::ioctl_read_bad; | |
9 | use regex::Regex; | |
f34d4401 | 10 | |
8b57cd44 DM |
11 | use proxmox::*; // for IP macros |
12 | ||
f34d4401 DM |
13 | pub static IPV4_REVERSE_MASK: &[&'static str] = &[ |
14 | "0.0.0.0", | |
15 | "128.0.0.0", | |
16 | "192.0.0.0", | |
17 | "224.0.0.0", | |
18 | "240.0.0.0", | |
19 | "248.0.0.0", | |
20 | "252.0.0.0", | |
21 | "254.0.0.0", | |
22 | "255.0.0.0", | |
23 | "255.128.0.0", | |
24 | "255.192.0.0", | |
25 | "255.224.0.0", | |
26 | "255.240.0.0", | |
27 | "255.248.0.0", | |
28 | "255.252.0.0", | |
29 | "255.254.0.0", | |
30 | "255.255.0.0", | |
31 | "255.255.128.0", | |
32 | "255.255.192.0", | |
33 | "255.255.224.0", | |
34 | "255.255.240.0", | |
35 | "255.255.248.0", | |
36 | "255.255.252.0", | |
37 | "255.255.254.0", | |
38 | "255.255.255.0", | |
39 | "255.255.255.128", | |
40 | "255.255.255.192", | |
41 | "255.255.255.224", | |
42 | "255.255.255.240", | |
43 | "255.255.255.248", | |
44 | "255.255.255.252", | |
45 | "255.255.255.254", | |
46 | "255.255.255.255", | |
47 | ]; | |
48 | ||
49 | lazy_static! { | |
50 | pub static ref IPV4_MASK_HASH_LOCALNET: HashMap<&'static str, u8> = { | |
51 | let mut map = HashMap::new(); | |
52 | for i in 8..32 { | |
53 | map.insert(IPV4_REVERSE_MASK[i], i as u8); | |
54 | } | |
55 | map | |
56 | }; | |
57 | } | |
1ec7f8a0 | 58 | |
8b57cd44 DM |
59 | pub fn parse_cidr(cidr: &str) -> Result<(String, u8, bool), Error> { |
60 | ||
61 | lazy_static! { | |
62 | pub static ref CIDR_V4_REGEX: Regex = Regex::new( | |
0c226bc1 | 63 | concat!(r"^(", IPV4RE!(), r")(?:/(\d{1,2}))$") |
8b57cd44 DM |
64 | ).unwrap(); |
65 | pub static ref CIDR_V6_REGEX: Regex = Regex::new( | |
0c226bc1 | 66 | concat!(r"^(", IPV6RE!(), r")(?:/(\d{1,3}))$") |
8b57cd44 DM |
67 | ).unwrap(); |
68 | } | |
69 | ||
70 | if let Some(caps) = CIDR_V4_REGEX.captures(&cidr) { | |
71 | let address = &caps[1]; | |
72 | let mask = &caps[2]; | |
73 | let mask = u8::from_str_radix(mask, 10) | |
74 | .map(|mask| { | |
75 | if !(mask > 0 && mask <= 32) { | |
76 | bail!("IPv4 mask '{}' is out of range (1..32).", mask); | |
77 | } | |
78 | Ok(mask) | |
79 | })?; | |
80 | return Ok((address.to_string(), mask.unwrap(), false)); | |
81 | } else if let Some(caps) = CIDR_V6_REGEX.captures(&cidr) { | |
82 | let address = &caps[1]; | |
83 | let mask = &caps[2]; | |
84 | let mask = u8::from_str_radix(mask, 10) | |
85 | .map(|mask| { | |
86 | if !(mask >= 1 && mask <= 128) { | |
87 | bail!("IPv6 mask '{}' is out of range (1..128).", mask); | |
88 | } | |
89 | Ok(mask) | |
90 | })?; | |
91 | return Ok((address.to_string(), mask.unwrap(), true)); | |
92 | } else { | |
93 | bail!("invalid address/mask '{}'", cidr); | |
94 | } | |
95 | } | |
96 | ||
1ec7f8a0 DM |
97 | pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> { |
98 | ||
99 | const PROC_NET_DEV: &str = "/proc/net/dev"; | |
100 | ||
101 | #[repr(C)] | |
102 | pub struct ifreq { | |
103 | ifr_name: [libc::c_uchar; libc::IFNAMSIZ], | |
104 | ifru_flags: libc::c_short, | |
105 | } | |
106 | ||
107 | ioctl_read_bad!(get_interface_flags, libc::SIOCGIFFLAGS, ifreq); | |
108 | ||
109 | lazy_static!{ | |
110 | static ref IFACE_LINE_REGEX: Regex = Regex::new(r"^\s*([^:\s]+):").unwrap(); | |
111 | } | |
112 | let raw = std::fs::read_to_string(PROC_NET_DEV) | |
113 | .map_err(|err| format_err!("unable to read {} - {}", PROC_NET_DEV, err))?; | |
114 | ||
115 | let lines = raw.lines(); | |
116 | ||
117 | let sock = socket(AddressFamily::Inet, SockType::Datagram, SockFlag::empty(), None) | |
118 | .or_else(|_| socket(AddressFamily::Inet6, SockType::Datagram, SockFlag::empty(), None))?; | |
119 | ||
120 | let mut interface_list = HashMap::new(); | |
121 | ||
122 | for line in lines { | |
123 | if let Some(cap) = IFACE_LINE_REGEX.captures(line) { | |
124 | let ifname = &cap[1]; | |
125 | ||
126 | let mut req = ifreq { ifr_name: *b"0000000000000000", ifru_flags: 0 }; | |
127 | for (i, b) in std::ffi::CString::new(ifname)?.as_bytes_with_nul().iter().enumerate() { | |
128 | if i < (libc::IFNAMSIZ-1) { req.ifr_name[i] = *b as libc::c_uchar; } | |
129 | } | |
130 | let res = unsafe { get_interface_flags(sock, &mut req)? }; | |
131 | if res != 0 { | |
132 | bail!("ioctl get_interface_flags for '{}' failed ({})", ifname, res); | |
133 | } | |
134 | let is_up = (req.ifru_flags & (libc::IFF_UP as libc::c_short)) != 0; | |
135 | interface_list.insert(ifname.to_string(), is_up); | |
136 | } | |
137 | } | |
138 | ||
139 | Ok(interface_list) | |
140 | } | |
2eefd9ae DM |
141 | |
142 | pub fn compute_file_diff(filename: &str, shadow: &str) -> Result<String, Error> { | |
143 | ||
144 | let output = Command::new("/usr/bin/diff") | |
145 | .arg("-b") | |
146 | .arg("-u") | |
147 | .arg(filename) | |
148 | .arg(shadow) | |
149 | .output() | |
150 | .map_err(|err| format_err!("failed to execute diff - {}", err))?; | |
151 | ||
152 | if !output.status.success() { | |
153 | match output.status.code() { | |
154 | Some(code) => { | |
155 | if code == 0 { return Ok(String::new()); } | |
156 | if code != 1 { | |
157 | let msg = String::from_utf8(output.stderr) | |
158 | .map(|m| if m.is_empty() { String::from("no error message") } else { m }) | |
159 | .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)")); | |
160 | ||
161 | bail!("diff failed with status code: {} - {}", code, msg); | |
162 | } | |
163 | } | |
164 | None => bail!("diff terminated by signal"), | |
165 | } | |
166 | } | |
167 | ||
168 | let diff = String::from_utf8(output.stdout)?; | |
169 | ||
170 | Ok(diff) | |
171 | } | |
172 | ||
173 | pub fn assert_ifupdown2_installed() -> Result<(), Error> { | |
174 | if !Path::new("/usr/share/ifupdown2").exists() { | |
175 | bail!("ifupdown2 is not installed."); | |
176 | } | |
177 | ||
178 | Ok(()) | |
179 | } | |
180 | ||
181 | pub fn network_reload() -> Result<(), Error> { | |
182 | ||
183 | let status = Command::new("/sbin/ifreload") | |
184 | .arg("-a") | |
185 | .status() | |
186 | .map_err(|err| format_err!("failed to execute ifreload: - {}", err))?; | |
187 | ||
188 | if !status.success() { | |
189 | match status.code() { | |
190 | Some(code) => bail!("ifreload failed with status code: {}", code), | |
191 | None => bail!("ifreload terminated by signal") | |
192 | } | |
193 | } | |
194 | ||
195 | Ok(()) | |
196 | } |