1 /* SPDX-License-Identifier: LGPL-2.1-only */
2 /*
3 * Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
4 */
5
6 /**
7 * @ingroup tc
8 * @defgroup classid ClassID Management
9 * @{
10 */
11
12 #include <netlink-private/netlink.h>
13 #include <netlink-private/tc.h>
14 #include <netlink/netlink.h>
15 #include <netlink/utils.h>
16 #include <netlink/route/tc.h>
17
18 struct classid_map
19 {
20 uint32_t classid;
21 char * name;
22 struct nl_list_head name_list;
23 };
24
25 #define CLASSID_NAME_HT_SIZ 256
26
27 static struct nl_list_head tbl_name[CLASSID_NAME_HT_SIZ];
28
29 static void *id_root = NULL;
30
compare_id(const void * pa,const void * pb)31 static int compare_id(const void *pa, const void *pb)
32 {
33 const struct classid_map *ma = pa;
34 const struct classid_map *mb = pb;
35
36 if (ma->classid < mb->classid)
37 return -1;
38
39 if (ma->classid > mb->classid)
40 return 1;
41
42 return 0;
43 }
44
45 /* djb2 */
classid_tbl_hash(const char * str)46 static unsigned int classid_tbl_hash(const char *str)
47 {
48 unsigned long hash = 5381;
49 int c;
50
51 while ((c = *str++))
52 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
53
54 return hash % CLASSID_NAME_HT_SIZ;
55 }
56
classid_lookup(const char * name,uint32_t * result)57 static int classid_lookup(const char *name, uint32_t *result)
58 {
59 struct classid_map *map;
60 int n = classid_tbl_hash(name);
61
62 nl_list_for_each_entry(map, &tbl_name[n], name_list) {
63 if (!strcasecmp(map->name, name)) {
64 *result = map->classid;
65 return 0;
66 }
67 }
68
69 return -NLE_OBJ_NOTFOUND;
70 }
71
name_lookup(const uint32_t classid)72 static char *name_lookup(const uint32_t classid)
73 {
74 void *res;
75 struct classid_map cm = {
76 .classid = classid,
77 .name = "search entry",
78 };
79
80 if ((res = tfind(&cm, &id_root, &compare_id)))
81 return (*(struct classid_map **) res)->name;
82
83 return NULL;
84 }
85
86 /**
87 * @name Traffic Control Handle Translations
88 * @{
89 */
90
91 /**
92 * Convert a traffic control handle to a character string (Reentrant).
93 * @arg handle traffic control handle
94 * @arg buf destination buffer
95 * @arg len buffer length
96 *
97 * Converts a tarffic control handle to a character string in the
98 * form of \c MAJ:MIN and stores it in the specified destination buffer.
99 *
100 * @return The destination buffer or the type encoded in hexidecimal
101 * form if no match was found.
102 */
rtnl_tc_handle2str(uint32_t handle,char * buf,size_t len)103 char *rtnl_tc_handle2str(uint32_t handle, char *buf, size_t len)
104 {
105 if (TC_H_ROOT == handle)
106 snprintf(buf, len, "root");
107 else if (TC_H_UNSPEC == handle)
108 snprintf(buf, len, "none");
109 else if (TC_H_INGRESS == handle)
110 snprintf(buf, len, "ingress");
111 else {
112 char *name;
113
114 if ((name = name_lookup(handle)))
115 snprintf(buf, len, "%s", name);
116 else if (0 == TC_H_MAJ(handle))
117 snprintf(buf, len, ":%x", TC_H_MIN(handle));
118 else if (0 == TC_H_MIN(handle))
119 snprintf(buf, len, "%x:", TC_H_MAJ(handle) >> 16);
120 else
121 snprintf(buf, len, "%x:%x",
122 TC_H_MAJ(handle) >> 16, TC_H_MIN(handle));
123 }
124
125 return buf;
126 }
127
128 /**
129 * Convert a charactering strint to a traffic control handle
130 * @arg str traffic control handle as character string
131 * @arg res destination buffer
132 *
133 * Converts the provided character string specifying a traffic
134 * control handle to the corresponding numeric value.
135 *
136 * The handle must be provided in one of the following formats:
137 * - NAME
138 * - root
139 * - none
140 * - MAJ:
141 * - :MIN
142 * - NAME:MIN
143 * - MAJ:MIN
144 * - MAJMIN
145 *
146 * @return 0 on success or a negative error code
147 */
rtnl_tc_str2handle(const char * str,uint32_t * res)148 int rtnl_tc_str2handle(const char *str, uint32_t *res)
149 {
150 char *colon, *end;
151 uint32_t h;
152 int err;
153
154 if (!strcasecmp(str, "root")) {
155 *res = TC_H_ROOT;
156 return 0;
157 }
158
159 if (!strcasecmp(str, "none")) {
160 *res = TC_H_UNSPEC;
161 return 0;
162 }
163
164 if (!strcasecmp(str, "ingress")) {
165 *res = TC_H_INGRESS;
166 return 0;
167 }
168
169 h = strtoul(str, &colon, 16);
170
171 /* MAJ is not a number */
172 if (colon == str) {
173 not_a_number:
174 if (*colon == ':') {
175 /* :YYYY */
176 h = 0;
177 } else {
178 size_t len;
179 char name[64] = { 0 };
180
181 if (!(colon = strpbrk(str, ":"))) {
182 /* NAME */
183 return classid_lookup(str, res);
184 } else {
185 /* NAME:YYYY */
186 len = colon - str;
187 if (len >= sizeof(name))
188 return -NLE_INVAL;
189
190 memcpy(name, str, len);
191
192 if ((err = classid_lookup(name, &h)) < 0)
193 return err;
194
195 /* Name must point to a qdisc alias */
196 if (TC_H_MIN(h))
197 return -NLE_INVAL;
198
199 /* NAME: is not allowed */
200 if (colon[1] == '\0')
201 return -NLE_INVAL;
202
203 goto update;
204 }
205 }
206 }
207
208 if (':' == *colon) {
209 /* check if we would lose bits */
210 if (TC_H_MAJ(h))
211 return -NLE_RANGE;
212 h <<= 16;
213
214 if ('\0' == colon[1]) {
215 /* XXXX: */
216 *res = h;
217 } else {
218 /* XXXX:YYYY */
219 uint32_t l;
220
221 update:
222 l = strtoul(colon+1, &end, 16);
223
224 /* check if we overlap with major part */
225 if (TC_H_MAJ(l))
226 return -NLE_RANGE;
227
228 if ('\0' != *end)
229 return -NLE_INVAL;
230
231 *res = (h | l);
232 }
233 } else if ('\0' == *colon) {
234 /* XXXXYYYY */
235 *res = h;
236 } else
237 goto not_a_number;
238
239 return 0;
240 }
241
free_nothing(void * arg)242 static void free_nothing(void *arg)
243 {
244 }
245
classid_map_free(struct classid_map * map)246 static void classid_map_free(struct classid_map *map)
247 {
248 if (!map)
249 return;
250
251 free(map->name);
252 free(map);
253 }
254
clear_hashtable(void)255 static void clear_hashtable(void)
256 {
257 int i;
258
259 for (i = 0; i < CLASSID_NAME_HT_SIZ; i++) {
260 struct classid_map *map, *n;
261
262 nl_list_for_each_entry_safe(map, n, &tbl_name[i], name_list)
263 classid_map_free(map);
264
265 nl_init_list_head(&tbl_name[i]);
266
267 }
268
269 if (id_root) {
270 tdestroy(&id_root, &free_nothing);
271 id_root = NULL;
272 }
273 }
274
classid_map_add(uint32_t classid,const char * name)275 static int classid_map_add(uint32_t classid, const char *name)
276 {
277 struct classid_map *map;
278 int n;
279
280 if (!(map = calloc(1, sizeof(*map))))
281 return -NLE_NOMEM;
282
283 map->classid = classid;
284 map->name = strdup(name);
285
286 n = classid_tbl_hash(map->name);
287 nl_list_add_tail(&map->name_list, &tbl_name[n]);
288
289 if (!tsearch((void *) map, &id_root, &compare_id)) {
290 classid_map_free(map);
291 return -NLE_NOMEM;
292 }
293
294 return 0;
295 }
296
297 /**
298 * (Re-)read classid file
299 *
300 * Rereads the contents of the classid file (typically found at the location
301 * /etc/libnl/classid) and refreshes the classid maps.
302 *
303 * @return 0 on success or a negative error code.
304 */
rtnl_tc_read_classid_file(void)305 int rtnl_tc_read_classid_file(void)
306 {
307 static time_t last_read;
308 struct stat st;
309 char buf[256], *path;
310 FILE *fd;
311 int err;
312
313 if (build_sysconf_path(&path, "classid") < 0)
314 return -NLE_NOMEM;
315
316 /* if stat fails, just (re-)read the file */
317 if (stat(path, &st) == 0) {
318 /* Don't re-read file if file is unchanged */
319 if (last_read == st.st_mtime) {
320 err = 0;
321 goto errout;
322 }
323 }
324
325 if (!(fd = fopen(path, "re"))) {
326 err = -nl_syserr2nlerr(errno);
327 goto errout;
328 }
329
330 clear_hashtable();
331
332 while (fgets(buf, sizeof(buf), fd)) {
333 uint32_t classid;
334 char *ptr, *tok;
335
336 /* ignore comments and empty lines */
337 if (*buf == '#' || *buf == '\n' || *buf == '\r')
338 continue;
339
340 /* token 1 */
341 if (!(tok = strtok_r(buf, " \t", &ptr))) {
342 err = -NLE_INVAL;
343 goto errout_close;
344 }
345
346 if ((err = rtnl_tc_str2handle(tok, &classid)) < 0)
347 goto errout_close;
348
349 if (!(tok = strtok_r(NULL, " \t\n\r#", &ptr))) {
350 err = -NLE_INVAL;
351 goto errout_close;
352 }
353
354 if ((err = classid_map_add(classid, tok)) < 0)
355 goto errout_close;
356 }
357
358 err = 0;
359 last_read = st.st_mtime;
360
361 errout_close:
362 fclose(fd);
363 errout:
364 free(path);
365
366 return err;
367
368 }
369
rtnl_classid_generate(const char * name,uint32_t * result,uint32_t parent)370 int rtnl_classid_generate(const char *name, uint32_t *result, uint32_t parent)
371 {
372 static uint32_t base = 0x4000 << 16;
373 uint32_t classid;
374 char *path;
375 FILE *fd;
376 int err = 0;
377
378 if (parent == TC_H_ROOT || parent == TC_H_INGRESS) {
379 do {
380 base += (1 << 16);
381 if (base == TC_H_MAJ(TC_H_ROOT))
382 base = 0x4000 << 16;
383 } while (name_lookup(base));
384
385 classid = base;
386 } else {
387 classid = TC_H_MAJ(parent);
388 do {
389 if (TC_H_MIN(++classid) == TC_H_MIN(TC_H_ROOT))
390 return -NLE_RANGE;
391 } while (name_lookup(classid));
392 }
393
394 NL_DBG(2, "Generated new classid %#x\n", classid);
395
396 if (build_sysconf_path(&path, "classid") < 0)
397 return -NLE_NOMEM;
398
399 if (!(fd = fopen(path, "ae"))) {
400 err = -nl_syserr2nlerr(errno);
401 goto errout;
402 }
403
404 fprintf(fd, "%x:", TC_H_MAJ(classid) >> 16);
405 if (TC_H_MIN(classid))
406 fprintf(fd, "%x", TC_H_MIN(classid));
407 fprintf(fd, "\t\t\t%s\n", name);
408
409 fclose(fd);
410
411 if (classid_map_add(classid, name) < 0) {
412 /*
413 * Error adding classid map, re-read classid file is best
414 * option here. It is likely to fail as well but better
415 * than nothing, entry was added to the file already anyway.
416 */
417 rtnl_tc_read_classid_file();
418 }
419
420 *result = classid;
421 err = 0;
422 errout:
423 free(path);
424
425 return err;
426 }
427
428 /** @} */
429
classid_init(void)430 static void __init classid_init(void)
431 {
432 int err, i;
433
434 for (i = 0; i < CLASSID_NAME_HT_SIZ; i++)
435 nl_init_list_head(&tbl_name[i]);
436
437 if ((err = rtnl_tc_read_classid_file()) < 0)
438 NL_DBG(1, "Failed to read classid file: %s\n", nl_geterror(err));
439 }
440
free_map(void * map)441 static void free_map(void *map)
442 {
443 free(((struct classid_map *)map)->name);
444 free(map);
445 }
446
classid_exit(void)447 static void __exit classid_exit(void)
448 {
449 tdestroy(id_root, free_map);
450 }
451 /** @} */
452