Subject: batctl: Parse the new /proc vis format.
Add code to batctl so that it can parse /proc/net/batman-adv/vis and generate either graphvis dot or JSON output.
Tested and bugs found and fixed by Linus Luessing.
Signed-off-by: Andrew Lunn andrew@lunn.ch Index: vis.c =================================================================== --- vis.c (revision 0) +++ vis.c (revision 0) @@ -0,0 +1,326 @@ +/* Copyright (C) 2009 B.A.T.M.A.N. contributors: + * Andrew Lunn andrew@lunn.ch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <getopt.h> +#include <string.h> + +#include "main.h" +#include "vis.h" +#include "functions.h" +#include "bat-hosts.h" +#include "proc.h" + +#define TQ_MAX_VALUE 255 + +typedef void (*print_tq_t)(char * orig, char * from, const long tq); +typedef void (*print_HNA_t)(char * orig, char * from); +typedef void (*print_1st_t)(char * orig); +typedef void (*print_2nd_t)(char * orig, char * from); +typedef void (*print_header_t)(void); +typedef void (*print_footer_t)(void); + +struct funcs +{ + print_tq_t print_tq; + print_HNA_t print_HNA; + print_1st_t print_1st; + print_2nd_t print_2nd; + print_header_t print_header; + print_footer_t print_footer; +}; + +static bool with_HNA = true; +static bool with_2nd = true; +static bool with_names = true; + +static void +usage(void) +{ + printf("batctl vis dot {--no-HNA|-h} {--no-2nd|-2} {--numbers|-n}\n"); + printf("or\n"); + printf("batctl vis json {--no-HNA|-h} {--no-2nd|-2} {--numbers|-n}\n"); +} + +static void +dot_print_tq(char * orig, char * from, const long tq) +{ + int int_part = TQ_MAX_VALUE / tq; + int frac_part = (1000 * TQ_MAX_VALUE / tq) - (int_part * 1000); + + printf("\t"%s" -> ", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf(""%s" [label="%d.%d"]\n", + get_name_by_macstr(from, (with_names ? USE_BAT_HOSTS : 0)), + int_part, frac_part); +} + +static void +dot_print_HNA(char * orig, char * from) +{ + printf("\t"%s" -> ", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf(""%s" [label="HNA"]\n", + get_name_by_macstr(from, (with_names ? USE_BAT_HOSTS : 0))); +} + +static void +dot_print_1st(char * orig) +{ + printf("\tsubgraph "cluster_%s" {\n", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf("\t\t"%s" [peripheries=2]\n", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf("\t}\n"); +} + +static void +dot_print_2nd(char * orig, char * from) +{ + printf("\tsubgraph "cluster_%s" {\n", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf("\t\t"%s" [peripheries=2]\n", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf("\t\t"%s"\n", + get_name_by_macstr(from, (with_names ? USE_BAT_HOSTS : 0))); + printf("\t}\n"); +} + +static void +dot_print_header(void) +{ + printf("digraph {\n"); +} + +static void +dot_print_footer(void) +{ + printf("}\n"); +} + +const struct funcs dot_funcs = + { dot_print_tq, + dot_print_HNA, + dot_print_1st, + dot_print_2nd, + dot_print_header, + dot_print_footer +}; + +static void +json_print_tq(char * orig, char * from, const long tq) +{ + int int_part = TQ_MAX_VALUE / tq; + int frac_part = (1000 * TQ_MAX_VALUE / tq) - (int_part * 1000); + + printf("\t{ router : "%s", ", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf("neighbor : "%s", label : "%d.%d" }\n", + get_name_by_macstr(from, (with_names ? USE_BAT_HOSTS : 0)), + int_part, frac_part); +} + +static void +json_print_HNA(char * orig, char * from) +{ + printf("\t{ router : "%s", ", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); + printf("gateway : "%s", label : "HNA" }\n", + get_name_by_macstr(from, (with_names ? USE_BAT_HOSTS : 0))); +} + +static void +json_print_1st(char * orig) +{ + printf("\t{ primary : "%s" }\n", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); +} + +static void +json_print_2nd(char * orig, char * from) +{ + printf("\t{ secondary : "%s", ", + get_name_by_macstr(from, (with_names ? USE_BAT_HOSTS : 0))); + + printf("of : "%s" }\n", + get_name_by_macstr(orig, (with_names ? USE_BAT_HOSTS : 0))); +} + +const struct funcs json_funcs = + { json_print_tq, + json_print_HNA, + json_print_1st, + json_print_2nd, + NULL, + NULL +}; + +static FILE * +open_vis(void) +{ + char full_path[500]; + + if (check_proc_dir("/proc") != EXIT_SUCCESS) + return NULL; + + strncpy(full_path, PROC_ROOT_PATH, strlen(PROC_ROOT_PATH)); + full_path[strlen(PROC_ROOT_PATH)] = '\0'; + strncat(full_path, "vis", sizeof(full_path) - strlen(full_path)); + + return fopen(full_path, "r"); +} + +static int +format(const struct funcs *funcs) +{ + size_t len = 0; + ssize_t read; + char * line = NULL; + char * orig, * from; + char * duplet; + char * line_save_ptr; + char * duplet_save_ptr; + char * endptr; + char * value; + long tq; + char * flag; + + FILE * fp = open_vis(); + + if (!fp) + return EXIT_FAILURE; + + if (funcs->print_header) + funcs->print_header(); + + while ((read = getline(&line, &len, fp)) != -1) { + /* First MAC address is the originator */ + orig = strtok_r(line, ",", &line_save_ptr); + + duplet_save_ptr = line_save_ptr; + while ((duplet = strtok_r(NULL, ",", &duplet_save_ptr)) != NULL) { + flag = strtok(duplet, " "); + if (!flag) + continue; + if (!strcmp(flag, "TQ")) { + from = strtok(NULL, " "); + value = strtok(NULL, " "); + tq = strtoul(value, &endptr, 0); + funcs->print_tq(orig, from, tq); + continue; + } + if (!strcmp(flag, "HNA")) { + /* We have an HNA record */ + if (!with_HNA) + continue; + from = strtok(NULL, " "); + funcs->print_HNA(orig, from); + continue; + } + if (!strcmp(flag, "SEC") && with_2nd) { + /* We found a secondary interface MAC address.*/ + from = strtok(NULL, " "); + funcs->print_2nd(orig, from); + } + if (!strcmp(flag, "PRIMARY") && with_2nd) { + /* We found a primary interface MAC address.*/ + funcs->print_1st(orig); + } + } + } + + if (funcs->print_footer) + funcs->print_footer(); + + if (line) + free(line); + return EXIT_SUCCESS; +} + +int +vis(int argc, char *argv[]) +{ + bool dot = false; + bool json = false; + int c; + + if (argc <=1) { + usage(); + return EXIT_FAILURE; + } + + /* Do we know the requested format? */ + if (strcmp(argv[1], "dot") == 0) + dot=true; + if (strcmp(argv[1], "json") == 0) + json=true; + + if (!dot && !json) { + usage(); + return EXIT_FAILURE; + } + + /* Move over the output format */ + argc--; + argv++; + + while (1) { + int option_index = 0; + static struct option long_options[] = { + {"no-HNA", 0, 0, 'h'}, + {"no-2nd", 0, 0, '2'}, + {"numbers", 0, 0, 'n'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "h2n", long_options, &option_index); + if (c == -1) + break; + + switch(c) { + case 'h': + with_HNA = false; + break; + case '2': + with_2nd = false; + break; + case 'n': + with_names = false; + break; + default: + usage(); + return -1; + } + } + + if (with_names) + bat_hosts_init(); + + if (dot) + return format(&dot_funcs); + + if (json) + return format(&json_funcs); + + return EXIT_FAILURE; +} Index: vis.h =================================================================== --- vis.h (revision 0) +++ vis.h (revision 0) @@ -0,0 +1,21 @@ +/* Copyright (C) 2009 B.A.T.M.A.N. contributors: + * Andrew Lunn andrew@lunn.ch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + */ + +int vis(int argc, char * argv[]); + Index: functions.c =================================================================== --- functions.c (revision 1489) +++ functions.c (working copy) @@ -99,7 +99,7 @@ return get_name_by_macaddr(mac_addr, read_opt); }
-static int check_proc_dir(char *dir) +int check_proc_dir(char *dir) { struct stat st;
Index: functions.h =================================================================== --- functions.h (revision 1489) +++ functions.h (working copy) @@ -34,6 +34,7 @@ char *get_name_by_macstr(char *mac_str, int read_opt); int read_file(char *dir, char *path, int read_opt); int write_file(char *dir, char *path, char *value); +int check_proc_dir(char *dir);
extern char read_buff[10];
Index: main.c =================================================================== --- main.c (revision 1489) +++ main.c (working copy) @@ -35,6 +35,7 @@ #include "traceroute.h" #include "tcpdump.h" #include "bisect.h" +#include "vis.h"
void print_usage(void) { @@ -47,7 +48,7 @@ printf(" \tlog|l \tread the log produced by the kernel module\n"); printf(" \ttranslocal|tl \tdisplay the local translation table\n"); printf(" \ttransglobal|tg \tdisplay the global translation table\n"); - printf(" \tvisformat|vf [format] \tdisplay or modify the vis output format\n"); + printf(" \tvis [dot|JSON] \tdisplay the VIS data in dot or JSON format\n"); printf(" \taggregation|ag [0|1] \tdisplay or modify the packet aggregation setting\n"); printf("\n"); printf(" \tping|p <destination> \tping another batman adv host via layer 2\n"); @@ -74,7 +75,8 @@ }
/* check if user is root */ - if ((strcmp(argv[1], "bisect") != 0) && ((getuid()) || (getgid()))) { + if (((strcmp(argv[1], "bisect") != 0) && (strcmp(argv[1], "vis") != 0)) + && ((getuid()) || (getgid()))) { fprintf(stderr, "Error - you must be root to run '%s' !\n", argv[0]); exit(EXIT_FAILURE); } @@ -119,9 +121,9 @@
ret = handle_proc_setting(argc - 1, argv + 1, PROC_ORIG_INTERVAL, orig_interval_usage);
- } else if ((strcmp(argv[1], "visformat") == 0) || (strcmp(argv[1], "vf") == 0)) { + } else if (strcmp(argv[1], "vis") == 0) {
- ret = handle_proc_setting(argc - 1, argv + 1, PROC_VIS_FORMAT, vis_format_usage); + ret = vis(argc - 1, argv + 1);
} else if ((strcmp(argv[1], "aggregation") == 0) || (strcmp(argv[1], "ag") == 0)) {
Index: man/batctl.8 =================================================================== --- man/batctl.8 (revision 1489) +++ man/batctl.8 (working copy) @@ -63,9 +63,22 @@ .IP "\fBtransglobal|tg\fP" Once started batctl will refresh the displayed global translation table every second. Use the "-b" option to let batctl display the table only once (useful for scripts). If "-n" was given batctl will not replace the mac addresses with bat-host names in the output. .br -.IP "\fBvisformat|vf [format]\fP" -If no parameter is given the current vis format settings are displayed otherwise the parameter is used to set the vis format. +.IP "\fBvis dot\fP" +Display the visualisation data in graphviz dot(1) format. If +"--numbers" or "-n" batctl will not replace the mac addresses with +bat-host names in the output. With "--no-HNA" or "-h" the HNA +entries are not displayed, so the pure mesh topology can be seen. With +"--no-2nd" or "-2" a dot cluster is not formed around primary and +secondary addresses from the same device. .br +.IP "\fBvis json\fP" +Display the visualisation data in JSON format. If +"--numbers" or "-n" batctl will not replace the mac addresses with +bat-host names in the output. With "--no-HNA" or "-h" the HNA +entries are not displayed, so the pure mesh topology can be seen. With +"--no-2nd" or "-2" a dot cluster is not formed around primary and +secondary addresses from the same device. +.br .IP "\fBaggregation|ag [0|1]\fP" If no parameter is given the current aggregation settings are displayed otherwise the parameter is used to enable or disable the packet aggregation. .br Index: Makefile =================================================================== --- Makefile (revision 1489) +++ Makefile (working copy) @@ -39,8 +39,8 @@ EXTRA_MODULES_C := bisect.c EXTRA_MODULES_H := bisect.h
-SRC_C = main.c bat-hosts.c functions.c proc.c sys.c ping.c traceroute.c tcpdump.c list-batman.c hash.c $(EXTRA_MODULES_C) -SRC_H = main.h bat-hosts.h functions.h proc.h sys.h ping.h traceroute.h tcpdump.h list-batman.h hash.h allocate.h $(EXTRA_MODULES_H) +SRC_C = main.c bat-hosts.c functions.c proc.c sys.c ping.c traceroute.c tcpdump.c list-batman.c hash.c vis.c $(EXTRA_MODULES_C) +SRC_H = main.h bat-hosts.h functions.h proc.h sys.h ping.h traceroute.h tcpdump.h list-batman.h hash.h allocate.h vis.h $(EXTRA_MODULES_H) SRC_O = $(SRC_C:.c=.o)
PACKAGE_NAME = batctl