diff mbox

[iproute2] tc class: Show class names from file

Message ID 1425400878-7608-1-git-send-email-vadim4j@gmail.com
State Accepted, archived
Delegated to: stephen hemminger
Headers show

Commit Message

Vadym Kochan March 3, 2015, 4:41 p.m. UTC
From: Vadim Kochan <vadim4j@gmail.com>

It is possible to use class names from file /etc/iproute2/cls_names
which tc will use when showing class info:

    # tc/tc -nm class show dev lo
	class htb 1:10 parent 1:1 leaf 10: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
	class htb 1:1 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
	class htb web#1:20 parent 1:1 leaf 20: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
	class htb 1:2 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
	class htb 1:30 parent 1:1 leaf 30: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
	class htb voip#1:40 parent 1:2 leaf 40: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
	class htb 1:50 parent 1:2 leaf 50: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
	class htb 1:60 parent 1:2 leaf 60: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b

or to specify via file path:

    # tc/tc -nm -cf /tmp/cls_names class show dev lo

Class names file contains simple "maj:min  name" structure:

1:20    web
1:40    voip

Signed-off-by: Vadim Kochan <vadim4j@gmail.com>
---
 include/names.h |  25 +++++++++
 lib/Makefile    |   3 +-
 lib/names.c     | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tc/tc.c         |  25 ++++++++-
 tc/tc_common.h  |   1 +
 tc/tc_util.c    |  48 ++++++++++++++---
 tc/tc_util.h    |   3 ++
 7 files changed, 252 insertions(+), 9 deletions(-)
 create mode 100644 include/names.h
 create mode 100644 lib/names.c

Comments

Stephen Hemminger March 3, 2015, 5:58 p.m. UTC | #1
On Tue,  3 Mar 2015 18:41:18 +0200
Vadim Kochan <vadim4j@gmail.com> wrote:

> From: Vadim Kochan <vadim4j@gmail.com>
> 
> It is possible to use class names from file /etc/iproute2/cls_names
> which tc will use when showing class info:
> 
>     # tc/tc -nm class show dev lo
> 	class htb 1:10 parent 1:1 leaf 10: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> 	class htb 1:1 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb web#1:20 parent 1:1 leaf 20: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:2 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:30 parent 1:1 leaf 30: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb voip#1:40 parent 1:2 leaf 40: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> 	class htb 1:50 parent 1:2 leaf 50: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:60 parent 1:2 leaf 60: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> 
> or to specify via file path:
> 
>     # tc/tc -nm -cf /tmp/cls_names class show dev lo
> 
> Class names file contains simple "maj:min  name" structure:
> 
> 1:20    web
> 1:40    voip
> 
> Signed-off-by: Vadim Kochan <vadim4j@gmail.com>

This adds new dependency on berkely db.
See how iproute handled that for arpd and use same #ifdef and config.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Vadym Kochan March 3, 2015, 6:52 p.m. UTC | #2
On Tue, Mar 03, 2015 at 09:58:45AM -0800, Stephen Hemminger wrote:
> On Tue,  3 Mar 2015 18:41:18 +0200
> Vadim Kochan <vadim4j@gmail.com> wrote:
> 
> > From: Vadim Kochan <vadim4j@gmail.com>
> > 
> > It is possible to use class names from file /etc/iproute2/cls_names
> > which tc will use when showing class info:
> > 
> >     # tc/tc -nm class show dev lo
> > 	class htb 1:10 parent 1:1 leaf 10: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> > 	class htb 1:1 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> > 	class htb web#1:20 parent 1:1 leaf 20: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> > 	class htb 1:2 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> > 	class htb 1:30 parent 1:1 leaf 30: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> > 	class htb voip#1:40 parent 1:2 leaf 40: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> > 	class htb 1:50 parent 1:2 leaf 50: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> > 	class htb 1:60 parent 1:2 leaf 60: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> > 
> > or to specify via file path:
> > 
> >     # tc/tc -nm -cf /tmp/cls_names class show dev lo
> > 
> > Class names file contains simple "maj:min  name" structure:
> > 
> > 1:20    web
> > 1:40    voip
> > 
> > Signed-off-by: Vadim Kochan <vadim4j@gmail.com>
> 
> This adds new dependency on berkely db.
> See how iproute handled that for arpd and use same #ifdef and config.

Sorry I did not get how this adds the dependency for berkely db ?
Would you please help me to understand the problem ?

Thanks,
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Hemminger March 6, 2015, 12:27 a.m. UTC | #3
On Tue, 3 Mar 2015 20:52:49 +0200
Vadim Kochan <vadim4j@gmail.com> wrote:

