]>
Commit | Line | Data |
---|---|---|
904e9886 | 1 | use std::io::{BufRead}; |
f34d4401 | 2 | use std::iter::{Peekable, Iterator}; |
a9bb491e | 3 | use std::collections::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 | ||
f34d4401 DM |
140 | fn parse_to_eol(&mut self) -> Result<String, Error> { |
141 | let mut line = String::new(); | |
142 | loop { | |
143 | match self.next()? { | |
144 | (Token::Newline, _) => return Ok(line), | |
145 | (_, text) => { | |
146 | if !line.is_empty() { line.push(' '); } | |
147 | line.push_str(&text); | |
148 | } | |
149 | } | |
150 | } | |
151 | } | |
152 | ||
1d9a68c2 DM |
153 | fn parse_iface_list(&mut self) -> Result<Vec<String>, Error> { |
154 | let mut list = Vec::new(); | |
155 | ||
156 | loop { | |
157 | let (token, text) = self.next()?; | |
158 | match token { | |
159 | Token::Newline => break, | |
160 | Token::Text => { | |
161 | if &text != "none" { | |
162 | list.push(text); | |
163 | } | |
164 | } | |
165 | _ => bail!("unable to parse interface list - unexpected token '{:?}'", token), | |
166 | } | |
167 | } | |
168 | ||
169 | Ok(list) | |
170 | } | |
c38b4bb8 | 171 | |
5f60a58f DM |
172 | fn parse_iface_attributes( |
173 | &mut self, | |
174 | interface: &mut Interface, | |
175 | address_family_v4: bool, | |
176 | address_family_v6: bool, | |
177 | ) -> Result<(), Error> { | |
f34d4401 DM |
178 | |
179 | loop { | |
180 | match self.peek()? { | |
5f60a58f DM |
181 | Token::Attribute => { self.eat(Token::Attribute)?; }, |
182 | Token::Comment => { | |
183 | let comment = self.eat(Token::Comment)?; | |
184 | if !address_family_v4 && address_family_v6 { | |
8a6b86b8 DM |
185 | let mut comments = interface.comments_v6.take().unwrap_or(String::new()); |
186 | if !comments.is_empty() { comments.push('\n'); } | |
187 | comments.push_str(&comment); | |
188 | interface.comments_v6 = Some(comments); | |
5f60a58f | 189 | } else { |
8a6b86b8 DM |
190 | let mut comments = interface.comments_v4.take().unwrap_or(String::new()); |
191 | if !comments.is_empty() { comments.push('\n'); } | |
192 | comments.push_str(&comment); | |
193 | interface.comments_v4 = Some(comments); | |
5f60a58f DM |
194 | } |
195 | self.eat(Token::Newline)?; | |
196 | continue; | |
197 | } | |
f34d4401 | 198 | Token::Newline => break, |
5f60a58f DM |
199 | Token::EOF => break, |
200 | unexpected => bail!("unexpected token {:?} (expected iface attribute)", unexpected), | |
f34d4401 DM |
201 | } |
202 | ||
203 | match self.peek()? { | |
204 | Token::Address => self.parse_iface_address(interface)?, | |
205 | Token::Gateway => self.parse_iface_gateway(interface)?, | |
3fce3bc3 DM |
206 | Token::MTU => { |
207 | let mtu = self.parse_iface_mtu()?; | |
2c18efd9 | 208 | interface.mtu = Some(mtu); |
3fce3bc3 | 209 | } |
1d9a68c2 DM |
210 | Token::BridgePorts => { |
211 | self.eat(Token::BridgePorts)?; | |
212 | let ports = self.parse_iface_list()?; | |
213 | interface.bridge_ports = Some(ports); | |
c38b4bb8 | 214 | interface.set_interface_type(NetworkInterfaceType::Bridge)?; |
1d9a68c2 | 215 | } |
42fbe91a DM |
216 | Token::BondSlaves => { |
217 | self.eat(Token::BondSlaves)?; | |
218 | let slaves = self.parse_iface_list()?; | |
219 | interface.bond_slaves = Some(slaves); | |
c38b4bb8 | 220 | interface.set_interface_type(NetworkInterfaceType::Bond)?; |
42fbe91a | 221 | } |
8b57cd44 | 222 | Token::Netmask => bail!("netmask is deprecated and no longer supported"), |
5f60a58f DM |
223 | |
224 | _ => { // parse addon attributes | |
225 | let option = self.parse_to_eol()?; | |
226 | if !option.is_empty() { | |
227 | if !address_family_v4 && address_family_v6 { | |
228 | interface.options_v6.push(option); | |
229 | } else { | |
230 | interface.options_v4.push(option); | |
231 | } | |
232 | }; | |
233 | }, | |
f34d4401 DM |
234 | } |
235 | } | |
236 | ||
237 | Ok(()) | |
238 | } | |
239 | ||
240 | fn parse_iface(&mut self, config: &mut NetworkConfig) -> Result<(), Error> { | |
241 | self.eat(Token::Iface)?; | |
242 | let iface = self.next_text()?; | |
243 | ||
92310d58 DM |
244 | let mut address_family_v4 = false; |
245 | let mut address_family_v6 = false; | |
f34d4401 DM |
246 | let mut config_method = None; |
247 | ||
248 | loop { | |
249 | let (token, text) = self.next()?; | |
250 | match token { | |
251 | Token::Newline => break, | |
92310d58 DM |
252 | Token::Inet => address_family_v4 = true, |
253 | Token::Inet6 => address_family_v6 = true, | |
7e02d08c DM |
254 | Token::Loopback => config_method = Some(NetworkConfigMethod::Loopback), |
255 | Token::Static => config_method = Some(NetworkConfigMethod::Static), | |
256 | Token::Manual => config_method = Some(NetworkConfigMethod::Manual), | |
257 | Token::DHCP => config_method = Some(NetworkConfigMethod::DHCP), | |
f34d4401 DM |
258 | _ => bail!("unknown iface option {}", text), |
259 | } | |
260 | } | |
261 | ||
7e02d08c | 262 | let config_method = config_method.unwrap_or(NetworkConfigMethod::Static); |
92310d58 DM |
263 | |
264 | if !(address_family_v4 || address_family_v6) { | |
265 | address_family_v4 = true; | |
266 | address_family_v6 = true; | |
267 | } | |
f34d4401 DM |
268 | |
269 | if let Some(mut interface) = config.interfaces.get_mut(&iface) { | |
92310d58 DM |
270 | if address_family_v4 { |
271 | interface.set_method_v4(config_method)?; | |
272 | } | |
273 | if address_family_v6 { | |
274 | interface.set_method_v6(config_method)?; | |
f34d4401 | 275 | } |
92310d58 | 276 | |
5f60a58f | 277 | self.parse_iface_attributes(&mut interface, address_family_v4, address_family_v6)?; |
f34d4401 | 278 | } else { |
92310d58 DM |
279 | let mut interface = Interface::new(iface.clone()); |
280 | if address_family_v4 { | |
281 | interface.set_method_v4(config_method)?; | |
282 | } | |
283 | if address_family_v6 { | |
284 | interface.set_method_v6(config_method)?; | |
285 | } | |
f34d4401 | 286 | |
5f60a58f | 287 | self.parse_iface_attributes(&mut interface, address_family_v4, address_family_v6)?; |
f34d4401 DM |
288 | |
289 | config.interfaces.insert(interface.name.clone(), interface); | |
92310d58 | 290 | |
f34d4401 DM |
291 | config.order.push(NetworkOrderEntry::Iface(iface)); |
292 | } | |
293 | ||
294 | Ok(()) | |
295 | } | |
296 | ||
297 | pub fn parse_interfaces(&mut self) -> Result<NetworkConfig, Error> { | |
298 | self._parse_interfaces() | |
299 | .map_err(|err| format_err!("line {}: {}", self.line_nr, err)) | |
300 | } | |
301 | ||
302 | pub fn _parse_interfaces(&mut self) -> Result<NetworkConfig, Error> { | |
303 | let mut config = NetworkConfig::new(); | |
304 | ||
a9bb491e DM |
305 | let mut auto_flag: HashSet<String> = HashSet::new(); |
306 | ||
f34d4401 | 307 | loop { |
e2d940b9 | 308 | match self.peek()? { |
f34d4401 | 309 | Token::EOF => { |
a9bb491e | 310 | break; |
f34d4401 DM |
311 | } |
312 | Token::Newline => { | |
e2d940b9 | 313 | // skip empty lines |
f34d4401 | 314 | self.eat(Token::Newline)?; |
f34d4401 DM |
315 | } |
316 | Token::Comment => { | |
317 | let (_, text) = self.next()?; | |
f34d4401 DM |
318 | config.order.push(NetworkOrderEntry::Comment(text)); |
319 | self.eat(Token::Newline)?; | |
320 | } | |
321 | Token::Auto => { | |
a9bb491e | 322 | self.parse_auto(&mut auto_flag)?; |
f34d4401 DM |
323 | } |
324 | Token::Iface => { | |
325 | self.parse_iface(&mut config)?; | |
326 | } | |
327 | _ => { | |
328 | let option = self.parse_to_eol()?; | |
329 | if !option.is_empty() { | |
330 | config.order.push(NetworkOrderEntry::Option(option)); | |
331 | } | |
332 | } | |
333 | } | |
334 | } | |
a9bb491e DM |
335 | |
336 | for iface in auto_flag.iter() { | |
337 | if let Some(interface) = config.interfaces.get_mut(iface) { | |
f1026a5a | 338 | interface.auto = true; |
a9bb491e DM |
339 | } |
340 | } | |
341 | ||
3f129233 DM |
342 | let existing_interfaces = get_network_interfaces()?; |
343 | ||
344 | lazy_static!{ | |
02269f3d DM |
345 | static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap(); |
346 | static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap(); | |
347 | static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap(); | |
3f129233 DM |
348 | } |
349 | ||
350 | for (iface, active) in existing_interfaces.iter() { | |
3f129233 | 351 | if let Some(interface) = config.interfaces.get_mut(iface) { |
3f129233 | 352 | interface.active = *active; |
96d94786 DM |
353 | if interface.interface_type == NetworkInterfaceType::Unknown { |
354 | interface.interface_type = NetworkInterfaceType::Ethernet; | |
355 | } | |
356 | } else if PHYSICAL_NIC_REGEX.is_match(iface) { // also add all physical NICs | |
3f129233 | 357 | let mut interface = Interface::new(iface.clone()); |
7e02d08c | 358 | interface.set_method_v4(NetworkConfigMethod::Manual)?; |
02269f3d | 359 | interface.interface_type = NetworkInterfaceType::Ethernet; |
3f129233 DM |
360 | interface.active = *active; |
361 | config.interfaces.insert(interface.name.clone(), interface); | |
362 | config.order.push(NetworkOrderEntry::Iface(iface.to_string())); | |
363 | } | |
364 | } | |
365 | ||
02269f3d DM |
366 | for (name, interface) in config.interfaces.iter_mut() { |
367 | if interface.interface_type != NetworkInterfaceType::Unknown { continue; } | |
368 | if name == "lo" { | |
369 | interface.interface_type = NetworkInterfaceType::Loopback; | |
370 | continue; | |
371 | } | |
372 | if INTERFACE_ALIAS_REGEX.is_match(name) { | |
373 | interface.interface_type = NetworkInterfaceType::Alias; | |
374 | continue; | |
375 | } | |
376 | if VLAN_INTERFACE_REGEX.is_match(name) { | |
377 | interface.interface_type = NetworkInterfaceType::Vlan; | |
378 | continue; | |
379 | } | |
380 | if PHYSICAL_NIC_REGEX.is_match(name) { | |
96d94786 | 381 | interface.interface_type = NetworkInterfaceType::Vanished; |
02269f3d DM |
382 | continue; |
383 | } | |
384 | } | |
385 | ||
a9bb491e | 386 | Ok(config) |
f34d4401 DM |
387 | } |
388 | } |