1 /*
2 * strset.c - string set handling
3 *
4 * Implementation of local cache of ethtool string sets.
5 */
6
7 #include <errno.h>
8 #include <string.h>
9
10 #include "../internal.h"
11 #include "netlink.h"
12 #include "nlsock.h"
13 #include "msgbuff.h"
14
15 struct stringset {
16 const char **strings;
17 void *raw_data;
18 unsigned int count;
19 bool loaded;
20 };
21
22 struct perdev_strings {
23 int ifindex;
24 char devname[ALTIFNAMSIZ];
25 struct stringset strings[ETH_SS_COUNT];
26 struct perdev_strings *next;
27 };
28
29 /* universal string sets */
30 static struct stringset global_strings[ETH_SS_COUNT];
31 /* linked list of string sets related to network devices */
32 static struct perdev_strings *device_strings;
33
drop_stringset(struct stringset * set)34 static void drop_stringset(struct stringset *set)
35 {
36 if (!set)
37 return;
38
39 free(set->strings);
40 free(set->raw_data);
41 memset(set, 0, sizeof(*set));
42 }
43
import_stringset(struct stringset * dest,const struct nlattr * nest)44 static int import_stringset(struct stringset *dest, const struct nlattr *nest)
45 {
46 const struct nlattr *tb_stringset[ETHTOOL_A_STRINGSET_MAX + 1] = {};
47 DECLARE_ATTR_TB_INFO(tb_stringset);
48 const struct nlattr *string;
49 unsigned int size;
50 unsigned int count;
51 unsigned int idx;
52 int ret;
53
54 ret = mnl_attr_parse_nested(nest, attr_cb, &tb_stringset_info);
55 if (ret < 0)
56 return ret;
57 if (!tb_stringset[ETHTOOL_A_STRINGSET_ID] ||
58 !tb_stringset[ETHTOOL_A_STRINGSET_COUNT] ||
59 !tb_stringset[ETHTOOL_A_STRINGSET_STRINGS])
60 return -EFAULT;
61 idx = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_ID]);
62 if (idx >= ETH_SS_COUNT)
63 return 0;
64 if (dest[idx].loaded)
65 drop_stringset(&dest[idx]);
66 dest[idx].loaded = true;
67 count = mnl_attr_get_u32(tb_stringset[ETHTOOL_A_STRINGSET_COUNT]);
68 if (count == 0)
69 return 0;
70
71 size = mnl_attr_get_len(tb_stringset[ETHTOOL_A_STRINGSET_STRINGS]);
72 ret = -ENOMEM;
73 dest[idx].raw_data = malloc(size);
74 if (!dest[idx].raw_data)
75 goto err;
76 memcpy(dest[idx].raw_data, tb_stringset[ETHTOOL_A_STRINGSET_STRINGS],
77 size);
78 dest[idx].strings = calloc(count, sizeof(dest[idx].strings[0]));
79 if (!dest[idx].strings)
80 goto err;
81 dest[idx].count = count;
82
83 nest = dest[idx].raw_data;
84 mnl_attr_for_each_nested(string, nest) {
85 const struct nlattr *tb[ETHTOOL_A_STRING_MAX + 1] = {};
86 DECLARE_ATTR_TB_INFO(tb);
87 unsigned int i;
88
89 if (mnl_attr_get_type(string) != ETHTOOL_A_STRINGS_STRING)
90 continue;
91 ret = mnl_attr_parse_nested(string, attr_cb, &tb_info);
92 if (ret < 0)
93 goto err;
94 ret = -EFAULT;
95 if (!tb[ETHTOOL_A_STRING_INDEX] || !tb[ETHTOOL_A_STRING_VALUE])
96 goto err;
97
98 i = mnl_attr_get_u32(tb[ETHTOOL_A_STRING_INDEX]);
99 if (i >= count)
100 goto err;
101 dest[idx].strings[i] =
102 mnl_attr_get_payload(tb[ETHTOOL_A_STRING_VALUE]);
103 }
104
105 return 0;
106 err:
107 drop_stringset(&dest[idx]);
108 return ret;
109 }
110
get_perdev_by_ifindex(int ifindex)111 static struct perdev_strings *get_perdev_by_ifindex(int ifindex)
112 {
113 struct perdev_strings *perdev = device_strings;
114
115 while (perdev && perdev->ifindex != ifindex)
116 perdev = perdev->next;
117 if (perdev)
118 return perdev;
119
120 /* not found, allocate and insert into list */
121 perdev = calloc(sizeof(*perdev), 1);
122 if (!perdev)
123 return NULL;
124 perdev->ifindex = ifindex;
125 perdev->next = device_strings;
126 device_strings = perdev;
127
128 return perdev;
129 }
130
strset_reply_cb(const struct nlmsghdr * nlhdr,void * data)131 static int strset_reply_cb(const struct nlmsghdr *nlhdr, void *data)
132 {
133 const struct nlattr *tb[ETHTOOL_A_STRSET_MAX + 1] = {};
134 DECLARE_ATTR_TB_INFO(tb);
135 struct nl_context *nlctx = data;
136 char devname[ALTIFNAMSIZ] = "";
137 struct stringset *dest;
138 struct nlattr *attr;
139 int ifindex = 0;
140 int ret;
141
142 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
143 if (ret < 0)
144 return ret;
145 if (tb[ETHTOOL_A_STRSET_HEADER]) {
146 ret = get_dev_info(tb[ETHTOOL_A_STRSET_HEADER], &ifindex,
147 devname);
148 if (ret < 0)
149 return MNL_CB_OK;
150 if (ifindex && nlctx->filter_devname &&
151 strncmp(devname, nlctx->filter_devname, ALTIFNAMSIZ))
152 return MNL_CB_OK;
153 }
154
155 if (ifindex) {
156 struct perdev_strings *perdev;
157
158 perdev = get_perdev_by_ifindex(ifindex);
159 if (!perdev)
160 return MNL_CB_OK;
161 copy_devname(perdev->devname, devname);
162 dest = perdev->strings;
163 } else {
164 dest = global_strings;
165 }
166
167 if (!tb[ETHTOOL_A_STRSET_STRINGSETS])
168 return MNL_CB_OK;
169 mnl_attr_for_each_nested(attr, tb[ETHTOOL_A_STRSET_STRINGSETS]) {
170 if (mnl_attr_get_type(attr) ==
171 ETHTOOL_A_STRINGSETS_STRINGSET)
172 import_stringset(dest, attr);
173 }
174
175 return MNL_CB_OK;
176 }
177
fill_stringset_id(struct nl_msg_buff * msgbuff,unsigned int type)178 static int fill_stringset_id(struct nl_msg_buff *msgbuff, unsigned int type)
179 {
180 struct nlattr *nest_sets;
181 struct nlattr *nest_set;
182
183 nest_sets = ethnla_nest_start(msgbuff, ETHTOOL_A_STRSET_STRINGSETS);
184 if (!nest_sets)
185 return -EMSGSIZE;
186 nest_set = ethnla_nest_start(msgbuff, ETHTOOL_A_STRINGSETS_STRINGSET);
187 if (!nest_set)
188 goto err;
189 if (ethnla_put_u32(msgbuff, ETHTOOL_A_STRINGSET_ID, type))
190 goto err;
191 ethnla_nest_end(msgbuff, nest_set);
192 ethnla_nest_end(msgbuff, nest_sets);
193 return 0;
194
195 err:
196 ethnla_nest_cancel(msgbuff, nest_sets);
197 return -EMSGSIZE;
198 }
199
stringset_load_request(struct nl_socket * nlsk,const char * devname,int type,bool is_dump)200 static int stringset_load_request(struct nl_socket *nlsk, const char *devname,
201 int type, bool is_dump)
202 {
203 struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
204 int ret;
205
206 ret = msg_init(nlsk->nlctx, msgbuff, ETHTOOL_MSG_STRSET_GET,
207 NLM_F_REQUEST | NLM_F_ACK | (is_dump ? NLM_F_DUMP : 0));
208 if (ret < 0)
209 return ret;
210 if (ethnla_fill_header(msgbuff, ETHTOOL_A_STRSET_HEADER, devname, 0))
211 return -EMSGSIZE;
212 if (type >= 0) {
213 ret = fill_stringset_id(msgbuff, type);
214 if (ret < 0)
215 return ret;
216 }
217
218 ret = nlsock_send_get_request(nlsk, strset_reply_cb);
219 return ret;
220 }
221
222 /* interface */
223
global_stringset(unsigned int type,struct nl_socket * nlsk)224 const struct stringset *global_stringset(unsigned int type,
225 struct nl_socket *nlsk)
226 {
227 int ret;
228
229 if (type >= ETH_SS_COUNT)
230 return NULL;
231 if (global_strings[type].loaded)
232 return &global_strings[type];
233 ret = stringset_load_request(nlsk, NULL, type, false);
234 return ret < 0 ? NULL : &global_strings[type];
235 }
236
perdev_stringset(const char * devname,unsigned int type,struct nl_socket * nlsk)237 const struct stringset *perdev_stringset(const char *devname, unsigned int type,
238 struct nl_socket *nlsk)
239 {
240 const struct perdev_strings *p;
241 int ret;
242
243 if (type >= ETH_SS_COUNT)
244 return NULL;
245 for (p = device_strings; p; p = p->next)
246 if (!strcmp(p->devname, devname))
247 return &p->strings[type];
248
249 ret = stringset_load_request(nlsk, devname, type, false);
250 if (ret < 0)
251 return NULL;
252 for (p = device_strings; p; p = p->next)
253 if (!strcmp(p->devname, devname))
254 return &p->strings[type];
255
256 return NULL;
257 }
258
get_count(const struct stringset * set)259 unsigned int get_count(const struct stringset *set)
260 {
261 return set->count;
262 }
263
get_string(const struct stringset * set,unsigned int idx)264 const char *get_string(const struct stringset *set, unsigned int idx)
265 {
266 if (!set || idx >= set->count)
267 return NULL;
268 return set->strings[idx];
269 }
270
preload_global_strings(struct nl_socket * nlsk)271 int preload_global_strings(struct nl_socket *nlsk)
272 {
273 return stringset_load_request(nlsk, NULL, -1, false);
274 }
275
preload_perdev_strings(struct nl_socket * nlsk,const char * dev)276 int preload_perdev_strings(struct nl_socket *nlsk, const char *dev)
277 {
278 return stringset_load_request(nlsk, dev, -1, !dev);
279 }
280
cleanup_all_strings(void)281 void cleanup_all_strings(void)
282 {
283 struct perdev_strings *perdev;
284 unsigned int i;
285
286 for (i = 0; i < ETH_SS_COUNT; i++)
287 drop_stringset(&global_strings[i]);
288
289 perdev = device_strings;
290 while (perdev) {
291 device_strings = perdev->next;
292 for (i = 0; i < ETH_SS_COUNT; i++)
293 drop_stringset(&perdev->strings[i]);
294 free(perdev);
295 perdev = device_strings;
296 }
297 }
298