canbusload: add auto detection of CAN interfaces

Rework to use a single CAN socket for the received CAN traffic.
This allows to shutdown and restart various CAN interfaces without
terminating the application.
Additionally an auto detection has been implemented with the 'any' CAN
interface. E.g. with any@500000,2000000 CAN interfaces that have not
been defined before are assigned with the given 'any' bitrates when a
CAN frame from this CAN interface has been received.

Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
pull/556/head
Oliver Hartkopp 2024-08-07 19:57:08 +02:00
parent b592ec5b1f
commit 7665e1e236
1 changed files with 107 additions and 63 deletions

View File

@ -65,7 +65,8 @@
#include "terminal.h" #include "terminal.h"
#include "canframelen.h" #include "canframelen.h"
#define MAXSOCK 16 /* max. number of CAN interfaces given on the cmdline */ #define ANYDEV "any" /* name of interface to receive from any CAN interface */
#define MAXDEVS 20 /* max. number of CAN interfaces given on the cmdline */
#define PERCENTRES 5 /* resolution in percent for bargraph */ #define PERCENTRES 5 /* resolution in percent for bargraph */
#define NUMBAR (100 / PERCENTRES) /* number of bargraph elements */ #define NUMBAR (100 / PERCENTRES) /* number of bargraph elements */
@ -74,13 +75,14 @@ extern int optind, opterr, optopt;
static struct { static struct {
char devname[IFNAMSIZ + 1]; char devname[IFNAMSIZ + 1];
int ifindex;
unsigned int bitrate; unsigned int bitrate;
unsigned int dbitrate; unsigned int dbitrate;
unsigned int recv_frames; unsigned int recv_frames;
unsigned int recv_bits_total; unsigned int recv_bits_total;
unsigned int recv_bits_payload; unsigned int recv_bits_payload;
unsigned int recv_bits_dbitrate; unsigned int recv_bits_dbitrate;
} stat[MAXSOCK + 1]; } stat[MAXDEVS + 1];
static volatile int running = 1; static volatile int running = 1;
static volatile sig_atomic_t signal_num; static volatile sig_atomic_t signal_num;
@ -107,8 +109,9 @@ static void print_usage(char *prg)
fprintf(stderr, " -i (ignore bitstuffing in bandwidth calculation)\n"); fprintf(stderr, " -i (ignore bitstuffing in bandwidth calculation)\n");
fprintf(stderr, " -e (exact calculation of stuffed bits)\n"); fprintf(stderr, " -e (exact calculation of stuffed bits)\n");
fprintf(stderr, "\n"); fprintf(stderr, "\n");
fprintf(stderr, "Up to %d CAN interfaces with mandatory bitrate can be specified on the \n", MAXSOCK); fprintf(stderr, "Up to %d CAN interfaces with mandatory bitrate can be specified on the \n", MAXDEVS);
fprintf(stderr, "commandline in the form: <ifname>@<bitrate>[,<dbitrate>]\n\n"); fprintf(stderr, "commandline in the form: <ifname>@<bitrate>[,<dbitrate>]\n");
fprintf(stderr, "The interface name 'any' enables an auto detection with the given bitrate[s]\n\n");
fprintf(stderr, "The bitrate is mandatory as it is needed to know the CAN bus bitrate to\n"); fprintf(stderr, "The bitrate is mandatory as it is needed to know the CAN bus bitrate to\n");
fprintf(stderr, "calculate the bus load percentage based on the received CAN frames.\n"); fprintf(stderr, "calculate the bus load percentage based on the received CAN frames.\n");
fprintf(stderr, "Due to the bitstuffing estimation the calculated busload may exceed 100%%.\n"); fprintf(stderr, "Due to the bitstuffing estimation the calculated busload may exceed 100%%.\n");
@ -243,14 +246,16 @@ static void printstats(int signo)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
fd_set rdfs; fd_set rdfs;
int s[MAXSOCK]; int s;
int opt; int opt;
char *ptr, *nptr; char *ptr, *nptr;
struct sockaddr_can addr; struct sockaddr_can addr;
struct canfd_frame frame; struct canfd_frame frame;
int nbytes, i; int nbytes, i;
struct ifreq ifr;
int have_anydev = 0;
unsigned int anydev_bitrate = 0;
unsigned int anydev_dbitrate = 0;
signal(SIGTERM, sigterm); signal(SIGTERM, sigterm);
signal(SIGHUP, sigterm); signal(SIGHUP, sigterm);
@ -300,13 +305,14 @@ int main(int argc, char **argv)
currmax = argc - optind; /* find real number of CAN devices */ currmax = argc - optind; /* find real number of CAN devices */
if (currmax > MAXSOCK) { if (currmax > MAXDEVS) {
printf("More than %d CAN devices given on commandline!\n", MAXSOCK); printf("More than %d CAN devices given on commandline!\n", MAXDEVS);
return 1; return 1;
} }
/* prefill stat[] array with given interface assignments */
for (i = 0; i < currmax; i++) { for (i = 0; i < currmax; i++) {
ptr = argv[optind + i]; ptr = argv[optind + i + have_anydev];
nbytes = strlen(ptr); nbytes = strlen(ptr);
if (nbytes >= (int)(IFNAMSIZ + sizeof("@1000000,2000000") + 1)) { if (nbytes >= (int)(IFNAMSIZ + sizeof("@1000000,2000000") + 1)) {
@ -314,13 +320,7 @@ int main(int argc, char **argv)
return 1; return 1;
} }
pr_debug("open %d '%s'.\n", i, ptr); pr_debug("handle %d '%s'.\n", i, ptr);
s[i] = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (s[i] < 0) {
perror("socket");
return 1;
}
nptr = strchr(ptr, '@'); nptr = strchr(ptr, '@');
@ -330,22 +330,24 @@ int main(int argc, char **argv)
return 1; return 1;
} }
/* interface name length */
nbytes = nptr - ptr; /* interface name is up the first '@' */ nbytes = nptr - ptr; /* interface name is up the first '@' */
if (nbytes >= (int)IFNAMSIZ) { if (nbytes >= (int)IFNAMSIZ) {
printf("name of CAN device '%s' is too long!\n", ptr); printf("name of CAN device '%s' is too long!\n", ptr);
return 1; return 1;
} }
/* copy interface name to stat[] entry */
strncpy(stat[i].devname, ptr, nbytes); strncpy(stat[i].devname, ptr, nbytes);
memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
strncpy(ifr.ifr_name, ptr, nbytes);
if (nbytes > max_devname_len) if (nbytes > max_devname_len)
max_devname_len = nbytes; /* for nice printing */ max_devname_len = nbytes; /* for nice printing */
char *endp; char *endp;
stat[i].bitrate = strtol(nptr + 1, &endp, 0); /* bitrate is placed behind the '@' */ /* bitrate is placed behind the '@' */
stat[i].bitrate = strtol(nptr + 1, &endp, 0);
/* check for CAN FD additional data bitrate */
if (*endp == ',') if (*endp == ',')
/* data bitrate is placed behind the ',' */ /* data bitrate is placed behind the ',' */
stat[i].dbitrate = strtol(endp + 1, &endp, 0); stat[i].dbitrate = strtol(endp + 1, &endp, 0);
@ -357,28 +359,45 @@ int main(int argc, char **argv)
return 1; return 1;
} }
/* length of entire (data)bitrate description */
nbytes = strlen(nptr + 1); nbytes = strlen(nptr + 1);
if (nbytes > max_bitrate_len) if (nbytes > max_bitrate_len)
max_bitrate_len = nbytes; /* for nice printing */ max_bitrate_len = nbytes; /* for nice printing */
pr_debug("using interface name '%s'.\n", ifr.ifr_name); /* handling for 'any' device */
if (have_anydev == 0 && strcmp(ANYDEV, stat[i].devname) == 0) {
/* try to switch the socket into CAN FD mode */ anydev_bitrate = stat[i].bitrate;
const int canfd_on = 1; anydev_dbitrate = stat[i].dbitrate;
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on)); /* no real interface: remove this command line entry */
have_anydev = 1;
if (ioctl(s[i], SIOCGIFINDEX, &ifr) < 0) { currmax--;
perror("SIOCGIFINDEX"); i--;
exit(1); } else {
stat[i].ifindex = if_nametoindex(stat[i].devname);
if (!stat[i].ifindex) {
printf("invalid CAN device '%s'!\n", stat[i].devname);
return 1;
}
pr_debug("using interface name '%s'.\n", stat[i].devname);
} }
}
addr.can_family = AF_CAN; s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
addr.can_ifindex = ifr.ifr_ifindex; if (s < 0) {
perror("socket");
return 1;
}
if (bind(s[i], (struct sockaddr *)&addr, sizeof(addr)) < 0) { /* try to switch the socket into CAN FD mode */
perror("bind"); const int canfd_on = 1;
return 1; setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on));
}
addr.can_family = AF_CAN;
addr.can_ifindex = 0; /* any CAN device */
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
return 1;
} }
alarm(1); alarm(1);
@ -387,42 +406,67 @@ int main(int argc, char **argv)
printf("%s", CLR_SCREEN); printf("%s", CLR_SCREEN);
while (running) { while (running) {
FD_ZERO(&rdfs); socklen_t len = sizeof(addr);
for (i = 0; i < currmax; i++) int flags = 0;
FD_SET(s[i], &rdfs);
if (select(s[currmax - 1] + 1, &rdfs, NULL, NULL, NULL) < 0) { FD_ZERO(&rdfs);
FD_SET(s, &rdfs);
if (select(s + 1, &rdfs, NULL, NULL, NULL) < 0) {
//perror("pselect"); //perror("pselect");
continue; continue;
} }
for (i = 0; i < currmax; i++) { /* check all CAN RAW sockets */ nbytes = recvfrom(s, &frame, sizeof(struct canfd_frame),
flags, (struct sockaddr*)&addr, &len);
if (FD_ISSET(s[i], &rdfs)) { if (nbytes < 0) {
nbytes = read(s[i], &frame, sizeof(frame)); perror("read");
return 1;
if (nbytes < 0) {
perror("read");
return 1;
}
if (nbytes < (int)sizeof(struct can_frame)) {
fprintf(stderr, "read: incomplete CAN frame\n");
return 1;
}
stat[i].recv_frames++;
stat[i].recv_bits_payload += frame.len * 8;
stat[i].recv_bits_dbitrate += can_frame_dbitrate_length(
&frame, mode, sizeof(frame));
stat[i].recv_bits_total += can_frame_length(&frame,
mode, nbytes);
}
} }
if (nbytes != (int)sizeof(struct can_frame) &&
nbytes != (int)sizeof(struct canfd_frame)) {
fprintf(stderr, "read: incomplete CAN frame\n");
return 1;
}
/* find received ifindex in stat[] array */
for (i = 0; i < currmax; i++) {
if (stat[i].ifindex == addr.can_ifindex)
break;
}
/* not found? check for unknown interface */
if (i >= currmax) {
/* drop unwanted traffic */
if (have_anydev == 0)
continue;
/* can we add another interface? */
if (currmax >= MAXDEVS)
continue;
/* add an new entry */
stat[i].ifindex = addr.can_ifindex;
stat[i].bitrate = anydev_bitrate;
stat[i].dbitrate = anydev_dbitrate;
if_indextoname(addr.can_ifindex, stat[i].devname);
nbytes = strlen(stat[i].devname);
if (nbytes > max_devname_len)
max_devname_len = nbytes; /* for nice printing */
currmax++;
}
stat[i].recv_frames++;
stat[i].recv_bits_payload += frame.len * 8;
stat[i].recv_bits_dbitrate += can_frame_dbitrate_length(
&frame, mode, sizeof(frame));
stat[i].recv_bits_total += can_frame_length(&frame,
mode, nbytes);
} }
for (i = 0; i < currmax; i++) close(s);
close(s[i]);
if (signal_num) if (signal_num)
return 128 + signal_num; return 128 + signal_num;