vswitchd: Implement port mirroring.
authorBen Pfaff <blp@nicira.com>
Wed, 31 Dec 2008 22:38:43 +0000 (14:38 -0800)
committerBen Pfaff <blp@nicira.com>
Wed, 31 Dec 2008 22:59:28 +0000 (14:59 -0800)
vswitchd/bridge.c
vswitchd/vswitchd.conf.5

index 1b9af42d959b7d248ee881702b0d1ad12222eb92..5202fbf43dfacfdcfd0081c3808b3d663056e31b 100644 (file)
@@ -41,6 +41,7 @@
 #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>
@@ -89,6 +90,28 @@ struct bond_entry {
     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;
@@ -107,6 +130,10 @@ struct port {
     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
@@ -153,6 +180,9 @@ struct bridge {
     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. */
@@ -187,9 +217,16 @@ static void bond_wait(struct bridge *);
 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);
@@ -805,6 +842,8 @@ bridge_reconfigure_one(struct bridge *br)
         br->sc_retries = 0;
         br->sc_state = SC_UNSTARTED;
     }
+
+    mirror_reconfigure(br);
 }
 
 static void
@@ -1211,6 +1250,43 @@ swap_dst(struct ft_dst *p, struct ft_dst *q)
     *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)
 {
@@ -1241,42 +1317,70 @@ add_vlan_action(struct ofpbuf *buf, uint16_t old_vlan, uint16_t new_vlan)
     }
 }
 
+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
@@ -2137,6 +2241,13 @@ port_destroy(struct port *port)
         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;
 
@@ -2156,6 +2267,20 @@ port_from_dp_ifidx(const struct bridge *br, uint16_t dp_ifidx)
     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)
 {
@@ -2251,3 +2376,271 @@ iface_from_dp_ifidx(const struct bridge *br, uint16_t dp_ifidx)
 {
     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);
+}
+
index b63451a6c8f8a075ffed27a1b884ac72e3d1b3d5..e9be931c084b2888814f1db5a64fa9bffa845ad0 100644 (file)
@@ -1,4 +1,9 @@
 .\" -*- nroff -*-
+.de TQ
+.  br
+.  ns
+.  TP \$1
+..
 .TH vswitchd.conf 5 "December 2008" "OpenFlow" "OpenFlow Manual"
 .
 .SH NAME
@@ -135,6 +140,92 @@ with physical network devices \fBeth2\fR and \fBeth3\fR:
         
 .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