summaryrefslogtreecommitdiffstats
path: root/lib/libgenl.c
blob: fca07f9fe768affe50c8d07bce63602b64d7e995 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * libgenl.c	GENL library
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <linux/genetlink.h>
#include "libgenl.h"

static int genl_parse_getfamily(struct nlmsghdr *nlh)
{
	struct rtattr *tb[CTRL_ATTR_MAX + 1];
	struct genlmsghdr *ghdr = NLMSG_DATA(nlh);
	int len = nlh->nlmsg_len;
	struct rtattr *attrs;

	if (nlh->nlmsg_type != GENL_ID_CTRL) {
		fprintf(stderr, "Not a controller message, nlmsg_len=%d "
			"nlmsg_type=0x%x\n", nlh->nlmsg_len, nlh->nlmsg_type);
		return -1;
	}

	len -= NLMSG_LENGTH(GENL_HDRLEN);

	if (len < 0) {
		fprintf(stderr, "wrong controller message len %d\n", len);
		return -1;
	}

	if (ghdr->cmd != CTRL_CMD_NEWFAMILY) {
		fprintf(stderr, "Unknown controller command %d\n", ghdr->cmd);
		return -1;
	}

	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);

	if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
		fprintf(stderr, "Missing family id TLV\n");
		return -1;
	}

	return rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]);
}

int genl_resolve_family(struct rtnl_handle *grth, const char *family)
{
	GENL_REQUEST(req, 1024, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY,
		     NLM_F_REQUEST);
	struct nlmsghdr *answer;
	int fnum;

	addattr_l(&req.n, sizeof(req), CTRL_ATTR_FAMILY_NAME,
		  family, strlen(family) + 1);

	if (rtnl_talk(grth, &req.n, &answer) < 0) {
		fprintf(stderr, "Error talking to the kernel\n");
		return -2;
	}

	fnum = genl_parse_getfamily(answer);
	free(answer);

	return fnum;
}

static int genl_parse_grps(struct rtattr *attr, const char *name, unsigned int *id)
{
	const struct rtattr *pos;

	rtattr_for_each_nested(pos, attr) {
		struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1];

		parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, pos);

		if (tb[CTRL_ATTR_MCAST_GRP_NAME] && tb[CTRL_ATTR_MCAST_GRP_ID]) {
			if (strcmp(name, rta_getattr_str(tb[CTRL_ATTR_MCAST_GRP_NAME])) == 0) {
				*id = rta_getattr_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
				return 0;
			}
		}
	}

	errno = ENOENT;
	return -1;
}

int genl_add_mcast_grp(struct rtnl_handle *grth, __u16 fnum, const char *group)
{
	GENL_REQUEST(req, 1024, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY,
		     NLM_F_REQUEST);
	struct rtattr *tb[CTRL_ATTR_MAX + 1];
	struct nlmsghdr *answer = NULL;
	struct genlmsghdr *ghdr;
	struct rtattr *attrs;
	int len, ret = -1;
	unsigned int id;

	addattr16(&req.n, sizeof(req), CTRL_ATTR_FAMILY_ID, fnum);

	if (rtnl_talk(grth, &req.n, &answer) < 0) {
		fprintf(stderr, "Error talking to the kernel\n");
		return -2;
	}

	ghdr = NLMSG_DATA(answer);
	len = answer->nlmsg_len;

	if (answer->nlmsg_type != GENL_ID_CTRL) {
		errno = EINVAL;
		goto err_free;
	}

	len -= NLMSG_LENGTH(GENL_HDRLEN);
	if (len < 0) {
		errno = EINVAL;
		goto err_free;
	}

	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);

	if (tb[CTRL_ATTR_MCAST_GROUPS] == NULL) {
		errno = ENOENT;
		fprintf(stderr, "Missing mcast groups TLV\n");
		goto err_free;
	}

	if (genl_parse_grps(tb[CTRL_ATTR_MCAST_GROUPS], group, &id) < 0)
		goto err_free;

	ret = rtnl_add_nl_group(grth, id);

err_free:
	free(answer);
	return ret;
}

int genl_init_handle(struct rtnl_handle *grth, const char *family,
		     int *genl_family)
{
	if (*genl_family >= 0)
		return 0;

	if (rtnl_open_byproto(grth, 0, NETLINK_GENERIC) < 0) {
		fprintf(stderr, "Cannot open generic netlink socket\n");
		return -1;
	}

	*genl_family = genl_resolve_family(grth, family);
	if (*genl_family < 0)
		return -1;

	return 0;
}