[ovs-dev] [PATCH v3 12/13] ofproto: Refactor packet_out handling.

Jarno Rajahalme jarno at ovn.org
Mon Sep 12 20:52:42 UTC 2016


Refactor handle_packet_out() to prepare for bundle support for packet
outs in a later patch.

Two new callbacks are introduced in ofproto-provider class:
->packet_xlate() and ->packet_execute().  ->packet_xlate() translates
the packet using the flow and actions provided by the caller, but
defers all OpenFlow-visible side-effects (stats, learn actions, actual
packet output, etc.) to be explicitly executed with the
->packet_execute() call.

Adds a new ofproto_rule_reduce_timeouts__() that must be called with
'ofproto_mutex' held.  This is used in the next patch.

Signed-off-by: Jarno Rajahalme <jarno at ovn.org>
---
v3: Removed layer violations by making ofproto-dpif initialize and
    manage its private 'aux' member in struct ofproto_packet_out.

 ofproto/ofproto-dpif-upcall.c      |  11 +-
 ofproto/ofproto-dpif-xlate-cache.c |  17 ++-
 ofproto/ofproto-dpif-xlate-cache.h |   2 +
 ofproto/ofproto-dpif-xlate.c       |  40 +++---
 ofproto/ofproto-dpif-xlate.h       |   3 +-
 ofproto/ofproto-dpif.c             | 213 +++++++++++++++++++++++++---
 ofproto/ofproto-dpif.h             |  15 +-
 ofproto/ofproto-provider.h         |  91 ++++++------
 ofproto/ofproto.c                  | 275 ++++++++++++++++++++++++++-----------
 9 files changed, 488 insertions(+), 179 deletions(-)

diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index 3080919..6bf659a 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -1078,7 +1078,9 @@ upcall_xlate(struct udpif *udpif, struct upcall *upcall,
     stats.used = time_msec();
     stats.tcp_flags = ntohs(upcall->flow->tcp_flags);
 
-    xlate_in_init(&xin, upcall->ofproto, upcall->flow, upcall->in_port, NULL,
+    xlate_in_init(&xin, upcall->ofproto,
+                  ofproto_dpif_get_tables_version(upcall->ofproto),
+                  upcall->flow, upcall->in_port, NULL,
                   stats.tcp_flags, upcall->packet, wc, odp_actions);
 
     if (upcall->type == DPIF_UC_MISS) {
@@ -1908,7 +1910,8 @@ revalidate_ukey(struct udpif *udpif, struct udpif_key *ukey,
         ukey->xcache = xlate_cache_new();
     }
 
-    xlate_in_init(&xin, ofproto, &flow, ofp_in_port, NULL, push.tcp_flags,
+    xlate_in_init(&xin, ofproto, ofproto_dpif_get_tables_version(ofproto),
+                  &flow, ofp_in_port, NULL, push.tcp_flags,
                   NULL, need_revalidate ? &wc : NULL, odp_actions);
     if (push.n_packets) {
         xin.resubmit_stats = &push;
@@ -2086,7 +2089,9 @@ push_dp_ops(struct udpif *udpif, struct ukey_op *ops, size_t n_ops)
             if (!error) {
                 struct xlate_in xin;
 
-                xlate_in_init(&xin, ofproto, &flow, ofp_in_port, NULL,
+                xlate_in_init(&xin, ofproto,
+                              ofproto_dpif_get_tables_version(ofproto),
+                              &flow, ofp_in_port, NULL,
                               push->tcp_flags, NULL, NULL, NULL);
                 xin.resubmit_stats = push->n_packets ? push : NULL;
                 xin.may_learn = push->n_packets > 0;
diff --git a/ofproto/ofproto-dpif-xlate-cache.c b/ofproto/ofproto-dpif-xlate-cache.c
index 68ea958..18ffc52 100644
--- a/ofproto/ofproto-dpif-xlate-cache.c
+++ b/ofproto/ofproto-dpif-xlate-cache.c
@@ -47,12 +47,17 @@
 
 VLOG_DEFINE_THIS_MODULE(ofproto_xlate_cache);
 
+void
+xlate_cache_init(struct xlate_cache *xcache)
+{
+    ofpbuf_init(&xcache->entries, 512);
+}
+
 struct xlate_cache *
 xlate_cache_new(void)
 {
     struct xlate_cache *xcache = xmalloc(sizeof *xcache);
-
-    ofpbuf_init(&xcache->entries, 512);
+    xlate_cache_init(xcache);
     return xcache;
 }
 
@@ -261,9 +266,15 @@ xlate_cache_clear(struct xlate_cache *xcache)
 }
 
 void
-xlate_cache_delete(struct xlate_cache *xcache)
+xlate_cache_uninit(struct xlate_cache *xcache)
 {
     xlate_cache_clear(xcache);
     ofpbuf_uninit(&xcache->entries);
+}
+
+void
+xlate_cache_delete(struct xlate_cache *xcache)
+{
+    xlate_cache_uninit(xcache);
     free(xcache);
 }
diff --git a/ofproto/ofproto-dpif-xlate-cache.h b/ofproto/ofproto-dpif-xlate-cache.h
index 3705000..875cf10 100644
--- a/ofproto/ofproto-dpif-xlate-cache.h
+++ b/ofproto/ofproto-dpif-xlate-cache.h
@@ -130,12 +130,14 @@ struct xlate_cache {
     struct ofpbuf entries;
 };
 
+void xlate_cache_init(struct xlate_cache *);
 struct xlate_cache *xlate_cache_new(void);
 struct xc_entry *xlate_cache_add_entry(struct xlate_cache *, enum xc_type);
 void xlate_push_stats_entry(struct xc_entry *, const struct dpif_flow_stats *);
 void xlate_push_stats(struct xlate_cache *, const struct dpif_flow_stats *);
 void xlate_cache_clear_entry(struct xc_entry *);
 void xlate_cache_clear(struct xlate_cache *);
+void xlate_cache_uninit(struct xlate_cache *);
 void xlate_cache_delete(struct xlate_cache *);
 
 #endif /* ofproto-dpif-xlate-cache.h */
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 0783d48..067701f 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -170,9 +170,6 @@ struct xlate_ctx {
 
     const struct xbridge *xbridge;
 
-    /* Flow tables version at the beginning of the translation. */
-    ovs_version_t tables_version;
-
     /* Flow at the last commit. */
     struct flow base_flow;
 
@@ -1432,7 +1429,7 @@ group_is_alive(const struct xlate_ctx *ctx, uint32_t group_id, int depth)
     struct group_dpif *group;
 
     group = group_dpif_lookup(ctx->xbridge->ofproto, group_id,
-                              ctx->tables_version, false);
+                              ctx->xin->tables_version, false);
     if (group) {
         return group_first_live_bucket(ctx, group, depth) != NULL;
     }
@@ -2699,8 +2696,9 @@ compose_table_xlate(struct xlate_ctx *ctx, const struct xport *out_dev,
     output.port = OFPP_TABLE;
     output.max_len = 0;
 
-    return ofproto_dpif_execute_actions__(xbridge->ofproto, &flow, NULL,
-                                          &output.ofpact, sizeof output,
+    return ofproto_dpif_execute_actions__(xbridge->ofproto,
+                                          ctx->xin->tables_version, &flow,
+                                          NULL, &output.ofpact, sizeof output,
                                           ctx->indentation, ctx->depth,
                                           ctx->resubmits, packet);
 }
@@ -2888,7 +2886,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
         struct flow old_flow = ctx->xin->flow;
         bool old_conntrack = ctx->conntracked;
         bool old_was_mpls = ctx->was_mpls;
-        ovs_version_t old_version = ctx->tables_version;
+        ovs_version_t old_version = ctx->xin->tables_version;
         struct ofpbuf old_stack = ctx->stack;
         union mf_subvalue new_stack[1024 / sizeof(union mf_subvalue)];
         struct ofpbuf old_action_set = ctx->action_set;
@@ -2906,7 +2904,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
         clear_conntrack(flow);
 
         /* The bridge is now known so obtain its table version. */
-        ctx->tables_version
+        ctx->xin->tables_version
             = ofproto_dpif_get_tables_version(ctx->xbridge->ofproto);
 
         if (!process_special(ctx, peer) && may_receive(peer, ctx)) {
@@ -2943,7 +2941,7 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port,
         ctx->stack = old_stack;
 
         /* Restore calling bridge's lookup version. */
-        ctx->tables_version = old_version;
+        ctx->xin->tables_version = old_version;
 
         /* The peer bridge popping MPLS should have no effect on the original
          * bridge. */
@@ -3184,7 +3182,7 @@ xlate_table_action(struct xlate_ctx *ctx, ofp_port_t in_port, uint8_t table_id,
         ctx->table_id = table_id;
 
         rule = rule_dpif_lookup_from_table(ctx->xbridge->ofproto,
-                                           ctx->tables_version,
+                                           ctx->xin->tables_version,
                                            &ctx->xin->flow, ctx->wc,
                                            ctx->xin->resubmit_stats,
                                            &ctx->table_id, in_port,
@@ -3432,7 +3430,7 @@ xlate_group_action(struct xlate_ctx *ctx, uint32_t group_id)
 
         /* Take ref only if xcache exists. */
         group = group_dpif_lookup(ctx->xbridge->ofproto, group_id,
-                                  ctx->tables_version, ctx->xin->xcache);
+                                  ctx->xin->tables_version, ctx->xin->xcache);
         if (!group) {
             /* XXX: Should set ctx->error ? */
             return true;
@@ -4956,12 +4954,13 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
 
 void
 xlate_in_init(struct xlate_in *xin, struct ofproto_dpif *ofproto,
-              const struct flow *flow, ofp_port_t in_port,
-              struct rule_dpif *rule, uint16_t tcp_flags,
+              ovs_version_t version, const struct flow *flow,
+              ofp_port_t in_port, struct rule_dpif *rule, uint16_t tcp_flags,
               const struct dp_packet *packet, struct flow_wildcards *wc,
               struct ofpbuf *odp_actions)
 {
     xin->ofproto = ofproto;
+    xin->tables_version = version;
     xin->flow = *flow;
     xin->flow.in_port.ofp_port = in_port;
     xin->flow.actset_output = OFPP_UNSET;
@@ -5314,6 +5313,9 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
                 goto exit;
             }
             ctx.xbridge = new_bridge;
+            /* The bridge is now known so obtain its table version. */
+            ctx.xin->tables_version
+                = ofproto_dpif_get_tables_version(ctx.xbridge->ofproto);
         }
 
         /* Set the thawed table id.  Note: A table lookup is done only if there
@@ -5364,12 +5366,10 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         ctx.error = XLATE_NO_RECIRCULATION_CONTEXT;
         goto exit;
     }
-    /* The bridge is now known so obtain its table version. */
-    ctx.tables_version = ofproto_dpif_get_tables_version(ctx.xbridge->ofproto);
 
     if (!xin->ofpacts && !ctx.rule) {
         ctx.rule = rule_dpif_lookup_from_table(
-            ctx.xbridge->ofproto, ctx.tables_version, flow, ctx.wc,
+            ctx.xbridge->ofproto, ctx.xin->tables_version, flow, ctx.wc,
             ctx.xin->resubmit_stats, &ctx.table_id,
             flow->in_port.ofp_port, true, true, ctx.xin->xcache);
         if (ctx.xin->resubmit_stats) {
@@ -5552,7 +5552,8 @@ xlate_resume(struct ofproto_dpif *ofproto,
     flow_extract(&packet, &flow);
 
     struct xlate_in xin;
-    xlate_in_init(&xin, ofproto, &flow, 0, NULL, ntohs(flow.tcp_flags),
+    xlate_in_init(&xin, ofproto, ofproto_dpif_get_tables_version(ofproto),
+                  &flow, 0, NULL, ntohs(flow.tcp_flags),
                   &packet, NULL, odp_actions);
 
     struct ofpact_note noop;
@@ -5630,7 +5631,10 @@ xlate_send_packet(const struct ofport_dpif *ofport, bool oam,
 
     ofpact_put_OUTPUT(&ofpacts)->port = xport->ofp_port;
 
-    return ofproto_dpif_execute_actions(xport->xbridge->ofproto, &flow, NULL,
+    /* Actions here are not referring to anything versionable (flow tables or
+     * groups) so we don't need to worry about the version here. */
+    return ofproto_dpif_execute_actions(xport->xbridge->ofproto,
+                                        OVS_VERSION_MAX, &flow, NULL,
                                         ofpacts.data, ofpacts.size, packet);
 }
 
diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h
index 8f08733..a5f25bf 100644
--- a/ofproto/ofproto-dpif-xlate.h
+++ b/ofproto/ofproto-dpif-xlate.h
@@ -46,6 +46,7 @@ struct xlate_out {
 
 struct xlate_in {
     struct ofproto_dpif *ofproto;
+    ovs_version_t        tables_version;   /* Lookup in this version. */
 
     /* Flow to which the OpenFlow actions apply.  xlate_actions() will modify
      * this flow when actions change header fields. */
@@ -201,7 +202,7 @@ const char *xlate_strerror(enum xlate_error error);
 
 enum xlate_error xlate_actions(struct xlate_in *, struct xlate_out *);
 
-void xlate_in_init(struct xlate_in *, struct ofproto_dpif *,
+void xlate_in_init(struct xlate_in *, struct ofproto_dpif *, ovs_version_t,
                    const struct flow *, ofp_port_t in_port, struct rule_dpif *,
                    uint16_t tcp_flags, const struct dp_packet *packet,
                    struct flow_wildcards *, struct ofpbuf *odp_actions);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 0df9c0f..4414f80 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -3748,7 +3748,7 @@ ofproto_dpif_set_packet_odp_port(const struct ofproto_dpif *ofproto,
 
 int
 ofproto_dpif_execute_actions__(struct ofproto_dpif *ofproto,
-                               const struct flow *flow,
+                               ovs_version_t version, const struct flow *flow,
                                struct rule_dpif *rule,
                                const struct ofpact *ofpacts, size_t ofpacts_len,
                                int indentation, int depth, int resubmits,
@@ -3770,7 +3770,7 @@ ofproto_dpif_execute_actions__(struct ofproto_dpif *ofproto,
 
     uint64_t odp_actions_stub[1024 / 8];
     struct ofpbuf odp_actions = OFPBUF_STUB_INITIALIZER(odp_actions_stub);
-    xlate_in_init(&xin, ofproto, flow, flow->in_port.ofp_port, rule,
+    xlate_in_init(&xin, ofproto, version, flow, flow->in_port.ofp_port, rule,
                   stats.tcp_flags, packet, NULL, &odp_actions);
     xin.ofpacts = ofpacts;
     xin.ofpacts_len = ofpacts_len;
@@ -3808,13 +3808,14 @@ out:
  * 'flow' must reflect the data in 'packet'. */
 int
 ofproto_dpif_execute_actions(struct ofproto_dpif *ofproto,
-                             const struct flow *flow,
+                             ovs_version_t version, const struct flow *flow,
                              struct rule_dpif *rule,
                              const struct ofpact *ofpacts, size_t ofpacts_len,
                              struct dp_packet *packet)
 {
-    return ofproto_dpif_execute_actions__(ofproto, flow, rule, ofpacts,
-                                          ofpacts_len, 0, 0, 0, packet);
+    return ofproto_dpif_execute_actions__(ofproto, version, flow, rule,
+                                          ofpacts, ofpacts_len, 0, 0, 0,
+                                          packet);
 }
 
 static void
@@ -3893,7 +3894,7 @@ rule_set_recirc_id(struct rule *rule_, uint32_t id)
 }
 
 ovs_version_t
-ofproto_dpif_get_tables_version(struct ofproto_dpif *ofproto OVS_UNUSED)
+ofproto_dpif_get_tables_version(struct ofproto_dpif *ofproto)
 {
     ovs_version_t version;
 
@@ -4271,6 +4272,184 @@ rule_get_stats(struct rule *rule_, uint64_t *packets, uint64_t *bytes,
     ovs_mutex_unlock(&rule->stats_mutex);
 }
 
+struct ofproto_dpif_packet_out {
+    struct xlate_cache xcache;
+    struct ofpbuf odp_actions;
+    struct recirc_refs rr;
+    bool needs_help;
+};
+
+
+static struct ofproto_dpif_packet_out *
+ofproto_dpif_packet_out_new(void)
+{
+    struct ofproto_dpif_packet_out *aux = xmalloc(sizeof *aux);
+    xlate_cache_init(&aux->xcache);
+    ofpbuf_init(&aux->odp_actions, 64);
+    aux->rr = RECIRC_REFS_EMPTY_INITIALIZER;
+    aux->needs_help = false;
+
+    return aux;
+}
+
+static void
+ofproto_dpif_packet_out_delete(struct ofproto_dpif_packet_out *aux)
+{
+    if (aux) {
+        xlate_cache_uninit(&aux->xcache);
+        ofpbuf_uninit(&aux->odp_actions);
+        recirc_refs_unref(&aux->rr);
+        free(aux);
+    }
+}
+
+static enum ofperr
+packet_xlate(struct ofproto *ofproto_, struct ofproto_packet_out *opo)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+    struct xlate_out xout;
+    struct xlate_in xin;
+    enum ofperr error = 0;
+
+    struct ofproto_dpif_packet_out *aux = ofproto_dpif_packet_out_new();
+
+    xlate_in_init(&xin, ofproto, opo->version, opo->flow,
+                  opo->flow->in_port.ofp_port, NULL, 0, opo->packet, NULL,
+                  &aux->odp_actions);
+    xin.ofpacts = opo->ofpacts;
+    xin.ofpacts_len = opo->ofpacts_len;
+    /* No learning or stats, but collect side effects to xcache. */
+    xin.may_learn = false;
+    xin.resubmit_stats = NULL;
+    xin.xcache = &aux->xcache;
+
+    if (xlate_actions(&xin, &xout) != XLATE_OK) {
+        error = OFPERR_OFPFMFC_UNKNOWN;   /* Error processing actions. */
+        goto error_out;
+    } else {
+        /* Prepare learn actions. */
+        struct xc_entry *entry;
+        struct ofpbuf entries = aux->xcache.entries;
+
+        XC_ENTRY_FOR_EACH (entry, &entries) {
+            if (entry->type == XC_LEARN) {
+                struct ofproto_flow_mod *ofm = entry->u.learn.ofm;
+
+                error = ofproto_flow_mod_learn_refresh(ofm);
+                if (error) {
+                    goto error_out;
+                }
+                struct rule *rule = ofm->temp_rule;
+                ofm->learn_adds_rule = (rule->state == RULE_INITIALIZED);
+                if (ofm->learn_adds_rule) {
+                    /* If learning on a different bridge, must use its next
+                     * version number. */
+                    ofm->version = (rule->ofproto == ofproto_)
+                        ? opo->version : rule->ofproto->tables_version + 1;
+                    error = ofproto_flow_mod_learn_start(ofm);
+                    if (error) {
+                        goto error_out;
+                    }
+                }
+            }
+        }
+
+        /* Success. */
+        aux->needs_help = (xout.slow & SLOW_ACTION) != 0;
+        recirc_refs_swap(&aux->rr, &xout.recircs); /* Hold recirc refs. */
+    }
+    xlate_out_uninit(&xout);
+    opo->aux = aux;
+    return 0;
+
+error_out:
+    xlate_out_uninit(&xout);
+    ofproto_dpif_packet_out_delete(aux);
+    opo->aux = NULL;
+    return error;
+}
+
+/* Push stats and perform side effects of flow translation. */
+static void
+ofproto_dpif_xcache_execute(struct ofproto_dpif *ofproto,
+                            struct xlate_cache *xcache,
+                            const struct dpif_flow_stats *stats)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct xc_entry *entry;
+    struct ofpbuf entries = xcache->entries;
+
+    XC_ENTRY_FOR_EACH (entry, &entries) {
+        switch (entry->type) {
+        case XC_LEARN:
+            /* Finish the learned flows. */
+            if (entry->u.learn.ofm->learn_adds_rule) {
+                ofproto_flow_mod_learn_finish(entry->u.learn.ofm,
+                                              &ofproto->up);
+            }
+            break;
+        case XC_FIN_TIMEOUT:
+            if (stats->tcp_flags & (TCP_FIN | TCP_RST)) {
+                /* 'ofproto_mutex' already held */
+                ofproto_rule_reduce_timeouts__(&entry->u.fin.rule->up,
+                                               entry->u.fin.idle,
+                                               entry->u.fin.hard);
+            }
+            break;
+            /* All the rest can be dealt with by the xlate layer. */
+        case XC_TABLE:
+        case XC_RULE:
+        case XC_BOND:
+        case XC_NETDEV:
+        case XC_NETFLOW:
+        case XC_MIRROR:
+        case XC_NORMAL:
+        case XC_GROUP:
+        case XC_TNL_NEIGH:
+        case XC_CONTROLLER:
+            xlate_push_stats_entry(entry, stats);
+            break;
+        default:
+            OVS_NOT_REACHED();
+        }
+    }
+}
+
+static void
+packet_execute(struct ofproto *ofproto_, struct ofproto_packet_out *opo)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+    struct dpif_flow_stats stats;
+    struct dpif_execute execute;
+
+    struct ofproto_dpif_packet_out *aux = opo->aux;
+    ovs_assert(aux);
+
+    /* Run the side effects from the xcache. */
+    dpif_flow_stats_extract(opo->flow, opo->packet, time_msec(), &stats);
+    ofproto_dpif_xcache_execute(ofproto, &aux->xcache, &stats);
+
+    execute.actions = aux->odp_actions.data;
+    execute.actions_len = aux->odp_actions.size;
+
+    pkt_metadata_from_flow(&opo->packet->md, opo->flow);
+    execute.packet = opo->packet;
+    execute.flow = opo->flow;
+    execute.needs_help = aux->needs_help;
+    execute.probe = false;
+    execute.mtu = 0;
+
+    /* Fix up in_port. */
+    ofproto_dpif_set_packet_odp_port(ofproto, opo->flow->in_port.ofp_port,
+                                     opo->packet);
+
+    dpif_execute(ofproto->backer->dpif, &execute);
+    ofproto_dpif_packet_out_delete(aux);
+    opo->aux = NULL;
+}
+
 static struct group_dpif *group_dpif_cast(const struct ofgroup *group)
 {
     return group ? CONTAINER_OF(group, struct group_dpif, up) : NULL;
@@ -4469,18 +4648,6 @@ set_frag_handling(struct ofproto *ofproto_,
 }
 
 static enum ofperr
-packet_out(struct ofproto *ofproto_, struct dp_packet *packet,
-           const struct flow *flow,
-           const struct ofpact *ofpacts, size_t ofpacts_len)
-{
-    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-
-    ofproto_dpif_execute_actions(ofproto, flow, NULL, ofpacts,
-                                 ofpacts_len, packet);
-    return 0;
-}
-
-static enum ofperr
 nxt_resume(struct ofproto *ofproto_,
            const struct ofputil_packet_in_private *pin)
 {
@@ -5161,9 +5328,10 @@ ofproto_trace(struct ofproto_dpif *ofproto, struct flow *flow,
     trace.result = ds;
     trace.key = flow; /* Original flow key, used for megaflow. */
     trace.flow = *flow; /* May be modified by actions. */
-    xlate_in_init(&trace.xin, ofproto, flow, flow->in_port.ofp_port, NULL,
-                  ntohs(flow->tcp_flags), packet, &trace.wc,
-                  &trace.odp_actions);
+    xlate_in_init(&trace.xin, ofproto,
+                  ofproto_dpif_get_tables_version(ofproto), flow,
+                  flow->in_port.ofp_port, NULL, ntohs(flow->tcp_flags),
+                  packet, &trace.wc, &trace.odp_actions);
     trace.xin.ofpacts = ofpacts;
     trace.xin.ofpacts_len = ofpacts_len;
     trace.xin.resubmit_hook = trace_resubmit;
@@ -5656,8 +5824,9 @@ const struct ofproto_class ofproto_dpif_class = {
     rule_destruct,
     rule_dealloc,
     rule_get_stats,
+    packet_xlate,
+    packet_execute,
     set_frag_handling,
-    packet_out,
     nxt_resume,
     set_netflow,
     get_netflow_ids,
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 54c8703..ec8830d 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -152,13 +152,14 @@ const char *group_dpif_get_selection_method(const struct group_dpif *group);
 uint64_t group_dpif_get_selection_method_param(const struct group_dpif *group);
 const struct field_array *group_dpif_get_fields(const struct group_dpif *group);
 
-int ofproto_dpif_execute_actions(struct ofproto_dpif *, const struct flow *,
-                                 struct rule_dpif *, const struct ofpact *,
-                                 size_t ofpacts_len, struct dp_packet *);
-int ofproto_dpif_execute_actions__(struct ofproto_dpif *, const struct flow *,
-                                   struct rule_dpif *, const struct ofpact *,
-                                   size_t ofpacts_len, int indentation,
-                                   int depth, int resubmits,
+int ofproto_dpif_execute_actions(struct ofproto_dpif *, ovs_version_t,
+                                 const struct flow *, struct rule_dpif *,
+                                 const struct ofpact *, size_t ofpacts_len,
+                                 struct dp_packet *);
+int ofproto_dpif_execute_actions__(struct ofproto_dpif *, ovs_version_t,
+                                   const struct flow *, struct rule_dpif *,
+                                   const struct ofpact *, size_t ofpacts_len,
+                                   int indentation, int depth, int resubmits,
                                    struct dp_packet *);
 void ofproto_dpif_send_async_msg(struct ofproto_dpif *,
                                  struct ofproto_async_msg *);
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 74bf89a..dd700d2 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -57,6 +57,7 @@ struct ofputil_flow_mod;
 struct bfd_cfg;
 struct meter;
 struct ofoperation;
+struct ofproto_packet_out;
 
 extern struct ovs_mutex ofproto_mutex;
 
@@ -512,6 +513,9 @@ void ofproto_rule_expire(struct rule *rule, uint8_t reason)
     OVS_REQUIRES(ofproto_mutex);
 void ofproto_rule_delete(struct ofproto *, struct rule *)
     OVS_EXCLUDED(ofproto_mutex);
+void ofproto_rule_reduce_timeouts__(struct rule *rule, uint16_t idle_timeout,
+                                    uint16_t hard_timeout)
+    OVS_REQUIRES(ofproto_mutex) OVS_EXCLUDED(rule->mutex);
 void ofproto_rule_reduce_timeouts(struct rule *rule, uint16_t idle_timeout,
                                   uint16_t hard_timeout)
     OVS_EXCLUDED(ofproto_mutex);
@@ -1290,6 +1294,30 @@ struct ofproto_class {
                            uint64_t *byte_count, long long int *used)
         /* OVS_EXCLUDED(ofproto_mutex) */;
 
+    /* Translates actions in 'opo->ofpacts', for 'opo->packet' in flow tables
+     * in version 'opo->version'.  This is useful for OpenFlow OFPT_PACKET_OUT.
+     *
+     * This function must validate that it can correctly translate
+     * 'opo->ofpacts'.  If not, then it should return an OpenFlow error code.
+     *
+     * 'opo->flow' reflects the flow information for 'opo->packet'.  All of the
+     * information in 'opo->flow' is extracted from 'opo->packet', except for
+     * 'in_port', which is assigned to the correct value for the incoming
+     * packet.  'tunnel' and register values should be zeroed.  'packet''s
+     * header pointers and offsets (e.g. packet->l3) are appropriately
+     * initialized.  packet->l3 is aligned on a 32-bit boundary.
+     *
+     * Returns 0 if successful, otherwise an OpenFlow error code.
+     *
+     * This function may be called with 'ofproto_mutex' held. */
+    enum ofperr (*packet_xlate)(struct ofproto *,
+                                struct ofproto_packet_out *opo);
+
+    /* Executes the datapath actions, translation side-effects, and stats as
+     * produced by ->packet_xlate().  The caller retains ownership of 'opo'.
+     */
+    void (*packet_execute)(struct ofproto *, struct ofproto_packet_out *opo);
+
     /* Changes the OpenFlow IP fragment handling policy to 'frag_handling',
      * which takes one of the following values, with the corresponding
      * meanings:
@@ -1321,49 +1349,6 @@ struct ofproto_class {
     bool (*set_frag_handling)(struct ofproto *ofproto,
                               enum ofputil_frag_handling frag_handling);
 
-    /* Implements the OpenFlow OFPT_PACKET_OUT command.  The datapath should
-     * execute the 'ofpacts_len' bytes of "struct ofpacts" in 'ofpacts'.
-     *
-     * The caller retains ownership of 'packet' and of 'ofpacts', so
-     * ->packet_out() should not modify or free them.
-     *
-     * This function must validate that it can correctly implement 'ofpacts'.
-     * If not, then it should return an OpenFlow error code.
-     *
-     * 'flow' reflects the flow information for 'packet'.  All of the
-     * information in 'flow' is extracted from 'packet', except for
-     * flow->in_port (see below).  flow->tunnel and its register values are
-     * zeroed.
-     *
-     * flow->in_port comes from the OpenFlow OFPT_PACKET_OUT message.  The
-     * implementation should reject invalid flow->in_port values by returning
-     * OFPERR_OFPBRC_BAD_PORT.  (If the implementation called
-     * ofproto_init_max_ports(), then the client will reject these ports
-     * itself.)  For consistency, the implementation should consider valid for
-     * flow->in_port any value that could possibly be seen in a packet that it
-     * passes to connmgr_send_packet_in().  Ideally, even an implementation
-     * that never generates packet-ins (e.g. due to hardware limitations)
-     * should still allow flow->in_port values for every possible physical port
-     * and OFPP_LOCAL.  The only virtual ports (those above OFPP_MAX) that the
-     * caller will ever pass in as flow->in_port, other than OFPP_LOCAL, are
-     * OFPP_NONE and OFPP_CONTROLLER.  The implementation should allow both of
-     * these, treating each of them as packets generated by the controller as
-     * opposed to packets originating from some switch port.
-     *
-     * (Ordinarily the only effect of flow->in_port is on output actions that
-     * involve the input port, such as actions that output to OFPP_IN_PORT,
-     * OFPP_FLOOD, or OFPP_ALL.  flow->in_port can also affect Nicira extension
-     * "resubmit" actions.)
-     *
-     * 'packet' is not matched against the OpenFlow flow table, so its
-     * statistics should not be included in OpenFlow flow statistics.
-     *
-     * Returns 0 if successful, otherwise an OpenFlow error code. */
-    enum ofperr (*packet_out)(struct ofproto *ofproto, struct dp_packet *packet,
-                              const struct flow *flow,
-                              const struct ofpact *ofpacts,
-                              size_t ofpacts_len);
-
     enum ofperr (*nxt_resume)(struct ofproto *ofproto,
                               const struct ofputil_packet_in_private *);
 
@@ -1875,6 +1860,7 @@ struct ofproto_flow_mod {
      * ofproto_flow_mod_uninit() does NOT clean these up. */
     ovs_version_t version;              /* Version in which changes take
                                          * effect. */
+    bool learn_adds_rule;               /* Learn execution adds a rule. */
     struct rule_collection old_rules;   /* Affected rules. */
     struct rule_collection new_rules;   /* Replacement rules. */
 };
@@ -1897,6 +1883,19 @@ struct ofproto_group_mod {
     struct group_collection old_groups; /* Affected groups. */
 };
 
+/* packet_out with execution context. */
+struct ofproto_packet_out {
+    ovs_version_t version;
+    struct dp_packet *packet;
+    struct flow *flow;
+    struct ofpact *ofpacts;
+    size_t ofpacts_len;
+
+    void *aux;   /* Provider private. */
+};
+
+void ofproto_packet_out_uninit(struct ofproto_packet_out *);
+
 enum ofperr ofproto_flow_mod(struct ofproto *, const struct ofputil_flow_mod *)
     OVS_EXCLUDED(ofproto_mutex);
 enum ofperr ofproto_flow_mod_init_for_learn(struct ofproto *,
@@ -1905,6 +1904,12 @@ enum ofperr ofproto_flow_mod_init_for_learn(struct ofproto *,
     OVS_EXCLUDED(ofproto_mutex);
 enum ofperr ofproto_flow_mod_learn(struct ofproto_flow_mod *, bool keep_ref)
     OVS_EXCLUDED(ofproto_mutex);
+enum ofperr ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm);
+enum ofperr ofproto_flow_mod_learn_start(struct ofproto_flow_mod *ofm)
+    OVS_REQUIRES(ofproto_mutex);
+void ofproto_flow_mod_learn_finish(struct ofproto_flow_mod *ofm,
+                                          struct ofproto *orig_ofproto)
+    OVS_REQUIRES(ofproto_mutex);
 void ofproto_add_flow(struct ofproto *, const struct match *, int priority,
                       const struct ofpact *ofpacts, size_t ofpacts_len)
     OVS_EXCLUDED(ofproto_mutex);
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 34fd6ee..ae07e7e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3361,10 +3361,13 @@ reject_slave_controller(struct ofconn *ofconn)
  *
  *    - If they use any groups, then 'ofproto' has that group configured.
  *
- * Returns 0 if successful, otherwise an OpenFlow error. */
+ * Returns 0 if successful, otherwise an OpenFlow error.  Caller must hold
+ * 'ofproto_mutex' for the result to be valid also after this function
+ * returns. */
 enum ofperr
 ofproto_check_ofpacts(struct ofproto *ofproto,
                       const struct ofpact ofpacts[], size_t ofpacts_len)
+    OVS_REQUIRES(ofproto_mutex)
 {
     uint32_t mid;
 
@@ -3383,71 +3386,134 @@ ofproto_check_ofpacts(struct ofproto *ofproto,
     return 0;
 }
 
+void
+ofproto_packet_out_uninit(struct ofproto_packet_out *opo)
+{
+    dp_packet_delete(opo->packet);
+    opo->packet = NULL;
+    free(opo->flow);
+    opo->flow = NULL;
+    free(opo->ofpacts);
+    opo->ofpacts = NULL;
+    opo->ofpacts_len = 0;
+    ovs_assert(!opo->aux);
+}
+
+/* Takes ownership of po->ofpacts, which must have been malloc'ed. */
+static enum ofperr
+ofproto_packet_out_init(struct ofproto *ofproto,
+                        struct ofconn *ofconn,
+                        struct ofproto_packet_out *opo,
+                        const struct ofputil_packet_out *po)
+{
+    enum ofperr error;
+
+    if (ofp_to_u16(po->in_port) >= ofproto->max_ports
+        && ofp_to_u16(po->in_port) < ofp_to_u16(OFPP_MAX)) {
+        return OFPERR_OFPBRC_BAD_PORT;
+    }
+
+    /* Get payload. */
+    if (po->buffer_id != UINT32_MAX) {
+        return OFPERR_OFPBRC_BUFFER_UNKNOWN;
+    }
+
+    /* Ensure that the L3 header is 32-bit aligned. */
+    opo->packet = dp_packet_clone_data_with_headroom(po->packet,
+                                                     po->packet_len, 2);
+    /* Store struct flow. */
+    opo->flow = xmalloc(sizeof *opo->flow);
+    flow_extract(opo->packet, opo->flow);
+    opo->flow->in_port.ofp_port = po->in_port;
+
+    /* Check actions like for flow mods.  We pass a 'table_id' of 0 to
+     * ofproto_check_consistency(), which isn't strictly correct because these
+     * actions aren't in any table.  This is OK as 'table_id' is only used to
+     * check instructions (e.g., goto-table), which can't appear on the action
+     * list of a packet-out. */
+    error = ofpacts_check_consistency(po->ofpacts, po->ofpacts_len,
+                                      opo->flow,
+                                      u16_to_ofp(ofproto->max_ports), 0,
+                                      ofproto->n_tables,
+                                      ofconn_get_protocol(ofconn));
+    if (error) {
+        dp_packet_delete(opo->packet);
+        free(opo->flow);
+        return error;
+    }
+
+    opo->ofpacts = po->ofpacts;
+    opo->ofpacts_len = po->ofpacts_len;
+
+    opo->aux = NULL;
+    return 0;
+}
+
+static enum ofperr
+ofproto_packet_out_start(struct ofproto *ofproto,
+                         struct ofproto_packet_out *opo)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    enum ofperr error;
+
+    error = ofproto_check_ofpacts(ofproto, opo->ofpacts, opo->ofpacts_len);
+    if (error) {
+        return error;
+    }
+
+    return ofproto->ofproto_class->packet_xlate(ofproto, opo);
+}
+
+static void
+ofproto_packet_out_finish(struct ofproto *ofproto,
+                          struct ofproto_packet_out *opo)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    ofproto->ofproto_class->packet_execute(ofproto, opo);
+}
+
 static enum ofperr
 handle_packet_out(struct ofconn *ofconn, const struct ofp_header *oh)
+    OVS_EXCLUDED(ofproto_mutex)
 {
     struct ofproto *p = ofconn_get_ofproto(ofconn);
     struct ofputil_packet_out po;
-    struct dp_packet *payload;
+    struct ofproto_packet_out opo;
     uint64_t ofpacts_stub[1024 / 8];
     struct ofpbuf ofpacts;
-    struct flow flow;
     enum ofperr error;
 
     COVERAGE_INC(ofproto_packet_out);
 
     error = reject_slave_controller(ofconn);
     if (error) {
-        goto exit;
+        return error;
     }
 
     /* Decode message. */
     ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
     error = ofputil_decode_packet_out(&po, oh, &ofpacts);
     if (error) {
-        goto exit_free_ofpacts;
-    }
-    if (ofp_to_u16(po.in_port) >= p->max_ports
-        && ofp_to_u16(po.in_port) < ofp_to_u16(OFPP_MAX)) {
-        error = OFPERR_OFPBRC_BAD_PORT;
-        goto exit_free_ofpacts;
+        ofpbuf_uninit(&ofpacts);
+        return error;
     }
 
-    /* Get payload. */
-    if (po.buffer_id != UINT32_MAX) {
-        error = OFPERR_OFPBRC_BUFFER_UNKNOWN;
-        goto exit_free_ofpacts;
+    po.ofpacts = ofpbuf_steal_data(&ofpacts);   /* Move to heap. */
+    error = ofproto_packet_out_init(p, ofconn, &opo, &po);
+    if (error) {
+        free(po.ofpacts);
+        return error;
     }
-    /* Ensure that the L3 header is 32-bit aligned. */
-    payload = dp_packet_clone_data_with_headroom(po.packet, po.packet_len, 2);
-
-    /* Verify actions against packet, then send packet if successful. */
-    flow_extract(payload, &flow);
-    flow.in_port.ofp_port = po.in_port;
 
-    /* Check actions like for flow mods.  We pass a 'table_id' of 0 to
-     * ofproto_check_consistency(), which isn't strictly correct because these
-     * actions aren't in any table.  This is OK as 'table_id' is only used to
-     * check instructions (e.g., goto-table), which can't appear on the action
-     * list of a packet-out. */
-    error = ofpacts_check_consistency(po.ofpacts, po.ofpacts_len,
-                                      &flow, u16_to_ofp(p->max_ports),
-                                      0, p->n_tables,
-                                      ofconn_get_protocol(ofconn));
+    ovs_mutex_lock(&ofproto_mutex);
+    opo.version = p->tables_version;
+    error = ofproto_packet_out_start(p, &opo);
     if (!error) {
-        /* Should hold ofproto_mutex to guarantee state don't
-         * change between the check and the execution. */
-        error = ofproto_check_ofpacts(p, po.ofpacts, po.ofpacts_len);
-        if (!error) {
-            error = p->ofproto_class->packet_out(p, payload, &flow,
-                                                 po.ofpacts, po.ofpacts_len);
-        }
+        ofproto_packet_out_finish(p, &opo);
     }
-    dp_packet_delete(payload);
+    ovs_mutex_unlock(&ofproto_mutex);
 
-exit_free_ofpacts:
-    ofpbuf_uninit(&ofpacts);
-exit:
+    ofproto_packet_out_uninit(&opo);
     return error;
 }
 
@@ -4849,18 +4915,8 @@ ofproto_flow_mod_init_for_learn(struct ofproto *ofproto,
     return 0;
 }
 
-/* Refresh 'ofm->temp_rule', for which the caller holds a reference, if already
- * in the classifier, insert it otherwise.  If the rule has already been
- * removed from the classifier, a new rule is created using 'ofm->temp_rule' as
- * a template and the reference to the old 'ofm->temp_rule' is freed.  If
- * 'keep_ref' is true, then a reference to the current rule is held, otherwise
- * it is released and 'ofm->temp_rule' is set to NULL.
- *
- * Caller needs to be the exclusive owner of 'ofm' as it is being manipulated
- * during the call. */
 enum ofperr
-ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref)
-    OVS_EXCLUDED(ofproto_mutex)
+ofproto_flow_mod_learn_refresh(struct ofproto_flow_mod *ofm)
 {
     enum ofperr error = 0;
 
@@ -4871,22 +4927,20 @@ ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref)
     if (!rule) {
         return OFPERR_OFPFMFC_UNKNOWN;
     }
-    struct ofproto *ofproto = rule->ofproto;
-    enum rule_state state = rule->state;
 
     /* Create a new rule if the current one has been removed from the
      * classifier.  We need to do this since RCU does not allow a current rule
      * to be reinserted before all threads have quiesced.
      *
      * It is possible that the rule is removed asynchronously, e.g., right
-     * after we have read the 'rule->state' above.  In this case the next time
+     * after we have read the 'rule->state' below.  In this case the next time
      * this function is executed the rule will be reinstated. */
-    if (state == RULE_REMOVED) {
+    if (rule->state == RULE_REMOVED) {
         struct cls_rule cr;
 
         cls_rule_clone(&cr, &rule->cr);
         ovs_mutex_lock(&rule->mutex);
-        error = ofproto_rule_create(ofproto, &cr, rule->table_id,
+        error = ofproto_rule_create(rule->ofproto, &cr, rule->table_id,
                                     rule->flow_cookie,
                                     rule->idle_timeout,
                                     rule->hard_timeout, rule->flags,
@@ -4895,41 +4949,78 @@ ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref)
                                     rule->actions->ofpacts_len,
                                     &ofm->temp_rule);
         ovs_mutex_unlock(&rule->mutex);
-        if (error) {
-            goto error_out;
-        }
-        ofproto_rule_unref(rule);
-        rule = ofm->temp_rule;
-        state = rule->state;
-    }
-
-    /* Do we need to insert the rule?  In this case no-one else has the
-     * reference to this rule yet, but multiple threads may compete inserting
-     * duplicate rules.  This race is resolved by holding the ofproto_mutex and
-     * any possible old rule we don't know of yet being removed by the flow
-     * add. */
-    if (state == RULE_INITIALIZED) {
-        ovs_mutex_lock(&ofproto_mutex);
-        ofm->version = ofproto->tables_version + 1;
-        /* ofproto_flow_mod_start() consumes the reference, so we take a new
-         * one. */
-        ofproto_rule_ref(rule);
-        error = ofproto_flow_mod_start(ofproto, ofm);
-        ofm->temp_rule = rule;
         if (!error) {
-            ofproto_bump_tables_version(ofproto);
-            ofproto_flow_mod_finish(ofproto, ofm, NULL);
-            ofmonitor_flush(ofproto->connmgr);
+            ofproto_rule_unref(rule);   /* Release old reference. */
         }
-        ovs_mutex_unlock(&ofproto_mutex);
     } else {
         /* Refresh the existing rule. */
         ovs_mutex_lock(&rule->mutex);
         rule->modified = time_msec();
         ovs_mutex_unlock(&rule->mutex);
     }
+    return error;
+}
+
+enum ofperr
+ofproto_flow_mod_learn_start(struct ofproto_flow_mod *ofm)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct rule *rule = ofm->temp_rule;
+
+    /* ofproto_flow_mod_start() consumes the reference, so we
+     * take a new one. */
+    ofproto_rule_ref(rule);
+    enum ofperr error = ofproto_flow_mod_start(rule->ofproto, ofm);
+    ofm->temp_rule = rule;
+
+    return error;
+}
+
+void
+ofproto_flow_mod_learn_finish(struct ofproto_flow_mod *ofm,
+                              struct ofproto *orig_ofproto)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct rule *rule = rule_collection_rules(&ofm->new_rules)[0];
+
+    /* If learning on a different bridge, must bump its version
+     * number and flush connmgr afterwards. */
+    if (rule->ofproto != orig_ofproto) {
+        ofproto_bump_tables_version(rule->ofproto);
+    }
+    ofproto_flow_mod_finish(rule->ofproto, ofm, NULL);
+    if (rule->ofproto != orig_ofproto) {
+        ofmonitor_flush(rule->ofproto->connmgr);
+    }
+}
+
+/* Refresh 'ofm->temp_rule', for which the caller holds a reference, if already
+ * in the classifier, insert it otherwise.  If the rule has already been
+ * removed from the classifier, a new rule is created using 'ofm->temp_rule' as
+ * a template and the reference to the old 'ofm->temp_rule' is freed.  If
+ * 'keep_ref' is true, then a reference to the current rule is held, otherwise
+ * it is released and 'ofm->temp_rule' is set to NULL.
+ *
+ * Caller needs to be the exclusive owner of 'ofm' as it is being manipulated
+ * during the call. */
+enum ofperr
+ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref)
+    OVS_EXCLUDED(ofproto_mutex)
+{
+    enum ofperr error = ofproto_flow_mod_learn_refresh(ofm);
+    struct rule *rule = ofm->temp_rule;
+
+    /* Do we need to insert the rule? */
+    if (!error && rule->state == RULE_INITIALIZED) {
+        ovs_mutex_lock(&ofproto_mutex);
+        ofm->version = rule->ofproto->tables_version + 1;
+        error = ofproto_flow_mod_learn_start(ofm);
+        if (!error) {
+            ofproto_flow_mod_learn_finish(ofm, NULL);
+        }
+        ovs_mutex_unlock(&ofproto_mutex);
+    }
 
-error_out:
     if (!keep_ref) {
         ofproto_rule_unref(rule);
         ofm->temp_rule = NULL;
@@ -5513,6 +5604,26 @@ reduce_timeout(uint16_t max, uint16_t *timeout)
  *
  * Suitable for implementing OFPACT_FIN_TIMEOUT. */
 void
+ofproto_rule_reduce_timeouts__(struct rule *rule,
+                               uint16_t idle_timeout, uint16_t hard_timeout)
+    OVS_REQUIRES(ofproto_mutex)
+    OVS_EXCLUDED(rule->mutex)
+{
+    if (!idle_timeout && !hard_timeout) {
+        return;
+    }
+
+    if (ovs_list_is_empty(&rule->expirable)) {
+        ovs_list_insert(&rule->ofproto->expirable, &rule->expirable);
+    }
+
+    ovs_mutex_lock(&rule->mutex);
+    reduce_timeout(idle_timeout, &rule->idle_timeout);
+    reduce_timeout(hard_timeout, &rule->hard_timeout);
+    ovs_mutex_unlock(&rule->mutex);
+}
+
+void
 ofproto_rule_reduce_timeouts(struct rule *rule,
                              uint16_t idle_timeout, uint16_t hard_timeout)
     OVS_EXCLUDED(ofproto_mutex, rule->mutex)
-- 
2.1.4





More information about the dev mailing list