mirror of
				https://git.proxmox.com/git/mirror_iproute2
				synced 2025-11-04 12:09:07 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			414 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * ctrl.c	generic netlink controller
 | 
						|
 *
 | 
						|
 *		This program is free software; you can distribute it and/or
 | 
						|
 *		modify it under the terms of the GNU General Public License
 | 
						|
 *		as published by the Free Software Foundation; either version
 | 
						|
 *		2 of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 * Authors:	J Hadi Salim (hadi@cyberus.ca)
 | 
						|
 *		Johannes Berg (johannes@sipsolutions.net)
 | 
						|
 */
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <sys/socket.h>
 | 
						|
#include <netinet/in.h>
 | 
						|
#include <arpa/inet.h>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include "utils.h"
 | 
						|
#include "genl_utils.h"
 | 
						|
 | 
						|
#define GENL_MAX_FAM_OPS	256
 | 
						|
#define GENL_MAX_FAM_GRPS	256
 | 
						|
 | 
						|
static int usage(void)
 | 
						|
{
 | 
						|
	fprintf(stderr,"Usage: ctrl <CMD>\n" \
 | 
						|
		       "CMD   := get <PARMS> | list | monitor\n" \
 | 
						|
		       "PARMS := name <name> | id <id>\n" \
 | 
						|
		       "Examples:\n" \
 | 
						|
		       "\tctrl ls\n" \
 | 
						|
		       "\tctrl monitor\n" \
 | 
						|
		       "\tctrl get name foobar\n" \
 | 
						|
		       "\tctrl get id 0xF\n");
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
int genl_ctrl_resolve_family(const char *family)
 | 
						|
{
 | 
						|
	struct rtnl_handle rth;
 | 
						|
	int ret = 0;
 | 
						|
	struct {
 | 
						|
		struct nlmsghdr         n;
 | 
						|
		struct genlmsghdr	g;
 | 
						|
		char                    buf[4096];
 | 
						|
	} req = {
 | 
						|
		.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN),
 | 
						|
		.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
 | 
						|
		.n.nlmsg_type = GENL_ID_CTRL,
 | 
						|
		.g.cmd = CTRL_CMD_GETFAMILY,
 | 
						|
	};
 | 
						|
	struct nlmsghdr *nlh = &req.n;
 | 
						|
	struct genlmsghdr *ghdr = &req.g;
 | 
						|
	struct nlmsghdr *answer = NULL;
 | 
						|
 | 
						|
	if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC) < 0) {
 | 
						|
		fprintf(stderr, "Cannot open generic netlink socket\n");
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
 | 
						|
	addattr_l(nlh, 128, CTRL_ATTR_FAMILY_NAME, family, strlen(family) + 1);
 | 
						|
 | 
						|
	if (rtnl_talk(&rth, nlh, &answer) < 0) {
 | 
						|
		fprintf(stderr, "Error talking to the kernel\n");
 | 
						|
		goto errout;
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		struct rtattr *tb[CTRL_ATTR_MAX + 1];
 | 
						|
		int len = answer->nlmsg_len;
 | 
						|
		struct rtattr *attrs;
 | 
						|
 | 
						|
		if (answer->nlmsg_type !=  GENL_ID_CTRL) {
 | 
						|
			fprintf(stderr, "Not a controller message, nlmsg_len=%d "
 | 
						|
				"nlmsg_type=0x%x\n", answer->nlmsg_len, answer->nlmsg_type);
 | 
						|
			goto errout;
 | 
						|
		}
 | 
						|
 | 
						|
		if (ghdr->cmd != CTRL_CMD_NEWFAMILY) {
 | 
						|
			fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd);
 | 
						|
			goto errout;
 | 
						|
		}
 | 
						|
 | 
						|
		len -= NLMSG_LENGTH(GENL_HDRLEN);
 | 
						|
 | 
						|
		if (len < 0) {
 | 
						|
			fprintf(stderr, "wrong controller message len %d\n", len);
 | 
						|
			free(answer);
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
 | 
						|
		attrs = (struct rtattr *) ((char *) answer + NLMSG_LENGTH(GENL_HDRLEN));
 | 
						|
		parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);
 | 
						|
 | 
						|
		if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
 | 
						|
			fprintf(stderr, "Missing family id TLV\n");
 | 
						|
			goto errout;
 | 
						|
		}
 | 
						|
 | 
						|
		ret = rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]);
 | 
						|
	}
 | 
						|
 | 
						|