> Sorry I did not get how this adds the dependency for berkely db ?
> Would you please help me to understand the problem ?
> 
> Thanks,

Sorry, i was thinking and typing at same time.
I was thinking this would be a good case for using a berkely db for
1000's of classes, but a file works ok.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Vadym Kochan March 6, 2015, 11:06 a.m. UTC | #4
On Thu, Mar 05, 2015 at 04:27:16PM -0800, Stephen Hemminger wrote:
> On Tue, 3 Mar 2015 20:52:49 +0200
> Vadim Kochan <vadim4j@gmail.com> wrote:
> 
> > Sorry I did not get how this adds the dependency for berkely db ?
> > Would you please help me to understand the problem ?
> > 
> > Thanks,
> 
> Sorry, i was thinking and typing at same time.
> I was thinking this would be a good case for using a berkely db for
> 1000's of classes, but a file works ok.

I think that /etc/iproute2/tc_cls might be better name for the default db file.

Also if this feature have chance to be applied I will try to add ability
to use class name to other places - 'tc filter', 'tc class del/change/show'.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Hemminger March 15, 2015, 7:29 p.m. UTC | #5
On Tue,  3 Mar 2015 18:41:18 +0200
Vadim Kochan <vadim4j@gmail.com> wrote:

> From: Vadim Kochan <vadim4j@gmail.com>
> 
> It is possible to use class names from file /etc/iproute2/cls_names
> which tc will use when showing class info:
> 
>     # tc/tc -nm class show dev lo
> 	class htb 1:10 parent 1:1 leaf 10: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> 	class htb 1:1 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb web#1:20 parent 1:1 leaf 20: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:2 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:30 parent 1:1 leaf 30: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb voip#1:40 parent 1:2 leaf 40: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> 	class htb 1:50 parent 1:2 leaf 50: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:60 parent 1:2 leaf 60: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> 
> or to specify via file path:
> 
>     # tc/tc -nm -cf /tmp/cls_names class show dev lo
> 
> Class names file contains simple "maj:min  name" structure:
> 
> 1:20    web
> 1:40    voip
> 
> Signed-off-by: Vadim Kochan <vadim4j@gmail.com>

Applied, thanks

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Daniel Borkmann March 15, 2015, 8:47 p.m. UTC | #6
Hi Vadim,

On 03/03/2015 05:41 PM, Vadim Kochan wrote:
> From: Vadim Kochan <vadim4j@gmail.com>
>
> It is possible to use class names from file /etc/iproute2/cls_names
> which tc will use when showing class info:
>
>      # tc/tc -nm class show dev lo
> 	class htb 1:10 parent 1:1 leaf 10: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> 	class htb 1:1 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb web#1:20 parent 1:1 leaf 20: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:2 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:30 parent 1:1 leaf 30: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb voip#1:40 parent 1:2 leaf 40: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> 	class htb 1:50 parent 1:2 leaf 50: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> 	class htb 1:60 parent 1:2 leaf 60: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
>
> or to specify via file path:
>
>      # tc/tc -nm -cf /tmp/cls_names class show dev lo
>
> Class names file contains simple "maj:min  name" structure:
>
> 1:20    web
> 1:40    voip
>
> Signed-off-by: Vadim Kochan <vadim4j@gmail.com>

in your patch, there are a couple of malloc() calls in your db
creation, that you should check if they actually fail, see i.e.
db_names_alloc(). Would be good if you could follow-up with a fix.

Thanks,
Daniel
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Vadym Kochan March 16, 2015, 1:22 p.m. UTC | #7
On Sun, Mar 15, 2015 at 09:47:00PM +0100, Daniel Borkmann wrote:
> Hi Vadim,
> 
> On 03/03/2015 05:41 PM, Vadim Kochan wrote:
> >From: Vadim Kochan <vadim4j@gmail.com>
> >
> >It is possible to use class names from file /etc/iproute2/cls_names
> >which tc will use when showing class info:
> >
> >     # tc/tc -nm class show dev lo
> >	class htb 1:10 parent 1:1 leaf 10: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> >	class htb 1:1 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> >	class htb web#1:20 parent 1:1 leaf 20: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> >	class htb 1:2 root rate 6Mbit ceil 6Mbit burst 15Kb cburst 1599b
> >	class htb 1:30 parent 1:1 leaf 30: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> >	class htb voip#1:40 parent 1:2 leaf 40: prio 0 rate 5Mbit ceil 5Mbit burst 15Kb cburst 1600b
> >	class htb 1:50 parent 1:2 leaf 50: prio 0 rate 3Mbit ceil 6Mbit burst 15Kb cburst 1599b
> >	class htb 1:60 parent 1:2 leaf 60: prio 0 rate 1Kbit ceil 6Mbit burst 15Kb cburst 1599b
> >
> >or to specify via file path:
> >
> >     # tc/tc -nm -cf /tmp/cls_names class show dev lo
> >
> >Class names file contains simple "maj:min  name" structure:
> >
> >1:20    web
> >1:40    voip
> >
> >Signed-off-by: Vadim Kochan <vadim4j@gmail.com>
> 
> in your patch, there are a couple of malloc() calls in your db
> creation, that you should check if they actually fail, see i.e.
> db_names_alloc(). Would be good if you could follow-up with a fix.
> 
> Thanks,
> Daniel

