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