ofproto: Group functions related to flow expiration together.
[openvswitch] / ofproto / ofproto.c
index e571bd4e2314d2113213ca83c769c7bb16e27334..6de30b399eb54afe2f5e52e77f9099919f5af3cb 100644 (file)
@@ -30,6 +30,8 @@
 #include "dpif.h"
 #include "dynamic-string.h"
 #include "fail-open.h"
+#include "hash.h"
+#include "hmap.h"
 #include "in-band.h"
 #include "mac-learning.h"
 #include "netdev.h"
@@ -46,7 +48,6 @@
 #include "pinsched.h"
 #include "pktbuf.h"
 #include "poll-loop.h"
-#include "port-array.h"
 #include "rconn.h"
 #include "shash.h"
 #include "status.h"
@@ -68,9 +69,12 @@ enum {
     TABLEID_CLASSIFIER = 1
 };
 
+
 struct ofport {
+    struct hmap_node hmap_node; /* In struct ofproto's "ports" hmap. */
     struct netdev *netdev;
     struct ofp_phy_port opp;    /* In host byte order. */
+    uint16_t odp_port;
 };
 
 static void ofport_free(struct ofport *);
@@ -272,8 +276,7 @@ struct ofproto {
     /* Datapath. */
     struct dpif *dpif;
     struct netdev_monitor *netdev_monitor;
-    struct port_array ports;    /* Index is ODP port nr; ofport->opp.port_no is
-                                 * OFP port nr. */
+    struct hmap ports;          /* Contains "struct ofport"s. */
     struct shash port_by_name;
     uint32_t max_ports;
 
@@ -321,11 +324,10 @@ static const struct ofhooks default_ofhooks;
 static uint64_t pick_datapath_id(const struct ofproto *);
 static uint64_t pick_fallback_dpid(void);
 
-static void update_used(struct ofproto *);
+static void ofproto_expire(struct ofproto *);
+
 static void update_stats(struct ofproto *, struct rule *,
                          const struct odp_flow_stats *);
-static void expire_rule(struct cls_rule *, void *ofproto);
-static void active_timeout(struct ofproto *ofproto, struct rule *rule);
 static bool revalidate_rule(struct ofproto *p, struct rule *rule);
 static void revalidate_cb(struct cls_rule *rule_, void *p_);
 
@@ -336,6 +338,7 @@ static void handle_openflow(struct ofconn *, struct ofproto *,
 
 static void refresh_port_groups(struct ofproto *);
 
+static struct ofport *get_port(const struct ofproto *, uint16_t odp_port);
 static void update_port(struct ofproto *, const char *devname);
 static int init_ports(struct ofproto *);
 static void reinit_ports(struct ofproto *);
@@ -388,7 +391,7 @@ ofproto_create(const char *datapath, const char *datapath_type,
     /* Initialize datapath. */
     p->dpif = dpif;
     p->netdev_monitor = netdev_monitor_create();
-    port_array_init(&p->ports);
+    hmap_init(&p->ports);
     shash_init(&p->port_by_name);
     p->max_ports = stats.max_ports;
 
@@ -528,7 +531,7 @@ find_controller_by_target(struct ofproto *ofproto, const char *target)
 {
     struct ofconn *ofconn;
 
-    HMAP_FOR_EACH_WITH_HASH (ofconn, struct ofconn, hmap_node,
+    HMAP_FOR_EACH_WITH_HASH (ofconn, hmap_node,
                              hash_string(target, 0), &ofproto->controllers) {
         if (!strcmp(ofconn_get_target(ofconn), target)) {
             return ofconn;
@@ -553,7 +556,7 @@ update_in_band_remotes(struct ofproto *ofproto)
 
     /* Add all the remotes. */
     discovery = false;
-    HMAP_FOR_EACH (ofconn, struct ofconn, hmap_node, &ofproto->controllers) {
+    HMAP_FOR_EACH (ofconn, hmap_node, &ofproto->controllers) {
         struct sockaddr_in *sin = &addrs[n_addrs];
 
         if (ofconn->band == OFPROTO_OUT_OF_BAND) {
@@ -612,7 +615,7 @@ update_fail_open(struct ofproto *p)
 
         n = 0;
         rconns = xmalloc(hmap_count(&p->controllers) * sizeof *rconns);
-        HMAP_FOR_EACH (ofconn, struct ofconn, hmap_node, &p->controllers) {
+        HMAP_FOR_EACH (ofconn, hmap_node, &p->controllers) {
             rconns[n++] = ofconn->rconn;
         }
 
@@ -661,8 +664,7 @@ ofproto_set_controllers(struct ofproto *p,
     /* Delete controllers that are no longer configured.
      * Update configuration of all now-existing controllers. */
     ss_exists = false;
-    HMAP_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, hmap_node,
-                        &p->controllers) {
+    HMAP_FOR_EACH_SAFE (ofconn, next_ofconn, hmap_node, &p->controllers) {
         struct ofproto_controller *c;
 
         c = shash_find_data(&new_controllers, ofconn_get_target(ofconn));
@@ -678,8 +680,7 @@ ofproto_set_controllers(struct ofproto *p,
 
     /* Delete services that are no longer configured.
      * Update configuration of all now-existing services. */
-    HMAP_FOR_EACH_SAFE (ofservice, next_ofservice, struct ofservice, node,
-                        &p->services) {
+    HMAP_FOR_EACH_SAFE (ofservice, next_ofservice, node, &p->services) {
         struct ofproto_controller *c;
 
         c = shash_find_data(&new_controllers,
@@ -718,7 +719,7 @@ ofproto_reconnect_controllers(struct ofproto *ofproto)
 {
     struct ofconn *ofconn;
 
-    LIST_FOR_EACH (ofconn, struct ofconn, node, &ofproto->all_conns) {
+    LIST_FOR_EACH (ofconn, node, &ofproto->all_conns) {
         rconn_reconnect(ofconn->rconn);
     }
 }
@@ -883,12 +884,11 @@ ofproto_set_sflow(struct ofproto *ofproto,
     if (oso) {
         if (!os) {
             struct ofport *ofport;
-            unsigned int odp_port;
 
             os = ofproto->sflow = ofproto_sflow_create(ofproto->dpif);
             refresh_port_groups(ofproto);
-            PORT_ARRAY_FOR_EACH (ofport, &ofproto->ports, odp_port) {
-                ofproto_sflow_add_port(os, odp_port,
+            HMAP_FOR_EACH (ofport, hmap_node, &ofproto->ports) {
+                ofproto_sflow_add_port(os, ofport->odp_port,
                                        netdev_get_name(ofport->netdev));
             }
         }
@@ -932,8 +932,7 @@ ofproto_destroy(struct ofproto *p)
 {
     struct ofservice *ofservice, *next_ofservice;
     struct ofconn *ofconn, *next_ofconn;
-    struct ofport *ofport;
-    unsigned int port_no;
+    struct ofport *ofport, *next_ofport;
     size_t i;
 
     if (!p) {
@@ -951,15 +950,15 @@ ofproto_destroy(struct ofproto *p)
     ofproto_flush_flows(p);
     classifier_destroy(&p->cls);
 
-    LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node,
-                        &p->all_conns) {
+    LIST_FOR_EACH_SAFE (ofconn, next_ofconn, node, &p->all_conns) {
         ofconn_destroy(ofconn);
     }
     hmap_destroy(&p->controllers);
 
     dpif_close(p->dpif);
     netdev_monitor_destroy(p->netdev_monitor);
-    PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) {
+    HMAP_FOR_EACH_SAFE (ofport, next_ofport, hmap_node, &p->ports) {
+        hmap_remove(&p->ports, &ofport->hmap_node);
         ofport_free(ofport);
     }
     shash_destroy(&p->port_by_name);
@@ -968,8 +967,7 @@ ofproto_destroy(struct ofproto *p)
     netflow_destroy(p->netflow);
     ofproto_sflow_destroy(p->sflow);
 
-    HMAP_FOR_EACH_SAFE (ofservice, next_ofservice, struct ofservice, node,
-                        &p->services) {
+    HMAP_FOR_EACH_SAFE (ofservice, next_ofservice, node, &p->services) {
         ofservice_destroy(p, ofservice);
     }
     hmap_destroy(&p->services);
@@ -987,7 +985,7 @@ ofproto_destroy(struct ofproto *p)
     free(p->serial_desc);
     free(p->dp_desc);
 
-    port_array_destroy(&p->ports);
+    hmap_destroy(&p->ports);
 
     free(p);
 }
@@ -1041,7 +1039,7 @@ add_snooper(struct ofproto *ofproto, struct vconn *vconn)
 
     /* Pick a controller for monitoring. */
     best = NULL;
-    LIST_FOR_EACH (ofconn, struct ofconn, node, &ofproto->all_conns) {
+    LIST_FOR_EACH (ofconn, node, &ofproto->all_conns) {
         if (ofconn->type == OFCONN_PRIMARY
             && (!best || snoop_preference(ofconn) > snoop_preference(best))) {
             best = ofconn;
@@ -1104,8 +1102,7 @@ ofproto_run1(struct ofproto *p)
         in_band_run(p->in_band);
     }
 
-    LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node,
-                        &p->all_conns) {
+    LIST_FOR_EACH_SAFE (ofconn, next_ofconn, node, &p->all_conns) {
         ofconn_run(ofconn, p);
     }
 
@@ -1115,7 +1112,7 @@ ofproto_run1(struct ofproto *p)
         fail_open_run(p->fail_open);
     }
 
-    HMAP_FOR_EACH (ofservice, struct ofservice, node, &p->services) {
+    HMAP_FOR_EACH (ofservice, node, &p->services) {
         struct vconn *vconn;
         int retval;
 
@@ -1151,18 +1148,8 @@ ofproto_run1(struct ofproto *p)
 
     if (time_msec() >= p->next_expiration) {
         COVERAGE_INC(ofproto_expiration);
+        ofproto_expire(p);
         p->next_expiration = time_msec() + 1000;
-        update_used(p);
-
-        classifier_for_each(&p->cls, CLS_INC_ALL, expire_rule, p);
-
-        /* Let the hook know that we're at a stable point: all outstanding data
-         * in existing flows has been accounted to the account_cb.  Thus, the
-         * hook can now reasonably do operations that depend on having accurate
-         * flow volume accounting (currently, that's just bond rebalancing). */
-        if (p->ofhooks->account_checkpoint_cb) {
-            p->ofhooks->account_checkpoint_cb(p->aux);
-        }
     }
 
     if (p->netflow) {
@@ -1211,7 +1198,7 @@ ofproto_wait(struct ofproto *p)
     dpif_recv_wait(p->dpif);
     dpif_port_poll_wait(p->dpif);
     netdev_monitor_poll_wait(p->netdev_monitor);
-    LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) {
+    LIST_FOR_EACH (ofconn, node, &p->all_conns) {
         ofconn_wait(ofconn);
     }
     if (p->in_band) {
@@ -1234,7 +1221,7 @@ ofproto_wait(struct ofproto *p)
     } else if (p->next_expiration != LLONG_MAX) {
         poll_timer_wait_until(p->next_expiration);
     }
-    HMAP_FOR_EACH (ofservice, struct ofservice, node, &p->services) {
+    HMAP_FOR_EACH (ofservice, node, &p->services) {
         pvconn_wait(ofservice->pvconn);
     }
     for (i = 0; i < p->n_snoops; i++) {
@@ -1343,13 +1330,12 @@ reinit_ports(struct ofproto *p)
 {
     struct svec devnames;
     struct ofport *ofport;
-    unsigned int port_no;
     struct odp_port *odp_ports;
     size_t n_odp_ports;
     size_t i;
 
     svec_init(&devnames);
-    PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) {
+    HMAP_FOR_EACH (ofport, hmap_node, &p->ports) {
         svec_add (&devnames, (char *) ofport->opp.name);
     }
     dpif_port_list(p->dpif, &odp_ports, &n_odp_ports);
@@ -1371,15 +1357,14 @@ refresh_port_group(struct ofproto *p, unsigned int group)
     uint16_t *ports;
     size_t n_ports;
     struct ofport *port;
-    unsigned int port_no;
 
     assert(group == DP_GROUP_ALL || group == DP_GROUP_FLOOD);
 
-    ports = xmalloc(port_array_count(&p->ports) * sizeof *ports);
+    ports = xmalloc(hmap_count(&p->ports) * sizeof *ports);
     n_ports = 0;
-    PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) {
+    HMAP_FOR_EACH (port, hmap_node, &p->ports) {
         if (group == DP_GROUP_ALL || !(port->opp.config & OFPPC_NO_FLOOD)) {
-            ports[n_ports++] = port_no;
+            ports[n_ports++] = port->odp_port;
         }
     }
     dpif_port_group_set(p->dpif, group, ports, n_ports);
@@ -1423,6 +1408,7 @@ make_ofport(const struct odp_port *odp_port)
 
     ofport = xmalloc(sizeof *ofport);
     ofport->netdev = netdev;
+    ofport->odp_port = odp_port->port;
     ofport->opp.port_no = odp_port_to_ofp_port(odp_port->port);
     netdev_get_etheraddr(netdev, ofport->opp.hw_addr);
     memcpy(ofport->opp.name, odp_port->devname,
@@ -1444,7 +1430,7 @@ make_ofport(const struct odp_port *odp_port)
 static bool
 ofport_conflicts(const struct ofproto *p, const struct odp_port *odp_port)
 {
-    if (port_array_get(&p->ports, odp_port->port)) {
+    if (get_port(p, odp_port->port)) {
         VLOG_WARN_RL(&rl, "ignoring duplicate port %"PRIu16" in datapath",
                      odp_port->port);
         return true;
@@ -1481,7 +1467,7 @@ send_port_status(struct ofproto *p, const struct ofport *ofport,
 {
     /* XXX Should limit the number of queued port status change messages. */
     struct ofconn *ofconn;
-    LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) {
+    LIST_FOR_EACH (ofconn, node, &p->all_conns) {
         struct ofp_port_status *ops;
         struct ofpbuf *b;
 
@@ -1503,28 +1489,25 @@ send_port_status(struct ofproto *p, const struct ofport *ofport,
 static void
 ofport_install(struct ofproto *p, struct ofport *ofport)
 {
-    uint16_t odp_port = ofp_port_to_odp_port(ofport->opp.port_no);
     const char *netdev_name = (const char *) ofport->opp.name;
 
     netdev_monitor_add(p->netdev_monitor, ofport->netdev);
-    port_array_set(&p->ports, odp_port, ofport);
+    hmap_insert(&p->ports, &ofport->hmap_node, hash_int(ofport->odp_port, 0));
     shash_add(&p->port_by_name, netdev_name, ofport);
     if (p->sflow) {
-        ofproto_sflow_add_port(p->sflow, odp_port, netdev_name);
+        ofproto_sflow_add_port(p->sflow, ofport->odp_port, netdev_name);
     }
 }
 
 static void
 ofport_remove(struct ofproto *p, struct ofport *ofport)
 {
-    uint16_t odp_port = ofp_port_to_odp_port(ofport->opp.port_no);
-
     netdev_monitor_remove(p->netdev_monitor, ofport->netdev);
-    port_array_delete(&p->ports, odp_port);
+    hmap_remove(&p->ports, &ofport->hmap_node);
     shash_delete(&p->port_by_name,
                  shash_find(&p->port_by_name, (char *) ofport->opp.name));
     if (p->sflow) {
-        ofproto_sflow_del_port(p->sflow, odp_port);
+        ofproto_sflow_del_port(p->sflow, ofport->odp_port);
     }
 }
 
@@ -1537,6 +1520,20 @@ ofport_free(struct ofport *ofport)
     }
 }
 
+static struct ofport *
+get_port(const struct ofproto *ofproto, uint16_t odp_port)
+{
+    struct ofport *port;
+
+    HMAP_FOR_EACH_IN_BUCKET (port, hmap_node,
+                             hash_int(odp_port, 0), &ofproto->ports) {
+        if (port->odp_port == odp_port) {
+            return port;
+        }
+    }
+    return NULL;
+}
+
 static void
 update_port(struct ofproto *p, const char *devname)
 {
@@ -1564,7 +1561,7 @@ update_port(struct ofproto *p, const char *devname)
              * reliably but more portably by comparing the old port's MAC
              * against the new port's MAC.  However, this code isn't that smart
              * and always sends an OFPPR_MODIFY (XXX). */
-            old_ofport = port_array_get(&p->ports, odp_port.port);
+            old_ofport = get_port(p, odp_port.port);
         }
     } else if (error != ENOENT && error != ENODEV) {
         VLOG_WARN_RL(&rl, "dpif_port_query_by_name returned unexpected error "
@@ -1834,8 +1831,8 @@ ofservice_lookup(struct ofproto *ofproto, const char *target)
 {
     struct ofservice *ofservice;
 
-    HMAP_FOR_EACH_WITH_HASH (ofservice, struct ofservice, node,
-                             hash_string(target, 0), &ofproto->services) {
+    HMAP_FOR_EACH_WITH_HASH (ofservice, node, hash_string(target, 0),
+                             &ofproto->services) {
         if (!strcmp(pvconn_get_name(ofservice->pvconn), target)) {
             return ofservice;
         }
@@ -1863,8 +1860,10 @@ rule_create(struct ofproto *ofproto, struct rule *super,
     } else {
         list_init(&rule->list);
     }
-    rule->n_actions = n_actions;
-    rule->actions = xmemdup(actions, n_actions * sizeof *actions);
+    if (n_actions > 0) {
+        rule->n_actions = n_actions;
+        rule->actions = xmemdup(actions, n_actions * sizeof *actions);
+    }
     netflow_flow_clear(&rule->nf_flow);
     netflow_flow_update_time(ofproto->netflow, &rule->nf_flow, rule->created);
 
@@ -1898,7 +1897,7 @@ rule_destroy(struct ofproto *ofproto, struct rule *rule)
 {
     if (!rule->super) {
         struct rule *subrule, *next;
-        LIST_FOR_EACH_SAFE (subrule, next, struct rule, list, &rule->list) {
+        LIST_FOR_EACH_SAFE (subrule, next, list, &rule->list) {
             revalidate_rule(ofproto, subrule);
         }
     } else {
@@ -2339,7 +2338,6 @@ handle_features_request(struct ofproto *p, struct ofconn *ofconn,
 {
     struct ofp_switch_features *osf;
     struct ofpbuf *buf;
-    unsigned int port_no;
     struct ofport *port;
 
     osf = make_openflow_xid(sizeof *osf, OFPT_FEATURES_REPLY, oh->xid, &buf);
@@ -2361,7 +2359,7 @@ handle_features_request(struct ofproto *p, struct ofconn *ofconn,
                          (1u << OFPAT_SET_TP_DST) |
                          (1u << OFPAT_ENQUEUE));
 
-    PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) {
+    HMAP_FOR_EACH (port, hmap_node, &p->ports) {
         hton_ofp_phy_port(ofpbuf_put(buf, &port->opp, sizeof port->opp));
     }
 
@@ -2469,7 +2467,7 @@ static void do_xlate_actions(const union ofp_action *in, size_t n_in,
 static void
 add_output_action(struct action_xlate_ctx *ctx, uint16_t port)
 {
-    const struct ofport *ofport = port_array_get(&ctx->ofproto->ports, port);
+    const struct ofport *ofport = get_port(ctx->ofproto, port);
 
     if (ofport) {
         if (ofport->opp.config & OFPPC_NO_FWD) {
@@ -2654,12 +2652,33 @@ xlate_enqueue_action(struct action_xlate_ctx *ctx,
     }
 }
 
+static void
+xlate_set_queue_action(struct action_xlate_ctx *ctx,
+                       const struct nx_action_set_queue *nasq)
+{
+    uint32_t priority;
+    int error;
+
+    error = dpif_queue_to_priority(ctx->ofproto->dpif, ntohl(nasq->queue_id),
+                                   &priority);
+    if (error) {
+        /* Couldn't translate queue to a priority, so ignore.  A warning
+         * has already been logged. */
+        return;
+    }
+
+    remove_pop_action(ctx);
+    odp_actions_add(ctx->out, ODPAT_SET_PRIORITY)->priority.priority
+        = priority;
+}
+
 static void
 xlate_nicira_action(struct action_xlate_ctx *ctx,
                     const struct nx_action_header *nah)
 {
     const struct nx_action_resubmit *nar;
     const struct nx_action_set_tunnel *nast;
+    const struct nx_action_set_queue *nasq;
     union odp_action *oa;
     int subtype = ntohs(nah->subtype);
 
@@ -2682,6 +2701,15 @@ xlate_nicira_action(struct action_xlate_ctx *ctx,
         }
         break;
 
+    case NXAST_SET_QUEUE:
+        nasq = (const struct nx_action_set_queue *) nah;
+        xlate_set_queue_action(ctx, nasq);
+        break;
+
+    case NXAST_POP_QUEUE:
+        odp_actions_add(ctx->out, ODPAT_POP_PRIORITY);
+        break;
+
     /* If you add a new action here that modifies flow data, don't forget to
      * update the flow key in ctx->flow at the same time. */
 
@@ -2699,7 +2727,7 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
     const union ofp_action *ia;
     const struct ofport *port;
 
-    port = port_array_get(&ctx->ofproto->ports, ctx->flow.in_port);
+    port = get_port(ctx->ofproto, ctx->flow.in_port);
     if (port && port->opp.config & (OFPPC_NO_RECV | OFPPC_NO_RECV_STP) &&
         port->opp.config & (eth_addr_equals(ctx->flow.dl_dst, eth_addr_stp)
                             ? OFPPC_NO_RECV_STP : OFPPC_NO_RECV)) {
@@ -2948,8 +2976,7 @@ handle_port_mod(struct ofproto *p, struct ofconn *ofconn,
     }
     opm = (struct ofp_port_mod *) oh;
 
-    port = port_array_get(&p->ports,
-                          ofp_port_to_odp_port(ntohs(opm->port_no)));
+    port = get_port(p, ofp_port_to_odp_port(ntohs(opm->port_no)));
     if (!port) {
         return ofp_mkerr(OFPET_PORT_MOD_FAILED, OFPPMFC_BAD_PORT);
     } else if (memcmp(port->opp.hw_addr, opm->hw_addr, OFP_ETH_ALEN)) {
@@ -3074,7 +3101,7 @@ handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn,
 }
 
 static void
-append_port_stat(struct ofport *port, uint16_t port_no, struct ofconn *ofconn,
+append_port_stat(struct ofport *port, struct ofconn *ofconn,
                  struct ofpbuf **msgp)
 {
     struct netdev_stats stats;
@@ -3086,7 +3113,7 @@ append_port_stat(struct ofport *port, uint16_t port_no, struct ofconn *ofconn,
     netdev_get_stats(port->netdev, &stats);
 
     ops = append_stats_reply(sizeof *ops, ofconn, msgp);
-    ops->port_no = htons(odp_port_to_ofp_port(port_no));
+    ops->port_no = htons(port->opp.port_no);
     memset(ops->pad, 0, sizeof ops->pad);
     ops->rx_packets = htonll(stats.rx_packets);
     ops->tx_packets = htonll(stats.tx_packets);
@@ -3111,7 +3138,6 @@ handle_port_stats_request(struct ofproto *p, struct ofconn *ofconn,
     struct ofp_port_stats *ops;
     struct ofpbuf *msg;
     struct ofport *port;
-    unsigned int port_no;
 
     if (arg_size != sizeof *psr) {
         return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
@@ -3120,14 +3146,13 @@ handle_port_stats_request(struct ofproto *p, struct ofconn *ofconn,
 
     msg = start_stats_reply(osr, sizeof *ops * 16);
     if (psr->port_no != htons(OFPP_NONE)) {
-        port = port_array_get(&p->ports,
-                ofp_port_to_odp_port(ntohs(psr->port_no)));
+        port = get_port(p, ofp_port_to_odp_port(ntohs(psr->port_no)));
         if (port) {
-            append_port_stat(port, ntohs(psr->port_no), ofconn, &msg);
+            append_port_stat(port, ofconn, &msg);
         }
     } else {
-        PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) {
-            append_port_stat(port, port_no, ofconn, &msg);
+        HMAP_FOR_EACH (port, hmap_node, &p->ports) {
+            append_port_stat(port, ofconn, &msg);
         }
     }
 
@@ -3171,7 +3196,7 @@ query_stats(struct ofproto *p, struct rule *rule,
     odp_flows = xzalloc(n_odp_flows * sizeof *odp_flows);
     if (rule->cr.wc.wildcards) {
         size_t i = 0;
-        LIST_FOR_EACH (subrule, struct rule, list, &rule->list) {
+        LIST_FOR_EACH (subrule, list, &rule->list) {
             odp_flows[i++].key = subrule->cr.flow;
             packet_count += subrule->packet_count;
             byte_count += subrule->byte_count;
@@ -3232,7 +3257,9 @@ flow_stats_cb(struct cls_rule *rule_, void *cbdata_)
     memset(ofs->pad2, 0, sizeof ofs->pad2);
     ofs->packet_count = htonll(packet_count);
     ofs->byte_count = htonll(byte_count);
-    memcpy(ofs->actions, rule->actions, act_len);
+    if (rule->n_actions > 0) {
+        memcpy(ofs->actions, rule->actions, act_len);
+    }
 }
 
 static int
@@ -3301,7 +3328,9 @@ flow_stats_ds_cb(struct cls_rule *rule_, void *cbdata_)
     ds_put_format(results, "n_packets=%"PRIu64", ", packet_count);
     ds_put_format(results, "n_bytes=%"PRIu64", ", byte_count);
     ofp_print_match(results, &match, true);
-    ofp_print_actions(results, &rule->actions->header, act_len);
+    if (act_len > 0) {
+        ofp_print_actions(results, &rule->actions->header, act_len);
+    }
     ds_put_cstr(results, "\n");
 }
 
@@ -3389,8 +3418,8 @@ handle_aggregate_stats_request(struct ofproto *p, struct ofconn *ofconn,
 
 struct queue_stats_cbdata {
     struct ofconn *ofconn;
+    struct ofport *ofport;
     struct ofpbuf *msg;
-    uint16_t port_no;
 };
 
 static void
@@ -3400,7 +3429,7 @@ put_queue_stats(struct queue_stats_cbdata *cbdata, uint32_t queue_id,
     struct ofp_queue_stats *reply;
 
     reply = append_stats_reply(sizeof *reply, cbdata->ofconn, &cbdata->msg);
-    reply->port_no = htons(cbdata->port_no);
+    reply->port_no = htons(cbdata->ofport->opp.port_no);
     memset(reply->pad, 0, sizeof reply->pad);
     reply->queue_id = htonl(queue_id);
     reply->tx_bytes = htonll(stats->tx_bytes);
@@ -3419,19 +3448,19 @@ handle_queue_stats_dump_cb(uint32_t queue_id,
 }
 
 static void
-handle_queue_stats_for_port(struct ofport *port, uint16_t port_no,
-                            uint32_t queue_id,
+handle_queue_stats_for_port(struct ofport *port, uint32_t queue_id,
                             struct queue_stats_cbdata *cbdata)
 {
-    cbdata->port_no = port_no;
+    cbdata->ofport = port;
     if (queue_id == OFPQ_ALL) {
         netdev_dump_queue_stats(port->netdev,
                                 handle_queue_stats_dump_cb, cbdata);
     } else {
         struct netdev_queue_stats stats;
 
-        netdev_get_queue_stats(port->netdev, queue_id, &stats);
-        put_queue_stats(cbdata, queue_id, &stats);
+        if (!netdev_get_queue_stats(port->netdev, queue_id, &stats)) {
+            put_queue_stats(cbdata, queue_id, &stats);
+        }
     }
 }
 
@@ -3459,13 +3488,13 @@ handle_queue_stats_request(struct ofproto *ofproto, struct ofconn *ofconn,
     port_no = ntohs(qsr->port_no);
     queue_id = ntohl(qsr->queue_id);
     if (port_no == OFPP_ALL) {
-        PORT_ARRAY_FOR_EACH (port, &ofproto->ports, port_no) {
-            handle_queue_stats_for_port(port, port_no, queue_id, &cbdata);
+        HMAP_FOR_EACH (port, hmap_node, &ofproto->ports) {
+            handle_queue_stats_for_port(port, queue_id, &cbdata);
         }
     } else if (port_no < ofproto->max_ports) {
-        port = port_array_get(&ofproto->ports, port_no);
+        port = get_port(ofproto, ofp_port_to_odp_port(port_no));
         if (port) {
-            handle_queue_stats_for_port(port, port_no, queue_id, &cbdata);
+            handle_queue_stats_for_port(port, queue_id, &cbdata);
         }
     } else {
         ofpbuf_delete(cbdata.msg);
@@ -3729,14 +3758,14 @@ modify_flow(struct ofproto *p, const struct ofp_flow_mod *ofm,
 
     /* If the actions are the same, do nothing. */
     if (n_actions == rule->n_actions
-        && !memcmp(ofm->actions, rule->actions, actions_len))
+        && (!n_actions || !memcmp(ofm->actions, rule->actions, actions_len)))
     {
         return 0;
     }
 
     /* Replace actions. */
     free(rule->actions);
-    rule->actions = xmemdup(ofm->actions, actions_len);
+    rule->actions = n_actions ? xmemdup(ofm->actions, actions_len) : NULL;
     rule->n_actions = n_actions;
 
     /* Make sure that the datapath gets updated properly. */
@@ -3946,8 +3975,7 @@ handle_role_request(struct ofproto *ofproto,
     if (role == NX_ROLE_MASTER) {
         struct ofconn *other;
 
-        HMAP_FOR_EACH (other, struct ofconn, hmap_node,
-                       &ofproto->controllers) {
+        HMAP_FOR_EACH (other, hmap_node, &ofproto->controllers) {
             if (other->role == NX_ROLE_MASTER) {
                 other->role = NX_ROLE_SLAVE;
             }
@@ -4110,7 +4138,7 @@ handle_odp_miss_msg(struct ofproto *p, struct ofpbuf *packet)
     rule = lookup_valid_rule(p, &flow);
     if (!rule) {
         /* Don't send a packet-in if OFPPC_NO_PACKET_IN asserted. */
-        struct ofport *port = port_array_get(&p->ports, msg->port);
+        struct ofport *port = get_port(p, msg->port);
         if (port) {
             if (port->opp.config & OFPPC_NO_PACKET_IN) {
                 COVERAGE_INC(ofproto_no_packet_in);
@@ -4190,6 +4218,180 @@ handle_odp_msg(struct ofproto *p, struct ofpbuf *packet)
     }
 }
 \f
+/* Flow expiration. */
+
+struct expire_cbdata {
+    struct ofproto *ofproto;
+};
+
+static void ofproto_update_used(struct ofproto *);
+static void rule_expire(struct cls_rule *, void *cbdata);
+
+/* This function is called periodically by ofproto_run().  Its job is to
+ * collect updates for the flows that have been installed into the datapath,
+ * most importantly when they last were used, and then use that information to
+ * expire flows that have not been used recently. */
+static void
+ofproto_expire(struct ofproto *ofproto)
+{
+    struct expire_cbdata cbdata;
+
+    /* Update 'used' for each flow in the datapath. */
+    ofproto_update_used(ofproto);
+
+    /* Expire idle flows. */
+    cbdata.ofproto = ofproto;
+    classifier_for_each(&ofproto->cls, CLS_INC_ALL, rule_expire, &cbdata);
+
+    /* Let the hook know that we're at a stable point: all outstanding data
+     * in existing flows has been accounted to the account_cb.  Thus, the
+     * hook can now reasonably do operations that depend on having accurate
+     * flow volume accounting (currently, that's just bond rebalancing). */
+    if (ofproto->ofhooks->account_checkpoint_cb) {
+        ofproto->ofhooks->account_checkpoint_cb(ofproto->aux);
+    }
+}
+
+/* Update 'used' member of each flow currently installed into the datapath. */
+static void
+ofproto_update_used(struct ofproto *p)
+{
+    struct odp_flow *flows;
+    size_t n_flows;
+    size_t i;
+    int error;
+
+    error = dpif_flow_list_all(p->dpif, &flows, &n_flows);
+    if (error) {
+        return;
+    }
+
+    for (i = 0; i < n_flows; i++) {
+        struct odp_flow *f = &flows[i];
+        struct rule *rule;
+
+        rule = rule_from_cls_rule(
+            classifier_find_rule_exactly(&p->cls, &f->key, 0, UINT16_MAX));
+
+        if (rule && rule->installed) {
+            update_time(p, rule, &f->stats);
+            rule_account(p, rule, f->stats.n_bytes);
+        } else {
+            /* There's a flow in the datapath that we know nothing about.
+             * Delete it. */
+            COVERAGE_INC(ofproto_unexpected_rule);
+            dpif_flow_del(p->dpif, f);
+        }
+
+    }
+    free(flows);
+}
+
+static void
+rule_active_timeout(struct ofproto *ofproto, struct rule *rule)
+{
+    if (ofproto->netflow && !is_controller_rule(rule) &&
+        netflow_active_timeout_expired(ofproto->netflow, &rule->nf_flow)) {
+        struct ofexpired expired;
+        struct odp_flow odp_flow;
+
+        /* Get updated flow stats.
+         *
+         * XXX We could avoid this call entirely if (1) ofproto_update_used()
+         * updated TCP flags and (2) the dpif_flow_list_all() in
+         * ofproto_update_used() zeroed TCP flags. */
+        memset(&odp_flow, 0, sizeof odp_flow);
+        if (rule->installed) {
+            odp_flow.key = rule->cr.flow;
+            odp_flow.flags = ODPFF_ZERO_TCP_FLAGS;
+            dpif_flow_get(ofproto->dpif, &odp_flow);
+
+            if (odp_flow.stats.n_packets) {
+                update_time(ofproto, rule, &odp_flow.stats);
+                netflow_flow_update_flags(&rule->nf_flow,
+                                          odp_flow.stats.tcp_flags);
+            }
+        }
+
+        expired.flow = rule->cr.flow;
+        expired.packet_count = rule->packet_count +
+                               odp_flow.stats.n_packets;
+        expired.byte_count = rule->byte_count + odp_flow.stats.n_bytes;
+        expired.used = rule->used;
+
+        netflow_expire(ofproto->netflow, &rule->nf_flow, &expired);
+
+        /* Schedule us to send the accumulated records once we have
+         * collected all of them. */
+        poll_immediate_wake();
+    }
+}
+
+/* If 'cls_rule' is an OpenFlow rule, that has expired according to OpenFlow
+ * rules, then delete it entirely.
+ *
+ * If 'cls_rule' is a subrule, that has not been used recently, remove it from
+ * the datapath and fold its statistics back into its super-rule.
+ *
+ * (This is a callback function for classifier_for_each().) */
+static void
+rule_expire(struct cls_rule *cls_rule, void *cbdata_)
+{
+    struct expire_cbdata *cbdata = cbdata_;
+    struct ofproto *ofproto = cbdata->ofproto;
+    struct rule *rule = rule_from_cls_rule(cls_rule);
+    long long int hard_expire, idle_expire, expire, now;
+
+    /* Calculate OpenFlow expiration times for 'rule'. */
+    hard_expire = (rule->hard_timeout
+                   ? rule->created + rule->hard_timeout * 1000
+                   : LLONG_MAX);
+    idle_expire = (rule->idle_timeout
+                   && (rule->super || list_is_empty(&rule->list))
+                   ? rule->used + rule->idle_timeout * 1000
+                   : LLONG_MAX);
+    expire = MIN(hard_expire, idle_expire);
+
+    now = time_msec();
+    if (now < expire) {
+        /* 'rule' has not expired according to OpenFlow rules. */
+        if (rule->installed && now >= rule->used + 5000) {
+            /* This rule is idle, so uninstall it from the datapath. */
+            if (rule->super) {
+                rule_remove(ofproto, rule);
+            } else {
+                rule_uninstall(ofproto, rule);
+            }
+        } else if (!rule->cr.wc.wildcards) {
+            /* Send NetFlow active timeout if appropriate. */
+            rule_active_timeout(cbdata->ofproto, rule);
+        }
+    } else {
+        /* 'rule' has expired according to OpenFlow rules. */
+        COVERAGE_INC(ofproto_expired);
+
+        /* Update stats.  (This is a no-op if the rule expired due to an idle
+         * timeout, because that only happens when the rule has no subrules
+         * left.) */
+        if (rule->cr.wc.wildcards) {
+            struct rule *subrule, *next;
+            LIST_FOR_EACH_SAFE (subrule, next, list, &rule->list) {
+                rule_remove(cbdata->ofproto, subrule);
+            }
+        } else {
+            rule_uninstall(cbdata->ofproto, rule);
+        }
+
+        /* Get rid of the rule. */
+        if (!rule_is_hidden(rule)) {
+            send_flow_removed(cbdata->ofproto, rule, now,
+                              (now >= hard_expire
+                               ? OFPRR_HARD_TIMEOUT : OFPRR_IDLE_TIMEOUT));
+        }
+        rule_remove(cbdata->ofproto, rule);
+    }
+}
+\f
 static void
 revalidate_cb(struct cls_rule *sub_, void *cbdata_)
 {
@@ -4256,19 +4458,6 @@ compose_flow_removed(struct ofproto *p, const struct rule *rule,
     return buf;
 }
 
-static void
-uninstall_idle_flow(struct ofproto *ofproto, struct rule *rule)
-{
-    assert(rule->installed);
-    assert(!rule->cr.wc.wildcards);
-
-    if (rule->super) {
-        rule_remove(ofproto, rule);
-    } else {
-        rule_uninstall(ofproto, rule);
-    }
-}
-
 static void
 send_flow_removed(struct ofproto *p, struct rule *rule,
                   long long int now, uint8_t reason)
@@ -4284,7 +4473,7 @@ send_flow_removed(struct ofproto *p, struct rule *rule,
      * requests that would not add new flows, so it is imperfect.) */
 
     prev = NULL;
-    LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) {
+    LIST_FOR_EACH (ofconn, node, &p->all_conns) {
         if (rule->send_flow_removed && rconn_is_connected(ofconn->rconn)
             && ofconn_receives_async_msgs(ofconn)) {
             if (prev) {
@@ -4300,122 +4489,6 @@ send_flow_removed(struct ofproto *p, struct rule *rule,
     }
 }
 
-
-static void
-expire_rule(struct cls_rule *cls_rule, void *p_)
-{
-    struct ofproto *p = p_;
-    struct rule *rule = rule_from_cls_rule(cls_rule);
-    long long int hard_expire, idle_expire, expire, now;
-
-    hard_expire = (rule->hard_timeout
-                   ? rule->created + rule->hard_timeout * 1000
-                   : LLONG_MAX);
-    idle_expire = (rule->idle_timeout
-                   && (rule->super || list_is_empty(&rule->list))
-                   ? rule->used + rule->idle_timeout * 1000
-                   : LLONG_MAX);
-    expire = MIN(hard_expire, idle_expire);
-
-    now = time_msec();
-    if (now < expire) {
-        if (rule->installed && now >= rule->used + 5000) {
-            uninstall_idle_flow(p, rule);
-        } else if (!rule->cr.wc.wildcards) {
-            active_timeout(p, rule);
-        }
-
-        return;
-    }
-
-    COVERAGE_INC(ofproto_expired);
-
-    /* Update stats.  This code will be a no-op if the rule expired
-     * due to an idle timeout. */
-    if (rule->cr.wc.wildcards) {
-        struct rule *subrule, *next;
-        LIST_FOR_EACH_SAFE (subrule, next, struct rule, list, &rule->list) {
-            rule_remove(p, subrule);
-        }
-    } else {
-        rule_uninstall(p, rule);
-    }
-
-    if (!rule_is_hidden(rule)) {
-        send_flow_removed(p, rule, now,
-                          (now >= hard_expire
-                           ? OFPRR_HARD_TIMEOUT : OFPRR_IDLE_TIMEOUT));
-    }
-    rule_remove(p, rule);
-}
-
-static void
-active_timeout(struct ofproto *ofproto, struct rule *rule)
-{
-    if (ofproto->netflow && !is_controller_rule(rule) &&
-        netflow_active_timeout_expired(ofproto->netflow, &rule->nf_flow)) {
-        struct ofexpired expired;
-        struct odp_flow odp_flow;
-
-        /* Get updated flow stats. */
-        memset(&odp_flow, 0, sizeof odp_flow);
-        if (rule->installed) {
-            odp_flow.key = rule->cr.flow;
-            odp_flow.flags = ODPFF_ZERO_TCP_FLAGS;
-            dpif_flow_get(ofproto->dpif, &odp_flow);
-
-            if (odp_flow.stats.n_packets) {
-                update_time(ofproto, rule, &odp_flow.stats);
-                netflow_flow_update_flags(&rule->nf_flow,
-                                          odp_flow.stats.tcp_flags);
-            }
-        }
-
-        expired.flow = rule->cr.flow;
-        expired.packet_count = rule->packet_count +
-                               odp_flow.stats.n_packets;
-        expired.byte_count = rule->byte_count + odp_flow.stats.n_bytes;
-        expired.used = rule->used;
-
-        netflow_expire(ofproto->netflow, &rule->nf_flow, &expired);
-
-        /* Schedule us to send the accumulated records once we have
-         * collected all of them. */
-        poll_immediate_wake();
-    }
-}
-
-static void
-update_used(struct ofproto *p)
-{
-    struct odp_flow *flows;
-    size_t n_flows;
-    size_t i;
-    int error;
-
-    error = dpif_flow_list_all(p->dpif, &flows, &n_flows);
-    if (error) {
-        return;
-    }
-
-    for (i = 0; i < n_flows; i++) {
-        struct odp_flow *f = &flows[i];
-        struct rule *rule;
-
-        rule = rule_from_cls_rule(
-            classifier_find_rule_exactly(&p->cls, &f->key, 0, UINT16_MAX));
-        if (!rule || !rule->installed) {
-            COVERAGE_INC(ofproto_unexpected_rule);
-            dpif_flow_del(p->dpif, f);
-            continue;
-        }
-
-        update_time(p, rule, &f->stats);
-        rule_account(p, rule, f->stats.n_bytes);
-    }
-    free(flows);
-}
-
 /* pinsched callback for sending 'packet' on 'ofconn'. */
 static void
 do_send_packet_in(struct ofpbuf *packet, void *ofconn_)
@@ -4547,7 +4620,7 @@ send_packet_in(struct ofproto *ofproto, struct ofpbuf *packet)
     max_len = do_convert_to_packet_in(packet);
 
     prev = NULL;
-    LIST_FOR_EACH (ofconn, struct ofconn, node, &ofproto->all_conns) {
+    LIST_FOR_EACH (ofconn, node, &ofproto->all_conns) {
         if (ofconn_receives_async_msgs(ofconn)) {
             if (prev) {
                 schedule_packet_in(prev, packet, max_len, true);
@@ -4567,7 +4640,7 @@ pick_datapath_id(const struct ofproto *ofproto)
 {
     const struct ofport *port;
 
-    port = port_array_get(&ofproto->ports, ODPP_LOCAL);
+    port = get_port(ofproto, ODPP_LOCAL);
     if (port) {
         uint8_t ea[ETH_ADDR_LEN];
         int error;