Netdev Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release
@ 2020-02-16 22:46 Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 01/19] move UAPI header copies to a separate directory Michal Kubecek
                   ` (18 more replies)
  0 siblings, 19 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:46 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

This series adds initial support for ethtool netlink interface provided by
kernel since 5.6-rc1. The traditional ioctl interface is still supported
for compatibility with older kernels. The netlink interface and message
formats are documented in Documentation/networking/ethtool-netlink.rst file
in kernel source tree.

Netlink interface is preferred but ethtool falls back to ioctl if netlink
interface is not available (i.e. the "ethtool" genetlink family is not
registered). It also falls back if a particular command is not implemented
in netlink (kernel returns -EOPNOTSUPP). This allows new ethtool versions
to work with older kernel versions while support for ethool commands is
added in steps.

The series aims to touch existing ioctl code as little as possible in the
first phase to minimize the risk of introducing regressions. It is also
possible to build ethtool without netlink support if --disable-netlink is
passed to configure script. The most visible changes to existing code are

  - UAPI header copies are moved to uapi/ under original names
  - some variables and functions which are going to be shared with netlink
    code are moved from ethtool.c to common.c and common.h
  - args[] array in ethtool.c was rewritten to use named initializers

Except for changes to main(), all netlink specific code is in a separate
directory netlink/ and is divided into multiple files.

Michal Kubecek (19):
  move UAPI header copies to a separate directory
  update UAPI header copies
  add --debug option to control debugging messages
  use named initializers in command line option list
  netlink: add netlink related UAPI header files
  netlink: introduce the netlink interface
  netlink: message buffer and composition helpers
  netlink: netlink socket wrapper and helpers
  netlink: initialize ethtool netlink socket
  netlink: add support for string sets
  netlink: add notification monitor
  move shared code into a common file
  netlink: add bitset helpers
  netlink: partial netlink handler for gset (no option)
  netlink: support getting wake-on-lan and debugging settings
  netlink: add basic command line parsing helpers
  netlink: add bitset command line parser handlers
  netlink: add netlink handler for sset (-s)
  netlink: support tests with netlink enabled

 Makefile.am                                  |   27 +-
 common.c                                     |  145 +++
 common.h                                     |   26 +
 configure.ac                                 |   14 +-
 ethtool.8.in                                 |   48 +-
 ethtool.c                                    |  818 ++++++++------
 internal.h                                   |   30 +-
 netlink/bitset.c                             |  201 ++++
 netlink/bitset.h                             |   25 +
 netlink/extapi.h                             |   44 +
 netlink/monitor.c                            |  229 ++++
 netlink/msgbuff.c                            |  169 +++
 netlink/msgbuff.h                            |  106 ++
 netlink/netlink.c                            |  216 ++++
 netlink/netlink.h                            |   78 ++
 netlink/nlsock.c                             |  293 +++++
 netlink/nlsock.h                             |   35 +
 netlink/parser.c                             | 1058 ++++++++++++++++++
 netlink/parser.h                             |  144 +++
 netlink/settings.c                           |  945 ++++++++++++++++
 netlink/strset.c                             |  297 +++++
 netlink/strset.h                             |   25 +
 test-cmdline.c                               |   29 +-
 test-features.c                              |   11 +
 ethtool-copy.h => uapi/linux/ethtool.h       |   17 +
 uapi/linux/ethtool_netlink.h                 |  237 ++++
 uapi/linux/genetlink.h                       |   89 ++
 net_tstamp-copy.h => uapi/linux/net_tstamp.h |   27 +
 uapi/linux/netlink.h                         |  248 ++++
 29 files changed, 5250 insertions(+), 381 deletions(-)
 create mode 100644 common.c
 create mode 100644 common.h
 create mode 100644 netlink/bitset.c
 create mode 100644 netlink/bitset.h
 create mode 100644 netlink/extapi.h
 create mode 100644 netlink/monitor.c
 create mode 100644 netlink/msgbuff.c
 create mode 100644 netlink/msgbuff.h
 create mode 100644 netlink/netlink.c
 create mode 100644 netlink/netlink.h
 create mode 100644 netlink/nlsock.c
 create mode 100644 netlink/nlsock.h
 create mode 100644 netlink/parser.c
 create mode 100644 netlink/parser.h
 create mode 100644 netlink/settings.c
 create mode 100644 netlink/strset.c
 create mode 100644 netlink/strset.h
 rename ethtool-copy.h => uapi/linux/ethtool.h (99%)
 create mode 100644 uapi/linux/ethtool_netlink.h
 create mode 100644 uapi/linux/genetlink.h
 rename net_tstamp-copy.h => uapi/linux/net_tstamp.h (84%)
 create mode 100644 uapi/linux/netlink.h

-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 01/19] move UAPI header copies to a separate directory
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
@ 2020-02-16 22:46 ` Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 02/19] update UAPI header copies Michal Kubecek
                   ` (17 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:46 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

The upcoming netlink series is going to add more local copies of kernel
UAPI header files and some of them are going to include others. Keeping
them in the main directory under modified name would require modifying
those includes as well which would be impractical.

Create a subdirectory uapi and move the UAPI headers there to allow
including them in the usual way.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am                                  | 10 +++++-----
 internal.h                                   |  4 ++--
 ethtool-copy.h => uapi/linux/ethtool.h       |  0
 net_tstamp-copy.h => uapi/linux/net_tstamp.h |  0
 4 files changed, 7 insertions(+), 7 deletions(-)
 rename ethtool-copy.h => uapi/linux/ethtool.h (100%)
 rename net_tstamp-copy.h => uapi/linux/net_tstamp.h (100%)

diff --git a/Makefile.am b/Makefile.am
index 3af4d4c2b5da..05beb22be669 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,12 @@
-AM_CFLAGS = -Wall
+AM_CFLAGS = -I./uapi -Wall
 LDADD = -lm
 
 man_MANS = ethtool.8
 EXTRA_DIST = LICENSE ethtool.8 ethtool.spec.in aclocal.m4 ChangeLog autogen.sh
 
 sbin_PROGRAMS = ethtool
-ethtool_SOURCES = ethtool.c ethtool-copy.h internal.h net_tstamp-copy.h \
-		  rxclass.c
+ethtool_SOURCES = ethtool.c uapi/linux/ethtool.h internal.h \
+		  uapi/linux/net_tstamp.h rxclass.c
 if ETHTOOL_ENABLE_PRETTY_DUMP
 ethtool_SOURCES += \
 		  amd8111e.c de2104x.c dsa.c e100.c e1000.c et131x.c igb.c	\
@@ -25,9 +25,9 @@ endif
 TESTS = test-cmdline test-features
 check_PROGRAMS = test-cmdline test-features
 test_cmdline_SOURCES = test-cmdline.c test-common.c $(ethtool_SOURCES) 
-test_cmdline_CFLAGS = -DTEST_ETHTOOL
+test_cmdline_CFLAGS = $(AM_FLAGS) -DTEST_ETHTOOL
 test_features_SOURCES = test-features.c test-common.c $(ethtool_SOURCES) 
-test_features_CFLAGS = -DTEST_ETHTOOL
+test_features_CFLAGS = $(AM_FLAGS) -DTEST_ETHTOOL
 
 dist-hook:
 	cp $(top_srcdir)/ethtool.spec $(distdir)
diff --git a/internal.h b/internal.h
index ff52c6e7660c..527245633338 100644
--- a/internal.h
+++ b/internal.h
@@ -44,8 +44,8 @@ typedef int32_t s32;
 #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
 #endif
 
-#include "ethtool-copy.h"
-#include "net_tstamp-copy.h"
+#include <linux/ethtool.h>
+#include <linux/net_tstamp.h>
 
 #if __BYTE_ORDER == __BIG_ENDIAN
 static inline u16 cpu_to_be16(u16 value)
diff --git a/ethtool-copy.h b/uapi/linux/ethtool.h
similarity index 100%
rename from ethtool-copy.h
rename to uapi/linux/ethtool.h
diff --git a/net_tstamp-copy.h b/uapi/linux/net_tstamp.h
similarity index 100%
rename from net_tstamp-copy.h
rename to uapi/linux/net_tstamp.h
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 02/19] update UAPI header copies
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 01/19] move UAPI header copies to a separate directory Michal Kubecek
@ 2020-02-16 22:46 ` Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 03/19] add --debug option to control debugging messages Michal Kubecek
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:46 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Update to v5.6-rc1.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 uapi/linux/ethtool.h    | 17 +++++++++++++++++
 uapi/linux/net_tstamp.h | 27 +++++++++++++++++++++++++++
 2 files changed, 44 insertions(+)

diff --git a/uapi/linux/ethtool.h b/uapi/linux/ethtool.h
index 9afd2e6c5eea..16198061d723 100644
--- a/uapi/linux/ethtool.h
+++ b/uapi/linux/ethtool.h
@@ -591,6 +591,9 @@ struct ethtool_pauseparam {
  * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names
  * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS
  * @ETH_SS_PHY_TUNABLES: PHY tunable names
+ * @ETH_SS_LINK_MODES: link mode names
+ * @ETH_SS_MSG_CLASSES: debug message class names
+ * @ETH_SS_WOL_MODES: wake-on-lan modes
  */
 enum ethtool_stringset {
 	ETH_SS_TEST		= 0,
@@ -602,6 +605,12 @@ enum ethtool_stringset {
 	ETH_SS_TUNABLES,
 	ETH_SS_PHY_STATS,
 	ETH_SS_PHY_TUNABLES,
+	ETH_SS_LINK_MODES,
+	ETH_SS_MSG_CLASSES,
+	ETH_SS_WOL_MODES,
+
+	/* add new constants above here */
+	ETH_SS_COUNT
 };
 
 /**
@@ -1505,6 +1514,11 @@ enum ethtool_link_mode_bit_indices {
 	ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT	 = 66,
 	ETHTOOL_LINK_MODE_100baseT1_Full_BIT		 = 67,
 	ETHTOOL_LINK_MODE_1000baseT1_Full_BIT		 = 68,
+	ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT	 = 69,
+	ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT	 = 70,
+	ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT = 71,
+	ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT	 = 72,
+	ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT	 = 73,
 
 	/* must be last entry */
 	__ETHTOOL_LINK_MODE_MASK_NBITS
@@ -1616,6 +1630,7 @@ enum ethtool_link_mode_bit_indices {
 #define SPEED_56000		56000
 #define SPEED_100000		100000
 #define SPEED_200000		200000
+#define SPEED_400000		400000
 
 #define SPEED_UNKNOWN		-1
 
@@ -1680,6 +1695,8 @@ static __inline__ int ethtool_validate_duplex(__u8 duplex)
 #define WAKE_MAGICSECURE	(1 << 6) /* only meaningful if WAKE_MAGIC */
 #define WAKE_FILTER		(1 << 7)
 
+#define WOL_MODE_COUNT		8
+
 /* L2-L4 network traffic flow types */
 #define	TCP_V4_FLOW	0x01	/* hash or spec (tcp_ip4_spec) */
 #define	UDP_V4_FLOW	0x02	/* hash or spec (udp_ip4_spec) */
diff --git a/uapi/linux/net_tstamp.h b/uapi/linux/net_tstamp.h
index 3d421d912193..f96e650d0af9 100644
--- a/uapi/linux/net_tstamp.h
+++ b/uapi/linux/net_tstamp.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
 /*
  * Userspace API for hardware time stamping of network packets
  *
@@ -89,6 +90,14 @@ enum hwtstamp_tx_types {
 	 * queue.
 	 */
 	HWTSTAMP_TX_ONESTEP_SYNC,
+
+	/*
+	 * Same as HWTSTAMP_TX_ONESTEP_SYNC, but also enables time
+	 * stamp insertion directly into PDelay_Resp packets. In this
+	 * case, neither transmitted Sync nor PDelay_Resp packets will
+	 * receive a time stamp via the socket error queue.
+	 */
+	HWTSTAMP_TX_ONESTEP_P2P,
 };
 
 /* possible values for hwtstamp_config->rx_filter */
@@ -140,4 +149,22 @@ struct scm_ts_pktinfo {
 	__u32 reserved[2];
 };
 
+/*
+ * SO_TXTIME gets a struct sock_txtime with flags being an integer bit
+ * field comprised of these values.
+ */
+enum txtime_flags {
+	SOF_TXTIME_DEADLINE_MODE = (1 << 0),
+	SOF_TXTIME_REPORT_ERRORS = (1 << 1),
+
+	SOF_TXTIME_FLAGS_LAST = SOF_TXTIME_REPORT_ERRORS,
+	SOF_TXTIME_FLAGS_MASK = (SOF_TXTIME_FLAGS_LAST - 1) |
+				 SOF_TXTIME_FLAGS_LAST
+};
+
+struct sock_txtime {
+	__kernel_clockid_t	clockid;/* reference clockid */
+	__u32			flags;	/* as defined by enum txtime_flags */
+};
+
 #endif /* _NET_TIMESTAMPING_H */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 03/19] add --debug option to control debugging messages
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 01/19] move UAPI header copies to a separate directory Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 02/19] update UAPI header copies Michal Kubecek
@ 2020-02-16 22:46 ` Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 04/19] use named initializers in command line option list Michal Kubecek
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:46 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Introduce --debug option to control which debugging messages will be shown.
Argument is a number which is interpreted as bit mask; default value is
zero (i.e. no debug messages).

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.8.in | 12 ++++++++++++
 ethtool.c    | 18 ++++++++++++++++--
 internal.h   |  6 ++++++
 3 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/ethtool.8.in b/ethtool.8.in
index 94364c626330..680cad9fbb8f 100644
--- a/ethtool.8.in
+++ b/ethtool.8.in
@@ -129,6 +129,10 @@ ethtool \- query or control network driver and hardware settings
 .HP
 .B ethtool \-\-version
 .HP
+.B ethtool
+.BN --debug
+.I args
+.HP
 .B ethtool \-a|\-\-show\-pause
 .I devname
 .HP
@@ -437,6 +441,14 @@ Shows a short help message.
 .B \-\-version
 Shows the ethtool version number.
 .TP
+.BI \-\-debug \ N
+Turns on debugging messages. Argument is interpreted as a mask:
+.TS
+nokeep;
+lB	l.
+0x01  Parser information
+.TE
+.TP
 .B \-a \-\-show\-pause
 Queries the specified Ethernet device for pause parameter information.
 .TP
diff --git a/ethtool.c b/ethtool.c
index acf183dc5586..dd0242b27a2f 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5493,10 +5493,10 @@ static int show_usage(struct cmd_context *ctx maybe_unused)
 	fprintf(stdout, PACKAGE " version " VERSION "\n");
 	fprintf(stdout,
 		"Usage:\n"
-		"        ethtool DEVNAME\t"
+		"        ethtool [ --debug MASK ] DEVNAME\t"
 		"Display standard information about device\n");
 	for (i = 0; args[i].opts; i++) {
-		fputs("        ethtool ", stdout);
+		fputs("        ethtool [ --debug MASK ] ", stdout);
 		fprintf(stdout, "%s %s\t%s\n",
 			args[i].opts,
 			args[i].want_device ? "DEVNAME" : "\t",
@@ -5712,6 +5712,20 @@ int main(int argc, char **argp)
 	argp++;
 	argc--;
 
+	ctx.debug = 0;
+	if (*argp && !strcmp(*argp, "--debug")) {
+		char *eptr;
+
+		if (argc < 2)
+			exit_bad_args();
+		ctx.debug = strtoul(argp[1], &eptr, 0);
+		if (!argp[1][0] || *eptr)
+			exit_bad_args();
+
+		argp += 2;
+		argc -= 2;
+	}
+
 	/* First argument must be either a valid option or a device
 	 * name to get settings for (which we don't expect to begin
 	 * with '-').
diff --git a/internal.h b/internal.h
index 527245633338..9ec145f55dcb 100644
--- a/internal.h
+++ b/internal.h
@@ -107,6 +107,11 @@ static inline int test_bit(unsigned int nr, const unsigned long *addr)
 #define SIOCETHTOOL     0x8946
 #endif
 
+/* debugging flags */
+enum {
+	DEBUG_PARSE,
+};
+
 /* Internal values for old-style offload flags.  Values and names
  * must not clash with the flags defined for ETHTOOL_{G,S}FLAGS.
  */
@@ -197,6 +202,7 @@ struct cmd_context {
 	struct ifreq ifr;	/* ifreq suitable for ethtool ioctl */
 	int argc;		/* number of arguments to the sub-command */
 	char **argp;		/* arguments to the sub-command */
+	unsigned long debug;	/* debugging mask */
 };
 
 #ifdef TEST_ETHTOOL
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 04/19] use named initializers in command line option list
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (2 preceding siblings ...)
  2020-02-16 22:46 ` [PATCH ethtool 03/19] add --debug option to control debugging messages Michal Kubecek
@ 2020-02-16 22:46 ` Michal Kubecek
  2020-02-16 22:46 ` [PATCH ethtool 05/19] netlink: add netlink related UAPI header files Michal Kubecek
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:46 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

The command line option list initialization does not look very nice and
adding another struct member would not make it look any better.

Reformat it to use named initializers. Also replace want_device with (bool)
no_dev as only two options have want_device = 0 so that using an opposite
would allow omitting the initializer for almost all options.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.c | 559 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 358 insertions(+), 201 deletions(-)

diff --git a/ethtool.c b/ethtool.c
index dd0242b27a2f..3186a72644fd 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5288,200 +5288,357 @@ int send_ioctl(struct cmd_context *ctx, void *cmd)
 
 static int show_usage(struct cmd_context *ctx);
 
-static const struct option {
-	const char *opts;
-	int want_device;
-	int (*func)(struct cmd_context *);
-	char *help;
-	char *opthelp;
-} args[] = {
-	{ "-s|--change", 1, do_sset, "Change generic options",
-	  "		[ speed %d ]\n"
-	  "		[ duplex half|full ]\n"
-	  "		[ port tp|aui|bnc|mii|fibre ]\n"
-	  "		[ mdix auto|on|off ]\n"
-	  "		[ autoneg on|off ]\n"
-	  "		[ advertise %x ]\n"
-	  "		[ phyad %d ]\n"
-	  "		[ xcvr internal|external ]\n"
-	  "		[ wol p|u|m|b|a|g|s|f|d... ]\n"
-	  "		[ sopass %x:%x:%x:%x:%x:%x ]\n"
-	  "		[ msglvl %d | msglvl type on|off ... ]\n" },
-	{ "-a|--show-pause", 1, do_gpause, "Show pause options" },
-	{ "-A|--pause", 1, do_spause, "Set pause options",
-	  "		[ autoneg on|off ]\n"
-	  "		[ rx on|off ]\n"
-	  "		[ tx on|off ]\n" },
-	{ "-c|--show-coalesce", 1, do_gcoalesce, "Show coalesce options" },
-	{ "-C|--coalesce", 1, do_scoalesce, "Set coalesce options",
-	  "		[adaptive-rx on|off]\n"
-	  "		[adaptive-tx on|off]\n"
-	  "		[rx-usecs N]\n"
-	  "		[rx-frames N]\n"
-	  "		[rx-usecs-irq N]\n"
-	  "		[rx-frames-irq N]\n"
-	  "		[tx-usecs N]\n"
-	  "		[tx-frames N]\n"
-	  "		[tx-usecs-irq N]\n"
-	  "		[tx-frames-irq N]\n"
-	  "		[stats-block-usecs N]\n"
-	  "		[pkt-rate-low N]\n"
-	  "		[rx-usecs-low N]\n"
-	  "		[rx-frames-low N]\n"
-	  "		[tx-usecs-low N]\n"
-	  "		[tx-frames-low N]\n"
-	  "		[pkt-rate-high N]\n"
-	  "		[rx-usecs-high N]\n"
-	  "		[rx-frames-high N]\n"
-	  "		[tx-usecs-high N]\n"
-	  "		[tx-frames-high N]\n"
-	  "		[sample-interval N]\n" },
-	{ "-g|--show-ring", 1, do_gring, "Query RX/TX ring parameters" },
-	{ "-G|--set-ring", 1, do_sring, "Set RX/TX ring parameters",
-	  "		[ rx N ]\n"
-	  "		[ rx-mini N ]\n"
-	  "		[ rx-jumbo N ]\n"
-	  "		[ tx N ]\n" },
-	{ "-k|--show-features|--show-offload", 1, do_gfeatures,
-	  "Get state of protocol offload and other features" },
-	{ "-K|--features|--offload", 1, do_sfeatures,
-	  "Set protocol offload and other features",
-	  "		FEATURE on|off ...\n" },
-	{ "-i|--driver", 1, do_gdrv, "Show driver information" },
-	{ "-d|--register-dump", 1, do_gregs, "Do a register dump",
-	  "		[ raw on|off ]\n"
-	  "		[ file FILENAME ]\n" },
-	{ "-e|--eeprom-dump", 1, do_geeprom, "Do a EEPROM dump",
-	  "		[ raw on|off ]\n"
-	  "		[ offset N ]\n"
-	  "		[ length N ]\n" },
-	{ "-E|--change-eeprom", 1, do_seeprom,
-	  "Change bytes in device EEPROM",
-	  "		[ magic N ]\n"
-	  "		[ offset N ]\n"
-	  "		[ length N ]\n"
-	  "		[ value N ]\n" },
-	{ "-r|--negotiate", 1, do_nway_rst, "Restart N-WAY negotiation" },
-	{ "-p|--identify", 1, do_phys_id,
-	  "Show visible port identification (e.g. blinking)",
-	  "               [ TIME-IN-SECONDS ]\n" },
-	{ "-t|--test", 1, do_test, "Execute adapter self test",
-	  "               [ online | offline | external_lb ]\n" },
-	{ "-S|--statistics", 1, do_gnicstats, "Show adapter statistics" },
-	{ "--phy-statistics", 1, do_gphystats,
-	  "Show phy statistics" },
-	{ "-n|-u|--show-nfc|--show-ntuple", 1, do_grxclass,
-	  "Show Rx network flow classification options or rules",
-	  "		[ rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
-	  "tcp6|udp6|ah6|esp6|sctp6 [context %d] |\n"
-	  "		  rule %d ]\n" },
-	{ "-N|-U|--config-nfc|--config-ntuple", 1, do_srxclass,
-	  "Configure Rx network flow classification options or rules",
-	  "		rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
-	  "tcp6|udp6|ah6|esp6|sctp6 m|v|t|s|d|f|n|r... [context %d] |\n"
-	  "		flow-type ether|ip4|tcp4|udp4|sctp4|ah4|esp4|"
-	  "ip6|tcp6|udp6|ah6|esp6|sctp6\n"
-	  "			[ src %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
-	  "			[ dst %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
-	  "			[ proto %d [m %x] ]\n"
-	  "			[ src-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
-	  "			[ dst-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
-	  "			[ tos %d [m %x] ]\n"
-	  "			[ tclass %d [m %x] ]\n"
-	  "			[ l4proto %d [m %x] ]\n"
-	  "			[ src-port %d [m %x] ]\n"
-	  "			[ dst-port %d [m %x] ]\n"
-	  "			[ spi %d [m %x] ]\n"
-	  "			[ vlan-etype %x [m %x] ]\n"
-	  "			[ vlan %x [m %x] ]\n"
-	  "			[ user-def %x [m %x] ]\n"
-	  "			[ dst-mac %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
-	  "			[ action %d ] | [ vf %d queue %d ]\n"
-	  "			[ context %d ]\n"
-	  "			[ loc %d]] |\n"
-	  "		delete %d\n" },
-	{ "-T|--show-time-stamping", 1, do_tsinfo,
-	  "Show time stamping capabilities" },
-	{ "-x|--show-rxfh-indir|--show-rxfh", 1, do_grxfh,
-	  "Show Rx flow hash indirection table and/or RSS hash key",
-	  "		[ context %d ]\n" },
-	{ "-X|--set-rxfh-indir|--rxfh", 1, do_srxfh,
-	  "Set Rx flow hash indirection table and/or RSS hash key",
-	  "		[ context %d|new ]\n"
-	  "		[ equal N | weight W0 W1 ... | default ]\n"
-	  "		[ hkey %x:%x:%x:%x:%x:.... ]\n"
-	  "		[ hfunc FUNC ]\n"
-	  "		[ delete ]\n" },
-	{ "-f|--flash", 1, do_flash,
-	  "Flash firmware image from the specified file to a region on the device",
-	  "               FILENAME [ REGION-NUMBER-TO-FLASH ]\n" },
-	{ "-P|--show-permaddr", 1, do_permaddr,
-	  "Show permanent hardware address" },
-	{ "-w|--get-dump", 1, do_getfwdump,
-	  "Get dump flag, data",
-	  "		[ data FILENAME ]\n" },
-	{ "-W|--set-dump", 1, do_setfwdump,
-	  "Set dump flag of the device",
-	  "		N\n"},
-	{ "-l|--show-channels", 1, do_gchannels, "Query Channels" },
-	{ "-L|--set-channels", 1, do_schannels, "Set Channels",
-	  "               [ rx N ]\n"
-	  "               [ tx N ]\n"
-	  "               [ other N ]\n"
-	  "               [ combined N ]\n" },
-	{ "--show-priv-flags", 1, do_gprivflags, "Query private flags" },
-	{ "--set-priv-flags", 1, do_sprivflags, "Set private flags",
-	  "		FLAG on|off ...\n" },
-	{ "-m|--dump-module-eeprom|--module-info", 1, do_getmodule,
-	  "Query/Decode Module EEPROM information and optical diagnostics if available",
-	  "		[ raw on|off ]\n"
-	  "		[ hex on|off ]\n"
-	  "		[ offset N ]\n"
-	  "		[ length N ]\n" },
-	{ "--show-eee", 1, do_geee, "Show EEE settings"},
-	{ "--set-eee", 1, do_seee, "Set EEE settings",
-	  "		[ eee on|off ]\n"
-	  "		[ advertise %x ]\n"
-	  "		[ tx-lpi on|off ]\n"
-	  "		[ tx-timer %d ]\n"},
-	{ "--set-phy-tunable", 1, do_set_phy_tunable, "Set PHY tunable",
-	  "		[ downshift on|off [count N] ]\n"
-	  "		[ fast-link-down on|off [msecs N] ]\n"
-	  "		[ energy-detect-power-down on|off [msecs N] ]\n"},
-	{ "--get-phy-tunable", 1, do_get_phy_tunable, "Get PHY tunable",
-	  "		[ downshift ]\n"
-	  "		[ fast-link-down ]\n"
-	  "		[ energy-detect-power-down ]\n"},
-	{ "--reset", 1, do_reset, "Reset components",
-	  "		[ flags %x ]\n"
-	  "		[ mgmt ]\n"
-	  "		[ mgmt-shared ]\n"
-	  "		[ irq ]\n"
-	  "		[ irq-shared ]\n"
-	  "		[ dma ]\n"
-	  "		[ dma-shared ]\n"
-	  "		[ filter ]\n"
-	  "		[ filter-shared ]\n"
-	  "		[ offload ]\n"
-	  "		[ offload-shared ]\n"
-	  "		[ mac ]\n"
-	  "		[ mac-shared ]\n"
-	  "		[ phy ]\n"
-	  "		[ phy-shared ]\n"
-	  "		[ ram ]\n"
-	  "		[ ram-shared ]\n"
-	  "		[ ap ]\n"
-	  "		[ ap-shared ]\n"
-	  "		[ dedicated ]\n"
-	  "		[ all ]\n"},
-	{ "--show-fec", 1, do_gfec, "Show FEC settings"},
-	{ "--set-fec", 1, do_sfec, "Set FEC settings",
-	  "		[ encoding auto|off|rs|baser [...]]\n"},
-	{ "-Q|--per-queue", 1, do_perqueue, "Apply per-queue command."
-	  "The supported sub commands include --show-coalesce, --coalesce",
-	  "             [queue_mask %x] SUB_COMMAND\n"},
-	{ "-h|--help", 0, show_usage, "Show this help" },
-	{ "--version", 0, do_version, "Show version number" },
+struct option {
+	const char	*opts;
+	bool		no_dev;
+	int		(*func)(struct cmd_context *);
+	const char	*help;
+	const char	*xhelp;
+};
+
+static const struct option args[] = {
+	{
+		.opts	= "-s|--change",
+		.func	= do_sset,
+		.help	= "Change generic options",
+		.xhelp	= "		[ speed %d ]\n"
+			  "		[ duplex half|full ]\n"
+			  "		[ port tp|aui|bnc|mii|fibre ]\n"
+			  "		[ mdix auto|on|off ]\n"
+			  "		[ autoneg on|off ]\n"
+			  "		[ advertise %x ]\n"
+			  "		[ phyad %d ]\n"
+			  "		[ xcvr internal|external ]\n"
+			  "		[ wol p|u|m|b|a|g|s|f|d... ]\n"
+			  "		[ sopass %x:%x:%x:%x:%x:%x ]\n"
+			  "		[ msglvl %d | msglvl type on|off ... ]\n"
+	},
+	{
+		.opts	= "-a|--show-pause",
+		.func	= do_gpause,
+		.help	= "Show pause options"
+	},
+	{
+		.opts	= "-A|--pause",
+		.func	= do_spause,
+		.help	= "Set pause options",
+		.xhelp	= "		[ autoneg on|off ]\n"
+			  "		[ rx on|off ]\n"
+			  "		[ tx on|off ]\n"
+	},
+	{
+		.opts	= "-c|--show-coalesce",
+		.func	= do_gcoalesce,
+		.help	= "Show coalesce options"
+	},
+	{
+		.opts	= "-C|--coalesce",
+		.func	= do_scoalesce,
+		.help	= "Set coalesce options",
+		.xhelp	= "		[adaptive-rx on|off]\n"
+			  "		[adaptive-tx on|off]\n"
+			  "		[rx-usecs N]\n"
+			  "		[rx-frames N]\n"
+			  "		[rx-usecs-irq N]\n"
+			  "		[rx-frames-irq N]\n"
+			  "		[tx-usecs N]\n"
+			  "		[tx-frames N]\n"
+			  "		[tx-usecs-irq N]\n"
+			  "		[tx-frames-irq N]\n"
+			  "		[stats-block-usecs N]\n"
+			  "		[pkt-rate-low N]\n"
+			  "		[rx-usecs-low N]\n"
+			  "		[rx-frames-low N]\n"
+			  "		[tx-usecs-low N]\n"
+			  "		[tx-frames-low N]\n"
+			  "		[pkt-rate-high N]\n"
+			  "		[rx-usecs-high N]\n"
+			  "		[rx-frames-high N]\n"
+			  "		[tx-usecs-high N]\n"
+			  "		[tx-frames-high N]\n"
+			  "		[sample-interval N]\n"
+	},
+	{
+		.opts	= "-g|--show-ring",
+		.func	= do_gring,
+		.help	= "Query RX/TX ring parameters"
+	},
+	{
+		.opts	= "-G|--set-ring",
+		.func	= do_sring,
+		.help	= "Set RX/TX ring parameters",
+		.xhelp	= "		[ rx N ]\n"
+			  "		[ rx-mini N ]\n"
+			  "		[ rx-jumbo N ]\n"
+			  "		[ tx N ]\n"
+	},
+	{
+		.opts	= "-k|--show-features|--show-offload",
+		.func	= do_gfeatures,
+		.help	= "Get state of protocol offload and other features"
+	},
+	{
+		.opts	= "-K|--features|--offload",
+		.func	= do_sfeatures,
+		.help	= "Set protocol offload and other features",
+		.xhelp	= "		FEATURE on|off ...\n"
+	},
+	{
+		.opts	= "-i|--driver",
+		.func	= do_gdrv,
+		.help	= "Show driver information"
+	},
+	{
+		.opts	= "-d|--register-dump",
+		.func	= do_gregs,
+		.help	= "Do a register dump",
+		.xhelp	= "		[ raw on|off ]\n"
+			  "		[ file FILENAME ]\n"
+	},
+	{
+		.opts	= "-e|--eeprom-dump",
+		.func	= do_geeprom,
+		.help	= "Do a EEPROM dump",
+		.xhelp	= "		[ raw on|off ]\n"
+			  "		[ offset N ]\n"
+			  "		[ length N ]\n"
+	},
+	{
+		.opts	= "-E|--change-eeprom",
+		.func	= do_seeprom,
+		.help	= "Change bytes in device EEPROM",
+		.xhelp	= "		[ magic N ]\n"
+			  "		[ offset N ]\n"
+			  "		[ length N ]\n"
+			  "		[ value N ]\n"
+	},
+	{
+		.opts	= "-r|--negotiate",
+		.func	= do_nway_rst,
+		.help	= "Restart N-WAY negotiation"
+	},
+	{
+		.opts	= "-p|--identify",
+		.func	= do_phys_id,
+		.help	= "Show visible port identification (e.g. blinking)",
+		.xhelp	= "               [ TIME-IN-SECONDS ]\n"
+	},
+	{
+		.opts	= "-t|--test",
+		.func	= do_test,
+		.help	= "Execute adapter self test",
+		.xhelp	= "               [ online | offline | external_lb ]\n"
+	},
+	{
+		.opts	= "-S|--statistics",
+		.func	= do_gnicstats,
+		.help	= "Show adapter statistics"
+	},
+	{
+		.opts	= "--phy-statistics",
+		.func	= do_gphystats,
+		.help	= "Show phy statistics"
+	},
+	{
+		.opts	= "-n|-u|--show-nfc|--show-ntuple",
+		.func	= do_grxclass,
+		.help	= "Show Rx network flow classification options or rules",
+		.xhelp	= "		[ rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
+			  "tcp6|udp6|ah6|esp6|sctp6 [context %d] |\n"
+			  "		  rule %d ]\n"
+	},
+	{
+		.opts	= "-N|-U|--config-nfc|--config-ntuple",
+		.func	= do_srxclass,
+		.help	= "Configure Rx network flow classification options or rules",
+		.xhelp	= "		rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|"
+			  "tcp6|udp6|ah6|esp6|sctp6 m|v|t|s|d|f|n|r... [context %d] |\n"
+			  "		flow-type ether|ip4|tcp4|udp4|sctp4|ah4|esp4|"
+			  "ip6|tcp6|udp6|ah6|esp6|sctp6\n"
+			  "			[ src %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
+			  "			[ dst %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
+			  "			[ proto %d [m %x] ]\n"
+			  "			[ src-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
+			  "			[ dst-ip IP-ADDRESS [m IP-ADDRESS] ]\n"
+			  "			[ tos %d [m %x] ]\n"
+			  "			[ tclass %d [m %x] ]\n"
+			  "			[ l4proto %d [m %x] ]\n"
+			  "			[ src-port %d [m %x] ]\n"
+			  "			[ dst-port %d [m %x] ]\n"
+			  "			[ spi %d [m %x] ]\n"
+			  "			[ vlan-etype %x [m %x] ]\n"
+			  "			[ vlan %x [m %x] ]\n"
+			  "			[ user-def %x [m %x] ]\n"
+			  "			[ dst-mac %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n"
+			  "			[ action %d ] | [ vf %d queue %d ]\n"
+			  "			[ context %d ]\n"
+			  "			[ loc %d]] |\n"
+			  "		delete %d\n"
+	},
+	{
+		.opts	= "-T|--show-time-stamping",
+		.func	= do_tsinfo,
+		.help	= "Show time stamping capabilities"
+	},
+	{
+		.opts	= "-x|--show-rxfh-indir|--show-rxfh",
+		.func	= do_grxfh,
+		.help	= "Show Rx flow hash indirection table and/or RSS hash key",
+		.xhelp	= "		[ context %d ]\n"
+	},
+	{
+		.opts	= "-X|--set-rxfh-indir|--rxfh",
+		.func	= do_srxfh,
+		.help	= "Set Rx flow hash indirection table and/or RSS hash key",
+		.xhelp	= "		[ context %d|new ]\n"
+			  "		[ equal N | weight W0 W1 ... | default ]\n"
+			  "		[ hkey %x:%x:%x:%x:%x:.... ]\n"
+			  "		[ hfunc FUNC ]\n"
+			  "		[ delete ]\n"
+	},
+	{
+		.opts	= "-f|--flash",
+		.func	= do_flash,
+		.help	= "Flash firmware image from the specified file to a region on the device",
+		.xhelp	= "               FILENAME [ REGION-NUMBER-TO-FLASH ]\n"
+	},
+	{
+		.opts	= "-P|--show-permaddr",
+		.func	= do_permaddr,
+		.help	= "Show permanent hardware address"
+	},
+	{
+		.opts	= "-w|--get-dump",
+		.func	= do_getfwdump,
+		.help	= "Get dump flag, data",
+		.xhelp	= "		[ data FILENAME ]\n"
+	},
+	{
+		.opts	= "-W|--set-dump",
+		.func	= do_setfwdump,
+		.help	= "Set dump flag of the device",
+		.xhelp	= "		N\n"
+	},
+	{
+		.opts	= "-l|--show-channels",
+		.func	= do_gchannels,
+		.help	= "Query Channels"
+	},
+	{
+		.opts	= "-L|--set-channels",
+		.func	= do_schannels,
+		.help	= "Set Channels",
+		.xhelp	= "               [ rx N ]\n"
+			  "               [ tx N ]\n"
+			  "               [ other N ]\n"
+			  "               [ combined N ]\n"
+	},
+	{
+		.opts	= "--show-priv-flags",
+		.func	= do_gprivflags,
+		.help	= "Query private flags"
+	},
+	{
+		.opts	= "--set-priv-flags",
+		.func	= do_sprivflags,
+		.help	= "Set private flags",
+		.xhelp	= "		FLAG on|off ...\n"
+	},
+	{
+		.opts	= "-m|--dump-module-eeprom|--module-info",
+		.func	= do_getmodule,
+		.help	= "Query/Decode Module EEPROM information and optical diagnostics if available",
+		.xhelp	= "		[ raw on|off ]\n"
+			  "		[ hex on|off ]\n"
+			  "		[ offset N ]\n"
+			  "		[ length N ]\n"
+	},
+	{
+		.opts	= "--show-eee",
+		.func	= do_geee,
+		.help	= "Show EEE settings",
+	},
+	{
+		.opts	= "--set-eee",
+		.func	= do_seee,
+		.help	= "Set EEE settings",
+		.xhelp	= "		[ eee on|off ]\n"
+			  "		[ advertise %x ]\n"
+			  "		[ tx-lpi on|off ]\n"
+			  "		[ tx-timer %d ]\n"
+	},
+	{
+		.opts	= "--set-phy-tunable",
+		.func	= do_set_phy_tunable,
+		.help	= "Set PHY tunable",
+		.xhelp	= "		[ downshift on|off [count N] ]\n"
+			  "		[ fast-link-down on|off [msecs N] ]\n"
+			  "		[ energy-detect-power-down on|off [msecs N] ]\n"
+	},
+	{
+		.opts	= "--get-phy-tunable",
+		.func	= do_get_phy_tunable,
+		.help	= "Get PHY tunable",
+		.xhelp	= "		[ downshift ]\n"
+			  "		[ fast-link-down ]\n"
+			  "		[ energy-detect-power-down ]\n"
+	},
+	{
+		.opts	= "--reset",
+		.func	= do_reset,
+		.help	= "Reset components",
+		.xhelp	= "		[ flags %x ]\n"
+			  "		[ mgmt ]\n"
+			  "		[ mgmt-shared ]\n"
+			  "		[ irq ]\n"
+			  "		[ irq-shared ]\n"
+			  "		[ dma ]\n"
+			  "		[ dma-shared ]\n"
+			  "		[ filter ]\n"
+			  "		[ filter-shared ]\n"
+			  "		[ offload ]\n"
+			  "		[ offload-shared ]\n"
+			  "		[ mac ]\n"
+			  "		[ mac-shared ]\n"
+			  "		[ phy ]\n"
+			  "		[ phy-shared ]\n"
+			  "		[ ram ]\n"
+			  "		[ ram-shared ]\n"
+			  "		[ ap ]\n"
+			  "		[ ap-shared ]\n"
+			  "		[ dedicated ]\n"
+			  "		[ all ]\n"
+	},
+	{
+		.opts	= "--show-fec",
+		.func	= do_gfec,
+		.help	= "Show FEC settings",
+	},
+	{
+		.opts	= "--set-fec",
+		.func	= do_sfec,
+		.help	= "Set FEC settings",
+		.xhelp	= "		[ encoding auto|off|rs|baser [...]]\n"
+	},
+	{
+		.opts	= "-Q|--per-queue",
+		.func	= do_perqueue,
+		.help	= "Apply per-queue command. ",
+		.xhelp	= "The supported sub commands include --show-coalesce, --coalesce"
+			  "             [queue_mask %x] SUB_COMMAND\n",
+	},
+	{
+		.opts	= "-h|--help",
+		.no_dev	= true,
+		.func	= show_usage,
+		.help	= "Show this help"
+	},
+	{
+		.opts	= "--version",
+		.no_dev	= true,
+		.func	= do_version,
+		.help	= "Show version number"
+	},
 	{}
 };
 
@@ -5499,10 +5656,10 @@ static int show_usage(struct cmd_context *ctx maybe_unused)
 		fputs("        ethtool [ --debug MASK ] ", stdout);
 		fprintf(stdout, "%s %s\t%s\n",
 			args[i].opts,
-			args[i].want_device ? "DEVNAME" : "\t",
+			args[i].no_dev ? "\t" : "DEVNAME",
 			args[i].help);
-		if (args[i].opthelp)
-			fputs(args[i].opthelp, stdout);
+		if (args[i].xhelp)
+			fputs(args[i].xhelp, stdout);
 	}
 
 	return 0;
