| /* SPDX-License-Identifier: LGPL-2.1-only */ |
| /* |
| * Copyright (c) 2010-2013 Thomas Graf <[email protected]> |
| */ |
| |
| /** |
| * @ingroup tc |
| * @defgroup classid ClassID Management |
| * @{ |
| */ |
| |
| #include "nl-default.h" |
| |
| #include <sys/stat.h> |
| #include <search.h> |
| |
| #include <netlink/netlink.h> |
| #include <netlink/utils.h> |
| #include <netlink/route/tc.h> |
| |
| #include "nl-route.h" |
| #include "nl-aux-core/nl-core.h" |
| |
| struct classid_map |
| { |
| uint32_t classid; |
| char * name; |
| struct nl_list_head name_list; |
| }; |
| |
| #define CLASSID_NAME_HT_SIZ 256 |
| |
| static struct nl_list_head tbl_name[CLASSID_NAME_HT_SIZ]; |
| |
| static void *id_root = NULL; |
| |
| static int compare_id(const void *pa, const void *pb) |
| { |
| const struct classid_map *ma = pa; |
| const struct classid_map *mb = pb; |
| |
| if (ma->classid < mb->classid) |
| return -1; |
| |
| if (ma->classid > mb->classid) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* djb2 */ |
| static unsigned int classid_tbl_hash(const char *str) |
| { |
| unsigned long hash = 5381; |
| int c; |
| |
| while ((c = *str++)) |
| hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ |
| |
| return hash % CLASSID_NAME_HT_SIZ; |
| } |
| |
| static int classid_lookup(const char *name, uint32_t *result) |
| { |
| struct classid_map *map; |
| int n = classid_tbl_hash(name); |
| |
| nl_list_for_each_entry(map, &tbl_name[n], name_list) { |
| if (!strcasecmp(map->name, name)) { |
| *result = map->classid; |
| return 0; |
| } |
| } |
| |
| return -NLE_OBJ_NOTFOUND; |
| } |
| |
| static char *name_lookup(const uint32_t classid) |
| { |
| void *res; |
| struct classid_map cm = { |
| .classid = classid, |
| .name = "search entry", |
| }; |
| |
| if ((res = tfind(&cm, &id_root, &compare_id))) |
| return (*(struct classid_map **) res)->name; |
| |
| return NULL; |
| } |
| |
| /** |
| * @name Traffic Control Handle Translations |
| * @{ |
| */ |
| |
| /** |
| * Convert a traffic control handle to a character string (Reentrant). |
| * @arg handle traffic control handle |
| * @arg buf destination buffer |
| * @arg len buffer length |
| * |
| * Converts a tarffic control handle to a character string in the |
| * form of \c MAJ:MIN and stores it in the specified destination buffer. |
| * |
| * @return The destination buffer or the type encoded in hexidecimal |
| * form if no match was found. |
| */ |
| char *rtnl_tc_handle2str(uint32_t handle, char *buf, size_t len) |
| { |
| if (TC_H_ROOT == handle) |
| snprintf(buf, len, "root"); |
| else if (TC_H_UNSPEC == handle) |
| snprintf(buf, len, "none"); |
| else if (TC_H_INGRESS == handle) |
| snprintf(buf, len, "ingress"); |
| else { |
| char *name; |
| |
| if ((name = name_lookup(handle))) |
| snprintf(buf, len, "%s", name); |
| else if (0 == TC_H_MAJ(handle)) |
| snprintf(buf, len, ":%x", TC_H_MIN(handle)); |
| else if (0 == TC_H_MIN(handle)) |
| snprintf(buf, len, "%x:", TC_H_MAJ(handle) >> 16); |
| else |
| snprintf(buf, len, "%x:%x", |
| TC_H_MAJ(handle) >> 16, TC_H_MIN(handle)); |
| } |
| |
| return buf; |
| } |
| |
| /** |
| * Convert a charactering strint to a traffic control handle |
| * @arg str traffic control handle as character string |
| * @arg res destination buffer |
| * |
| * Converts the provided character string specifying a traffic |
| * control handle to the corresponding numeric value. |
| * |
| * The handle must be provided in one of the following formats: |
| * - NAME |
| * - root |
| * - none |
| * - MAJ: |
| * - :MIN |
| * - NAME:MIN |
| * - MAJ:MIN |
| * - MAJMIN |
| * |
| * @return 0 on success or a negative error code |
| */ |
| int rtnl_tc_str2handle(const char *str, uint32_t *res) |
| { |
| char *colon, *end; |
| uint32_t h; |
| int err; |
| |
| if (!strcasecmp(str, "root")) { |
| *res = TC_H_ROOT; |
| return 0; |
| } |
| |
| if (!strcasecmp(str, "none")) { |
| *res = TC_H_UNSPEC; |
| return 0; |
| } |
| |
| if (!strcasecmp(str, "ingress")) { |
| *res = TC_H_INGRESS; |
| return 0; |
| } |
| |
| h = strtoul(str, &colon, 16); |
| |
| /* MAJ is not a number */ |
| if (colon == str) { |
| not_a_number: |
| if (*colon == ':') { |
| /* :YYYY */ |
| h = 0; |
| } else { |
| size_t len; |
| char name[64] = { 0 }; |
| |
| if (!(colon = strpbrk(str, ":"))) { |
| /* NAME */ |
| return classid_lookup(str, res); |
| } else { |
| /* NAME:YYYY */ |
| len = colon - str; |
| if (len >= sizeof(name)) |
| return -NLE_INVAL; |
| |
| memcpy(name, str, len); |
| |
| if ((err = classid_lookup(name, &h)) < 0) |
| return err; |
| |
| /* Name must point to a qdisc alias */ |
| if (TC_H_MIN(h)) |
| return -NLE_INVAL; |
| |
| /* NAME: is not allowed */ |
| if (colon[1] == '\0') |
| return -NLE_INVAL; |
| |
| goto update; |
| } |
| } |
| } |
| |
| if (':' == *colon) { |
| /* check if we would lose bits */ |
| if (TC_H_MAJ(h)) |
| return -NLE_RANGE; |
| h <<= 16; |
| |
| if ('\0' == colon[1]) { |
| /* XXXX: */ |
| *res = h; |
| } else { |
| /* XXXX:YYYY */ |
| uint32_t l; |
| |
| update: |
| l = strtoul(colon+1, &end, 16); |
| |
| /* check if we overlap with major part */ |
| if (TC_H_MAJ(l)) |
| return -NLE_RANGE; |
| |
| if ('\0' != *end) |
| return -NLE_INVAL; |
| |
| *res = (h | l); |
| } |
| } else if ('\0' == *colon) { |
| /* XXXXYYYY */ |
| *res = h; |
| } else |
| goto not_a_number; |
| |
| return 0; |
| } |
| |
| static void free_nothing(void *arg) |
| { |
| } |
| |
| static void classid_map_free(struct classid_map *map) |
| { |
| if (!map) |
| return; |
| |
| free(map->name); |
| free(map); |
| } |
| |
| static void clear_hashtable(void) |
| { |
| int i; |
| |
| for (i = 0; i < CLASSID_NAME_HT_SIZ; i++) { |
| struct classid_map *map, *n; |
| |
| nl_list_for_each_entry_safe(map, n, &tbl_name[i], name_list) |
| classid_map_free(map); |
| |
| nl_init_list_head(&tbl_name[i]); |
| |
| } |
| |
| if (id_root) { |
| tdestroy(&id_root, &free_nothing); |
| id_root = NULL; |
| } |
| } |
| |
| static int classid_map_add(uint32_t classid, const char *name) |
| { |
| struct classid_map *map; |
| int n; |
| |
| if (!(map = calloc(1, sizeof(*map)))) |
| return -NLE_NOMEM; |
| |
| map->classid = classid; |
| map->name = strdup(name); |
| |
| n = classid_tbl_hash(map->name); |
| nl_list_add_tail(&map->name_list, &tbl_name[n]); |
| |
| if (!tsearch((void *) map, &id_root, &compare_id)) { |
| classid_map_free(map); |
| return -NLE_NOMEM; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * (Re-)read classid file |
| * |
| * Rereads the contents of the classid file (typically found at the location |
| * /etc/libnl/classid) and refreshes the classid maps. |
| * |
| * @return 0 on success or a negative error code. |
| */ |
| int rtnl_tc_read_classid_file(void) |
| { |
| static time_t last_read; |
| struct stat st; |
| char buf[256], *path; |
| FILE *fd; |
| int err; |
| |
| if (build_sysconf_path(&path, "classid") < 0) |
| return -NLE_NOMEM; |
| |
| /* if stat fails, just (re-)read the file */ |
| if (stat(path, &st) == 0) { |
| /* Don't re-read file if file is unchanged */ |
| if (last_read == st.st_mtime) { |
| err = 0; |
| goto errout; |
| } |
| } |
| |
| if (!(fd = fopen(path, "re"))) { |
| err = -nl_syserr2nlerr(errno); |
| goto errout; |
| } |
| |
| clear_hashtable(); |
| |
| while (fgets(buf, sizeof(buf), fd)) { |
| uint32_t classid; |
| char *ptr, *tok; |
| |
| /* ignore comments and empty lines */ |
| if (*buf == '#' || *buf == '\n' || *buf == '\r') |
| continue; |
| |
| /* token 1 */ |
| if (!(tok = strtok_r(buf, " \t", &ptr))) { |
| err = -NLE_INVAL; |
| goto errout_close; |
| } |
| |
| if ((err = rtnl_tc_str2handle(tok, &classid)) < 0) |
| goto errout_close; |
| |
| if (!(tok = strtok_r(NULL, " \t\n\r#", &ptr))) { |
| err = -NLE_INVAL; |
| goto errout_close; |
| } |
| |
| if ((err = classid_map_add(classid, tok)) < 0) |
| goto errout_close; |
| } |
| |
| err = 0; |
| last_read = st.st_mtime; |
| |
| errout_close: |
| fclose(fd); |
| errout: |
| free(path); |
| |
| return err; |
| |
| } |
| |
| int rtnl_classid_generate(const char *name, uint32_t *result, uint32_t parent) |
| { |
| static uint32_t base = 0x4000 << 16; |
| uint32_t classid; |
| char *path; |
| FILE *fd; |
| int err = 0; |
| |
| if (parent == TC_H_ROOT || parent == TC_H_INGRESS) { |
| do { |
| base += (1 << 16); |
| if (base == TC_H_MAJ(TC_H_ROOT)) |
| base = 0x4000 << 16; |
| } while (name_lookup(base)); |
| |
| classid = base; |
| } else { |
| classid = TC_H_MAJ(parent); |
| do { |
| if (TC_H_MIN(++classid) == TC_H_MIN(TC_H_ROOT)) |
| return -NLE_RANGE; |
| } while (name_lookup(classid)); |
| } |
| |
| NL_DBG(2, "Generated new classid %#x\n", classid); |
| |
| if (build_sysconf_path(&path, "classid") < 0) |
| return -NLE_NOMEM; |
| |
| if (!(fd = fopen(path, "ae"))) { |
| err = -nl_syserr2nlerr(errno); |
| goto errout; |
| } |
| |
| fprintf(fd, "%x:", TC_H_MAJ(classid) >> 16); |
| if (TC_H_MIN(classid)) |
| fprintf(fd, "%x", TC_H_MIN(classid)); |
| fprintf(fd, "\t\t\t%s\n", name); |
| |
| fclose(fd); |
| |
| if (classid_map_add(classid, name) < 0) { |
| /* |
| * Error adding classid map, re-read classid file is best |
| * option here. It is likely to fail as well but better |
| * than nothing, entry was added to the file already anyway. |
| */ |
| rtnl_tc_read_classid_file(); |
| } |
| |
| *result = classid; |
| err = 0; |
| errout: |
| free(path); |
| |
| return err; |
| } |
| |
| /** @} */ |
| |
| static void _nl_init classid_init(void) |
| { |
| int err, i; |
| |
| for (i = 0; i < CLASSID_NAME_HT_SIZ; i++) |
| nl_init_list_head(&tbl_name[i]); |
| |
| if ((err = rtnl_tc_read_classid_file()) < 0) |
| NL_DBG(1, "Failed to read classid file: %s\n", nl_geterror(err)); |
| } |
| |
| static void free_map(void *map) |
| { |
| free(((struct classid_map *)map)->name); |
| free(map); |
| } |
| |
| static void _nl_exit classid_exit(void) |
| { |
| tdestroy(id_root, free_map); |
| } |
| /** @} */ |