diff mbox

iproute2: show network device dependency tree

Message ID 20170225165900.10129-1-zaboj.campula@post.cz
State Changes Requested, archived
Delegated to: stephen hemminger
Headers show

Commit Message

Zaboj Campula Feb. 25, 2017, 4:59 p.m. UTC
Add the argument '-tree' to ip-link to show network devices dependency tree.

Example:

$ ip -tree link
eth0
    bond0
eth1
    bond0
eth2
    bond1
eth3
    bond1

Signed-off-by: Zaboj Campula <zaboj.campula@post.cz>
---
 include/utils.h |  1 +
 ip/ip.c         |  5 ++-
 ip/ipaddress.c  | 97 ++++++++++++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 87 insertions(+), 16 deletions(-)

Comments

Jiri Pirko Feb. 25, 2017, 5:39 p.m. UTC | #1
Sat, Feb 25, 2017 at 05:59:00PM CET, zaboj.campula@post.cz wrote:
>Add the argument '-tree' to ip-link to show network devices dependency tree.
>
>Example:
>
>$ ip -tree link
>eth0
>    bond0
>eth1
>    bond0
>eth2
>    bond1
>eth3
>    bond1


Hmm, what is this good for? I'm probably missing something...



>
>Signed-off-by: Zaboj Campula <zaboj.campula@post.cz>
>---
> include/utils.h |  1 +
> ip/ip.c         |  5 ++-
> ip/ipaddress.c  | 97 ++++++++++++++++++++++++++++++++++++++++++++++++---------
> 3 files changed, 87 insertions(+), 16 deletions(-)
>
>diff --git a/include/utils.h b/include/utils.h
>index 22369e0..f1acf4d 100644
>--- a/include/utils.h
>+++ b/include/utils.h
>@@ -20,6 +20,7 @@ extern int show_raw;
> extern int resolve_hosts;
> extern int oneline;
> extern int brief;
>+extern int tree;;
> extern int timestamp;
> extern int timestamp_short;
> extern const char * _SL_;
>diff --git a/ip/ip.c b/ip/ip.c
>index 07050b0..29747a5 100644
>--- a/ip/ip.c
>+++ b/ip/ip.c
>@@ -33,6 +33,7 @@ int show_details;
> int resolve_hosts;
> int oneline;
> int brief;
>+int tree;
> int timestamp;
> const char *_SL_;
> int force;
>@@ -57,7 +58,7 @@ static void usage(void)
> "                    -h[uman-readable] | -iec |\n"
> "                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n"
> "                    -4 | -6 | -I | -D | -B | -0 |\n"
>-"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |\n"
>+"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] | -tr[ee] |\n"
> "                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
> "                    -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}\n");
> 	exit(-1);
>@@ -257,6 +258,8 @@ int main(int argc, char **argv)
> 			batch_file = argv[1];
> 		} else if (matches(opt, "-brief") == 0) {
> 			++brief;
>+		} else if (matches(opt, "-tree") == 0) {
>+			++tree;
> 		} else if (matches(opt, "-rcvbuf") == 0) {
> 			unsigned int size;
> 
>diff --git a/ip/ipaddress.c b/ip/ipaddress.c
>index 242c6ea..5ebcb1a 100644
>--- a/ip/ipaddress.c
>+++ b/ip/ipaddress.c
>@@ -1534,6 +1534,69 @@ static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen)
> 	return 0;
> }
> 
>+static int has_master(struct nlmsg_chain *linfo, int index)
>+{
>+	struct nlmsg_list *l;
>+	struct rtattr *tb[IFLA_MAX+1];
>+	int len;
>+	for (l = linfo->head; l; l = l->next) {
>+		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>+		len = l->h.nlmsg_len;
>+		len -= NLMSG_LENGTH(sizeof(*ifi));
>+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
>+		if (tb[IFLA_MASTER] && *(int *)RTA_DATA(tb[IFLA_MASTER]) == index)
>+			return 1;
>+	}
>+	return 0;
>+}
>+
>+static struct nlmsg_list *get_master(struct nlmsg_chain *linfo, struct rtattr **tb)
>+{
>+	struct nlmsg_list *l;
>+	if (tb[IFLA_MASTER]) {
>+		int master = *(int *)RTA_DATA(tb[IFLA_MASTER]);
>+		for (l = linfo->head; l; l = l->next) {
>+			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>+			if (ifi->ifi_index == master)
>+				return l;
>+		}
>+	}
>+	return NULL;
>+}
>+
>+static void print_dev_tree_item(struct nlmsg_chain *linfo, struct nlmsg_list *l, int indent) {
>+	char *name;
>+	int len;
>+	struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>+	struct rtattr *tb[IFLA_MAX+1];
>+	len = l->h.nlmsg_len;
>+	len -= NLMSG_LENGTH(sizeof(*ifi));
>+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
>+	name = (char *)(tb[IFLA_IFNAME] ? rta_getattr_str(tb[IFLA_IFNAME]) : "<nil>");
>+
>+	printf("%*s%s\n", indent * 4, "", name);
>+
>+	struct nlmsg_list *master = get_master(linfo, tb);
>+	if (master) {
>+		if (indent > 8) {
>+			printf("%*s...\n", (indent + 1) * 4, "");
>+		} else {
>+			print_dev_tree_item(linfo, master, indent + 1);
>+		}
>+	}
>+}
>+
>+static void print_devtree(struct nlmsg_chain *linfo)
>+{
>+	struct nlmsg_list *l;
>+	for (l = linfo->head; l; l = l->next) {
>+		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>+		if (!has_master(linfo, ifi->ifi_index)) {
>+			print_dev_tree_item(linfo, l, 0);
>+		}
>+	}
>+}
>+
> static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
> {
> 	struct nlmsg_chain linfo = { NULL, NULL};
>@@ -1742,23 +1805,27 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
> 		ipaddr_filter(&linfo, &ainfo);
> 	}
> 
>-	for (l = linfo.head; l; l = l->next) {
>-		int res = 0;
>-		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>-
>-		if (brief) {
>-			if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
>+	if (tree) {
>+		print_devtree(&linfo);
>+	} else {
>+		for (l = linfo.head; l; l = l->next) {
>+			int res = 0;
>+			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>+
>+			 if (brief) {
>+				if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
>+					if (filter.family != AF_PACKET)
>+						print_selected_addrinfo(ifi,
>+									ainfo.head,
>+									stdout);
>+			} else if (no_link ||
>+				 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
> 				if (filter.family != AF_PACKET)
> 					print_selected_addrinfo(ifi,
>-								ainfo.head,
>-								stdout);
>-		} else if (no_link ||
>-			 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
>-			if (filter.family != AF_PACKET)
>-				print_selected_addrinfo(ifi,
>-							ainfo.head, stdout);
>-			if (res > 0 && !do_link && show_stats)
>-				print_link_stats(stdout, &l->h);
>+								ainfo.head, stdout);
>+				if (res > 0 && !do_link && show_stats)
>+					print_link_stats(stdout, &l->h);
>+			}
> 		}
> 	}
> 	fflush(stdout);
>-- 
>2.9.3
>
Zaboj Campula Feb. 25, 2017, 8:22 p.m. UTC | #2
On Sat, 2017-02-25 at 18:39 +0100, Jiri Pirko wrote:
> > Sat, Feb 25, 2017 at 05:59:00PM CET, zaboj.campula@post.cz wrote:
> > Add the argument '-tree' to ip-link to show network devices dependency tree.
> > 
> > Example:
> > 
> > $ ip -tree link
> > eth0
> >    bond0
> > eth1
> >    bond0
> > eth2
> >    bond1
> > eth3
> >    bond1
> 
> 
> Hmm, what is this good for? I'm probably missing something...

I consider this kind of output useful when troubleshooting a complex
configuration with many interfaces. It may show relations among
interfaces.


> 
> 
> 
> > 
> > > > Signed-off-by: Zaboj Campula <zaboj.campula@post.cz>
> > ---
> > include/utils.h |  1 +
> > ip/ip.c         |  5 ++-
> > ip/ipaddress.c  | 97 ++++++++++++++++++++++++++++++++++++++++++++++++---------
> > 3 files changed, 87 insertions(+), 16 deletions(-)
> > 
> > diff --git a/include/utils.h b/include/utils.h
> > index 22369e0..f1acf4d 100644
> > --- a/include/utils.h
> > +++ b/include/utils.h
> > @@ -20,6 +20,7 @@ extern int show_raw;
> > extern int resolve_hosts;
> > extern int oneline;
> > extern int brief;
> > +extern int tree;;
> > extern int timestamp;
> > extern int timestamp_short;
> > extern const char * _SL_;
> > diff --git a/ip/ip.c b/ip/ip.c
> > index 07050b0..29747a5 100644
> > --- a/ip/ip.c
> > +++ b/ip/ip.c
> > @@ -33,6 +33,7 @@ int show_details;
> > int resolve_hosts;
> > int oneline;
> > int brief;
> > +int tree;
> > int timestamp;
> > const char *_SL_;
> > int force;
> > @@ -57,7 +58,7 @@ static void usage(void)
> > "                    -h[uman-readable] | -iec |\n"
> > "                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n"
> > "                    -4 | -6 | -I | -D | -B | -0 |\n"
> > -"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |\n"
> > +"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] | -tr[ee] |\n"
> > "                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
> > "                    -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}\n");
> > 	exit(-1);
> > @@ -257,6 +258,8 @@ int main(int argc, char **argv)
> > > > 			batch_file = argv[1];
> > > > 		} else if (matches(opt, "-brief") == 0) {
> > > > 			++brief;
> > > > +		} else if (matches(opt, "-tree") == 0) {
> > > > +			++tree;
> > > > 		} else if (matches(opt, "-rcvbuf") == 0) {
> > > > 			unsigned int size;
> > 
> > diff --git a/ip/ipaddress.c b/ip/ipaddress.c
> > index 242c6ea..5ebcb1a 100644
> > --- a/ip/ipaddress.c
> > +++ b/ip/ipaddress.c
> > @@ -1534,6 +1534,69 @@ static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen)
> > 	return 0;
> > }
> > 
> > +static int has_master(struct nlmsg_chain *linfo, int index)
> > +{
> > > > +	struct nlmsg_list *l;
> > > > +	struct rtattr *tb[IFLA_MAX+1];
> > > > +	int len;
> > > > +	for (l = linfo->head; l; l = l->next) {
> > > > +		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > +		len = l->h.nlmsg_len;
> > > > +		len -= NLMSG_LENGTH(sizeof(*ifi));
> > > > +		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
> > > > +		if (tb[IFLA_MASTER] && *(int *)RTA_DATA(tb[IFLA_MASTER]) == index)
> > > > +			return 1;
> > > > +	}
> > > > +	return 0;
> > +}
> > +
> > +static struct nlmsg_list *get_master(struct nlmsg_chain *linfo, struct rtattr **tb)
> > +{
> > > > +	struct nlmsg_list *l;
> > > > +	if (tb[IFLA_MASTER]) {
> > > > +		int master = *(int *)RTA_DATA(tb[IFLA_MASTER]);
> > > > +		for (l = linfo->head; l; l = l->next) {
> > > > +			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > +			if (ifi->ifi_index == master)
> > > > +				return l;
> > > > +		}
> > > > +	}
> > > > +	return NULL;
> > +}
> > +
> > +static void print_dev_tree_item(struct nlmsg_chain *linfo, struct nlmsg_list *l, int indent) {
> > > > +	char *name;
> > > > +	int len;
> > > > +	struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > +	struct rtattr *tb[IFLA_MAX+1];
> > > > +	len = l->h.nlmsg_len;
> > > > +	len -= NLMSG_LENGTH(sizeof(*ifi));
> > > > +	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
> > > > +	name = (char *)(tb[IFLA_IFNAME] ? rta_getattr_str(tb[IFLA_IFNAME]) : "<nil>");
> > +
> > > > +	printf("%*s%s\n", indent * 4, "", name);
> > +
> > > > +	struct nlmsg_list *master = get_master(linfo, tb);
> > > > +	if (master) {
> > > > +		if (indent > 8) {
> > > > +			printf("%*s...\n", (indent + 1) * 4, "");
> > > > +		} else {
> > > > +			print_dev_tree_item(linfo, master, indent + 1);
> > > > +		}
> > > > +	}
> > +}
> > +
> > +static void print_devtree(struct nlmsg_chain *linfo)
> > +{
> > > > +	struct nlmsg_list *l;
> > > > +	for (l = linfo->head; l; l = l->next) {
> > > > +		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > +		if (!has_master(linfo, ifi->ifi_index)) {
> > > > +			print_dev_tree_item(linfo, l, 0);
> > > > +		}
> > > > +	}
> > +}
> > +
> > static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
> > {
> > 	struct nlmsg_chain linfo = { NULL, NULL};
> > @@ -1742,23 +1805,27 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
> > > > 		ipaddr_filter(&linfo, &ainfo);
> > 	}
> > 
> > > > -	for (l = linfo.head; l; l = l->next) {
> > > > -		int res = 0;
> > > > -		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > -
> > > > -		if (brief) {
> > > > -			if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
> > > > +	if (tree) {
> > > > +		print_devtree(&linfo);
> > > > +	} else {
> > > > +		for (l = linfo.head; l; l = l->next) {
> > > > +			int res = 0;
> > > > +			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > +
> > > > +			 if (brief) {
> > > > +				if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
> > > > +					if (filter.family != AF_PACKET)
> > > > +						print_selected_addrinfo(ifi,
> > > > +									ainfo.head,
> > > > +									stdout);
> > > > +			} else if (no_link ||
> > > > +				 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
> > > > 				if (filter.family != AF_PACKET)
> > > > 					print_selected_addrinfo(ifi,
> > > > -								ainfo.head,
> > > > -								stdout);
> > > > -		} else if (no_link ||
> > > > -			 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
> > > > -			if (filter.family != AF_PACKET)
> > > > -				print_selected_addrinfo(ifi,
> > > > -							ainfo.head, stdout);
> > > > -			if (res > 0 && !do_link && show_stats)
> > > > -				print_link_stats(stdout, &l->h);
> > > > +								ainfo.head, stdout);
> > > > +				if (res > 0 && !do_link && show_stats)
> > > > +					print_link_stats(stdout, &l->h);
> > > > +			}
> > > > 		}
> > 	}
> > 	fflush(stdout);
> > -- 
> > 2.9.3
> >
Jiri Pirko Feb. 26, 2017, 7:56 a.m. UTC | #3
Sat, Feb 25, 2017 at 09:22:22PM CET, zaboj.campula@post.cz wrote:
>On Sat, 2017-02-25 at 18:39 +0100, Jiri Pirko wrote:
>> > Sat, Feb 25, 2017 at 05:59:00PM CET, zaboj.campula@post.cz wrote:
>> > Add the argument '-tree' to ip-link to show network devices dependency tree.
>> > 
>> > Example:
>> > 
>> > $ ip -tree link
>> > eth0
>> >    bond0
>> > eth1
>> >    bond0
>> > eth2
>> >    bond1
>> > eth3
>> >    bond1
>> 
>> 
>> Hmm, what is this good for? I'm probably missing something...
>
>I consider this kind of output useful when troubleshooting a complex
>configuration with many interfaces. It may show relations among
>interfaces.

Did you see https://github.com/jbenc/plotnetcfg ?


>
>
>> 
>> 
>> 
>> > 
>> > > > Signed-off-by: Zaboj Campula <zaboj.campula@post.cz>
>> > ---
>> > include/utils.h |  1 +
>> > ip/ip.c         |  5 ++-
>> > ip/ipaddress.c  | 97 ++++++++++++++++++++++++++++++++++++++++++++++++---------
>> > 3 files changed, 87 insertions(+), 16 deletions(-)
>> > 
>> > diff --git a/include/utils.h b/include/utils.h
>> > index 22369e0..f1acf4d 100644
>> > --- a/include/utils.h
>> > +++ b/include/utils.h
>> > @@ -20,6 +20,7 @@ extern int show_raw;
>> > extern int resolve_hosts;
>> > extern int oneline;
>> > extern int brief;
>> > +extern int tree;;
>> > extern int timestamp;
>> > extern int timestamp_short;
>> > extern const char * _SL_;
>> > diff --git a/ip/ip.c b/ip/ip.c
>> > index 07050b0..29747a5 100644
>> > --- a/ip/ip.c
>> > +++ b/ip/ip.c
>> > @@ -33,6 +33,7 @@ int show_details;
>> > int resolve_hosts;
>> > int oneline;
>> > int brief;
>> > +int tree;
>> > int timestamp;
>> > const char *_SL_;
>> > int force;
>> > @@ -57,7 +58,7 @@ static void usage(void)
>> > "                    -h[uman-readable] | -iec |\n"
>> > "                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n"
>> > "                    -4 | -6 | -I | -D | -B | -0 |\n"
>> > -"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |\n"
>> > +"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] | -tr[ee] |\n"
>> > "                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
>> > "                    -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}\n");
>> > 	exit(-1);
>> > @@ -257,6 +258,8 @@ int main(int argc, char **argv)
>> > > > 			batch_file = argv[1];
>> > > > 		} else if (matches(opt, "-brief") == 0) {
>> > > > 			++brief;
>> > > > +		} else if (matches(opt, "-tree") == 0) {
>> > > > +			++tree;
>> > > > 		} else if (matches(opt, "-rcvbuf") == 0) {
>> > > > 			unsigned int size;
>> > 
>> > diff --git a/ip/ipaddress.c b/ip/ipaddress.c
>> > index 242c6ea..5ebcb1a 100644
>> > --- a/ip/ipaddress.c
>> > +++ b/ip/ipaddress.c
>> > @@ -1534,6 +1534,69 @@ static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen)
>> > 	return 0;
>> > }
>> > 
>> > +static int has_master(struct nlmsg_chain *linfo, int index)
>> > +{
>> > > > +	struct nlmsg_list *l;
>> > > > +	struct rtattr *tb[IFLA_MAX+1];
>> > > > +	int len;
>> > > > +	for (l = linfo->head; l; l = l->next) {
>> > > > +		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>> > > > +		len = l->h.nlmsg_len;
>> > > > +		len -= NLMSG_LENGTH(sizeof(*ifi));
>> > > > +		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
>> > > > +		if (tb[IFLA_MASTER] && *(int *)RTA_DATA(tb[IFLA_MASTER]) == index)
>> > > > +			return 1;
>> > > > +	}
>> > > > +	return 0;
>> > +}
>> > +
>> > +static struct nlmsg_list *get_master(struct nlmsg_chain *linfo, struct rtattr **tb)
>> > +{
>> > > > +	struct nlmsg_list *l;
>> > > > +	if (tb[IFLA_MASTER]) {
>> > > > +		int master = *(int *)RTA_DATA(tb[IFLA_MASTER]);
>> > > > +		for (l = linfo->head; l; l = l->next) {
>> > > > +			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>> > > > +			if (ifi->ifi_index == master)
>> > > > +				return l;
>> > > > +		}
>> > > > +	}
>> > > > +	return NULL;
>> > +}
>> > +
>> > +static void print_dev_tree_item(struct nlmsg_chain *linfo, struct nlmsg_list *l, int indent) {
>> > > > +	char *name;
>> > > > +	int len;
>> > > > +	struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>> > > > +	struct rtattr *tb[IFLA_MAX+1];
>> > > > +	len = l->h.nlmsg_len;
>> > > > +	len -= NLMSG_LENGTH(sizeof(*ifi));
>> > > > +	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
>> > > > +	name = (char *)(tb[IFLA_IFNAME] ? rta_getattr_str(tb[IFLA_IFNAME]) : "<nil>");
>> > +
>> > > > +	printf("%*s%s\n", indent * 4, "", name);
>> > +
>> > > > +	struct nlmsg_list *master = get_master(linfo, tb);
>> > > > +	if (master) {
>> > > > +		if (indent > 8) {
>> > > > +			printf("%*s...\n", (indent + 1) * 4, "");
>> > > > +		} else {
>> > > > +			print_dev_tree_item(linfo, master, indent + 1);
>> > > > +		}
>> > > > +	}
>> > +}
>> > +
>> > +static void print_devtree(struct nlmsg_chain *linfo)
>> > +{
>> > > > +	struct nlmsg_list *l;
>> > > > +	for (l = linfo->head; l; l = l->next) {
>> > > > +		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>> > > > +		if (!has_master(linfo, ifi->ifi_index)) {
>> > > > +			print_dev_tree_item(linfo, l, 0);
>> > > > +		}
>> > > > +	}
>> > +}
>> > +
>> > static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
>> > {
>> > 	struct nlmsg_chain linfo = { NULL, NULL};
>> > @@ -1742,23 +1805,27 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
>> > > > 		ipaddr_filter(&linfo, &ainfo);
>> > 	}
>> > 
>> > > > -	for (l = linfo.head; l; l = l->next) {
>> > > > -		int res = 0;
>> > > > -		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>> > -
>> > > > -		if (brief) {
>> > > > -			if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
>> > > > +	if (tree) {
>> > > > +		print_devtree(&linfo);
>> > > > +	} else {
>> > > > +		for (l = linfo.head; l; l = l->next) {
>> > > > +			int res = 0;
>> > > > +			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
>> > +
>> > > > +			 if (brief) {
>> > > > +				if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
>> > > > +					if (filter.family != AF_PACKET)
>> > > > +						print_selected_addrinfo(ifi,
>> > > > +									ainfo.head,
>> > > > +									stdout);
>> > > > +			} else if (no_link ||
>> > > > +				 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
>> > > > 				if (filter.family != AF_PACKET)
>> > > > 					print_selected_addrinfo(ifi,
>> > > > -								ainfo.head,
>> > > > -								stdout);
>> > > > -		} else if (no_link ||
>> > > > -			 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
>> > > > -			if (filter.family != AF_PACKET)
>> > > > -				print_selected_addrinfo(ifi,
>> > > > -							ainfo.head, stdout);
>> > > > -			if (res > 0 && !do_link && show_stats)
>> > > > -				print_link_stats(stdout, &l->h);
>> > > > +								ainfo.head, stdout);
>> > > > +				if (res > 0 && !do_link && show_stats)
>> > > > +					print_link_stats(stdout, &l->h);
>> > > > +			}
>> > > > 		}
>> > 	}
>> > 	fflush(stdout);
>> > -- 
>> > 2.9.3
>> >
Zaboj Campula Feb. 26, 2017, 2 p.m. UTC | #4
On Sun, 2017-02-26 at 08:56 +0100, Jiri Pirko wrote:
> Sat, Feb 25, 2017 at 09:22:22PM CET, zaboj.campula@post.cz wrote:
> > On Sat, 2017-02-25 at 18:39 +0100, Jiri Pirko wrote:
> > > > Sat, Feb 25, 2017 at 05:59:00PM CET, zaboj.campula@post.cz
> > > > wrote:
> > > > Add the argument '-tree' to ip-link to show network devices
> > > > dependency tree.
> > > > 
> > > > Example:
> > > > 
> > > > $ ip -tree link
> > > > eth0
> > > >    bond0
> > > > eth1
> > > >    bond0
> > > > eth2
> > > >    bond1
> > > > eth3
> > > >    bond1
> > > 
> > > 
> > > Hmm, what is this good for? I'm probably missing something...
> > 
> > I consider this kind of output useful when troubleshooting a complex
> > configuration with many interfaces. It may show relations among
> > interfaces.
> 
> Did you see https://github.com/jbenc/plotnetcfg ?
> 

Thanks for the link. I haven't seen plotnetcfg and I like it.
It is handy when the analyzed system has GUI.


> 
> > 
> > 
> > > 
> > > 
> > > 
> > > > 
> > > > > > > > Signed-off-by: Zaboj Campula <zaboj.campula@post.cz>
> > > > 
> > > > ---
> > > > include/utils.h |  1 +
> > > > ip/ip.c         |  5 ++-
> > > > ip/ipaddress.c  | 97 ++++++++++++++++++++++++++++++++++++++++++++++++---------
> > > > 3 files changed, 87 insertions(+), 16 deletions(-)
> > > > 
> > > > diff --git a/include/utils.h b/include/utils.h
> > > > index 22369e0..f1acf4d 100644
> > > > --- a/include/utils.h
> > > > +++ b/include/utils.h
> > > > @@ -20,6 +20,7 @@ extern int show_raw;
> > > > extern int resolve_hosts;
> > > > extern int oneline;
> > > > extern int brief;
> > > > +extern int tree;;
> > > > extern int timestamp;
> > > > extern int timestamp_short;
> > > > extern const char * _SL_;
> > > > diff --git a/ip/ip.c b/ip/ip.c
> > > > index 07050b0..29747a5 100644
> > > > --- a/ip/ip.c
> > > > +++ b/ip/ip.c
> > > > @@ -33,6 +33,7 @@ int show_details;
> > > > int resolve_hosts;
> > > > int oneline;
> > > > int brief;
> > > > +int tree;
> > > > int timestamp;
> > > > const char *_SL_;
> > > > int force;
> > > > @@ -57,7 +58,7 @@ static void usage(void)
> > > > "                    -h[uman-readable] | -iec |\n"
> > > > "                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n"
> > > > "                    -4 | -6 | -I | -D | -B | -0 |\n"
> > > > -"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |\n"
> > > > +"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] | -tr[ee] |\n"
> > > > "                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
> > > > "                    -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}\n");
> > > > 	exit(-1);
> > > > @@ -257,6 +258,8 @@ int main(int argc, char **argv)
> > > > > > > > > > > > 			batch_file = argv[1];
> > > > > > > > > > > > 		} else if (matches(opt, "-brief") == 0) {
> > > > > > > > > > > > 			++brief;
> > > > > > > > > > > > +		} else if (matches(opt, "-tree") == 0) {
> > > > > > > > > > > > +			++tree;
> > > > > > > > > > > > 		} else if (matches(opt, "-rcvbuf") == 0) {
> > > > > > 			unsigned int size;
> > > > 
> > > > diff --git a/ip/ipaddress.c b/ip/ipaddress.c
> > > > index 242c6ea..5ebcb1a 100644
> > > > --- a/ip/ipaddress.c
> > > > +++ b/ip/ipaddress.c
> > > > @@ -1534,6 +1534,69 @@ static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen)
> > > > 	return 0;
> > > > }
> > > > 
> > > > +static int has_master(struct nlmsg_chain *linfo, int index)
> > > > +{
> > > > > > > > > > > > +	struct nlmsg_list *l;
> > > > > > > > > > > > +	struct rtattr *tb[IFLA_MAX+1];
> > > > > > > > > > > > +	int len;
> > > > > > > > > > > > +	for (l = linfo->head; l; l = l->next) {
> > > > > > > > > > > > +		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > > > > > > > > > +		len = l->h.nlmsg_len;
> > > > > > > > > > > > +		len -= NLMSG_LENGTH(sizeof(*ifi));
> > > > > > > > > > > > +		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
> > > > > > > > > > > > +		if (tb[IFLA_MASTER] && *(int *)RTA_DATA(tb[IFLA_MASTER]) == index)
> > > > > > > > > > > > +			return 1;
> > > > > > > > > > > > +	}
> > > > > > +	return 0;
> > > > 
> > > > +}
> > > > +
> > > > +static struct nlmsg_list *get_master(struct nlmsg_chain *linfo, struct rtattr **tb)
> > > > +{
> > > > > > > > > > > > +	struct nlmsg_list *l;
> > > > > > > > > > > > +	if (tb[IFLA_MASTER]) {
> > > > > > > > > > > > +		int master = *(int *)RTA_DATA(tb[IFLA_MASTER]);
> > > > > > > > > > > > +		for (l = linfo->head; l; l = l->next) {
> > > > > > > > > > > > +			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > > > > > > > > > +			if (ifi->ifi_index == master)
> > > > > > > > > > > > +				return l;
> > > > > > > > > > > > +		}
> > > > > > > > > > > > +	}
> > > > > > +	return NULL;
> > > > 
> > > > +}
> > > > +
> > > > +static void print_dev_tree_item(struct nlmsg_chain *linfo, struct nlmsg_list *l, int indent) {
> > > > > > > > > > > > +	char *name;
> > > > > > > > > > > > +	int len;
> > > > > > > > > > > > +	struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > > > > > > > > > +	struct rtattr *tb[IFLA_MAX+1];
> > > > > > > > > > > > +	len = l->h.nlmsg_len;
> > > > > > > > > > > > +	len -= NLMSG_LENGTH(sizeof(*ifi));
> > > > > > > > > > > > +	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
> > > > > > +	name = (char *)(tb[IFLA_IFNAME] ? rta_getattr_str(tb[IFLA_IFNAME]) : "<nil>");
> > > > 
> > > > +
> > > > > > +	printf("%*s%s\n", indent * 4, "", name);
> > > > 
> > > > +
> > > > > > > > > > > > +	struct nlmsg_list *master = get_master(linfo, tb);
> > > > > > > > > > > > +	if (master) {
> > > > > > > > > > > > +		if (indent > 8) {
> > > > > > > > > > > > +			printf("%*s...\n", (indent + 1) * 4, "");
> > > > > > > > > > > > +		} else {
> > > > > > > > > > > > +			print_dev_tree_item(linfo, master, indent + 1);
> > > > > > > > > > > > +		}
> > > > > > +	}
> > > > 
> > > > +}
> > > > +
> > > > +static void print_devtree(struct nlmsg_chain *linfo)
> > > > +{
> > > > > > > > > > > > +	struct nlmsg_list *l;
> > > > > > > > > > > > +	for (l = linfo->head; l; l = l->next) {
> > > > > > > > > > > > +		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > > > > > > > > > +		if (!has_master(linfo, ifi->ifi_index)) {
> > > > > > > > > > > > +			print_dev_tree_item(linfo, l, 0);
> > > > > > > > > > > > +		}
> > > > > > +	}
> > > > 
> > > > +}
> > > > +
> > > > static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
> > > > {
> > > > 	struct nlmsg_chain linfo = { NULL, NULL};
> > > > @@ -1742,23 +1805,27 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
> > > > > > 		ipaddr_filter(&linfo, &ainfo);
> > > > 
> > > > 	}
> > > > 
> > > > > > > > > > > > -	for (l = linfo.head; l; l = l->next) {
> > > > > > > > > > > > -		int res = 0;
> > > > > > -		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > 
> > > > -
> > > > > > > > > > > > -		if (brief) {
> > > > > > > > > > > > -			if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
> > > > > > > > > > > > +	if (tree) {
> > > > > > > > > > > > +		print_devtree(&linfo);
> > > > > > > > > > > > +	} else {
> > > > > > > > > > > > +		for (l = linfo.head; l; l = l->next) {
> > > > > > > > > > > > +			int res = 0;
> > > > > > +			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
> > > > 
> > > > +
> > > > > > > > > > > > +			 if (brief) {
> > > > > > > > > > > > +				if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
> > > > > > > > > > > > +					if (filter.family != AF_PACKET)
> > > > > > > > > > > > +						print_selected_addrinfo(ifi,
> > > > > > > > > > > > +									ainfo.head,
> > > > > > > > > > > > +									stdout);
> > > > > > > > > > > > +			} else if (no_link ||
> > > > > > > > > > > > +				 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
> > > > > > > > > > > > 				if (filter.family != AF_PACKET)
> > > > > > > > > > > > 					print_selected_addrinfo(ifi,
> > > > > > > > > > > > -								ainfo.head,
> > > > > > > > > > > > -								stdout);
> > > > > > > > > > > > -		} else if (no_link ||
> > > > > > > > > > > > -			 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
> > > > > > > > > > > > -			if (filter.family != AF_PACKET)
> > > > > > > > > > > > -				print_selected_addrinfo(ifi,
> > > > > > > > > > > > -							ainfo.head, stdout);
> > > > > > > > > > > > -			if (res > 0 && !do_link && show_stats)
> > > > > > > > > > > > -				print_link_stats(stdout, &l->h);
> > > > > > > > > > > > +								ainfo.head, stdout);
> > > > > > > > > > > > +				if (res > 0 && !do_link && show_stats)
> > > > > > > > > > > > +					print_link_stats(stdout, &l->h);
> > > > > > > > > > > > +			}
> > > > > > 		}
> > > > 
> > > > 	}
> > > > 	fflush(stdout);
> > > > -- 
> > > > 2.9.3
> > > >
Jiri Pirko Feb. 26, 2017, 2:46 p.m. UTC | #5
Sun, Feb 26, 2017 at 03:00:14PM CET, zaboj.campula@post.cz wrote:
>On Sun, 2017-02-26 at 08:56 +0100, Jiri Pirko wrote:
>> Sat, Feb 25, 2017 at 09:22:22PM CET, zaboj.campula@post.cz wrote:
>> > On Sat, 2017-02-25 at 18:39 +0100, Jiri Pirko wrote:
>> > > > Sat, Feb 25, 2017 at 05:59:00PM CET, zaboj.campula@post.cz
>> > > > wrote:
>> > > > Add the argument '-tree' to ip-link to show network devices
>> > > > dependency tree.
>> > > > 
>> > > > Example:
>> > > > 
>> > > > $ ip -tree link
>> > > > eth0
>> > > >    bond0
>> > > > eth1
>> > > >    bond0
>> > > > eth2
>> > > >    bond1
>> > > > eth3
>> > > >    bond1
>> > > 
>> > > 
>> > > Hmm, what is this good for? I'm probably missing something...
>> > 
>> > I consider this kind of output useful when troubleshooting a complex
>> > configuration with many interfaces. It may show relations among
>> > interfaces.
>> 
>> Did you see https://github.com/jbenc/plotnetcfg ?
>> 
>
>Thanks for the link. I haven't seen plotnetcfg and I like it.
>It is handy when the analyzed system has GUI.

You can also run it remotelly. Also I believe that you can catch the
state into some dump file and process it later on. Not 100% sure though.
Ccing Jiri Benc who is the original author of plotnetcfg.
Jiri Benc Feb. 27, 2017, 4:38 p.m. UTC | #6
On Sun, 26 Feb 2017 15:46:10 +0100, Jiri Pirko wrote:
> You can also run it remotelly. Also I believe that you can catch the
> state into some dump file and process it later on. Not 100% sure though.
> Ccing Jiri Benc who is the original author of plotnetcfg.

It produces dot (graphviz) output or json and has no dependencies on
anything GUI related. Just run it on the remote machine and display the
output locally.

ssh root@remote plotnetcfg | dot -Tpdf | whatever_pdf_viewer

Note that some pdf viewers can't read stdin or require dash as the
parameter to use stdin.

I don't think it's possible to enhance iproute2 to display the network
interface dependencies in an useful way. It's just too complex. It's
not even a (undirected) tree.

 Jiri
Stephen Hemminger Feb. 27, 2017, 5:26 p.m. UTC | #7
On Sun, 26 Feb 2017 08:56:33 +0100
Jiri Pirko <jiri@resnulli.us> wrote:

> Did you see https://github.com/jbenc/plotnetcfg ?


Cool, thanks.
Stephen Hemminger Feb. 27, 2017, 6:52 p.m. UTC | #8
On Sat, 25 Feb 2017 16:59:00 +0000
Zaboj Campula <zaboj.campula@post.cz> wrote:

> dd the argument '-tree' to ip-link to show network devices dependency tree.
> 
> Example:
> 
> $ ip -tree link
> eth0
>     bond0
> eth1
>     bond0
> eth2
>     bond1
> eth3
>     bond1

Maybe use format similar to other utilities (lspci, lsusb, etc)?
Stephen Hemminger Feb. 27, 2017, 6:55 p.m. UTC | #9
On Sat, 25 Feb 2017 16:59:00 +0000
Zaboj Campula <zaboj.campula@post.cz> wrote:

> Add the argument '-tree' to ip-link to show network devices dependency tree.
> 
> Example:
> 
> $ ip -tree link
> eth0
>     bond0
> eth1
>     bond0
> eth2
>     bond1
> eth3
>     bond1

Another alternative format would be to make -tree a output modifier and ident (like ps tree options).

$ ip -t link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
8: bond0 <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
    link/ether 52:54:00:66:24:cd brd ff:ff:ff:ff:ff:ff 
    2: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master bond0 state DOWN mode DEFAULT group default qlen 1000
        link/ether 52:54:00:66:24:cd brd ff:ff:ff:ff:ff:ff
Zaboj Campula Feb. 28, 2017, 8:07 p.m. UTC | #10
On Mon, 2017-02-27 at 17:38 +0100, Jiri Benc wrote:
> 
> It produces dot (graphviz) output or json and has no dependencies on
> anything GUI related. Just run it on the remote machine and display the
> output locally.
> 
> ssh root@remote plotnetcfg | dot -Tpdf | whatever_pdf_viewer
> 
> Note that some pdf viewers can't read stdin or require dash as the
> parameter to use stdin.
> 
> I don't think it's possible to enhance iproute2 to display the network
> interface dependencies in an useful way. It's just too complex. It's
> not even a (undirected) tree.

I know there is an option to execute something remotely but I think 
a pure text output may be useful to get a quick overview.

Well it is impossible to draw a simple tree showing the configuration
exactly with all details. May be it is too ambitious to draw a tree
at all. But neither directory structure is a tree (when consider links)
and there are a plenty of tools showing directory tree.
Zaboj Campula Feb. 28, 2017, 8:19 p.m. UTC | #11
On Mon, 2017-02-27 at 10:55 -0800, Stephen Hemminger wrote:
> 
> Another alternative format would be to make -tree a output modifier and ident (like ps tree options).
> 
> $ ip -t link
> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
>     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
> 8: bond0 <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
>     link/ether 52:54:00:66:24:cd brd ff:ff:ff:ff:ff:ff 
>     2: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master bond0 state DOWN mode DEFAULT group default qlen 1000
>         link/ether 52:54:00:66:24:cd brd ff:ff:ff:ff:ff:ff

OK, it looks better because the information is complete.

I looked at several tree printing utilities and I like
the lsblk format. But tabular format does not fit the ip-show
well. I will try to indent the current ip-show output may be
with fancy lines.
Jiri Pirko Feb. 28, 2017, 9:47 p.m. UTC | #12
Tue, Feb 28, 2017 at 09:19:23PM CET, zaboj.campula@post.cz wrote:
>On Mon, 2017-02-27 at 10:55 -0800, Stephen Hemminger wrote:
>> 
>> Another alternative format would be to make -tree a output modifier and ident (like ps tree options).
>> 
>> $ ip -t link
>> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
>>     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
>> 8: bond0 <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
>>     link/ether 52:54:00:66:24:cd brd ff:ff:ff:ff:ff:ff 
>>     2: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master bond0 state DOWN mode DEFAULT group default qlen 1000
>>         link/ether 52:54:00:66:24:cd brd ff:ff:ff:ff:ff:ff
>
>OK, it looks better because the information is complete.
>
>I looked at several tree printing utilities and I like
>the lsblk format. But tabular format does not fit the ip-show
>well. I will try to indent the current ip-show output may be
>with fancy lines.

I don't really think that the output you provide has any value. As JiriB
wrote, it is really problematic to draw relationships between devices.
Let's take OVS as a very non-trivial example. Your patch aims to handle
only the very basic ones.
Jiri Benc March 1, 2017, 10:22 a.m. UTC | #13
On Tue, 28 Feb 2017 20:07:37 +0000, Zaboj Campula wrote:
> Well it is impossible to draw a simple tree showing the configuration
> exactly with all details. May be it is too ambitious to draw a tree
> at all.

I tried that and failed. I didn't want to have something that would
work only "somehow" as that would create confusion instead of helping.
See the example below.

> But neither directory structure is a tree (when consider links)
> and there are a plenty of tools showing directory tree.

Directory structure *is* a tree. Symlinks are a special case, for the
purpose of displaying the tree they're just files. Hardlinks can be
ignored when displaying a tree (because there are no hardlinks to
directories). We don't have any of that.

Consider the very simple case of an interface with two vlan interfaces
and both of them in a bridge.

         vlan0
      /        \
eth0             br0
      \        /
         vlan1

You can't represent this in a tree view. And this is just a very simple
example, in reality it tends to be much more complex.

 Jiri
Zaboj Campula March 2, 2017, 7:51 p.m. UTC | #14
On Wed, 2017-03-01 at 11:22 +0100, Jiri Benc wrote:
> On Tue, 28 Feb 2017 20:07:37 +0000, Zaboj Campula wrote:
> > Well it is impossible to draw a simple tree showing the configuration
> > exactly with all details. May be it is too ambitious to draw a tree
> > at all.
> 
> I tried that and failed. I didn't want to have something that would
> work only "somehow" as that would create confusion instead of helping.

OK, I give up. My patch was naive and I deleted it.
Nevertheless I still think it would be useful to show network
interfaces dependencies in a pure text format.


> Consider the very simple case of an interface with two vlan interfaces
> and both of them in a bridge.
> 
>          vlan0
>       /        \
> eth0             br0
>       \        /
>          vlan1
> 
> You can't represent this in a tree view. And this is just a very simple
> example, in reality it tends to be much more complex.

Perhaps something like that:
eth0
    vlan0
        br0
    vlan1
        br0
diff mbox

Patch

diff --git a/include/utils.h b/include/utils.h
index 22369e0..f1acf4d 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -20,6 +20,7 @@  extern int show_raw;
 extern int resolve_hosts;
 extern int oneline;
 extern int brief;
+extern int tree;;
 extern int timestamp;
 extern int timestamp_short;
 extern const char * _SL_;
diff --git a/ip/ip.c b/ip/ip.c
index 07050b0..29747a5 100644
--- a/ip/ip.c
+++ b/ip/ip.c
@@ -33,6 +33,7 @@  int show_details;
 int resolve_hosts;
 int oneline;
 int brief;
+int tree;
 int timestamp;
 const char *_SL_;
 int force;
@@ -57,7 +58,7 @@  static void usage(void)
 "                    -h[uman-readable] | -iec |\n"
 "                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n"
 "                    -4 | -6 | -I | -D | -B | -0 |\n"
-"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |\n"
+"                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] | -tr[ee] |\n"
 "                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n"
 "                    -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}\n");
 	exit(-1);