@@ -5702,8 +5859,8 @@ static int do_perqueue(struct cmd_context *ctx)
 int main(int argc, char **argp)
 {
 	int (*func)(struct cmd_context *);
-	int want_device;
 	struct cmd_context ctx;
+	bool no_dev;
 	int k;
 
 	init_global_link_mode_masks();
@@ -5738,16 +5895,16 @@ int main(int argc, char **argp)
 		argp++;
 		argc--;
 		func = args[k].func;
-		want_device = args[k].want_device;
+		no_dev = args[k].no_dev;
 		goto opt_found;
 	}
 	if ((*argp)[0] == '-')
 		exit_bad_args();
 	func = do_gset;
-	want_device = 1;
+	no_dev = false;
 
 opt_found:
-	if (want_device) {
+	if (!no_dev) {
 		ctx.devname = *argp++;
 		argc--;
 
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 05/19] netlink: add netlink related UAPI header files
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (3 preceding siblings ...)
  2020-02-16 22:46 ` [PATCH ethtool 04/19] use named initializers in command line option list Michal Kubecek
@ 2020-02-16 22:46 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 06/19] netlink: introduce the netlink interface Michal Kubecek
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:46 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Add copies of kernel UAPI header files needed for netlink interface:

  <linux/netlink.h>
  <linux/genetlink.h>
  <linux/ethtool_netlink.h>

The (sanitized) copies are taken from v5.6-rc1.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 uapi/linux/ethtool_netlink.h | 237 +++++++++++++++++++++++++++++++++
 uapi/linux/genetlink.h       |  89 +++++++++++++
 uapi/linux/netlink.h         | 248 +++++++++++++++++++++++++++++++++++
 3 files changed, 574 insertions(+)
 create mode 100644 uapi/linux/ethtool_netlink.h
 create mode 100644 uapi/linux/genetlink.h
 create mode 100644 uapi/linux/netlink.h

diff --git a/uapi/linux/ethtool_netlink.h b/uapi/linux/ethtool_netlink.h
new file mode 100644
index 000000000000..ad6d3a00019d
--- /dev/null
+++ b/uapi/linux/ethtool_netlink.h
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * include/uapi/linux/ethtool_netlink.h - netlink interface for ethtool
+ *
+ * See Documentation/networking/ethtool-netlink.txt in kernel source tree for
+ * doucumentation of the interface.
+ */
+
+#ifndef _LINUX_ETHTOOL_NETLINK_H_
+#define _LINUX_ETHTOOL_NETLINK_H_
+
+#include <linux/ethtool.h>
+
+/* message types - userspace to kernel */
+enum {
+	ETHTOOL_MSG_USER_NONE,
+	ETHTOOL_MSG_STRSET_GET,
+	ETHTOOL_MSG_LINKINFO_GET,
+	ETHTOOL_MSG_LINKINFO_SET,
+	ETHTOOL_MSG_LINKMODES_GET,
+	ETHTOOL_MSG_LINKMODES_SET,
+	ETHTOOL_MSG_LINKSTATE_GET,
+	ETHTOOL_MSG_DEBUG_GET,
+	ETHTOOL_MSG_DEBUG_SET,
+	ETHTOOL_MSG_WOL_GET,
+	ETHTOOL_MSG_WOL_SET,
+
+	/* add new constants above here */
+	__ETHTOOL_MSG_USER_CNT,
+	ETHTOOL_MSG_USER_MAX = __ETHTOOL_MSG_USER_CNT - 1
+};
+
+/* message types - kernel to userspace */
+enum {
+	ETHTOOL_MSG_KERNEL_NONE,
+	ETHTOOL_MSG_STRSET_GET_REPLY,
+	ETHTOOL_MSG_LINKINFO_GET_REPLY,
+	ETHTOOL_MSG_LINKINFO_NTF,
+	ETHTOOL_MSG_LINKMODES_GET_REPLY,
+	ETHTOOL_MSG_LINKMODES_NTF,
+	ETHTOOL_MSG_LINKSTATE_GET_REPLY,
+	ETHTOOL_MSG_DEBUG_GET_REPLY,
+	ETHTOOL_MSG_DEBUG_NTF,
+	ETHTOOL_MSG_WOL_GET_REPLY,
+	ETHTOOL_MSG_WOL_NTF,
+
+	/* add new constants above here */
+	__ETHTOOL_MSG_KERNEL_CNT,
+	ETHTOOL_MSG_KERNEL_MAX = __ETHTOOL_MSG_KERNEL_CNT - 1
+};
+
+/* request header */
+
+/* use compact bitsets in reply */
+#define ETHTOOL_FLAG_COMPACT_BITSETS	(1 << 0)
+/* provide optional reply for SET or ACT requests */
+#define ETHTOOL_FLAG_OMIT_REPLY	(1 << 1)
+
+#define ETHTOOL_FLAG_ALL (ETHTOOL_FLAG_COMPACT_BITSETS | \
+			  ETHTOOL_FLAG_OMIT_REPLY)
+
+enum {
+	ETHTOOL_A_HEADER_UNSPEC,
+	ETHTOOL_A_HEADER_DEV_INDEX,		/* u32 */
+	ETHTOOL_A_HEADER_DEV_NAME,		/* string */
+	ETHTOOL_A_HEADER_FLAGS,			/* u32 - ETHTOOL_FLAG_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_HEADER_CNT,
+	ETHTOOL_A_HEADER_MAX = __ETHTOOL_A_HEADER_CNT - 1
+};
+
+/* bit sets */
+
+enum {
+	ETHTOOL_A_BITSET_BIT_UNSPEC,
+	ETHTOOL_A_BITSET_BIT_INDEX,		/* u32 */
+	ETHTOOL_A_BITSET_BIT_NAME,		/* string */
+	ETHTOOL_A_BITSET_BIT_VALUE,		/* flag */
+
+	/* add new constants above here */
+	__ETHTOOL_A_BITSET_BIT_CNT,
+	ETHTOOL_A_BITSET_BIT_MAX = __ETHTOOL_A_BITSET_BIT_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_BITSET_BITS_UNSPEC,
+	ETHTOOL_A_BITSET_BITS_BIT,		/* nest - _A_BITSET_BIT_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_BITSET_BITS_CNT,
+	ETHTOOL_A_BITSET_BITS_MAX = __ETHTOOL_A_BITSET_BITS_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_BITSET_UNSPEC,
+	ETHTOOL_A_BITSET_NOMASK,		/* flag */
+	ETHTOOL_A_BITSET_SIZE,			/* u32 */
+	ETHTOOL_A_BITSET_BITS,			/* nest - _A_BITSET_BITS_* */
+	ETHTOOL_A_BITSET_VALUE,			/* binary */
+	ETHTOOL_A_BITSET_MASK,			/* binary */
+
+	/* add new constants above here */
+	__ETHTOOL_A_BITSET_CNT,
+	ETHTOOL_A_BITSET_MAX = __ETHTOOL_A_BITSET_CNT - 1
+};
+
+/* string sets */
+
+enum {
+	ETHTOOL_A_STRING_UNSPEC,
+	ETHTOOL_A_STRING_INDEX,			/* u32 */
+	ETHTOOL_A_STRING_VALUE,			/* string */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRING_CNT,
+	ETHTOOL_A_STRING_MAX = __ETHTOOL_A_STRING_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_STRINGS_UNSPEC,
+	ETHTOOL_A_STRINGS_STRING,		/* nest - _A_STRINGS_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRINGS_CNT,
+	ETHTOOL_A_STRINGS_MAX = __ETHTOOL_A_STRINGS_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_STRINGSET_UNSPEC,
+	ETHTOOL_A_STRINGSET_ID,			/* u32 */
+	ETHTOOL_A_STRINGSET_COUNT,		/* u32 */
+	ETHTOOL_A_STRINGSET_STRINGS,		/* nest - _A_STRINGS_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRINGSET_CNT,
+	ETHTOOL_A_STRINGSET_MAX = __ETHTOOL_A_STRINGSET_CNT - 1
+};
+
+enum {
+	ETHTOOL_A_STRINGSETS_UNSPEC,
+	ETHTOOL_A_STRINGSETS_STRINGSET,		/* nest - _A_STRINGSET_* */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRINGSETS_CNT,
+	ETHTOOL_A_STRINGSETS_MAX = __ETHTOOL_A_STRINGSETS_CNT - 1
+};
+
+/* STRSET */
+
+enum {
+	ETHTOOL_A_STRSET_UNSPEC,
+	ETHTOOL_A_STRSET_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_STRSET_STRINGSETS,		/* nest - _A_STRINGSETS_* */
+	ETHTOOL_A_STRSET_COUNTS_ONLY,		/* flag */
+
+	/* add new constants above here */
+	__ETHTOOL_A_STRSET_CNT,
+	ETHTOOL_A_STRSET_MAX = __ETHTOOL_A_STRSET_CNT - 1
+};
+
+/* LINKINFO */
+
+enum {
+	ETHTOOL_A_LINKINFO_UNSPEC,
+	ETHTOOL_A_LINKINFO_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_LINKINFO_PORT,		/* u8 */
+	ETHTOOL_A_LINKINFO_PHYADDR,		/* u8 */
+	ETHTOOL_A_LINKINFO_TP_MDIX,		/* u8 */
+	ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,	/* u8 */
+	ETHTOOL_A_LINKINFO_TRANSCEIVER,		/* u8 */
+
+	/* add new constants above here */
+	__ETHTOOL_A_LINKINFO_CNT,
+	ETHTOOL_A_LINKINFO_MAX = __ETHTOOL_A_LINKINFO_CNT - 1
+};
+
+/* LINKMODES */
+
+enum {
+	ETHTOOL_A_LINKMODES_UNSPEC,
+	ETHTOOL_A_LINKMODES_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_LINKMODES_AUTONEG,		/* u8 */
+	ETHTOOL_A_LINKMODES_OURS,		/* bitset */
+	ETHTOOL_A_LINKMODES_PEER,		/* bitset */
+	ETHTOOL_A_LINKMODES_SPEED,		/* u32 */
+	ETHTOOL_A_LINKMODES_DUPLEX,		/* u8 */
+
+	/* add new constants above here */
+	__ETHTOOL_A_LINKMODES_CNT,
+	ETHTOOL_A_LINKMODES_MAX = __ETHTOOL_A_LINKMODES_CNT - 1
+};
+
+/* LINKSTATE */
+
+enum {
+	ETHTOOL_A_LINKSTATE_UNSPEC,
+	ETHTOOL_A_LINKSTATE_HEADER,		/* nest - _A_HEADER_* */
+	ETHTOOL_A_LINKSTATE_LINK,		/* u8 */
+
+	/* add new constants above here */
+	__ETHTOOL_A_LINKSTATE_CNT,
+	ETHTOOL_A_LINKSTATE_MAX = __ETHTOOL_A_LINKSTATE_CNT - 1
+};
+
+/* DEBUG */
+
+enum {
+	ETHTOOL_A_DEBUG_UNSPEC,
+	ETHTOOL_A_DEBUG_HEADER,			/* nest - _A_HEADER_* */
+	ETHTOOL_A_DEBUG_MSGMASK,		/* bitset */
+
+	/* add new constants above here */
+	__ETHTOOL_A_DEBUG_CNT,
+	ETHTOOL_A_DEBUG_MAX = __ETHTOOL_A_DEBUG_CNT - 1
+};
+
+/* WOL */
+
+enum {
+	ETHTOOL_A_WOL_UNSPEC,
+	ETHTOOL_A_WOL_HEADER,			/* nest - _A_HEADER_* */
+	ETHTOOL_A_WOL_MODES,			/* bitset */
+	ETHTOOL_A_WOL_SOPASS,			/* binary */
+
+	/* add new constants above here */
+	__ETHTOOL_A_WOL_CNT,
+	ETHTOOL_A_WOL_MAX = __ETHTOOL_A_WOL_CNT - 1
+};
+
+/* generic netlink info */
+#define ETHTOOL_GENL_NAME "ethtool"
+#define ETHTOOL_GENL_VERSION 1
+
+#define ETHTOOL_MCGRP_MONITOR_NAME "monitor"
+
+#endif /* _LINUX_ETHTOOL_NETLINK_H_ */
diff --git a/uapi/linux/genetlink.h b/uapi/linux/genetlink.h
new file mode 100644
index 000000000000..1317119cbff8
--- /dev/null
+++ b/uapi/linux/genetlink.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_GENERIC_NETLINK_H
+#define __LINUX_GENERIC_NETLINK_H
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#define GENL_NAMSIZ	16	/* length of family name */
+
+#define GENL_MIN_ID	NLMSG_MIN_TYPE
+#define GENL_MAX_ID	1023
+
+struct genlmsghdr {
+	__u8	cmd;
+	__u8	version;
+	__u16	reserved;
+};
+
+#define GENL_HDRLEN	NLMSG_ALIGN(sizeof(struct genlmsghdr))
+
+#define GENL_ADMIN_PERM		0x01
+#define GENL_CMD_CAP_DO		0x02
+#define GENL_CMD_CAP_DUMP	0x04
+#define GENL_CMD_CAP_HASPOL	0x08
+#define GENL_UNS_ADMIN_PERM	0x10
+
+/*
+ * List of reserved static generic netlink identifiers:
+ */
+#define GENL_ID_CTRL		NLMSG_MIN_TYPE
+#define GENL_ID_VFS_DQUOT	(NLMSG_MIN_TYPE + 1)
+#define GENL_ID_PMCRAID		(NLMSG_MIN_TYPE + 2)
+/* must be last reserved + 1 */
+#define GENL_START_ALLOC	(NLMSG_MIN_TYPE + 3)
+
+/**************************************************************************
+ * Controller
+ **************************************************************************/
+
+enum {
+	CTRL_CMD_UNSPEC,
+	CTRL_CMD_NEWFAMILY,
+	CTRL_CMD_DELFAMILY,
+	CTRL_CMD_GETFAMILY,
+	CTRL_CMD_NEWOPS,
+	CTRL_CMD_DELOPS,
+	CTRL_CMD_GETOPS,
+	CTRL_CMD_NEWMCAST_GRP,
+	CTRL_CMD_DELMCAST_GRP,
+	CTRL_CMD_GETMCAST_GRP, /* unused */
+	__CTRL_CMD_MAX,
+};
+
+#define CTRL_CMD_MAX (__CTRL_CMD_MAX - 1)
+
+enum {
+	CTRL_ATTR_UNSPEC,
+	CTRL_ATTR_FAMILY_ID,
+	CTRL_ATTR_FAMILY_NAME,
+	CTRL_ATTR_VERSION,
+	CTRL_ATTR_HDRSIZE,
+	CTRL_ATTR_MAXATTR,
+	CTRL_ATTR_OPS,
+	CTRL_ATTR_MCAST_GROUPS,
+	__CTRL_ATTR_MAX,
+};
+
+#define CTRL_ATTR_MAX (__CTRL_ATTR_MAX - 1)
+
+enum {
+	CTRL_ATTR_OP_UNSPEC,
+	CTRL_ATTR_OP_ID,
+	CTRL_ATTR_OP_FLAGS,
+	__CTRL_ATTR_OP_MAX,
+};
+
+#define CTRL_ATTR_OP_MAX (__CTRL_ATTR_OP_MAX - 1)
+
+enum {
+	CTRL_ATTR_MCAST_GRP_UNSPEC,
+	CTRL_ATTR_MCAST_GRP_NAME,
+	CTRL_ATTR_MCAST_GRP_ID,
+	__CTRL_ATTR_MCAST_GRP_MAX,
+};
+
+#define CTRL_ATTR_MCAST_GRP_MAX (__CTRL_ATTR_MCAST_GRP_MAX - 1)
+
+
+#endif /* __LINUX_GENERIC_NETLINK_H */
diff --git a/uapi/linux/netlink.h b/uapi/linux/netlink.h
new file mode 100644
index 000000000000..2c28d329e595
--- /dev/null
+++ b/uapi/linux/netlink.h
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef __LINUX_NETLINK_H
+#define __LINUX_NETLINK_H
+
+#include <linux/kernel.h>
+#include <linux/socket.h> /* for __kernel_sa_family_t */
+#include <linux/types.h>
+
+#define NETLINK_ROUTE		0	/* Routing/device hook				*/
+#define NETLINK_UNUSED		1	/* Unused number				*/
+#define NETLINK_USERSOCK	2	/* Reserved for user mode socket protocols 	*/
+#define NETLINK_FIREWALL	3	/* Unused number, formerly ip_queue		*/
+#define NETLINK_SOCK_DIAG	4	/* socket monitoring				*/
+#define NETLINK_NFLOG		5	/* netfilter/iptables ULOG */
+#define NETLINK_XFRM		6	/* ipsec */
+#define NETLINK_SELINUX		7	/* SELinux event notifications */
+#define NETLINK_ISCSI		8	/* Open-iSCSI */
+#define NETLINK_AUDIT		9	/* auditing */
+#define NETLINK_FIB_LOOKUP	10	
+#define NETLINK_CONNECTOR	11
+#define NETLINK_NETFILTER	12	/* netfilter subsystem */
+#define NETLINK_IP6_FW		13
+#define NETLINK_DNRTMSG		14	/* DECnet routing messages */
+#define NETLINK_KOBJECT_UEVENT	15	/* Kernel messages to userspace */
+#define NETLINK_GENERIC		16
+/* leave room for NETLINK_DM (DM Events) */
+#define NETLINK_SCSITRANSPORT	18	/* SCSI Transports */
+#define NETLINK_ECRYPTFS	19
+#define NETLINK_RDMA		20
+#define NETLINK_CRYPTO		21	/* Crypto layer */
+#define NETLINK_SMC		22	/* SMC monitoring */
+
+#define NETLINK_INET_DIAG	NETLINK_SOCK_DIAG
+
+#define MAX_LINKS 32		
+
+struct sockaddr_nl {
+	__kernel_sa_family_t	nl_family;	/* AF_NETLINK	*/
+	unsigned short	nl_pad;		/* zero		*/
+	__u32		nl_pid;		/* port ID	*/
+       	__u32		nl_groups;	/* multicast groups mask */
+};
+
+struct nlmsghdr {
+	__u32		nlmsg_len;	/* Length of message including header */
+	__u16		nlmsg_type;	/* Message content */
+	__u16		nlmsg_flags;	/* Additional flags */
+	__u32		nlmsg_seq;	/* Sequence number */
+	__u32		nlmsg_pid;	/* Sending process port ID */
+};
+
+/* Flags values */
+
+#define NLM_F_REQUEST		0x01	/* It is request message. 	*/
+#define NLM_F_MULTI		0x02	/* Multipart message, terminated by NLMSG_DONE */
+#define NLM_F_ACK		0x04	/* Reply with ack, with zero or error code */
+#define NLM_F_ECHO		0x08	/* Echo this request 		*/
+#define NLM_F_DUMP_INTR		0x10	/* Dump was inconsistent due to sequence change */
+#define NLM_F_DUMP_FILTERED	0x20	/* Dump was filtered as requested */
+
+/* Modifiers to GET request */
+#define NLM_F_ROOT	0x100	/* specify tree	root	*/
+#define NLM_F_MATCH	0x200	/* return all matching	*/
+#define NLM_F_ATOMIC	0x400	/* atomic GET		*/
+#define NLM_F_DUMP	(NLM_F_ROOT|NLM_F_MATCH)
+
+/* Modifiers to NEW request */
+#define NLM_F_REPLACE	0x100	/* Override existing		*/
+#define NLM_F_EXCL	0x200	/* Do not touch, if it exists	*/
+#define NLM_F_CREATE	0x400	/* Create, if it does not exist	*/
+#define NLM_F_APPEND	0x800	/* Add to end of list		*/
+
+/* Modifiers to DELETE request */
+#define NLM_F_NONREC	0x100	/* Do not delete recursively	*/
+
+/* Flags for ACK message */
+#define NLM_F_CAPPED	0x100	/* request was capped */
+#define NLM_F_ACK_TLVS	0x200	/* extended ACK TVLs were included */
+
+/*
+   4.4BSD ADD		NLM_F_CREATE|NLM_F_EXCL
+   4.4BSD CHANGE	NLM_F_REPLACE
+
+   True CHANGE		NLM_F_CREATE|NLM_F_REPLACE
+   Append		NLM_F_CREATE
+   Check		NLM_F_EXCL
+ */
+
+#define NLMSG_ALIGNTO	4U
+#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
+#define NLMSG_HDRLEN	 ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
+#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
+#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
+#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
+#define NLMSG_NEXT(nlh,len)	 ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
+				  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
+#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
+			   (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
+			   (nlh)->nlmsg_len <= (len))
+#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
+
+#define NLMSG_NOOP		0x1	/* Nothing.		*/
+#define NLMSG_ERROR		0x2	/* Error		*/
+#define NLMSG_DONE		0x3	/* End of a dump	*/
+#define NLMSG_OVERRUN		0x4	/* Data lost		*/
+
+#define NLMSG_MIN_TYPE		0x10	/* < 0x10: reserved control messages */
+
+struct nlmsgerr {
+	int		error;
+	struct nlmsghdr msg;
+	/*
+	 * followed by the message contents unless NETLINK_CAP_ACK was set
+	 * or the ACK indicates success (error == 0)
+	 * message length is aligned with NLMSG_ALIGN()
+	 */
+	/*
+	 * followed by TLVs defined in enum nlmsgerr_attrs
+	 * if NETLINK_EXT_ACK was set
+	 */
+};
+
+/**
+ * enum nlmsgerr_attrs - nlmsgerr attributes
+ * @NLMSGERR_ATTR_UNUSED: unused
+ * @NLMSGERR_ATTR_MSG: error message string (string)
+ * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original
+ *	 message, counting from the beginning of the header (u32)
+ * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to
+ *	be used - in the success case - to identify a created
+ *	object or operation or similar (binary)
+ * @__NLMSGERR_ATTR_MAX: number of attributes
+ * @NLMSGERR_ATTR_MAX: highest attribute number
+ */
+enum nlmsgerr_attrs {
+	NLMSGERR_ATTR_UNUSED,
+	NLMSGERR_ATTR_MSG,
+	NLMSGERR_ATTR_OFFS,
+	NLMSGERR_ATTR_COOKIE,
+
+	__NLMSGERR_ATTR_MAX,
+	NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
+};
+
+#define NETLINK_ADD_MEMBERSHIP		1
+#define NETLINK_DROP_MEMBERSHIP		2
+#define NETLINK_PKTINFO			3
+#define NETLINK_BROADCAST_ERROR		4
+#define NETLINK_NO_ENOBUFS		5
+#define NETLINK_RX_RING			6
+#define NETLINK_TX_RING			7
+#define NETLINK_LISTEN_ALL_NSID		8
+#define NETLINK_LIST_MEMBERSHIPS	9
+#define NETLINK_CAP_ACK			10
+#define NETLINK_EXT_ACK			11
+#define NETLINK_GET_STRICT_CHK		12
+
+struct nl_pktinfo {
+	__u32	group;
+};
+
+struct nl_mmap_req {
+	unsigned int	nm_block_size;
+	unsigned int	nm_block_nr;
+	unsigned int	nm_frame_size;
+	unsigned int	nm_frame_nr;
+};
+
+struct nl_mmap_hdr {
+	unsigned int	nm_status;
+	unsigned int	nm_len;
+	__u32		nm_group;
+	/* credentials */
+	__u32		nm_pid;
+	__u32		nm_uid;
+	__u32		nm_gid;
+};
+
+enum nl_mmap_status {
+	NL_MMAP_STATUS_UNUSED,
+	NL_MMAP_STATUS_RESERVED,
+	NL_MMAP_STATUS_VALID,
+	NL_MMAP_STATUS_COPY,
+	NL_MMAP_STATUS_SKIP,
+};
+
+#define NL_MMAP_MSG_ALIGNMENT		NLMSG_ALIGNTO
+#define NL_MMAP_MSG_ALIGN(sz)		__ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT)
+#define NL_MMAP_HDRLEN			NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr))
+
+#define NET_MAJOR 36		/* Major 36 is reserved for networking 						*/
+
+enum {
+	NETLINK_UNCONNECTED = 0,
+	NETLINK_CONNECTED,
+};
+
+/*
+ *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ * |        Header       | Pad |     Payload       | Pad |
+ * |   (struct nlattr)   | ing |                   | ing |
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ *  <-------------- nlattr->nla_len -------------->
+ */
+
+struct nlattr {
+	__u16           nla_len;
+	__u16           nla_type;
+};
+
+/*
+ * nla_type (16 bits)
+ * +---+---+-------------------------------+
+ * | N | O | Attribute Type                |
+ * +---+---+-------------------------------+
+ * N := Carries nested attributes
+ * O := Payload stored in network byte order
+ *
+ * Note: The N and O flag are mutually exclusive.
+ */
+#define NLA_F_NESTED		(1 << 15)
+#define NLA_F_NET_BYTEORDER	(1 << 14)
+#define NLA_TYPE_MASK		~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
+
+#define NLA_ALIGNTO		4
+#define NLA_ALIGN(len)		(((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
+#define NLA_HDRLEN		((int) NLA_ALIGN(sizeof(struct nlattr)))
+
+/* Generic 32 bitflags attribute content sent to the kernel.
+ *
+ * The value is a bitmap that defines the values being set
+ * The selector is a bitmask that defines which value is legit
+ *
+ * Examples:
+ *  value = 0x0, and selector = 0x1
+ *  implies we are selecting bit 1 and we want to set its value to 0.
+ *
+ *  value = 0x2, and selector = 0x2
+ *  implies we are selecting bit 2 and we want to set its value to 1.
+ *
+ */
+struct nla_bitfield32 {
+	__u32 value;
+	__u32 selector;
+};
+
+#endif /* __LINUX_NETLINK_H */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 06/19] netlink: introduce the netlink interface
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (4 preceding siblings ...)
  2020-02-16 22:46 ` [PATCH ethtool 05/19] netlink: add netlink related UAPI header files Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 07/19] netlink: message buffer and composition helpers Michal Kubecek
                   ` (12 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Initial part of netlink interface based on genetlink and libmnl. The
netlink interface is available in kernel since 5.6-rc1. This commit only
adds the generic infrastructure but does not override any ethtool command
so that there is no actual change in behaviour.

Netlink handlers for ethtool commands are added as nlfunc members to args
array in ethtool.c. A netlink handler is used if it is available (i.e.
nlfunc is not null) and ethtool succeeds to initialize netlink socket.
If netlink implementation exists for a command but the request is not
supported by kernel (e.g. when new ethtool is used on older kernel), ioctl
implementation is also used as fallback.

Running configure with --disable-netlink completely disables the netlink
interface.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       | 17 +++++++++--
 configure.ac      | 14 ++++++++-
 ethtool.c         | 76 +++++++++++++++++++++++++++++++++++------------
 internal.h        | 12 ++++++++
 netlink/extapi.h  | 31 +++++++++++++++++++
 netlink/netlink.c | 36 ++++++++++++++++++++++
 netlink/netlink.h | 26 ++++++++++++++++
 7 files changed, 190 insertions(+), 22 deletions(-)
 create mode 100644 netlink/extapi.h
 create mode 100644 netlink/netlink.c
 create mode 100644 netlink/netlink.h

diff --git a/Makefile.am b/Makefile.am
index 05beb22be669..3748d0df5608 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,6 +7,8 @@ EXTRA_DIST = LICENSE ethtool.8 ethtool.spec.in aclocal.m4 ChangeLog autogen.sh
 sbin_PROGRAMS = ethtool
 ethtool_SOURCES = ethtool.c uapi/linux/ethtool.h internal.h \
 		  uapi/linux/net_tstamp.h rxclass.c
+ethtool_CFLAGS = $(AM_CFLAGS)
+ethtool_LDADD = $(LDADD)
 if ETHTOOL_ENABLE_PRETTY_DUMP
 ethtool_SOURCES += \
 		  amd8111e.c de2104x.c dsa.c e100.c e1000.c et131x.c igb.c	\
@@ -22,12 +24,23 @@ bashcompletiondir = $(BASH_COMPLETION_DIR)
 dist_bashcompletion_DATA = shell-completion/bash/ethtool
 endif
 
+if ETHTOOL_ENABLE_NETLINK
+ethtool_SOURCES += \
+		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
+		  uapi/linux/ethtool_netlink.h \
+		  uapi/linux/netlink.h uapi/linux/genetlink.h
+ethtool_CFLAGS += @MNL_CFLAGS@
+ethtool_LDADD += @MNL_LIBS@
+endif
+
 TESTS = test-cmdline test-features
 check_PROGRAMS = test-cmdline test-features
 test_cmdline_SOURCES = test-cmdline.c test-common.c $(ethtool_SOURCES) 
-test_cmdline_CFLAGS = $(AM_FLAGS) -DTEST_ETHTOOL
+test_cmdline_CFLAGS = $(ethtool_CFLAGS) -DTEST_ETHTOOL
+test_cmdline_LDADD = $(ethtool_LDADD)
 test_features_SOURCES = test-features.c test-common.c $(ethtool_SOURCES) 
-test_features_CFLAGS = $(AM_FLAGS) -DTEST_ETHTOOL
+test_features_CFLAGS = $(ethtool_CFLAGS) -DTEST_ETHTOOL
+test_features_LDADD = $(ethtool_LDADD)
 
 dist-hook:
 	cp $(top_srcdir)/ethtool.spec $(distdir)
diff --git a/configure.ac b/configure.ac
index a5c1469f9b63..19e7fcb44fb5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Process this file with autoconf to produce a configure script.
 AC_INIT(ethtool, 5.4, netdev@vger.kernel.org)
 AC_PREREQ(2.52)
 AC_CONFIG_SRCDIR([ethtool.c])
-AM_INIT_AUTOMAKE([gnu])
+AM_INIT_AUTOMAKE([gnu subdir-objects])
 AC_CONFIG_HEADERS([ethtool-config.h])
 
 AM_MAINTAINER_MODE
@@ -66,5 +66,17 @@ AC_SUBST([BASH_COMPLETION_DIR])
 AM_CONDITIONAL([ENABLE_BASH_COMPLETION],
 	       [test "x$with_bash_completion_dir" != xno])
 
+AC_ARG_ENABLE(netlink,
+	      [  --enable-netlink	  enable netlink interface (enabled by default)],
+	      ,
+	      enable_netlink=yes)
+if test x$enable_netlink = xyes; then
+    PKG_PROG_PKG_CONFIG
+    PKG_CHECK_MODULES([MNL], [libmnl])
+    AC_DEFINE(ETHTOOL_ENABLE_NETLINK, 1,
+	      Define this to support netlink interface to talk to kernel.)
+fi
+AM_CONDITIONAL([ETHTOOL_ENABLE_NETLINK], [test x$enable_netlink = xyes])
+
 AC_CONFIG_FILES([Makefile ethtool.spec ethtool.8])
 AC_OUTPUT
diff --git a/ethtool.c b/ethtool.c
index 3186a72644fd..5d1ef537f692 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -48,6 +48,8 @@
 #include <linux/sockios.h>
 #include <linux/netlink.h>
 
+#include "netlink/extapi.h"
+
 #ifndef MAX_ADDR_LEN
 #define MAX_ADDR_LEN	32
 #endif
@@ -5292,6 +5294,7 @@ struct option {
 	const char	*opts;
 	bool		no_dev;
 	int		(*func)(struct cmd_context *);
+	int		(*nlfunc)(struct cmd_context *);
 	const char	*help;
 	const char	*xhelp;
 };
@@ -5856,11 +5859,36 @@ static int do_perqueue(struct cmd_context *ctx)
 	return 0;
 }
 
+static int ioctl_init(struct cmd_context *ctx, bool no_dev)
+{
+	if (no_dev) {
+		ctx->fd = -1;
+		return 0;
+	}
+
+	/* Setup our control structures. */
+	memset(&ctx->ifr, 0, sizeof(ctx->ifr));
+	strcpy(ctx->ifr.ifr_name, ctx->devname);
+
+	/* Open control socket. */
+	ctx->fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (ctx->fd < 0)
+		ctx->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+	if (ctx->fd < 0) {
+		perror("Cannot get control socket");
+		return 70;
+	}
+
+	return 0;
+}
+
 int main(int argc, char **argp)
 {
+	int (*nlfunc)(struct cmd_context *) = NULL;
 	int (*func)(struct cmd_context *);
-	struct cmd_context ctx;
+	struct cmd_context ctx = {};
 	bool no_dev;
+	int ret;
 	int k;
 
 	init_global_link_mode_masks();
@@ -5869,7 +5897,6 @@ int main(int argc, char **argp)
 	argp++;
 	argc--;
 
-	ctx.debug = 0;
 	if (*argp && !strcmp(*argp, "--debug")) {
 		char *eptr;
 
@@ -5895,6 +5922,7 @@ int main(int argc, char **argp)
 		argp++;
 		argc--;
 		func = args[k].func;
+		nlfunc = args[k].nlfunc;
 		no_dev = args[k].no_dev;
 		goto opt_found;
 	}
@@ -5904,33 +5932,43 @@ int main(int argc, char **argp)
 	no_dev = false;
 
 opt_found:
+	if (nlfunc) {
+		if (netlink_init(&ctx))
+			nlfunc = NULL;		/* fallback to ioctl() */
+	}
+
 	if (!no_dev) {
 		ctx.devname = *argp++;
 		argc--;
 
-		if (ctx.devname == NULL)
-			exit_bad_args();
-		if (strlen(ctx.devname) >= IFNAMSIZ)
+		/* netlink supports altnames, we will have to recheck against
+		 * IFNAMSIZ later in case of fallback to ioctl
+		 */
+		if (!ctx.devname || strlen(ctx.devname) >= ALTIFNAMSIZ) {
+			netlink_done(&ctx);
 			exit_bad_args();
-
-		/* Setup our control structures. */
-		memset(&ctx.ifr, 0, sizeof(ctx.ifr));
-		strcpy(ctx.ifr.ifr_name, ctx.devname);
-
-		/* Open control socket. */
-		ctx.fd = socket(AF_INET, SOCK_DGRAM, 0);
-		if (ctx.fd < 0)
-			ctx.fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
-		if (ctx.fd < 0) {
-			perror("Cannot get control socket");
-			return 70;
 		}
-	} else {
-		ctx.fd = -1;
 	}
 
 	ctx.argc = argc;
 	ctx.argp = argp;
 
+	if (nlfunc) {
+		ret = nlfunc(&ctx);
+		netlink_done(&ctx);
+		if ((ret != -EOPNOTSUPP) || !func)
+			return (ret >= 0) ? ret : 1;
+	}
+
+	if (ctx.devname && strlen(ctx.devname) >= IFNAMSIZ) {
+		fprintf(stderr,
+			"ethtool: device names longer than %u characters are only allowed with netlink\n",
+			IFNAMSIZ - 1);
+		exit_bad_args();
+	}
+	ret = ioctl_init(&ctx, no_dev);
+	if (ret)
+		return ret;
+
 	return func(&ctx);
 }
diff --git a/internal.h b/internal.h
index 9ec145f55dcb..72a04e638a13 100644
--- a/internal.h
+++ b/internal.h
@@ -25,6 +25,11 @@
 
 #define maybe_unused __attribute__((__unused__))
 
+/* internal for netlink interface */
+#ifdef ETHTOOL_ENABLE_NETLINK
+struct nl_context;
+#endif
+
 /* ethtool.h expects these to be defined by <linux/types.h> */
 #ifndef HAVE_BE_TYPES
 typedef uint16_t __be16;
@@ -44,6 +49,10 @@ typedef int32_t s32;
 #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
 #endif
 
+#ifndef ALTIFNAMSIZ
+#define ALTIFNAMSIZ 128
+#endif
+
 #include <linux/ethtool.h>
 #include <linux/net_tstamp.h>
 
@@ -203,6 +212,9 @@ struct cmd_context {
 	int argc;		/* number of arguments to the sub-command */
 	char **argp;		/* arguments to the sub-command */
 	unsigned long debug;	/* debugging mask */
+#ifdef ETHTOOL_ENABLE_NETLINK
+	struct nl_context *nlctx;	/* netlink context (opaque) */
+#endif
 };
 
 #ifdef TEST_ETHTOOL
diff --git a/netlink/extapi.h b/netlink/extapi.h
new file mode 100644
index 000000000000..898dc6cfee71
--- /dev/null
+++ b/netlink/extapi.h
@@ -0,0 +1,31 @@
+/*
+ * extapi.h - external interface of netlink code
+ *
+ * Declarations needed by non-netlink code (mostly ethtool.c).
+ */
+
+#ifndef ETHTOOL_EXTAPI_H__
+#define ETHTOOL_EXTAPI_H__
+
+struct cmd_context;
+struct nl_context;
+
+#ifdef ETHTOOL_ENABLE_NETLINK
+
+int netlink_init(struct cmd_context *ctx);
+void netlink_done(struct cmd_context *ctx);
+
+#else /* ETHTOOL_ENABLE_NETLINK */
+
+static inline int netlink_init(struct cmd_context *ctx maybe_unused)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void netlink_done(struct cmd_context *ctx maybe_unused)
+{
+}
+
+#endif /* ETHTOOL_ENABLE_NETLINK */
+
+#endif /* ETHTOOL_EXTAPI_H__ */
diff --git a/netlink/netlink.c b/netlink/netlink.c
new file mode 100644
index 000000000000..84e188119989
--- /dev/null
+++ b/netlink/netlink.c
@@ -0,0 +1,36 @@
+/*
+ * netlink.c - basic infrastructure for netlink code
+ *
+ * Heart of the netlink interface implementation.
+ */
+
+#include <errno.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "extapi.h"
+
+/* initialization */
+
+int netlink_init(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx;
+
+	nlctx = calloc(1, sizeof(*nlctx));
+	if (!nlctx)
+		return -ENOMEM;
+	nlctx->ctx = ctx;
+
+	ctx->nlctx = nlctx;
+
+	return 0;
+}
+
+void netlink_done(struct cmd_context *ctx)
+{
+	if (!ctx->nlctx)
+		return;
+
+	free(ctx->nlctx);
+	ctx->nlctx = NULL;
+}
diff --git a/netlink/netlink.h b/netlink/netlink.h
new file mode 100644
index 000000000000..610beccbefda
--- /dev/null
+++ b/netlink/netlink.h
@@ -0,0 +1,26 @@
+/*
+ * netlink.h - common interface for all netlink code
+ *
+ * Declarations of data structures, global data and helpers for netlink code
+ */
+
+#ifndef ETHTOOL_NETLINK_INT_H__
+#define ETHTOOL_NETLINK_INT_H__
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ethtool_netlink.h>
+
+#define WILDCARD_DEVNAME "*"
+
+struct nl_context {
+	struct cmd_context	*ctx;
+	void			*cmd_private;
+	const char		*devname;
+	bool			is_dump;
+	int			exit_code;
+	bool			suppress_nlerr;
+};
+
+#endif /* ETHTOOL_NETLINK_INT_H__ */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 07/19] netlink: message buffer and composition helpers
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (5 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 06/19] netlink: introduce the netlink interface Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 08/19] netlink: netlink socket wrapper and helpers Michal Kubecek
                   ` (11 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Add data structure for flexible message buffer and helpers for safe message
composition.

The nl_msg_buff structure is an abstraction for a message buffer used to
compose an outgoing netlink message. When the message exceeds currently
allocated length, buffer is reallocated. Only if the buffer size reaches
MAX_MSG_SIZE (4 MB), an error is issued.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   1 +
 netlink/msgbuff.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/msgbuff.h | 106 +++++++++++++++++++++++++++++
 netlink/netlink.h |   1 +
 4 files changed, 277 insertions(+)
 create mode 100644 netlink/msgbuff.c
 create mode 100644 netlink/msgbuff.h

diff --git a/Makefile.am b/Makefile.am
index 3748d0df5608..12412b433445 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,6 +27,7 @@ endif
 if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
+		  netlink/msgbuff.c netlink/msgbuff.h \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/netlink/msgbuff.c b/netlink/msgbuff.c
new file mode 100644
index 000000000000..1e73354d194b
--- /dev/null
+++ b/netlink/msgbuff.c
@@ -0,0 +1,169 @@
+/*
+ * msgbuff.c - netlink message buffer
+ *
+ * Data structures and code for flexible message buffer abstraction.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "msgbuff.h"
+
+#define MAX_MSG_SIZE (4 << 20)		/* 4 MB */
+
+int msgbuff_realloc(struct nl_msg_buff *msgbuff, unsigned int new_size)
+{
+	unsigned int nlhdr_off, genlhdr_off, payload_off;
+	unsigned int old_size = msgbuff->size;
+	char *nbuff;
+
+	nlhdr_off = (char *)msgbuff->nlhdr - msgbuff->buff;
+	genlhdr_off = (char *)msgbuff->genlhdr - msgbuff->buff;
+	payload_off = (char *)msgbuff->payload - msgbuff->buff;
+
+	if (!new_size)
+		new_size = old_size + MNL_SOCKET_BUFFER_SIZE;
+	if (new_size <= old_size)
+		return 0;
+	if (new_size > MAX_MSG_SIZE)
+		return -EMSGSIZE;
+	nbuff = realloc(msgbuff->buff, new_size);
+	if (!nbuff) {
+		msgbuff->buff = NULL;
+		msgbuff->size = 0;
+		msgbuff->left = 0;
+		return -ENOMEM;
+	}
+	if (nbuff != msgbuff->buff) {
+		if (new_size > old_size)
+			memset(nbuff + old_size, '\0', new_size - old_size);
+		msgbuff->nlhdr = (struct nlmsghdr *)(nbuff + nlhdr_off);
+		msgbuff->genlhdr = (struct genlmsghdr *)(nbuff + genlhdr_off);
+		msgbuff->payload = nbuff + payload_off;
+		msgbuff->buff = nbuff;
+	}
+	msgbuff->size = new_size;
+	msgbuff->left += (new_size - old_size);
+
+	return 0;
+}
+
+int msgbuff_append(struct nl_msg_buff *dest, struct nl_msg_buff *src)
+{
+	unsigned int src_len = mnl_nlmsg_get_payload_len(src->nlhdr);
+	unsigned int dest_len = MNL_ALIGN(msgbuff_len(dest));
+	int ret;
+
+	ret = msgbuff_realloc(dest, dest_len + src_len);
+	if (ret < 0)
+		return ret;
+	memcpy(mnl_nlmsg_get_payload_tail(dest->nlhdr), src->payload, src_len);
+	msgbuff_reset(dest, dest_len + src_len);
+
+	return 0;
+}
+
+bool ethnla_put(struct nl_msg_buff *msgbuff, uint16_t type, size_t len,
+		const void *data)
+{
+	struct nlmsghdr *nlhdr = msgbuff->nlhdr;
+
+	while (!mnl_attr_put_check(nlhdr, msgbuff->left, type, len, data)) {
+		int ret = msgbuff_realloc(msgbuff, 0);
+
+		if (ret < 0)
+			return true;
+	}
+
+	return false;
+}
+
+struct nlattr *ethnla_nest_start(struct nl_msg_buff *msgbuff, uint16_t type)
+{
+	struct nlmsghdr *nlhdr = msgbuff->nlhdr;
+	struct nlattr *attr;
+
+	do {
+		attr = mnl_attr_nest_start_check(nlhdr, msgbuff->left, type);
+		if (attr)
+			return attr;
+	} while (msgbuff_realloc(msgbuff, 0) == 0);
+
+	return NULL;
+}
+
+bool ethnla_fill_header(struct nl_msg_buff *msgbuff, uint16_t type,
+			const char *devname, uint32_t flags)
+{
+	struct nlattr *nest;
+
+	nest = ethnla_nest_start(msgbuff, type);
+	if (!nest)
+		return true;
+
+	if ((devname &&
+	     ethnla_put_strz(msgbuff, ETHTOOL_A_HEADER_DEV_NAME, devname)) ||
+	    (flags &&
+	     ethnla_put_u32(msgbuff, ETHTOOL_A_HEADER_FLAGS, flags)))
+		goto err;
+
+	ethnla_nest_end(msgbuff, nest);
+	return false;
+
+err:
+	ethnla_nest_cancel(msgbuff, nest);
+	return true;
+}
+
+/* initialize a genetlink message and fill netlink and genetlink header */
+int __msg_init(struct nl_msg_buff *msgbuff, int family, int cmd,
+	       unsigned int flags, int version)
+{
+	struct nlmsghdr *nlhdr;
+	struct genlmsghdr *genlhdr;
+	int ret;
+
+	ret = msgbuff_realloc(msgbuff, MNL_SOCKET_BUFFER_SIZE);
+	if (ret < 0)
+		return ret;
+	memset(msgbuff->buff, '\0', NLMSG_HDRLEN + GENL_HDRLEN);
+
+	nlhdr = mnl_nlmsg_put_header(msgbuff->buff);
+	nlhdr->nlmsg_type = family;
+	nlhdr->nlmsg_flags = flags;
+	msgbuff->nlhdr = nlhdr;
+
+	genlhdr = mnl_nlmsg_put_extra_header(nlhdr, sizeof(*genlhdr));
+	genlhdr->cmd = cmd;
+	genlhdr->version = version;
+	msgbuff->genlhdr = genlhdr;
+
+	msgbuff->payload = mnl_nlmsg_get_payload_offset(nlhdr, GENL_HDRLEN);
+
+	return 0;
+}
+
+/* simplified wrapper to initialize an ethtool netlink message */
+int msg_init(struct nl_context *nlctx, struct nl_msg_buff *msgbuff, int cmd,
+	     unsigned int flags)
+{
+	return __msg_init(msgbuff, nlctx->ethnl_fam, cmd, flags,
+			  ETHTOOL_GENL_VERSION);
+}
+
+void msgbuff_init(struct nl_msg_buff *msgbuff)
+{
+	memset(msgbuff, '\0', sizeof(*msgbuff));
+}
+
+void msgbuff_done(struct nl_msg_buff *msgbuff)
+{
+	free(msgbuff->buff);
+	msgbuff->buff = NULL;
+	msgbuff->size = 0;
+	msgbuff->left = 0;
+}
diff --git a/netlink/msgbuff.h b/netlink/msgbuff.h
new file mode 100644
index 000000000000..6f3baa9992fd
--- /dev/null
+++ b/netlink/msgbuff.h
@@ -0,0 +1,106 @@
+/*
+ * msgbuff.h - netlink message buffer
+ *
+ * Declarations of netlink message buffer and related functions.
+ */
+
+#ifndef ETHTOOL_NETLINK_MSGBUFF_H__
+#define ETHTOOL_NETLINK_MSGBUFF_H__
+
+#include <string.h>
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+
+struct nl_context;
+
+struct nl_msg_buff {
+	char			*buff;
+	unsigned int		size;
+	unsigned int		left;
+	struct nlmsghdr		*nlhdr;
+	struct genlmsghdr	*genlhdr;
+	void			*payload;
+};
+
+void msgbuff_init(struct nl_msg_buff *msgbuff);
+void msgbuff_done(struct nl_msg_buff *msgbuff);
+int msgbuff_realloc(struct nl_msg_buff *msgbuff, unsigned int new_size);
+int msgbuff_append(struct nl_msg_buff *dest, struct nl_msg_buff *src);
+
+int __msg_init(struct nl_msg_buff *msgbuff, int family, int cmd,
+	       unsigned int flags, int version);
+int msg_init(struct nl_context *nlctx, struct nl_msg_buff *msgbuff, int cmd,
+	     unsigned int flags);
+
+bool ethnla_put(struct nl_msg_buff *msgbuff, uint16_t type, size_t len,
+		const void *data);
+struct nlattr *ethnla_nest_start(struct nl_msg_buff *msgbuff, uint16_t type);
+bool ethnla_fill_header(struct nl_msg_buff *msgbuff, uint16_t type,
+			const char *devname, uint32_t flags);
+
+static inline unsigned int msgbuff_len(const struct nl_msg_buff *msgbuff)
+{
+	return msgbuff->nlhdr->nlmsg_len;
+}
+
+static inline void msgbuff_reset(const struct nl_msg_buff *msgbuff,
+				 unsigned int len)
+{
+	msgbuff->nlhdr->nlmsg_len = len;
+}
+
+/* put data wrappers */
+
+static inline void ethnla_nest_end(struct nl_msg_buff *msgbuff,
+				   struct nlattr *nest)
+{
+	return mnl_attr_nest_end(msgbuff->nlhdr, nest);
+}
+
+static inline void ethnla_nest_cancel(struct nl_msg_buff *msgbuff,
+				      struct nlattr *nest)
+{
+	return mnl_attr_nest_cancel(msgbuff->nlhdr, nest);
+}
+
+static inline bool ethnla_put_u32(struct nl_msg_buff *msgbuff, uint16_t type,
+				  uint32_t data)
+{
+	return ethnla_put(msgbuff, type, sizeof(uint32_t), &data);
+}
+
+static inline bool ethnla_put_u8(struct nl_msg_buff *msgbuff, uint16_t type,
+				 uint8_t data)
+{
+	return ethnla_put(msgbuff, type, sizeof(uint8_t), &data);
+}
+
+static inline bool ethnla_put_flag(struct nl_msg_buff *msgbuff, uint16_t type,
+				   bool val)
+{
+	if (val)
+		return ethnla_put(msgbuff, type, 0, &val);
+	else
+		return false;
+}
+
+static inline bool ethnla_put_bitfield32(struct nl_msg_buff *msgbuff,
+					 uint16_t type, uint32_t value,
+					 uint32_t selector)
+{
+	struct nla_bitfield32 val = {
+		.value		= value,
+		.selector	= selector,
+	};
+
+	return ethnla_put(msgbuff, type, sizeof(val), &val);
+}
+
+static inline bool ethnla_put_strz(struct nl_msg_buff *msgbuff, uint16_t type,
+				   const char *data)
+{
+	return ethnla_put(msgbuff, type, strlen(data) + 1, data);
+}
+
+#endif /* ETHTOOL_NETLINK_MSGBUFF_H__ */
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 610beccbefda..48c4ebb3d7aa 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -21,6 +21,7 @@ struct nl_context {
 	bool			is_dump;
 	int			exit_code;
 	bool			suppress_nlerr;
+	uint16_t		ethnl_fam;
 };
 
 #endif /* ETHTOOL_NETLINK_INT_H__ */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 08/19] netlink: netlink socket wrapper and helpers
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (6 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 07/19] netlink: message buffer and composition helpers Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 09/19] netlink: initialize ethtool netlink socket Michal Kubecek
                   ` (10 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Add data structure for netlink socket and few helpers to work with it.

The nl_socket structure is a wrapper for struct mnl_socket from libmnl,
a message buffer and some additional data. Helpers for sending netlink
(and genetlink), processing replies are provided as well as debugging
helpers.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   3 +-
 internal.h        |   8 ++
 netlink/netlink.c |  28 +++++
 netlink/netlink.h |  11 ++
 netlink/nlsock.c  | 291 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/nlsock.h  |  35 ++++++
 6 files changed, 375 insertions(+), 1 deletion(-)
 create mode 100644 netlink/nlsock.c
 create mode 100644 netlink/nlsock.h

diff --git a/Makefile.am b/Makefile.am
index 12412b433445..857de24a130a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,7 +27,8 @@ endif
 if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
-		  netlink/msgbuff.c netlink/msgbuff.h \
+		  netlink/msgbuff.c netlink/msgbuff.h netlink/nlsock.c \
+		  netlink/nlsock.h \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/internal.h b/internal.h
index 72a04e638a13..a518bfa99380 100644
--- a/internal.h
+++ b/internal.h
@@ -119,8 +119,16 @@ static inline int test_bit(unsigned int nr, const unsigned long *addr)
 /* debugging flags */
 enum {
 	DEBUG_PARSE,
+	DEBUG_NL_MSGS,		/* incoming/outgoing netlink messages */
+	DEBUG_NL_DUMP_SND,	/* dump outgoing netlink messages */
+	DEBUG_NL_DUMP_RCV,	/* dump incoming netlink messages */
 };
 
+static inline bool debug_on(unsigned long debug, unsigned int bit)
+{
+	return (debug & (1 << bit));
+}
+
 /* Internal values for old-style offload flags.  Values and names
  * must not clash with the flags defined for ETHTOOL_{G,S}FLAGS.
  */
diff --git a/netlink/netlink.c b/netlink/netlink.c
index 84e188119989..7d5eca666c84 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -10,6 +10,34 @@
 #include "netlink.h"
 #include "extapi.h"
 
+/* Used as reply callback for requests where no reply is expected (e.g. most
+ * "set" type commands)
+ */
+int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+
+	fprintf(stderr, "received unexpected message: len=%u type=%u cmd=%u\n",
+		nlhdr->nlmsg_len, nlhdr->nlmsg_type, ghdr->cmd);
+	return MNL_CB_OK;
+}
+
+/* standard attribute parser callback; it fills provided array with pointers
+ * to attributes like kernel nla_parse(). We must expect to run on top of
+ * a newer kernel which may send attributes that we do not know (yet). Rather
+ * than treating them as an error, just ignore them.
+ */
+int attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct attr_tb_info *tb_info = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (type >= 0 && type <= tb_info->max_type)
+		tb_info->tb[type] = attr;
+
+	return MNL_CB_OK;
+}
+
 /* initialization */
 
 int netlink_init(struct cmd_context *ctx)
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 48c4ebb3d7aa..54d2baaab516 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -24,4 +24,15 @@ struct nl_context {
 	uint16_t		ethnl_fam;
 };
 
+struct attr_tb_info {
+	const struct nlattr **tb;
+	unsigned int max_type;
+};
+
+#define DECLARE_ATTR_TB_INFO(tbl) \
+	struct attr_tb_info tbl ## _info = { (tbl), (MNL_ARRAY_SIZE(tbl) - 1) }
+
+int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int attr_cb(const struct nlattr *attr, void *data);
+
 #endif /* ETHTOOL_NETLINK_INT_H__ */
diff --git a/netlink/nlsock.c b/netlink/nlsock.c
new file mode 100644
index 000000000000..8cb661e96e30
--- /dev/null
+++ b/netlink/nlsock.c
@@ -0,0 +1,291 @@
+/*
+ * nlsock.c - netlink socket
+ *
+ * Data structure and code for netlink socket abstraction.
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include "../internal.h"
+#include "nlsock.h"
+#include "netlink.h"
+
+#define NLSOCK_RECV_BUFFSIZE 65536
+
+#define __MSG_NAME(x) \
+	[ETHTOOL_MSG_ ## x] = "ETHTOOL_MSG_" #x
+
+static const char *const ethnl_umsg_names[] = {
+	__MSG_NAME(USER_NONE),
+	__MSG_NAME(STRSET_GET),
+	__MSG_NAME(LINKINFO_GET),
+	__MSG_NAME(LINKINFO_SET),
+	__MSG_NAME(LINKMODES_GET),
+	__MSG_NAME(LINKMODES_SET),
+	__MSG_NAME(LINKSTATE_GET),
+	__MSG_NAME(DEBUG_GET),
+	__MSG_NAME(DEBUG_SET),
+	__MSG_NAME(WOL_GET),
+	__MSG_NAME(WOL_SET),
+};
+
+static const char *const ethnl_kmsg_names[] = {
+	__MSG_NAME(KERNEL_NONE),
+	__MSG_NAME(STRSET_GET_REPLY),
+	__MSG_NAME(LINKINFO_GET_REPLY),
+	__MSG_NAME(LINKINFO_NTF),
+	__MSG_NAME(LINKMODES_GET_REPLY),
+	__MSG_NAME(LINKMODES_NTF),
+	__MSG_NAME(LINKSTATE_GET_REPLY),
+	__MSG_NAME(DEBUG_GET_REPLY),
+	__MSG_NAME(DEBUG_NTF),
+	__MSG_NAME(WOL_GET_REPLY),
+	__MSG_NAME(WOL_NTF),
+};
+
+#undef __MSG_NAME
+
+static const char *msg_type_str(unsigned int type, unsigned int ethnl_fam)
+{
+	if (type == ethnl_fam)
+		return "ethtool";
+
+	switch (type) {
+	case NLMSG_NOOP:
+		return "noop";
+	case NLMSG_ERROR:
+		return "error";
+	case NLMSG_DONE:
+		return "done";
+	case NLMSG_OVERRUN:
+		return "overrun";
+	case GENL_ID_CTRL:
+		return "genl-ctrl";
+	}
+
+	return "unknown";
+}
+
+static void debug_msg_summary(const struct nlmsghdr *nlhdr, int ethnl_fam,
+			      bool outgoing)
+{
+	const char * const *msg_names =
+		outgoing ? ethnl_umsg_names : ethnl_kmsg_names;
+	const unsigned int names_cnt =
+		outgoing ? MNL_ARRAY_SIZE(ethnl_umsg_names) :
+			   MNL_ARRAY_SIZE(ethnl_kmsg_names);
+
+	printf("    msg length %u family %u (%s) flags %04x", nlhdr->nlmsg_len,
+	       nlhdr->nlmsg_type, msg_type_str(nlhdr->nlmsg_type, ethnl_fam),
+	       nlhdr->nlmsg_flags);
+	if (nlhdr->nlmsg_type == ethnl_fam &&
+	    nlhdr->nlmsg_len >= NLMSG_HDRLEN + GENL_HDRLEN) {
+		const struct genlmsghdr *ghdr = mnl_nlmsg_get_payload(nlhdr);
+		unsigned int cmd = ghdr->cmd;
+		const char *cmd_name = "unknown";
+
+		if (cmd < names_cnt && msg_names[cmd])
+			cmd_name = msg_names[cmd];
+		printf(" ethtool cmd %u (%s)", cmd, cmd_name);
+	}
+	fputc('\n', stdout);
+}
+
+static void debug_msg(struct nl_socket *nlsk, const void *msg, unsigned int len,
+		      bool outgoing)
+{
+	const char *dirlabel = outgoing ? "sending" : "received";
+	uint32_t debug = nlsk->nlctx->ctx->debug;
+	const struct nlmsghdr *nlhdr = msg;
+	bool summary, dump;
+	int left = len;
+
+	summary = debug_on(debug, DEBUG_NL_MSGS);
+	dump = debug_on(debug,
+			outgoing ? DEBUG_NL_DUMP_SND : DEBUG_NL_DUMP_RCV);
+	if (!summary && !dump)
+		return;
+	printf("%s packet (%u bytes):\n", dirlabel, len);
+
+	while (nlhdr && left > 0 && mnl_nlmsg_ok(nlhdr, left)) {
+		if (summary)
+			debug_msg_summary(nlhdr, nlsk->nlctx->ethnl_fam,
+					  outgoing);
+		if (dump)
+			mnl_nlmsg_fprintf(stdout, nlhdr, nlhdr->nlmsg_len,
+					  GENL_HDRLEN);
+
+		nlhdr = mnl_nlmsg_next(nlhdr, &left);
+	}
+}
+
+static int nlsock_process_ack(struct nlmsghdr *nlhdr, ssize_t len, bool silent)
+{
+	const struct nlattr *tb[NLMSGERR_ATTR_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	unsigned int tlv_offset;
+	struct nlmsgerr *nlerr;
+
+	if (len < NLMSG_HDRLEN + sizeof(*nlerr))
+		return -EFAULT;
+	nlerr = mnl_nlmsg_get_payload(nlhdr);
+	if (silent || !(nlhdr->nlmsg_flags & NLM_F_ACK_TLVS))
+		goto out;
+	tlv_offset = sizeof(*nlerr);
+	if (!(nlhdr->nlmsg_flags & NLM_F_CAPPED))
+		tlv_offset += MNL_ALIGN(mnl_nlmsg_get_payload_len(&nlerr->msg));
+
+	if (mnl_attr_parse(nlhdr, tlv_offset, attr_cb, &tb_info) < 0)
+		goto out;
+	if (tb[NLMSGERR_ATTR_MSG]) {
+		const char *msg = mnl_attr_get_str(tb[NLMSGERR_ATTR_MSG]);
+
+		fprintf(stderr, "netlink %s: %s",
+			nlerr->error ? "error" : "warning", msg);
+		if (tb[NLMSGERR_ATTR_OFFS])
+			fprintf(stderr, " (offset %u)",
+				mnl_attr_get_u32(tb[NLMSGERR_ATTR_OFFS]));
+		fputc('\n', stderr);
+	}
+
+out:
+	if (nlerr->error) {
+		errno = -nlerr->error;
+		if (!silent)
+			perror("netlink error");
+	}
+	return nlerr->error;
+}
+
+int nlsock_process_reply(struct nl_socket *nlsk, mnl_cb_t reply_cb, void *data)
+{
+	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+	struct nlmsghdr *nlhdr;
+	ssize_t len;
+	char *buff;
+	int ret;
+
+	ret = msgbuff_realloc(msgbuff, NLSOCK_RECV_BUFFSIZE);
+	if (ret < 0)
+		return ret;
+	buff = msgbuff->buff;
+
+	do {
+		len = mnl_socket_recvfrom(nlsk->sk, buff, msgbuff->size);
+		if (len <= 0)
+			return (len ? -EFAULT : 0);
+		debug_msg(nlsk, buff, len, false);
+		if (len < NLMSG_HDRLEN)
+			return -EFAULT;
+
+		nlhdr = (struct nlmsghdr *)buff;
+		if (nlhdr->nlmsg_type == NLMSG_ERROR)
+			return nlsock_process_ack(nlhdr, len,
+						  nlsk->nlctx->suppress_nlerr);
+
+		msgbuff->nlhdr = nlhdr;
+		msgbuff->genlhdr = mnl_nlmsg_get_payload(nlhdr);
+		msgbuff->payload =
+			mnl_nlmsg_get_payload_offset(nlhdr, GENL_HDRLEN);
+		ret = mnl_cb_run(buff, len, nlsk->seq, nlsk->port, reply_cb,
+				 data);
+	} while (ret > 0);
+
+	return ret;
+}
+
+int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
+			    uint16_t hdr_attrtype, u32 flags)
+{
+	unsigned int nlm_flags = NLM_F_REQUEST | NLM_F_ACK;
+	struct nl_context *nlctx = nlsk->nlctx;
+	const char *devname = nlctx->ctx->devname;
+	int ret;
+
+	if (devname && !strcmp(devname, WILDCARD_DEVNAME)) {
+		devname = NULL;
+		nlm_flags |= NLM_F_DUMP;
+	}
+	nlctx->is_dump = !devname;
+
+	ret = msg_init(nlctx, &nlsk->msgbuff, nlcmd, nlm_flags);
+	if (ret < 0)
+		return ret;
+	if (ethnla_fill_header(&nlsk->msgbuff, hdr_attrtype, devname, flags))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *altbuff)
+{
+	struct nl_msg_buff *msgbuff = altbuff ?: &nlsk->msgbuff;
+	struct nlmsghdr *nlhdr = msgbuff->nlhdr;
+
+	nlhdr->nlmsg_seq = ++nlsk->seq;
+	debug_msg(nlsk, msgbuff->buff, nlhdr->nlmsg_len, true);
+	return mnl_socket_sendto(nlsk->sk, nlhdr, nlhdr->nlmsg_len);
+}
+
+int nlsock_send_get_request(struct nl_socket *nlsk, mnl_cb_t cb)
+{
+	int ret;
+
+	ret = nlsock_sendmsg(nlsk, NULL);
+	if (ret < 0)
+		goto err;
+	ret = nlsock_process_reply(nlsk, cb, nlsk->nlctx);
+	if (ret == 0)
+		return 0;
+err:
+	return nlsk->nlctx->exit_code ?: 1;
+}
+
+int __nlsock_init(struct nl_context *nlctx, struct nl_socket **__nlsk, int bus)
+{
+	struct nl_socket *nlsk;
+	int val;
+	int ret;
+
+	nlsk = calloc(1, sizeof(*nlsk));
+	if (!nlsk)
+		return -ENOMEM;
+	nlsk->nlctx = nlctx;
+	msgbuff_init(&nlsk->msgbuff);
+
+	ret = -ECONNREFUSED;
+	nlsk->sk = mnl_socket_open(bus);
+	if (!nlsk->sk)
+		goto out_msgbuff;
+	val = 1;
+	mnl_socket_setsockopt(nlsk->sk, NETLINK_EXT_ACK, &val, sizeof(val));
+	ret = mnl_socket_bind(nlsk->sk, 0, MNL_SOCKET_AUTOPID);
+	if (ret < 0)
+		goto out_close;
+	nlsk->port = mnl_socket_get_portid(nlsk->sk);
+
+	*__nlsk = nlsk;
+	return 0;
+
+out_close:
+	if (nlsk->sk)
+		mnl_socket_close(nlsk->sk);
+out_msgbuff:
+	msgbuff_done(&nlsk->msgbuff);
+	free(nlsk);
+	return ret;
+}
+
+int nlsock_init(struct nl_context *nlctx,  struct nl_socket **__nlsk)
+{
+	return __nlsock_init(nlctx, __nlsk, NETLINK_GENERIC);
+}
+
+void nlsock_done(struct nl_socket *nlsk)
+{
+	if (nlsk->sk)
+		mnl_socket_close(nlsk->sk);
+	msgbuff_done(&nlsk->msgbuff);
+	memset(nlsk, '\0', sizeof(*nlsk));
+}
diff --git a/netlink/nlsock.h b/netlink/nlsock.h
new file mode 100644
index 000000000000..beab176e6775
--- /dev/null
+++ b/netlink/nlsock.h
@@ -0,0 +1,35 @@
+/*
+ * nlsock.h - netlink socket
+ *
+ * Declarations of netlink socket structure and related functions.
+ */
+
+#ifndef ETHTOOL_NETLINK_NLSOCK_H__
+#define ETHTOOL_NETLINK_NLSOCK_H__
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ethtool_netlink.h>
+#include "msgbuff.h"
+
+struct nl_context;
+
+struct nl_socket {
+	struct nl_context	*nlctx;
+	struct mnl_socket	*sk;
+	struct nl_msg_buff	msgbuff;
+	unsigned int		port;
+	unsigned int		seq;
+};
+
+int __nlsock_init(struct nl_context *nlctx, struct nl_socket **__nlsk, int bus);
+int nlsock_init(struct nl_context *nlctx,  struct nl_socket **__nlsk);
+void nlsock_done(struct nl_socket *nlsk);
+int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
+			    uint16_t hdr_attrtype, u32 flags);
+ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *__msgbuff);
+int nlsock_send_get_request(struct nl_socket *nlsk, mnl_cb_t cb);
+int nlsock_process_reply(struct nl_socket *nlsk, mnl_cb_t reply_cb, void *data);
+
+#endif /* ETHTOOL_NETLINK_NLSOCK_H__ */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 09/19] netlink: initialize ethtool netlink socket
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (7 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 08/19] netlink: netlink socket wrapper and helpers Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 10/19] netlink: add support for string sets Michal Kubecek
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

As part of the netlink initialization, set up netlink socket for ethtool
netlink and get id of ethtool genetlink family and monitor multicast group.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 netlink/netlink.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++-
 netlink/netlink.h | 11 ++++++
 2 files changed, 104 insertions(+), 1 deletion(-)

diff --git a/netlink/netlink.c b/netlink/netlink.c
index 7d5eca666c84..2128e2775e12 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -9,6 +9,8 @@
 #include "../internal.h"
 #include "netlink.h"
 #include "extapi.h"
+#include "msgbuff.h"
+#include "nlsock.h"
 
 /* Used as reply callback for requests where no reply is expected (e.g. most
  * "set" type commands)
@@ -40,18 +42,108 @@ int attr_cb(const struct nlattr *attr, void *data)
 
 /* initialization */
 
+struct fam_info {
+	const char	*fam_name;
+	const char	*grp_name;
+	uint16_t	fam_id;
+	uint32_t	grp_id;
+};
+
+static void find_mc_group(struct nlattr *nest, struct fam_info *info)
+{
+	const struct nlattr *grp_tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(grp_tb);
+	struct nlattr *grp_attr;
+	int ret;
+
+	mnl_attr_for_each_nested(grp_attr, nest) {
+		ret = mnl_attr_parse_nested(grp_attr, attr_cb, &grp_tb_info);
+		if (ret < 0)
+			return;
+		if (!grp_tb[CTRL_ATTR_MCAST_GRP_NAME] ||
+		    !grp_tb[CTRL_ATTR_MCAST_GRP_ID])
+			continue;
+		if (strcmp(mnl_attr_get_str(grp_tb[CTRL_ATTR_MCAST_GRP_NAME]),
+			   info->grp_name))
+			continue;
+		info->grp_id =
+			mnl_attr_get_u32(grp_tb[CTRL_ATTR_MCAST_GRP_ID]);
+		return;
+	}
+}
+
+static int family_info_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	struct fam_info *info = data;
+	struct nlattr *attr;
+
+	mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+		switch (mnl_attr_get_type(attr)) {
+		case CTRL_ATTR_FAMILY_ID:
+			info->fam_id = mnl_attr_get_u16(attr);
+			break;
+		case CTRL_ATTR_MCAST_GROUPS:
+			find_mc_group(attr, info);
+			break;
+		}
+	}
+
+	return MNL_CB_OK;
+}
+
+static int get_genl_family(struct nl_socket *nlsk, struct fam_info *info)
+{
+	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+	int ret;
+
+	nlsk->nlctx->suppress_nlerr = true;
+	ret = __msg_init(msgbuff, GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
+			 NLM_F_REQUEST | NLM_F_ACK, 1);
+	if (ret < 0)
+		goto out;
+	ret = -EMSGSIZE;
+	if (ethnla_put_strz(msgbuff, CTRL_ATTR_FAMILY_NAME, info->fam_name))
+		goto out;
+
+	nlsock_sendmsg(nlsk, NULL);
+	nlsock_process_reply(nlsk, family_info_cb, info);
+	ret = info->fam_id ? 0 : -EADDRNOTAVAIL;
+
+out:
+	nlsk->nlctx->suppress_nlerr = false;
+	return ret;
+}
+
 int netlink_init(struct cmd_context *ctx)
 {
+	struct fam_info info = {
+		.fam_name	= ETHTOOL_GENL_NAME,
+		.grp_name	= ETHTOOL_MCGRP_MONITOR_NAME,
+	};
 	struct nl_context *nlctx;
+	int ret;
 
 	nlctx = calloc(1, sizeof(*nlctx));
 	if (!nlctx)
 		return -ENOMEM;
 	nlctx->ctx = ctx;
+	ret = nlsock_init(nlctx, &nlctx->ethnl_socket);
+	if (ret < 0)
+		goto out_free;
+	ret = get_genl_family(nlctx->ethnl_socket, &info);
+	if (ret < 0)
+		goto out_nlsk;
+	nlctx->ethnl_fam = info.fam_id;
+	nlctx->ethnl_mongrp = info.grp_id;
 
 	ctx->nlctx = nlctx;
-
 	return 0;
+
+out_nlsk:
+	nlsock_done(nlctx->ethnl_socket);
+out_free:
+	free(nlctx);
+	return ret;
 }
 
 void netlink_done(struct cmd_context *ctx)
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 54d2baaab516..081226be73e8 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -11,6 +11,7 @@
 #include <linux/netlink.h>
 #include <linux/genetlink.h>
 #include <linux/ethtool_netlink.h>
+#include "nlsock.h"
 
 #define WILDCARD_DEVNAME "*"
 
@@ -22,6 +23,9 @@ struct nl_context {
 	int			exit_code;
 	bool			suppress_nlerr;
 	uint16_t		ethnl_fam;
+	uint32_t		ethnl_mongrp;
+	struct nl_socket	*ethnl_socket;
+	struct nl_socket	*ethnl2_socket;
 };
 
 struct attr_tb_info {
@@ -35,4 +39,11 @@ struct attr_tb_info {
 int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 int attr_cb(const struct nlattr *attr, void *data);
 
+static inline int netlink_init_ethnl2_socket(struct nl_context *nlctx)
+{
+	if (nlctx->ethnl2_socket)
+		return 0;
+	return nlsock_init(nlctx, &nlctx->ethnl2_socket);
+}
+
 #endif /* ETHTOOL_NETLINK_INT_H__ */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 10/19] netlink: add support for string sets
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (8 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 09/19] netlink: initialize ethtool netlink socket Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 11/19] netlink: add notification monitor Michal Kubecek
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Add infrastructure for querying kernel for string sets (analog to ioctl
commands ETHTOOL_GSSET_INFO and ETHTOOL_GSTRINGS), caching the results and
making them available to netlink code.

There are two types of string sets: global (not related to a device) and
per device (each device has its set of string sets). Per device string sets
are stored in a linked list (one entry for each device) for now.

String sets can be either preloaded completely on start using
preload_global_strings() and preload_perdev_strings() or requested by one
when there is a need for them. In the latter case (preferred, in particular
for one shot operation mode), second netlink socket is used to request the
string set contents.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   2 +-
 netlink/netlink.c |  53 +++++++++
 netlink/netlink.h |   9 ++
 netlink/strset.c  | 295 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/strset.h  |  25 ++++
 5 files changed, 383 insertions(+), 1 deletion(-)
 create mode 100644 netlink/strset.c
 create mode 100644 netlink/strset.h

diff --git a/Makefile.am b/Makefile.am
index 857de24a130a..2685280aee15 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,7 +28,7 @@ if ETHTOOL_ENABLE_NETLINK
 ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
 		  netlink/msgbuff.c netlink/msgbuff.h netlink/nlsock.c \
-		  netlink/nlsock.h \
+		  netlink/nlsock.h netlink/strset.c netlink/strset.h \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/netlink/netlink.c b/netlink/netlink.c
index 2128e2775e12..2e49c6618c53 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -11,6 +11,7 @@
 #include "extapi.h"
 #include "msgbuff.h"
 #include "nlsock.h"
+#include "strset.h"
 
 /* Used as reply callback for requests where no reply is expected (e.g. most
  * "set" type commands)
@@ -40,6 +41,57 @@ int attr_cb(const struct nlattr *attr, void *data)
 	return MNL_CB_OK;
 }
 
+/* misc helpers */
+
+const char *get_dev_name(const struct nlattr *nest)
+{
+	const struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	if (!nest)
+		return NULL;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	if (ret < 0 || !tb[ETHTOOL_A_HEADER_DEV_NAME])
+		return "(none)";
+	return mnl_attr_get_str(tb[ETHTOOL_A_HEADER_DEV_NAME]);
+}
+
+int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname)
+{
+	const struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1] = {};
+	const struct nlattr *index_attr;
+	const struct nlattr *name_attr;
+	DECLARE_ATTR_TB_INFO(tb);
+	int ret;
+
+	if (ifindex)
+		*ifindex = 0;
+	if (ifname)
+		memset(ifname, '\0', ALTIFNAMSIZ);
+
+	if (!nest)
+		return -EFAULT;
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
+	index_attr = tb[ETHTOOL_A_HEADER_DEV_INDEX];
+	name_attr = tb[ETHTOOL_A_HEADER_DEV_NAME];
+	if (ret < 0 || (ifindex && !index_attr) || (ifname && !name_attr))
+		return -EFAULT;
+
+	if (ifindex)
+		*ifindex = mnl_attr_get_u32(index_attr);
+	if (ifname) {
+		strncpy(ifname, mnl_attr_get_str(name_attr), ALTIFNAMSIZ);
+		if (ifname[ALTIFNAMSIZ - 1]) {
+			ifname[ALTIFNAMSIZ - 1] = '\0';
+			fprintf(stderr, "kernel device name too long: '%s'\n",
+				mnl_attr_get_str(name_attr));
+			return -EFAULT;
+		}
+	}
+	return 0;
+}
+
 /* initialization */
 
 struct fam_info {
@@ -153,4 +205,5 @@ void netlink_done(struct cmd_context *ctx)
 
 	free(ctx->nlctx);
 	ctx->nlctx = NULL;
+	cleanup_all_strings();
 }
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 081226be73e8..84a5e5c0f204 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -39,6 +39,15 @@ struct attr_tb_info {
 int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 int attr_cb(const struct nlattr *attr, void *data);
 
+const char *get_dev_name(const struct nlattr *nest);
+int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname);
+
+static inline void copy_devname(char *dst, const char *src)
+{
+	strncpy(dst, src, ALTIFNAMSIZ);
+	dst[ALTIFNAMSIZ - 1] = '\0';
+}
+
 static inline int netlink_init_ethnl2_socket(struct nl_context *nlctx)
 {
 	if (nlctx->ethnl2_socket)
diff --git a/netlink/strset.c b/netlink/strset.c
new file mode 100644
index 000000000000..bc468ae5a88e
--- /dev/null
+++ b/netlink/strset.c
@@ -0,0 +1,295 @@
+/*
+ * strset.c - string set handling
+ *
+ * Implementation of local cache of ethtool string sets.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "nlsock.h"
+#include "msgbuff.h"
+
+struct stringset {
+	const char		**strings;
+	void			*raw_data;
+	unsigned int		count;
+	bool			loaded;
+};
+
+struct perdev_strings {
+	int			ifindex;
+	char			devname[ALTIFNAMSIZ];
+	struct stringset	strings[ETH_SS_COUNT];
+	struct perdev_strings	*next;
+};
+
+/* universal string sets */
+static struct stringset global_strings[ETH_SS_COUNT];
+/* linked list of string sets related to network devices */
+static struct perdev_strings *device_strings;
+
+static void drop_stringset(struct stringset *set)
+{
+	if (!set)
+		return;
+
+	free(set->strings);
+	free(set->raw_data);
+	memset(set, 0, sizeof(*set));
+}
+
+static int import_stringset(struct stringset *dest, const struct nlattr *nest)
+{
+	const struct nlattr *tb_stringset[ETHTOOL_A_STRINGSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb_stringset);
+	const struct nlattr *string;
+	unsigned int size;
+	unsigned int count;
+	unsigned int idx;
+	int ret;
+
+	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_stringset_info);
+	if (ret < 0)
+		return ret;
+	if (!tb_stringset[ETHTOOL_A_STRINGSET_ID] ||
+	    !tb_stringset[ETHTOOL_A_STRINGSET_COUNT] ||
+	    !tb_stringset[ETHTOOL_A_STRINGSET_STRINGS])
+		return -EFAULT;
+	idx = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_ID]);
+	if (idx >= ETH_SS_COUNT)
+		return 0;
+	if (dest[idx].loaded)
+		drop_stringset(&dest[idx]);
+	dest[idx].loaded = true;
+	count = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_COUNT]);
+	if (count == 0)
+		return 0;
+
+	size = mnl_attr_get_len(tb_stringset[ETHTOOL_A_STRINGSET_STRINGS]);
+	ret = -ENOMEM;
+	dest[idx].raw_data = malloc(size);
+	if (!dest[idx].raw_data)
+		goto err;
+	memcpy(dest[idx].raw_data, tb_stringset[ETHTOOL_A_STRINGSET_STRINGS],
+	       size);
+	dest[idx].strings = calloc(count, sizeof(dest[idx].strings[0]));
+	if (!dest[idx].strings)
+		goto err;
+	dest[idx].count = count;
+
+	nest = dest[idx].raw_data;
+	mnl_attr_for_each_nested(string, nest) {
+		const struct nlattr *tb[ETHTOOL_A_STRING_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+		unsigned int i;
+
+		if (mnl_attr_get_type(string) != ETHTOOL_A_STRINGS_STRING)
+			continue;
+		ret = mnl_attr_parse_nested(string, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		ret = -EFAULT;
+		if (!tb[ETHTOOL_A_STRING_INDEX] || !tb[ETHTOOL_A_STRING_VALUE])
+			goto err;
+
+		i = mnl_attr_get_u32(tb[ETHTOOL_A_STRING_INDEX]);
+		if (i >= count)
+			goto err;
+		dest[idx].strings[i] =
+			mnl_attr_get_payload(tb[ETHTOOL_A_STRING_VALUE]);
+	}
+
+	return 0;
+err:
+	drop_stringset(&dest[idx]);
+	return ret;
+}
+
+static struct perdev_strings *get_perdev_by_ifindex(int ifindex)
+{
+	struct perdev_strings *perdev = device_strings;
+
+	while (perdev && perdev->ifindex != ifindex)
+		perdev = perdev->next;
+	if (perdev)
+		return perdev;
+
+	/* not found, allocate and insert into list */
+	perdev = calloc(sizeof(*perdev), 1);
+	if (!perdev)
+		return NULL;
+	perdev->ifindex = ifindex;
+	perdev->next = device_strings;
+	device_strings = perdev;
+
+	return perdev;
+}
+
+static int strset_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_STRSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	char devname[ALTIFNAMSIZ] = "";
+	struct stringset *dest;
+	struct nlattr *attr;
+	int ifindex = 0;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	if (tb[ETHTOOL_A_STRSET_HEADER]) {
+		ret = get_dev_info(tb[ETHTOOL_A_STRSET_HEADER], &ifindex,
+				   devname);
+		if (ret < 0)
+			return MNL_CB_OK;
+		nlctx->devname = devname;
+	}
+
+	if (ifindex) {
+		struct perdev_strings *perdev;
+
+		perdev = get_perdev_by_ifindex(ifindex);
+		if (!perdev)
+			return MNL_CB_OK;
+		copy_devname(perdev->devname, devname);
+		dest = perdev->strings;
+	} else {
+		dest = global_strings;
+	}
+
+	if (!tb[ETHTOOL_A_STRSET_STRINGSETS])
+		return MNL_CB_OK;
+	mnl_attr_for_each_nested(attr, tb[ETHTOOL_A_STRSET_STRINGSETS]) {
+		if (mnl_attr_get_type(attr) ==
+		    ETHTOOL_A_STRINGSETS_STRINGSET)
+			import_stringset(dest, attr);
+	}
+
+	return MNL_CB_OK;
+}
+
+static int fill_stringset_id(struct nl_msg_buff *msgbuff, unsigned int type)
+{
+	struct nlattr *nest_sets;
+	struct nlattr *nest_set;
+
+	nest_sets = ethnla_nest_start(msgbuff, ETHTOOL_A_STRSET_STRINGSETS);
+	if (!nest_sets)
+		return -EMSGSIZE;
+	nest_set = ethnla_nest_start(msgbuff, ETHTOOL_A_STRINGSETS_STRINGSET);
+	if (!nest_set)
+		goto err;
+	if (ethnla_put_u32(msgbuff, ETHTOOL_A_STRINGSET_ID, type))
+		goto err;
+	ethnla_nest_end(msgbuff, nest_set);
+	ethnla_nest_end(msgbuff, nest_sets);
+	return 0;
+
+err:
+	ethnla_nest_cancel(msgbuff, nest_sets);
+	return -EMSGSIZE;
+}
+
+static int stringset_load_request(struct nl_socket *nlsk, const char *devname,
+				  int type, bool is_dump)
+{
+	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+	int ret;
+
+	ret = msg_init(nlsk->nlctx, msgbuff, ETHTOOL_MSG_STRSET_GET,
+		       NLM_F_REQUEST | NLM_F_ACK | (is_dump ? NLM_F_DUMP : 0));
+	if (ret < 0)
+		return ret;
+	if (ethnla_fill_header(msgbuff, ETHTOOL_A_STRSET_HEADER, devname, 0))
+		return -EMSGSIZE;
+	if (type >= 0) {
+		ret = fill_stringset_id(msgbuff, type);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = nlsock_send_get_request(nlsk, strset_reply_cb);
+	return ret;
+}
+
+/* interface */
+
+const struct stringset *global_stringset(unsigned int type,
+					 struct nl_socket *nlsk)
+{
+	int ret;
+
+	if (type >= ETH_SS_COUNT)
+		return NULL;
+	if (global_strings[type].loaded)
+		return &global_strings[type];
+	ret = stringset_load_request(nlsk, NULL, type, false);
+	return ret < 0 ? NULL : &global_strings[type];
+}
+
+const struct stringset *perdev_stringset(const char *devname, unsigned int type,
+					 struct nl_socket *nlsk)
+{
+	const struct perdev_strings *p;
+	int ret;
+
+	if (type >= ETH_SS_COUNT)
+		return NULL;
+	for (p = device_strings; p; p = p->next)
+		if (!strcmp(p->devname, devname))
+			return &p->strings[type];
+
+	ret = stringset_load_request(nlsk, devname, type, false);
+	if (ret < 0)
+		return NULL;
+	for (p = device_strings; p; p = p->next)
+		if (!strcmp(p->devname, devname))
+			return &p->strings[type];
+
+	return NULL;
+}
+
+unsigned int get_count(const struct stringset *set)
+{
+	return set->count;
+}
+
+const char *get_string(const struct stringset *set, unsigned int idx)
+{
+	if (!set || idx >= set->count)
+		return NULL;
+	return set->strings[idx];
+}
+
+int preload_global_strings(struct nl_socket *nlsk)
+{
+	return stringset_load_request(nlsk, NULL, -1, false);
+}
+
+int preload_perdev_strings(struct nl_socket *nlsk, const char *dev)
+{
+	return stringset_load_request(nlsk, dev, -1, !dev);
+}
+
+void cleanup_all_strings(void)
+{
+	struct perdev_strings *perdev;
+	unsigned int i;
+
+	for (i = 0; i < ETH_SS_COUNT; i++)
+		drop_stringset(&global_strings[i]);
+
+	perdev = device_strings;
+	while (perdev) {
+		device_strings = perdev->next;
+		for (i = 0; i < ETH_SS_COUNT; i++)
+			drop_stringset(&perdev->strings[i]);
+		free(perdev);
+		perdev = device_strings;
+	}
+}
diff --git a/netlink/strset.h b/netlink/strset.h
new file mode 100644
index 000000000000..72a4a3929244
--- /dev/null
+++ b/netlink/strset.h
@@ -0,0 +1,25 @@
+/*
+ * strset.h - string set handling
+ *
+ * Interface for local cache of ethtool string sets.
+ */
+
+#ifndef ETHTOOL_NETLINK_STRSET_H__
+#define ETHTOOL_NETLINK_STRSET_H__
+
+struct nl_socket;
+struct stringset;
+
+const struct stringset *global_stringset(unsigned int type,
+					 struct nl_socket *nlsk);
+const struct stringset *perdev_stringset(const char *dev, unsigned int type,
+					 struct nl_socket *nlsk);
+
+unsigned int get_count(const struct stringset *set);
+const char *get_string(const struct stringset *set, unsigned int idx);
+
+int preload_global_strings(struct nl_socket *nlsk);
+int preload_perdev_strings(struct nl_socket *nlsk, const char *dev);
+void cleanup_all_strings(void);
+
+#endif /* ETHTOOL_NETLINK_STRSET_H__ */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 11/19] netlink: add notification monitor
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (9 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 10/19] netlink: add support for string sets Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 12/19] move shared code into a common file Michal Kubecek
                   ` (7 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

With 'ethtool --monitor [ --all | opt ] [dev]', let ethtool listen to
netlink notification and display them in a format similar to output of
related "get" commands.

With --all or without option, show all types of notifications. If a device
name is specified, show only notifications for that device, if no device
name or "*" is passed, show notifications for all devices.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   1 +
 ethtool.8.in      |  21 +++++
 ethtool.c         |  14 ++++
 netlink/extapi.h  |   8 ++
 netlink/monitor.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/netlink.h |  11 +++
 netlink/strset.c  |   2 +
 7 files changed, 254 insertions(+)
 create mode 100644 netlink/monitor.c

diff --git a/Makefile.am b/Makefile.am
index 2685280aee15..92aefb9843bc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -29,6 +29,7 @@ ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
 		  netlink/msgbuff.c netlink/msgbuff.h netlink/nlsock.c \
 		  netlink/nlsock.h netlink/strset.c netlink/strset.h \
+		  netlink/monitor.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/ethtool.8.in b/ethtool.8.in
index 680cad9fbb8f..28e4f75eee8d 100644
--- a/ethtool.8.in
+++ b/ethtool.8.in
@@ -133,6 +133,13 @@ ethtool \- query or control network driver and hardware settings
 .BN --debug
 .I args
 .HP
+.B ethtool \-\-monitor
+[
+.I command
+] [
+.I devname
+]
+.HP
 .B ethtool \-a|\-\-show\-pause
 .I devname
 .HP
@@ -1244,6 +1251,20 @@ If queue_mask is not set, the sub command will be applied to all queues.
 Sub command to apply. The supported sub commands include --show-coalesce and
 --coalesce.
 .RE
+.TP
+.B \-\-monitor
+Listens to netlink notification and displays them.
+.RS 4
+.TP
+.I command
+If argument matching a command is used, ethtool only shows notifications of
+this type. Without such argument or with --all, all notification types are
+shown.
+.TP
+.I devname
+If a device name is used as argument, only notification for this device are
+shown. Default is to show notifications for all devices.
+.RE
 .SH BUGS
 Not supported (in part or whole) on all network drivers.
 .SH AUTHOR
diff --git a/ethtool.c b/ethtool.c
index 5d1ef537f692..97eaa58a3090 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5664,6 +5664,7 @@ static int show_usage(struct cmd_context *ctx maybe_unused)
 		if (args[i].xhelp)
 			fputs(args[i].xhelp, stdout);
 	}
