[ovs-dev] [bundle 5/5] vswitch: Implement bundle action.
Ethan Jackson
ethan at nicira.com
Fri Jul 15 20:50:04 UTC 2011
This patch creates a new action called "bundle". Bundles are a way
to implement a simple form of multipath in OpenFlow by grouping
several ports in a single output-like action.
---
include/openflow/nicira-ext.h | 75 ++++++++++++-
lib/automake.mk | 2 +
lib/bundle.c | 266 +++++++++++++++++++++++++++++++++++++++++
lib/bundle.h | 48 ++++++++
lib/ofp-parse.c | 3 +
lib/ofp-print.c | 5 +
lib/ofp-util.c | 7 +
lib/ofp-util.h | 3 +-
ofproto/ofproto-dpif.c | 44 +++++++
tests/.gitignore | 1 +
tests/automake.mk | 7 +
tests/bundle.at | 123 +++++++++++++++++++
tests/ovs-ofctl.at | 6 +
tests/test-bundle.c | 239 ++++++++++++++++++++++++++++++++++++
tests/testsuite.at | 1 +
utilities/ovs-ofctl.8.in | 11 ++
16 files changed, 839 insertions(+), 2 deletions(-)
create mode 100644 lib/bundle.c
create mode 100644 lib/bundle.h
create mode 100644 tests/bundle.at
create mode 100644 tests/test-bundle.c
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index ccde271..9b39568 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -278,7 +278,8 @@ enum nx_action_subtype {
NXAST_NOTE, /* struct nx_action_note */
NXAST_SET_TUNNEL64, /* struct nx_action_set_tunnel64 */
NXAST_MULTIPATH, /* struct nx_action_multipath */
- NXAST_AUTOPATH /* struct nx_action_autopath */
+ NXAST_AUTOPATH, /* struct nx_action_autopath */
+ NXAST_BUNDLE /* struct nx_action_bundle */
};
/* Header for Nicira-defined actions. */
@@ -666,6 +667,78 @@ struct nx_action_autopath {
};
OFP_ASSERT(sizeof(struct nx_action_autopath) == 24);
+/* Action structure for NXAST_BUNDLE.
+ *
+ * NXAST_BUNDLE chooses a slave from a supplied list of options, and outputs to
+ * its selection.
+ *
+ * The list of possible slaves is appended to the end of the nx_action_bundle
+ * structure. The size of each slave is governed by its type as indicated by
+ * the 'slave_type' parameter. The list of slaves should be padded at its end
+ * with zeros to make the total length of the action a multiple of 8.
+ *
+ * Switches infer from the 'slave_type' parameter the size of each slave. All
+ * implementations must support the NXM_OF_IN_PORT 'slave_type' which indicates
+ * that the slaves are OpenFlow port numbers with NXM_LENGTH(NXM_OF_IN_PORT) ==
+ * 16 bit width. Switches should reject actions which indicate unknown or
+ * unsupported slave types.
+ *
+ * Switches use a strategy dictated by the 'algorithm' parameter to choose a
+ * slave. If the switch does not support the specified 'algorithm' parameter,
+ * it should reject the action.
+ *
+ * Some slave selection strategies require the use of a hash function, in which
+ * case the 'fields' and 'basis' parameters should be populated. The 'fields'
+ * parameter (one of NX_HASH_FIELDS_*) designates which parts of the flow to
+ * hash. Refer to the definition of "enum nx_hash_fields" for details. The
+ * 'basis' parameter is used as a universal hash parameter. Different values
+ * of 'basis' yield different hash results.
+ *
+ * The 'zero' parameter at the end of the action structure is reserved for
+ * future use. Switches are required to reject actions which have nonzero
+ * bytes in the 'zero' field. */
+
+struct nx_action_bundle {
+ ovs_be16 type; /* OFPAT_VENDOR. */
+ ovs_be16 len; /* Length including slaves. */
+ ovs_be32 vendor; /* NX_VENDOR_ID. */
+ ovs_be16 subtype; /* NXAST_BUNDLE. */
+
+ /* Slave choice algorithm to apply to hash value. */
+ ovs_be16 algorithm; /* One of NX_BD_ALG_*. */
+
+ /* What fields to hash and how. */
+ ovs_be16 fields; /* One of NX_BD_FIELDS_*. */
+ ovs_be16 basis; /* Universal hash parameter. */
+
+ ovs_be16 slave_type; /* NXM_OF_IN_PORT. */
+ ovs_be16 n_slaves; /* Number of slaves. */
+
+ uint8_t zero[12]; /* Reserved. Must be zero. */
+};
+OFP_ASSERT(sizeof(struct nx_action_bundle) == 32);
+
+/* NXAST_BUNDLE: Bundle slave choice algorithm to apply.
+ *
+ * In the descriptions below, 'slaves' is the list of possible slaves in the
+ * order they appear in the OpenFlow action. */
+enum nx_bd_algorithm {
+ /* Chooses the first live slave listed in the bundle.
+ *
+ * O(n_slaves) performance. */
+ NX_BD_ALG_ACTIVE_BACKUP,
+
+ /* for i in [0,n_slaves):
+ * weights[i] = hash(flow, i)
+ * slave = { slaves[i] such that weights[i] >= weights[j] for all j != i }
+ *
+ * Redistributes 1/n_slaves of traffic when a slave's liveness changes.
+ * O(n_slaves) performance.
+ *
+ * Uses the 'fields' and 'basis' parameters. */
+ NX_BD_ALG_HRW = NX_MP_ALG_HRW /* Highest Random Weight. */
+};
+
/* Flexible flow specifications (aka NXM = Nicira Extended Match).
*
* OpenFlow 1.0 has "struct ofp_match" for specifying flow matches. This
diff --git a/lib/automake.mk b/lib/automake.mk
index e3d7c3f..8d93c7a 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -18,6 +18,8 @@ lib_libopenvswitch_a_SOURCES = \
lib/bitmap.h \
lib/bond.c \
lib/bond.h \
+ lib/bundle.c \
+ lib/bundle.h \
lib/byte-order.h \
lib/byteq.c \
lib/byteq.h \
diff --git a/lib/bundle.c b/lib/bundle.c
new file mode 100644
index 0000000..2b7b1a0
--- /dev/null
+++ b/lib/bundle.c
@@ -0,0 +1,266 @@
+/* Copyright (c) 2011 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "bundle.h"
+
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include "dynamic-string.h"
+#include "multipath.h"
+#include "nx-match.h"
+#include "ofpbuf.h"
+#include "ofp-util.h"
+#include "openflow/nicira-ext.h"
+#include "vlog.h"
+
+#define BUNDLE_MAX_SLAVES 2048
+
+VLOG_DEFINE_THIS_MODULE(bundle);
+
+/* Executes 'nab' on 'flow'. Uses 'slave_enabled' to determine if the slave
+ * designated by 'ofp_port' is up. Returns the choosen slave, or OFPP_NONE if
+ * none of the slaves are acceptable. */
+uint16_t
+bundle_execute(const struct nx_action_bundle *nab, const struct flow *flow,
+ bool slave_enabled(uint16_t ofp_port, void *aux), void *aux)
+{
+ uint32_t flow_hash, best_hash;
+ const ovs_be16 *slaves;
+ int best, i;
+
+ assert(nab->algorithm == htons(NX_BD_ALG_HRW));
+
+ flow_hash = flow_hash_fields(flow, ntohs(nab->fields), ntohs(nab->basis));
+ slaves = bundle_slaves(nab);
+ best = -1;
+
+ for (i = 0; i < ntohs(nab->n_slaves); i++) {
+ if (slave_enabled(ntohs(slaves[i]), aux)) {
+ uint32_t hash = hash_2words(i, flow_hash);
+
+ if (best < 0 || hash > best_hash) {
+ best_hash = hash;
+ best = i;
+ }
+ }
+ }
+
+ return best >= 0 ? ntohs(slaves[best]) : OFPP_NONE;
+}
+
+/* Checks that 'nab' specifies a bundle action which is supported by this
+ * bundle module. Uses the 'max_ports' parameter to validate each port using
+ * ofputil_check_output_port(). Returns 0 if 'nab' is supported, otherwise an
+ * OpenFlow error code (as returned by ofp_mkerr()). */
+int
+bundle_check(const struct nx_action_bundle *nab, int max_ports)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ uint16_t n_slaves, fields, algorithm, slave_type, subtype;
+ size_t slaves_size, i;
+ int error;
+
+ subtype = ntohs(nab->subtype);
+ n_slaves = ntohs(nab->n_slaves);
+ fields = ntohs(nab->fields);
+ algorithm = ntohs(nab->algorithm);
+ slave_type = ntohs(nab->slave_type);
+ slaves_size = ntohs(nab->len) - sizeof *nab;
+
+ error = ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_ARGUMENT);
+ if (fields != NX_HASH_FIELDS_ETH_SRC
+ && fields != NX_HASH_FIELDS_SYMMETRIC_L4) {
+ VLOG_WARN_RL(&rl, "unsupported fields %"PRIu16, fields);
+ } else if (n_slaves > BUNDLE_MAX_SLAVES) {
+ VLOG_WARN_RL(&rl, "too may slaves");
+ } else if (algorithm != NX_BD_ALG_HRW) {
+ VLOG_WARN_RL(&rl, "unsupported algorithm %"PRIu16, algorithm);
+ } else if (slave_type != NXM_OF_IN_PORT) {
+ VLOG_WARN_RL(&rl, "unsupported slave type %"PRIu16, slave_type);
+ } else {
+ error = 0;
+ }
+
+ for (i = 0; i < 12; i++) {
+ if (nab->zero[i]) {
+ VLOG_WARN_RL(&rl, "reserved field is nonzero");
+ error = ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_ARGUMENT);
+ }
+ }
+
+ if (slaves_size < n_slaves * sizeof(ovs_be16)) {
+ VLOG_WARN_RL(&rl, "Nicira action %"PRIu16" only has %zu bytes"
+ " allocated for slaves. %zu bytes are required for"
+ " %"PRIu16" slaves.\n", subtype, slaves_size,
+ n_slaves * sizeof(ovs_be16), n_slaves);
+ error = ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN);
+ }
+
+ for (i = 0; i < n_slaves; i++) {
+ uint16_t ofp_port = ntohs(bundle_slaves(nab)[i]);
+ int ofputil_error = ofputil_check_output_port(ofp_port, max_ports);
+
+ if (ofputil_error) {
+ VLOG_WARN_RL(&rl, "invalid slave %"PRIu16, ofp_port);
+ error = ofputil_error;
+ }
+
+ /* Controller slaves are unsupported due to the lack of a max_len
+ * argument. This may or may not change in the future. There doesn't
+ * seem to be a real-world use-case for supporting it. */
+ if (ofp_port == OFPP_CONTROLLER) {
+ VLOG_WARN_RL(&rl, "unsupported controller slave");
+ error = ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_OUT_PORT);
+ }
+ }
+
+ return error;
+}
+
+/* Converts a bundle action string contained in 's' to an nx_action_bundle and
+ * stores it in 'b'. 'b' will be cleared before populated. */
+void
+bundle_parse(struct ofpbuf *b, const char *s)
+{
+ char *fields, *basis, *algorithm, *slave_type, *slave_delim;
+ struct nx_action_bundle *nab;
+ char *tokstr, *save_ptr;
+ uint16_t n_slaves;
+
+ save_ptr = NULL;
+ tokstr = xstrdup(s);
+ fields = strtok_r(tokstr, ", ", &save_ptr);
+ basis = strtok_r(NULL, ", ", &save_ptr);
+ algorithm = strtok_r(NULL, ", ", &save_ptr);
+ slave_type = strtok_r(NULL, ", ", &save_ptr);
+ slave_delim = strtok_r(NULL, ": ", &save_ptr);
+
+ if (!slave_delim) {
+ ovs_fatal(0, "%s: not enough arguments to bundle action", s);
+ }
+
+ if (strcasecmp(slave_delim, "slaves")) {
+ ovs_fatal(0, "%s: missing slave delimiter, expected `slaves' got `%s'",
+ s, slave_delim);
+ }
+
+ ofpbuf_clear(b);
+ ofpbuf_prealloc_headroom(b, sizeof *nab);
+
+ n_slaves = 0;
+ for (;;) {
+ ovs_be16 slave_be;
+ char *slave;
+
+ slave = strtok_r(NULL, ", ", &save_ptr);
+ if (!slave || n_slaves >= BUNDLE_MAX_SLAVES) {
+ break;
+ }
+
+ slave_be = htons(atoi(slave));
+ ofpbuf_put(b, &slave_be, sizeof slave_be);
+
+ n_slaves++;
+ }
+
+ /* Slaves array must be multiple of 8 bytes long. */
+ ofpbuf_put_zeros(b, 8 - b->size % 8);
+
+ nab = ofpbuf_push_zeros(b, sizeof *nab);
+ nab->type = htons(OFPAT_VENDOR);
+ nab->len = htons(b->size);
+ nab->vendor = htonl(NX_VENDOR_ID);
+ nab->subtype = htons(NXAST_BUNDLE);
+ nab->n_slaves = htons(n_slaves);
+ nab->basis = htons(atoi(basis));
+
+ if (!strcasecmp(fields, "eth_src")) {
+ nab->fields = htons(NX_HASH_FIELDS_ETH_SRC);
+ } else if (!strcasecmp(fields, "symmetric_l4")) {
+ nab->fields = htons(NX_HASH_FIELDS_SYMMETRIC_L4);
+ } else {
+ ovs_fatal(0, "%s: unknown fields `%s'", s, fields);
+ }
+
+ if (!strcasecmp(algorithm, "active_backup")) {
+ nab->algorithm = htons(NX_BD_ALG_ACTIVE_BACKUP);
+ } else if (!strcasecmp(algorithm, "hrw")) {
+ nab->algorithm = htons(NX_BD_ALG_HRW);
+ } else {
+ ovs_fatal(0, "%s: unknown algorithm `%s'", s, algorithm);
+ }
+
+ if (!strcasecmp(slave_type, "ofport")) {
+ nab->slave_type = htons(NXM_OF_IN_PORT);
+ } else {
+ ovs_fatal(0, "%s: unknown slave_type `%s'", s, slave_type);
+ }
+
+ free(tokstr);
+}
+
+/* Appends a human-readbale representation of 'nab' to 's'. */
+void
+bundle_format(const struct nx_action_bundle *nab, struct ds *s)
+{
+ char *fields, *algorithm, *slave_type;
+ size_t i;
+
+ switch (ntohs(nab->fields)) {
+ case NX_HASH_FIELDS_ETH_SRC:
+ fields = "eth_src";
+ break;
+ case NX_HASH_FIELDS_SYMMETRIC_L4:
+ fields = "symmetric_l4";
+ break;
+ default:
+ fields = "<unknown>";
+ }
+
+ switch (ntohs(nab->algorithm)) {
+ case NX_BD_ALG_HRW:
+ algorithm = "hrw";
+ break;
+ case NX_BD_ALG_ACTIVE_BACKUP:
+ algorithm = "active_backup";
+ break;
+ default:
+ algorithm = "<unknown>";
+ }
+
+ switch (ntohs(nab->slave_type)) {
+ case NXM_OF_IN_PORT:
+ slave_type = "ofport";
+ break;
+ default:
+ slave_type = "<unknown>";
+ }
+
+ ds_put_format(s, "bundle(%s,%"PRIu16",%s,%s,slaves:", fields,
+ ntohs(nab->basis), algorithm, slave_type);
+
+ for (i = 0; i < ntohs(nab->n_slaves); i++) {
+ if (i) {
+ ds_put_cstr(s, ",");
+ }
+
+ ds_put_format(s, "%"PRIu16, ntohs(bundle_slaves(nab)[i]));
+ }
+
+ ds_put_cstr(s, ")");
+}
diff --git a/lib/bundle.h b/lib/bundle.h
new file mode 100644
index 0000000..2802244
--- /dev/null
+++ b/lib/bundle.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2011 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BUNDLE_H
+#define BUNDLE_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "openflow/nicira-ext.h"
+#include "openvswitch/types.h"
+
+struct ds;
+struct flow;
+struct ofpbuf;
+
+/* NXAST_BUNDLE helper functions.
+ *
+ * See include/openflow/nicira-ext.h for NXAST_BUNDLE specification. */
+
+uint16_t bundle_execute(const struct nx_action_bundle *, const struct flow *,
+ bool slave_enabled(uint16_t ofp_port, void *aux),
+ void *aux);
+int bundle_check(const struct nx_action_bundle *, int max_ports);
+void bundle_parse(struct ofpbuf *, const char *);
+void bundle_format(const struct nx_action_bundle *, struct ds *);
+
+/* Returns a pointer to the array of slaves contained in 'nab' */
+static inline const ovs_be16 *
+bundle_slaves(const struct nx_action_bundle *nab)
+{
+ return (ovs_be16 *)((char *)nab + sizeof *nab);
+}
+
+#endif /* bundle.h */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 5bc0484..25056e7 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -23,6 +23,7 @@
#include <stdlib.h>
#include "autopath.h"
+#include "bundle.h"
#include "byte-order.h"
#include "dynamic-string.h"
#include "netdev.h"
@@ -504,6 +505,8 @@ str_to_action(char *str, struct ofpbuf *b)
struct nx_action_autopath *naa;
naa = ofpbuf_put_uninit(b, sizeof *naa);
autopath_parse(naa, arg);
+ } else if (!strcasecmp(act, "bundle")) {
+ bundle_parse(b, arg);
} else if (!strcasecmp(act, "output")) {
put_output_action(b, str_to_u32(arg));
} else if (!strcasecmp(act, "enqueue")) {
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 4f4e33c..aa3ff54 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -26,6 +26,7 @@
#include <stdlib.h>
#include <ctype.h>
+#include "bundle.h"
#include "byte-order.h"
#include "compiler.h"
#include "dynamic-string.h"
@@ -342,6 +343,10 @@ ofp_print_action(struct ds *s, const union ofp_action *a,
ds_put_char(s, ')');
break;
+ case OFPUTIL_NXAST_BUNDLE:
+ bundle_format((const struct nx_action_bundle *) a, s);
+ break;
+
default:
break;
}
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 39bd0d1..803b033 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -21,6 +21,7 @@
#include <netinet/icmp6.h>
#include <stdlib.h>
#include "autopath.h"
+#include "bundle.h"
#include "byte-order.h"
#include "classifier.h"
#include "dynamic-string.h"
@@ -2035,6 +2036,11 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
error = autopath_check((const struct nx_action_autopath *) a);
break;
+ case OFPUTIL_NXAST_BUNDLE:
+ error = bundle_check((const struct nx_action_bundle *) a,
+ max_ports);
+ break;
+
case OFPUTIL_OFPAT_STRIP_VLAN:
case OFPUTIL_OFPAT_SET_NW_SRC:
case OFPUTIL_OFPAT_SET_NW_DST:
@@ -2107,6 +2113,7 @@ static const struct ofputil_nxast_action nxast_actions[] = {
{ OFPUTIL_NXAST_SET_TUNNEL64, 24, 24 },
{ OFPUTIL_NXAST_MULTIPATH, 32, 32 },
{ OFPUTIL_NXAST_AUTOPATH, 24, 24 },
+ { OFPUTIL_NXAST_BUNDLE, 32, UINT_MAX },
};
static int
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index adad087..7938aa0 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -301,7 +301,8 @@ enum ofputil_action_code {
OFPUTIL_NXAST_NOTE,
OFPUTIL_NXAST_SET_TUNNEL64,
OFPUTIL_NXAST_MULTIPATH,
- OFPUTIL_NXAST_AUTOPATH
+ OFPUTIL_NXAST_AUTOPATH,
+ OFPUTIL_NXAST_BUNDLE
};
int ofputil_decode_action(const union ofp_action *);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index a6eb0db..0a2025c 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -22,6 +22,7 @@
#include "autopath.h"
#include "bond.h"
+#include "bundle.h"
#include "byte-order.h"
#include "connmgr.h"
#include "coverage.h"
@@ -332,6 +333,8 @@ struct ofproto_dpif {
/* Support for debugging async flow mods. */
struct list completions;
+
+ bool has_bundle_action; /* True when the first bundle action appears. */
};
/* Defer flow mod completion until "ovs-appctl ofproto/unclog"? (Useful only
@@ -467,6 +470,8 @@ construct(struct ofproto *ofproto_)
ofproto_dpif_unixctl_init();
+ ofproto->has_bundle_action = false;
+
return 0;
}
@@ -1436,6 +1441,14 @@ port_run(struct ofport_dpif *ofport)
enable = enable && lacp_slave_may_enable(ofport->bundle->lacp, ofport);
}
+ if (ofport->may_enable != enable) {
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+
+ if (ofproto->has_bundle_action) {
+ ofproto->need_revalidate = true;
+ }
+ }
+
ofport->may_enable = enable;
}
@@ -3047,6 +3060,28 @@ xlate_autopath(struct action_xlate_ctx *ctx,
autopath_execute(naa, &ctx->flow, ofp_port);
}
+static bool
+slave_enabled_cb(uint16_t ofp_port, void *ofproto_)
+{
+ struct ofproto_dpif *ofproto = ofproto_;
+ struct ofport_dpif *port;
+
+ switch (ofp_port) {
+ case OFPP_IN_PORT:
+ case OFPP_TABLE:
+ case OFPP_NORMAL:
+ case OFPP_FLOOD:
+ case OFPP_ALL:
+ case OFPP_LOCAL:
+ return true;
+ case OFPP_CONTROLLER: /* Not supported by the bundle action. */
+ return false;
+ default:
+ port = get_ofp_port(ofproto, ofp_port);
+ return port ? port->may_enable : false;
+ }
+}
+
static void
do_xlate_actions(const union ofp_action *in, size_t n_in,
struct action_xlate_ctx *ctx)
@@ -3072,6 +3107,7 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
const struct nx_action_set_queue *nasq;
const struct nx_action_multipath *nam;
const struct nx_action_autopath *naa;
+ const struct nx_action_bundle *nab;
enum ofputil_action_code code;
ovs_be64 tun_id;
@@ -3178,6 +3214,14 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
naa = (const struct nx_action_autopath *) ia;
xlate_autopath(ctx, naa);
break;
+
+ case OFPUTIL_NXAST_BUNDLE:
+ ctx->ofproto->has_bundle_action = true;
+ nab = (const struct nx_action_bundle *) ia;
+ xlate_output_action__(ctx, bundle_execute(nab, &ctx->flow,
+ slave_enabled_cb,
+ ctx->ofproto), 0);
+ break;
}
}
}
diff --git a/tests/.gitignore b/tests/.gitignore
index e2b293c..e945a85 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -9,6 +9,7 @@
/ovs-pki.log
/pki/
/test-aes128
+/test-bundle
/test-byte-order
/test-classifier
/test-csum
diff --git a/tests/automake.mk b/tests/automake.mk
index 750f420..be09b4a 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -8,6 +8,7 @@ TESTSUITE_AT = \
tests/testsuite.at \
tests/ovsdb-macros.at \
tests/library.at \
+ tests/bundle.at \
tests/classifier.at \
tests/check-structs.at \
tests/daemon.at \
@@ -66,6 +67,7 @@ lcov_wrappers = \
tests/lcov/ovsdb-server \
tests/lcov/ovsdb-tool \
tests/lcov/test-aes128 \
+ tests/lcov/test-bundle \
tests/lcov/test-byte-order \
tests/lcov/test-classifier \
tests/lcov/test-csum \
@@ -118,6 +120,7 @@ valgrind_wrappers = \
tests/valgrind/ovsdb-server \
tests/valgrind/ovsdb-tool \
tests/valgrind/test-aes128 \
+ tests/valgrind/test-bundle \
tests/valgrind/test-byte-order \
tests/valgrind/test-classifier \
tests/valgrind/test-csum \
@@ -185,6 +188,10 @@ noinst_PROGRAMS += tests/test-aes128
tests_test_aes128_SOURCES = tests/test-aes128.c
tests_test_aes128_LDADD = lib/libopenvswitch.a
+noinst_PROGRAMS += tests/test-bundle
+tests_test_bundle_SOURCES = tests/test-bundle.c
+tests_test_bundle_LDADD = lib/libopenvswitch.a
+
noinst_PROGRAMS += tests/test-classifier
tests_test_classifier_SOURCES = tests/test-classifier.c
tests_test_classifier_LDADD = lib/libopenvswitch.a
diff --git a/tests/bundle.at b/tests/bundle.at
new file mode 100644
index 0000000..fb2842d
--- /dev/null
+++ b/tests/bundle.at
@@ -0,0 +1,123 @@
+AT_BANNER([bundle link selection])
+
+# The test-bundle program prints a lot of output on stdout, but each of the
+# tests below ignores it because it will vary a bit depending on endianness and
+# floating point precision. test-bundle will output an error message on
+# stderr and return with exit code 1 if anything really goes wrong. In each
+# case, we list the (approximate) expected output in a comment to aid debugging
+# if the test does fail.
+
+AT_SETUP([hrw bundle link selection])
+AT_CHECK([[test-bundle 'symmetric_l4,60,hrw,ofport,slaves:1,2,3,4,5,6']],
+ [0], [ignore])
+# 100000: disruption=1.00 (perfect=1.00) 1.00 0.00 0.00 0.00 0.00 0.00
+# 110000: disruption=0.50 (perfect=0.50) 0.50 0.50 0.00 0.00 0.00 0.00
+# 010000: disruption=0.50 (perfect=0.50) 0.00 1.00 0.00 0.00 0.00 0.00
+# 011000: disruption=0.50 (perfect=0.50) 0.00 0.50 0.50 0.00 0.00 0.00
+# 111000: disruption=0.33 (perfect=0.33) 0.33 0.33 0.34 0.00 0.00 0.00
+# 101000: disruption=0.33 (perfect=0.33) 0.50 0.00 0.50 0.00 0.00 0.00
+# 001000: disruption=0.50 (perfect=0.50) 0.00 0.00 1.00 0.00 0.00 0.00
+# 001100: disruption=0.50 (perfect=0.50) 0.00 0.00 0.50 0.50 0.00 0.00
+# 101100: disruption=0.33 (perfect=0.33) 0.33 0.00 0.34 0.33 0.00 0.00
+# 111100: disruption=0.25 (perfect=0.25) 0.25 0.25 0.25 0.25 0.00 0.00
+# 011100: disruption=0.25 (perfect=0.25) 0.00 0.33 0.33 0.33 0.00 0.00
+# 010100: disruption=0.33 (perfect=0.33) 0.00 0.50 0.00 0.50 0.00 0.00
+# 110100: disruption=0.33 (perfect=0.33) 0.33 0.33 0.00 0.34 0.00 0.00
+# 100100: disruption=0.33 (perfect=0.33) 0.50 0.00 0.00 0.50 0.00 0.00
+# 000100: disruption=0.50 (perfect=0.50) 0.00 0.00 0.00 1.00 0.00 0.00
+# 000110: disruption=0.50 (perfect=0.50) 0.00 0.00 0.00 0.50 0.50 0.00
+# 100110: disruption=0.33 (perfect=0.33) 0.33 0.00 0.00 0.33 0.33 0.00
+# 110110: disruption=0.25 (perfect=0.25) 0.25 0.25 0.00 0.25 0.25 0.00
+# 010110: disruption=0.25 (perfect=0.25) 0.00 0.34 0.00 0.33 0.33 0.00
+# 011110: disruption=0.25 (perfect=0.25) 0.00 0.25 0.25 0.25 0.25 0.00
+# 111110: disruption=0.20 (perfect=0.20) 0.20 0.20 0.20 0.20 0.20 0.00
+# 101110: disruption=0.20 (perfect=0.20) 0.25 0.00 0.25 0.25 0.25 0.00
+# 001110: disruption=0.25 (perfect=0.25) 0.00 0.00 0.34 0.33 0.33 0.00
+# 001010: disruption=0.33 (perfect=0.33) 0.00 0.00 0.50 0.00 0.50 0.00
+# 101010: disruption=0.33 (perfect=0.33) 0.33 0.00 0.34 0.00 0.33 0.00
+# 111010: disruption=0.25 (perfect=0.25) 0.25 0.25 0.25 0.00 0.25 0.00
+# 011010: disruption=0.25 (perfect=0.25) 0.00 0.33 0.34 0.00 0.33 0.00
+# 010010: disruption=0.34 (perfect=0.33) 0.00 0.50 0.00 0.00 0.50 0.00
+# 110010: disruption=0.33 (perfect=0.33) 0.33 0.33 0.00 0.00 0.33 0.00
+# 100010: disruption=0.33 (perfect=0.33) 0.50 0.00 0.00 0.00 0.50 0.00
+# 000010: disruption=0.50 (perfect=0.50) 0.00 0.00 0.00 0.00 1.00 0.00
+# 000011: disruption=0.50 (perfect=0.50) 0.00 0.00 0.00 0.00 0.50 0.50
+# 100011: disruption=0.33 (perfect=0.33) 0.33 0.00 0.00 0.00 0.33 0.33
+# 110011: disruption=0.25 (perfect=0.25) 0.25 0.25 0.00 0.00 0.25 0.25
+# 010011: disruption=0.25 (perfect=0.25) 0.00 0.33 0.00 0.00 0.33 0.33
+# 011011: disruption=0.25 (perfect=0.25) 0.00 0.25 0.25 0.00 0.25 0.25
+# 111011: disruption=0.20 (perfect=0.20) 0.20 0.20 0.20 0.00 0.20 0.20
+# 101011: disruption=0.20 (perfect=0.20) 0.25 0.00 0.25 0.00 0.25 0.25
+# 001011: disruption=0.25 (perfect=0.25) 0.00 0.00 0.34 0.00 0.33 0.33
+# 001111: disruption=0.25 (perfect=0.25) 0.00 0.00 0.25 0.25 0.25 0.25
+# 101111: disruption=0.20 (perfect=0.20) 0.20 0.00 0.20 0.20 0.20 0.20
+# 111111: disruption=0.17 (perfect=0.17) 0.17 0.17 0.17 0.17 0.17 0.17
+# 011111: disruption=0.17 (perfect=0.17) 0.00 0.20 0.20 0.20 0.20 0.20
+# 010111: disruption=0.20 (perfect=0.20) 0.00 0.25 0.00 0.25 0.25 0.25
+# 110111: disruption=0.20 (perfect=0.20) 0.20 0.20 0.00 0.20 0.20 0.20
+# 100111: disruption=0.20 (perfect=0.20) 0.25 0.00 0.00 0.25 0.25 0.25
+# 000111: disruption=0.25 (perfect=0.25) 0.00 0.00 0.00 0.33 0.33 0.33
+# 000101: disruption=0.33 (perfect=0.33) 0.00 0.00 0.00 0.50 0.00 0.50
+# 100101: disruption=0.33 (perfect=0.33) 0.33 0.00 0.00 0.33 0.00 0.33
+# 110101: disruption=0.25 (perfect=0.25) 0.25 0.25 0.00 0.25 0.00 0.25
+# 010101: disruption=0.25 (perfect=0.25) 0.00 0.33 0.00 0.33 0.00 0.33
+# 011101: disruption=0.25 (perfect=0.25) 0.00 0.25 0.25 0.25 0.00 0.25
+# 111101: disruption=0.20 (perfect=0.20) 0.20 0.20 0.20 0.20 0.00 0.20
+# 101101: disruption=0.20 (perfect=0.20) 0.25 0.00 0.25 0.25 0.00 0.25
+# 001101: disruption=0.25 (perfect=0.25) 0.00 0.00 0.33 0.33 0.00 0.33
+# 001001: disruption=0.33 (perfect=0.33) 0.00 0.00 0.50 0.00 0.00 0.50
+# 101001: disruption=0.33 (perfect=0.33) 0.33 0.00 0.33 0.00 0.00 0.33
+# 111001: disruption=0.25 (perfect=0.25) 0.25 0.25 0.25 0.00 0.00 0.25
+# 011001: disruption=0.25 (perfect=0.25) 0.00 0.33 0.34 0.00 0.00 0.33
+# 010001: disruption=0.34 (perfect=0.33) 0.00 0.50 0.00 0.00 0.00 0.50
+# 110001: disruption=0.33 (perfect=0.33) 0.33 0.33 0.00 0.00 0.00 0.34
+# 100001: disruption=0.33 (perfect=0.33) 0.50 0.00 0.00 0.00 0.00 0.50
+# 000001: disruption=0.50 (perfect=0.50) 0.00 0.00 0.00 0.00 0.00 1.00
+# 000000: disruption=1.00 (perfect=1.00) 0.00 0.00 0.00 0.00 0.00 0.00
+# 100000: disruption=1.00 (perfect=1.00) 1.00 0.00 0.00 0.00 0.00 0.00
+AT_CLEANUP
+
+AT_SETUP([hrw bundle single link selection])
+AT_CHECK([[test-bundle 'symmetric_l4,60,hrw,ofport,slaves:1']],
+ [0], [ignore])
+# 1: disruption=1.00 (perfect=1.00) 1.00
+# 0: disruption=1.00 (perfect=1.00) 0.00
+# 1: disruption=1.00 (perfect=1.00) 1.00
+AT_CLEANUP
+
+AT_SETUP([hrw bundle no link selection])
+AT_CHECK([[test-bundle 'symmetric_l4,60,hrw,ofport,slaves:']],
+ [0], [ignore])
+AT_CLEANUP
+#: disruption=0.00 (perfect=0.00)
+#: disruption=0.00 (perfect=0.00)
+
+AT_SETUP([bundle action missing argument])
+AT_CHECK([ovs-ofctl parse-flow actions=bundle], [1], [],
+ [ovs-ofctl: : not enough arguments to bundle action
+])
+AT_CLEANUP
+
+AT_SETUP([bundle action bad fields])
+AT_CHECK([ovs-ofctl parse-flow 'actions=bundle(xyzzy,60,hrw,ofport,slaves:1,2))'], [1], [],
+ [ovs-ofctl: xyzzy,60,hrw,ofport,slaves:1,2: unknown fields `xyzzy'
+])
+AT_CLEANUP
+
+AT_SETUP([bundle action bad algorithm])
+AT_CHECK([ovs-ofctl parse-flow 'actions=bundle(symmetric_l4,60,fubar,ofport,slaves:1,2))'], [1], [],
+ [ovs-ofctl: symmetric_l4,60,fubar,ofport,slaves:1,2: unknown algorithm `fubar'
+])
+AT_CLEANUP
+
+AT_SETUP([bundle action bad slave type])
+AT_CHECK([ovs-ofctl parse-flow 'actions=bundle(symmetric_l4,60,hrw,robot,slaves:1,2))'], [1], [],
+ [ovs-ofctl: symmetric_l4,60,hrw,robot,slaves:1,2: unknown slave_type `robot'
+])
+AT_CLEANUP
+
+AT_SETUP([bundle action bad slave delimeter])
+AT_CHECK([ovs-ofctl parse-flow 'actions=bundle(symmetric_l4,60,hrw,ofport,robot:1,2))'], [1], [],
+ [ovs-ofctl: symmetric_l4,60,hrw,ofport,robot:1,2: missing slave delimiter, expected `slaves' got `robot'
+])
+AT_CLEANUP
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 9e347cf..7d6212d 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -15,6 +15,9 @@ actions=set_tunnel:0x1234,set_tunnel64:0x9876,set_tunnel:0x123456789
actions=multipath(eth_src, 50, hrw, 12, 0, NXM_NX_REG0[0..3]),multipath(symmetric_l4, 1024, iter_hash, 5000, 5050, NXM_NX_REG0[0..12])
table=1,actions=drop
tun_id=0x1234000056780000/0xffff0000ffff0000,actions=drop
+actions=bundle(eth_src,50,active_backup,ofport,slaves:1)
+actions=bundle(symmetric_l4,60,hrw,ofport,slaves:2,3)
+actions=bundle(symmetric_l4,60,hrw,ofport,slaves:)
]])
AT_CHECK([ovs-ofctl parse-flows flows.txt
], [0], [stdout])
@@ -33,6 +36,9 @@ NXT_FLOW_MOD: ADD actions=multipath(eth_src,50,hrw,12,0,NXM_NX_REG0[0..3]),multi
NXT_FLOW_MOD_TABLE_ID: enable
NXT_FLOW_MOD: ADD table:1 actions=drop
NXT_FLOW_MOD: ADD table:255 tun_id=0x1234000056780000/0xffff0000ffff0000 actions=drop
+NXT_FLOW_MOD: ADD table:255 actions=bundle(eth_src,50,active_backup,ofport,slaves:1)
+NXT_FLOW_MOD: ADD table:255 actions=bundle(symmetric_l4,60,hrw,ofport,slaves:2,3)
+NXT_FLOW_MOD: ADD table:255 actions=bundle(symmetric_l4,60,hrw,ofport,slaves:)
]])
AT_CLEANUP
diff --git a/tests/test-bundle.c b/tests/test-bundle.c
new file mode 100644
index 0000000..2108135
--- /dev/null
+++ b/tests/test-bundle.c
@@ -0,0 +1,239 @@
+/* Copyright (c) 2011 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "bundle.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+#include "flow.h"
+#include "ofpbuf.h"
+#include "random.h"
+
+#include "util.h"
+
+#define N_FLOWS 30000
+#define BD_MAX_SLAVES 8
+
+struct slave {
+ uint16_t slave_id;
+
+ bool enabled;
+ size_t flow_count;
+};
+
+struct slave_group {
+ size_t n_slaves;
+ struct slave slaves[BD_MAX_SLAVES];
+};
+
+static struct slave *
+slave_lookup(struct slave_group *sg, uint16_t slave_id)
+{
+ size_t i;
+
+ for (i = 0; i < sg->n_slaves; i++) {
+ if (sg->slaves[i].slave_id == slave_id) {
+ return &sg->slaves[i];
+ }
+ }
+
+ return NULL;
+}
+
+static bool
+slave_enabled_cb(uint16_t slave_id, void *aux)
+{
+ struct slave *slave;
+
+ slave = slave_lookup(aux, slave_id);
+ return slave ? slave->enabled : false;
+}
+
+static struct nx_action_bundle *
+parse_bundle_actions(char *actions)
+{
+ struct nx_action_bundle *nab;
+ struct ofpbuf b;
+
+ ofpbuf_init(&b, 0);
+ bundle_parse(&b, actions);
+ nab = ofpbuf_steal_data(&b);
+ ofpbuf_uninit(&b);
+
+ if (ntohs(nab->n_slaves) > BD_MAX_SLAVES) {
+ ovs_fatal(0, "At most %u slaves are supported", BD_MAX_SLAVES);
+ }
+
+ return nab;
+}
+
+static const char *
+mask_str(uint8_t mask, size_t n_bits)
+{
+ static char str[9];
+ size_t i;
+
+ n_bits = MIN(n_bits, 8);
+ for (i = 0; i < n_bits; i++) {
+ str[i] = (1 << i) & mask ? '1' : '0';
+ }
+ str[i] = '\0';
+
+ return str;
+}
+
+int
+main(int argc, char *argv[])
+{
+ bool ok = true;
+ struct nx_action_bundle *nab;
+ struct flow *flows;
+ size_t i, n_permute, old_n_enabled;
+ struct slave_group sg;
+
+ set_program_name(argv[0]);
+ random_init();
+
+ if (argc != 2) {
+ ovs_fatal(0, "usage: %s bundle_action", program_name);
+ }
+
+ nab = parse_bundle_actions(argv[1]);
+
+ /* Generate 'slaves' array. */
+ sg.n_slaves = 0;
+ for (i = 0; i < ntohs(nab->n_slaves); i++) {
+ uint16_t slave_id;
+
+ slave_id = ntohs(bundle_slaves(nab)[i]);
+
+ if (slave_lookup(&sg, slave_id)) {
+ ovs_fatal(0, "Redundant slaves are not supported. ");
+ }
+
+ sg.slaves[sg.n_slaves].slave_id = slave_id;
+ sg.n_slaves++;
+ }
+
+ /* Generate flows. */
+ flows = xmalloc(N_FLOWS * sizeof *flows);
+ for (i = 0; i < N_FLOWS; i++) {
+ random_bytes(&flows[i], sizeof flows[i]);
+ flows[i].regs[0] = OFPP_NONE;
+ }
+
+ /* Cycles through each possible liveness permutation for the given
+ * n_slaves. The initial state is equivalent to all slaves down, so we
+ * skip it by starting at i = 1. We do one extra iteration to cover
+ * transitioning from the final state back to the initial state. */
+ old_n_enabled = 0;
+ n_permute = 1 << sg.n_slaves;
+ for (i = 1; i <= n_permute + 1; i++) {
+ struct slave *slave;
+ size_t j, n_enabled, changed;
+ double disruption, perfect;
+ uint8_t mask;
+
+ mask = i % n_permute;
+
+ /* Gray coding ensures that in each iteration exactly one slave
+ * changes its liveness. This makes the expected disruption a bit
+ * easier to calculate, and is likely similar to how failures will be
+ * experienced in the wild. */
+ mask = mask ^ (mask >> 1);
+
+ /* Initialize slaves. */
+ n_enabled = 0;
+ for (j = 0; j < sg.n_slaves; j++) {
+ slave = &sg.slaves[j];
+ slave->flow_count = 0;
+ slave->enabled = (1 << j) & mask;
+
+ if (slave->enabled) {
+ n_enabled++;
+ }
+ }
+
+ changed = 0;
+ for (j = 0; j < N_FLOWS; j++) {
+ struct flow *flow = &flows[j];
+ uint16_t old_slave_id;
+
+ old_slave_id = flow->regs[0];
+ flow->regs[0] = bundle_execute(nab, flow, slave_enabled_cb, &sg);
+
+ if (flow->regs[0] != OFPP_NONE) {
+ slave_lookup(&sg, flow->regs[0])->flow_count++;
+ }
+
+ if (old_slave_id != flow->regs[0]) {
+ changed++;
+ }
+ }
+
+ if (old_n_enabled || n_enabled) {
+ perfect = 1.0 / MAX(old_n_enabled, n_enabled);
+ } else {
+ /* This will happen when 'sg.n_slaves' is 0. */
+ perfect = 0;
+ }
+
+ disruption = changed / (double)N_FLOWS;
+ printf("%s: disruption=%.2f (perfect=%.2f) ",
+ mask_str(mask, sg.n_slaves), disruption, perfect);
+
+ for (j = 0 ; j < sg.n_slaves; j++) {
+ struct slave *slave = &sg.slaves[j];
+ double flow_percent;
+
+ flow_percent = slave->flow_count / (double)N_FLOWS;
+ printf("%.2f ", flow_percent);
+
+ if (slave->enabled) {
+ double perfect_fp = 1.0 / n_enabled;
+
+ if (fabs(flow_percent - perfect_fp) >= .01) {
+ fprintf(stderr, "%s: slave %d: flow_percentage=%.5f for"
+ " differs from perfect=%.5f by more than .01\n",
+ mask_str(mask, sg.n_slaves), slave->slave_id,
+ flow_percent, perfect_fp);
+ ok = false;
+ }
+ } else if (slave->flow_count) {
+ fprintf(stderr, "%s: slave %d: disabled slave received"
+ " flows.\n", mask_str(mask, sg.n_slaves),
+ slave->slave_id);
+ ok = false;
+ }
+ }
+ printf("\n");
+
+ if (fabs(disruption - perfect) >= .01) {
+ fprintf(stderr, "%s: disruption=%.5f differs from perfect=%.5f by"
+ " more than .01\n", mask_str(mask, sg.n_slaves),
+ disruption, perfect);
+ ok = false;
+ }
+
+ old_n_enabled = n_enabled;
+ }
+
+ free(nab);
+ free(flows);
+ return ok ? 0 : 1;
+}
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 2867893..6ec77f8 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -39,6 +39,7 @@ m4_include([tests/ovsdb-macros.at])
m4_include([tests/ofproto-macros.at])
m4_include([tests/library.at])
+m4_include([tests/bundle.at])
m4_include([tests/classifier.at])
m4_include([tests/check-structs.at])
m4_include([tests/daemon.at])
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 9e96a2b..9695f85 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -729,6 +729,17 @@ Otherwise, the register will be populated with \fIid\fR itself.
Refer to \fBnicira\-ext.h\fR for more details.
.RE
.
+.IP "\fBbundle(\fIfields\fB, \fIbasis\fB, \fIalgorithm\fB, \fIslave_type\fB, slaves:[\fIs1\fB, \fIs2\fB, ...])\fR"
+Hashes \fIfields\fR using \fIbasis\fR as a universal hash parameter, then
+applies the bundle link selection \fIalgorithm\fR to choose one of the listed
+slaves represented as \fIslave_type\fR. Outputs to the selected slave.
+.IP
+Currently, \fIfields\fR must be either \fBeth_src\fR or \fBsymmetric_l4\fR and
+\fIalgorithm\fR must be one of \fBhrw\fR and \fBactive_backup\fR.
+.IP
+Refer to \fBnicira\-ext.h\fR for more details.
+.RE
+.
.IP
(The OpenFlow protocol supports other actions that \fBovs\-ofctl\fR does
not yet expose to the user.)
--
1.7.6
More information about the dev
mailing list