@@ -257,6 +258,8 @@  int main(int argc, char **argv)
 			batch_file = argv[1];
 		} else if (matches(opt, "-brief") == 0) {
 			++brief;
+		} else if (matches(opt, "-tree") == 0) {
+			++tree;
 		} else if (matches(opt, "-rcvbuf") == 0) {
 			unsigned int size;
 
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index 242c6ea..5ebcb1a 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
@@ -1534,6 +1534,69 @@  static int iplink_filter_req(struct nlmsghdr *nlh, int reqlen)
 	return 0;
 }
 
+static int has_master(struct nlmsg_chain *linfo, int index)
+{
+	struct nlmsg_list *l;
+	struct rtattr *tb[IFLA_MAX+1];
+	int len;
+	for (l = linfo->head; l; l = l->next) {
+		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+		len = l->h.nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+		if (tb[IFLA_MASTER] && *(int *)RTA_DATA(tb[IFLA_MASTER]) == index)
+			return 1;
+	}
+	return 0;
+}
+
+static struct nlmsg_list *get_master(struct nlmsg_chain *linfo, struct rtattr **tb)
+{
+	struct nlmsg_list *l;
+	if (tb[IFLA_MASTER]) {
+		int master = *(int *)RTA_DATA(tb[IFLA_MASTER]);
+		for (l = linfo->head; l; l = l->next) {
+			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+			if (ifi->ifi_index == master)
+				return l;
+		}
+	}
+	return NULL;
+}
+
+static void print_dev_tree_item(struct nlmsg_chain *linfo, struct nlmsg_list *l, int indent) {
+	char *name;
+	int len;
+	struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+	struct rtattr *tb[IFLA_MAX+1];
+	len = l->h.nlmsg_len;
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+	name = (char *)(tb[IFLA_IFNAME] ? rta_getattr_str(tb[IFLA_IFNAME]) : "<nil>");
+
+	printf("%*s%s\n", indent * 4, "", name);
+
+	struct nlmsg_list *master = get_master(linfo, tb);
+	if (master) {
+		if (indent > 8) {
+			printf("%*s...\n", (indent + 1) * 4, "");
+		} else {
+			print_dev_tree_item(linfo, master, indent + 1);
+		}
+	}
+}
+
+static void print_devtree(struct nlmsg_chain *linfo)
+{
+	struct nlmsg_list *l;
+	for (l = linfo->head; l; l = l->next) {
+		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+		if (!has_master(linfo, ifi->ifi_index)) {
+			print_dev_tree_item(linfo, l, 0);
+		}
+	}
+}
+
 static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
 {
 	struct nlmsg_chain linfo = { NULL, NULL};
@@ -1742,23 +1805,27 @@  static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
 		ipaddr_filter(&linfo, &ainfo);
 	}
 
-	for (l = linfo.head; l; l = l->next) {
-		int res = 0;
-		struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
-
-		if (brief) {
-			if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
+	if (tree) {
+		print_devtree(&linfo);
+	} else {
+		for (l = linfo.head; l; l = l->next) {
+			int res = 0;
+			struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+
+			 if (brief) {
+				if (print_linkinfo_brief(NULL, &l->h, stdout) == 0)
+					if (filter.family != AF_PACKET)
+						print_selected_addrinfo(ifi,
+									ainfo.head,
+									stdout);
+			} else if (no_link ||
+				 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
 				if (filter.family != AF_PACKET)
 					print_selected_addrinfo(ifi,
-								ainfo.head,
-								stdout);
-		} else if (no_link ||
-			 (res = print_linkinfo(NULL, &l->h, stdout)) >= 0) {
-			if (filter.family != AF_PACKET)
-				print_selected_addrinfo(ifi,
-							ainfo.head, stdout);
-			if (res > 0 && !do_link && show_stats)
-				print_link_stats(stdout, &l->h);
+								ainfo.head, stdout);
+				if (res > 0 && !do_link && show_stats)
+					print_link_stats(stdout, &l->h);
+			}
 		}
 	}
 	fflush(stdout);