+	nl_monitor_usage();
 
 	return 0;
 }
@@ -5909,6 +5910,19 @@ int main(int argc, char **argp)
 		argp += 2;
 		argc -= 2;
 	}
+#ifdef ETHTOOL_ENABLE_NETLINK
+	if (*argp && !strcmp(*argp, "--monitor")) {
+		if (netlink_init(&ctx)) {
+			fprintf(stderr,
+				"Option --monitor is only available with netlink.\n");
+			return 1;
+		} else {
+			ctx.argp = ++argp;
+			ctx.argc = --argc;
+			return nl_monitor(&ctx);
+		}
+	}
+#endif
 
 	/* First argument must be either a valid option or a device
 	 * name to get settings for (which we don't expect to begin
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 898dc6cfee71..1d5b68226af4 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -15,6 +15,10 @@ struct nl_context;
 int netlink_init(struct cmd_context *ctx);
 void netlink_done(struct cmd_context *ctx);
 
+int nl_monitor(struct cmd_context *ctx);
+
+void nl_monitor_usage(void);
+
 #else /* ETHTOOL_ENABLE_NETLINK */
 
 static inline int netlink_init(struct cmd_context *ctx maybe_unused)
@@ -26,6 +30,10 @@ static inline void netlink_done(struct cmd_context *ctx maybe_unused)
 {
 }
 
