• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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