80c79285c7ced1f5d095b21d103dd6041915586b
[oonf.git] / src-plugins / layer2_viewer / layer2_viewer.c
1
2 /*
3  * The olsr.org Optimized Link-State Routing daemon(olsrd)
4  * Copyright (c) 2004-2013, the olsr.org team - see HISTORY file
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above copyright
14  *   notice, this list of conditions and the following disclaimer in
15  *   the documentation and/or other materials provided with the
16  *   distribution.
17  * * Neither the name of olsr.org, olsrd nor the names of its
18  *   contributors may be used to endorse or promote products derived
19  *   from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * Visit http://www.olsr.org for more information.
35  *
36  * If you find this software useful feel free to make a donation
37  * to the project. For more information see the website or contact
38  * the copyright holders.
39  *
40  */
41
42 #include <stdio.h>
43
44 #include "common/common_types.h"
45 #include "common/autobuf.h"
46 #include "common/netaddr.h"
47 #include "common/netaddr_acl.h"
48 #include "common/string.h"
49 #include "common/template.h"
50
51 #include "config/cfg_schema.h"
52 #include "core/oonf_logging.h"
53 #include "core/oonf_plugins.h"
54 #include "subsystems/oonf_clock.h"
55 #include "subsystems/oonf_interface.h"
56 #include "subsystems/oonf_layer2.h"
57 #include "subsystems/oonf_telnet.h"
58
59 #include "layer2_viewer/layer2_viewer.h"
60
61 /* keys for template engine */
62 #define KEY_neighbor "neighbor"
63 #define KEY_radio "radio"
64 #define KEY_ifindex "ifindex"
65 #define KEY_interface "interface"
66 #define KEY_active "active"
67 #define KEY_shortactive "shortactive"
68 #define KEY_lastseen "lastseen"
69 #define KEY_ssid "ssid"
70 #define KEY_frequency "frequency"
71 #define KEY_signal "signal"
72 #define KEY_rxbitrate "rxbitrate"
73 #define KEY_rxbytes "rxbytes"
74 #define KEY_rxpackets "rxpackets"
75 #define KEY_txbitrate "txbitrate"
76 #define KEY_txbytes "txbytes"
77 #define KEY_txpackets "txpackets"
78 #define KEY_txretries "txretries"
79 #define KEY_txfailed "txfailed"
80
81 /* definitions */
82 struct _l2viewer_config {
83   struct netaddr_acl acl;
84 };
85
86 /* prototypes */
87 static int _init(void);
88 static void _cleanup(void);
89
90 static void _cb_config_changed(void);
91
92 static enum oonf_telnet_result _cb_handle_layer2(struct oonf_telnet_data *data);
93
94 /* configuration */
95 static struct cfg_schema_entry _layer2_entries[] = {
96   CFG_MAP_ACL(_l2viewer_config, acl, "acl", "default_accept", "acl for layer2 telnet command"),
97 };
98
99 static struct cfg_schema_section _layer2_section = {
100   .type = OONF_PLUGIN_GET_NAME(),
101   .cb_delta_handler = _cb_config_changed,
102   .entries = _layer2_entries,
103   .entry_count = ARRAYSIZE(_layer2_entries),
104 };
105
106 static struct _l2viewer_config _config;
107
108 /* plugin declaration */
109 struct oonf_subsystem oonf_layer2_viewer_subsystem = {
110   .name = OONF_PLUGIN_GET_NAME(),
111   .descr = "OONFD layer2 viewer plugin",
112   .author = "Henning Rogge",
113
114   .cfg_section = &_layer2_section,
115
116   .init = _init,
117   .cleanup = _cleanup,
118 };
119 DECLARE_OONF_PLUGIN(oonf_layer2_viewer_subsystem);
120
121 /* telnet command */
122 static struct oonf_telnet_command _telnet_cmd =
123   TELNET_CMD("layer2", _cb_handle_layer2,
124       "\"layer2 net\": show data of all known WLAN networks\n"
125       "\"layer2 net list\": show a table of all known active WLAN networks\n"
126       "\"layer2 net "JSON_TEMPLATE_FORMAT"\": show a json output of all known active WLAN networks\n"
127       "\"layer2 net <template>\": show a table of all known active WLAN networks\n"
128       "     (use net_full/net_inactive to output all/inactive networks)\n"
129       "\"layer2 neigh\": show data of all known WLAN neighbors\n"
130       "\"layer2 neigh list\": show a table of all known WLAN neighbors\n"
131       "\"layer2 neigh "JSON_TEMPLATE_FORMAT"\": show a json output of all known WLAN neighbors\n"
132       "\"layer2 neigh <template>\": show a table of all known WLAN neighbors\n"
133       "     (use neigh_full/neigh_inactive to output all/inactive neighbors)\n",
134       .acl = &_config.acl);
135
136 /* template buffers */
137 static struct {
138   struct netaddr_str neighbor;
139   struct netaddr_str radio;
140   char ifindex[10];
141   char interface[IF_NAMESIZE];
142   char active[JSON_BOOL_LENGTH];
143   char shortactive[2];
144   struct human_readable_str lastseen;
145   char ssid[33];
146   struct human_readable_str frequency;
147   char signal[7];
148   struct human_readable_str rxbitrate;
149   struct human_readable_str rxbytes;
150   struct human_readable_str rxpackets;
151   struct human_readable_str txbitrate;
152   struct human_readable_str txbytes;
153   struct human_readable_str txpackets;
154   struct human_readable_str txretries;
155   struct human_readable_str txfailed;
156 } _template_buf;
157
158 static struct abuf_template_data _template_neigh_data[] = {
159   { .key = KEY_neighbor, .value = _template_buf.neighbor.buf, .string = true},
160   { .key = KEY_radio, .value = _template_buf.radio.buf, .string = true},
161   { .key = KEY_ifindex, .value = _template_buf.ifindex },
162   { .key = KEY_interface, .value = _template_buf.interface, .string = true },
163   { .key = KEY_active, .value = _template_buf.active },
164   { .key = KEY_shortactive, .value = _template_buf.shortactive, .string = true },
165   { .key = KEY_lastseen, .value = _template_buf.lastseen.buf },
166   { .key = KEY_signal, .value = _template_buf.signal },
167   { .key = KEY_rxbitrate, .value = _template_buf.rxbitrate.buf },
168   { .key = KEY_rxbytes, .value = _template_buf.rxbytes.buf },
169   { .key = KEY_rxpackets, .value = _template_buf.rxpackets.buf },
170   { .key = KEY_txbitrate, .value = _template_buf.txbitrate.buf },
171   { .key = KEY_txbytes, .value = _template_buf.txbytes.buf },
172   { .key = KEY_txpackets, .value = _template_buf.txpackets.buf },
173   { .key = KEY_txretries, .value = _template_buf.txretries.buf },
174   { .key = KEY_txfailed, .value = _template_buf.txfailed.buf },
175 };
176
177 static struct abuf_template_data _template_net_data[] = {
178   { .key = KEY_radio, .value = _template_buf.radio.buf, .string = true},
179   { .key = KEY_ifindex, .value = _template_buf.ifindex },
180   { .key = KEY_interface, .value = _template_buf.interface, .string = true },
181   { .key = KEY_active, .value = _template_buf.active },
182   { .key = KEY_lastseen, .value = _template_buf.lastseen.buf },
183   { .key = KEY_ssid, .value = _template_buf.ssid, .string = true},
184   { .key = KEY_frequency, .value = _template_buf.frequency.buf },
185 };
186
187 struct _command_params {
188   const char *cmd_full;
189   const char *cmd_active;
190   const char *cmd_inactive;
191   const char *tmpl_full;
192   const char *tmpl_table;
193   const char *tmpl_filtered_table;
194   const char *headline_table;
195   const char *headline_filtered_table;
196
197   /* set by runtime */
198   const char *template;
199   bool active;
200   bool inactive;
201 };
202
203 struct _command_params _net_params = {
204   .cmd_full = "net_full",
205   .cmd_active = "net",
206   .cmd_inactive = "net_inactive",
207
208   .tmpl_full =
209       "Radio MAC: %" KEY_radio     "%\n"
210       "Active:    %" KEY_active    "%\n"
211       "If-Index:  %" KEY_ifindex   "%\n"
212       "Interface: %" KEY_interface "%\n"
213       "SSID:      %" KEY_ssid      "%\n"
214       "Last seen: %" KEY_lastseen  "% seconds ago\n"
215       "Frequency: %" KEY_frequency "%\n"
216       "\n",
217   .tmpl_table =
218       "%"KEY_shortactive"%%"KEY_interface"%\t%"KEY_radio"%\n",
219   .tmpl_filtered_table =
220       "%"KEY_interface"%\t%"KEY_radio"%\n",
221
222   .headline_table = "  If\tRadio            \n",
223   .headline_filtered_table = "If\tRadio            \n",
224 };
225
226 struct _command_params _neigh_params = {
227   .cmd_full = "neigh_full",
228   .cmd_active = "neigh",
229   .cmd_inactive = "neigh_inactive",
230
231   .tmpl_full =
232       "Neighbor MAC: %" KEY_neighbor  "%\n"
233       "Active:       %" KEY_active    "%\n"
234       "Radio MAC:    %" KEY_radio     "%\n"
235       "If-Index:     %" KEY_ifindex   "%\n"
236       "Interface:    %" KEY_interface "%\n"
237       "Last seen:    %" KEY_lastseen  "% seconds ago\n"
238       "Signal:       %" KEY_signal    "% dBm\n"
239       "Rx bitrate:   %" KEY_rxbitrate "%\n"
240       "Rx bytes:     %" KEY_rxbytes   "%\n"
241       "Rx packets:   %" KEY_rxpackets "%\n"
242       "Tx bitrate:   %" KEY_txbitrate "%\n"
243       "Tx bytes:     %" KEY_txbytes   "%\n"
244       "Tx packets:   %" KEY_txpackets "%\n"
245       "Tx retries:   %" KEY_txretries "%\n"
246       "Tx failed:    %" KEY_txfailed  "%\n"
247       "\n",
248   .tmpl_table =
249       "%"KEY_shortactive"%%"KEY_interface"%\t%"KEY_radio"%\t%"KEY_neighbor"%\n",
250   .tmpl_filtered_table =
251       "%"KEY_interface"%\t%"KEY_radio"%\t%"KEY_neighbor"%\n",
252
253   .headline_table = "  If\tRadio            \tNeighbor\n",
254   .headline_filtered_table = "If\tRadio            \tNeighbor\n",
255 };
256
257 /**
258  * Constructor of plugin
259  * @return 0 if initialization was successful, -1 otherwise
260  */
261 static int
262 _init(void) {
263   oonf_telnet_add(&_telnet_cmd);
264   return 0;
265 }
266
267 /**
268  * Disable plugin
269  */
270 static void
271 _cleanup(void) {
272   oonf_telnet_remove(&_telnet_cmd);
273 }
274
275 /**
276  * Print the data of a layer2 network to the telnet stream
277  * @param out pointer to output stream
278  * @param net pointer to layer2 network data
279  * @return -1 if an error happened, 0 otherwise
280  */
281 static int
282 _init_network_template(struct oonf_layer2_network *net, bool raw) {
283   memset (&_template_buf, 0, sizeof(_template_buf));
284
285   if (NULL == netaddr_to_string(&_template_buf.radio, &net->radio_id))
286     return -1;
287
288   strcpy (_template_buf.active, abuf_json_getbool(net->active));
289   strcpy (_template_buf.shortactive, net->active ? "*" : " ");
290
291   if (net->if_index) {
292     sprintf(_template_buf.ifindex, "%u", net->if_index);
293     if_indextoname(net->if_index, _template_buf.interface);
294   }
295
296   if (oonf_layer2_network_has_ssid(net)) {
297     strscpy(_template_buf.ssid, net->ssid, sizeof(_template_buf.ssid));
298   }
299
300   if (oonf_layer2_network_has_last_seen(net)) {
301     int64_t relative;
302
303     relative = oonf_clock_get_relative(net->last_seen);
304     if (NULL == oonf_clock_toIntervalString(&_template_buf.lastseen, -relative)) {
305       return -1;
306     }
307   }
308   if (oonf_layer2_network_has_frequency(net)) {
309     if (NULL == str_get_human_readable_u64(
310         &_template_buf.frequency, net->frequency, "Hz", 3, false, raw)) {
311       return -1;
312     }
313   }
314   return 0;
315 }
316
317 /**
318  * Print the data of a layer2 neighbor to the telnet stream
319  * @param out pointer to output stream
320  * @param net pointer to layer2 neighbor data
321  * @return -1 if an error happened, 0 otherwise
322  */
323 static int
324 _init_neighbor_template(struct oonf_layer2_neighbor *neigh, bool raw) {
325   if (NULL == netaddr_to_string(&_template_buf.neighbor, &neigh->key.neighbor_mac))
326     return -1;
327
328   strcpy (_template_buf.active, abuf_json_getbool(neigh->active));
329   strcpy (_template_buf.shortactive, neigh->active ? "*" : " ");
330
331   if (NULL == netaddr_to_string(&_template_buf.radio, &neigh->key.radio_mac))
332     return -1;
333
334   if (neigh->if_index) {
335     sprintf(_template_buf.ifindex, "%u", neigh->if_index);
336     if_indextoname(neigh->if_index, _template_buf.interface);
337   }
338
339   if (oonf_layer2_neighbor_has_last_seen(neigh)) {
340     int64_t relative;
341
342     relative = oonf_clock_get_relative(neigh->last_seen);
343     if (NULL == oonf_clock_toIntervalString(&_template_buf.lastseen, -relative)) {
344       return -1;
345     }
346   }
347
348   if (oonf_layer2_neighbor_has_signal(neigh)) {
349     sprintf(_template_buf.signal, "%d", neigh->signal_dbm);
350   }
351   if (oonf_layer2_neighbor_has_rx_bitrate(neigh)) {
352     if (NULL == str_get_human_readable_u64(
353         &_template_buf.rxbitrate, neigh->rx_bitrate, "bit/s", 1, true, raw)) {
354       return -1;
355     }
356   }
357   if (oonf_layer2_neighbor_has_rx_bytes(neigh)) {
358     if (NULL == str_get_human_readable_u64(
359         &_template_buf.rxbytes, neigh->rx_bytes, "Byte", 1, true, raw)) {
360       return -1;
361     }
362   }
363   if (oonf_layer2_neighbor_has_rx_packets(neigh)) {
364     if (NULL == str_get_human_readable_u64(
365         &_template_buf.rxpackets, neigh->rx_packets, "", 0, true, raw)) {
366       return -1;
367     }
368   }
369   if (oonf_layer2_neighbor_has_tx_bitrate(neigh)) {
370     if (NULL == str_get_human_readable_u64(
371         &_template_buf.txbitrate, neigh->tx_bitrate, "bit/s", 1, true, raw)) {
372       return -1;
373     }
374   }
375   if (oonf_layer2_neighbor_has_tx_bytes(neigh)) {
376     if (NULL == str_get_human_readable_u64(
377         &_template_buf.txbytes, neigh->tx_bytes, "Byte", 1, true, raw)) {
378       return -1;
379     }
380   }
381   if (oonf_layer2_neighbor_has_tx_packets(neigh)) {
382     if (NULL == str_get_human_readable_u64(
383         &_template_buf.txpackets, neigh->tx_packets, "", 0, true, raw)) {
384       return -1;
385     }
386   }
387   if (oonf_layer2_neighbor_has_tx_retries(neigh)) {
388     if (NULL == str_get_human_readable_u64(
389         &_template_buf.txretries, neigh->tx_retries, "", 3, true, raw)) {
390       return -1;
391     }
392   }
393   if (oonf_layer2_neighbor_has_tx_failed(neigh)) {
394     if (NULL == str_get_human_readable_u64(
395         &_template_buf.txfailed, neigh->tx_failed, "", 3, true, raw)) {
396       return -1;
397     }
398   }
399   return 0;
400 }
401
402 /**
403  * Parse a group of subcommands to support filtered/nonfiltered output with
404  * full, list, json and custom template mode
405  * @param out output buffer
406  * @param cmd command stream
407  * @param params pointer to subcommand description
408  * @return true if one of the subcommands was found, false otherwise
409  */
410 static bool
411 _parse_mode(struct autobuf *out, const char *cmd, struct _command_params *params) {
412   const char *next;
413   bool filtered;
414
415   filtered = false;
416   if ((next = str_hasnextword(cmd, params->cmd_full))) {
417     params->active = true;
418     params->inactive = true;
419   }
420   else if ((next = str_hasnextword(cmd, params->cmd_active))) {
421     params->active = true;
422     filtered = true;
423   }
424   else if ((next = str_hasnextword(cmd, params->cmd_inactive))) {
425     params->inactive = true;
426     filtered = true;
427   }
428   else {
429     return false;
430   }
431
432   if (strcasecmp(next, "list") == 0) {
433     if (filtered) {
434       abuf_puts(out, params->headline_filtered_table);
435       params->template = params->tmpl_filtered_table;
436     }
437     else {
438       abuf_puts(out, params->headline_table);
439       params->template = params->tmpl_table;
440     }
441   }
442   else if (strcasecmp(next, JSON_TEMPLATE_FORMAT) == 0) {
443     params->template = NULL;
444   }
445   else if (*next == 0) {
446     params->template = params->tmpl_full;
447   }
448   else {
449     params->template = next;
450   }
451   return true;
452 }
453
454 /**
455  * Implementation of 'layer2' telnet command
456  * @param data pointer to telnet data
457  * @return return code for telnet server
458  */
459 static enum oonf_telnet_result
460 _cb_handle_layer2(struct oonf_telnet_data *data) {
461   struct oonf_layer2_network *net;
462   struct oonf_layer2_neighbor *neigh;
463   struct abuf_template_storage *tmpl_storage = NULL;
464
465   if (data->parameter == NULL || *data->parameter == 0) {
466     abuf_puts(data->out, "Error, 'layer2' needs a parameter\n");
467     return TELNET_RESULT_ACTIVE;
468   }
469
470   if (_parse_mode(data->out, data->parameter, &_net_params)) {
471     if (_net_params.template) {
472       tmpl_storage = abuf_template_init(
473         _template_net_data, ARRAYSIZE(_template_net_data), _net_params.template);
474       if (tmpl_storage == NULL) {
475         return TELNET_RESULT_INTERNAL_ERROR;
476       }
477     }
478
479     avl_for_each_element(&oonf_layer2_network_id_tree, net, _id_node) {
480       if (net->active ? _net_params.active : _net_params.inactive) {
481         if (_init_network_template(net, _net_params.template == NULL)) {
482           free(tmpl_storage);
483           return TELNET_RESULT_INTERNAL_ERROR;
484         }
485         if (_net_params.template) {
486           abuf_add_template(data->out, _net_params.template, tmpl_storage);
487         }
488         else {
489           abuf_add_json(data->out, "",
490               _template_net_data, ARRAYSIZE(_template_net_data));
491         }
492       }
493     }
494   }
495   else if (_parse_mode(data->out, data->parameter, &_neigh_params)) {
496     if (_neigh_params.template) {
497       tmpl_storage = abuf_template_init(
498         _template_neigh_data, ARRAYSIZE(_template_neigh_data), _neigh_params.template);
499       if (tmpl_storage == NULL) {
500         return TELNET_RESULT_INTERNAL_ERROR;
501       }
502     }
503
504     avl_for_each_element(&oonf_layer2_neighbor_tree, neigh, _node) {
505       if (neigh->active ? _neigh_params.active : _neigh_params.inactive) {
506         if (_init_neighbor_template(neigh, _neigh_params.template == NULL)) {
507           free(tmpl_storage);
508           return TELNET_RESULT_INTERNAL_ERROR;
509         }
510
511         if (_neigh_params.template) {
512           abuf_add_template(data->out, _neigh_params.template, tmpl_storage);
513         }
514         else {
515           abuf_add_json(data->out, "", _template_neigh_data, ARRAYSIZE(_template_neigh_data));
516         }
517       }
518     }
519   }
520   else {
521     abuf_appendf(data->out, "Error, unknown parameters for %s command: %s\n",
522         data->command, data->parameter);
523   }
524
525   free(tmpl_storage);
526   return TELNET_RESULT_ACTIVE;
527 }
528
529 /**
530  * Update configuration of layer2-viewer plugin
531  */
532 static void
533 _cb_config_changed(void) {
534   if (cfg_schema_tobin(&_config, _layer2_section.post,
535       _layer2_entries, ARRAYSIZE(_layer2_entries))) {
536     OONF_WARN(LOG_LAYER2_VIEWER, "Could not convert layer2_listener config to bin");
537     return;
538   }
539 }