+static inline void nl_monitor_usage(void)
+{
+}
+
 #endif /* ETHTOOL_ENABLE_NETLINK */
 
 #endif /* ETHTOOL_EXTAPI_H__ */
diff --git a/netlink/monitor.c b/netlink/monitor.c
new file mode 100644
index 000000000000..300fd5bc2e51
--- /dev/null
+++ b/netlink/monitor.c
@@ -0,0 +1,197 @@
+/*
+ * monitor.c - netlink notification monitor
+ *
+ * Implementation of "ethtool --monitor" for watching netlink notifications.
+ */
+
+#include <errno.h>
+
+#include "../internal.h"
+#include "netlink.h"
+#include "nlsock.h"
+#include "strset.h"
+
+static struct {
+	uint8_t		cmd;
+	mnl_cb_t	cb;
+} monitor_callbacks[] = {
+};
+
+static void clear_filter(struct nl_context *nlctx)
+{
+	unsigned int i;
+
+	for (i = 0; i < CMDMASK_WORDS; i++)
+		nlctx->filter_cmds[i] = 0;
+}
+
+static bool test_filter_cmd(const struct nl_context *nlctx, unsigned int cmd)
+{
+	return nlctx->filter_cmds[cmd / 32] & (1U << (cmd % 32));
+}
+
+static void set_filter_cmd(struct nl_context *nlctx, unsigned int cmd)
+{
+	nlctx->filter_cmds[cmd / 32] |= (1U << (cmd % 32));
+}
+
+static void set_filter_all(struct nl_context *nlctx)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(monitor_callbacks); i++)
+		set_filter_cmd(nlctx, monitor_callbacks[i].cmd);
+}
+
+static int monitor_any_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+	struct nl_context *nlctx = data;
+	unsigned int i;
+
+	if (!test_filter_cmd(nlctx, ghdr->cmd))
+		return MNL_CB_OK;
+
+	for (i = 0; i < MNL_ARRAY_SIZE(monitor_callbacks); i++)
+		if (monitor_callbacks[i].cmd == ghdr->cmd)
+			return monitor_callbacks[i].cb(nlhdr, data);
+
+	return MNL_CB_OK;
+}
+
+struct monitor_option {
+	const char	*pattern;
+	uint8_t		cmd;
+	uint32_t	info_mask;
+};
+
+static struct monitor_option monitor_opts[] = {
+	{
+		.pattern	= "|--all",
+		.cmd		= 0,
+	},
+};
+
+static bool pattern_match(const char *s, const char *pattern)
+{
+	const char *opt = pattern;
+	const char *next;
+	int slen = strlen(s);
+	int optlen;
+
+	do {
+		next = opt;
+		while (*next && *next != '|')
+			next++;
+		optlen = next - opt;
+		if (slen == optlen && !strncmp(s, opt, optlen))
+			return true;
+
+		opt = next;
+		if (*opt == '|')
+			opt++;
+	} while (*opt);
+
+	return false;
+}
+
+static int parse_monitor(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	char **argp = ctx->argp;
+	int argc = ctx->argc;
+	const char *opt = "";
+	bool opt_found;
+	unsigned int i;
+
+	if (*argp && argp[0][0] == '-') {
+		opt = *argp;
+		argp++;
+		argc--;
+	}
+	opt_found = false;
+	clear_filter(nlctx);
+	for (i = 0; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+		if (pattern_match(opt, monitor_opts[i].pattern)) {
+			unsigned int cmd = monitor_opts[i].cmd;
+
+			if (!cmd)
+				set_filter_all(nlctx);
+			else
+				set_filter_cmd(nlctx, cmd);
+			opt_found = true;
+		}
+	}
+	if (!opt_found) {
+		fprintf(stderr, "monitoring for option '%s' not supported\n",
+			*argp);
+		return -1;
+	}
+
+	if (*argp && strcmp(*argp, WILDCARD_DEVNAME))
+		ctx->devname = *argp;
+	return 0;
+}
+
+int nl_monitor(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	struct nl_socket *nlsk = nlctx->ethnl_socket;
+	uint32_t grpid = nlctx->ethnl_mongrp;
+	bool is_dev;
+	int ret;
+
+	if (!grpid) {
+		fprintf(stderr, "multicast group 'monitor' not found\n");
+		return -EOPNOTSUPP;
+	}
+	if (parse_monitor(ctx) < 0)
+		return 1;
+	is_dev = ctx->devname && strcmp(ctx->devname, WILDCARD_DEVNAME);
+
+	ret = preload_global_strings(nlsk);
+	if (ret < 0)
+		return ret;
+	ret = mnl_socket_setsockopt(nlsk->sk, NETLINK_ADD_MEMBERSHIP,
+				    &grpid, sizeof(grpid));
+	if (ret < 0)
+		return ret;
+	if (is_dev) {
+		ret = preload_perdev_strings(nlsk, ctx->devname);
+		if (ret < 0)
+			goto out_strings;
+	}
+
+	nlctx->filter_devname = ctx->devname;
+	nlctx->is_monitor = true;
+	nlsk->port = 0;
+	nlsk->seq = 0;
+
+	fputs("listening...\n", stdout);
+	fflush(stdout);
+	ret = nlsock_process_reply(nlsk, monitor_any_cb, nlctx);
+
+out_strings:
+	cleanup_all_strings();
+	return ret;
+}
+
+void nl_monitor_usage(void)
+{
+	unsigned int i;
+	const char *p;
+
+	fputs("        ethtool --monitor               Show kernel notifications\n",
+	      stdout);
+	fputs("                ( [ --all ]", stdout);
+	for (i = 1; i < MNL_ARRAY_SIZE(monitor_opts); i++) {
+		fputs("\n                  | ", stdout);
+		for (p = monitor_opts[i].pattern; *p; p++)
+			if (*p == '|')
+				fputs(" | ", stdout);
+			else
+				fputc(*p, stdout);
+	}
+	fputs(" )\n", stdout);
+	fputs("                [ DEVNAME | * ]\n", stdout);
+}
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 84a5e5c0f204..1a2e743ff6b2 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -14,6 +14,7 @@
 #include "nlsock.h"
 
 #define WILDCARD_DEVNAME "*"
