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