Browse Source

package/libmnl: add rtnl-link-can example

The iproute2 package requires MMU support, and the BusyBox "ip link"
command does not handle CAN interfaces. The rtnl-link-can application,
added by applying the upstream patch, allows for the use of CAN devices
even on such systems.

Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com>
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
Dario Binacchi 2 years ago
parent
commit
7731440881
2 changed files with 507 additions and 1 deletions
  1. 504 0
      package/libmnl/0001-examples-add-rtnl-link-can.patch
  2. 3 1
      package/libmnl/libmnl.mk

+ 504 - 0
package/libmnl/0001-examples-add-rtnl-link-can.patch

@@ -0,0 +1,504 @@
+From 80442c4e32cde0b27b471c5c9688ee8488f14bec Mon Sep 17 00:00:00 2001
+From: Dario Binacchi <dario.binacchi@amarulasolutions.com>
+Date: Thu, 20 Apr 2023 21:21:15 +0200
+Subject: [PATCH] examples: add rtnl-link-can
+
+I developed this application to test the Linux kernel series referenced below.
+I could not use the iproute2 package since the microcontroller is without MMU.
+
+On suggestion of the Linux CAN subsystem maintainer I decided to upstream it.
+
+Cc: Marc Kleine-Budde <mkl@pengutronix.de>
+Link: https://marc.info/?l=linux-netdev&m=167999323611710&w=2
+Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com>
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Upstream: https://git.netfilter.org/libmnl/commit/?id=80442c4e32cde0b27b471c5c9688ee8488f14bec
+---
+ examples/rtnl/Makefile.am     |   4 +
+ examples/rtnl/rtnl-link-can.c | 452 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 456 insertions(+)
+ create mode 100644 examples/rtnl/rtnl-link-can.c
+
+diff --git a/examples/rtnl/Makefile.am b/examples/rtnl/Makefile.am
+index dd8a77d914b6..5a28e0965926 100644
+--- a/examples/rtnl/Makefile.am
++++ b/examples/rtnl/Makefile.am
+@@ -2,6 +2,7 @@ include $(top_srcdir)/Make_global.am
+ 
+ check_PROGRAMS = rtnl-addr-add \
+ 		 rtnl-addr-dump \
++		 rtnl-link-can \
+ 		 rtnl-link-dump rtnl-link-dump2 rtnl-link-dump3 \
+ 		 rtnl-link-event \
+ 		 rtnl-link-set \
+@@ -13,6 +14,9 @@ check_PROGRAMS = rtnl-addr-add \
+ rtnl_addr_add_SOURCES = rtnl-addr-add.c
+ rtnl_addr_add_LDADD = ../../src/libmnl.la
+ 
++rtnl_link_can_SOURCES = rtnl-link-can.c
++rtnl_link_can_LDADD = ../../src/libmnl.la
++
+ rtnl_addr_dump_SOURCES = rtnl-addr-dump.c
+ rtnl_addr_dump_LDADD = ../../src/libmnl.la
+ 
+diff --git a/examples/rtnl/rtnl-link-can.c b/examples/rtnl/rtnl-link-can.c
+new file mode 100644
+index 000000000000..8ed70d141475
+--- /dev/null
++++ b/examples/rtnl/rtnl-link-can.c
+@@ -0,0 +1,452 @@
++/* This example is placed in the public domain. */
++#include <errno.h>
++#include <limits.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++#include <time.h>
++
++#include <libmnl/libmnl.h>
++#include <linux/can/netlink.h>
++#include <linux/if.h>
++#include <linux/if_link.h>
++#include <linux/rtnetlink.h>
++
++static void incomplete_command(void) __attribute__((noreturn));
++
++#define NEXT_ARG()				\
++	do { \
++		if (argc <= 0) incomplete_command();	\
++		argv++; \
++		argc--; \
++	} while (0)
++
++static void duparg2(const char *key, const char *arg)
++{
++	fprintf(stderr,
++		"Error: either \"%s\" is duplicate, or \"%s\" is a garbage.\n",
++		key, arg);
++	exit(-1);
++}
++
++static void incomplete_command(void)
++{
++	fprintf(stderr, "Command line is not complete. Try option \"help\"\n");
++	exit(EXIT_FAILURE);
++}
++
++/* Returns false if 'prefix' is a not empty prefix of 'string'.
++ */
++static bool matches(const char *prefix, const char *string)
++{
++	if (!*prefix)
++		return true;
++
++	while (*string && *prefix == *string) {
++		prefix++;
++		string++;
++	}
++
++	return !!*prefix;
++}
++
++static int get_u16(__u16 *val, const char *arg, int base)
++{
++	unsigned long res;
++	char *ptr;
++
++	if (!arg || !*arg)
++		return -1;
++
++	res = strtoul(arg, &ptr, base);
++
++	/* empty string or trailing non-digits */
++	if (!ptr || ptr == arg || *ptr)
++		return -1;
++
++	/* overflow */
++	if (res == ULONG_MAX && errno == ERANGE)
++		return -1;
++
++	if (res > 0xFFFFUL)
++		return -1;
++
++	*val = res;
++	return 0;
++}
++
++static int get_u32(__u32 *val, const char *arg, int base)
++{
++	unsigned long res;
++	char *ptr;
++
++	if (!arg || !*arg)
++		return -1;
++
++	res = strtoul(arg, &ptr, base);
++
++	/* empty string or trailing non-digits */
++	if (!ptr || ptr == arg || *ptr)
++		return -1;
++
++	/* overflow */
++	if (res == ULONG_MAX && errno == ERANGE)
++		return -1;
++
++	/* in case UL > 32 bits */
++	if (res > 0xFFFFFFFFUL)
++		return -1;
++
++	*val = res;
++	return 0;
++}
++
++static int get_float(float *val, const char *arg)
++{
++	float res;
++	char *ptr;
++
++	if (!arg || !*arg)
++		return -1;
++
++	res = strtof(arg, &ptr);
++	if (!ptr || ptr == arg || *ptr)
++		return -1;
++
++	*val = res;
++	return 0;
++}
++
++static void set_ctrlmode(char *name, char *arg,
++			 struct can_ctrlmode *cm, __u32 flags)
++{
++	if (strcmp(arg, "on") == 0) {
++		cm->flags |= flags;
++	} else if (strcmp(arg, "off") != 0) {
++		fprintf(stderr,
++			"Error: argument of \"%s\" must be \"on\" or \"off\", not \"%s\"\n",
++			name, arg);
++		exit(EXIT_FAILURE);
++	}
++
++	cm->mask |= flags;
++}
++
++static void invarg(const char *msg, const char *arg)
++{
++	fprintf(stderr, "Error: argument \"%s\" is wrong: %s\n", arg, msg);
++	exit(-1);
++}
++
++static void print_usage(FILE *f)
++{
++	fprintf(f,
++		"Usage: ip link set DEVICE type can\n"
++		"\t[ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |\n"
++		"\t[ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1\n \t  phase-seg2 PHASE-SEG2 [ sjw SJW ] ]\n"
++		"\n"
++		"\t[ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] |\n"
++		"\t[ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1\n \t  dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ]\n"
++		"\n"
++		"\t[ loopback { on | off } ]\n"
++		"\t[ listen-only { on | off } ]\n"
++		"\t[ triple-sampling { on | off } ]\n"
++		"\t[ one-shot { on | off } ]\n"
++		"\t[ berr-reporting { on | off } ]\n"
++		"\t[ fd { on | off } ]\n"
++		"\t[ fd-non-iso { on | off } ]\n"
++		"\t[ presume-ack { on | off } ]\n"
++		"\t[ cc-len8-dlc { on | off } ]\n"
++		"\n"
++		"\t[ restart-ms TIME-MS ]\n"
++		"\t[ restart ]\n"
++		"\n"
++		"\t[ termination { 0..65535 } ]\n"
++		"\n"
++		"\tWhere: BITRATE	:= { 1..1000000 }\n"
++		"\t	  SAMPLE-POINT	:= { 0.000..0.999 }\n"
++		"\t	  TQ		:= { NUMBER }\n"
++		"\t	  PROP-SEG	:= { 1..8 }\n"
++		"\t	  PHASE-SEG1	:= { 1..8 }\n"
++		"\t	  PHASE-SEG2	:= { 1..8 }\n"
++		"\t	  SJW		:= { 1..4 }\n"
++		"\t	  RESTART-MS	:= { 0 | NUMBER }\n"
++		);
++}
++
++static void usage(void)
++{
++	print_usage(stderr);
++}
++
++static int iplink_set_can_parse(int argc, char **argv, struct nlmsghdr *nlh)
++{
++	struct can_bittiming bt = {}, dbt = {};
++	struct can_ctrlmode cm = {};
++
++	while (argc > 0) {
++		if (matches(*argv, "bitrate") == 0) {
++			NEXT_ARG();
++			if (get_u32(&bt.bitrate, *argv, 0))
++				invarg("invalid \"bitrate\" value\n", *argv);
++		} else if (matches(*argv, "sample-point") == 0) {
++			float sp;
++
++			NEXT_ARG();
++			if (get_float(&sp, *argv))
++				invarg("invalid \"sample-point\" value\n",
++				       *argv);
++
++			bt.sample_point = (__u32)(sp * 1000);
++		} else if (matches(*argv, "tq") == 0) {
++			NEXT_ARG();
++			if (get_u32(&bt.tq, *argv, 0))
++				invarg("invalid \"tq\" value\n", *argv);
++		} else if (matches(*argv, "prop-seg") == 0) {
++			NEXT_ARG();
++			if (get_u32(&bt.prop_seg, *argv, 0))
++				invarg("invalid \"prop-seg\" value\n", *argv);
++		} else if (matches(*argv, "phase-seg1") == 0) {
++			NEXT_ARG();
++			if (get_u32(&bt.phase_seg1, *argv, 0))
++				invarg("invalid \"phase-seg1\" value\n", *argv);
++		} else if (matches(*argv, "phase-seg2") == 0) {
++			NEXT_ARG();
++			if (get_u32(&bt.phase_seg2, *argv, 0))
++				invarg("invalid \"phase-seg2\" value\n", *argv);
++		} else if (matches(*argv, "sjw") == 0) {
++			NEXT_ARG();
++			if (get_u32(&bt.sjw, *argv, 0))
++				invarg("invalid \"sjw\" value\n", *argv);
++		} else if (matches(*argv, "dbitrate") == 0) {
++			NEXT_ARG();
++			if (get_u32(&dbt.bitrate, *argv, 0))
++				invarg("invalid \"dbitrate\" value\n", *argv);
++		} else if (matches(*argv, "dsample-point") == 0) {
++			float sp;
++
++			NEXT_ARG();
++			if (get_float(&sp, *argv))
++				invarg("invalid \"dsample-point\" value\n", *argv);
++			dbt.sample_point = (__u32)(sp * 1000);
++		} else if (matches(*argv, "dtq") == 0) {
++			NEXT_ARG();
++			if (get_u32(&dbt.tq, *argv, 0))
++				invarg("invalid \"dtq\" value\n", *argv);
++		} else if (matches(*argv, "dprop-seg") == 0) {
++			NEXT_ARG();
++			if (get_u32(&dbt.prop_seg, *argv, 0))
++				invarg("invalid \"dprop-seg\" value\n", *argv);
++		} else if (matches(*argv, "dphase-seg1") == 0) {
++			NEXT_ARG();
++			if (get_u32(&dbt.phase_seg1, *argv, 0))
++				invarg("invalid \"dphase-seg1\" value\n", *argv);
++		} else if (matches(*argv, "dphase-seg2") == 0) {
++			NEXT_ARG();
++			if (get_u32(&dbt.phase_seg2, *argv, 0))
++				invarg("invalid \"dphase-seg2\" value\n", *argv);
++		} else if (matches(*argv, "dsjw") == 0) {
++			NEXT_ARG();
++			if (get_u32(&dbt.sjw, *argv, 0))
++				invarg("invalid \"dsjw\" value\n", *argv);
++		} else if (matches(*argv, "loopback") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("loopback", *argv, &cm,
++				     CAN_CTRLMODE_LOOPBACK);
++		} else if (matches(*argv, "listen-only") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("listen-only", *argv, &cm,
++				     CAN_CTRLMODE_LISTENONLY);
++		} else if (matches(*argv, "triple-sampling") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("triple-sampling", *argv, &cm,
++				     CAN_CTRLMODE_3_SAMPLES);
++		} else if (matches(*argv, "one-shot") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("one-shot", *argv, &cm,
++				     CAN_CTRLMODE_ONE_SHOT);
++		} else if (matches(*argv, "berr-reporting") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("berr-reporting", *argv, &cm,
++				     CAN_CTRLMODE_BERR_REPORTING);
++		} else if (matches(*argv, "fd") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("fd", *argv, &cm,
++				     CAN_CTRLMODE_FD);
++		} else if (matches(*argv, "fd-non-iso") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("fd-non-iso", *argv, &cm,
++				     CAN_CTRLMODE_FD_NON_ISO);
++		} else if (matches(*argv, "presume-ack") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("presume-ack", *argv, &cm,
++				     CAN_CTRLMODE_PRESUME_ACK);
++#if defined(CAN_CTRLMODE_CC_LEN8_DLC)
++		} else if (matches(*argv, "cc-len8-dlc") == 0) {
++			NEXT_ARG();
++			set_ctrlmode("cc-len8-dlc", *argv, &cm,
++				     CAN_CTRLMODE_CC_LEN8_DLC);
++#endif
++		} else if (matches(*argv, "restart") == 0) {
++			__u32 val = 1;
++
++			mnl_attr_put(nlh, IFLA_CAN_RESTART, sizeof(val), &val);
++		} else if (matches(*argv, "restart-ms") == 0) {
++			__u32 val;
++
++			NEXT_ARG();
++			if (get_u32(&val, *argv, 0))
++				invarg("invalid \"restart-ms\" value\n", *argv);
++
++			mnl_attr_put(nlh, IFLA_CAN_RESTART_MS, sizeof(val), &val);
++		} else if (matches(*argv, "termination") == 0) {
++			__u16 val;
++
++			NEXT_ARG();
++			if (get_u16(&val, *argv, 0))
++				invarg("invalid \"termination\" value\n",
++				       *argv);
++
++			mnl_attr_put(nlh, IFLA_CAN_TERMINATION, sizeof(val), &val);
++		} else {
++			fprintf(stderr, "unknown option \"%s\"\n", *argv);
++			usage();
++			return -1;
++		}
++
++		NEXT_ARG();
++	}
++
++	if (bt.bitrate || bt.tq)
++		mnl_attr_put(nlh, IFLA_CAN_BITTIMING, sizeof(bt), &bt);
++
++	if (cm.mask)
++		mnl_attr_put(nlh, IFLA_CAN_CTRLMODE, sizeof(cm), &cm);
++
++	return 0;
++}
++
++int main(int argc, char *argv[])
++{
++	char buf[MNL_SOCKET_BUFFER_SIZE];
++	struct mnl_socket *nl;
++	struct nlmsghdr *nlh;
++	struct ifinfomsg *ifm;
++	int ret;
++	unsigned int seq, portid;
++	struct nlattr *linkinfo, *data;
++	const char *signatures[] = {
++		"ip", "link", "set", ""
++	};
++	char *type = NULL;
++	char *dev = NULL;
++	int i;
++
++	NEXT_ARG();
++	for (i = 0; argc > 0 && signatures[i][0];) {
++		if (matches(*argv, signatures[i]))
++			incomplete_command();
++
++		NEXT_ARG();
++		i++;
++	}
++
++	if (argc == 0)
++		incomplete_command();
++
++	nlh = mnl_nlmsg_put_header(buf);
++	nlh->nlmsg_type	= RTM_NEWLINK;
++	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
++	nlh->nlmsg_seq = seq = time(NULL);
++	ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
++	ifm->ifi_family = AF_UNSPEC;
++	ifm->ifi_change = 0;
++	ifm->ifi_flags = 0;
++
++	while (argc > 0) {
++		if (matches(*argv, "up") == 0) {
++			ifm->ifi_change |= IFF_UP;
++			ifm->ifi_flags |= IFF_UP;
++		} else if (matches(*argv, "down") == 0) {
++			ifm->ifi_change |= IFF_UP;
++			ifm->ifi_flags &= ~IFF_UP;
++		} else if (matches(*argv, "type") == 0) {
++			NEXT_ARG();
++			type = *argv;
++			NEXT_ARG();
++			break;
++		} else if (matches(*argv, "help") == 0) {
++			usage();
++			exit(EXIT_FAILURE);
++		} else {
++			if (matches(*argv, "dev") == 0)
++				NEXT_ARG();
++
++			if (dev)
++				duparg2("dev", *argv);
++
++			dev = *argv;
++		}
++
++		NEXT_ARG();
++	}
++
++	if (dev)
++		mnl_attr_put_str(nlh, IFLA_IFNAME, dev);
++
++	if (type) {
++		if (matches(type, "can")) {
++			fprintf(stderr, "unknown type \"%s\"\n", type);
++			usage();
++			exit(EXIT_FAILURE);
++		}
++
++		linkinfo = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
++		mnl_attr_put_str(nlh, IFLA_INFO_KIND, "can");
++		data = mnl_attr_nest_start(nlh, IFLA_INFO_DATA);
++
++		if (iplink_set_can_parse(argc, argv, nlh))
++			return -1;
++
++		mnl_attr_nest_end(nlh, data);
++		mnl_attr_nest_end(nlh, linkinfo);
++	}
++
++	nl = mnl_socket_open(NETLINK_ROUTE);
++	if (nl == NULL) {
++		perror("mnl_socket_open");
++		exit(EXIT_FAILURE);
++	}
++
++	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
++		perror("mnl_socket_bind");
++		exit(EXIT_FAILURE);
++	}
++
++	portid = mnl_socket_get_portid(nl);
++
++	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len,
++			  sizeof(struct ifinfomsg));
++
++	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
++		perror("mnl_socket_sendto");
++		exit(EXIT_FAILURE);
++	}
++
++	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
++	if (ret == -1) {
++		perror("mnl_socket_recvfrom");
++		exit(EXIT_FAILURE);
++	}
++
++	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
++	if (ret == -1) {
++		perror("mnl_cb_run");
++		exit(EXIT_FAILURE);
++	}
++
++	mnl_socket_close(nl);
++
++	return 0;
++}
+-- 
+2.32.0
+

+ 3 - 1
package/libmnl/libmnl.mk

@@ -11,6 +11,8 @@ LIBMNL_INSTALL_STAGING = YES
 LIBMNL_LICENSE = LGPL-2.1+
 LIBMNL_LICENSE_FILES = COPYING
 LIBMNL_CPE_ID_VENDOR = netfilter
+# 0001-examples-add-rtnl-link-can.patch patches Makefile.am
+LIBMNL_AUTORECONF = YES
 
 ifeq ($(BR2_PACKAGE_LIBMNL_EXAMPLES),y)
 define LIBMNL_EXAMPLES_BUILD_CMDS
@@ -28,7 +30,7 @@ LIBMNL_EXAMPLES_INSTALL_TARGETS += \
 		nf-queue)
 LIBMNL_EXAMPLES_INSTALL_TARGETS += \
 	$(addprefix examples/rtnl/, rtnl-addr-add rtnl-addr-dump \
-		rtnl-link-dump rtnl-link-dump2 rtnl-link-dump3 \
+		rtnl-link-can rtnl-link-dump rtnl-link-dump2 rtnl-link-dump3 \
 		rtnl-link-event rtnl-link-set rtnl-neigh-dump \
 		rtnl-route-add rtnl-route-dump rtnl-route-event)