Home | History | Annotate | Download | only in route
      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 (at) 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 
     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 */
     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 
     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 
     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  */
    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  */
    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 
    248 static void free_nothing(void *arg)
    249 {
    250 }
    251 
    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 
    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 
    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  */
    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 
    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 
    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 
    447 static void free_map(void *map) {
    448 	free(((struct classid_map *)map)->name);
    449 	free(map);
    450 };
    451 
    452 static void __exit classid_exit(void)
    453 {
    454 	tdestroy(id_root, free_map);
    455 }
    456 /** @} */
    457