From c3f3d15b70a407395e915915f2db6ce5e3b77bf0 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 31 Dec 2008 14:38:43 -0800 Subject: [PATCH] vswitchd: Implement port mirroring. --- vswitchd/bridge.c | 427 +++++++++++++++++++++++++++++++++++++-- vswitchd/vswitchd.conf.5 | 91 +++++++++ 2 files changed, 501 insertions(+), 17 deletions(-) diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index 1b9af42d..5202fbf4 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -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); } + +/* 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); +} + diff --git a/vswitchd/vswitchd.conf.5 b/vswitchd/vswitchd.conf.5 index b63451a6..e9be931c 100644 --- a/vswitchd/vswitchd.conf.5 +++ b/vswitchd/vswitchd.conf.5 @@ -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 -- 2.30.2