]>
Commit | Line | Data |
---|---|---|
904e9886 | 1 | use std::io::{BufRead}; |
f34d4401 | 2 | use std::iter::{Peekable, Iterator}; |
1ca540a6 | 3 | use std::collections::{HashMap, HashSet}; |
f34d4401 DM |
4 | |
5 | use anyhow::{Error, bail, format_err}; | |
6 | use lazy_static::lazy_static; | |
7 | use regex::Regex; | |
8 | ||
f34d4401 DM |
9 | use super::helper::*; |
10 | use super::lexer::*; | |
11 | ||
02269f3d | 12 | use super::{NetworkConfig, NetworkOrderEntry, Interface, NetworkConfigMethod, NetworkInterfaceType}; |
f34d4401 | 13 | |
904e9886 DM |
14 | pub struct NetworkParser<R: BufRead> { |
15 | input: Peekable<Lexer<R>>, | |
f34d4401 DM |
16 | line_nr: usize, |
17 | } | |
18 | ||
904e9886 | 19 | impl <R: BufRead> NetworkParser<R> { |
f34d4401 | 20 | |
904e9886 | 21 | pub fn new(reader: R) -> Self { |
f34d4401 DM |
22 | let input = Lexer::new(reader).peekable(); |
23 | Self { input, line_nr: 1 } | |
24 | } | |
25 | ||
26 | fn peek(&mut self) -> Result<Token, Error> { | |
27 | match self.input.peek() { | |
28 | Some(Err(err)) => { | |
29 | bail!("input error - {}", err); | |
30 | } | |
31 | Some(Ok((token, _))) => { | |
32 | return Ok(*token); | |
33 | } | |
34 | None => { | |
35 | bail!("got unexpected end of stream (inside peek)"); | |
36 | } | |
37 | } | |
38 | } | |
39 | ||
40 | fn next(&mut self) -> Result<(Token, String), Error> { | |
41 | match self.input.next() { | |
42 | Some(Err(err)) => { | |
43 | bail!("input error - {}", err); | |
44 | } | |
45 | Some(Ok((token, text))) => { | |
46 | if token == Token::Newline { self.line_nr += 1; } | |
47 | return Ok((token, text)); | |
48 | } | |
49 | None => { | |
50 | bail!("got unexpected end of stream (inside peek)"); | |
51 | } | |
52 | } | |
53 | } | |
54 | ||
55 | fn next_text(&mut self) -> Result<String, Error> { | |
56 | match self.next()? { | |
57 | (Token::Text, text) => Ok(text), | |
58 | (unexpected, _) => bail!("got unexpected token {:?} (expecting Text)", unexpected), | |
59 | } | |
60 | } | |
61 | ||
5f60a58f DM |
62 | fn eat(&mut self, expected: Token) -> Result<String, Error> { |
63 | let (next, text) = self.next()?; | |
f34d4401 DM |
64 | if next != expected { |
65 | bail!("expected {:?}, got {:?}", expected, next); | |
66 | } | |
5f60a58f | 67 | Ok(text) |
f34d4401 DM |
68 | } |
69 | ||
a9bb491e | 70 | fn parse_auto(&mut self, auto_flag: &mut HashSet<String>) -> Result<(), Error> { |
f34d4401 DM |
71 | self.eat(Token::Auto)?; |
72 | ||
a9bb491e DM |
73 | loop { |
74 | match self.next()? { | |
75 | (Token::Text, iface) => { | |
e2d940b9 | 76 | auto_flag.insert(iface.to_string()); |
a9bb491e DM |
77 | } |
78 | (Token::Newline, _) => break, | |
79 | unexpected => { | |
80 | bail!("expected {:?}, got {:?}", Token::Text, unexpected); | |
81 | } | |
82 | } | |
f34d4401 | 83 | |
a9bb491e | 84 | } |
f34d4401 DM |
85 | |
86 | Ok(()) | |
87 | } | |
88 | ||
89 | fn parse_iface_address(&mut self, interface: &mut Interface) -> Result<(), Error> { | |
90 | self.eat(Token::Address)?; | |
8b57cd44 | 91 | let cidr = self.next_text()?; |
f34d4401 | 92 | |
8b57cd44 DM |
93 | let (_address, _mask, ipv6) = parse_cidr(&cidr)?; |
94 | if ipv6 { | |
95 | interface.set_cidr_v6(cidr)?; | |
f34d4401 | 96 | } else { |
8b57cd44 | 97 | interface.set_cidr_v4(cidr)?; |
f34d4401 DM |
98 | } |
99 | ||
100 | self.eat(Token::Newline)?; | |
101 | ||
102 | Ok(()) | |
103 | } | |
104 | ||
105 | fn parse_iface_gateway(&mut self, interface: &mut Interface) -> Result<(), Error> { | |
106 | self.eat(Token::Gateway)?; | |
107 | let gateway = self.next_text()?; | |
108 | ||
109 | if proxmox::tools::common_regex::IP_REGEX.is_match(&gateway) { | |
110 | if gateway.contains(':') { | |
111 | interface.set_gateway_v6(gateway)?; | |
112 | } else { | |
113 | interface.set_gateway_v4(gateway)?; | |
114 | } | |
115 | } else { | |
116 | bail!("unable to parse gateway address"); | |
117 | } | |
118 | ||
119 | self.eat(Token::Newline)?; | |
120 | ||
121 | Ok(()) | |
122 | } | |
123 | ||
3fce3bc3 DM |
124 | fn parse_iface_mtu(&mut self) -> Result<u64, Error> { |
125 | self.eat(Token::MTU)?; | |
126 | ||
127 | let mtu = self.next_text()?; | |
128 | let mtu = match u64::from_str_radix(&mtu, 10) { | |
129 | Ok(mtu) => mtu, | |
130 | Err(err) => { | |
131 | bail!("unable to parse mtu value '{}' - {}", mtu, err); | |
132 | } | |
133 | }; | |
134 | ||
135 | self.eat(Token::Newline)?; | |
136 | ||
137 | Ok(mtu) | |
138 | } | |
139 | ||
7b22acd0 DM |
140 | fn parse_yes_no(&mut self) -> Result<bool, Error> { |
141 | let text = self.next_text()?; | |
142 | let value = match text.to_lowercase().as_str() { | |
143 | "yes" => true, | |
144 | "no" => false, | |
145 | _ => { | |
146 | bail!("unable to bool value '{}' - (expected yes/no)", text); | |
147 | } | |
148 | }; | |
149 | ||
150 | self.eat(Token::Newline)?; | |
151 | ||
152 | Ok(value) | |
153 | } | |
154 | ||
f34d4401 DM |
155 | fn parse_to_eol(&mut self) -> Result<String, Error> { |
156 | let mut line = String::new(); | |
157 | loop { | |
158 | match self.next()? { | |
159 | (Token::Newline, _) => return Ok(line), | |
160 | (_, text) => { | |
161 | if !line.is_empty() { line.push(' '); } | |
162 | line.push_str(&text); | |
163 | } | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
1d9a68c2 DM |
168 | fn parse_iface_list(&mut self) -> Result<Vec<String>, Error> { |
169 | let mut list = Vec::new(); | |
170 | ||
171 | loop { | |
172 | let (token, text) = self.next()?; | |
173 | match token { | |
174 | Token::Newline => break, | |
175 | Token::Text => { | |
176 | if &text != "none" { | |
177 | list.push(text); | |
178 | } | |
179 | } | |
180 | _ => bail!("unable to parse interface list - unexpected token '{:?}'", token), | |
181 | } | |
182 | } | |
183 | ||
184 | Ok(list) | |
185 | } | |
c38b4bb8 | 186 | |
5f60a58f DM |
187 | fn parse_iface_attributes( |
188 | &mut self, | |
189 | interface: &mut Interface, | |
190 | address_family_v4: bool, | |
191 | address_family_v6: bool, | |
192 | ) -> Result<(), Error> { | |
f34d4401 DM |
193 | |
194 | loop { | |
195 | match self.peek()? { | |
5f60a58f DM |
196 | Token::Attribute => { self.eat(Token::Attribute)?; }, |
197 | Token::Comment => { | |
198 | let comment = self.eat(Token::Comment)?; | |
199 | if !address_family_v4 && address_family_v6 { | |
7b22acd0 | 200 | let mut comments = interface.comments6.take().unwrap_or(String::new()); |
8a6b86b8 DM |
201 | if !comments.is_empty() { comments.push('\n'); } |
202 | comments.push_str(&comment); | |
7b22acd0 | 203 | interface.comments6 = Some(comments); |
5f60a58f | 204 | } else { |
7b22acd0 | 205 | let mut comments = interface.comments.take().unwrap_or(String::new()); |
8a6b86b8 DM |
206 | if !comments.is_empty() { comments.push('\n'); } |
207 | comments.push_str(&comment); | |
7b22acd0 | 208 | interface.comments = Some(comments); |
5f60a58f DM |
209 | } |
210 | self.eat(Token::Newline)?; | |
211 | continue; | |
212 | } | |
f34d4401 | 213 | Token::Newline => break, |
5f60a58f DM |
214 | Token::EOF => break, |
215 | unexpected => bail!("unexpected token {:?} (expected iface attribute)", unexpected), | |
f34d4401 DM |
216 | } |
217 | ||
218 | match self.peek()? { | |
219 | Token::Address => self.parse_iface_address(interface)?, | |
220 | Token::Gateway => self.parse_iface_gateway(interface)?, | |
3fce3bc3 DM |
221 | Token::MTU => { |
222 | let mtu = self.parse_iface_mtu()?; | |
2c18efd9 | 223 | interface.mtu = Some(mtu); |
3fce3bc3 | 224 | } |
7b22acd0 DM |
225 | Token::BridgeVlanAware => { |
226 | self.eat(Token::BridgeVlanAware)?; | |
227 | let bridge_vlan_aware = self.parse_yes_no()?; | |
228 | interface.bridge_vlan_aware = Some(bridge_vlan_aware); | |
229 | } | |
1d9a68c2 DM |
230 | Token::BridgePorts => { |
231 | self.eat(Token::BridgePorts)?; | |
232 | let ports = self.parse_iface_list()?; | |
233 | interface.bridge_ports = Some(ports); | |
c38b4bb8 | 234 | interface.set_interface_type(NetworkInterfaceType::Bridge)?; |
1d9a68c2 | 235 | } |
42fbe91a DM |
236 | Token::BondSlaves => { |
237 | self.eat(Token::BondSlaves)?; | |
238 | let slaves = self.parse_iface_list()?; | |
239 | interface.bond_slaves = Some(slaves); | |
c38b4bb8 | 240 | interface.set_interface_type(NetworkInterfaceType::Bond)?; |
42fbe91a | 241 | } |
8b57cd44 | 242 | Token::Netmask => bail!("netmask is deprecated and no longer supported"), |
5f60a58f DM |
243 | |
244 | _ => { // parse addon attributes | |
245 | let option = self.parse_to_eol()?; | |
246 | if !option.is_empty() { | |
247 | if !address_family_v4 && address_family_v6 { | |
7b22acd0 | 248 | interface.options6.push(option); |
5f60a58f | 249 | } else { |
7b22acd0 | 250 | interface.options.push(option); |
5f60a58f DM |
251 | } |
252 | }; | |
253 | }, | |
f34d4401 DM |
254 | } |
255 | } | |
256 | ||
257 | Ok(()) | |
258 | } | |
259 | ||
260 | fn parse_iface(&mut self, config: &mut NetworkConfig) -> Result<(), Error> { | |
261 | self.eat(Token::Iface)?; | |
262 | let iface = self.next_text()?; | |
263 | ||
92310d58 DM |
264 | let mut address_family_v4 = false; |
265 | let mut address_family_v6 = false; | |
f34d4401 DM |
266 | let mut config_method = None; |
267 | ||
268 | loop { | |
269 | let (token, text) = self.next()?; | |
270 | match token { | |
271 | Token::Newline => break, | |
92310d58 DM |
272 | Token::Inet => address_family_v4 = true, |
273 | Token::Inet6 => address_family_v6 = true, | |
7e02d08c DM |
274 | Token::Loopback => config_method = Some(NetworkConfigMethod::Loopback), |
275 | Token::Static => config_method = Some(NetworkConfigMethod::Static), | |
276 | Token::Manual => config_method = Some(NetworkConfigMethod::Manual), | |
277 | Token::DHCP => config_method = Some(NetworkConfigMethod::DHCP), | |
f34d4401 DM |
278 | _ => bail!("unknown iface option {}", text), |
279 | } | |
280 | } | |
281 | ||
7e02d08c | 282 | let config_method = config_method.unwrap_or(NetworkConfigMethod::Static); |
92310d58 DM |
283 | |
284 | if !(address_family_v4 || address_family_v6) { | |
285 | address_family_v4 = true; | |
286 | address_family_v6 = true; | |
287 | } | |
f34d4401 DM |
288 | |
289 | if let Some(mut interface) = config.interfaces.get_mut(&iface) { | |
92310d58 DM |
290 | if address_family_v4 { |
291 | interface.set_method_v4(config_method)?; | |
292 | } | |
293 | if address_family_v6 { | |
294 | interface.set_method_v6(config_method)?; | |
f34d4401 | 295 | } |
92310d58 | 296 | |
5f60a58f | 297 | self.parse_iface_attributes(&mut interface, address_family_v4, address_family_v6)?; |
f34d4401 | 298 | } else { |
92310d58 DM |
299 | let mut interface = Interface::new(iface.clone()); |
300 | if address_family_v4 { | |
301 | interface.set_method_v4(config_method)?; | |
302 | } | |
303 | if address_family_v6 { | |
304 | interface.set_method_v6(config_method)?; | |
305 | } | |
f34d4401 | 306 | |
5f60a58f | 307 | self.parse_iface_attributes(&mut interface, address_family_v4, address_family_v6)?; |
f34d4401 DM |
308 | |
309 | config.interfaces.insert(interface.name.clone(), interface); | |
92310d58 | 310 | |
f34d4401 DM |
311 | config.order.push(NetworkOrderEntry::Iface(iface)); |
312 | } | |
313 | ||
314 | Ok(()) | |
315 | } | |
316 | ||
1ca540a6 DM |
317 | pub fn parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { |
318 | self._parse_interfaces(existing_interfaces) | |
f34d4401 DM |
319 | .map_err(|err| format_err!("line {}: {}", self.line_nr, err)) |
320 | } | |
321 | ||
1ca540a6 | 322 | pub fn _parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { |
f34d4401 DM |
323 | let mut config = NetworkConfig::new(); |
324 | ||
a9bb491e DM |
325 | let mut auto_flag: HashSet<String> = HashSet::new(); |
326 | ||
f34d4401 | 327 | loop { |
e2d940b9 | 328 | match self.peek()? { |
f34d4401 | 329 | Token::EOF => { |
a9bb491e | 330 | break; |
f34d4401 DM |
331 | } |
332 | Token::Newline => { | |
e2d940b9 | 333 | // skip empty lines |
f34d4401 | 334 | self.eat(Token::Newline)?; |
f34d4401 DM |
335 | } |
336 | Token::Comment => { | |
337 | let (_, text) = self.next()?; | |
f34d4401 DM |
338 | config.order.push(NetworkOrderEntry::Comment(text)); |
339 | self.eat(Token::Newline)?; | |
340 | } | |
341 | Token::Auto => { | |
a9bb491e | 342 | self.parse_auto(&mut auto_flag)?; |
f34d4401 DM |
343 | } |
344 | Token::Iface => { | |
345 | self.parse_iface(&mut config)?; | |
346 | } | |
347 | _ => { | |
348 | let option = self.parse_to_eol()?; | |
349 | if !option.is_empty() { | |
350 | config.order.push(NetworkOrderEntry::Option(option)); | |
351 | } | |
352 | } | |
353 | } | |
354 | } | |
a9bb491e DM |
355 | |
356 | for iface in auto_flag.iter() { | |
357 | if let Some(interface) = config.interfaces.get_mut(iface) { | |
7b22acd0 | 358 | interface.autostart = true; |
a9bb491e DM |
359 | } |
360 | } | |
361 | ||
3f129233 | 362 | lazy_static!{ |
02269f3d DM |
363 | static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap(); |
364 | static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap(); | |
365 | static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap(); | |
3f129233 DM |
366 | } |
367 | ||
1ca540a6 DM |
368 | if let Some(existing_interfaces) = existing_interfaces { |
369 | for (iface, active) in existing_interfaces.iter() { | |
370 | if let Some(interface) = config.interfaces.get_mut(iface) { | |
371 | interface.active = *active; | |
7b22acd0 DM |
372 | if interface.interface_type == NetworkInterfaceType::Unknown && PHYSICAL_NIC_REGEX.is_match(iface) { |
373 | interface.interface_type = NetworkInterfaceType::Eth; | |
1ca540a6 DM |
374 | } |
375 | } else if PHYSICAL_NIC_REGEX.is_match(iface) { // also add all physical NICs | |
376 | let mut interface = Interface::new(iface.clone()); | |
377 | interface.set_method_v4(NetworkConfigMethod::Manual)?; | |
7b22acd0 | 378 | interface.interface_type = NetworkInterfaceType::Eth; |
1ca540a6 DM |
379 | interface.active = *active; |
380 | config.interfaces.insert(interface.name.clone(), interface); | |
381 | config.order.push(NetworkOrderEntry::Iface(iface.to_string())); | |
96d94786 | 382 | } |
3f129233 DM |
383 | } |
384 | } | |
385 | ||
02269f3d DM |
386 | for (name, interface) in config.interfaces.iter_mut() { |
387 | if interface.interface_type != NetworkInterfaceType::Unknown { continue; } | |
388 | if name == "lo" { | |
389 | interface.interface_type = NetworkInterfaceType::Loopback; | |
390 | continue; | |
391 | } | |
392 | if INTERFACE_ALIAS_REGEX.is_match(name) { | |
393 | interface.interface_type = NetworkInterfaceType::Alias; | |
394 | continue; | |
395 | } | |
396 | if VLAN_INTERFACE_REGEX.is_match(name) { | |
397 | interface.interface_type = NetworkInterfaceType::Vlan; | |
398 | continue; | |
399 | } | |
400 | if PHYSICAL_NIC_REGEX.is_match(name) { | |
7b22acd0 | 401 | interface.interface_type = NetworkInterfaceType::Eth; |
02269f3d DM |
402 | continue; |
403 | } | |
404 | } | |
405 | ||
1ca540a6 DM |
406 | if config.interfaces.get("lo").is_none() { |
407 | let mut interface = Interface::new(String::from("lo")); | |
408 | interface.set_method_v4(NetworkConfigMethod::Loopback)?; | |
409 | interface.interface_type = NetworkInterfaceType::Loopback; | |
7b22acd0 | 410 | interface.autostart = true; |
1ca540a6 DM |
411 | config.interfaces.insert(interface.name.clone(), interface); |
412 | ||
413 | // Note: insert 'lo' as first interface after initial comments | |
414 | let mut new_order = Vec::new(); | |
415 | let mut added_lo = false; | |
416 | for entry in config.order { | |
417 | if added_lo { new_order.push(entry); continue; } // copy the rest | |
418 | match entry { | |
419 | NetworkOrderEntry::Comment(_) => { | |
420 | new_order.push(entry); | |
421 | } | |
422 | _ => { | |
423 | new_order.push(NetworkOrderEntry::Iface(String::from("lo"))); | |
424 | added_lo = true; | |
425 | new_order.push(entry); | |
426 | } | |
427 | } | |
428 | } | |
429 | config.order = new_order; | |
430 | } | |
431 | ||
a9bb491e | 432 | Ok(config) |
f34d4401 DM |
433 | } |
434 | } |