+#define CMDMASK_WORDS DIV_ROUND_UP(__ETHTOOL_MSG_KERNEL_CNT, 32)
 
 struct nl_context {
 	struct cmd_context	*ctx;
@@ -26,6 +27,9 @@ struct nl_context {
 	uint32_t		ethnl_mongrp;
 	struct nl_socket	*ethnl_socket;
 	struct nl_socket	*ethnl2_socket;
+	bool			is_monitor;
+	uint32_t		filter_cmds[CMDMASK_WORDS];
+	const char		*filter_devname;
 };
 
 struct attr_tb_info {
@@ -48,6 +52,13 @@ static inline void copy_devname(char *dst, const char *src)
 	dst[ALTIFNAMSIZ - 1] = '\0';
 }
 
+static inline bool dev_ok(const struct nl_context *nlctx)
+{
+	return !nlctx->filter_devname ||
+	       (nlctx->devname &&
+		!strcmp(nlctx->devname, nlctx->filter_devname));
+}
+
 static inline int netlink_init_ethnl2_socket(struct nl_context *nlctx)
 {
 	if (nlctx->ethnl2_socket)
diff --git a/netlink/strset.c b/netlink/strset.c
index bc468ae5a88e..75f0327bbe43 100644
--- a/netlink/strset.c
+++ b/netlink/strset.c
@@ -149,6 +149,8 @@ static int strset_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 			return MNL_CB_OK;
 		nlctx->devname = devname;
 	}
+	if (ifindex && !dev_ok(nlctx))
+		return MNL_CB_OK;
 
 	if (ifindex) {
 		struct perdev_strings *perdev;
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 12/19] move shared code into a common file
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (10 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 11/19] netlink: add notification monitor Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 13/19] netlink: add bitset helpers Michal Kubecek
                   ` (6 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Move code which is going to be shared between ioctl and netlink
implementation into a common file common.c and declarations into header
file common.h.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am |   2 +-
 common.c    | 145 +++++++++++++++++++++++++++++++++++++++++++++++++
 common.h    |  26 +++++++++
 ethtool.c   | 151 +++-------------------------------------------------
 4 files changed, 179 insertions(+), 145 deletions(-)
 create mode 100644 common.c
 create mode 100644 common.h

diff --git a/Makefile.am b/Makefile.am
index 92aefb9843bc..77b13da1442c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = LICENSE ethtool.8 ethtool.spec.in aclocal.m4 ChangeLog autogen.sh
 
 sbin_PROGRAMS = ethtool
 ethtool_SOURCES = ethtool.c uapi/linux/ethtool.h internal.h \
-		  uapi/linux/net_tstamp.h rxclass.c
+		  uapi/linux/net_tstamp.h rxclass.c common.c common.h
 ethtool_CFLAGS = $(AM_CFLAGS)
 ethtool_LDADD = $(LDADD)
 if ETHTOOL_ENABLE_PRETTY_DUMP
diff --git a/common.c b/common.c
new file mode 100644
index 000000000000..f9c41a32d3a3
--- /dev/null
+++ b/common.c
@@ -0,0 +1,145 @@
+/*
+ * common.h - common code header
+ *
+ * Data and functions shared by ioctl and netlink implementation.
+ */
+
+#include "internal.h"
+#include "common.h"
+
+#ifndef HAVE_NETIF_MSG
+enum {
+	NETIF_MSG_DRV		= 0x0001,
+	NETIF_MSG_PROBE		= 0x0002,
+	NETIF_MSG_LINK		= 0x0004,
+	NETIF_MSG_TIMER		= 0x0008,
+	NETIF_MSG_IFDOWN	= 0x0010,
+	NETIF_MSG_IFUP		= 0x0020,
+	NETIF_MSG_RX_ERR	= 0x0040,
+	NETIF_MSG_TX_ERR	= 0x0080,
+	NETIF_MSG_TX_QUEUED	= 0x0100,
+	NETIF_MSG_INTR		= 0x0200,
+	NETIF_MSG_TX_DONE	= 0x0400,
+	NETIF_MSG_RX_STATUS	= 0x0800,
+	NETIF_MSG_PKTDATA	= 0x1000,
+	NETIF_MSG_HW		= 0x2000,
+	NETIF_MSG_WOL		= 0x4000,
+};
+#endif
+
+const struct flag_info flags_msglvl[] = {
+	{ "drv",	NETIF_MSG_DRV },
+	{ "probe",	NETIF_MSG_PROBE },
+	{ "link",	NETIF_MSG_LINK },
+	{ "timer",	NETIF_MSG_TIMER },
+	{ "ifdown",	NETIF_MSG_IFDOWN },
+	{ "ifup",	NETIF_MSG_IFUP },
+	{ "rx_err",	NETIF_MSG_RX_ERR },
+	{ "tx_err",	NETIF_MSG_TX_ERR },
+	{ "tx_queued",	NETIF_MSG_TX_QUEUED },
+	{ "intr",	NETIF_MSG_INTR },
+	{ "tx_done",	NETIF_MSG_TX_DONE },
+	{ "rx_status",	NETIF_MSG_RX_STATUS },
+	{ "pktdata",	NETIF_MSG_PKTDATA },
+	{ "hw",		NETIF_MSG_HW },
+	{ "wol",	NETIF_MSG_WOL },
+	{}
+};
+const unsigned int n_flags_msglvl = ARRAY_SIZE(flags_msglvl) - 1;
+
+void print_flags(const struct flag_info *info, unsigned int n_info, u32 value)
+{
+	const char *sep = "";
+
+	while (n_info) {
+		if (value & info->value) {
+			printf("%s%s", sep, info->name);
+			sep = " ";
+			value &= ~info->value;
+		}
+		++info;
+		--n_info;
+	}
+
+	/* Print any unrecognised flags in hex */
+	if (value)
+		printf("%s%#x", sep, value);
+}
+
+static char *unparse_wolopts(int wolopts)
+{
+	static char buf[16];
+	char *p = buf;
+
+	memset(buf, 0, sizeof(buf));
+
+	if (wolopts) {
+		if (wolopts & WAKE_PHY)
+			*p++ = 'p';
+		if (wolopts & WAKE_UCAST)
+			*p++ = 'u';
+		if (wolopts & WAKE_MCAST)
+			*p++ = 'm';
+		if (wolopts & WAKE_BCAST)
+			*p++ = 'b';
+		if (wolopts & WAKE_ARP)
+			*p++ = 'a';
+		if (wolopts & WAKE_MAGIC)
+			*p++ = 'g';
+		if (wolopts & WAKE_MAGICSECURE)
+			*p++ = 's';
+		if (wolopts & WAKE_FILTER)
+			*p++ = 'f';
+	} else {
+		*p = 'd';
+	}
+
+	return buf;
+}
+
+int dump_wol(struct ethtool_wolinfo *wol)
+{
+	fprintf(stdout, "	Supports Wake-on: %s\n",
+		unparse_wolopts(wol->supported));
+	fprintf(stdout, "	Wake-on: %s\n",
+		unparse_wolopts(wol->wolopts));
+	if (wol->supported & WAKE_MAGICSECURE) {
+		int i;
+		int delim = 0;
+
+		fprintf(stdout, "        SecureOn password: ");
+		for (i = 0; i < SOPASS_MAX; i++) {
+			fprintf(stdout, "%s%02x", delim ? ":" : "",
+				wol->sopass[i]);
+			delim = 1;
+		}
+		fprintf(stdout, "\n");
+	}
+
+	return 0;
+}
+
+void dump_mdix(u8 mdix, u8 mdix_ctrl)
+{
+	fprintf(stdout, "	MDI-X: ");
+	if (mdix_ctrl == ETH_TP_MDI) {
+		fprintf(stdout, "off (forced)\n");
+	} else if (mdix_ctrl == ETH_TP_MDI_X) {
+		fprintf(stdout, "on (forced)\n");
+	} else {
+		switch (mdix) {
+		case ETH_TP_MDI:
+			fprintf(stdout, "off");
+			break;
+		case ETH_TP_MDI_X:
+			fprintf(stdout, "on");
+			break;
+		default:
+			fprintf(stdout, "Unknown");
+			break;
+		}
+		if (mdix_ctrl == ETH_TP_MDI_AUTO)
+			fprintf(stdout, " (auto)");
+		fprintf(stdout, "\n");
+	}
+}
diff --git a/common.h b/common.h
new file mode 100644
index 000000000000..3a680114a7c2
--- /dev/null
+++ b/common.h
@@ -0,0 +1,26 @@
+/*
+ * common.h - common code header
+ *
+ * Declarations for data and functions shared by ioctl and netlink code.
+ */
+
+#ifndef ETHTOOL_COMMON_H__
+#define ETHTOOL_COMMON_H__
+
+#include "internal.h"
+
+#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
+
+struct flag_info {
+	const char *name;
+	u32 value;
+};
+
+extern const struct flag_info flags_msglvl[];
+extern const unsigned int n_flags_msglvl;
+
+void print_flags(const struct flag_info *info, unsigned int n_info, u32 value);
+int dump_wol(struct ethtool_wolinfo *wol);
+void dump_mdix(u8 mdix, u8 mdix_ctrl);
+
+#endif /* ETHTOOL_COMMON_H__ */
diff --git a/ethtool.c b/ethtool.c
index 97eaa58a3090..c2b7cc8c0502 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -48,32 +48,13 @@
 #include <linux/sockios.h>
 #include <linux/netlink.h>
 
+#include "common.h"
 #include "netlink/extapi.h"
 
 #ifndef MAX_ADDR_LEN
 #define MAX_ADDR_LEN	32
 #endif
 
-#ifndef HAVE_NETIF_MSG
-enum {
-	NETIF_MSG_DRV		= 0x0001,
-	NETIF_MSG_PROBE		= 0x0002,
-	NETIF_MSG_LINK		= 0x0004,
-	NETIF_MSG_TIMER		= 0x0008,
-	NETIF_MSG_IFDOWN	= 0x0010,
-	NETIF_MSG_IFUP		= 0x0020,
-	NETIF_MSG_RX_ERR	= 0x0040,
-	NETIF_MSG_TX_ERR	= 0x0080,
-	NETIF_MSG_TX_QUEUED	= 0x0100,
-	NETIF_MSG_INTR		= 0x0200,
-	NETIF_MSG_TX_DONE	= 0x0400,
-	NETIF_MSG_RX_STATUS	= 0x0800,
-	NETIF_MSG_PKTDATA	= 0x1000,
-	NETIF_MSG_HW		= 0x2000,
-	NETIF_MSG_WOL		= 0x4000,
-};
-#endif
-
 #ifndef NETLINK_GENERIC
 #define NETLINK_GENERIC	16
 #endif
@@ -121,29 +102,6 @@ struct cmdline_info {
 	void *seen_val;
 };
 
-struct flag_info {
-	const char *name;
-	u32 value;
-};
-
-static const struct flag_info flags_msglvl[] = {
-	{ "drv",	NETIF_MSG_DRV },
-	{ "probe",	NETIF_MSG_PROBE },
-	{ "link",	NETIF_MSG_LINK },
-	{ "timer",	NETIF_MSG_TIMER },
-	{ "ifdown",	NETIF_MSG_IFDOWN },
-	{ "ifup",	NETIF_MSG_IFUP },
-	{ "rx_err",	NETIF_MSG_RX_ERR },
-	{ "tx_err",	NETIF_MSG_TX_ERR },
-	{ "tx_queued",	NETIF_MSG_TX_QUEUED },
-	{ "intr",	NETIF_MSG_INTR },
-	{ "tx_done",	NETIF_MSG_TX_DONE },
-	{ "rx_status",	NETIF_MSG_RX_STATUS },
-	{ "pktdata",	NETIF_MSG_PKTDATA },
-	{ "hw",		NETIF_MSG_HW },
-	{ "wol",	NETIF_MSG_WOL },
-};
-
 struct off_flag_def {
 	const char *short_name;
 	const char *long_name;
@@ -426,26 +384,6 @@ static void flag_to_cmdline_info(const char *name, u32 value,
 	cli->seen_val = mask;
 }
 
-static void
-print_flags(const struct flag_info *info, unsigned int n_info, u32 value)
-{
-	const char *sep = "";
-
-	while (n_info) {
-		if (value & info->value) {
-			printf("%s%s", sep, info->name);
-			sep = " ";
-			value &= ~info->value;
-		}
-		++info;
-		--n_info;
-	}
-
-	/* Print any unrecognised flags in hex */
-	if (value)
-		printf("%s%#x", sep, value);
-}
-
 static int rxflow_str_to_type(const char *str)
 {
 	int flow_type = 0;
@@ -904,31 +842,9 @@ dump_link_usettings(const struct ethtool_link_usettings *link_usettings)
 		(link_usettings->base.autoneg == AUTONEG_DISABLE) ?
 		"off" : "on");
 
-	if (link_usettings->base.port == PORT_TP) {
-		fprintf(stdout, "	MDI-X: ");
-		if (link_usettings->base.eth_tp_mdix_ctrl == ETH_TP_MDI) {
-			fprintf(stdout, "off (forced)\n");
-		} else if (link_usettings->base.eth_tp_mdix_ctrl
-			   == ETH_TP_MDI_X) {
-			fprintf(stdout, "on (forced)\n");
-		} else {
-			switch (link_usettings->base.eth_tp_mdix) {
-			case ETH_TP_MDI:
-				fprintf(stdout, "off");
-				break;
-			case ETH_TP_MDI_X:
-				fprintf(stdout, "on");
-				break;
-			default:
-				fprintf(stdout, "Unknown");
-				break;
-			}
-			if (link_usettings->base.eth_tp_mdix_ctrl
-			    == ETH_TP_MDI_AUTO)
-				fprintf(stdout, " (auto)");
-			fprintf(stdout, "\n");
-		}
-	}
+	if (link_usettings->base.port == PORT_TP)
+		dump_mdix(link_usettings->base.eth_tp_mdix,
+			  link_usettings->base.eth_tp_mdix_ctrl);
 
 	return 0;
 }
@@ -1000,58 +916,6 @@ static int parse_wolopts(char *optstr, u32 *data)
 	return 0;
 }
 
-static char *unparse_wolopts(int wolopts)
-{
-	static char buf[16];
-	char *p = buf;
-
-	memset(buf, 0, sizeof(buf));
-
-	if (wolopts) {
-		if (wolopts & WAKE_PHY)
-			*p++ = 'p';
-		if (wolopts & WAKE_UCAST)
-			*p++ = 'u';
-		if (wolopts & WAKE_MCAST)
-			*p++ = 'm';
-		if (wolopts & WAKE_BCAST)
-			*p++ = 'b';
-		if (wolopts & WAKE_ARP)
-			*p++ = 'a';
-		if (wolopts & WAKE_MAGIC)
-			*p++ = 'g';
-		if (wolopts & WAKE_MAGICSECURE)
-			*p++ = 's';
-		if (wolopts & WAKE_FILTER)
-			*p++ = 'f';
-	} else {
-		*p = 'd';
-	}
-
-	return buf;
-}
-
-static int dump_wol(struct ethtool_wolinfo *wol)
-{
-	fprintf(stdout, "	Supports Wake-on: %s\n",
-		unparse_wolopts(wol->supported));
-	fprintf(stdout, "	Wake-on: %s\n",
-		unparse_wolopts(wol->wolopts));
-	if (wol->supported & WAKE_MAGICSECURE) {
-		int i;
-		int delim = 0;
-
-		fprintf(stdout, "        SecureOn password: ");
-		for (i = 0; i < SOPASS_MAX; i++) {
-			fprintf(stdout, "%s%02x", delim?":":"", wol->sopass[i]);
-			delim = 1;
-		}
-		fprintf(stdout, "\n");
-	}
-
-	return 0;
-}
-
 static int parse_rxfhashopts(char *optstr, u32 *data)
 {
 	*data = 0;
@@ -2839,8 +2703,7 @@ static int do_gset(struct cmd_context *ctx)
 		fprintf(stdout, "	Current message level: 0x%08x (%d)\n"
 			"			       ",
 			edata.data, edata.data);
-		print_flags(flags_msglvl, ARRAY_SIZE(flags_msglvl),
-			    edata.data);
+		print_flags(flags_msglvl, n_flags_msglvl, edata.data);
 		fprintf(stdout, "\n");
 		allfail = 0;
 	} else if (errno != EOPNOTSUPP) {
@@ -2886,13 +2749,13 @@ static int do_sset(struct cmd_context *ctx)
 	int msglvl_changed = 0;
 	u32 msglvl_wanted = 0;
 	u32 msglvl_mask = 0;
-	struct cmdline_info cmdline_msglvl[ARRAY_SIZE(flags_msglvl)];
+	struct cmdline_info cmdline_msglvl[n_flags_msglvl];
 	int argc = ctx->argc;
 	char **argp = ctx->argp;
 	int i;
 	int err = 0;
 
-	for (i = 0; i < ARRAY_SIZE(flags_msglvl); i++)
+	for (i = 0; i < n_flags_msglvl; i++)
 		flag_to_cmdline_info(flags_msglvl[i].name,
 				     flags_msglvl[i].value,
 				     &msglvl_wanted, &msglvl_mask,
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 13/19] netlink: add bitset helpers
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (11 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 12/19] move shared code into a common file Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 14/19] netlink: partial netlink handler for gset (no option) Michal Kubecek
                   ` (5 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Add basic functions for work with arbitrary length bitsets used in ethtool
netlink interface.

The most important function is walk_bitset() which iterates through bits of
a bitset (passed as pointer to the nest attribute) and calls provided
callback for each bit.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am      |   2 +-
 netlink/bitset.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++
 netlink/bitset.h |  25 ++++++
 3 files changed, 227 insertions(+), 1 deletion(-)
 create mode 100644 netlink/bitset.c
 create mode 100644 netlink/bitset.h

diff --git a/Makefile.am b/Makefile.am
index 77b13da1442c..65586d9f51a1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -29,7 +29,7 @@ ethtool_SOURCES += \
 		  netlink/netlink.c netlink/netlink.h netlink/extapi.h \
 		  netlink/msgbuff.c netlink/msgbuff.h netlink/nlsock.c \
 		  netlink/nlsock.h netlink/strset.c netlink/strset.h \
-		  netlink/monitor.c \
+		  netlink/monitor.c netlink/bitset.c netlink/bitset.h \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/netlink/bitset.c b/netlink/bitset.c
new file mode 100644
index 000000000000..69c3ed993cd7
--- /dev/null
+++ b/netlink/bitset.c
@@ -0,0 +1,201 @@
+/*
+ * bitset.h - netlink bitset helpers
+ *
+ * Functions for easier handling of ethtool netlink bitset attributes.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+
+uint32_t bitset_get_count(const struct nlattr *bitset, int *retptr)
+{
+	const struct nlattr *attr;
+
+	mnl_attr_for_each_nested(attr, bitset) {
+		if (mnl_attr_get_type(attr) != ETHTOOL_A_BITSET_SIZE)
+			continue;
+		*retptr = 0;
+		return mnl_attr_get_u32(attr);
+	}
+
+	*retptr = -EFAULT;
+	return 0;
+}
+
+bool bitset_get_bit(const struct nlattr *bitset, bool mask, unsigned int idx,
+		    int *retptr)
+{
+	const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(bitset_tb);
+	const struct nlattr *bits;
+	const struct nlattr *bit;
+	int ret;
+
+	*retptr = 0;
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+	if (ret < 0)
+		goto err;
+
+	bits = mask ? bitset_tb[ETHTOOL_A_BITSET_MASK] :
+		      bitset_tb[ETHTOOL_A_BITSET_VALUE];
+	if (bits) {
+		const uint32_t *bitmap =
+			(const uint32_t *)mnl_attr_get_payload(bits);
+
+		if (idx >= 8 * mnl_attr_get_payload_len(bits))
+			return false;
+		return bitmap[idx / 32] & (1U << (idx % 32));
+	}
+
+	bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+	if (!bits)
+		goto err;
+	mnl_attr_for_each_nested(bit, bits) {
+		const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+		unsigned int my_idx;
+
+		if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+			continue;
+		ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		ret = -EFAULT;
+		if (!tb[ETHTOOL_A_BITSET_BIT_INDEX])
+			goto err;
+
+		my_idx = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_BIT_INDEX]);
+		if (my_idx == idx)
+			return mask || tb[ETHTOOL_A_BITSET_BIT_VALUE];
+	}
+
+	return false;
+err:
+	fprintf(stderr, "malformed netlink message (bitset)\n");
+	*retptr = ret;
+	return false;
+}
+
+bool bitset_is_empty(const struct nlattr *bitset, bool mask, int *retptr)
+{
+	const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(bitset_tb);
+	const struct nlattr *bits;
+	const struct nlattr *bit;
+	int ret;
+
+	*retptr = 0;
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+	if (ret < 0)
+		goto err;
+
+	bits = mask ? bitset_tb[ETHTOOL_A_BITSET_MASK] :
+		      bitset_tb[ETHTOOL_A_BITSET_VALUE];
+	if (bits) {
+		const uint32_t *bitmap =
+			(const uint32_t *)mnl_attr_get_payload(bits);
+		unsigned int n = mnl_attr_get_payload_len(bits);
+		unsigned int i;
+
+		ret = -EFAULT;
+		if (n % 4)
+			goto err;
+		for (i = 0; i < n / 4; i++)
+			if (bitmap[i])
+				return false;
+		return true;
+	}
+
+	bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+	if (!bits)
+		goto err;
+	mnl_attr_for_each_nested(bit, bits) {
+		const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+
+		if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+			continue;
+		if (mask || bitset_tb[ETHTOOL_A_BITSET_NOMASK])
+			return false;
+
+		ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		if (tb[ETHTOOL_A_BITSET_BIT_VALUE])
+			return false;
+	}
+
+	return true;
+err:
+	fprintf(stderr, "malformed netlink message (bitset)\n");
+	*retptr = ret;
+	return true;
+}
+
+int walk_bitset(const struct nlattr *bitset, const struct stringset *labels,
+		bitset_walk_callback cb, void *data)
+{
+	const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(bitset_tb);
+	const struct nlattr *bits;
+	const struct nlattr *bit;
+	bool is_list;
+	int ret;
+
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+	if (ret < 0)
+		return ret;
+	is_list = bitset_tb[ETHTOOL_A_BITSET_NOMASK];
+
+	bits = bitset_tb[ETHTOOL_A_BITSET_VALUE];
+	if (bits) {
+		const struct nlattr *mask = bitset_tb[ETHTOOL_A_BITSET_MASK];
+		unsigned int count, nwords, idx;
+		uint32_t *val_bm;
+		uint32_t *mask_bm;
+
+		if (!bitset_tb[ETHTOOL_A_BITSET_SIZE])
+			return -EFAULT;
+		count = mnl_attr_get_u32(bitset_tb[ETHTOOL_A_BITSET_SIZE]);
+		nwords = (count + 31) / 32;
+		if ((mnl_attr_get_payload_len(bits) / 4 < nwords) ||
+		    (mask && mnl_attr_get_payload_len(mask) / 4 < nwords))
+			return -EFAULT;
+
+		val_bm = mnl_attr_get_payload(bits);
+		mask_bm = mask ? mnl_attr_get_payload(mask) : NULL;
+		for (idx = 0; idx < count; idx++)
+			if (!mask_bm || (mask_bm[idx / 32] & (1 << (idx % 32))))
+				cb(idx, get_string(labels, idx),
+				   val_bm[idx / 32] & (1 << (idx % 32)), data);
+		return 0;
+	}
+
+	bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+	if (!bits)
+		return -EFAULT;
+	mnl_attr_for_each_nested(bit, bits) {
+		const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+		const char *name;
+		unsigned int idx;
+
+		if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+			continue;
+
+		ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+		if (ret < 0 || !tb[ETHTOOL_A_BITSET_BIT_INDEX] ||
+		    !tb[ETHTOOL_A_BITSET_BIT_NAME])
+			return -EFAULT;
+
+		idx = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_BIT_INDEX]);
+		name = mnl_attr_get_str(tb[ETHTOOL_A_BITSET_BIT_NAME]);
+		cb(idx, name, is_list || tb[ETHTOOL_A_BITSET_BIT_VALUE], data);
+	}
+
+	return 0;
+}
diff --git a/netlink/bitset.h b/netlink/bitset.h
new file mode 100644
index 000000000000..0f349af87f46
--- /dev/null
+++ b/netlink/bitset.h
@@ -0,0 +1,25 @@
+/*
+ * bitset.h - netlink bitset helpers
+ *
+ * Declarations of helpers for handling ethtool netlink bitsets.
+ */
+
+#ifndef ETHTOOL_NETLINK_BITSET_H__
+#define ETHTOOL_NETLINK_BITSET_H__
+
+#include <libmnl/libmnl.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ethtool_netlink.h>
+#include "strset.h"
+
+typedef void (*bitset_walk_callback)(unsigned int, const char *, bool, void *);
+
+uint32_t bitset_get_count(const struct nlattr *bitset, int *retptr);
+bool bitset_get_bit(const struct nlattr *bitset, bool mask, unsigned int idx,
+		    int *retptr);
+bool bitset_is_empty(const struct nlattr *bitset, bool mask, int *retptr);
+int walk_bitset(const struct nlattr *bitset, const struct stringset *labels,
+		bitset_walk_callback cb, void *data);
+
+#endif /* ETHTOOL_NETLINK_BITSET_H__ */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 14/19] netlink: partial netlink handler for gset (no option)
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (12 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 13/19] netlink: add bitset helpers Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 15/19] netlink: support getting wake-on-lan and debugging settings Michal Kubecek
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Partially implement "ethtool <dev>" subcommand. This commit retrieves and
displays information provided by ETHTOOL_MSG_LINKINFO_GET,
ETHTOOL_MSG_LINKMODES_GET and ETHTOOL_MSG_LINKSTATE_GET netlink requests,
i.e. everything except wake-on-lan and debugging (msglevel).

