From: Stephen Hemminger Date: Wed, 1 Aug 2012 22:23:49 +0000 (-0700) Subject: Add bridge command X-Git-Tag: v3.5.0~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d04bc300c3e367702817fed6eea55e997a328c66;p=thirdparty%2Fiproute2.git Add bridge command New tool to allow manipulating forwarding entries and monitoring bridge events. --- diff --git a/Makefile b/Makefile index c10795593..917862b05 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ WFLAGS = -Wall -Wstrict-prototypes CFLAGS = $(WFLAGS) $(CCOPTS) -I../include $(DEFINES) YACCFLAGS = -d -t -v -SUBDIRS=lib ip tc misc netem genl man +SUBDIRS=lib ip tc bridge misc netem genl man LIBNETLINK=../lib/libnetlink.a ../lib/libutil.a LDLIBS += $(LIBNETLINK) diff --git a/bridge/.gitignore b/bridge/.gitignore new file mode 100644 index 000000000..7096907a5 --- /dev/null +++ b/bridge/.gitignore @@ -0,0 +1 @@ +bridge diff --git a/bridge/Makefile b/bridge/Makefile new file mode 100644 index 000000000..0c333bf7d --- /dev/null +++ b/bridge/Makefile @@ -0,0 +1,14 @@ +BROBJ = bridge.o fdb.o monitor.o link.o + +include ../Config + +all: bridge + +bridge: $(BROBJ) $(LIBNETLINK) + +install: all + install -m 0755 br $(DESTDIR)$(SBINDIR) + +clean: + rm -f $(BROBJ) br + diff --git a/bridge/br_common.h b/bridge/br_common.h new file mode 100644 index 000000000..ec1671d7f --- /dev/null +++ b/bridge/br_common.h @@ -0,0 +1,13 @@ +extern int print_linkinfo(const struct sockaddr_nl *who, + struct nlmsghdr *n, + void *arg); +extern int print_fdb(const struct sockaddr_nl *who, + struct nlmsghdr *n, void *arg); + +extern int do_fdb(int argc, char **argv); +extern int do_monitor(int argc, char **argv); + +extern int show_stats; +extern int show_detail; +extern int timestamp; +extern struct rtnl_handle rth; diff --git a/bridge/bridge.c b/bridge/bridge.c new file mode 100644 index 000000000..9e5f69ced --- /dev/null +++ b/bridge/bridge.c @@ -0,0 +1,104 @@ +/* + * Get/set/delete bridge with netlink + * + * Authors: Stephen Hemminger + */ + +#include +#include +#include +#include +#include + +#include "SNAPSHOT.h" +#include "utils.h" +#include "br_common.h" + +struct rtnl_handle rth = { .fd = -1 }; +int resolve_hosts; +int show_stats; +int show_details; +int timestamp; + +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, +"Usage: br [ OPTIONS ] OBJECT { COMMAND | help }\n" +"where OBJECT := { fdb | monitor }\n" +" OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails]\n" ); + exit(-1); +} + +static int do_help(int argc, char **argv) +{ + usage(); +} + + +static const struct cmd { + const char *cmd; + int (*func)(int argc, char **argv); +} cmds[] = { + { "fdb", do_fdb }, + { "monitor", do_monitor }, + { "help", do_help }, + { 0 } +}; + +static int do_cmd(const char *argv0, int argc, char **argv) +{ + const struct cmd *c; + + for (c = cmds; c->cmd; ++c) { + if (matches(argv0, c->cmd) == 0) + return c->func(argc-1, argv+1); + } + + fprintf(stderr, "Object \"%s\" is unknown, try \"br help\".\n", argv0); + return -1; +} + +int +main(int argc, char **argv) +{ + while (argc > 1) { + char *opt = argv[1]; + if (strcmp(opt,"--") == 0) { + argc--; argv++; + break; + } + if (opt[0] != '-') + break; + if (opt[1] == '-') + opt++; + + if (matches(opt, "-help") == 0) { + usage(); + } else if (matches(opt, "-Version") == 0) { + printf("br utility, 0.0\n"); + exit(0); + } else if (matches(opt, "-stats") == 0 || + matches(opt, "-statistics") == 0) { + ++show_stats; + } else if (matches(opt, "-details") == 0) { + ++show_details; + } else if (matches(opt, "-timestamp") == 0) { + ++timestamp; + } else { + fprintf(stderr, "Option \"%s\" is unknown, try \"br -help\".\n", opt); + exit(-1); + } + argc--; argv++; + } + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + if (argc > 1) + return do_cmd(argv[1], argc-1, argv+1); + + rtnl_close(&rth); + usage(); +} diff --git a/bridge/fdb.c b/bridge/fdb.c new file mode 100644 index 000000000..04984f106 --- /dev/null +++ b/bridge/fdb.c @@ -0,0 +1,244 @@ +/* + * Get/set/delete fdb table with netlink + * + * Authors: Stephen Hemminger + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libnetlink.h" +#include "br_common.h" +#include "utils.h" + +int filter_index; + +static void usage(void) +{ + fprintf(stderr, "Usage: br fdb { add | del | replace } ADDR dev DEV\n"); + fprintf(stderr, " br fdb {show} [ dev DEV ]\n"); + exit(-1); +} + +static const char *state_n2a(unsigned s) +{ + static char buf[32]; + + if (s & NUD_PERMANENT) + return "local"; + + if (s & NUD_NOARP) + return "static"; + + if (s & NUD_STALE) + return "stale"; + + if (s & NUD_REACHABLE) + return ""; + + sprintf(buf, "state=%#x", s); + return buf; +} + +static char *fmt_time(char *b, size_t l, unsigned long tick) +{ + static int hz; + + if (hz == 0) + hz = __get_user_hz(); + + snprintf(b, l, "%lu.%02lu", tick / hz, ((tick % hz) * hz) / 100); + return b; +} + +int print_fdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + struct ndmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * tb[NDA_MAX+1]; + const __u8 *addr = NULL; + char b1[32]; + + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (r->ndm_family != AF_BRIDGE) + return 0; + + if (filter_index && filter_index != r->ndm_ifindex) + return 0; + + parse_rtattr(tb, NDA_MAX, NDA_RTA(r), + n->nlmsg_len - NLMSG_LENGTH(sizeof(*r))); + + if (n->nlmsg_type == RTM_DELNEIGH) + printf("Deleted "); + + if (tb[NDA_LLADDR]) + addr = RTA_DATA(tb[NDA_LLADDR]); + else { + fprintf(stderr, "missing lladdr\n"); + return -1; + } + + printf("%s\t%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\t%s", + ll_index_to_name(r->ndm_ifindex), + addr[0], addr[1], addr[2], + addr[3], addr[4], addr[5], + state_n2a(r->ndm_state)); + + if (show_stats && tb[NDA_CACHEINFO]) { + struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]); + + printf("\t%8s", fmt_time(b1, sizeof(b1), ci->ndm_updated)); + printf(" %8s", fmt_time(b1, sizeof(b1), ci->ndm_used)); + } + printf("\n"); + + return 0; +} + +static int fdb_show(int argc, char **argv) +{ + char *filter_dev = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (filter_dev) + duparg("dev", *argv); + filter_dev = *argv; + } + argc--; argv++; + } + + if (filter_dev) { + if ((filter_index = if_nametoindex(filter_dev)) == 0) { + fprintf(stderr, "Cannot find device \"%s\"\n", filter_dev); + return -1; + } + } + + if (rtnl_wilddump_request(&rth, PF_BRIDGE, RTM_GETNEIGH) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + printf("port\tmac addr\t\tflags%s\n", + show_stats ? "\t updated used" : ""); + + if (rtnl_dump_filter(&rth, print_fdb, NULL) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + + return 0; +} + +static int fdb_modify(int cmd, int flags, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct ndmsg ndm; + char buf[256]; + } req; + char *addr = NULL; + char *d = NULL; + char abuf[ETH_ALEN]; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST|flags; + req.n.nlmsg_type = cmd; + req.ndm.ndm_family = PF_BRIDGE; + req.ndm.ndm_state = NUD_NOARP; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "local") == 0) { + req.ndm.ndm_state = NUD_PERMANENT; + } else if (strcmp(*argv, "temp") == 0) { + req.ndm.ndm_state = NUD_REACHABLE; + } else { + if (strcmp(*argv, "to") == 0) { + NEXT_ARG(); + } + if (matches(*argv, "help") == 0) { + NEXT_ARG(); + } + if (addr) + duparg2("to", *argv); + addr = *argv; + } + argc--; argv++; + } + + if (d == NULL || addr == NULL) { + fprintf(stderr, "Device and address are required arguments.\n"); + exit(-1); + } + + if (sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + abuf, abuf+1, abuf+2, + abuf+3, abuf+4, abuf+5) != 6) { + fprintf(stderr, "Invalid mac address %s\n", addr); + exit(-1); + } + + addattr_l(&req.n, sizeof(req), NDA_LLADDR, abuf, ETH_ALEN); + + req.ndm.ndm_ifindex = ll_name_to_index(d); + if (req.ndm.ndm_ifindex == 0) { + fprintf(stderr, "Cannot find device \"%s\"\n", d); + return -1; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0) + exit(2); + + return 0; +} + +int do_fdb(int argc, char **argv) +{ + ll_init_map(&rth); + + if (argc > 0) { + if (matches(*argv, "add") == 0) + return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1); + if (matches(*argv, "change") == 0) + return fdb_modify(RTM_NEWNEIGH, NLM_F_REPLACE, argc-1, argv+1); + + if (matches(*argv, "replace") == 0) + return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1); + if (matches(*argv, "delete") == 0) + return fdb_modify(RTM_DELNEIGH, 0, argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return fdb_show(argc-1, argv+1); + if (matches(*argv, "help") == 0) + usage(); + } else + return fdb_show(0, NULL); + + fprintf(stderr, "Command \"%s\" is unknown, try \"ip neigh help\".\n", *argv); + exit(-1); +} diff --git a/bridge/link.c b/bridge/link.c new file mode 100644 index 000000000..1b9541d5c --- /dev/null +++ b/bridge/link.c @@ -0,0 +1,142 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "br_common.h" + +static const char *port_states[] = { + [BR_STATE_DISABLED] = "disabled", + [BR_STATE_LISTENING] = "listening", + [BR_STATE_LEARNING] = "learning", + [BR_STATE_FORWARDING] = "forwarding", + [BR_STATE_BLOCKING] = "blocking", +}; + +extern char *if_indextoname (unsigned int __ifindex, char *__ifname); + +static void print_link_flags(FILE *fp, unsigned flags) +{ + fprintf(fp, "<"); + if (flags & IFF_UP && !(flags & IFF_RUNNING)) + fprintf(fp, "NO-CARRIER%s", flags ? "," : ""); + flags &= ~IFF_RUNNING; +#define _PF(f) if (flags&IFF_##f) { \ + flags &= ~IFF_##f ; \ + fprintf(fp, #f "%s", flags ? "," : ""); } + _PF(LOOPBACK); + _PF(BROADCAST); + _PF(POINTOPOINT); + _PF(MULTICAST); + _PF(NOARP); + _PF(ALLMULTI); + _PF(PROMISC); + _PF(MASTER); + _PF(SLAVE); + _PF(DEBUG); + _PF(DYNAMIC); + _PF(AUTOMEDIA); + _PF(PORTSEL); + _PF(NOTRAILERS); + _PF(UP); + _PF(LOWER_UP); + _PF(DORMANT); + _PF(ECHO); +#undef _PF + if (flags) + fprintf(fp, "%x", flags); + fprintf(fp, "> "); +} + +static const char *oper_states[] = { + "UNKNOWN", "NOTPRESENT", "DOWN", "LOWERLAYERDOWN", + "TESTING", "DORMANT", "UP" +}; + +static void print_operstate(FILE *f, __u8 state) +{ + if (state >= sizeof(oper_states)/sizeof(oper_states[0])) + fprintf(f, "state %#x ", state); + else + fprintf(f, "state %s ", oper_states[state]); +} + +int print_linkinfo(const struct sockaddr_nl *who, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = arg; + int len = n->nlmsg_len; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct rtattr * tb[IFLA_MAX+1]; + char b1[IFNAMSIZ]; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if (len < 0) { + fprintf(stderr, "Message too short!\n"); + return -1; + } + + if (!(ifi->ifi_family == AF_BRIDGE || ifi->ifi_family == AF_UNSPEC)) + return 0; + + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); + + if (tb[IFLA_IFNAME] == NULL) { + fprintf(stderr, "BUG: nil ifname\n"); + return -1; + } + + if (n->nlmsg_type == RTM_DELLINK) + fprintf(fp, "Deleted "); + + fprintf(fp, "%d: %s ", ifi->ifi_index, + tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : ""); + + if (tb[IFLA_OPERSTATE]) + print_operstate(fp, *(__u8 *)RTA_DATA(tb[IFLA_OPERSTATE])); + + if (tb[IFLA_LINK]) { + SPRINT_BUF(b1); + int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]); + + if (iflink == 0) + fprintf(fp, "@NONE: "); + else { + fprintf(fp, "@%s: ", + if_indextoname(iflink, b1)); + } + } else { + fprintf(fp, ": "); + } + + print_link_flags(fp, ifi->ifi_flags); + + if (tb[IFLA_MTU]) + fprintf(fp, "mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); + + if (tb[IFLA_MASTER]) { + fprintf(fp, "master %s ", + if_indextoname(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1)); + } + + if (tb[IFLA_PROTINFO]) { + uint8_t state = *(uint8_t *)RTA_DATA(tb[IFLA_PROTINFO]); + if (state <= BR_STATE_BLOCKING) + fprintf(fp, "state %s", port_states[state]); + else + fprintf(fp, "state (%d)", state); + } + + + fprintf(fp, "\n"); + fflush(fp); + return 0; +} diff --git a/bridge/monitor.c b/bridge/monitor.c new file mode 100644 index 000000000..37468e6e8 --- /dev/null +++ b/bridge/monitor.c @@ -0,0 +1,138 @@ +/* + * brmonitor.c "br monitor" + * + * 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. + * + * Authors: Stephen Hemminger + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "br_common.h" + + +static void usage(void) __attribute__((noreturn)); +int prefix_banner; + +static void usage(void) +{ + fprintf(stderr, "Usage: br monitor\n"); + exit(-1); +} + +static int show_mark(FILE *fp, const struct nlmsghdr *n) +{ + char *tstr; + time_t secs = ((__u32*)NLMSG_DATA(n))[0]; + long usecs = ((__u32*)NLMSG_DATA(n))[1]; + tstr = asctime(localtime(&secs)); + tstr[strlen(tstr)-1] = 0; + fprintf(fp, "Timestamp: %s %lu us\n", tstr, usecs); + return 0; +} + +int accept_msg(const struct sockaddr_nl *who, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = arg; + + if (timestamp) + print_timestamp(fp); + + switch (n->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: + if (prefix_banner) + fprintf(fp, "[LINK]"); + + return print_linkinfo(who, n, arg); + + case RTM_NEWNEIGH: + case RTM_DELNEIGH: + if (prefix_banner) + fprintf(fp, "[NEIGH]"); + return print_fdb(who, n, arg); + + case 15: + return show_mark(fp, n); + + default: + return 0; + } + + +} + +int do_monitor(int argc, char **argv) +{ + char *file = NULL; + unsigned groups = ~RTMGRP_TC; + int llink=0; + int lneigh=0; + + rtnl_close(&rth); + + while (argc > 0) { + if (matches(*argv, "file") == 0) { + NEXT_ARG(); + file = *argv; + } else if (matches(*argv, "link") == 0) { + llink=1; + groups = 0; + } else if (matches(*argv, "fdb") == 0) { + lneigh = 1; + groups = 0; + } else if (strcmp(*argv, "all") == 0) { + groups = ~RTMGRP_TC; + prefix_banner=1; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + fprintf(stderr, "Argument \"%s\" is unknown, try \"br monitor help\".\n", *argv); + exit(-1); + } + argc--; argv++; + } + + if (llink) + groups |= nl_mgrp(RTNLGRP_LINK); + + if (lneigh) { + groups |= nl_mgrp(RTNLGRP_NEIGH); + } + + if (file) { + FILE *fp; + fp = fopen(file, "r"); + if (fp == NULL) { + perror("Cannot fopen"); + exit(-1); + } + return rtnl_from_file(fp, accept_msg, stdout); + } + + if (rtnl_open(&rth, groups) < 0) + exit(1); + ll_init_map(&rth); + + if (rtnl_listen(&rth, accept_msg, stdout) < 0) + exit(2); + + return 0; +} + diff --git a/man/man8/Makefile b/man/man8/Makefile index d4e1dd5c3..1b671a42b 100644 --- a/man/man8/Makefile +++ b/man/man8/Makefile @@ -4,7 +4,7 @@ MAN8PAGES = $(TARGETS) ip.8 arpd.8 lnstat.8 routel.8 rtacct.8 rtmon.8 ss.8 \ tc-bfifo.8 tc-cbq-details.8 tc-cbq.8 tc-drr.8 tc-htb.8 \ tc-pfifo.8 tc-pfifo_fast.8 tc-prio.8 tc-red.8 tc-sfq.8 \ tc-tbf.8 tc.8tc-codel.8 tc-fq_codel.8 tc-sfb.8 tc-netem.8 tc-choke.8 \ - rtstat.8 ctstat.8 nstat.8 routef.8 \ + bridge.8 rtstat.8 ctstat.8 nstat.8 routef.8 \ ip-tunnel.8 ip-rule.8 ip-ntable.8 \ ip-monitor.8 tc-stab.8 tc-hfsc.8 ip-xfrm.8 ip-netns.8 \ ip-neighbour.8 ip-mroute.8 ip-maddress.8 ip-addrlabel.8 diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 new file mode 100644 index 000000000..63d166b58 --- /dev/null +++ b/man/man8/bridge.8 @@ -0,0 +1,186 @@ +.TH BRIDGE 8 "1 August 2012" "iproute2" "Linux" +.SH NAME +bridge \- show / manipulate bridge addresses and devices +.SH SYNOPSIS + +.ad l +.in +8 +.ti -8 +.B bridge +.RI "[ " OPTIONS " ] " OBJECT " { " COMMAND " | " +.BR help " }" +.sp + +.ti -8 +.IR OBJECT " := { " +.BR fdb " | " monitor " }" +.sp + +.ti -8 +.IR OPTIONS " := { " +\fB\-V\fR[\fIersion\fR] | +\fB\-s\fR[\fItatistics\fR] + +.ti -8 +.BR "bridge fdb" " { " add " | " del " | " change " | " replace " } " +.I LLADDR +.B dev +.IR DEV " { " +.BR local " | " temp " }" + +.ti -8 +.BR "bridge fdb" " [ " show " ] [ " +.B dev +.IR DEV " ]" + +.ti -8 +.BR "bridge monitor" " [ " all " | " neigh " | " link " ]" + +.SH OPTIONS + +.TP +.BR "\-V" , " -Version" +print the version of the +.B bridge +utility and exit. + +.TP +.BR "\-s" , " \-stats", " \-statistics" +output more information. If the option +appears twice or more, the amount of information increases. +As a rule, the information is statistics or some time values. + + +.SH BRIDGE - COMMAND SYNTAX + +.SS +.I OBJECT + +.TP +.B fdb +- Forwarding Database entry. + +.SS +.I COMMAND + +Specifies the action to perform on the object. +The set of possible actions depends on the object type. +As a rule, it is possible to +.BR "add" , " delete" +and +.B show +(or +.B list +) objects, but some objects do not allow all of these operations +or have some additional commands. The +.B help +command is available for all objects. It prints +out a list of available commands and argument syntax conventions. +.sp +If no command is given, some default command is assumed. +Usually it is +.B list +or, if the objects of this class cannot be listed, +.BR "help" . + +.SH bridge fdb - forwarding database management + +.B fdb +objects contain known Ethernet addresses on a link. + +.P +The corresponding commands display fdb entries, add new entries, +and delete old ones. + +.SS bridge fdb add - add a new neighbor entry +.SS bridge fdb change - change an existing entry +.SS bridge fdb replace - add a new entry or change an existing one + +These commands create new neighbor records or update existing ones. + +.TP +.BI "ADDRESS" +the Ethernet MAC address. + +.TP +.BI dev " NAME" +the interface to which this address is associated. + +.TP +.in +8 +.B local +- the address is associated with a local interface on the system +and is never forwarded. +.sp + +.B temp +- the address is a dynamic entry, and will be removed if not used. +.sp + +.in -8 + +.SS bridge fdb delete - delete a forwarding database entry +This command removes an existing fdb entry. + +.PP +The arguments are the same as with +.BR "bridge fdb add" , + +.SS bridge fdb show - list forwarding entries. + +This commands displays current forwarding table. + +.PP +With the +.B -statistics +option, the command becomes verbose. It prints out the last updated +and last used time for each entry. + +.SH bridge monitor - state monitoring + +The +.B bridge +utility can monitor the state of devices and addresses +continuously. This option has a slightly different format. +Namely, the +.B monitor +command is the first in the command line and then the object list follows: + +.BR "bridge monitor" " [ " all " |" +.IR LISTofOBJECTS " ]" + +.I OBJECT-LIST +is the list of object types that we want to monitor. +It may contain +.BR link ", and " fdb "." +If no +.B file +argument is given, +.B bridge +opens RTNETLINK, listens on it and dumps state changes in the format +described in previous sections. + +.P +If a file name is given, it does not listen on RTNETLINK, +but opens the file containing RTNETLINK messages saved in binary format +and dumps them. Such a history file can be generated with the + + +.SH NOTES +This command uses facilities added in Linux 3.0. + +Although the forwarding table is maintained on a per-bridge device basis +the bridge device is not part of the syntax. This is a limitation of the +underlying netlink neighbour message protocol. When displaying the +forwarding table, entries for all bridges are displayed. +Add/delete/modify commands determine the underlying bridge device +based on the bridge to which the coresponding ethernet device is attached. + + +.SH SEE ALSO +.BR ip (8) +.br +.RB "Please direct bugreports and patches to: " + +.SH AUTHOR +Original Manpage by Stephen Hemminger