Hi,

Sure I will do.

Also I was thinking about to get rid of this '-nm | -name' option and
resolve class names by default if they are in /etc/iproute2/cls_names or
specified by -cf option, what do you think ?

Regards,
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Vadym Kochan March 16, 2015, 2:42 p.m. UTC | #8
On Mon, Mar 16, 2015 at 03:46:54PM +0100, Daniel Borkmann wrote:
> On 03/16/2015 02:22 PM, Vadim Kochan wrote:
> ...
> >Also I was thinking about to get rid of this '-nm | -name' option and
> >resolve class names by default if they are in /etc/iproute2/cls_names or
> >specified by -cf option, what do you think ?
> 
> Generally, I think that would be good, but it could break applications
> or shell scripts' expectations if they do parse tc output. I think if
> admins do care, they could simply set up an alias.
> 
> Cheers,
> Daniel

Hm, yes, so may be let this option exist, but then may be it
should not fail if the default file /etc/iproute2/cls_names does not
exist.

Regards,
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Daniel Borkmann March 16, 2015, 2:46 p.m. UTC | #9
On 03/16/2015 02:22 PM, Vadim Kochan wrote:
...
> Also I was thinking about to get rid of this '-nm | -name' option and
> resolve class names by default if they are in /etc/iproute2/cls_names or
> specified by -cf option, what do you think ?

Generally, I think that would be good, but it could break applications
or shell scripts' expectations if they do parse tc output. I think if
admins do care, they could simply set up an alias.

Cheers,
Daniel
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/names.h b/include/names.h
new file mode 100644
index 0000000..4123d0b
--- /dev/null
+++ b/include/names.h
@@ -0,0 +1,25 @@ 
+#ifndef DB_NAMES_H_
+#define DB_NAMES_H_ 1
+
+#define IDNAME_MAX 256
+
+struct db_entry {
+	struct db_entry *next;
+	unsigned int id;
+	char *name;
+};
+
+struct db_names {
+	unsigned int size;
+	struct db_entry *cached;
+	struct db_entry **hash;
+	int max;
+};
+
+struct db_names *db_names_alloc(const char *path);
+void db_names_free(struct db_names *db);
+
+char *id_to_name(struct db_names *db, int id, char *name);
+int name_to_id(struct db_names *db, int *id, const char *name);
+
+#endif
diff --git a/lib/Makefile b/lib/Makefile
index 66f89f1..4c7cbc2 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -6,7 +6,8 @@  endif
 
 CFLAGS += -fPIC
 
-UTILOBJ=utils.o rt_names.o ll_types.o ll_proto.o ll_addr.o inet_proto.o namespace.o
+UTILOBJ=utils.o rt_names.o ll_types.o ll_proto.o ll_addr.o inet_proto.o namespace.o \
+	names.o
 
 NLOBJ=libgenl.o ll_map.o libnetlink.o
 
