mavtables  0.2.1
MAVLink router and firewall.
ConfigParser.cpp
Go to the documentation of this file.
1 // MAVLink router and firewall.
2 // Copyright (C) 2018 Michael R. Shannon <mrshannon.aerospace@gmail.com>
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 
17 
18 #include <map>
19 #include <memory>
20 #include <ostream>
21 #include <stdexcept>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 #include "Accept.hpp"
27 #include "App.hpp"
28 #include "Call.hpp"
29 #include "Chain.hpp"
30 #include "ConfigParser.hpp"
31 #include "config_grammar.hpp"
32 #include "ConnectionFactory.hpp"
33 #include "ConnectionPool.hpp"
34 #include "Filter.hpp"
35 #include "GoTo.hpp"
36 #include "If.hpp"
37 #include "IPAddress.hpp"
38 #include "parse_tree.hpp"
39 #include "Reject.hpp"
40 #include "SerialInterface.hpp"
41 #include "SerialPort.hpp"
42 #include "UDPInterface.hpp"
43 #include "UnixSerialPort.hpp"
44 #include "UnixUDPSocket.hpp"
45 #include "utility.hpp"
46 
47 
48 /** Construct a map of non default chains.
49  *
50  * \relates ConfigParser
51  * \param root Root of configuration AST.
52  * \returns Map of chain names to chains.
53  */
54 std::map<std::string, std::shared_ptr<Chain>> init_chains(
55  const config::parse_tree::node &root)
56 {
57  std::map<std::string, std::shared_ptr<Chain>> chains;
58 
59  // Loop through top AST nodes creating chains for.
60  for (auto &node : root.children)
61  {
62  if (node->name() == "config::chain")
63  {
64  if (node->has_content() && node->content() != "default")
65  {
66  chains[node->content()]
67  = std::make_shared<Chain>(node->content());
68  }
69  }
70  }
71 
72  return chains;
73 }
74 
75 
76 /** Construct a \ref Rule with action from AST, priority, and condition.
77  *
78  * \relates ConfigParser
79  * \param root AST action node.
80  * \param priority The priority to use when constructing the action. No
81  * priority if {}. If the AST node is a reject action the priority will be
82  * ignored.
83  * \param condition The condition to use when constructing the action.
84  * \param chains Map of chain names to chains for call and goto actions.
85  * \returns The action (or rule) parsed from the given AST node, priority, and
86  * condition.
87  * \throws std::invalid_argument if the action attempts to `call` or `goto` the
88  * default chain.
89  * \throws std::runtime_error if the action is not one of `accept`, `reject`,
90  * `call`, or `goto`.
91  */
92 std::unique_ptr<Rule> parse_action(
93  const config::parse_tree::node &root,
94  std::optional<int> priority,
95  std::optional<If> condition,
96  const std::map<std::string, std::shared_ptr<Chain>> &chains)
97 {
98  // Parse accept rule.
99  if (root.name() == "config::accept")
100  {
101  if (priority)
102  {
103  return std::make_unique<Accept>(
104  priority.value(), std::move(condition));
105  }
106  else
107  {
108  return std::make_unique<Accept>(std::move(condition));
109  }
110  }
111  // Parse reject rule.
112  else if (root.name() == "config::reject")
113  {
114  return std::make_unique<Reject>(std::move(condition));
115  }
116  // Parse call rule.
117  else if (root.name() == "config::call")
118  {
119  if (root.content() == "default")
120  {
121  throw std::invalid_argument("cannot 'call' the default chain");
122  }
123 
124  if (priority)
125  {
126  return std::make_unique<Call>(
127  chains.at(root.content()),
128  priority.value(),
129  std::move(condition));
130  }
131  else
132  {
133  return std::make_unique<Call>(
134  chains.at(root.content()),
135  std::move(condition));
136  }
137  }
138  // Parse call goto.
139  else if (root.name() == "config::goto_")
140  {
141  if (root.content() == "default")
142  {
143  throw std::invalid_argument("cannot 'goto' the default chain");
144  }
145 
146  if (priority)
147  {
148  return std::make_unique<GoTo>(
149  chains.at(root.content()),
150  priority.value(),
151  std::move(condition));
152  }
153  else
154  {
155  return std::make_unique<GoTo>(
156  chains.at(root.content()),
157  std::move(condition));
158  }
159  }
160 
161  // Only called if the AST is invalid, this can't be called as long as
162  // config_grammar.hpp does not contain any bugs.
163  // LCOV_EXCL_START
164  throw std::runtime_error("unknown action " + root.name());
165  // LCOV_EXCL_STOP
166 }
167 
168 
169 /** Add \ref Rule's from AST to a \ref Chain.
170  *
171  * \relates ConfigParser
172  * \param chain Chain to add rules to.
173  * \param root AST chain node containing rules.
174  * \param chains Map of chain names to chains for call and goto actions.
175  */
177  Chain &chain,
178  const config::parse_tree::node &root,
179  const std::map<std::string, std::shared_ptr<Chain>> &chains)
180 {
181  // Loop through each children.
182  for (auto &node : root.children)
183  {
184  std::optional<int> priority;
185  std::optional<If> condition;
186 
187  // Loop through rule options.
188  for (auto &child : node->children)
189  {
190  // Extract priority.
191  if (child->name() == "config::priority")
192  {
193  priority = std::stoi(child->content());
194  }
195  // Extract condition.
196  else if (child->name() == "config::condition")
197  {
198  condition = parse_condition(*child);
199  }
200  }
201 
202  // Create and add the new rule.
203  chain.append(
204  parse_action(
205  *node, std::move(priority), std::move(condition), chains));
206  }
207 }
208 
209 
210 /** Construct conditional (\ref If) from AST.
211  *
212  * \relates ConfigParser
213  * \param root AST conditional node.
214  * \returns The conditional constructed from the given AST node.
215  */
216 If parse_condition(const config::parse_tree::node &root)
217 {
218  If condition;
219 
220  // Parse condition options.
221  for (auto &child : root.children)
222  {
223  // Parse packet type.
224  if (child->name() == "config::packet_type")
225  {
226  condition.type(child->content());
227  }
228  // Parse source MAVLink address.
229  else if (child->name() == "config::source")
230  {
231  condition.from(child->content());
232  }
233  // Parse destination MAVLink address.
234  else if (child->name() == "config::dest")
235  {
236  condition.to(child->content());
237  }
238  }
239 
240  return condition;
241 }
242 
243 
244 /** Parse \ref Filter from AST.
245  *
246  * \relates ConfigParser
247  * \param root Root of configuration AST.
248  * \returns The \ref Filter parsed from the AST.
249  */
250 std::unique_ptr<Filter> parse_filter(const config::parse_tree::node &root)
251 {
252  Chain default_chain("default");
253  bool default_action = false;
254  std::map<std::string, std::shared_ptr<Chain>> chains = init_chains(root);
255 
256  // Look through top nodes.
257  for (auto &node : root.children)
258  {
259  // Parse chain.
260  if (node->name() == "config::chain")
261  {
262  // Parse default chain.
263  if (node->content() == "default")
264  {
265  parse_chain(default_chain, *node, chains);
266  }
267  // Parse named chain.
268  else
269  {
270  parse_chain(*chains.at(node->content()), *node, chains);
271  }
272  }
273  // Parse default filter action.
274  else if (node->name() == "config::default_action")
275  {
276  default_action = node->children[0]->name() == "config::accept";
277  }
278  }
279 
280  // Construct the filter.
281  return std::make_unique<Filter>(std::move(default_chain), default_action);
282 }
283 
284 
285 /** Parse UDP and serial port interfaces from AST root.
286  *
287  * \relates ConfigParser
288  * \param root The root of the AST to create \ref Interface's from.
289  * \param filter The packet \ref Filter to use for the interfaces.
290  * \returns A vector of UDP and serial port interfaces.
291  */
292 std::vector<std::unique_ptr<Interface>> parse_interfaces(
293  const config::parse_tree::node &root, std::unique_ptr<Filter> filter)
294 {
295  std::shared_ptr<Filter> shared_filter = std::move(filter);
296  std::vector<std::unique_ptr<Interface>> interfaces;
297  auto connection_pool = std::make_shared<ConnectionPool>();
298 
299  // Loop over each node of the root AST node.
300  for (auto &node : root.children)
301  {
302  // Parse UDP interface.
303  if (node->name() == "config::udp")
304  {
305  interfaces.push_back(
306  parse_udp(*node, shared_filter, connection_pool));
307  }
308  // Parse serial port interface.
309  else if (node->name() == "config::serial")
310  {
311  interfaces.push_back(
312  parse_serial(*node, shared_filter, connection_pool));
313  }
314  }
315 
316  return interfaces;
317 }
318 
319 
320 /** Parse a serial port interface from an AST.
321  *
322  * \relates ConfigParser
323  * \param root The serial port node to parse.
324  * \param filter The \ref Filter to use for the \ref SerialInterface.
325  * \param pool The connection pool to add the interface's connection to.
326  * \returns The serial port interface parsed from the AST and using the given
327  * filter and connection pool.
328  * \throws std::invalid_argument if the device string is missing.
329  */
330 std::unique_ptr<SerialInterface> parse_serial(
331  const config::parse_tree::node &root,
332  std::shared_ptr<Filter> filter,
333  std::shared_ptr<ConnectionPool> pool)
334 {
335  // Default settings.
336  std::optional<std::string> device;
337  unsigned long baud_rate = 9600;
339  std::vector<MAVAddress> preload;
340 
341  // Extract settings from AST.
342  for (auto &node : root.children)
343  {
344  // Extract device string.
345  if (node->name() == "config::device")
346  {
347  device = node->content();
348  }
349  // Extract device baud rate.
350  else if (node->name() == "config::baudrate")
351  {
352  baud_rate = static_cast<unsigned long>(std::stol(node->content()));
353  }
354  // Extract flow control.
355  else if (node->name() == "config::flow_control")
356  {
357  if (to_lower(node->content()) == "yes")
358  {
359  features = SerialPort::FLOW_CONTROL;
360  }
361  }
362  // Extract preloaded address.
363  else if (node->name() == "config::preload")
364  {
365  preload.push_back(MAVAddress(node->content()));
366  }
367  }
368 
369  // Throw error if no device was given.
370  if (!device.has_value())
371  {
372  throw std::invalid_argument("missing device string");
373  }
374 
375  // Construct serial interface.
376  auto port = std::make_unique<UnixSerialPort>(
377  device.value(), baud_rate, features);
378  auto connection = std::make_unique<Connection>(
379  device.value(), filter, false);
380 
381  for (const auto &addr : preload)
382  {
383  connection->add_address(addr);
384  }
385 
386  return std::make_unique<SerialInterface>(
387  std::move(port), pool, std::move(connection));
388 }
389 
390 
391 /** Parse a UPD interface from an AST.
392  *
393  * \relates ConfigParser
394  * \param root The UDP node to parse.
395  * \param filter The \ref Filter to use for the \ref UDPInterface.
396  * \param pool The connection pool to add the interface's connections to.
397  * \returns The UDP interface parsed from the AST and using the given filter
398  * and connection pool.
399  */
400 std::unique_ptr<UDPInterface> parse_udp(
401  const config::parse_tree::node &root,
402  std::shared_ptr<Filter> filter,
403  std::shared_ptr<ConnectionPool> pool)
404 {
405  unsigned int port = 14500;
406  std::optional<IPAddress> address;
407  unsigned long max_bitrate = 0;
408 
409  // Loop over options for UDP interface.
410  for (auto &node : root.children)
411  {
412  // Parse port number.
413  if (node->name() == "config::port")
414  {
415  port = static_cast<unsigned int>(std::stol(node->content()));
416  }
417  // Parse IP address and optionally port number.
418  else if (node->name() == "config::address")
419  {
420  address = IPAddress(node->content());
421  }
422  // Parse bitrate limit.
423  else if (node->name() == "config::max_bitrate")
424  {
425  max_bitrate = static_cast<unsigned long>(
426  std::stoll(node->content()));
427  }
428  }
429 
430  // Construct the UDP interface.
431  auto socket = std::make_unique<UnixUDPSocket>(port, address, max_bitrate);
432  auto factory = std::make_unique<ConnectionFactory<>>(filter, false);
433  return std::make_unique<UDPInterface>(
434  std::move(socket), pool, std::move(factory));
435 }
436 
437 
438 /** Construct a configuration parser from a file.
439  *
440  * \param filename The path of the configuration file to parse.
441  * \throws std::runtime_error if the configuration file cannot be parsed.
442  */
443 ConfigParser::ConfigParser(std::string filename)
444  : in_(filename)
445 {
447 
448  if (root_ == nullptr)
449  {
450  // It is technically impossible parsing errors should be raised as a
451  // parse_error.
452  // LCOV_EXCL_START
453  throw std::runtime_error(
454  "Unexpected error while parsing configuration file.");
455  // LCOV_EXCL_STOP
456  }
457 }
458 
459 
460 /** Build a mavtables application from the AST contained by the parser.
461  *
462  * \returns A mavtables application.
463  */
464 std::unique_ptr<App> ConfigParser::make_app()
465 {
466  auto filter = parse_filter(*root_);
467  auto interfaces = parse_interfaces(*root_, std::move(filter));
468  return std::make_unique<App>(std::move(interfaces));
469 }
470 
471 
472 /** Print the configuration settings to the given output stream.
473  *
474  * An example (that of test/mavtables.conf) is:
475  *
476  * ```
477  * ===== test/mavtables.conf =====
478  * :001: default_action
479  * :001: | accept
480  * :004: udp
481  * :005: | port 14500
482  * :006: | address 127.0.0.1
483  * :007: | max_bitrate 8388608
484  * :011: serial
485  * :012: | device ./ttyS0
486  * :013: | baudrate 115200
487  * :014: | flow_control yes
488  * :015: | preload 1.1
489  * :016: | preload 62.34
490  * :020: chain default
491  * :022: | call some_chain10
492  * :022: | | condition
493  * :022: | | | source 127.1
494  * :022: | | | dest 192.0
495  * :023: | reject
496  * :027: chain some_chain10
497  * :029: | accept
498  * :029: | | priority 99
499  * :029: | | condition
500  * :029: | | | dest 192.0
501  * :030: | accept
502  * :030: | | condition
503  * :030: | | | packet_type PING
504  * :031: | accept
505  * ```
506  *
507  * \relates ConfigParser
508  * \param os The output stream to print to.
509  * \param config_parser The configuration parser to print.
510  * \returns The output stream.
511  */
512 std::ostream &operator<<(std::ostream &os, const ConfigParser &config_parser)
513 {
514  os << *config_parser.root_;
515  return os;
516 }
If & from(MAVSubnet subnet)
Definition: If.cpp:98
If & type(unsigned long id)
Definition: If.cpp:69
tao::pegtl::read_input in_
std::unique_ptr< config::parse_tree::node > parse(Input &in)
If parse_condition(const config::parse_tree::node &root)
No special features.
Definition: SerialPort.hpp:61
std::unique_ptr< App > make_app()
Definition: If.hpp:35
std::unique_ptr< Filter > parse_filter(const config::parse_tree::node &root)
If & to(MAVSubnet subnet)
Definition: If.cpp:127
std::string to_lower(std::string string)
Definition: utility.cpp:37
Enable flow control.
Definition: SerialPort.hpp:62
std::unique_ptr< UDPInterface > parse_udp(const config::parse_tree::node &root, std::shared_ptr< Filter > filter, std::shared_ptr< ConnectionPool > pool)
std::unique_ptr< SerialInterface > parse_serial(const config::parse_tree::node &root, std::shared_ptr< Filter > filter, std::shared_ptr< ConnectionPool > pool)
std::ostream & operator<<(std::ostream &os, const Action &action)
Definition: Action.cpp:188
std::unique_ptr< config::parse_tree::node > root_
ConfigParser(std::string filename)
std::vector< std::unique_ptr< Interface > > parse_interfaces(const config::parse_tree::node &root, std::unique_ptr< Filter > filter)
Definition: Chain.hpp:37
void append(std::unique_ptr< Rule > rule)
Definition: Chain.cpp:116
void parse_chain(Chain &chain, const config::parse_tree::node &root, const std::map< std::string, std::shared_ptr< Chain >> &chains)
std::map< std::string, std::shared_ptr< Chain > > init_chains(const config::parse_tree::node &root)
std::unique_ptr< Rule > parse_action(const config::parse_tree::node &root, std::optional< int > priority, std::optional< If > condition, const std::map< std::string, std::shared_ptr< Chain >> &chains)