Also register the callbacks in monitor so that corresponding notifications
can be displayed with "ethtool --monitor".

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am        |   1 +
 ethtool.c          |   1 +
 netlink/extapi.h   |   3 +
 netlink/monitor.c  |  16 ++
 netlink/netlink.h  |   3 +
 netlink/settings.c | 641 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 665 insertions(+)
 create mode 100644 netlink/settings.c

diff --git a/Makefile.am b/Makefile.am
index 65586d9f51a1..e4e864add586 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,6 +30,7 @@ ethtool_SOURCES += \
 		  netlink/msgbuff.c netlink/msgbuff.h netlink/nlsock.c \
 		  netlink/nlsock.h netlink/strset.c netlink/strset.h \
 		  netlink/monitor.c netlink/bitset.c netlink/bitset.h \
+		  netlink/settings.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/ethtool.c b/ethtool.c
index c2b7cc8c0502..a69233bd73fc 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5805,6 +5805,7 @@ int main(int argc, char **argp)
 	}
 	if ((*argp)[0] == '-')
 		exit_bad_args();
+	nlfunc = nl_gset;
 	func = do_gset;
 	no_dev = false;
 
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 1d5b68226af4..8608ea7f51f5 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -15,6 +15,7 @@ struct nl_context;
 int netlink_init(struct cmd_context *ctx);
 void netlink_done(struct cmd_context *ctx);
 
+int nl_gset(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void nl_monitor_usage(void);
@@ -34,6 +35,8 @@ static inline void nl_monitor_usage(void)
 {
 }
 
+#define nl_gset			NULL
+
 #endif /* ETHTOOL_ENABLE_NETLINK */
 
 #endif /* ETHTOOL_EXTAPI_H__ */
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 300fd5bc2e51..e8fdcd2b93ef 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -15,6 +15,14 @@ static struct {
 	uint8_t		cmd;
 	mnl_cb_t	cb;
 } monitor_callbacks[] = {
+	{
+		.cmd	= ETHTOOL_MSG_LINKMODES_NTF,
+		.cb	= linkmodes_reply_cb,
+	},
+	{
+		.cmd	= ETHTOOL_MSG_LINKINFO_NTF,
+		.cb	= linkinfo_reply_cb,
+	},
 };
 
 static void clear_filter(struct nl_context *nlctx)
@@ -70,6 +78,14 @@ static struct monitor_option monitor_opts[] = {
 		.pattern	= "|--all",
 		.cmd		= 0,
 	},
+	{
+		.pattern	= "-s|--change",
+		.cmd		= ETHTOOL_MSG_LINKINFO_NTF,
+	},
+	{
+		.pattern	= "-s|--change",
+		.cmd		= ETHTOOL_MSG_LINKMODES_NTF,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 1a2e743ff6b2..790d0802b925 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -46,6 +46,9 @@ int attr_cb(const struct nlattr *attr, void *data);
 const char *get_dev_name(const struct nlattr *nest);
 int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname);
 
+int linkmodes_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int linkinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+
 static inline void copy_devname(char *dst, const char *src)
 {
 	strncpy(dst, src, ALTIFNAMSIZ);
diff --git a/netlink/settings.c b/netlink/settings.c
new file mode 100644
index 000000000000..24b7a4495961
--- /dev/null
+++ b/netlink/settings.c
@@ -0,0 +1,641 @@
+/*
+ * settings.c - netlink implementation of settings commands
+ *
+ * Implementation of "ethtool <dev>" and "ethtool -s <dev> ...".
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "strset.h"
+#include "bitset.h"
+
+/* GET_SETTINGS */
+
+enum link_mode_class {
+	LM_CLASS_UNKNOWN,
+	LM_CLASS_REAL,
+	LM_CLASS_AUTONEG,
+	LM_CLASS_PORT,
+	LM_CLASS_PAUSE,
+	LM_CLASS_FEC,
+};
+
+struct link_mode_info {
+	enum link_mode_class	class;
+	u32			speed;
+	u8			duplex;
+};
+
+static const char * const names_duplex[] = {
+	[DUPLEX_HALF]		= "Half",
+	[DUPLEX_FULL]		= "Full",
+};
+
+static const char * const names_port[] = {
+	[PORT_TP]		= "Twisted Pair",
+	[PORT_AUI]		= "AUI",
+	[PORT_BNC]		= "BNC",
+	[PORT_MII]		= "MII",
+	[PORT_FIBRE]		= "FIBRE",
+	[PORT_DA]		= "Direct Attach Copper",
+	[PORT_NONE]		= "None",
+	[PORT_OTHER]		= "Other",
+};
+
+static const char * const names_transceiver[] = {
+	[XCVR_INTERNAL]		= "internal",
+	[XCVR_EXTERNAL]		= "external",
+};
+
+/* the practice of putting completely unrelated flags into link mode bitmaps
+ * is rather unfortunate but as even ethtool_link_ksettings preserved that,
+ * there is little chance of getting them separated any time soon so let's
+ * sort them out ourselves
+ */
+static const struct link_mode_info link_modes[] = {
+	[ETHTOOL_LINK_MODE_10baseT_Half_BIT] =
+		{ LM_CLASS_REAL,	10,	DUPLEX_HALF },
+	[ETHTOOL_LINK_MODE_10baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	10,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100baseT_Half_BIT] =
+		{ LM_CLASS_REAL,	100,	DUPLEX_HALF },
+	[ETHTOOL_LINK_MODE_100baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	100,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_1000baseT_Half_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_HALF },
+	[ETHTOOL_LINK_MODE_1000baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_Autoneg_BIT] =
+		{ LM_CLASS_AUTONEG },
+	[ETHTOOL_LINK_MODE_TP_BIT] =
+		{ LM_CLASS_PORT },
+	[ETHTOOL_LINK_MODE_AUI_BIT] =
+		{ LM_CLASS_PORT },
+	[ETHTOOL_LINK_MODE_MII_BIT] =
+		{ LM_CLASS_PORT },
+	[ETHTOOL_LINK_MODE_FIBRE_BIT] =
+		{ LM_CLASS_PORT },
+	[ETHTOOL_LINK_MODE_BNC_BIT] =
+		{ LM_CLASS_PORT },
+	[ETHTOOL_LINK_MODE_10000baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_Pause_BIT] =
+		{ LM_CLASS_PAUSE },
+	[ETHTOOL_LINK_MODE_Asym_Pause_BIT] =
+		{ LM_CLASS_PAUSE },
+	[ETHTOOL_LINK_MODE_2500baseX_Full_BIT] =
+		{ LM_CLASS_REAL,	2500,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_Backplane_BIT] =
+		{ LM_CLASS_PORT },
+	[ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] =
+		{ LM_CLASS_REAL,	20000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] =
+		{ LM_CLASS_REAL,	20000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] =
+		{ LM_CLASS_REAL,	40000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] =
+		{ LM_CLASS_REAL,	56000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] =
+		{ LM_CLASS_REAL,	25000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] =
+		{ LM_CLASS_REAL,	25000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] =
+		{ LM_CLASS_REAL,	25000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_1000baseX_Full_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_10000baseER_Full_BIT] =
+		{ LM_CLASS_REAL,	10000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_2500baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	2500,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_5000baseT_Full_BIT] =
+		{ LM_CLASS_REAL,	5000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_FEC_NONE_BIT] =
+		{ LM_CLASS_FEC },
+	[ETHTOOL_LINK_MODE_FEC_RS_BIT] =
+		{ LM_CLASS_FEC },
+	[ETHTOOL_LINK_MODE_FEC_BASER_BIT] =
+		{ LM_CLASS_FEC },
+	[ETHTOOL_LINK_MODE_50000baseKR_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_50000baseSR_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_50000baseCR_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_50000baseDR_Full_BIT] =
+		{ LM_CLASS_REAL,	50000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT] =
+		{ LM_CLASS_REAL,	100000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT] =
+		{ LM_CLASS_REAL,	200000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT] =
+		{ LM_CLASS_REAL,	200000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT] =
+		{ LM_CLASS_REAL,	200000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT] =
+		{ LM_CLASS_REAL,	200000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT] =
+		{ LM_CLASS_REAL,	200000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_100baseT1_Full_BIT] =
+		{ LM_CLASS_REAL,	100,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_1000baseT1_Full_BIT] =
+		{ LM_CLASS_REAL,	1000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_400000baseKR8_Full_BIT] =
+		{ LM_CLASS_REAL,	400000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_400000baseSR8_Full_BIT] =
+		{ LM_CLASS_REAL,	400000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_400000baseLR8_ER8_FR8_Full_BIT] =
+		{ LM_CLASS_REAL,	400000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_400000baseDR8_Full_BIT] =
+		{ LM_CLASS_REAL,	400000,	DUPLEX_FULL },
+	[ETHTOOL_LINK_MODE_400000baseCR8_Full_BIT] =
+		{ LM_CLASS_REAL,	400000,	DUPLEX_FULL },
+};
+const unsigned int link_modes_count = ARRAY_SIZE(link_modes);
+
+static bool lm_class_match(unsigned int mode, enum link_mode_class class)
+{
+	unsigned int mode_class = (mode < link_modes_count) ?
+				   link_modes[mode].class : LM_CLASS_UNKNOWN;
+
+	return mode_class == class ||
+	       (class == LM_CLASS_REAL && mode_class == LM_CLASS_UNKNOWN);
+}
+
+static void print_enum(const char *const *info, unsigned int n_info,
+		       unsigned int val, const char *label)
+{
+	if (val >= n_info || !info[val])
+		printf("\t%s: Unknown! (%d)\n", label, val);
+	else
+		printf("\t%s: %s\n", label, info[val]);
+}
+
+static int dump_pause(const struct nlattr *attr, bool mask, const char *label)
+{
+	bool pause, asym;
+	int ret = 0;
+
+	pause = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Pause_BIT, &ret);
+	if (ret < 0)
+		goto err;
+	asym = bitset_get_bit(attr, mask, ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+			      &ret);
+	if (ret < 0)
+		goto err;
+
+	printf("\t%s", label);
+	if (pause)
+		printf("%s\n", asym ?  "Symmetric Receive-only" : "Symmetric");
+	else
+		printf("%s\n", asym ? "Transmit-only" : "No");
+
+	return 0;
+err:
+	fprintf(stderr, "malformed netlink message (pause modes)\n");
+	return ret;
+}
+
+static void print_banner(struct nl_context *nlctx, bool *first)
+{
+	if (!*first)
+		return;
+	printf("Settings for %s:\n", nlctx->devname);
+	*first = false;
+}
+
+static int dump_link_modes(struct nl_context *nlctx,
+			   const struct nlattr *bitset, bool mask,
+			   unsigned int class, const char *before,
+			   const char *between, const char *after,
+			   const char *if_none)
+{
+	const struct nlattr *bitset_tb[ETHTOOL_A_BITSET_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(bitset_tb);
+	const unsigned int before_len = strlen(before);
+	const struct nlattr *bits;
+	const struct nlattr *bit;
+	bool first = true;
+	int prev = -2;
+	int ret;
+
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &bitset_tb_info);
+	bits = bitset_tb[ETHTOOL_A_BITSET_BITS];
+	if (ret < 0)
+		goto err_nonl;
+	if (!bits) {
+		const struct stringset *lm_strings;
+		unsigned int count;
+		unsigned int idx;
+		const char *name;
+
+		ret = netlink_init_ethnl2_socket(nlctx);
+		if (ret < 0)
+			goto err_nonl;
+		lm_strings = global_stringset(ETH_SS_LINK_MODES,
+					      nlctx->ethnl2_socket);
+		bits = mask ? bitset_tb[ETHTOOL_A_BITSET_MASK] :
+			      bitset_tb[ETHTOOL_A_BITSET_VALUE];
+		ret = -EFAULT;
+		if (!bits || !bitset_tb[ETHTOOL_A_BITSET_SIZE])
+			goto err_nonl;
+		count = mnl_attr_get_u32(bitset_tb[ETHTOOL_A_BITSET_SIZE]);
+		if (mnl_attr_get_payload_len(bits) / 4 < (count + 31) / 32)
+			goto err_nonl;
+
+		printf("\t%s", before);
+		for (idx = 0; idx < count; idx++) {
+			const uint32_t *raw_data = mnl_attr_get_payload(bits);
+			char buff[10];
+
+			if (!(raw_data[idx / 32] & (1U << (idx % 32))))
+				continue;
+			if (!lm_class_match(idx, class))
+				continue;
+			name = get_string(lm_strings, idx);
+			if (!name) {
+				snprintf(buff, sizeof(buff), "BIT%u", idx);
+				name = buff;
+			}
+			if (first)
+				first = false;
+			/* ugly hack to preserve old output format */
+			if (class == LM_CLASS_REAL && (prev == idx - 1) &&
+			    prev < link_modes_count &&
+			    link_modes[prev].class == LM_CLASS_REAL &&
+			    link_modes[prev].duplex == DUPLEX_HALF)
+				putchar(' ');
+			else if (between)
+				printf("\t%s", between);
+			else
+				printf("\n\t%*s", before_len, "");
+			printf("%s", name);
+			prev = idx;
+		}
+		goto after;
+	}
+
+	printf("\t%s", before);
+	mnl_attr_for_each_nested(bit, bits) {
+		const struct nlattr *tb[ETHTOOL_A_BITSET_BIT_MAX + 1] = {};
+		DECLARE_ATTR_TB_INFO(tb);
+		unsigned int idx;
+		const char *name;
+
+		if (mnl_attr_get_type(bit) != ETHTOOL_A_BITSET_BITS_BIT)
+			continue;
+		ret = mnl_attr_parse_nested(bit, attr_cb, &tb_info);
+		if (ret < 0)
+			goto err;
+		ret = -EFAULT;
+		if (!tb[ETHTOOL_A_BITSET_BIT_INDEX] ||
+		    !tb[ETHTOOL_A_BITSET_BIT_NAME])
+			goto err;
+		if (!mask && !tb[ETHTOOL_A_BITSET_BIT_VALUE])
+			continue;
+
+		idx = mnl_attr_get_u32(tb[ETHTOOL_A_BITSET_BIT_INDEX]);
+		name = mnl_attr_get_str(tb[ETHTOOL_A_BITSET_BIT_NAME]);
+		if (!lm_class_match(idx, class))
+			continue;
+		if (first) {
+			first = false;
+		} else {
+			/* ugly hack to preserve old output format */
+			if ((class == LM_CLASS_REAL) && (prev == idx - 1) &&
+			    (prev < link_modes_count) &&
+			    (link_modes[prev].class == LM_CLASS_REAL) &&
+			    (link_modes[prev].duplex == DUPLEX_HALF))
+				putchar(' ');
+			else if (between)
+				printf("\t%s", between);
+			else
+				printf("\n\t%*s", before_len, "");
+		}
+		printf("%s", name);
+		prev = idx;
+	}
+after:
+	if (first && if_none)
+		printf("%s", if_none);
+	printf(after);
+
+	return 0;
+err:
+	putchar('\n');
+err_nonl:
+	fflush(stdout);
+	fprintf(stderr, "malformed netlink message (link_modes)\n");
+	return ret;
+}
+
+static int dump_our_modes(struct nl_context *nlctx, const struct nlattr *attr,
+			  bool *first)
+{
+	bool autoneg;
+	int ret;
+
+	print_banner(nlctx, first);
+	ret = dump_link_modes(nlctx, attr, true, LM_CLASS_PORT,
+			      "Supported ports: [ ", " ", " ]\n", NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = dump_link_modes(nlctx, attr, true, LM_CLASS_REAL,
+			      "Supported link modes:   ", NULL, "\n",
+			      "Not reported");
+	if (ret < 0)
+		return ret;
+	ret = dump_pause(attr, true, "Supported pause frame use: ");
+	if (ret < 0)
+		return ret;
+
+	autoneg = bitset_get_bit(attr, true, ETHTOOL_LINK_MODE_Autoneg_BIT,
+				 &ret);
+	if (ret < 0)
+		return ret;
+	printf("\tSupports auto-negotiation: %s\n", autoneg ? "Yes" : "No");
+
+	ret = dump_link_modes(nlctx, attr, true, LM_CLASS_FEC,
+			      "Supported FEC modes: ", " ", "\n",
+			      "Not reported");
+	if (ret < 0)
+		return ret;
+
+	ret = dump_link_modes(nlctx, attr, false, LM_CLASS_REAL,
+			      "Advertised link modes:  ", NULL, "\n",
+			      "Not reported");
+	if (ret < 0)
+		return ret;
+
+	ret = dump_pause(attr, false, "Advertised pause frame use: ");
+	if (ret < 0)
+		return ret;
+	autoneg = bitset_get_bit(attr, false, ETHTOOL_LINK_MODE_Autoneg_BIT,
+				 &ret);
+	if (ret < 0)
+		return ret;
+	printf("\tAdvertised auto-negotiation: %s\n", autoneg ? "Yes" : "No");
+
+	ret = dump_link_modes(nlctx, attr, true, LM_CLASS_FEC,
+			      "Advertised FEC modes: ", " ", "\n",
+			      "Not reported");
+	return ret;
+}
+
+static int dump_peer_modes(struct nl_context *nlctx, const struct nlattr *attr,
+			   bool *first)
+{
+	bool autoneg;
+	int ret;
+
+	print_banner(nlctx, first);
+	ret = dump_link_modes(nlctx, attr, false, LM_CLASS_REAL,
+			      "Link partner advertised link modes:  ",
+			      NULL, "\n", "Not reported");
+	if (ret < 0)
+		return ret;
+
+	ret = dump_pause(attr, false,
+			 "Link partner advertised pause frame use: ");
+	if (ret < 0)
+		return ret;
+
+	autoneg = bitset_get_bit(attr, false,
+				 ETHTOOL_LINK_MODE_Autoneg_BIT, &ret);
+	if (ret < 0)
+		return ret;
+	printf("\tLink partner advertised auto-negotiation: %s\n",
+	       autoneg ? "Yes" : "No");
+
+	ret = dump_link_modes(nlctx, attr, true, LM_CLASS_FEC,
+			      "Link partner advertised FEC modes: ",
+			      " ", "\n", "No");
+	return ret;
+}
+
+int linkmodes_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	bool first = true;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKMODES_HEADER]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (tb[ETHTOOL_A_LINKMODES_OURS]) {
+		ret = dump_our_modes(nlctx, tb[ETHTOOL_A_LINKMODES_OURS],
+				     &first);
+		if (ret < 0)
+			goto err;
+	}
+	if (tb[ETHTOOL_A_LINKMODES_PEER]) {
+		ret = dump_peer_modes(nlctx, tb[ETHTOOL_A_LINKMODES_PEER],
+				      &first);
+		if (ret < 0)
+			goto err;
+	}
+	if (tb[ETHTOOL_A_LINKMODES_SPEED]) {
+		uint32_t val = mnl_attr_get_u32(tb[ETHTOOL_A_LINKMODES_SPEED]);
+
+		print_banner(nlctx, &first);
+		if (val == 0 || val == (uint16_t)(-1) || val == (uint32_t)(-1))
+			printf("\tSpeed: Unknown!\n");
+		else
+			printf("\tSpeed: %uMb/s\n", val);
+	}
+	if (tb[ETHTOOL_A_LINKMODES_DUPLEX]) {
+		uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_DUPLEX]);
+
+		print_banner(nlctx, &first);
+		print_enum(names_duplex, ARRAY_SIZE(names_duplex), val,
+			   "Duplex");
+	}
+	if (tb[ETHTOOL_A_LINKMODES_AUTONEG]) {
+		int autoneg = mnl_attr_get_u8(tb[ETHTOOL_A_LINKMODES_AUTONEG]);
+
+		print_banner(nlctx, &first);
+		printf("\tAuto-negotiation: %s\n",
+		       (autoneg == AUTONEG_DISABLE) ? "off" : "on");
+	}
+
+	return MNL_CB_OK;
+err:
+	if (nlctx->is_monitor || nlctx->is_dump)
+		return MNL_CB_OK;
+	fputs("No data available\n", stdout);
+	nlctx->exit_code = 75;
+	return MNL_CB_ERROR;
+}
+
+int linkinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_LINKINFO_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	bool first = true;
+	int port = -1;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKINFO_HEADER]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (tb[ETHTOOL_A_LINKINFO_PORT]) {
+		uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_PORT]);
+
+		print_banner(nlctx, &first);
+		print_enum(names_port, ARRAY_SIZE(names_port), val, "Port");
+		port = val;
+	}
+	if (tb[ETHTOOL_A_LINKINFO_PHYADDR]) {
+		uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_PHYADDR]);
+
+		print_banner(nlctx, &first);
+		printf("\tPHYAD: %u\n", val);
+	}
+	if (tb[ETHTOOL_A_LINKINFO_TRANSCEIVER]) {
+		uint8_t val;
+
+		val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TRANSCEIVER]);
+		print_banner(nlctx, &first);
+		print_enum(names_transceiver, ARRAY_SIZE(names_transceiver),
+			   val, "Transceiver");
+	}
+	if (tb[ETHTOOL_A_LINKINFO_TP_MDIX] && tb[ETHTOOL_A_LINKINFO_TP_MDIX] &&
+	    port == PORT_TP) {
+		uint8_t mdix = mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TP_MDIX]);
+		uint8_t mdix_ctrl =
+			mnl_attr_get_u8(tb[ETHTOOL_A_LINKINFO_TP_MDIX_CTRL]);
+
+		print_banner(nlctx, &first);
+		dump_mdix(mdix, mdix_ctrl);
+	}
+
+	return MNL_CB_OK;
+}
+
+int linkstate_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_LINKSTATE_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	bool first = true;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHTOOL_A_LINKSTATE_HEADER]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (tb[ETHTOOL_A_LINKSTATE_LINK]) {
+		uint8_t val = mnl_attr_get_u8(tb[ETHTOOL_A_LINKSTATE_LINK]);
+
+		print_banner(nlctx, &first);
+		printf("\tLink detected: %s\n", val ? "yes" : "no");
+	}
+
+	return MNL_CB_OK;
+}
+
+int nl_gset(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	struct nl_socket *nlsk = nlctx->ethnl_socket;
+	int ret;
+
+	ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_LINKMODES_GET,
+				      ETHTOOL_A_LINKMODES_HEADER, 0);
+	if (ret < 0)
+		goto err;
+	ret = nlsock_send_get_request(nlsk, linkmodes_reply_cb);
+	if (ret < 0)
+		goto err;
+
+	ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_LINKINFO_GET,
+				      ETHTOOL_A_LINKINFO_HEADER, 0);
+	if (ret < 0)
+		goto err;
+	ret = nlsock_send_get_request(nlsk, linkinfo_reply_cb);
+	if (ret < 0)
+		goto err;
+
+	ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_LINKSTATE_GET,
+				      ETHTOOL_A_LINKSTATE_HEADER, 0);
+	if (ret < 0)
+		goto err;
+	ret = nlsock_send_get_request(nlsk, linkstate_reply_cb);
+	if (ret < 0)
+		goto err;
+
+	return 0;
+err:
+	return 75;
+}
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 15/19] netlink: support getting wake-on-lan and debugging settings
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (13 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 14/19] netlink: partial netlink handler for gset (no option) Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 16/19] netlink: add basic command line parsing helpers Michal Kubecek
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Finish "ethtool <dev>" command implementation by adding support for
wake-on-lan (ETHTOOL_MSG_WOL_GET) and debugging (ETHTOOL_MSG_DEBUG_GET,
currently only msglevel) settings.

Register the callbacks also with monitor so that "ethtool --monitor" can
display corresponding notifications.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 netlink/monitor.c  |  16 +++++++
 netlink/netlink.h  |   2 +
 netlink/settings.c | 111 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 129 insertions(+)

diff --git a/netlink/monitor.c b/netlink/monitor.c
index e8fdcd2b93ef..5fce6b64c08c 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -23,6 +23,14 @@ static struct {
 		.cmd	= ETHTOOL_MSG_LINKINFO_NTF,
 		.cb	= linkinfo_reply_cb,
 	},
+	{
+		.cmd	= ETHTOOL_MSG_WOL_NTF,
+		.cb	= wol_reply_cb,
+	},
+	{
+		.cmd	= ETHTOOL_MSG_DEBUG_NTF,
+		.cb	= debug_reply_cb,
+	},
 };
 
 static void clear_filter(struct nl_context *nlctx)
@@ -86,6 +94,14 @@ static struct monitor_option monitor_opts[] = {
 		.pattern	= "-s|--change",
 		.cmd		= ETHTOOL_MSG_LINKMODES_NTF,
 	},
+	{
+		.pattern	= "-s|--change",
+		.cmd		= ETHTOOL_MSG_WOL_NTF,
+	},
+	{
+		.pattern	= "-s|--change",
+		.cmd		= ETHTOOL_MSG_DEBUG_NTF,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 790d0802b925..5e1d00ed9ce8 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -48,6 +48,8 @@ int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname);
 
 int linkmodes_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 int linkinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int wol_reply_cb(const struct nlmsghdr *nlhdr, void *data);
+int debug_reply_cb(const struct nlmsghdr *nlhdr, void *data);
 
 static inline void copy_devname(char *dst, const char *src)
 {
diff --git a/netlink/settings.c b/netlink/settings.c
index 24b7a4495961..fed121471d0f 100644
--- a/netlink/settings.c
+++ b/netlink/settings.c
@@ -605,6 +605,101 @@ int linkstate_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 	return MNL_CB_OK;
 }
 
