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