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