Subject: Re: [PATCH 5/7] bridge: allow creating/deleting fdb entries via netlink

From: Stephen Hemminger <[email-masked]>
Date: Wed, 5 Oct 2011 12:13:05 -0700

On Wed, 5 Oct 2011 21:06:54 +0200
Kevin Wilson <[email-masked]> wrote:

> Hello all,
> I would appreciate if someone can elaborate about "bridge extensions
> to iproute2" mentioned here.
> I downloaded latest iproute2 git tree and did not find it there.
> googling for it did not gave much info about it.
> I will appreciate if someone can tell  who develop it, what is the
> status, site, repository tree, etc.
> 
> rgs,
> Kevin

The patch to handle this was posted, but is not committed to the tree yet.

--
>>From 8e3d00d0602420dadc3d23877a4995ea3d7496c2 Mon Sep 17 00:00:00 2001
From: Stephen Hemminger <[email-masked]>
Date: Tue, 4 Oct 2011 09:31:58 -0700
Subject: [PATCH] Add support for bridging control.

This adds a new 'bridge' command which is the bridging equivalent of
the ip command.
---
 Makefile          |    2 +-
 br/.gitignore     |    1 +
 br/Makefile       |   14 +++
 br/br_common.h    |   13 +++
 br/bridge.c       |  104 ++++++++++++++++++++++
 br/fdb.c          |  245 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 br/link.c         |  142 +++++++++++++++++++++++++++++++
 br/monitor.c      |  138 ++++++++++++++++++++++++++++++
 man/man8/bridge.8 |  177 ++++++++++++++++++++++++++++++++++++++
 9 files changed, 835 insertions(+), 1 deletions(-)
 create mode 100644 br/.gitignore
 create mode 100644 br/Makefile
 create mode 100644 br/br_common.h
 create mode 100644 br/bridge.c
 create mode 100644 br/fdb.c
 create mode 100644 br/link.c
 create mode 100644 br/monitor.c
 create mode 100644 man/man8/bridge.8

diff --git a/Makefile b/Makefile
index d1ace1f..f1d360a 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ CCOPTS = -D_GNU_SOURCE -O2 -Wstrict-prototypes -Wall
 CFLAGS = $(CCOPTS) -I../include $(DEFINES)
 YACCFLAGS = -d -t -v
 
-SUBDIRS=lib ip tc misc netem genl
+SUBDIRS=lib ip tc br misc netem genl
 
 LIBNETLINK=../lib/libnetlink.a ../lib/libutil.a
 LDLIBS += $(LIBNETLINK)
diff --git a/br/.gitignore b/br/.gitignore
new file mode 100644
index 0000000..7096907
--- /dev/null
+++ b/br/.gitignore
@@ -0,0 +1 @@
+bridge
diff --git a/br/Makefile b/br/Makefile
new file mode 100644
index 0000000..9a6743e
--- /dev/null
+++ b/br/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 bridge $(DESTDIR)$(SBINDIR)
+
+clean:
+	rm -f $(BROBJ) bridge
+
diff --git a/br/br_common.h b/br/br_common.h
new file mode 100644
index 0000000..ec1671d
--- /dev/null
+++ b/br/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/br/bridge.c b/br/bridge.c
new file mode 100644
index 0000000..9e5f69c
--- /dev/null
+++ b/br/bridge.c
@@ -0,0 +1,104 @@
+/*
+ * Get/set/delete bridge with netlink
+ *
+ * Authors:	Stephen Hemminger <[email-masked]>
+ */
+
+#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();
+}
diff --git a/br/fdb.c b/br/fdb.c
new file mode 100644
index 0000000..d849f97
--- /dev/null
+++ b/br/fdb.c
@@ -0,0 +1,245 @@
+/*
+ * Get/set/delete fdb table with netlink
+ *
+ * Authors:	Stephen Hemminger <[email-masked]>
+ */
+
+#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)
+{
+	FILE *fp = 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)
+		fprintf(fp, "Deleted ");
+
+	if (tb[NDA_LLADDR])
+		addr = RTA_DATA(tb[NDA_LLADDR]);
+	else {
+		fprintf(stderr, "missing lladdr\n");
+		return -1;
+	}
+
+	fprintf(fp, "%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]);
+
+		fprintf(fp, "\t%8s", fmt_time(b1, sizeof(b1), ci->ndm_updated));
+		fprintf(fp, " %8s", fmt_time(b1, sizeof(b1), ci->ndm_used));
+	}
+	fprintf(fp, "\n");
+	fflush(fp);
+	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, stdout, NULL, 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, NULL, 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/br/link.c b/br/link.c
new file mode 100644
index 0000000..1b9541d
--- /dev/null
+++ b/br/link.c
@@ -0,0 +1,142 @@
+
+#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;
+}
diff --git a/br/monitor.c b/br/monitor.c
new file mode 100644
index 0000000..37468e6
--- /dev/null
+++ b/br/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 <[email-masked]>
+ *
+ */
+
+#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;
+}
+
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
new file mode 100644
index 0000000..8a5d21e
--- /dev/null
+++ b/man/man8/bridge.8
@@ -0,0 +1,177 @@
+.TH BRIDGE 8 "4 October 2011" "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 ip
+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 fona  link.
+
+.P
+The corresponding commands display fdb entries, add new entries,
+and delete old ones.
+
+.SS bridge fdb add - add a new neighbour 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 neighbour 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 ip
+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 HISTORY
+.B bridge
+was written by Stephen Hemminger and uses kernel facilities added in Linux 3.0
+.SH SEE ALSO
+.BR ip (8)
+.br
+.RB "Please direct bugreports and patches to: " <[email-masked]>
+
+.SH AUTHOR
+Original Manpage by Stephen Hemminger
-- 
1.7.6.3