+void wol_modes_cb(unsigned int idx, const char *name, bool val, void *data)
+{
+	struct ethtool_wolinfo *wol = data;
+
+	if (idx >= 32)
+		return;
+	wol->supported |= (1U << idx);
+	if (val)
+		wol->wolopts |= (1U << idx);
+}
+
+int wol_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_WOL_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	struct ethtool_wolinfo wol = {};
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHTOOL_A_WOL_HEADER]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (tb[ETHTOOL_A_WOL_MODES])
+		walk_bitset(tb[ETHTOOL_A_WOL_MODES], NULL, wol_modes_cb, &wol);
+	if (tb[ETHTOOL_A_WOL_SOPASS]) {
+		unsigned int len;
+
+		len = mnl_attr_get_payload_len(tb[ETHTOOL_A_WOL_SOPASS]);
+		if (len != SOPASS_MAX)
+			fprintf(stderr, "invalid SecureOn password length %u (should be %u)\n",
+				len, SOPASS_MAX);
+		else
+			memcpy(wol.sopass,
+			       mnl_attr_get_payload(tb[ETHTOOL_A_WOL_SOPASS]),
+			       SOPASS_MAX);
+	}
+	dump_wol(&wol);
+
+	return MNL_CB_OK;
+}
+
+void msgmask_cb(unsigned int idx, const char *name, bool val, void *data)
+{
+	u32 *msg_mask = data;
+
+	if (idx >= 32)
+		return;
+	if (val)
+		*msg_mask |= (1U << idx);
+}
+
+void msgmask_cb2(unsigned int idx, const char *name, bool val, void *data)
+{
+	if (val)
+		printf(" %s", name);
+}
+
+int debug_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_DEBUG_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	const struct stringset *msgmask_strings;
+	struct nl_context *nlctx = data;
+	u32 msg_mask = 0;
+	int ret;
+
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	nlctx->devname = get_dev_name(tb[ETHTOOL_A_DEBUG_HEADER]);
+	if (!dev_ok(nlctx))
+		return MNL_CB_OK;
+
+	if (!tb[ETHTOOL_A_DEBUG_MSGMASK])
+		return MNL_CB_OK;
+	ret = netlink_init_ethnl2_socket(nlctx);
+	if (ret < 0)
+		return MNL_CB_OK;
+	msgmask_strings = global_stringset(ETH_SS_MSG_CLASSES,
+					   nlctx->ethnl2_socket);
+	walk_bitset(tb[ETHTOOL_A_DEBUG_MSGMASK], NULL, msgmask_cb, &msg_mask);
+	printf("        Current message level: 0x%08x (%u)\n"
+	       "                              ",
+	       msg_mask, msg_mask);
+	walk_bitset(tb[ETHTOOL_A_DEBUG_MSGMASK], msgmask_strings, msgmask_cb2,
+		    NULL);
+	fputc('\n', stdout);
+
+	return MNL_CB_OK;
+}
+
 int nl_gset(struct cmd_context *ctx)
 {
 	struct nl_context *nlctx = ctx->nlctx;
@@ -635,6 +730,22 @@ int nl_gset(struct cmd_context *ctx)
 	if (ret < 0)
 		goto err;
 
+	ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_WOL_GET,
+				      ETHTOOL_A_WOL_HEADER, 0);
+	if (ret < 0)
+		goto err;
+	ret = nlsock_send_get_request(nlsk, wol_reply_cb);
+	if (ret < 0)
+		goto err;
+
+	ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_DEBUG_GET,
+				      ETHTOOL_A_DEBUG_HEADER, 0);
+	if (ret < 0)
+		goto err;
+	ret = nlsock_send_get_request(nlsk, debug_reply_cb);
+	if (ret < 0)
+		goto err;
+
 	return 0;
 err:
 	return 75;
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 16/19] netlink: add basic command line parsing helpers
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (14 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 15/19] netlink: support getting wake-on-lan and debugging settings Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:47 ` [PATCH ethtool 17/19] netlink: add bitset command line parser handlers Michal Kubecek
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Existing command line parser functions from the ioctl interface are often
too closely tied to the ioctl data structures and would not be easily
adapted to generating netlink messages. Introduce a new parser framework
which assigns parser handlers to parameters and provide basic handlers for
most common parameter types:

  - flag represented by presence of a parameter
  - u8 bool represented by name followed by "on" or "off"
  - string represented by string argument
  - u32 represented by numeric argument
  - u8 represented by numeric argument
  - u32 represented by symbolic name (lookup table)
  - u8 represented by symbolic name (lookup table)
  - binary blob represented by MAC-like syntax

Main parser entry point is nl_parse() function; it gets an array of with
entries assigning parser handlers and optional data to command line
parameters and generates corresponding netlink attributes or fills raw
data.

Optionally, attributes can be divided into multiple netlink messages or
multiple nested attributes.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 Makefile.am       |   2 +-
 netlink/netlink.h |   4 +
 netlink/parser.c  | 615 ++++++++++++++++++++++++++++++++++++++++++++++
 netlink/parser.h  | 123 ++++++++++
 4 files changed, 743 insertions(+), 1 deletion(-)
 create mode 100644 netlink/parser.c
 create mode 100644 netlink/parser.h

diff --git a/Makefile.am b/Makefile.am
index e4e864add586..bb95ead79e9d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,7 +30,7 @@ ethtool_SOURCES += \
 		  netlink/msgbuff.c netlink/msgbuff.h netlink/nlsock.c \
 		  netlink/nlsock.h netlink/strset.c netlink/strset.h \
 		  netlink/monitor.c netlink/bitset.c netlink/bitset.h \
-		  netlink/settings.c \
+		  netlink/settings.c netlink/parser.c netlink/parser.h \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h
 ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 5e1d00ed9ce8..43f57f06e2d5 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -30,6 +30,10 @@ struct nl_context {
 	bool			is_monitor;
 	uint32_t		filter_cmds[CMDMASK_WORDS];
 	const char		*filter_devname;
+	const char		*cmd;
+	const char		*param;
+	char			**argp;
+	int			argc;
 };
 
 struct attr_tb_info {
diff --git a/netlink/parser.c b/netlink/parser.c
new file mode 100644
index 000000000000..0e2190eed0b4
--- /dev/null
+++ b/netlink/parser.c
@@ -0,0 +1,615 @@
+/*
+ * parser.c - netlink command line parser
+ *
+ * Implementation of command line parser used by netlink code.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "parser.h"
+
+static void parser_err_unknown_param(struct nl_context *nlctx)
+{
+	fprintf(stderr, "ethtool (%s): unknown parameter '%s'\n", nlctx->cmd,
+		nlctx->param);
+}
+
+static void parser_err_dup_param(struct nl_context *nlctx)
+{
+	fprintf(stderr, "ethtool (%s): duplicate parameter '%s'\n", nlctx->cmd,
+		nlctx->param);
+}
+
+static void parser_err_min_argc(struct nl_context *nlctx, unsigned int min_argc)
+{
+	if (min_argc == 1)
+		fprintf(stderr, "ethtool (%s): no value for parameter '%s'\n",
+			nlctx->cmd, nlctx->param);
+	else
+		fprintf(stderr,
+			"ethtool (%s): parameter '%s' requires %u words\n",
+			nlctx->cmd, nlctx->param, min_argc);
+}
+
+static void parser_err_invalid_value(struct nl_context *nlctx, const char *val)
+{
+	fprintf(stderr, "ethtool (%s): invalid value '%s' for parameter '%s'\n",
+		nlctx->cmd, val, nlctx->param);
+}
+
+static bool __prefix_0x(const char *p)
+{
+	return p[0] == '0' && (p[1] == 'x' || p[1] == 'X');
+}
+
+static int __parse_u32(const char *arg, uint32_t *result, uint32_t min,
+		       uint32_t max, int base)
+{
+	unsigned long long val;
+	char *endptr;
+
+	if (!arg || !arg[0])
+		return -EINVAL;
+	val = strtoul(arg, &endptr, base);
+	if (*endptr || val < min || val > max)
+		return -EINVAL;
+
+	*result = (uint32_t)val;
+	return 0;
+}
+
+static int parse_u32d(const char *arg, uint32_t *result)
+{
+	return __parse_u32(arg, result, 0, 0xffffffff, 10);
+}
+
+static int parse_x32(const char *arg, uint32_t *result)
+{
+	return __parse_u32(arg, result, 0, 0xffffffff, 16);
+}
+
+int parse_u32(const char *arg, uint32_t *result)
+{
+	if (!arg)
+		return -EINVAL;
+	if (__prefix_0x(arg))
+		return parse_x32(arg + 2, result);
+	else
+		return parse_u32d(arg, result);
+}
+
+static int parse_u8(const char *arg, uint8_t *result)
+{
+	uint32_t val;
+	int ret = parse_u32(arg, &val);
+
+	if (ret < 0)
+		return ret;
+	if (val > UINT8_MAX)
+		return -EINVAL;
+
+	*result = (uint8_t)val;
+	return 0;
+}
+
+static int lookup_u32(const char *arg, uint32_t *result,
+		      const struct lookup_entry_u32 *tbl)
+{
+	if (!arg)
+		return -EINVAL;
+	while (tbl->arg) {
+		if (!strcmp(tbl->arg, arg)) {
+			*result = tbl->val;
+			return 0;
+		}
+		tbl++;
+	}
+
+	return -EINVAL;
+}
+
+static int lookup_u8(const char *arg, uint8_t *result,
+		     const struct lookup_entry_u8 *tbl)
+{
+	if (!arg)
+		return -EINVAL;
+	while (tbl->arg) {
+		if (!strcmp(tbl->arg, arg)) {
+			*result = tbl->val;
+			return 0;
+		}
+		tbl++;
+	}
+
+	return -EINVAL;
+}
+
+/* Parser handler for a flag. Expects a name (with no additional argument),
+ * generates NLA_FLAG or sets a bool (if the name was present).
+ */
+int nl_parse_flag(struct nl_context *nlctx, uint16_t type, const void *data,
+		  struct nl_msg_buff *msgbuff, void *dest)
+{
+	if (dest)
+		*(bool *)dest = true;
+	return (type && ethnla_put_flag(msgbuff, type, true)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for null terminated string. Expects a string argument,
+ * generates NLA_NUL_STRING or fills const char *
+ */
+int nl_parse_string(struct nl_context *nlctx, uint16_t type, const void *data,
+		    struct nl_msg_buff *msgbuff, void *dest)
+{
+	const char *arg = *nlctx->argp;
+
+	nlctx->argp++;
+	nlctx->argc--;
+
+	if (dest)
+		*(const char **)dest = arg;
+	return (type && ethnla_put_strz(msgbuff, type, arg)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for unsigned 32-bit integer. Expects a numeric argument
+ * (may use 0x prefix), generates NLA_U32 or fills an uint32_t.
+ */
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+			const void *data, struct nl_msg_buff *msgbuff,
+			void *dest)
+{
+	const char *arg = *nlctx->argp;
+	uint32_t val;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	ret = parse_u32(arg, &val);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		return ret;
+	}
+
+	if (dest)
+		*(uint32_t *)dest = val;
+	return (type && ethnla_put_u32(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for unsigned 32-bit integer. Expects a numeric argument
+ * (may use 0x prefix), generates NLA_U32 or fills an uint32_t.
+ */
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data, struct nl_msg_buff *msgbuff,
+		       void *dest)
+{
+	const char *arg = *nlctx->argp;
+	uint8_t val;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	ret = parse_u8(arg, &val);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		return ret;
+	}
+
+	if (dest)
+		*(uint8_t *)dest = val;
+	return (type && ethnla_put_u8(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for (tri-state) bool. Expects "name on|off", generates
+ * NLA_U8 which is 1 for "on" and 0 for "off".
+ */
+int nl_parse_u8bool(struct nl_context *nlctx, uint16_t type, const void *data,
+		    struct nl_msg_buff *msgbuff, void *dest)
+{
+	const char *arg = *nlctx->argp;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	if (!strcmp(arg, "on")) {
+		if (dest)
+			*(uint8_t *)dest = 1;
+		ret = type ? ethnla_put_u8(msgbuff, type, 1) : 0;
+	} else if (!strcmp(arg, "off")) {
+		if (dest)
+			*(uint8_t *)dest = 0;
+		ret = type ? ethnla_put_u8(msgbuff, type, 0) : 0;
+	} else {
+		parser_err_invalid_value(nlctx, arg);
+		return -EINVAL;
+	}
+
+	return ret ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for 32-bit lookup value. Expects a string argument, looks it
+ * up in a table, generates NLA_U32 or fills uint32_t variable. The @data
+ * parameter is a null terminated array of struct lookup_entry_u32.
+ */
+int nl_parse_lookup_u32(struct nl_context *nlctx, uint16_t type,
+			const void *data, struct nl_msg_buff *msgbuff,
+			void *dest)
+{
+	const char *arg = *nlctx->argp;
+	uint32_t val;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	ret = lookup_u32(arg, &val, data);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		return ret;
+	}
+
+	if (dest)
+		*(uint32_t *)dest = val;
+	return (type && ethnla_put_u32(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+/* Parser handler for 8-bit lookup value. Expects a string argument, looks it
+ * up in a table, generates NLA_U8 or fills uint8_t variable. The @data
+ * parameter is a null terminated array of struct lookup_entry_u8.
+ */
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data, struct nl_msg_buff *msgbuff,
+		       void *dest)
+{
+	const char *arg = *nlctx->argp;
+	uint8_t val;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+	ret = lookup_u8(arg, &val, data);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		return ret;
+	}
+
+	if (dest)
+		*(uint8_t *)dest = val;
+	return (type && ethnla_put_u8(msgbuff, type, val)) ? -EMSGSIZE : 0;
+}
+
+static bool __is_hex(char c)
+{
+	if (isdigit(c))
+		return true;
+	else
+		return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
+
+static unsigned int __hex_val(char c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 0xa;
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 0xa;
+	return 0;
+}
+
+static bool __bytestr_delim(const char *p, char delim)
+{
+	return !*p || (delim ? (*p == delim) : !__is_hex(*p));
+}
+
+/* Parser handler for generic byte string in MAC-like format. Expects string
+ * argument in the "[[:xdigit:]]{2}(:[[:xdigit:]]{2})*" format, generates
+ * NLA_BINARY or fills a struct byte_str_value (if @dest is not null and the
+ * handler succeeds, caller is responsible for freeing the value). The @data
+ * parameter points to struct byte_str_parser_data.
+ */
+int nl_parse_byte_str(struct nl_context *nlctx, uint16_t type, const void *data,
+		      struct nl_msg_buff *msgbuff, void *dest)
+{
+	const struct byte_str_parser_data *pdata = data;
+	struct byte_str_value *dest_value = dest;
+	const char *arg = *nlctx->argp;
+	uint8_t *val = NULL;
+	unsigned int len, i;
+	const char *p;
+	int ret;
+
+	nlctx->argp++;
+	nlctx->argc--;
+
+	len = 0;
+	p = arg;
+	if (!*p)
+		goto err;
+	while (true) {
+		len++;
+		if (!__bytestr_delim(p, pdata->delim))
+			p++;
+		if (!__bytestr_delim(p, pdata->delim))
+			p++;
+		if (!__bytestr_delim(p, pdata->delim))
+			goto err;
+		if (!*p)
+			break;
+		p++;
+		if (*p && __bytestr_delim(p, pdata->delim))
+			goto err;
+	}
+	if (len < pdata->min_len || (pdata->max_len && len > pdata->max_len))
+		goto err;
+	val = malloc(len);
+	if (!val)
+		return -ENOMEM;
+
+	p = arg;
+	for (i = 0; i < len; i++) {
+		uint8_t byte = 0;
+
+		if (!__is_hex(*p))
+			goto err;
+		while (__is_hex(*p))
+			byte = 16 * byte + __hex_val(*p++);
+		if (!__bytestr_delim(p, pdata->delim))
+			goto err;
+		val[i] = byte;
+		if (*p)
+			p++;
+	}
+	ret = type ? ethnla_put(msgbuff, type, len, val) : 0;
+	if (dest) {
+		dest_value->len = len;
+		dest_value->data = val;
+	} else {
+		free(val);
+	}
+	return ret;
+
+err:
+	free(val);
+	fprintf(stderr, "ethtool (%s): invalid value '%s' of parameter '%s'\n",
+		nlctx->cmd, arg, nlctx->param);
+	return -EINVAL;
+}
+
+/* Parser handler for parameters recognized for backward compatibility but
+ * supposed to fail without passing to kernel. Does not generate any netlink
+ * attributes of fill any variable. The @data parameter points to struct
+ * error_parser_params (error message, return value and number of extra
+ * arguments to skip).
+ */
+int nl_parse_error(struct nl_context *nlctx, uint16_t type, const void *data,
+		   struct nl_msg_buff *msgbuff, void *dest)
+{
+	const struct error_parser_data *parser_data = data;
+	unsigned int skip = parser_data->extra_args;
+
+	fprintf(stderr, "ethtool (%s): ", nlctx->cmd);
+	fprintf(stderr, parser_data->err_msg, nlctx->param);
+	if (nlctx->argc < skip) {
+		fprintf(stderr, "ethtool (%s): too few arguments for parameter '%s' (expected %u)\n",
+			nlctx->cmd, nlctx->param, skip);
+	} else {
+		nlctx->argp += skip;
+		nlctx->argc -= skip;
+	}
+
+	return parser_data->ret_val;
+}
+
+/* parser implementation */
+
+static const struct param_parser *find_parser(const struct param_parser *params,
+					      const char *arg)
+{
+	const struct param_parser *parser;
+
+	for (parser = params; parser->arg; parser++)
+		if (!strcmp(arg, parser->arg))
+			return parser;
+	return NULL;
+}
+
+static bool __parser_bit(const uint64_t *map, unsigned int idx)
+{
+	return map[idx / 64] & (1 << (idx % 64));
+}
+
+static void __parser_set(uint64_t *map, unsigned int idx)
+{
+	map[idx / 64] |= (1 << (idx % 64));
+}
+
+struct tmp_buff {
+	struct nl_msg_buff	msgbuff;
+	unsigned int		id;
+	unsigned int		orig_len;
+	struct tmp_buff		*next;
+};
+
+static struct tmp_buff *tmp_buff_find(struct tmp_buff *head, unsigned int id)
+{
+	struct tmp_buff *buff;
+
+	for (buff = head; buff; buff = buff->next)
+		if (buff->id == id)
+			break;
+
+	return buff;
+}
+
+static struct tmp_buff *tmp_buff_find_or_create(struct tmp_buff **phead,
+						unsigned int id)
+{
+	struct tmp_buff **pbuff;
+	struct tmp_buff *new_buff;
+
+	for (pbuff = phead; *pbuff; pbuff = &(*pbuff)->next)
+		if ((*pbuff)->id == id)
+			return *pbuff;
+
+	new_buff = malloc(sizeof(*new_buff));
+	if (!new_buff)
+		return NULL;
+	new_buff->id = id;
+	msgbuff_init(&new_buff->msgbuff);
+	new_buff->next = NULL;
+	*pbuff = new_buff;
+
+	return new_buff;
+}
+
+static void tmp_buff_destroy(struct tmp_buff *head)
+{
+	struct tmp_buff *buff = head;
+	struct tmp_buff *next;
+
+	while (buff) {
+		next = buff->next;
+		msgbuff_done(&buff->msgbuff);
+		free(buff);
+		buff = next;
+	}
+}
+
+/* Main entry point of parser implementation.
+ * @nlctx: netlink context
+ * @params:      array of struct param_parser describing expected arguments
+ *               and their handlers; the array must be terminated by null
+ *               element {}
+ * @dest:        optional destination to copy parsed data to (at
+ *               param_parser::offset)
+ * @group_style: defines if identifiers in .group represent separate messages,
+ *               nested attributes or are not allowed
+ */
+int nl_parser(struct nl_context *nlctx, const struct param_parser *params,
+	      void *dest, enum parser_group_style group_style)
+{
+	struct nl_socket *nlsk = nlctx->ethnl_socket;
+	const struct param_parser *parser;
+	struct tmp_buff *buffs = NULL;
+	struct tmp_buff *buff;
+	unsigned int n_params;
+	uint64_t *params_seen;
+	int ret;
+
+	n_params = 0;
+	for (parser = params; parser->arg; parser++) {
+		struct nl_msg_buff *msgbuff;
+		struct nlattr *nest;
+
+		n_params++;
+		if (group_style == PARSER_GROUP_NONE || !parser->group)
+			continue;
+		ret = -ENOMEM;
+		buff = tmp_buff_find_or_create(&buffs, parser->group);
+		if (!buff)
+			goto out_free_buffs;
+		msgbuff = &buff->msgbuff;
+
+		switch (group_style) {
+		case PARSER_GROUP_NEST:
+			ret = -EMSGSIZE;
+			nest = ethnla_nest_start(&buff->msgbuff, parser->group);
+			if (!nest)
+				goto out_free_buffs;
+			break;
+		case PARSER_GROUP_MSG:
+			ret = msg_init(nlctx, msgbuff, parser->group,
+				       NLM_F_REQUEST | NLM_F_ACK);
+			if (ret < 0)
+				goto out_free_buffs;
+			if (ethnla_fill_header(msgbuff,
+					       ETHTOOL_A_LINKINFO_HEADER,
+					       nlctx->devname, 0))
+				goto out_free_buffs;
+			break;
+		default:
+			break;
+		}
+
+		buff->orig_len = msgbuff_len(msgbuff);
+	}
+	ret = -ENOMEM;
+	params_seen = calloc(DIV_ROUND_UP(n_params, 64), sizeof(uint64_t));
+	if (!params_seen)
+		goto out_free_buffs;
+
+	while (nlctx->argc > 0) {
+		struct nl_msg_buff *msgbuff;
+		void *param_dest;
+
+		nlctx->param = *nlctx->argp;
+		ret = -EINVAL;
+		parser = find_parser(params, nlctx->param);
+		if (!parser) {
+			parser_err_unknown_param(nlctx);
+			goto out_free;
+		}
+
+		/* check duplicates and minimum number of arguments */
+		if (__parser_bit(params_seen, parser - params)) {
+			parser_err_dup_param(nlctx);
+			goto out_free;
+		}
+		nlctx->argc--;
+		nlctx->argp++;
+		if (nlctx->argc < parser->min_argc) {
+			parser_err_min_argc(nlctx, parser->min_argc);
+			goto out_free;
+		}
+		__parser_set(params_seen, parser - params);
+
+		buff = NULL;
+		if (parser->group)
+			buff = tmp_buff_find(buffs, parser->group);
+		msgbuff = buff ? &buff->msgbuff : &nlsk->msgbuff;
+
+		param_dest = dest ? (dest + parser->dest_offset) : NULL;
+		ret = parser->handler(nlctx, parser->type, parser->handler_data,
+				      msgbuff, param_dest);
+		if (ret < 0)
+			goto out_free;
+	}
+
+	for (buff = buffs; buff; buff = buff->next) {
+		struct nl_msg_buff *msgbuff = &buff->msgbuff;
+
+		if (group_style == PARSER_GROUP_NONE ||
+		    msgbuff_len(msgbuff) == buff->orig_len)
+			continue;
+		switch (group_style) {
+		case PARSER_GROUP_NEST:
+			ethnla_nest_end(msgbuff, msgbuff->payload);
+			ret = msgbuff_append(&nlsk->msgbuff, msgbuff);
+			if (ret < 0)
+				goto out_free;
+			break;
+		case PARSER_GROUP_MSG:
+			ret = nlsock_sendmsg(nlsk, msgbuff);
+			if (ret < 0)
+				goto out_free;
+			ret = nlsock_process_reply(nlsk, nomsg_reply_cb, NULL);
+			if (ret < 0)
+				goto out_free;
+			break;
+		default:
+			break;
+		}
+	}
+
+	ret = 0;
+out_free:
+	free(params_seen);
+out_free_buffs:
+	tmp_buff_destroy(buffs);
+	return ret;
+}
diff --git a/netlink/parser.h b/netlink/parser.h
new file mode 100644
index 000000000000..b5e8cd418b50
--- /dev/null
+++ b/netlink/parser.h
@@ -0,0 +1,123 @@
+/*
+ * parser.h - netlink command line parser
+ *
+ * Interface for command line parser used by netlink code.
+ */
+
+#ifndef ETHTOOL_NETLINK_PARSER_H__
+#define ETHTOOL_NETLINK_PARSER_H__
+
+#include <stddef.h>
+
+#include "netlink.h"
+
+/* Some commands need to generate multiple netlink messages or multiple nested
+ * attributes from one set of command line parameters. Argument @group_style
+ * of nl_parser() takes one of three values:
+ *
+ * PARSER_GROUP_NONE - no grouping, flat series of attributes (default)
+ * PARSER_GROUP_NEST - one nest for each @group value (@group is nest type)
+ * PARSER_GROUP_MSG  - one message for each @group value (@group is command)
+ */
+enum parser_group_style {
+	PARSER_GROUP_NONE = 0,
+	PARSER_GROUP_NEST,
+	PARSER_GROUP_MSG,
+};
+
+typedef int (*param_parser_cb_t)(struct nl_context *, uint16_t, const void *,
+				 struct nl_msg_buff *, void *);
+
+struct param_parser {
+	/* command line parameter handled by this entry */
+	const char		*arg;
+	/* group id (see enum parser_group_style above) */
+	unsigned int		group;
+	/* netlink attribute type */
+	uint16_t		type;		/* netlink attribute type */
+	/* function called to parse parameter and its value */
+	param_parser_cb_t	handler;
+	/* pointer passed as @data argument of the handler */
+	const void		*handler_data;
+	/* minimum number of extra arguments after this parameter */
+	unsigned int		min_argc;
+	/* if @dest is passed to nl_parser(), offset to store value */
+	unsigned int		dest_offset;
+};
+
+/* data structures used for handler data */
+
+/* used by nl_parse_lookup_u32() */
+struct lookup_entry_u32 {
+	const char	*arg;
+	uint32_t	val;
+};
+
+/* used by nl_parse_lookup_u8() */
+struct lookup_entry_u8 {
+	const char	*arg;
+	uint8_t		val;
+};
+
+/* used by nl_parse_byte_str() */
+struct byte_str_parser_data {
+	unsigned int	min_len;
+	unsigned int	max_len;
+	char		delim;
+};
+
+/* used for value stored by nl_parse_byte_str() */
+struct byte_str_value {
+	unsigned int	len;
+	u8		*data;
+};
+
+/* used by nl_parse_error() */
+struct error_parser_data {
+	const char	*err_msg;
+	int		ret_val;
+	unsigned int	extra_args;
+};
+
+int parse_u32(const char *arg, uint32_t *result);
+
+/* parser handlers to use as param_parser::handler */
+
+/* NLA_FLAG represented by on | off */
+int nl_parse_flag(struct nl_context *nlctx, uint16_t type, const void *data,
+		  struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_NUL_STRING represented by a string argument */
+int nl_parse_string(struct nl_context *nlctx, uint16_t type, const void *data,
+		    struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_U32 represented as numeric argument */
+int nl_parse_direct_u32(struct nl_context *nlctx, uint16_t type,
+			const void *data, struct nl_msg_buff *msgbuff,
+			void *dest);
+/* NLA_U8 represented as numeric argument */
+int nl_parse_direct_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data, struct nl_msg_buff *msgbuff,
+		       void *dest);
+/* NLA_U8 represented as on | off */
+int nl_parse_u8bool(struct nl_context *nlctx, uint16_t type, const void *data,
+		    struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_U32 represented by a string argument (lookup table) */
+int nl_parse_lookup_u32(struct nl_context *nlctx, uint16_t type,
+			const void *data, struct nl_msg_buff *msgbuff,
+			void *dest);
+/* NLA_U8 represented by a string argument (lookup table) */
+int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
+		       const void *data, struct nl_msg_buff *msgbuff,
+		       void *dest);
+/* always fail (for deprecated parameters) */
+int nl_parse_error(struct nl_context *nlctx, uint16_t type, const void *data,
+		   struct nl_msg_buff *msgbuff, void *dest);
+/* NLA_BINARY represented by %x:%x:...%x (e.g. a MAC address) */
+int nl_parse_byte_str(struct nl_context *nlctx, uint16_t type,
+		      const void *data, struct nl_msg_buff *msgbuff,
+		      void *dest);
+
+/* main entry point called to parse the command line */
+int nl_parser(struct nl_context *nlctx, const struct param_parser *params,
+	      void *dest, enum parser_group_style group_style);
+
+#endif /* ETHTOOL_NETLINK_PARSER_H__ */
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 17/19] netlink: add bitset command line parser handlers
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (15 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 16/19] netlink: add basic command line parsing helpers Michal Kubecek
@ 2020-02-16 22:47 ` Michal Kubecek
  2020-02-16 22:48 ` [PATCH ethtool 18/19] netlink: add netlink handler for sset (-s) Michal Kubecek
  2020-02-16 22:48 ` [PATCH ethtool 19/19] netlink: support tests with netlink enabled Michal Kubecek
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:47 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Add three more command line parser handlers for different representation of
bitsets:

  - series of "name on|off" pairs for bitset with mask
  - series of names for bitsets without mask (lists)
  - string consisting of characters representing bits (e.g. WoL modes);
    extended syntax "([+-][a-z])+" is supported for bitsets with mask

Numeric syntax %u[/%u] is also supported.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 netlink/parser.c | 443 +++++++++++++++++++++++++++++++++++++++++++++++
 netlink/parser.h |  21 +++
 2 files changed, 464 insertions(+)

diff --git a/netlink/parser.c b/netlink/parser.c
index 0e2190eed0b4..40eb4a5c0b26 100644
--- a/netlink/parser.c
+++ b/netlink/parser.c
@@ -43,6 +43,12 @@ static void parser_err_invalid_value(struct nl_context *nlctx, const char *val)
 		nlctx->cmd, val, nlctx->param);
 }
 