errout:
 | 
						|
	free(answer);
 | 
						|
	rtnl_close(&rth);
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static void print_ctrl_cmd_flags(FILE *fp, __u32 fl)
 | 
						|
{
 | 
						|
	fprintf(fp, "\n\t\tCapabilities (0x%x):\n ", fl);
 | 
						|
	if (!fl) {
 | 
						|
		fprintf(fp, "\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	fprintf(fp, "\t\t ");
 | 
						|
 | 
						|
	if (fl & GENL_ADMIN_PERM)
 | 
						|
		fprintf(fp, " requires admin permission;");
 | 
						|
	if (fl & GENL_CMD_CAP_DO)
 | 
						|
		fprintf(fp, " can doit;");
 | 
						|
	if (fl & GENL_CMD_CAP_DUMP)
 | 
						|
		fprintf(fp, " can dumpit;");
 | 
						|
	if (fl & GENL_CMD_CAP_HASPOL)
 | 
						|
		fprintf(fp, " has policy");
 | 
						|
 | 
						|
	fprintf(fp, "\n");
 | 
						|
}
 | 
						|
 | 
						|
static int print_ctrl_cmds(FILE *fp, struct rtattr *arg, __u32 ctrl_ver)
 | 
						|
{
 | 
						|
	struct rtattr *tb[CTRL_ATTR_OP_MAX + 1];
 | 
						|
 | 
						|
	if (arg == NULL)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	parse_rtattr_nested(tb, CTRL_ATTR_OP_MAX, arg);
 | 
						|
	if (tb[CTRL_ATTR_OP_ID]) {
 | 
						|
		__u32 *id = RTA_DATA(tb[CTRL_ATTR_OP_ID]);
 | 
						|
		fprintf(fp, " ID-0x%x ",*id);
 | 
						|
	}
 | 
						|
	/* we are only gonna do this for newer version of the controller */
 | 
						|
	if (tb[CTRL_ATTR_OP_FLAGS] && ctrl_ver >= 0x2) {
 | 
						|
		__u32 *fl = RTA_DATA(tb[CTRL_ATTR_OP_FLAGS]);
 | 
						|
		print_ctrl_cmd_flags(fp, *fl);
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
static int print_ctrl_grp(FILE *fp, struct rtattr *arg, __u32 ctrl_ver)
 | 
						|
{
 | 
						|
	struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1];
 | 
						|
 | 
						|
	if (arg == NULL)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, arg);
 | 
						|
	if (tb[2]) {
 | 
						|
		__u32 *id = RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_ID]);
 | 
						|
		fprintf(fp, " ID-0x%x ",*id);
 | 
						|
	}
 | 
						|
	if (tb[1]) {
 | 
						|
		char *name = RTA_DATA(tb[CTRL_ATTR_MCAST_GRP_NAME]);
 | 
						|
		fprintf(fp, " name: %s ", name);
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * The controller sends one nlmsg per family
 | 
						|
*/
 | 
						|
static int print_ctrl(const struct sockaddr_nl *who,
 | 
						|
		      struct rtnl_ctrl_data *ctrl,
 | 
						|
		      struct nlmsghdr *n, void *arg)
 | 
						|
{
 | 
						|
	struct rtattr *tb[CTRL_ATTR_MAX + 1];
 | 
						|
	struct genlmsghdr *ghdr = NLMSG_DATA(n);
 | 
						|
	int len = n->nlmsg_len;
 | 
						|
	struct rtattr *attrs;
 | 
						|
	FILE *fp = (FILE *) arg;
 | 
						|
	__u32 ctrl_v = 0x1;
 | 
						|
 | 
						|
	if (n->nlmsg_type !=  GENL_ID_CTRL) {
 | 
						|
		fprintf(stderr, "Not a controller message, nlmsg_len=%d "
 | 
						|
			"nlmsg_type=0x%x\n", n->nlmsg_len, n->nlmsg_type);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ghdr->cmd != CTRL_CMD_GETFAMILY &&
 | 
						|
	    ghdr->cmd != CTRL_CMD_DELFAMILY &&
 | 
						|
	    ghdr->cmd != CTRL_CMD_NEWFAMILY &&
 | 
						|
	    ghdr->cmd != CTRL_CMD_NEWMCAST_GRP &&
 | 
						|
	    ghdr->cmd != CTRL_CMD_DELMCAST_GRP) {
 | 
						|
		fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	len -= NLMSG_LENGTH(GENL_HDRLEN);
 | 
						|
 | 
						|
	if (len < 0) {
 | 
						|
		fprintf(stderr, "wrong controller message len %d\n", len);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
 | 
						|
	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);
 | 
						|
 | 
						|
	if (tb[CTRL_ATTR_FAMILY_NAME]) {
 | 
						|
		char *name = RTA_DATA(tb[CTRL_ATTR_FAMILY_NAME]);
 | 
						|
		fprintf(fp, "\nName: %s\n",name);
 | 
						|
	}
 | 
						|
	if (tb[CTRL_ATTR_FAMILY_ID]) {
 | 
						|
		__u16 *id = RTA_DATA(tb[CTRL_ATTR_FAMILY_ID]);
 | 
						|
		fprintf(fp, "\tID: 0x%x ",*id);
 | 
						|
	}
 | 
						|
	if (tb[CTRL_ATTR_VERSION]) {
 | 
						|
		__u32 *v = RTA_DATA(tb[CTRL_ATTR_VERSION]);
 | 
						|
		fprintf(fp, " Version: 0x%x ",*v);
 | 
						|
		ctrl_v = *v;
 | 
						|
	}
 | 
						|
	if (tb[CTRL_ATTR_HDRSIZE]) {
 | 
						|
		__u32 *h = RTA_DATA(tb[CTRL_ATTR_HDRSIZE]);
 | 
						|
		fprintf(fp, " header size: %d ",*h);
 | 
						|
	}
 | 
						|
	if (tb[CTRL_ATTR_MAXATTR]) {
 | 
						|
		__u32 *ma = RTA_DATA(tb[CTRL_ATTR_MAXATTR]);
 | 
						|
		fprintf(fp, " max attribs: %d ",*ma);
 | 
						|
	}
 | 
						|
	/* end of family definitions .. */
 | 
						|
	fprintf(fp,"\n");
 | 
						|
	if (tb[CTRL_ATTR_OPS]) {
 | 
						|
		struct rtattr *tb2[GENL_MAX_FAM_OPS];
 | 
						|
		int i=0;
 | 
						|
		parse_rtattr_nested(tb2, GENL_MAX_FAM_OPS, tb[CTRL_ATTR_OPS]);
 | 
						|
		fprintf(fp, "\tcommands supported: \n");
 | 
						|
		for (i = 0; i < GENL_MAX_FAM_OPS; i++) {
 | 
						|
			if (tb2[i]) {
 | 
						|
				fprintf(fp, "\t\t#%d: ", i);
 | 
						|
				if (0 > print_ctrl_cmds(fp, tb2[i], ctrl_v)) {
 | 
						|
					fprintf(fp, "Error printing command\n");
 | 
						|
				}
 | 
						|
				/* for next command */
 | 
						|
				fprintf(fp,"\n");
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/* end of family::cmds definitions .. */
 | 
						|
		fprintf(fp,"\n");
 | 
						|
	}
 | 
						|
 | 
						|
	if (tb[CTRL_ATTR_MCAST_GROUPS]) {
 | 
						|
		struct rtattr *tb2[GENL_MAX_FAM_GRPS + 1];
 | 
						|
		int i;
 | 
						|
 | 
						|
		parse_rtattr_nested(tb2, GENL_MAX_FAM_GRPS,
 | 
						|
				    tb[CTRL_ATTR_MCAST_GROUPS]);
 | 
						|
		fprintf(fp, "\tmulticast groups:\n");
 | 
						|
 | 
						|
		for (i = 0; i < GENL_MAX_FAM_GRPS; i++) {
 | 
						|
			if (tb2[i]) {
 | 
						|
				fprintf(fp, "\t\t#%d: ", i);
 | 
						|
				if (0 > print_ctrl_grp(fp, tb2[i], ctrl_v))
 | 
						|
					fprintf(fp, "Error printing group\n");
 | 
						|
				/* for next group */
 | 
						|
				fprintf(fp,"\n");
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/* end of family::groups definitions .. */
 | 
						|
		fprintf(fp,"\n");
 | 
						|
	}
 | 
						|
 | 
						|
	fflush(fp);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int print_ctrl2(const struct sockaddr_nl *who,
 | 
						|
		      struct nlmsghdr *n, void *arg)
 | 
						|
{
 | 
						|
	return print_ctrl(who, NULL, n, arg);
 | 
						|
}
 | 
						|
 | 
						|
static int ctrl_list(int cmd, int argc, char **argv)
 | 
						|
{
 | 
						|
	struct rtnl_handle rth;
 | 
						|
	int ret = -1;
 | 
						|
	char d[GENL_NAMSIZ];
 | 
						|
	struct {
 | 
						|
		struct nlmsghdr         n;
 | 
						|
		struct genlmsghdr	g;
 | 
						|
		char                    buf[4096];
 | 
						|
	} req = {
 | 
						|
		.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN),
 | 
						|
		.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
 | 
						|
		.n.nlmsg_type = GENL_ID_CTRL,
 | 
						|
		.g.cmd = CTRL_CMD_GETFAMILY,
 | 
						|
	};
 | 
						|
	struct nlmsghdr *nlh = &req.n;
 | 
						|
	struct nlmsghdr *answer = NULL;
 | 
						|
 | 
						|
	if (rtnl_open_byproto(&rth, 0, NETLINK_GENERIC) < 0) {
 | 
						|
		fprintf(stderr, "Cannot open generic netlink socket\n");
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
 | 
						|
	if (cmd == CTRL_CMD_GETFAMILY) {
 | 
						|
		if (argc != 2) {
 | 
						|
			fprintf(stderr, "Wrong number of params\n");
 | 
						|
			return -1;
 | 
						|
		}
 | 
						|
 | 
						|
		if (matches(*argv, "name") == 0) {
 | 
						|
			NEXT_ARG();
 | 
						|
			strlcpy(d, *argv, sizeof(d));
 | 
						|
			addattr_l(nlh, 128, CTRL_ATTR_FAMILY_NAME,
 | 
						|
				  d, strlen(d) + 1);
 | 
						|
		} else if (matches(*argv, "id") == 0) {
 | 
						|
			__u16 id;
 | 
						|
			NEXT_ARG();
 | 
						|
			if (get_u16(&id, *argv, 0)) {
 | 
						|
				fprintf(stderr, "Illegal \"id\"\n");
 | 
						|
				goto ctrl_done;
 | 
						|
			}
 | 
						|
 | 
						|
			addattr_l(nlh, 128, CTRL_ATTR_FAMILY_ID, &id, 2);
 | 
						|
 | 
						|
		} else {
 | 
						|
			fprintf(stderr, "Wrong params\n");
 | 
						|
			goto ctrl_done;
 | 
						|
		}
 | 
						|
 | 
						|
		if (rtnl_talk(&rth, nlh, &answer) < 0) {
 | 
						|
			fprintf(stderr, "Error talking to the kernel\n");
 | 
						|
			goto ctrl_done;
 | 
						|
		}
 | 
						|
 | 
						|
		if (print_ctrl2(NULL, answer, (void *) stdout) < 0) {
 | 
						|
			fprintf(stderr, "Dump terminated\n");
 | 
						|
			goto ctrl_done;
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	if (cmd == CTRL_CMD_UNSPEC) {
 | 
						|
		nlh->nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
 | 
						|
		nlh->nlmsg_seq = rth.dump = ++rth.seq;
 | 
						|
 | 
						|
		if (rtnl_send(&rth, nlh, nlh->nlmsg_len) < 0) {
 | 
						|
			perror("Failed to send dump request\n");
 | 
						|
			goto ctrl_done;
 | 
						|
		}
 | 
						|
 | 
						|
		rtnl_dump_filter(&rth, print_ctrl2, stdout);
 | 
						|
 | 
						|
        }
 | 
						|
 | 
						|
	ret = 0;
 | 
						|
ctrl_done:
 | 
						|
	free(answer);
 | 
						|
	rtnl_close(&rth);
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static int ctrl_listen(int argc, char **argv)
 | 
						|
{
 | 
						|
	struct rtnl_handle rth;
 | 
						|
 | 
						|
	if (rtnl_open_byproto(&rth, nl_mgrp(GENL_ID_CTRL), NETLINK_GENERIC) < 0) {
 | 
						|
		fprintf(stderr, "Canot open generic netlink socket\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (rtnl_listen(&rth, print_ctrl, (void *) stdout) < 0)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int parse_ctrl(struct genl_util *a, int argc, char **argv)
 | 
						|
{
 | 
						|
	argv++;
 | 
						|
	if (--argc <= 0) {
 | 
						|
		fprintf(stderr, "wrong controller params\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (matches(*argv, "monitor") == 0)
 | 
						|
		return ctrl_listen(argc-1, argv+1);
 | 
						|
	if (matches(*argv, "get") == 0)
 | 
						|
		return ctrl_list(CTRL_CMD_GETFAMILY, argc-1, argv+1);
 | 
						|
	if (matches(*argv, "list") == 0 ||
 | 
						|
	    matches(*argv, "show") == 0 ||
 | 
						|
	    matches(*argv, "lst") == 0)
 | 
						|
		return ctrl_list(CTRL_CMD_UNSPEC, argc-1, argv+1);
 | 
						|
	if (matches(*argv, "help") == 0)
 | 
						|
		return usage();
 | 
						|
 | 
						|
	fprintf(stderr, "ctrl command \"%s\" is unknown, try \"ctrl help\".\n",
 | 
						|
		*argv);
 | 
						|
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
struct genl_util ctrl_genl_util = {
 | 
						|
	.name = "ctrl",
 | 
						|
	.parse_genlopt = parse_ctrl,
 | 
						|
	.print_genlopt = print_ctrl2,
 | 
						|
};
 |