diff --git a/lib/names.c b/lib/names.c
new file mode 100644
index 0000000..93933f7
--- /dev/null
+++ b/lib/names.c
@@ -0,0 +1,156 @@ 
+/*
+ * names.c		db names
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "names.h"
+
+#define MAX_ENTRIES  256
+#define NAME_MAX_LEN 512
+
+static int read_id_name(FILE *fp, int *id, char *name)
+{
+	char buf[NAME_MAX_LEN];
+	int min, maj;
+
+	while (fgets(buf, sizeof(buf), fp)) {
+		char *p = buf;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+
+		if (*p == '#' || *p == '\n' || *p == 0)
+			continue;
+
+		if (sscanf(p, "%x:%x %s\n", &maj, &min, name) == 3) {
+			*id = (maj << 16) | min;
+		} else if (sscanf(p, "%x:%x %s #", &maj, &min, name) == 3) {
+			*id = (maj << 16) | min;
+		} else if (sscanf(p, "0x%x %s\n", id, name) != 2 &&
+				sscanf(p, "0x%x %s #", id, name) != 2 &&
+				sscanf(p, "%d %s\n", id, name) != 2 &&
+				sscanf(p, "%d %s #", id, name) != 2) {
+			strcpy(name, p);
+			return -1;
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+struct db_names *db_names_alloc(const char *path)
+{
+	struct db_names *db;
+	struct db_entry *entry;
+	FILE *fp;
+	int id;
+	char namebuf[NAME_MAX_LEN] = {0};
+	int ret;
+
+	fp = fopen(path, "r");
+	if (!fp) {
+		fprintf(stderr, "Can't open file: %s\n", path);
+		return NULL;
+	}
+
+	db = malloc(sizeof(*db));
+	memset(db, 0, sizeof(*db));
+
+	db->size = MAX_ENTRIES;
+	db->hash = malloc(sizeof(struct db_entry *) * db->size);
+	memset(db->hash, 0, sizeof(struct db_entry *) * db->size);
+
+	while ((ret = read_id_name(fp, &id, &namebuf[0]))) {
+		if (ret == -1) {
+			fprintf(stderr, "Database %s is corrupted at %s\n",
+					path, namebuf);
+			fclose(fp);
+			return NULL;
+		}
+
+		if (id < 0)
+			continue;
+
+		entry = malloc(sizeof(*entry));
+		entry->id   = id;
+		entry->name = strdup(namebuf);
+		entry->next = db->hash[id & (db->size - 1)];
+		db->hash[id & (db->size - 1)] = entry;
+	}
+
+	fclose(fp);
+	return db;
+}
+
+void db_names_free(struct db_names *db)
+{
+	int i;
+
+	if (!db)
+		return;
+
+	for (i = 0; i < db->size; i++) {
+		struct db_entry *entry = db->hash[i];
+
+		while (entry) {
+			struct db_entry *next = entry->next;
+
+			free(entry->name);
+			free(entry);
+			entry = next;
+		}
+	}
+
+	free(db->hash);
+	free(db);
+}
+
+char *id_to_name(struct db_names *db, int id, char *name)
+{
+	struct db_entry *entry = db->hash[id & (db->size - 1)];
+
+	while (entry && entry->id != id)
+		entry = entry->next;
+
+	if (entry) {
+		strncpy(name, entry->name, IDNAME_MAX);
+		return name;
+	}
+
+	snprintf(name, IDNAME_MAX, "%d", id);
+	return NULL;
+}
+
+int name_to_id(struct db_names *db, int *id, const char *name)
+{
+	struct db_entry *entry;
+	int i;
+
+	if (db->cached && strcmp(db->cached->name, name) == 0) {
+		*id = db->cached->id;
+		return 0;
+	}
+
+	for (i = 0; i < db->size; i++) {
+		entry = db->hash[i];
+		while (entry && strcmp(entry->name, name))
+			entry = entry->next;
+		if (entry) {
+			db->cached = entry;
+			*id = entry->id;
+			return 0;
+		}
+	}
+
+	return -1;
+}
diff --git a/tc/tc.c b/tc/tc.c
index 9380305..22c3be4 100644
--- a/tc/tc.c
+++ b/tc/tc.c
@@ -41,6 +41,10 @@  int batch_mode = 0;
 int resolve_hosts = 0;
 int use_iec = 0;
 int force = 0;
+bool use_names = false;
+
+static char *conf_file;
+
 struct rtnl_handle rth;
 
 static void *BODY = NULL;	/* cached handle dlopen(NULL) */
@@ -188,7 +192,8 @@  static void usage(void)
 			"       tc [-force] -batch filename\n"
 	                "where  OBJECT := { qdisc | class | filter | action | monitor }\n"
 	                "       OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -p[retty] | -b[atch] [filename] | "
-			"-n[etns] name }\n");
+			"-n[etns] name |\n"
+			"                    -nm | -nam[es] | { -cf | -conf } path }\n");
 }
 
 static int do_cmd(int argc, char **argv)
