Commit d04bc300 authored by Stephen Hemminger's avatar Stephen Hemminger

Add bridge command

New tool to allow manipulating forwarding entries and monitoring
bridge events.
parent bc84585e
......@@ -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)
......
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
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;
/*
* Get/set/delete bridge with netlink
*
* Authors: Stephen Hemminger <shemminger@vyatta.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#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();
}
/*
* Get/set/delete fdb table with netlink
*
* Authors: Stephen Hemminger <shemminger@vyatta.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>
#include <linux/if_bridge.h>
#include <linux/if_ether.h>
#include <linux/neighbour.h>
#include <string.h>
#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);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <linux/if.h>
#include <linux/if_bridge.h>
#include <string.h>
#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]) : "<nil>");
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;
}
/*
* 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 <shemminger@vyatta.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <net/if.h>
#include <netinet/in.h>
#include <linux/if_bridge.h>
#include <linux/neighbour.h>
#include <string.h>
#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;
}
......@@ -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
......
.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: " <netdev@vger.kernel.org>
.SH AUTHOR
Original Manpage by Stephen Hemminger
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment