ofproto: Implement OpenFlow extension to allow control over async messages.
authorBen Pfaff <blp@nicira.com>
Thu, 9 Feb 2012 22:06:35 +0000 (14:06 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 9 Feb 2012 22:06:35 +0000 (14:06 -0800)
Until now, the rules that cover the asynchronous messages that Open vSwitch
sends to a controller have been ad hoc.  The new NXT_SET_ASYNC_CONFIG
message provides systematic, precise control.

Feature #7086.
Signed-off-by: Ben Pfaff <blp@nicira.com>
12 files changed:
DESIGN
NEWS
include/openflow/nicira-ext.h
lib/learning-switch.c
lib/ofp-print.c
lib/ofp-util.c
lib/ofp-util.h
ofproto/connmgr.c
ofproto/connmgr.h
ofproto/ofproto.c
tests/ofp-print.at
tests/ofproto.at

diff --git a/DESIGN b/DESIGN
index 164ada2..f383b65 100644 (file)
--- a/DESIGN
+++ b/DESIGN
@@ -9,8 +9,53 @@ successful deployment.  The end of this document contains contact
 information that can be used to let us know how we can make Open vSwitch
 more generally useful.
 
 information that can be used to let us know how we can make Open vSwitch
 more generally useful.
 
-OpenFlow
-========
+Asynchronous Messages
+=====================
+
+Over time, Open vSwitch has added many knobs that control whether a
+given controller receives OpenFlow asynchronous messages.  This
+section describes how all of these features interact.
+
+First, a service controller never receives any asynchronous messages
+unless it explicitly configures a miss_send_len greater than zero with
+an OFPT_SET_CONFIG message.
+
+Second, OFPT_FLOW_REMOVED and NXT_FLOW_REMOVED messages are generated
+only if the flow that was removed had the OFPFF_SEND_FLOW_REM flag
+set.
+
+Finally, Open vSwitch consults a per-connection table indexed by the
+message type, reason code, and current role.  The following table
+shows how this table is initialized by default when an OpenFlow
+connection is made.  An entry labeled "yes" means that the message is
+sent, an entry labeled "---" means that the message is suppressed.
+
+                                             master/
+  message and reason code                     other     slave
+  ----------------------------------------   -------    -----
+  OFPT_PACKET_IN / NXT_PACKET_IN
+    OFPR_NO_MATCH                              yes       ---
+    OFPR_ACTION                                yes       ---
+    OFPR_INVALID_TTL                           ---       ---
+
+  OFPT_FLOW_REMOVED / NXT_FLOW_REMOVED
+    OFPRR_IDLE_TIMEOUT                         yes       ---
+    OFPRR_HARD_TIMEOUT                         yes       ---
+    OFPRR_DELETE                               yes       ---
+
+  OFPT_PORT_STATUS
+    OFPPR_ADD                                  yes       yes
+    OFPPR_DELETE                               yes       yes
+    OFPPR_MODIFY                               yes       yes
+
+The NXT_SET_ASYNC_CONFIG message directly sets all of the values in
+this table for the current connection.  The
+OFPC_INVALID_TTL_TO_CONTROLLER bit in the OFPT_SET_CONFIG message
+controls the setting for OFPR_INVALID_TTL for the "master" role.
+
+
+OFPAT_ENQUEUE
+=============
 
 The OpenFlow 1.0 specification requires the output port of the OFPAT_ENQUEUE
 action to "refer to a valid physical port (i.e. < OFPP_MAX) or OFPP_IN_PORT".
 
 The OpenFlow 1.0 specification requires the output port of the OFPAT_ENQUEUE
 action to "refer to a valid physical port (i.e. < OFPP_MAX) or OFPP_IN_PORT".
diff --git a/NEWS b/NEWS
index c153370..fde19f0 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,13 @@ post-v1.5.0
     - OpenFlow:
         - Added support for bitwise matching on TCP and UDP ports.
           See ovs-ofctl(8) for more information.
     - OpenFlow:
         - Added support for bitwise matching on TCP and UDP ports.
           See ovs-ofctl(8) for more information.
+        - NXM flow dumps now include times elapsed toward idle and hard
+          timeouts.
+        - Added an OpenFlow extension NXT_SET_ASYNC_CONFIG that allows
+          controllers more precise control over which OpenFlow messages they
+          receive asynchronously.
+    - The default MAC learning timeout has been increased from 60 seconds
+      to 300 seconds.  The MAC learning timeout is now configurable.
     - Logging to console and file will have UTC timestamp as a default for all
       the daemons. An example of the default format is 2012-01-27T16:35:17Z.
       ovs-appctl can be used to change the default format as before.
     - Logging to console and file will have UTC timestamp as a default for all
       the daemons. An example of the default format is 2012-01-27T16:35:17Z.
       ovs-appctl can be used to change the default format as before.
@@ -18,13 +25,9 @@ post-v1.5.0
       table, with configurable policy for evicting flows upon
       overflow.  See the Flow_Table table in ovs-vswitch.conf.db(5)
       for more information.
       table, with configurable policy for evicting flows upon
       overflow.  See the Flow_Table table in ovs-vswitch.conf.db(5)
       for more information.
-    - OpenFlow:
-      - NXM flow dumps now include times elapsed toward idle and hard timeouts.
     - ofproto-provider interface:
         - "struct rule" has a new member "used" that ofproto implementations
           should maintain by updating with ofproto_rule_update_used().
     - ofproto-provider interface:
         - "struct rule" has a new member "used" that ofproto implementations
           should maintain by updating with ofproto_rule_update_used().
-    - The default MAC learning timeout has been increased from 60 seconds
-      to 300 seconds.  The MAC learning timeout is now configurable.
     - ovsdb-client:
         - The new option --timestamp causes the "monitor" command to print
           a timestamp with every update.
     - ovsdb-client:
         - The new option --timestamp causes the "monitor" command to print
           a timestamp with every update.
index b0c8e4e..ff95796 100644 (file)
@@ -114,6 +114,8 @@ enum nicira_type {
      * If so, the switch does not reply to this message (which consists only of
      * a "struct nicira_header").  If not, the switch sends an error reply. */
     NXT_FLOW_AGE = 18,
      * If so, the switch does not reply to this message (which consists only of
      * a "struct nicira_header").  If not, the switch sends an error reply. */
     NXT_FLOW_AGE = 18,
+
+    NXT_SET_ASYNC_CONFIG = 19,  /* struct nx_async_config. */
 };
 
 /* Header for Nicira vendor stats request and reply messages. */
 };
 
 /* Header for Nicira vendor stats request and reply messages. */
@@ -288,6 +290,28 @@ enum nx_role {
     NX_ROLE_MASTER,             /* Full access, at most one. */
     NX_ROLE_SLAVE               /* Read-only access. */
 };
     NX_ROLE_MASTER,             /* Full access, at most one. */
     NX_ROLE_SLAVE               /* Read-only access. */
 };
+
+/* NXT_SET_ASYNC_CONFIG.
+ *
+ * Sent by a controller, this message configures the asynchronous messages that
+ * the controller wants to receive.  Element 0 in each array specifies messages
+ * of interest when the controller has an "other" or "master" role; element 1,
+ * when the controller has a "slave" role.
+ *
+ * Each array element is a bitmask in which a 0-bit disables receiving a
+ * particular message and a 1-bit enables receiving it.  Each bit controls the
+ * message whose 'reason' corresponds to the bit index.  For example, the bit
+ * with value 1<<2 == 4 in port_status_mask[1] determines whether the
+ * controller will receive OFPT_PORT_STATUS messages with reason OFPPR_MODIFY
+ * (value 2) when the controller has a "slave" role.
+ */
+struct nx_async_config {
+    struct nicira_header nxh;
+    ovs_be32 packet_in_mask[2];    /* Bitmasks of OFPR_* values. */
+    ovs_be32 port_status_mask[2];  /* Bitmasks of OFPRR_* values. */
+    ovs_be32 flow_removed_mask[2]; /* Bitmasks of OFPPR_* values. */
+};
+OFP_ASSERT(sizeof(struct nx_async_config) == 40);
 \f
 /* Nicira vendor flow actions. */
 
 \f
 /* Nicira vendor flow actions. */
 
index 81aadeb..435acb9 100644 (file)
@@ -265,6 +265,7 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn,
     case OFPUTIL_NXT_FLOW_MOD:
     case OFPUTIL_NXT_FLOW_REMOVED:
     case OFPUTIL_NXT_FLOW_AGE:
     case OFPUTIL_NXT_FLOW_MOD:
     case OFPUTIL_NXT_FLOW_REMOVED:
     case OFPUTIL_NXT_FLOW_AGE:
+    case OFPUTIL_NXT_SET_ASYNC_CONFIG:
     case OFPUTIL_NXST_FLOW_REQUEST:
     case OFPUTIL_NXST_AGGREGATE_REQUEST:
     case OFPUTIL_NXST_FLOW_REPLY:
     case OFPUTIL_NXST_FLOW_REQUEST:
     case OFPUTIL_NXST_AGGREGATE_REQUEST:
     case OFPUTIL_NXST_FLOW_REPLY:
index f0c134f..30ecdbf 100644 (file)
@@ -80,6 +80,24 @@ ofp_packet_to_string(const void *data, size_t len)
     return ds_cstr(&ds);
 }
 
     return ds_cstr(&ds);
 }
 
+static const char *
+ofp_packet_in_reason_to_string(enum ofp_packet_in_reason reason)
+{
+    static char s[32];
+
+    switch (reason) {
+    case OFPR_NO_MATCH:
+        return "no_match";
+    case OFPR_ACTION:
+        return "action";
+    case OFPR_INVALID_TTL:
+        return "invalid_ttl";
+    default:
+        sprintf(s, "%d", (int) reason);
+        return s;
+    }
+}
+
 static void
 ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
                     int verbosity)
 static void
 ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
                     int verbosity)
@@ -121,20 +139,8 @@ ofp_print_packet_in(struct ds *string, const struct ofp_header *oh,
         }
     }
 
         }
     }
 
-    switch (pin.reason) {
-    case OFPR_NO_MATCH:
-        ds_put_cstr(string, " (via no_match)");
-        break;
-    case OFPR_ACTION:
-        ds_put_cstr(string, " (via action)");
-        break;
-    case OFPR_INVALID_TTL:
-        ds_put_cstr(string, " (via invalid_ttl)");
-        break;
-    default:
-        ds_put_format(string, " (***reason %"PRIu8"***)", pin.reason);
-        break;
-    }
+    ds_put_format(string, " (via %s)",
+                  ofp_packet_in_reason_to_string(pin.reason));
 
     ds_put_format(string, " data_len=%zu", pin.packet_len);
     if (pin.buffer_id == UINT32_MAX) {
 
     ds_put_format(string, " data_len=%zu", pin.packet_len);
     if (pin.buffer_id == UINT32_MAX) {
@@ -870,6 +876,24 @@ ofp_print_duration(struct ds *string, unsigned int sec, unsigned int nsec)
     ds_put_char(string, 's');
 }
 
     ds_put_char(string, 's');
 }
 
+static const char *
+ofp_flow_removed_reason_to_string(enum ofp_flow_removed_reason reason)
+{
+    static char s[32];
+
+    switch (reason) {
+    case OFPRR_IDLE_TIMEOUT:
+        return "idle";
+    case OFPRR_HARD_TIMEOUT:
+        return "hard";
+    case OFPRR_DELETE:
+        return "delete";
+    default:
+        sprintf(s, "%d", (int) reason);
+        return s;
+    }
+}
+
 static void
 ofp_print_flow_removed(struct ds *string, const struct ofp_header *oh)
 {
 static void
 ofp_print_flow_removed(struct ds *string, const struct ofp_header *oh)
 {
@@ -885,21 +909,8 @@ ofp_print_flow_removed(struct ds *string, const struct ofp_header *oh)
     ds_put_char(string, ' ');
     cls_rule_format(&fr.rule, string);
 
     ds_put_char(string, ' ');
     cls_rule_format(&fr.rule, string);
 
-    ds_put_cstr(string, " reason=");
-    switch (fr.reason) {
-    case OFPRR_IDLE_TIMEOUT:
-        ds_put_cstr(string, "idle");
-        break;
-    case OFPRR_HARD_TIMEOUT:
-        ds_put_cstr(string, "hard");
-        break;
-    case OFPRR_DELETE:
-        ds_put_cstr(string, "delete");
-        break;
-    default:
-        ds_put_format(string, "**%"PRIu8"**", fr.reason);
-        break;
-    }
+    ds_put_format(string, " reason=%s",
+                  ofp_flow_removed_reason_to_string(fr.reason));
 
     if (fr.cookie != htonll(0)) {
         ds_put_format(string, " cookie:0x%"PRIx64, ntohll(fr.cookie));
 
     if (fr.cookie != htonll(0)) {
         ds_put_format(string, " cookie:0x%"PRIx64, ntohll(fr.cookie));
@@ -1313,6 +1324,75 @@ ofp_print_nxt_set_packet_in_format(struct ds *string,
     }
 }
 
     }
 }
 
+static const char *
+ofp_port_reason_to_string(enum ofp_port_reason reason)
+{
+    static char s[32];
+
+    switch (reason) {
+    case OFPPR_ADD:
+        return "add";
+
+    case OFPPR_DELETE:
+        return "delete";
+
+    case OFPPR_MODIFY:
+        return "modify";
+
+    default:
+        sprintf(s, "%d", (int) reason);
+        return s;
+    }
+}
+
+static void
+ofp_print_nxt_set_async_config(struct ds *string,
+                               const struct nx_async_config *nac)
+{
+    int i;
+
+    for (i = 0; i < 2; i++) {
+        int j;
+
+        ds_put_format(string, "\n %s:\n", i == 0 ? "master" : "slave");
+
+        ds_put_cstr(string, "       PACKET_IN:");
+        for (j = 0; j < 32; j++) {
+            if (nac->packet_in_mask[i] & htonl(1u << j)) {
+                ds_put_format(string, " %s",
+                              ofp_packet_in_reason_to_string(j));
+            }
+        }
+        if (!nac->packet_in_mask[i]) {
+            ds_put_cstr(string, " (off)");
+        }
+        ds_put_char(string, '\n');
+
+        ds_put_cstr(string, "     PORT_STATUS:");
+        for (j = 0; j < 32; j++) {
+            if (nac->port_status_mask[i] & htonl(1u << j)) {
+                ds_put_format(string, " %s", ofp_port_reason_to_string(j));
+            }
+        }
+        if (!nac->port_status_mask[i]) {
+            ds_put_cstr(string, " (off)");
+        }
+        ds_put_char(string, '\n');
+
+        ds_put_cstr(string, "    FLOW_REMOVED:");
+        for (j = 0; j < 32; j++) {
+            if (nac->flow_removed_mask[i] & htonl(1u << j)) {
+                ds_put_format(string, " %s",
+                              ofp_flow_removed_reason_to_string(j));
+            }
+        }
+        if (!nac->flow_removed_mask[i]) {
+            ds_put_cstr(string, " (off)");
+        }
+        ds_put_char(string, '\n');
+    }
+}
+
 static void
 ofp_to_string__(const struct ofp_header *oh,
                 const struct ofputil_msg_type *type, struct ds *string,
 static void
 ofp_to_string__(const struct ofp_header *oh,
                 const struct ofputil_msg_type *type, struct ds *string,
@@ -1472,6 +1552,10 @@ ofp_to_string__(const struct ofp_header *oh,
     case OFPUTIL_NXT_FLOW_AGE:
         break;
 
     case OFPUTIL_NXT_FLOW_AGE:
         break;
 
+    case OFPUTIL_NXT_SET_ASYNC_CONFIG:
+        ofp_print_nxt_set_async_config(string, msg);
+        break;
+
     case OFPUTIL_NXST_AGGREGATE_REPLY:
         ofp_print_stats_reply(string, oh);
         ofp_print_nxst_aggregate_reply(string, msg);
     case OFPUTIL_NXST_AGGREGATE_REPLY:
         ofp_print_stats_reply(string, oh);
         ofp_print_nxst_aggregate_reply(string, msg);
index c377e3e..e936693 100644 (file)
@@ -400,6 +400,10 @@ ofputil_decode_vendor(const struct ofp_header *oh, size_t length,
         { OFPUTIL_NXT_FLOW_AGE, OFP10_VERSION,
           NXT_FLOW_AGE, "NXT_FLOW_AGE",
           sizeof(struct nicira_header), 0 },
         { OFPUTIL_NXT_FLOW_AGE, OFP10_VERSION,
           NXT_FLOW_AGE, "NXT_FLOW_AGE",
           sizeof(struct nicira_header), 0 },
+
+        { OFPUTIL_NXT_SET_ASYNC_CONFIG, OFP10_VERSION,
+          NXT_SET_ASYNC_CONFIG, "NXT_SET_ASYNC_CONFIG",
+          sizeof(struct nx_async_config), 0 },
     };
 
     static const struct ofputil_msg_category nxt_category = {
     };
 
     static const struct ofputil_msg_category nxt_category = {
index 0b6970a..816532e 100644 (file)
@@ -80,6 +80,7 @@ enum ofputil_msg_code {
     OFPUTIL_NXT_SET_PACKET_IN_FORMAT,
     OFPUTIL_NXT_PACKET_IN,
     OFPUTIL_NXT_FLOW_AGE,
     OFPUTIL_NXT_SET_PACKET_IN_FORMAT,
     OFPUTIL_NXT_PACKET_IN,
     OFPUTIL_NXT_FLOW_AGE,
+    OFPUTIL_NXT_SET_ASYNC_CONFIG,
 
     /* NXST_* stat requests. */
     OFPUTIL_NXST_FLOW_REQUEST,
 
     /* NXST_* stat requests. */
     OFPUTIL_NXST_FLOW_REQUEST,
index db848fb..a3ffb69 100644 (file)
@@ -60,8 +60,6 @@ struct ofconn {
     enum nx_flow_format flow_format; /* Currently selected flow format. */
     enum nx_packet_in_format packet_in_format; /* OFPT_PACKET_IN format. */
     bool flow_mod_table_id;     /* NXT_FLOW_MOD_TABLE_ID enabled? */
     enum nx_flow_format flow_format; /* Currently selected flow format. */
     enum nx_packet_in_format packet_in_format; /* OFPT_PACKET_IN format. */
     bool flow_mod_table_id;     /* NXT_FLOW_MOD_TABLE_ID enabled? */
-    bool invalid_ttl_to_controller; /* Send packets with invalid TTL
-                                       to the controller. */
 
     /* Asynchronous flow table operation support. */
     struct list opgroups;       /* Contains pending "ofopgroups", if any. */
 
     /* Asynchronous flow table operation support. */
     struct list opgroups;       /* Contains pending "ofopgroups", if any. */
@@ -80,6 +78,13 @@ struct ofconn {
      * requests.  */
 #define OFCONN_REPLY_MAX 100
     struct rconn_packet_counter *reply_counter;
      * requests.  */
 #define OFCONN_REPLY_MAX 100
     struct rconn_packet_counter *reply_counter;
+
+    /* Asynchronous message configuration in each possible roles.
+     *
+     * A 1-bit enables sending an asynchronous message for one possible reason
+     * that the message might be generated, a 0-bit disables it. */
+    uint32_t master_async_config[OAM_N_TYPES]; /* master, other */
+    uint32_t slave_async_config[OAM_N_TYPES];  /* slave */
 };
 
 static struct ofconn *ofconn_create(struct connmgr *, struct rconn *,
 };
 
 static struct ofconn *ofconn_create(struct connmgr *, struct rconn *,
@@ -100,8 +105,6 @@ static char *ofconn_make_name(const struct connmgr *, const char *target);
 
 static void ofconn_set_rate_limit(struct ofconn *, int rate, int burst);
 
 
 static void ofconn_set_rate_limit(struct ofconn *, int rate, int burst);
 
-static bool ofconn_receives_async_msgs(const struct ofconn *);
-
 static void ofconn_send(const struct ofconn *, struct ofpbuf *,
                         struct rconn_packet_counter *);
 
 static void ofconn_send(const struct ofconn *, struct ofpbuf *,
                         struct rconn_packet_counter *);
 
@@ -762,15 +765,21 @@ ofconn_set_role(struct ofconn *ofconn, enum nx_role role)
 }
 
 void
 }
 
 void
-ofconn_set_invalid_ttl_to_controller(struct ofconn *ofconn, bool val)
+ofconn_set_invalid_ttl_to_controller(struct ofconn *ofconn, bool enable)
 {
 {
-    ofconn->invalid_ttl_to_controller = val;
+    uint32_t bit = 1u << OFPR_INVALID_TTL;
+    if (enable) {
+        ofconn->master_async_config[OAM_PACKET_IN] |= bit;
+    } else {
+        ofconn->master_async_config[OAM_PACKET_IN] &= ~bit;
+    }
 }
 
 bool
 ofconn_get_invalid_ttl_to_controller(struct ofconn *ofconn)
 {
 }
 
 bool
 ofconn_get_invalid_ttl_to_controller(struct ofconn *ofconn)
 {
-    return ofconn->invalid_ttl_to_controller;
+    uint32_t bit = 1u << OFPR_INVALID_TTL;
+    return (ofconn->master_async_config[OAM_PACKET_IN] & bit) != 0;
 }
 
 /* Returns the currently configured flow format for 'ofconn', one of NXFF_*.
 }
 
 /* Returns the currently configured flow format for 'ofconn', one of NXFF_*.
@@ -840,6 +849,16 @@ ofconn_set_miss_send_len(struct ofconn *ofconn, int miss_send_len)
     ofconn->miss_send_len = miss_send_len;
 }
 
     ofconn->miss_send_len = miss_send_len;
 }
 
+void
+ofconn_set_async_config(struct ofconn *ofconn,
+                        const uint32_t master_masks[OAM_N_TYPES],
+                        const uint32_t slave_masks[OAM_N_TYPES])
+{
+    size_t size = sizeof ofconn->master_async_config;
+    memcpy(ofconn->master_async_config, master_masks, size);
+    memcpy(ofconn->slave_async_config, slave_masks, size);
+}
+
 /* Sends 'msg' on 'ofconn', accounting it as a reply.  (If there is a
  * sufficient number of OpenFlow replies in-flight on a single ofconn, then the
  * connmgr will stop accepting new OpenFlow requests on that ofconn until the
 /* Sends 'msg' on 'ofconn', accounting it as a reply.  (If there is a
  * sufficient number of OpenFlow replies in-flight on a single ofconn, then the
  * connmgr will stop accepting new OpenFlow requests on that ofconn until the
@@ -956,6 +975,8 @@ ofconn_create(struct connmgr *mgr, struct rconn *rconn, enum ofconn_type type)
 static void
 ofconn_flush(struct ofconn *ofconn)
 {
 static void
 ofconn_flush(struct ofconn *ofconn)
 {
+    uint32_t *master = ofconn->master_async_config;
+    uint32_t *slave = ofconn->slave_async_config;
     int i;
 
     ofconn->role = NX_ROLE_OTHER;
     int i;
 
     ofconn->role = NX_ROLE_OTHER;
@@ -996,6 +1017,24 @@ ofconn_flush(struct ofconn *ofconn)
 
     rconn_packet_counter_destroy(ofconn->reply_counter);
     ofconn->reply_counter = rconn_packet_counter_create();
 
     rconn_packet_counter_destroy(ofconn->reply_counter);
     ofconn->reply_counter = rconn_packet_counter_create();
+
+    /* "master" and "other" roles get all asynchronous messages by default,
+     * except that the controller needs to enable nonstandard "packet-in"
+     * reasons itself. */
+    master[OAM_PACKET_IN] = (1u << OFPR_NO_MATCH) | (1u << OFPR_ACTION);
+    master[OAM_PORT_STATUS] = ((1u << OFPPR_ADD)
+                               | (1u << OFPPR_DELETE)
+                               | (1u << OFPPR_MODIFY));
+    master[OAM_FLOW_REMOVED] = ((1u << OFPRR_IDLE_TIMEOUT)
+                                | (1u << OFPRR_HARD_TIMEOUT)
+                                | (1u << OFPRR_DELETE));
+
+    /* "slave" role gets port status updates by default. */
+    slave[OAM_PACKET_IN] = 0;
+    slave[OAM_PORT_STATUS] = ((1u << OFPPR_ADD)
+                              | (1u << OFPPR_DELETE)
+                              | (1u << OFPPR_MODIFY));
+    slave[OAM_FLOW_REMOVED] = 0;
 }
 
 static void
 }
 
 static void
@@ -1100,42 +1139,42 @@ ofconn_wait(struct ofconn *ofconn, bool handling_openflow)
     }
 }
 
     }
 }
 
-/* Returns true if 'ofconn' should receive asynchronous messages. */
+/* Returns true if 'ofconn' should receive asynchronous messages of the given
+ * OAM_* 'type' and 'reason', which should be a OFPR_* value for OAM_PACKET_IN,
+ * a OFPPR_* value for OAM_PORT_STATUS, or an OFPRR_* value for
+ * OAM_FLOW_REMOVED.  Returns false if the message should not be sent on
+ * 'ofconn'. */
 static bool
 static bool
-ofconn_receives_async_msgs__(const struct ofconn *ofconn)
+ofconn_receives_async_msg(const struct ofconn *ofconn,
+                          enum ofconn_async_msg_type type,
+                          unsigned int reason)
 {
 {
-    if (ofconn->type == OFCONN_PRIMARY) {
-        /* Primary controllers always get asynchronous messages unless they
-         * have configured themselves as "slaves".  */
-        return ofconn->role != NX_ROLE_SLAVE;
-    } else {
-        /* Service connections don't get asynchronous messages unless they have
-         * explicitly asked for them by setting a nonzero miss send length. */
-        return ofconn->miss_send_len > 0;
-    }
-}
+    const uint32_t *async_config;
+
+    assert(reason < 32);
+    assert((unsigned int) type < OAM_N_TYPES);
 
 
-static bool
-ofconn_receives_async_msgs(const struct ofconn *ofconn)
-{
     if (!rconn_is_connected(ofconn->rconn)) {
         return false;
     if (!rconn_is_connected(ofconn->rconn)) {
         return false;
-    } else {
-        return ofconn_receives_async_msgs__(ofconn);
     }
     }
-}
 
 
-static bool
-ofconn_interested_in_packet(const struct ofconn *ofconn,
-                            const struct ofputil_packet_in *pin)
-{
-    if (!rconn_is_connected(ofconn->rconn)) {
+    /* Keep the following code in sync with the documentation in the
+     * "Asynchronous Messages" section in DESIGN. */
+
+    if (ofconn->type == OFCONN_SERVICE && !ofconn->miss_send_len) {
+        /* Service connections don't get asynchronous messages unless they have
+         * explicitly asked for them by setting a nonzero miss send length. */
         return false;
         return false;
-    } else if (pin->reason == OFPR_INVALID_TTL) {
-        return ofconn->invalid_ttl_to_controller;
-    } else {
-        return ofconn_receives_async_msgs__(ofconn);
     }
     }
+
+    async_config = (ofconn->role == NX_ROLE_SLAVE
+                    ? ofconn->slave_async_config
+                    : ofconn->master_async_config);
+    if (!(async_config[type] & (1u << reason))) {
+        return false;
+    }
+
+    return true;
 }
 
 /* Returns a human-readable name for an OpenFlow connection between 'mgr' and
 }
 
 /* Returns a human-readable name for an OpenFlow connection between 'mgr' and
@@ -1195,20 +1234,15 @@ connmgr_send_port_status(struct connmgr *mgr, const struct ofp_phy_port *opp,
     struct ofconn *ofconn;
 
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
     struct ofconn *ofconn;
 
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
-        struct ofp_port_status *ops;
-        struct ofpbuf *b;
-
-        /* Primary controllers, even slaves, should always get port status
-           updates.  Otherwise obey ofconn_receives_async_msgs(). */
-        if (ofconn->type != OFCONN_PRIMARY
-            && !ofconn_receives_async_msgs(ofconn)) {
-            continue;
+        if (ofconn_receives_async_msg(ofconn, OAM_PORT_STATUS, reason)) {
+            struct ofp_port_status *ops;
+            struct ofpbuf *b;
+
+            ops = make_openflow_xid(sizeof *ops, OFPT_PORT_STATUS, 0, &b);
+            ops->reason = reason;
+            ops->desc = *opp;
+            ofconn_send(ofconn, b, NULL);
         }
         }
-
-        ops = make_openflow_xid(sizeof *ops, OFPT_PORT_STATUS, 0, &b);
-        ops->reason = reason;
-        ops->desc = *opp;
-        ofconn_send(ofconn, b, NULL);
     }
 }
 
     }
 }
 
@@ -1221,19 +1255,17 @@ connmgr_send_flow_removed(struct connmgr *mgr,
     struct ofconn *ofconn;
 
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
     struct ofconn *ofconn;
 
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
-        struct ofpbuf *msg;
-
-        if (!ofconn_receives_async_msgs(ofconn)) {
-            continue;
+        if (ofconn_receives_async_msg(ofconn, OAM_FLOW_REMOVED, fr->reason)) {
+            struct ofpbuf *msg;
+
+            /* Account flow expirations as replies to OpenFlow requests.  That
+             * works because preventing OpenFlow requests from being processed
+             * also prevents new flows from being added (and expiring).  (It
+             * also prevents processing OpenFlow requests that would not add
+             * new flows, so it is imperfect.) */
+            msg = ofputil_encode_flow_removed(fr, ofconn->flow_format);
+            ofconn_send_reply(ofconn, msg);
         }
         }
-
-        /* Account flow expirations as replies to OpenFlow requests.  That
-         * works because preventing OpenFlow requests from being processed also
-         * prevents new flows from being added (and expiring).  (It also
-         * prevents processing OpenFlow requests that would not add new flows,
-         * so it is imperfect.) */
-        msg = ofputil_encode_flow_removed(fr, ofconn->flow_format);
-        ofconn_send_reply(ofconn, msg);
     }
 }
 
     }
 }
 
@@ -1247,7 +1279,7 @@ connmgr_send_packet_in(struct connmgr *mgr,
     struct ofconn *ofconn;
 
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
     struct ofconn *ofconn;
 
     LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
-        if (ofconn_interested_in_packet(ofconn, pin)) {
+        if (ofconn_receives_async_msg(ofconn, OAM_PACKET_IN, pin->reason)) {
             schedule_packet_in(ofconn, *pin, flow);
         }
     }
             schedule_packet_in(ofconn, *pin, flow);
         }
     }
index 8ff89f3..5f9ac70 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009, 2010, 2011 Nicira Networks.
+ * Copyright (c) 2009, 2010, 2011, 2012 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,6 +51,14 @@ enum ofconn_type {
     OFCONN_SERVICE              /* A service connection, e.g. "ovs-ofctl". */
 };
 
     OFCONN_SERVICE              /* A service connection, e.g. "ovs-ofctl". */
 };
 
+/* The type of an OpenFlow asynchronous message. */
+enum ofconn_async_msg_type {
+    OAM_PACKET_IN,              /* OFPT_PACKET_IN or NXT_PACKET_IN. */
+    OAM_PORT_STATUS,            /* OFPT_PORT_STATUS. */
+    OAM_FLOW_REMOVED,           /* OFPT_FLOW_REMOVED or NXT_FLOW_REMOVED. */
+    OAM_N_TYPES
+};
+
 /* Basics. */
 struct connmgr *connmgr_create(struct ofproto *ofproto,
                                const char *dpif_name, const char *local_name);
 /* Basics. */
 struct connmgr *connmgr_create(struct ofproto *ofproto,
                                const char *dpif_name, const char *local_name);
@@ -98,6 +106,10 @@ bool ofconn_get_invalid_ttl_to_controller(struct ofconn *);
 int ofconn_get_miss_send_len(const struct ofconn *);
 void ofconn_set_miss_send_len(struct ofconn *, int miss_send_len);
 
 int ofconn_get_miss_send_len(const struct ofconn *);
 void ofconn_set_miss_send_len(struct ofconn *, int miss_send_len);
 
+void ofconn_set_async_config(struct ofconn *,
+                             const uint32_t master_masks[OAM_N_TYPES],
+                             const uint32_t slave_masks[OAM_N_TYPES]);
+
 void ofconn_send_reply(const struct ofconn *, struct ofpbuf *);
 void ofconn_send_replies(const struct ofconn *, struct list *);
 void ofconn_send_error(const struct ofconn *, const struct ofp_header *request,
 void ofconn_send_reply(const struct ofconn *, struct ofpbuf *);
 void ofconn_send_replies(const struct ofconn *, struct list *);
 void ofconn_send_error(const struct ofconn *, const struct ofp_header *request,
index 92744a2..7b99741 100644 (file)
@@ -3117,6 +3117,26 @@ handle_nxt_set_packet_in_format(struct ofconn *ofconn,
     return 0;
 }
 
     return 0;
 }
 
+static enum ofperr
+handle_nxt_set_async_config(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+    const struct nx_async_config *msg = (const struct nx_async_config *) oh;
+    uint32_t master[OAM_N_TYPES];
+    uint32_t slave[OAM_N_TYPES];
+
+    master[OAM_PACKET_IN] = ntohl(msg->packet_in_mask[0]);
+    master[OAM_PORT_STATUS] = ntohl(msg->port_status_mask[0]);
+    master[OAM_FLOW_REMOVED] = ntohl(msg->flow_removed_mask[0]);
+
+    slave[OAM_PACKET_IN] = ntohl(msg->packet_in_mask[1]);
+    slave[OAM_PORT_STATUS] = ntohl(msg->port_status_mask[1]);
+    slave[OAM_FLOW_REMOVED] = ntohl(msg->flow_removed_mask[1]);
+
+    ofconn_set_async_config(ofconn, master, slave);
+
+    return 0;
+}
+
 static enum ofperr
 handle_barrier_request(struct ofconn *ofconn, const struct ofp_header *oh)
 {
 static enum ofperr
 handle_barrier_request(struct ofconn *ofconn, const struct ofp_header *oh)
 {
@@ -3194,6 +3214,9 @@ handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
         /* Nothing to do. */
         return 0;
 
         /* Nothing to do. */
         return 0;
 
+    case OFPUTIL_NXT_SET_ASYNC_CONFIG:
+        return handle_nxt_set_async_config(ofconn, oh);
+
         /* Statistics requests. */
     case OFPUTIL_OFPST_DESC_REQUEST:
         return handle_desc_stats_request(ofconn, msg->data);
         /* Statistics requests. */
     case OFPUTIL_OFPST_DESC_REQUEST:
         return handle_desc_stats_request(ofconn, msg->data);
index 09c9e83..471a73c 100644 (file)
@@ -708,6 +708,26 @@ priority:0,tunnel:0,in_port:0000,tci(vlan:80,pcp:0) mac(80:81:81:81:81:81->82:82
 ])
 AT_CLEANUP
 
 ])
 AT_CLEANUP
 
+AT_SETUP([NXT_SET_ASYNC_CONFIG])
+AT_KEYWORDS([ofp-print])
+AT_CHECK([ovs-ofctl ofp-print "\
+01 04 00 28 00 00 00 00 00 00 23 20 00 00 00 13 \
+00 00 10 05 00 00 10 07 00 00 00 03 00 00 00 07 \
+00 00 00 00 00 00 00 03 \
+"], [0], [dnl
+NXT_SET_ASYNC_CONFIG (xid=0x0):
+ master:
+       PACKET_IN: no_match invalid_ttl 12
+     PORT_STATUS: add delete
+    FLOW_REMOVED: (off)
+
+ slave:
+       PACKET_IN: no_match action invalid_ttl 12
+     PORT_STATUS: add delete modify
+    FLOW_REMOVED: idle hard
+])
+AT_CLEANUP
+
 AT_SETUP([NXT_SET_FLOW_FORMAT])
 AT_KEYWORDS([ofp-print])
 AT_CHECK([ovs-ofctl ofp-print "\
 AT_SETUP([NXT_SET_FLOW_FORMAT])
 AT_KEYWORDS([ofp-print])
 AT_CHECK([ovs-ofctl ofp-print "\
index d7fef5d..9358a6a 100644 (file)
@@ -174,6 +174,7 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
  cookie=0x3, in_port=3 actions=output:0
 NXST_FLOW reply:
 ])
  cookie=0x3, in_port=3 actions=output:0
 NXST_FLOW reply:
 ])
+
 AT_CHECK([ovs-ofctl del-flows br0 cookie=0x3])
 AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
  cookie=0x1, in_port=1 actions=output:0
 AT_CHECK([ovs-ofctl del-flows br0 cookie=0x3])
 AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
  cookie=0x1, in_port=1 actions=output:0
@@ -421,3 +422,89 @@ NXST_FLOW reply:
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([ofproto - asynchronous message control])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-ofctl -P openflow10 monitor br0 --detach --no-chdir --pidfile])
+check_async () {
+    printf '\n\n--- check_async %d ---\n\n\n' $1
+    shift
+
+    ovs-appctl -t ovs-ofctl ofctl/set-output-file monitor.log
+    : > expout
+
+    # OFPT_PACKET_IN, OFPR_ACTION
+    ovs-ofctl -v packet-out br0 none controller '0001020304050010203040501234'
+    if test X"$1" = X"OFPR_ACTION"; then shift;
+        echo >>expout "OFPT_PACKET_IN: total_len=14 in_port=NONE (via action) data_len=14 (unbuffered)
+priority:0,tunnel:0,in_port:0000,tci(0) mac(00:10:20:30:40:50->00:01:02:03:04:05) type:1234 proto:0 tos:0 ttl:0 ip(0.0.0.0->0.0.0.0)"
+    fi
+
+    # OFPT_PACKET_IN, OFPR_INVALID_TTL
+    ovs-ofctl packet-out br0 none dec_ttl '002583dfb4000026b98cb0f908004500003fb7e200000011339bac11370dac100002d7730035002b8f6d86fb0100000100000000000006626c702d7873066e696369726103636f6d00000f00'
+    if test X"$1" = X"OFPR_INVALID_TTL"; then shift;
+        echo >>expout "OFPT_PACKET_IN: total_len=76 in_port=NONE (via invalid_ttl) data_len=76 (unbuffered)
+priority:0,tunnel:0,in_port:0000,tci(0) mac(00:26:b9:8c:b0:f9->00:25:83:df:b4:00) type:0800 proto:17 tos:0 ttl:0 ip(172.17.55.13->172.16.0.2) port(55155->53) udp_csum:8f6d"
+    fi
+
+    # OFPT_PORT_STATUS, OFPPR_ADD
+    ovs-vsctl add-port br0 test -- set Interface test type=dummy
+    if test X"$1" = X"OFPPR_ADD"; then shift;
+        echo >>expout "OFPT_PORT_STATUS: ADD: 1(test): addr:aa:55:aa:55:00:0x
+     config:     PORT_DOWN
+     state:      LINK_DOWN"
+    fi
+
+    # OFPT_PORT_STATUS, OFPPR_DELETE
+    ovs-vsctl del-port br0 test
+    if test X"$1" = X"OFPPR_DELETE"; then shift;
+        echo >>expout "OFPT_PORT_STATUS: DEL: 1(test): addr:aa:55:aa:55:00:0x
+     config:     PORT_DOWN
+     state:      LINK_DOWN"
+    fi
+
+    # OFPT_FLOW_REMOVED, OFPRR_DELETE
+    ovs-ofctl add-flow br0 send_flow_rem,actions=drop
+    ovs-ofctl --strict del-flows br0 ''
+    if test X"$1" = X"OFPRR_DELETE"; then shift;
+        echo >>expout "OFPT_FLOW_REMOVED:  reason=delete"
+    fi
+    AT_FAIL_IF([test X"$1" != X])
+
+    ovs-appctl -t ovs-ofctl ofctl/barrier
+    echo >>expout "send: OFPT_BARRIER_REQUEST:
+OFPT_BARRIER_REPLY:"
+
+    AT_CHECK(
+      [[sed '
+s/ (xid=0x[0-9a-fA-F]*)//
+s/ *duration.*//
+s/00:0.$/00:0x/' < monitor.log]],
+      [0], [expout])
+}
+
+# It's a service connection so initially there should be no async messages.
+check_async 1
+
+# Set miss_send_len to 128, turning on packet-outs for our service connection.
+ovs-appctl -t ovs-ofctl ofctl/send 0109000c0123456700000080
+check_async 2 OFPR_ACTION OFPPR_ADD OFPPR_DELETE OFPRR_DELETE
+
+# Set miss_send_len to 128 and enable invalid_ttl.
+ovs-appctl -t ovs-ofctl ofctl/send 0109000c0123456700040080
+check_async 3 OFPR_ACTION OFPR_INVALID_TTL OFPPR_ADD OFPPR_DELETE OFPRR_DELETE
+
+# Become slave, which should disable everything except port status.
+ovs-appctl -t ovs-ofctl ofctl/send 0104001400000002000023200000000a00000002
+check_async 4 OFPPR_ADD OFPPR_DELETE
+
+# Use NXT_SET_ASYNC_CONFIG to enable a patchwork of asynchronous messages.
+ovs-appctl -t ovs-ofctl ofctl/send 01040028000000020000232000000013000000020000000500000005000000020000000200000005
+check_async 5 OFPR_INVALID_TTL OFPPR_DELETE OFPRR_DELETE
+
+# Become master.
+ovs-appctl -t ovs-ofctl ofctl/send 0104001400000002000023200000000a00000001
+check_async 6 OFPR_ACTION OFPPR_ADD
+
+ovs-appctl -t ovs-ofctl exit
+AT_CLEANUP