+static void parser_err_invalid_flag(struct nl_context *nlctx, const char *flag)
+{
+	fprintf(stderr, "ethtool (%s): flag '%s' for parameter '%s' is not followed by 'on' or 'off'\n",
+		nlctx->cmd, flag, nlctx->param);
+}
+
 static bool __prefix_0x(const char *p)
 {
 	return p[0] == '0' && (p[1] == 'x' || p[1] == 'X');
@@ -282,6 +288,35 @@ int nl_parse_lookup_u8(struct nl_context *nlctx, uint16_t type,
 	return (type && ethnla_put_u8(msgbuff, type, val)) ? -EMSGSIZE : 0;
 }
 
+/* number of significant bits */
+static unsigned int __nsb(uint32_t x)
+{
+	unsigned int ret = 0;
+
+	if (x & 0xffff0000U) {
+		x >>= 16;
+		ret += 16;
+	}
+	if (x & 0xff00U) {
+		x >>= 8;
+		ret += 8;
+	}
+	if (x & 0xf0U) {
+		x >>= 4;
+		ret += 4;
+	}
+	if (x & 0xcU) {
+		x >>= 2;
+		ret += 2;
+	}
+	if (x & 0x2U) {
+		x >>= 1;
+		ret += 1;
+	}
+
+	return ret + x;
+}
+
 static bool __is_hex(char c)
 {
 	if (isdigit(c))
@@ -405,6 +440,414 @@ int nl_parse_error(struct nl_context *nlctx, uint16_t type, const void *data,
 	return parser_data->ret_val;
 }
 
+/* bitset parser handlers */
+
+/* Return true if a bitset argument should be parsed as numeric, i.e.
+ * (a) it starts with '0x'
+ * (b) it consists only of hex digits and at most one slash which can be
+ *     optionally followed by "0x"; if no_mask is true, slash is not allowed
+ */
+static bool is_numeric_bitset(const char *arg, bool no_mask)
+{
+	const char *p = arg;
+	bool has_slash = false;
+
+	if (!arg)
+		return false;
+	if (__prefix_0x(arg))
+		return true;
+	while (*p) {
+		if (*p == '/') {
+			if (has_slash || no_mask)
+				return false;
+			has_slash = true;
+			p++;
+			if (__prefix_0x(p))
+				p += 2;
+			continue;
+		}
+		if (!__is_hex(*p))
+			return false;
+		p++;
+	}
+	return true;
+}
+
+#define __MAX_U32_DIGITS 10
+
+/* Parse hex string (without leading "0x") into a bitmap consisting of 32-bit
+ * words. Caller must make sure arg is at least len characters long and dst has
+ * place for at least (len + 7) / 8 32-bit words. If force_hex is false, allow
+ * also base 10 unsigned 32-bit value.
+ *
+ * Returns number of significant bits in the bitmap on success and negative
+ * value on error.
+ */
+static int __parse_num_string(const char *arg, unsigned int len, uint32_t *dst,
+			      bool force_hex)
+{
+	char buff[__MAX_U32_DIGITS + 1] = {};
+	unsigned int nbits = 0;
+	const char *p = arg;
+
+	if (!len)
+		return -EINVAL;
+	if (!force_hex && len <= __MAX_U32_DIGITS) {
+		strncpy(buff, arg, len);
+		if (!buff[__MAX_U32_DIGITS]) {
+			u32 val;
+			int ret;
+
+			ret = parse_u32d(buff, &val);
+			if (!ret) {
+				*dst = val;
+				return __nsb(val);
+			}
+		}
+	}
+
+	dst += (len - 1) / 8;
+	while (len > 0) {
+		unsigned int chunk = (len % 8) ?: 8;
+		unsigned long val;
+		char *endp;
+
+		memcpy(buff, p, chunk);
+		buff[chunk] = '\0';
+		val = strtoul(buff, &endp, 16);
+		if (*endp)
+			return -EINVAL;
+		*dst-- = (uint32_t)val;
+		if (nbits)
+			nbits += 4 * chunk;
+		else
+			nbits = __nsb(val);
+
+		p += chunk;
+		len -= chunk;
+	}
+	return nbits;
+}
+
+/* Parse bitset provided as a base 16 numeric value (@no_mask is true) or pair
+ * of base 16 numeric values  separated by '/' (@no_mask is false). The "0x"
+ * prefix is optional. Generates bitset nested attribute in compact form.
+ */
+static int parse_numeric_bitset(struct nl_context *nlctx, uint16_t type,
+				bool no_mask, bool force_hex,
+				struct nl_msg_buff *msgbuff)
+{
+	unsigned int nwords, len1, len2;
+	const char *arg = *nlctx->argp;
+	bool force_hex1 = force_hex;
+	bool force_hex2 = force_hex;
+	uint32_t *value = NULL;
+	uint32_t *mask = NULL;
+	struct nlattr *nest;
+	const char *maskptr;
+	int ret = 0;
+	int nbits;
+
+	if (__prefix_0x(arg)) {
+		force_hex1 = true;
+		arg += 2;
+	}
+
+	maskptr = strchr(arg, '/');
+	if (maskptr && no_mask) {
+		parser_err_invalid_value(nlctx, arg);
+		return -EINVAL;
+	}
+	len1 = maskptr ? (maskptr - arg) : strlen(arg);
+	nwords = DIV_ROUND_UP(len1, 8);
+	nbits = 0;
+
+	if (maskptr) {
+		maskptr++;
+		if (__prefix_0x(maskptr)) {
+			maskptr += 2;
+			force_hex2 = true;
+		}
+		len2 = strlen(maskptr);
+		if (len2 > len1)
+			nwords = DIV_ROUND_UP(len2, 8);
+		mask = calloc(nwords, sizeof(uint32_t));
+		if (!mask)
+			return -ENOMEM;
+		ret = __parse_num_string(maskptr, strlen(maskptr), mask,
+					 force_hex2);
+		if (ret < 0) {
+			parser_err_invalid_value(nlctx, arg);
+			goto out_free;
+		}
+		nbits = ret;
+	}
+
+	value = calloc(nwords, sizeof(uint32_t));
+	if (!value)
+		return -ENOMEM;
+	ret = __parse_num_string(arg, len1, value, force_hex1);
+	if (ret < 0) {
+		parser_err_invalid_value(nlctx, arg);
+		goto out_free;
+	}
+	nbits = (nbits < ret) ? ret : nbits;
+	nwords = (nbits + 31) / 32;
+
+	ret = 0;
+	if (!type)
+		goto out_free;
+	ret = -EMSGSIZE;
+	nest = ethnla_nest_start(msgbuff, type);
+	if (!nest)
+	       goto out_free;
+	if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, !mask) ||
+	    ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_SIZE, nbits) ||
+	    ethnla_put(msgbuff, ETHTOOL_A_BITSET_VALUE,
+		       nwords * sizeof(uint32_t), value) ||
+	    (mask &&
+	     ethnla_put(msgbuff, ETHTOOL_A_BITSET_MASK,
+			nwords * sizeof(uint32_t), mask)))
+		goto out_free;
+	ethnla_nest_end(msgbuff, nest);
+	ret = 0;
+
+out_free:
+	free(value);
+	free(mask);
+	nlctx->argp++;
+	nlctx->argc--;
+	return ret;
+}
+
+/* Parse bitset provided as series of "name on|off" pairs (@no_mask is false)
+ * or names (@no_mask is true). Generates bitset nested attribute in verbose
+ * form with names from command line.
+ */
+static int parse_name_bitset(struct nl_context *nlctx, uint16_t type,
+			     bool no_mask, struct nl_msg_buff *msgbuff)
+{
+	struct nlattr *bitset_attr;
+	struct nlattr *bits_attr;
+	struct nlattr *bit_attr;
+	int ret;
+
+	bitset_attr = ethnla_nest_start(msgbuff, type);
+	if (!bitset_attr)
+		return -EMSGSIZE;
+	ret = -EMSGSIZE;
+	if (no_mask && ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true))
+		goto err;
+	bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
+	if (!bits_attr)
+		goto err;
+
+	while (nlctx->argc > 0) {
+		bool bit_val = true;
+
+		if (!strcmp(*nlctx->argp, "--")) {
+			nlctx->argp++;
+			nlctx->argc--;
+			break;
+		}
+		ret = -EINVAL;
+		if (!no_mask) {
+			if (nlctx->argc < 2 ||
+			    (strcmp(nlctx->argp[1], "on") &&
+			     strcmp(nlctx->argp[1], "off"))) {
+				parser_err_invalid_flag(nlctx, *nlctx->argp);
+				goto err;
+			}
+			bit_val = !strcmp(nlctx->argp[1], "on");
+		}
+
+		ret = -EMSGSIZE;
+		bit_attr = ethnla_nest_start(msgbuff,
+					     ETHTOOL_A_BITSET_BITS_BIT);
+		if (!bit_attr)
+			goto err;
+		if (ethnla_put_strz(msgbuff, ETHTOOL_A_BITSET_BIT_NAME,
+				    nlctx->argp[0]))
+			goto err;
+		if (!no_mask &&
+		    ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE,
+				    bit_val))
+			goto err;
+		ethnla_nest_end(msgbuff, bit_attr);
+
+		nlctx->argp += (no_mask ? 1 : 2);
+		nlctx->argc -= (no_mask ? 1 : 2);
+	}
+
+	ethnla_nest_end(msgbuff, bits_attr);
+	ethnla_nest_end(msgbuff, bitset_attr);
+	return 0;
+err:
+	ethnla_nest_cancel(msgbuff, bitset_attr);
+	return ret;
+}
+
+static bool is_char_bitset(const char *arg,
+			   const struct char_bitset_parser_data *data)
+{
+	bool mask = (arg[0] == '+' || arg[0] == '-');
+	unsigned int i;
+	const char *p;
+
+	for (p = arg; *p; p++) {
+		if (*p == data->reset_char)
+			continue;
+		if (mask && (*p == '+' || *p == '-'))
+			continue;
+		for (i = 0; i < data->nbits; i++)
+			if (*p == data->bit_chars[i])
+				goto found;
+		return false;
+found:
+		;
+	}
+
+	return true;
+}
+
+/* Parse bitset provided as a string consisting of characters corresponding to
+ * bit indices. The "reset character" resets the no-mask bitset to empty. If
+ * the first character is '+' or '-', generated bitset has mask and '+' and
+ * '-' switch between enabling and disabling the following bits (i.e. their
+ * value being true/false). In such case, "reset character" is not allowed.
+ */
+static int parse_char_bitset(struct nl_context *nlctx, uint16_t type,
+			     const struct char_bitset_parser_data *data,
+			     struct nl_msg_buff *msgbuff)
+{
+	const char *arg = *nlctx->argp;
+	struct nlattr *bitset_attr;
+	struct nlattr *saved_pos;
+	struct nlattr *bits_attr;
+	struct nlattr *bit_attr;
+	unsigned int idx;
+	bool val = true;
+	const char *p;
+	bool no_mask;
+	int ret;
+
+	no_mask = data->no_mask || !(arg[0] == '+' || arg[0] == '-');
+	bitset_attr = ethnla_nest_start(msgbuff, type);
+	if (!bitset_attr)
+		return -EMSGSIZE;
+	ret = -EMSGSIZE;
+	if (no_mask && ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_NOMASK, true))
+		goto err;
+	bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
+	if (!bits_attr)
+		goto err;
+	saved_pos = mnl_nlmsg_get_payload_tail(msgbuff->nlhdr);
+
+	for (p = arg; *p; p++) {
+		if (*p == '+' || *p == '-') {
+			if (no_mask) {
+				parser_err_invalid_value(nlctx, arg);
+				ret = -EINVAL;
+				goto err;
+			}
+			val = (*p == '+');
+			continue;
+		}
+		if (*p == data->reset_char) {
+			if (no_mask) {
+				mnl_attr_nest_cancel(msgbuff->nlhdr, saved_pos);
+				continue;
+			}
+			fprintf(stderr, "ethtool (%s): invalid char '%c' in '%s' for parameter '%s'\n",
+				nlctx->cmd, *p, arg, nlctx->param);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		for (idx = 0; idx < data->nbits; idx++) {
+			if (data->bit_chars[idx] == *p)
+				break;
+		}
+		if (idx >= data->nbits) {
+			fprintf(stderr, "ethtool (%s): invalid char '%c' in '%s' for parameter '%s'\n",
+				nlctx->cmd, *p, arg, nlctx->param);
+			ret = -EINVAL;
+			goto err;
+		}
+		bit_attr = ethnla_nest_start(msgbuff,
+					     ETHTOOL_A_BITSET_BITS_BIT);
+		if (!bit_attr)
+			goto err;
+		if (ethnla_put_u32(msgbuff, ETHTOOL_A_BITSET_BIT_INDEX, idx))
+			goto err;
+		if (!no_mask &&
+		    ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE, val))
+			goto err;
+		ethnla_nest_end(msgbuff, bit_attr);
+	}
+
+	ethnla_nest_end(msgbuff, bits_attr);
+	ethnla_nest_end(msgbuff, bitset_attr);
+	nlctx->argp++;
+	nlctx->argc--;
+	return 0;
+err:
+	ethnla_nest_cancel(msgbuff, bitset_attr);
+	return ret;
+}
+
+/* Parser handler for bitset. Expects either a numeric value (base 16 or 10
+ * (unless force_hex is set)), optionally followed by '/' and another numeric
+ * value (mask, unless no_mask is set), or a series of "name on|off" pairs
+ * (no_mask not set) or names (no_mask set). In the latter case, names are
+ * passed on as they are and kernel performs their interpretation and
+ * validation. The @data parameter points to struct bitset_parser_data.
+ * Generates only a bitset nested attribute. Fails if @type is zero or @dest
+ * is not null.
+ */
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data,
+		    struct nl_msg_buff *msgbuff, void *dest)
+{
+	const struct bitset_parser_data *parser_data = data;
+
+	if (!type || dest) {
+		fprintf(stderr, "ethtool (%s): internal error parsing '%s'\n",
+			nlctx->cmd, nlctx->param);
+		return -EFAULT;
+	}
+	if (is_numeric_bitset(*nlctx->argp, false))
+		return parse_numeric_bitset(nlctx, type, parser_data->no_mask,
+					    parser_data->force_hex, msgbuff);
+	else
+		return parse_name_bitset(nlctx, type, parser_data->no_mask,
+					 msgbuff);
+}
+
+/* Parser handler for bitset. Expects either a numeric value (base 10 or 16),
+ * optionally followed by '/' and another numeric value (mask, unless no_mask
+ * is set), or a string consisting of characters corresponding to bit indices.
+ * The @data parameter points to struct char_bitset_parser_data. Generates
+ * biset nested attribute. Fails if type is zero or if @dest is not null.
+ */
+int nl_parse_char_bitset(struct nl_context *nlctx, uint16_t type,
+			 const void *data, struct nl_msg_buff *msgbuff,
+			 void *dest)
+{
+	const struct char_bitset_parser_data *parser_data = data;
+
+	if (!type || dest) {
+		fprintf(stderr, "ethtool (%s): internal error parsing '%s'\n",
+			nlctx->cmd, nlctx->param);
+		return -EFAULT;
+	}
+	if (is_char_bitset(*nlctx->argp, data) ||
+	    !is_numeric_bitset(*nlctx->argp, false))
+		return parse_char_bitset(nlctx, type, parser_data, msgbuff);
+	else
+		return parse_numeric_bitset(nlctx, type, parser_data->no_mask,
+					    false, msgbuff);
+}
+
 /* parser implementation */
 
 static const struct param_parser *find_parser(const struct param_parser *params,
diff --git a/netlink/parser.h b/netlink/parser.h
index b5e8cd418b50..3cc26d21916c 100644
--- a/netlink/parser.h
+++ b/netlink/parser.h
@@ -79,6 +79,20 @@ struct error_parser_data {
 	unsigned int	extra_args;
 };
 
+/* used by nl_parse_bitset() */
+struct bitset_parser_data {
+	bool		force_hex;
+	bool		no_mask;
+};
+
+/* used by nl_parse_char_bitset() */
+struct char_bitset_parser_data {
+	const char	*bit_chars;
+	unsigned int	nbits;
+	char		reset_char;
+	bool		no_mask;
+};
+
 int parse_u32(const char *arg, uint32_t *result);
 
 /* parser handlers to use as param_parser::handler */
@@ -115,6 +129,13 @@ int nl_parse_error(struct nl_context *nlctx, uint16_t type, const void *data,
 int nl_parse_byte_str(struct nl_context *nlctx, uint16_t type,
 		      const void *data, struct nl_msg_buff *msgbuff,
 		      void *dest);
+/* bitset represented by %x[/%x] or name on|off ... [--] */
+int nl_parse_bitset(struct nl_context *nlctx, uint16_t type, const void *data,
+		    struct nl_msg_buff *msgbuff, void *dest);
+/* bitset represented by %u[/%u] or string (characters for bits) */
+int nl_parse_char_bitset(struct nl_context *nlctx, uint16_t type,
+			 const void *data, struct nl_msg_buff *msgbuff,
+			 void *dest);
 
 /* main entry point called to parse the command line */
 int nl_parser(struct nl_context *nlctx, const struct param_parser *params,
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 18/19] netlink: add netlink handler for sset (-s)
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (16 preceding siblings ...)
  2020-02-16 22:47 ` [PATCH ethtool 17/19] netlink: add bitset command line parser handlers Michal Kubecek
@ 2020-02-16 22:48 ` Michal Kubecek
  2020-02-16 22:48 ` [PATCH ethtool 19/19] netlink: support tests with netlink enabled Michal Kubecek
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:48 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

Implement "ethtool -s <dev>" subcommand using netlink interface request
ETHTOOL_MSG_LINKINFO_SET, ETHTOOL_MSG_LINKMODES_SET, ETHTOOL_MSG_WOL_SET
and ETHTOOL_MSG_DEBUG_SET.

Parser grouping with PARSER_GROUP_MSG group style is used to create
multiple request messages from one set of attributes which can be in
arbitrary order.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 ethtool.8.in       |  15 ++--
 ethtool.c          |   9 ++-
 netlink/extapi.h   |   2 +
 netlink/settings.c | 193 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 210 insertions(+), 9 deletions(-)

diff --git a/ethtool.8.in b/ethtool.8.in
index 28e4f75eee8d..ba85cfe4f413 100644
--- a/ethtool.8.in
+++ b/ethtool.8.in
@@ -242,16 +242,21 @@ ethtool \- query or control network driver and hardware settings
 .I devname
 .BN speed
 .B2 duplex half full
-.B4 port tp aui bnc mii fibre
+.B4 port tp aui bnc mii fibre da
 .B3 mdix auto on off
 .B2 autoneg on off
-.BN advertise
+.RB [ advertise \ \fIN\fP[\fB/\fP\fIM\fP]
+|
+.BI advertise \ mode
+.A1 on off
+.RB ...]
 .BN phyad
 .B2 xcvr internal external
-.RB [ wol \ \*(WO]
+.RB [ wol \ \fIN\fP[\fB/\fP\fIM\fP]
+.RB | \ wol \ \*(WO]
 .RB [ sopass \ \*(MA]
 .RB [ msglvl
-.IR N \ |
+.IR N\fP[/\fIM\fP] \ |
 .BI msglvl \ type
 .A1 on off
 .RB ...]
@@ -638,7 +643,7 @@ with just the device name as an argument will show you the supported device spee
 .A2 duplex half full
 Sets full or half duplex mode.
 .TP
-.A4 port tp aui bnc mii fibre
+.A4 port tp aui bnc mii fibre da
 Selects device port.
 .TP
 .A3 mdix auto on off
diff --git a/ethtool.c b/ethtool.c
index a69233bd73fc..baa6458bb486 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5166,18 +5166,19 @@ static const struct option args[] = {
 	{
 		.opts	= "-s|--change",
 		.func	= do_sset,
+		.nlfunc	= nl_sset,
 		.help	= "Change generic options",
 		.xhelp	= "		[ speed %d ]\n"
 			  "		[ duplex half|full ]\n"
-			  "		[ port tp|aui|bnc|mii|fibre ]\n"
+			  "		[ port tp|aui|bnc|mii|fibre|da ]\n"
 			  "		[ mdix auto|on|off ]\n"
 			  "		[ autoneg on|off ]\n"
-			  "		[ advertise %x ]\n"
+			  "		[ advertise %x[/%x] | mode on|off ... [--] ]\n"
 			  "		[ phyad %d ]\n"
 			  "		[ xcvr internal|external ]\n"
-			  "		[ wol p|u|m|b|a|g|s|f|d... ]\n"
+			  "		[ wol %d[/%d] | p|u|m|b|a|g|s|f|d... ]\n"
 			  "		[ sopass %x:%x:%x:%x:%x:%x ]\n"
-			  "		[ msglvl %d | msglvl type on|off ... ]\n"
+			  "		[ msglvl %d[/%d] | type on|off ... [--] ]\n"
 	},
 	{
 		.opts	= "-a|--show-pause",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 8608ea7f51f5..612002e8228b 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -16,6 +16,7 @@ int netlink_init(struct cmd_context *ctx);
 void netlink_done(struct cmd_context *ctx);
 
 int nl_gset(struct cmd_context *ctx);
+int nl_sset(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void nl_monitor_usage(void);
@@ -36,6 +37,7 @@ static inline void nl_monitor_usage(void)
 }
 
 #define nl_gset			NULL
+#define nl_sset			NULL
 
 #endif /* ETHTOOL_ENABLE_NETLINK */
 
diff --git a/netlink/settings.c b/netlink/settings.c
index fed121471d0f..9922747470ce 100644
--- a/netlink/settings.c
+++ b/netlink/settings.c
@@ -13,6 +13,7 @@
 #include "netlink.h"
 #include "strset.h"
 #include "bitset.h"
+#include "parser.h"
 
 /* GET_SETTINGS */
 
@@ -750,3 +751,195 @@ int nl_gset(struct cmd_context *ctx)
 err:
 	return 75;
 }
+
+/* SET_SETTINGS */
+
+enum {
+	WAKE_PHY_BIT		= 0,
+	WAKE_UCAST_BIT		= 1,
+	WAKE_MCAST_BIT		= 2,
+	WAKE_BCAST_BIT		= 3,
+	WAKE_ARP_BIT		= 4,
+	WAKE_MAGIC_BIT		= 5,
+	WAKE_MAGICSECURE_BIT	= 6,
+	WAKE_FILTER_BIT		= 7,
+};
+
+#define WAKE_ALL (WAKE_PHY | WAKE_UCAST | WAKE_MCAST | WAKE_BCAST | WAKE_ARP | \
+		  WAKE_MAGIC | WAKE_MAGICSECURE)
+
+static const struct lookup_entry_u8 port_values[] = {
+	{ .arg = "tp",		.val = PORT_TP },
+	{ .arg = "aui",		.val = PORT_AUI },
+	{ .arg = "mii",		.val = PORT_MII },
+	{ .arg = "fibre",	.val = PORT_FIBRE },
+	{ .arg = "bnc",		.val = PORT_BNC },
+	{ .arg = "da",		.val = PORT_DA },
+	{}
+};
+
+static const struct lookup_entry_u8 mdix_values[] = {
+	{ .arg = "auto",	.val = ETH_TP_MDI_AUTO },
+	{ .arg = "on",		.val = ETH_TP_MDI_X },
+	{ .arg = "off",		.val = ETH_TP_MDI },
+	{}
+};
+
+static const struct error_parser_data xcvr_parser_data = {
+	.err_msg	= "deprecated parameter '%s' not supported by kernel\n",
+	.ret_val	= -EINVAL,
+	.extra_args	= 1,
+};
+
+static const struct lookup_entry_u8 autoneg_values[] = {
+	{ .arg = "off",		.val = AUTONEG_DISABLE },
+	{ .arg = "on",		.val = AUTONEG_ENABLE },
+	{}
+};
+
+static const struct bitset_parser_data advertise_parser_data = {
+	.no_mask	= false,
+	.force_hex	= true,
+};
+
+static const struct lookup_entry_u32 duplex_values[] = {
+	{ .arg = "half",	.val = DUPLEX_HALF },
+	{ .arg = "full",	.val = DUPLEX_FULL },
+	{}
+};
+
+char wol_bit_chars[WOL_MODE_COUNT] = {
+	[WAKE_PHY_BIT]		= 'p',
+	[WAKE_UCAST_BIT]	= 'u',
+	[WAKE_MCAST_BIT]	= 'm',
+	[WAKE_BCAST_BIT]	= 'b',
+	[WAKE_ARP_BIT]		= 'a',
+	[WAKE_MAGIC_BIT]	= 'g',
+	[WAKE_MAGICSECURE_BIT]	= 's',
+	[WAKE_FILTER_BIT]	= 'f',
+};
+
+const struct char_bitset_parser_data wol_parser_data = {
+	.bit_chars	= wol_bit_chars,
+	.nbits		= WOL_MODE_COUNT,
+	.reset_char	= 'd',
+};
+
+const struct byte_str_parser_data sopass_parser_data = {
+	.min_len	= 6,
+	.max_len	= 6,
+	.delim		= ':',
+};
+
+static const struct bitset_parser_data msglvl_parser_data = {
+	.no_mask	= false,
+	.force_hex	= false,
+};
+
+static const struct param_parser sset_params[] = {
+	{
+		.arg		= "port",
+		.group		= ETHTOOL_MSG_LINKINFO_SET,
+		.type		= ETHTOOL_A_LINKINFO_PORT,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= port_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "mdix",
+		.group		= ETHTOOL_MSG_LINKINFO_SET,
+		.type		= ETHTOOL_A_LINKINFO_TP_MDIX_CTRL,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= mdix_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "phyad",
+		.group		= ETHTOOL_MSG_LINKINFO_SET,
+		.type		= ETHTOOL_A_LINKINFO_PHYADDR,
+		.handler	= nl_parse_direct_u8,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "xcvr",
+		.group		= ETHTOOL_MSG_LINKINFO_SET,
+		.handler	= nl_parse_error,
+		.handler_data	= &xcvr_parser_data,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "autoneg",
+		.group		= ETHTOOL_MSG_LINKMODES_SET,
+		.type		= ETHTOOL_A_LINKMODES_AUTONEG,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= autoneg_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "advertise",
+		.group		= ETHTOOL_MSG_LINKMODES_SET,
+		.type		= ETHTOOL_A_LINKMODES_OURS,
+		.handler	= nl_parse_bitset,
+		.handler_data	= &advertise_parser_data,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "speed",
+		.group		= ETHTOOL_MSG_LINKMODES_SET,
+		.type		= ETHTOOL_A_LINKMODES_SPEED,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "duplex",
+		.group		= ETHTOOL_MSG_LINKMODES_SET,
+		.type		= ETHTOOL_A_LINKMODES_DUPLEX,
+		.handler	= nl_parse_lookup_u8,
+		.handler_data	= duplex_values,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "wol",
+		.group		= ETHTOOL_MSG_WOL_SET,
+		.type		= ETHTOOL_A_WOL_MODES,
+		.handler	= nl_parse_char_bitset,
+		.handler_data	= &wol_parser_data,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "sopass",
+		.group		= ETHTOOL_MSG_WOL_SET,
+		.type		= ETHTOOL_A_WOL_SOPASS,
+		.handler	= nl_parse_byte_str,
+		.handler_data	= &sopass_parser_data,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "msglvl",
+		.group		= ETHTOOL_MSG_DEBUG_SET,
+		.type		= ETHTOOL_A_DEBUG_MSGMASK,
+		.handler	= nl_parse_bitset,
+		.handler_data	= &msglvl_parser_data,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_sset(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	int ret;
+
+	nlctx->cmd = "-s";
+	nlctx->argp = ctx->argp;
+	nlctx->argc = ctx->argc;
+	nlctx->devname = ctx->devname;
+
+	ret = nl_parser(nlctx, sset_params, NULL, PARSER_GROUP_MSG);
+	if (ret < 0)
+		return 1;
+
+	if (ret == 0)
+		return 0;
+	return nlctx->exit_code ?: 75;
+}
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH ethtool 19/19] netlink: support tests with netlink enabled
  2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
                   ` (17 preceding siblings ...)
  2020-02-16 22:48 ` [PATCH ethtool 18/19] netlink: add netlink handler for sset (-s) Michal Kubecek
@ 2020-02-16 22:48 ` Michal Kubecek
  18 siblings, 0 replies; 20+ messages in thread
From: Michal Kubecek @ 2020-02-16 22:48 UTC (permalink / raw)
  To: John Linville, netdev; +Cc: Andrew Lunn, Florian Fainelli

As there is no netlink implementation of features (-k / -K) yet, we only
need to take care of test-cmdline. With this patch, the full test suite
succeeds with both --enable-netlink and --disable-netlink.

There are two differences between results with netlink enabled and
disabled:

1. The netlink interface allows network device names up to 127 characters
(ALTIFNAMSIZ - 1). While alternative names can be used to identify a device
with ioctl, fixed structure does not allow passing names longer than 15
characters (IFNAMSIZ - 1).

2. Failure with deprecated 'xcvr' parameter for -s / --change is done in
slightly different way so that netlink code will have it interpreted as
a parser failure while ioctl code as (fake) request failure. Another
difference is that no kernel with ethtool netlink support can possibly
allow changing transceiver using ethtool request.

The command line tests affected by these differences have expected return
code depending on ETHTOOL_ENABLE_NETLINK.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 netlink/netlink.c |  7 +++++++
 netlink/nlsock.c  |  2 ++
 test-cmdline.c    | 29 ++++++++++++++++++++++++++---
 test-features.c   | 11 +++++++++++
 4 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/netlink/netlink.c b/netlink/netlink.c
index 2e49c6618c53..14f9f29b6135 100644
--- a/netlink/netlink.c
+++ b/netlink/netlink.c
@@ -143,6 +143,12 @@ static int family_info_cb(const struct nlmsghdr *nlhdr, void *data)
 	return MNL_CB_OK;
 }
 
+#ifdef TEST_ETHTOOL
+static int get_genl_family(struct nl_socket *nlsk, struct fam_info *info)
+{
+	return 0;
+}
+#else
 static int get_genl_family(struct nl_socket *nlsk, struct fam_info *info)
 {
 	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
@@ -165,6 +171,7 @@ out:
 	nlsk->nlctx->suppress_nlerr = false;
 	return ret;
 }
+#endif
 
 int netlink_init(struct cmd_context *ctx)
 {
diff --git a/netlink/nlsock.c b/netlink/nlsock.c
index 8cb661e96e30..3ed6baf3afa1 100644
--- a/netlink/nlsock.c
+++ b/netlink/nlsock.c
@@ -218,6 +218,7 @@ int nlsock_prep_get_request(struct nl_socket *nlsk, unsigned int nlcmd,
 	return 0;
 }
 
+#ifndef TEST_ETHTOOL
 ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *altbuff)
 {
 	struct nl_msg_buff *msgbuff = altbuff ?: &nlsk->msgbuff;
@@ -227,6 +228,7 @@ ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *altbuff)
 	debug_msg(nlsk, msgbuff->buff, nlhdr->nlmsg_len, true);
 	return mnl_socket_sendto(nlsk->sk, nlhdr, nlhdr->nlmsg_len);
 }
+#endif
 
 int nlsock_send_get_request(struct nl_socket *nlsk, mnl_cb_t cb)
 {
diff --git a/test-cmdline.c b/test-cmdline.c
index b76e2c3e640b..a6c0bd1efc2d 100644
--- a/test-cmdline.c
+++ b/test-cmdline.c
@@ -12,6 +12,12 @@
 #define TEST_NO_WRAPPERS
 #include "internal.h"
 
+#ifdef ETHTOOL_ENABLE_NETLINK
+#define IS_NL 1
+#else
+#define IS_NL 0
+#endif
+
 static struct test_case {
 	int rc;
 	const char *args;
@@ -19,7 +25,10 @@ static struct test_case {
 	{ 1, "" },
 	{ 0, "devname" },
 	{ 0, "15_char_devname" },
-	{ 1, "16_char_devname!" },
+	/* netlink interface allows names up to 127 characters */
+	{ !IS_NL, "16_char_devname!" },
+	{ !IS_NL, "127_char_devname0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde" },
+	{ 1, "128_char_devname0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" },
 	/* Argument parsing for -s is specialised */
 	{ 0, "-s devname" },
 	{ 0, "--change devname speed 100 duplex half mdix auto" },
@@ -55,7 +64,8 @@ static struct test_case {
 	{ 0, "-s devname phyad 1" },
 	{ 1, "--change devname phyad foo" },
 	{ 1, "-s devname phyad" },
-	{ 0, "--change devname xcvr external" },
+	/* Deprecated 'xcvr' detected by netlink parser */
+	{ IS_NL, "--change devname xcvr external" },
 	{ 1, "-s devname xcvr foo" },
 	{ 1, "--change devname xcvr" },
 	{ 0, "-s devname wol p" },
@@ -69,7 +79,9 @@ static struct test_case {
 	{ 0, "-s devname msglvl hw on rx_status off" },
 	{ 1, "--change devname msglvl hw foo" },
 	{ 1, "-s devname msglvl hw" },
-	{ 0, "--change devname speed 100 duplex half port tp autoneg on advertise 0x1 phyad 1 xcvr external wol p sopass 01:23:45:67:89:ab msglvl 1" },
+	{ 0, "--change devname speed 100 duplex half port tp autoneg on advertise 0x1 phyad 1 wol p sopass 01:23:45:67:89:ab msglvl 1" },
+	/* Deprecated 'xcvr' detected by netlink parser */
+	{ IS_NL, "--change devname speed 100 duplex half port tp autoneg on advertise 0x1 phyad 1 xcvr external wol p sopass 01:23:45:67:89:ab msglvl 1" },
 	{ 1, "-s devname foo" },
 	{ 1, "-s" },
 	{ 0, "-a devname" },
@@ -294,6 +306,17 @@ int send_ioctl(struct cmd_context *ctx, void *cmd)
 	test_exit(0);
 }
 
+#ifdef ETHTOOL_ENABLE_NETLINK
+struct nl_socket;
+struct nl_msg_buff;
+
+ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *altbuff)
+{
+	/* If we get this far then parsing succeeded */
+	test_exit(0);
+}
+#endif
+
 int main(void)
 {
 	struct test_case *tc;
diff --git a/test-features.c b/test-features.c
index 6ebb364803a2..b9f80f073d1f 100644
--- a/test-features.c
+++ b/test-features.c
@@ -511,6 +511,17 @@ int send_ioctl(struct cmd_context *ctx, void *cmd)
 	return rc;
 }
 
+#ifdef ETHTOOL_ENABLE_NETLINK
+struct nl_socket;
+struct nl_msg_buff;
+
+ssize_t nlsock_sendmsg(struct nl_socket *nlsk, struct nl_msg_buff *altbuff)
+{
+	/* Should not be called with test-features */
+	exit(1);
+}
+#endif
+
 int main(void)
 {
 	const struct test_case *tc;
-- 
2.25.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, back to index

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-16 22:46 [PATCH ethtool 00/19] initial netlink interface implementation for 5.6 release Michal Kubecek
2020-02-16 22:46 ` [PATCH ethtool 01/19] move UAPI header copies to a separate directory Michal Kubecek
2020-02-16 22:46 ` [PATCH ethtool 02/19] update UAPI header copies Michal Kubecek
2020-02-16 22:46 ` [PATCH ethtool 03/19] add --debug option to control debugging messages Michal Kubecek
2020-02-16 22:46 ` [PATCH ethtool 04/19] use named initializers in command line option list Michal Kubecek
2020-02-16 22:46 ` [PATCH ethtool 05/19] netlink: add netlink related UAPI header files Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 06/19] netlink: introduce the netlink interface Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 07/19] netlink: message buffer and composition helpers Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 08/19] netlink: netlink socket wrapper and helpers Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 09/19] netlink: initialize ethtool netlink socket Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 10/19] netlink: add support for string sets Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 11/19] netlink: add notification monitor Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 12/19] move shared code into a common file Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 13/19] netlink: add bitset helpers Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 14/19] netlink: partial netlink handler for gset (no option) Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 15/19] netlink: support getting wake-on-lan and debugging settings Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 16/19] netlink: add basic command line parsing helpers Michal Kubecek
2020-02-16 22:47 ` [PATCH ethtool 17/19] netlink: add bitset command line parser handlers Michal Kubecek
2020-02-16 22:48 ` [PATCH ethtool 18/19] netlink: add netlink handler for sset (-s) Michal Kubecek
2020-02-16 22:48 ` [PATCH ethtool 19/19] netlink: support tests with netlink enabled Michal Kubecek

Netdev Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/netdev/0 netdev/git/0.git
	git clone --mirror https://lore.kernel.org/netdev/1 netdev/git/1.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 netdev netdev/ https://lore.kernel.org/netdev \
		netdev@vger.kernel.org
	public-inbox-index netdev

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.netdev


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git