#include <openflow/openflow-netlink.h>
#include <signal.h>
#include <stdlib.h>
+#include <strings.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
tag_type iface_tag; /* Tag associated with iface_idx. */
};
+#define MAX_MIRRORS 32
+typedef uint32_t mirror_mask_t;
+#define MIRROR_MASK_C(X) UINT32_C(X)
+BUILD_ASSERT_DECL(sizeof(mirror_mask_t) * CHAR_BIT >= MAX_MIRRORS);
+struct mirror {
+ struct bridge *bridge;
+ size_t idx;
+ char *name;
+
+ /* Selection criteria. */
+ struct svec src_ports;
+ struct svec dst_ports;
+ int *vlans;
+ size_t n_vlans;
+
+ /* Transformations. */
+ int set_vlan; /* New VLAN tag or -1 to keep original tag. */
+
+ /* Output. */
+ struct port *out_port;
+};
+
#define FLOOD_PORT ((struct port *) 1) /* The 'flood' output port. */
struct port {
struct bridge *bridge;
tag_type active_iface_tag; /* Tag for bcast flows. */
tag_type no_ifaces_tag; /* Tag for flows when all ifaces disabled. */
int updelay, downdelay; /* Delay before iface goes up/down, in ms. */
+
+ /* Port mirroring info. */
+ mirror_mask_t src_mirrors; /* Mirrors triggered when packet received. */
+ mirror_mask_t dst_mirrors; /* Mirrors triggered when packet sent. */
};
#define DP_MAX_PORTS 255
struct stats_mgr *stats_mgr;
struct stats_request *flow_rq; /* Current flow statistics request. */
time_t next_stats_request;
+
+ /* Port mirroring. */
+ struct mirror *mirrors[MAX_MIRRORS];
};
/* List of all bridges. */
static void port_create(struct bridge *, const char *name);
static void port_reconfigure(struct port *);
static void port_destroy(struct port *);
+static struct port *port_lookup(const struct bridge *, const char *name);
static struct port *port_from_dp_ifidx(const struct bridge *,
uint16_t dp_ifidx);
+static void mirror_create(struct bridge *, const char *name);
+static void mirror_destroy(struct mirror *);
+static void mirror_reconfigure(struct bridge *);
+static void mirror_reconfigure_one(struct mirror *);
+static bool vlan_is_mirrored(const struct mirror *, int vlan);
+
static void iface_create(struct port *, const char *name);
static void iface_destroy(struct iface *);
static struct iface *iface_lookup(const struct bridge *, const char *name);
br->sc_retries = 0;
br->sc_state = SC_UNSTARTED;
}
+
+ mirror_reconfigure(br);
}
static void
*q = tmp;
}
+/* Moves all the ft_dsts with vlan == 'vlan' to the front of the 'n_dsts' in
+ * 'dsts'. (This may help performance by reducing the number of VLAN changes
+ * that we push over OpenFlow. We could in fact fully sort the array by vlan,
+ * but in most cases there are at most two different vlan tags so that's
+ * possibly overkill.) */
+static void
+partition_dsts(struct ft_dst *dsts, size_t n_dsts, int vlan)
+{
+ struct ft_dst *first = dsts;
+ struct ft_dst *last = dsts + n_dsts;
+
+ while (first != last) {
+ /* Invariants:
+ * - All ft_dsts < first have vlan == 'vlan'.
+ * - All ft_dsts >= last have vlan != 'vlan'.
+ * - first < last. */
+ while (first->vlan == vlan) {
+ if (++first == last) {
+ return;
+ }
+ }
+
+ /* Same invariants, plus one additional:
+ * - first->vlan != vlan.
+ */
+ while (last[-1].vlan != vlan) {
+ if (--last == first) {
+ return;
+ }
+ }
+
+ /* Same invariants, plus one additional:
+ * - last[-1].vlan == vlan.*/
+ swap_dst(first++, --last);
+ }
+}
+
static void *
add_action_header(struct ofpbuf *buf, uint16_t type)
{
}
}
+static int
+mirror_mask_ffs(mirror_mask_t mask)
+{
+ BUILD_ASSERT_DECL(sizeof(unsigned int) >= sizeof(mask));
+ return ffs(mask);
+}
+
+static bool
+dst_is_duplicate(const struct ft_dst *dsts, size_t n_dsts,
+ const struct ft_dst *test)
+{
+ size_t i;
+ for (i = 0; i < n_dsts; i++) {
+ if (dsts[i].vlan == test->vlan && dsts[i].dp_ifidx == test->dp_ifidx) {
+ return true;
+ }
+ }
+ return false;
+}
+
static size_t
compose_dsts(const struct bridge *br, const struct flow *flow, uint16_t vlan,
const struct port *in_port, const struct port *out_port,
struct ft_dst dsts[], tag_type *tags)
{
+ mirror_mask_t mirrors = in_port->src_mirrors;
+ struct ft_dst *dst; /* First unused 'dsts' element. */
+
+ dst = dsts;
if (out_port == FLOOD_PORT) {
/* Flood. */
/* XXX use OFPP_FLOOD if no vlans or bonding. */
- struct ft_dst *dst; /* Next element in 'dsts'. */
- struct ft_dst *vlan_dsts; /* First 'dsts' with vlan != flow->dl_vlan */
size_t i;
- dst = vlan_dsts = dsts;
for (i = 0; i < br->n_ports; i++) {
struct port *port = br->ports[i];
if (port != in_port && (!port->vlan || vlan == port->vlan)
&& set_dst(dst, flow, in_port, port, tags)) {
- /* Put destinations for original VLAN at the front, so that we
- * don't have to add actions to set the VLAN tag for those. */
- if (dst->vlan == ntohs(flow->dl_vlan)) {
- swap_dst(dst, vlan_dsts++);
- }
+ mirrors |= port->dst_mirrors;
dst++;
}
}
+ } else if (out_port && set_dst(dst, flow, in_port, out_port, tags)) {
+ mirrors |= out_port->dst_mirrors;
+ dst++;
+ }
- /* XXX Sort 'vlan_dsts' through 'dst' by VLAN, to reduce number of
- * VLAN-setting actions to minimum. */
- return dst - dsts;
- } else if (out_port) {
- /* Unicast. */
- return set_dst(dsts, flow, in_port, out_port, tags);
- } else {
- /* Drop. */
- return 0;
+ while (mirrors) {
+ struct mirror *m = br->mirrors[mirror_mask_ffs(mirrors) - 1];
+ if ((!m->n_vlans
+ || vlan_is_mirrored(m, in_port->vlan ? in_port->vlan : vlan))
+ && set_dst(dst, flow, in_port, m->out_port, tags)) {
+ if (m->set_vlan >= 0) {
+ dst->vlan = m->set_vlan;
+ }
+ if (!dst_is_duplicate(dsts, dst - dsts, dst)) {
+ dst++;
+ }
+ }
+ mirrors &= mirrors - 1;
}
+
+ partition_dsts(dsts, dst - dsts, ntohs(flow->dl_vlan));
+ return dst - dsts;
}
static void
struct port *del;
size_t i;
+ for (i = 0; i < MAX_MIRRORS; i++) {
+ struct mirror *m = br->mirrors[i];
+ if (m && m->out_port == port) {
+ mirror_destroy(m);
+ }
+ }
+
del = br->ports[port->port_idx] = br->ports[--br->n_ports];
del->port_idx = port->port_idx;
return iface ? iface->port : NULL;
}
+static struct port *
+port_lookup(const struct bridge *br, const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < br->n_ports; i++) {
+ struct port *port = br->ports[i];
+ if (!strcmp(port->name, name)) {
+ return port;
+ }
+ }
+ return NULL;
+}
+
static void
port_update_bonding(struct port *port)
{
{
return port_array_get(&br->ifaces, dp_ifidx);
}
+\f
+/* Port mirroring. */
+
+static void
+mirror_reconfigure(struct bridge *br)
+{
+ struct svec old_mirrors, new_mirrors;
+ int i;
+
+ /* Collect old and new mirrors. */
+ svec_init(&old_mirrors);
+ svec_init(&new_mirrors);
+ cfg_get_subsections(&new_mirrors, "mirror.%s", br->name);
+ for (i = 0; i < MAX_MIRRORS; i++) {
+ if (br->mirrors[i]) {
+ svec_add(&old_mirrors, br->mirrors[i]->name);
+ }
+ }
+
+ /* Get rid of deleted mirrors and add new mirrors. */
+ svec_sort(&old_mirrors);
+ svec_sort(&new_mirrors);
+ for (i = 0; i < MAX_MIRRORS; i++) {
+ struct mirror *m = br->mirrors[i];
+ if (m && !svec_contains(&new_mirrors, m->name)) {
+ mirror_destroy(m);
+ }
+ }
+ for (i = 0; i < new_mirrors.n; i++) {
+ const char *name = new_mirrors.names[i];
+ if (!svec_contains(&old_mirrors, name)) {
+ mirror_create(br, name);
+ }
+ }
+ svec_destroy(&old_mirrors);
+ svec_destroy(&new_mirrors);
+
+ /* Reconfigure all mirrors. */
+ for (i = 0; i < MAX_MIRRORS; i++) {
+ if (br->mirrors[i]) {
+ mirror_reconfigure_one(br->mirrors[i]);
+ }
+ }
+}
+
+static void
+mirror_create(struct bridge *br, const char *name)
+{
+ struct mirror *m;
+ size_t i;
+
+ for (i = 0; ; i++) {
+ if (i >= MAX_MIRRORS) {
+ VLOG_WARN("bridge %s: maximum of %d port mirrors reached, "
+ "cannot create %s", br->name, MAX_MIRRORS, name);
+ return;
+ }
+ if (!br->mirrors[i]) {
+ break;
+ }
+ }
+
+ VLOG_WARN("created port mirror %s on bridge %s", name, br->name);
+ bridge_flush(br);
+
+ br->mirrors[i] = m = xcalloc(1, sizeof *m);
+ m->bridge = br;
+ m->idx = i;
+ m->name = xstrdup(name);
+ svec_init(&m->src_ports);
+ svec_init(&m->dst_ports);
+ m->vlans = NULL;
+ m->n_vlans = 0;
+ m->set_vlan = -1;
+ m->out_port = NULL;
+}
+
+static void
+mirror_destroy(struct mirror *m)
+{
+ if (m) {
+ struct bridge *br = m->bridge;
+ size_t i;
+
+ for (i = 0; i < br->n_ports; i++) {
+ br->ports[i]->src_mirrors &= ~(MIRROR_MASK_C(1) << m->idx);
+ br->ports[i]->dst_mirrors &= ~(MIRROR_MASK_C(1) << m->idx);
+ }
+
+ m->bridge->mirrors[m->idx] = NULL;
+ free(m);
+
+ bridge_flush(br);
+ }
+}
+
+static void
+prune_ports(struct mirror *m, struct svec *ports)
+{
+ struct svec tmp;
+ size_t i;
+
+ svec_sort(ports);
+ svec_unique(ports);
+
+ svec_init(&tmp);
+ for (i = 0; i < ports->n; i++) {
+ const char *name = ports->names[i];
+ if (port_lookup(m->bridge, name)) {
+ svec_add(&tmp, name);
+ } else {
+ VLOG_WARN("mirror.%s.%s: cannot match on nonexistent port %s",
+ m->bridge->name, m->name, name);
+ }
+ }
+ svec_swap(ports, &tmp);
+ svec_destroy(&tmp);
+}
+
+static size_t
+prune_vlans(struct mirror *m, struct svec *vlan_strings, int **vlans)
+{
+ size_t n_vlans, i;
+
+ /* This isn't perfect: it won't combine "0" and "00", and the textual sort
+ * order won't give us numeric sort order. But that's good enough for what
+ * we need right now. */
+ svec_sort(vlan_strings);
+ svec_unique(vlan_strings);
+
+ *vlans = xmalloc(sizeof *vlans * vlan_strings->n);
+ n_vlans = 0;
+ for (i = 0; i < vlan_strings->n; i++) {
+ const char *name = vlan_strings->names[i];
+ int vlan;
+ if (!str_to_int(name, 10, &vlan) || vlan < 0 || vlan > 4095) {
+ VLOG_WARN("mirror.%s.%s.select.vlan: ignoring invalid VLAN %s",
+ m->bridge->name, m->name, name);
+ } else {
+ (*vlans)[n_vlans++] = vlan;
+ }
+ }
+ return n_vlans;
+}
+
+static bool
+vlan_is_mirrored(const struct mirror *m, int vlan)
+{
+ size_t i;
+
+ for (i = 0; i < m->n_vlans; i++) {
+ if (m->vlans[i] == vlan) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+mirror_reconfigure_one(struct mirror *m)
+{
+ char *pfx = xasprintf("mirror.%s.%s", m->bridge->name, m->name);
+ struct svec src_ports, dst_ports, ports;
+ struct svec vlan_strings;
+ mirror_mask_t mirror_bit;
+ const char *out_port_name;
+ struct port *out_port;
+ int set_vlan;
+ size_t n_vlans;
+ int *vlans;
+ size_t i;
+
+ /* Get output port. */
+ out_port_name = cfg_get_key(0, "mirror.%s.%s.output.port",
+ m->bridge->name, m->name);
+ if (!out_port_name) {
+ VLOG_WARN("%s.output.port: missing output port", pfx);
+ mirror_destroy(m);
+ free(pfx);
+ return;
+ }
+ out_port = port_lookup(m->bridge, out_port_name);
+ if (!out_port) {
+ VLOG_WARN("%s.output.port: bridge %s does not have a port "
+ "named %s", pfx, m->bridge->name, out_port_name);
+ mirror_destroy(m);
+ free(pfx);
+ return;
+ }
+
+ /* Get all the ports, and drop duplicates and ports that don't exist. */
+ svec_init(&src_ports);
+ svec_init(&dst_ports);
+ svec_init(&ports);
+ cfg_get_all_keys(&src_ports, "%s.select.src-port", pfx);
+ cfg_get_all_keys(&dst_ports, "%s.select.dst-port", pfx);
+ cfg_get_all_keys(&ports, "%s.select.port", pfx);
+ svec_append(&src_ports, &ports);
+ svec_append(&dst_ports, &ports);
+ svec_destroy(&ports);
+ prune_ports(m, &src_ports);
+ prune_ports(m, &dst_ports);
+ svec_print(&src_ports, "src_ports");
+ svec_print(&dst_ports, "dst_ports");
+
+ /* Get all the vlans, and drop duplicate and invalid vlans. */
+ svec_init(&vlan_strings);
+ cfg_get_all_keys(&vlan_strings, "%s.select.vlan", pfx);
+ n_vlans = prune_vlans(m, &vlan_strings, &vlans);
+ svec_destroy(&vlan_strings);
+
+ /* Determine the VLAN transformation. */
+ if (cfg_has("%s.transform.set-vlan", pfx)) {
+ set_vlan = cfg_get_int(0, "%s.transform.set-vlan", pfx);
+ if (set_vlan == 0) {
+ set_vlan = OFP_VLAN_NONE;
+ } else if (set_vlan < 0 || set_vlan > 4095) {
+ VLOG_WARN("%s.transform.set-vlan: %s is not a valid VLAN",
+ pfx, cfg_get_string(0, "%s.transform.set-vlan", pfx));
+ set_vlan = -1;
+ }
+ } else {
+ set_vlan = -1;
+ }
+
+ /* Update mirror data. */
+ if (!svec_equal(&m->src_ports, &src_ports)
+ || !svec_equal(&m->dst_ports, &dst_ports)
+ || m->n_vlans != n_vlans
+ || memcmp(m->vlans, vlans, sizeof *vlans * n_vlans)
+ || m->set_vlan != set_vlan
+ || m->out_port != out_port) {
+ bridge_flush(m->bridge);
+ }
+ svec_swap(&m->src_ports, &src_ports);
+ svec_swap(&m->dst_ports, &dst_ports);
+ free(m->vlans);
+ m->vlans = vlans;
+ m->n_vlans = n_vlans;
+ m->set_vlan = set_vlan;
+ m->out_port = out_port;
+
+ /* Update ports. */
+ mirror_bit = MIRROR_MASK_C(1) << m->idx;
+ for (i = 0; i < m->bridge->n_ports; i++) {
+ struct port *port = m->bridge->ports[i];
+
+ if (svec_contains(&m->src_ports, port->name)
+ || (m->n_vlans
+ && (!port->vlan || vlan_is_mirrored(m, port->vlan)))) {
+ port->src_mirrors |= mirror_bit;
+ } else {
+ port->src_mirrors &= ~mirror_bit;
+ }
+
+ if (svec_contains(&m->dst_ports, port->name)) {
+ port->dst_mirrors |= mirror_bit;
+ } else {
+ port->dst_mirrors &= ~mirror_bit;
+ }
+ }
+
+ /* Clean up. */
+ svec_destroy(&src_ports);
+ svec_destroy(&dst_ports);
+ free(pfx);
+}
+
.\" -*- nroff -*-
+.de TQ
+. br
+. ns
+. TP \$1
+..
.TH vswitchd.conf 5 "December 2008" "OpenFlow" "OpenFlow Manual"
.
.SH NAME
.fi
.RE
+.SS "Port mirroring"
+\fBvswitchd\fR may be configured to send certain frames to special
+"mirrored" ports, in addition to their normal destinations.
+.PP
+Up to 32 instances of port mirroring may be configured on a given
+bridge. Each must be given a name that is unique within the bridge.
+The keys associated with port mirroring instance \fIpmname\fR for
+bridge \fIbrname\fR begin with \fBmirror.\fIbrname\fB.\fIpmname\fR.
+.PP
+Each port mirroring instance requires three pieces of configuration:
+the frames to be mirrored, the types of transformations to be
+performed on the mirrored frames (if any), and the ports to which the
+frames should be sent. Each of these pieces is configured through a
+subsection of \fBmirror.\fIbrname\fB.\fIpmname\fR, named \fBselect\fR,
+\fBtransform\fB, and \fBoutput\fR, respectively.
+.PP
+.I "Selecting Frames to Mirror"
+.PP
+The values for the following keys, if specified, limit the frames that
+are chosen for mirroring. If none of these keys is specified, then
+all frames received by the bridge are mirrored. If more than one of
+these keys is specified, then a frame must meet all specified criteria
+to be mirrored.
+.TP
+\fBmirror.\fIbrname\fB.\fIpmname\fB.select.src-port = \fIport\fR
+.TQ
+\fBmirror.\fIbrname\fB.\fIpmname\fB.select.dst-port = \fIport\fR
+.TQ
+\fBmirror.\fIbrname\fB.\fIpmname\fB.select.port = \fIport\fR
+Frame received on \fIport\fR, output to \fIport\fR, or either received
+on or output to \fIport\fR, respectively. \fIport\fR must be part of
+\fIbridge\fR; that is, it must be listed on
+\fBbridge.\fIbrname\fB.port\fR.
+.TP
+\fBmirror.\fIbrname\fB.\fIpmname\fB.select.vlan = \fIvid\fR
+.
+\fIvid\fR must be an integer between 0 and 4095, inclusive. A nonzero
+\fIvid\fR selects frames that belong to VLAN \fIvid\fR, that is,
+frames that arrived on a trunk port tagged with VLAN \fIvid\fR or on a
+port that is configured as part of VLAN \fIvid\fR (see "802.1Q VLAN
+tagging," above). A \fIvid\fR of zero selects frames that do not
+belong to a VLAN, that is, frames that arrived on a trunk port without
+a VLAN tag or tagged with VLAN 0.
+.PP
+.I "Frame Transformations"
+.PP
+Only a single transformation is currently implemented.
+.TP
+\fBmirror.\fIbrname\fB.\fIpmname\fB.transform.set-vlan = \fIvid\fR
+.
+Changes the VLAN tag of the mirrored frames. \fIvid\fR must be an
+integer between 0 and 4095, inclusive. A nonzero \fIvid\fR sets the
+VLAN tag to \fIvid\fR, replacing any existing tag. A \fIvid\fR of
+zero removes any VLAN tag from mirrored frames.
+.PP
+.I "Mirror Output"
+.PP
+Only a single form of mirror output is currently implemented.
+.TP
+\fBmirror.\fIbrname\fB.\fIpmname\fB.output.port = \fIport\fR
+.
+Causes the selected and transformed frames to be sent out \fIport\fR,
+which must be part of \fIbridge\fR; that is, it must be listed on
+\fBbridge.\fIbrname\fB.port\fR.
+.PP
+.I "Example"
+.PP
+The following \fBvswitchd\fR configuration copies all frames received
+on \fBeth1\fR or \fBeth2\fR to \fBeth3\fR, setting their VLAN tags to
+222.
+.PP
+.RS
+.nf
+
+[bridge "mybr"]
+ port = eth1
+ port = eth2
+ port = eth3
+
+[mirror "mybr.a"]
+ select.src-port = eth1
+ select.src-port = eth2
+ transform.set-vlan = 222
+ output.port = eth3
+
+.fi
.SS "OpenFlow controller connectivity"
By default, \fBvswitchd\fR performs all configured bridging and
switching locally. It can also be configured to connect a given