@@ -293,7 +298,7 @@  int main(int argc, char **argv)
 			return 0;
 		} else if (matches(argv[1], "-force") == 0) {
 			++force;
-		} else 	if (matches(argv[1], "-batch") == 0) {
+		} else if (matches(argv[1], "-batch") == 0) {
 			argc--;	argv++;
 			if (argc <= 1)
 				usage();
@@ -302,6 +307,13 @@  int main(int argc, char **argv)
 			NEXT_ARG();
 			if (netns_switch(argv[1]))
 				return -1;
+		} else if (matches(argv[1], "-names") == 0 ||
+				matches(argv[1], "-nm") == 0) {
+			use_names = true;
+		} else if (matches(argv[1], "-cf") == 0 ||
+				matches(argv[1], "-conf") == 0) {
+			NEXT_ARG();
+			conf_file = argv[1];
 		} else {
 			fprintf(stderr, "Option \"%s\" is unknown, try \"tc -help\".\n", argv[1]);
 			return -1;
@@ -323,8 +335,17 @@  int main(int argc, char **argv)
 		exit(1);
 	}
 
+	if (use_names && cls_names_init(conf_file)) {
+		ret = -1;
+		goto Exit;
+	}
+
 	ret = do_cmd(argc-1, argv+1);
+Exit:
 	rtnl_close(&rth);
 
+	if (use_names)
+		cls_names_uninit();
+
 	return ret;
 }
diff --git a/tc/tc_common.h b/tc/tc_common.h
index ea16f7f..96a0e20 100644
--- a/tc/tc_common.h
+++ b/tc/tc_common.h
@@ -21,3 +21,4 @@  extern int parse_size_table(int *p_argc, char ***p_argv, struct tc_sizespec *s);
 extern int check_size_table_opts(struct tc_sizespec *s);
 
 extern int show_graph;
+extern bool use_names;
diff --git a/tc/tc_util.c b/tc/tc_util.c
index f1fca0a..feae439 100644
--- a/tc/tc_util.c
+++ b/tc/tc_util.c
@@ -23,12 +23,34 @@ 
 #include <math.h>
 
 #include "utils.h"
+#include "names.h"
 #include "tc_util.h"
+#include "tc_common.h"
 
 #ifndef LIBDIR
 #define LIBDIR "/usr/lib"
 #endif
 
+static struct db_names *cls_names = NULL;
+
+#define NAMES_DB "/etc/iproute2/cls_names"
+
+int cls_names_init(char *path)
+{
+	cls_names = db_names_alloc(path ?: NAMES_DB);
+	if (!cls_names) {
+		fprintf(stderr, "Error while opening class names file\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+void cls_names_uninit(void)
+{
+	db_names_free(cls_names);
+}
+
 const char *get_tc_lib(void)
 {
 	const char *lib_dir;
@@ -97,20 +119,34 @@  ok:
 
 int print_tc_classid(char *buf, int len, __u32 h)
 {
+	char handle[40] = {};
+
 	if (h == TC_H_ROOT)
-		sprintf(buf, "root");
+		sprintf(handle, "root");
 	else if (h == TC_H_UNSPEC)
-		snprintf(buf, len, "none");
+		snprintf(handle, len, "none");
 	else if (TC_H_MAJ(h) == 0)
-		snprintf(buf, len, ":%x", TC_H_MIN(h));
+		snprintf(handle, len, ":%x", TC_H_MIN(h));
 	else if (TC_H_MIN(h) == 0)
-		snprintf(buf, len, "%x:", TC_H_MAJ(h)>>16);
+		snprintf(handle, len, "%x:", TC_H_MAJ(h) >> 16);
 	else
-		snprintf(buf, len, "%x:%x", TC_H_MAJ(h)>>16, TC_H_MIN(h));
+		snprintf(handle, len, "%x:%x", TC_H_MAJ(h) >> 16, TC_H_MIN(h));
+
+	if (use_names) {
+		char clname[IDNAME_MAX] = {};
+
+		if (id_to_name(cls_names, h, clname))
+			snprintf(buf, len, "%s#%s", clname, handle);
+		else
+			snprintf(buf, len, "%s", handle);
+	} else {
+		snprintf(buf, len, "%s", handle);
+	}
+
 	return 0;
 }
 
-char * sprint_tc_classid(__u32 h, char *buf)
+char *sprint_tc_classid(__u32 h, char *buf)
 {
 	if (print_tc_classid(buf, SPRINT_BSIZE-1, h))
 		strcpy(buf, "???");
diff --git a/tc/tc_util.h b/tc/tc_util.h
index d418367..1be1b50 100644
--- a/tc/tc_util.h
+++ b/tc/tc_util.h
@@ -100,4 +100,7 @@  extern int  parse_action(int *, char ***, int, struct nlmsghdr *);
 extern void print_tm(FILE *f, const struct tcf_t *tm);
 extern int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt);
 
+extern int cls_names_init(char *path);
+extern void cls_names_uninit(void);
+
 #endif