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