You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
8.7 KiB
459 lines
8.7 KiB
/* SPDX-License-Identifier: LGPL-2.1-only */
|
|
/*
|
|
* lib/route/classid.c ClassID Management
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* Copyright (c) 2010-2013 Thomas Graf <tgraf@suug.ch>
|
|
*/
|
|
|
|
/**
|
|
* @ingroup tc
|
|
* @defgroup classid ClassID Management
|
|
* @{
|
|
*/
|
|
|
|
#include <netlink-private/netlink.h>
|
|
#include <netlink-private/tc.h>
|
|
#include <netlink/netlink.h>
|
|
#include <netlink/utils.h>
|
|
#include <netlink/route/tc.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 ((err = 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 __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 __exit classid_exit(void)
|
|
{
|
|
tdestroy(id_root, free_map);
|
|
}
|
|
/** @} */
|