summaryrefslogtreecommitdiffstats
path: root/netlink/netlink.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netlink/netlink.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/netlink/netlink.c b/netlink/netlink.c
new file mode 100644
index 0000000..ef0d825
--- /dev/null
+++ b/netlink/netlink.c
@@ -0,0 +1,527 @@
+/*
+ * 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"
+#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)
+ */
+int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data __maybe_unused)
+{
+ 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;
+ uint16_t type = mnl_attr_get_type(attr);
+
+ if (type <= tb_info->max_type)
+ tb_info->tb[type] = attr;
+
+ 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;
+}
+
+/**
+ * netlink_cmd_check() - check support for netlink command
+ * @ctx: ethtool command context
+ * @cmd: netlink command id
+ * @devname: device name from user
+ * @allow_wildcard: wildcard dumps supported
+ *
+ * Check if command @cmd is known to be unsupported based on ops information
+ * from genetlink family id request. Set nlctx->ioctl_fallback if ethtool
+ * should fall back to ioctl, i.e. when we do not know in advance that
+ * netlink request is supported. Set nlctx->wildcard_unsupported if "*" was
+ * used as device name but the request does not support wildcards (on either
+ * side).
+ *
+ * Return: true if we know the netlink request is not supported and should
+ * fail (and possibly fall back) without actually sending it to kernel.
+ */
+bool netlink_cmd_check(struct cmd_context *ctx, unsigned int cmd,
+ bool allow_wildcard)
+{
+ bool is_dump = !strcmp(ctx->devname, WILDCARD_DEVNAME);
+ uint32_t cap = is_dump ? GENL_CMD_CAP_DUMP : GENL_CMD_CAP_DO;
+ struct nl_context *nlctx = ctx->nlctx;
+
+ if (is_dump && !allow_wildcard) {
+ nlctx->wildcard_unsupported = true;
+ return true;
+ }
+ if (!nlctx->ops_info) {
+ nlctx->ioctl_fallback = true;
+ return false;
+ }
+ if (cmd > ETHTOOL_MSG_USER_MAX || !nlctx->ops_info[cmd].op_flags) {
+ nlctx->ioctl_fallback = true;
+ return true;
+ }
+
+ if (is_dump && !(nlctx->ops_info[cmd].op_flags & GENL_CMD_CAP_DUMP))
+ nlctx->wildcard_unsupported = true;
+
+ return !(nlctx->ops_info[cmd].op_flags & cap);
+}
+
+struct ethtool_op_policy_query_ctx {
+ struct nl_context *nlctx;
+ unsigned int op;
+ unsigned int op_hdr_attr;
+
+ bool op_policy_found;
+ bool hdr_policy_found;
+ unsigned int op_policy_idx;
+ unsigned int hdr_policy_idx;
+ uint64_t flag_mask;
+};
+
+static int family_policy_find_op(struct ethtool_op_policy_query_ctx *policy_ctx,
+ const struct nlattr *op_policy)
+{
+ const struct nlattr *attr;
+ unsigned int type;
+ int ret;
+
+ type = policy_ctx->nlctx->is_dump ?
+ CTRL_ATTR_POLICY_DUMP : CTRL_ATTR_POLICY_DO;
+
+ mnl_attr_for_each_nested(attr, op_policy) {
+ const struct nlattr *tb[CTRL_ATTR_POLICY_DUMP_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+
+ if (mnl_attr_get_type(attr) != policy_ctx->op)
+ continue;
+
+ ret = mnl_attr_parse_nested(attr, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+
+ if (!tb[type])
+ continue;
+
+ policy_ctx->op_policy_found = true;
+ policy_ctx->op_policy_idx = mnl_attr_get_u32(tb[type]);
+ break;
+ }
+
+ return 0;
+}
+
+static int family_policy_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tba[NL_POLICY_TYPE_ATTR_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tba);
+ const struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct ethtool_op_policy_query_ctx *policy_ctx = data;
+ const struct nlattr *policy_attr, *attr_attr, *attr;
+ unsigned int attr_idx, policy_idx;
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ if (!policy_ctx->op_policy_found) {
+ if (!tb[CTRL_ATTR_OP_POLICY]) {
+ fprintf(stderr, "Error: op policy map not present\n");
+ return MNL_CB_ERROR;
+ }
+ ret = family_policy_find_op(policy_ctx, tb[CTRL_ATTR_OP_POLICY]);
+ return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK;
+ }
+
+ if (!tb[CTRL_ATTR_POLICY])
+ return MNL_CB_OK;
+
+ policy_attr = mnl_attr_get_payload(tb[CTRL_ATTR_POLICY]);
+ policy_idx = mnl_attr_get_type(policy_attr);
+ attr_attr = mnl_attr_get_payload(policy_attr);
+ attr_idx = mnl_attr_get_type(attr_attr);
+
+ ret = mnl_attr_parse_nested(attr_attr, attr_cb, &tba_info);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ if (policy_idx == policy_ctx->op_policy_idx &&
+ attr_idx == policy_ctx->op_hdr_attr) {
+ attr = tba[NL_POLICY_TYPE_ATTR_POLICY_IDX];
+ if (!attr) {
+ fprintf(stderr, "Error: no policy index in what was expected to be ethtool header attribute\n");
+ return MNL_CB_ERROR;
+ }
+ policy_ctx->hdr_policy_found = true;
+ policy_ctx->hdr_policy_idx = mnl_attr_get_u32(attr);
+ }
+
+ if (policy_ctx->hdr_policy_found &&
+ policy_ctx->hdr_policy_idx == policy_idx &&
+ attr_idx == ETHTOOL_A_HEADER_FLAGS) {
+ attr = tba[NL_POLICY_TYPE_ATTR_MASK];
+ if (!attr) {
+ fprintf(stderr, "Error: validation mask not reported for ethtool header flags\n");
+ return MNL_CB_ERROR;
+ }
+
+ policy_ctx->flag_mask = mnl_attr_get_u64(attr);
+ }
+
+ return MNL_CB_OK;
+}
+
+static int read_flags_policy(struct nl_context *nlctx, struct nl_socket *nlsk,
+ unsigned int nlcmd, unsigned int hdrattr)
+{
+ struct ethtool_op_policy_query_ctx policy_ctx;
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ int ret;
+
+ if (nlctx->ops_info[nlcmd].hdr_policy_loaded)
+ return 0;
+
+ memset(&policy_ctx, 0, sizeof(policy_ctx));
+ policy_ctx.nlctx = nlctx;
+ policy_ctx.op = nlcmd;
+ policy_ctx.op_hdr_attr = hdrattr;
+
+ ret = __msg_init(msgbuff, GENL_ID_CTRL, CTRL_CMD_GETPOLICY,
+ NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, 1);
+ if (ret < 0)
+ return ret;
+ ret = -EMSGSIZE;
+ if (ethnla_put_u16(msgbuff, CTRL_ATTR_FAMILY_ID, nlctx->ethnl_fam))
+ return ret;
+ if (ethnla_put_u32(msgbuff, CTRL_ATTR_OP, nlcmd))
+ return ret;
+
+ nlsock_sendmsg(nlsk, NULL);
+ nlsock_process_reply(nlsk, family_policy_cb, &policy_ctx);
+
+ nlctx->ops_info[nlcmd].hdr_policy_loaded = 1;
+ nlctx->ops_info[nlcmd].hdr_flags = policy_ctx.flag_mask;
+ return 0;
+}
+
+u32 get_stats_flag(struct nl_context *nlctx, unsigned int nlcmd,
+ unsigned int hdrattr)
+{
+ if (!nlctx->ctx->show_stats)
+ return 0;
+ if (nlcmd > ETHTOOL_MSG_USER_MAX ||
+ !(nlctx->ops_info[nlcmd].op_flags & GENL_CMD_CAP_HASPOL))
+ return 0;
+
+ if (read_flags_policy(nlctx, nlctx->ethnl_socket, nlcmd, hdrattr) < 0)
+ return 0;
+
+ return nlctx->ops_info[nlcmd].hdr_flags & ETHTOOL_FLAG_STATS;
+}
+
+/* initialization */
+
+static int genl_read_ops(struct nl_context *nlctx,
+ const struct nlattr *ops_attr)
+{
+ struct nl_op_info *ops_info;
+ struct nlattr *op_attr;
+ int ret;
+
+ ops_info = calloc(__ETHTOOL_MSG_USER_CNT, sizeof(ops_info[0]));
+ if (!ops_info)
+ return -ENOMEM;
+
+ mnl_attr_for_each_nested(op_attr, ops_attr) {
+ const struct nlattr *tb[CTRL_ATTR_OP_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ uint32_t op_id;
+
+ ret = mnl_attr_parse_nested(op_attr, attr_cb, &tb_info);
+ if (ret < 0)
+ goto err;
+
+ if (!tb[CTRL_ATTR_OP_ID] || !tb[CTRL_ATTR_OP_FLAGS])
+ continue;
+ op_id = mnl_attr_get_u32(tb[CTRL_ATTR_OP_ID]);
+ if (op_id >= __ETHTOOL_MSG_USER_CNT)
+ continue;
+
+ ops_info[op_id].op_flags =
+ mnl_attr_get_u32(tb[CTRL_ATTR_OP_FLAGS]);
+ }
+
+ nlctx->ops_info = ops_info;
+ return 0;
+err:
+ free(ops_info);
+ return ret;
+}
+
+static void find_mc_group(struct nl_context *nlctx, struct nlattr *nest)
+{
+ 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]),
+ ETHTOOL_MCGRP_MONITOR_NAME))
+ continue;
+ nlctx->ethnl_mongrp =
+ mnl_attr_get_u32(grp_tb[CTRL_ATTR_MCAST_GRP_ID]);
+ return;
+ }
+}
+
+static int __maybe_unused family_info_cb(const struct nlmsghdr *nlhdr,
+ void *data)
+{
+ struct nl_context *nlctx = data;
+ struct nlattr *attr;
+ int ret;
+
+ mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+ switch (mnl_attr_get_type(attr)) {
+ case CTRL_ATTR_FAMILY_ID:
+ nlctx->ethnl_fam = mnl_attr_get_u16(attr);
+ break;
+ case CTRL_ATTR_OPS:
+ ret = genl_read_ops(nlctx, attr);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+ break;
+ case CTRL_ATTR_MCAST_GROUPS:
+ find_mc_group(nlctx, attr);
+ break;
+ }
+ }
+
+ return MNL_CB_OK;
+}
+
+#ifdef TEST_ETHTOOL
+static int get_genl_family(struct nl_context *nlctx __maybe_unused,
+ struct nl_socket *nlsk __maybe_unused)
+{
+ return 0;
+}
+#else
+static int get_genl_family(struct nl_context *nlctx, struct nl_socket *nlsk)
+{
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ int ret;
+
+ nlctx->suppress_nlerr = 2;
+ 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, ETHTOOL_GENL_NAME))
+ goto out;
+
+ nlsock_sendmsg(nlsk, NULL);
+ nlsock_process_reply(nlsk, family_info_cb, nlctx);
+ ret = nlctx->ethnl_fam ? 0 : -EADDRNOTAVAIL;
+
+out:
+ nlctx->suppress_nlerr = 0;
+ return ret;
+}
+#endif
+
+int netlink_init(struct cmd_context *ctx)
+{
+ 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, NETLINK_GENERIC);
+ if (ret < 0)
+ goto out_free;
+ ret = get_genl_family(nlctx, nlctx->ethnl_socket);
+ if (ret < 0)
+ goto out_nlsk;
+
+ ctx->nlctx = nlctx;
+ return 0;
+
+out_nlsk:
+ nlsock_done(nlctx->ethnl_socket);
+out_free:
+ free(nlctx->ops_info);
+ free(nlctx);
+ return ret;
+}
+
+static void netlink_done(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+
+ if (!nlctx)
+ return;
+
+ nlsock_done(nlctx->ethnl_socket);
+ nlsock_done(nlctx->ethnl2_socket);
+ nlsock_done(nlctx->rtnl_socket);
+ free(nlctx->ops_info);
+ free(nlctx);
+ ctx->nlctx = NULL;
+ cleanup_all_strings();
+}
+
+/**
+ * netlink_run_handler() - run netlink handler for subcommand
+ * @ctx: command context
+ * @nlchk: netlink capability check
+ * @nlfunc: subcommand netlink handler to call
+ * @no_fallback: there is no ioctl fallback handler
+ *
+ * This function returns only if ioctl() handler should be run as fallback.
+ * Otherwise it exits with appropriate return code.
+ */
+void netlink_run_handler(struct cmd_context *ctx, nl_chk_t nlchk,
+ nl_func_t nlfunc, bool no_fallback)
+{
+ bool wildcard = ctx->devname && !strcmp(ctx->devname, WILDCARD_DEVNAME);
+ bool wildcard_unsupported, ioctl_fallback;
+ struct nl_context *nlctx;
+ const char *reason;
+ int ret;
+
+ if (nlchk && !nlchk(ctx)) {
+ reason = "ioctl-only request";
+ goto no_support;
+ }
+ if (ctx->devname && strlen(ctx->devname) >= ALTIFNAMSIZ) {
+ fprintf(stderr, "device name '%s' longer than %u characters\n",
+ ctx->devname, ALTIFNAMSIZ - 1);
+ exit(1);
+ }
+
+ if (!nlfunc) {
+ reason = "ethtool netlink support for subcommand missing";
+ goto no_support;
+ }
+ if (netlink_init(ctx)) {
+ reason = "netlink interface initialization failed";
+ goto no_support;
+ }
+ nlctx = ctx->nlctx;
+
+ ret = nlfunc(ctx);
+ wildcard_unsupported = nlctx->wildcard_unsupported;
+ ioctl_fallback = nlctx->ioctl_fallback;
+ netlink_done(ctx);
+
+ if (no_fallback || ret != -EOPNOTSUPP || !ioctl_fallback) {
+ if (wildcard_unsupported)
+ fprintf(stderr, "%s\n",
+ "subcommand does not support wildcard dump");
+ exit(ret >= 0 ? ret : 1);
+ }
+ if (wildcard_unsupported)
+ reason = "subcommand does not support wildcard dump";
+ else
+ reason = "kernel netlink support for subcommand missing";
+
+no_support:
+ if (no_fallback) {
+ fprintf(stderr, "%s, subcommand not supported by ioctl\n",
+ reason);
+ exit(1);
+ }
+ if (wildcard) {
+ fprintf(stderr, "%s, wildcard dump not supported\n", reason);
+ exit(1);
+ }
+ if (ctx->devname && strlen(ctx->devname) >= IFNAMSIZ) {
+ fprintf(stderr,
+ "%s, device name longer than %u not supported\n",
+ reason, IFNAMSIZ - 1);
+ exit(1);
+ }
+
+ /* fallback to ioctl() */
+}