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