/*
- * Copyright (c) 2009, 2010, 2011, 2012 Nicira Networks.
+ * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include "ofp-util.h"
#include "ofpbuf.h"
#include "ofp-print.h"
+#include "ofproto-dpif-governor.h"
#include "ofproto-dpif-sflow.h"
#include "poll-loop.h"
#include "timer.h"
COVERAGE_DEFINE(facet_invalidated);
COVERAGE_DEFINE(facet_revalidate);
COVERAGE_DEFINE(facet_unexpected);
+COVERAGE_DEFINE(facet_suppress);
/* Maximum depth of flow table recursion (due to resubmit actions) in a
* flow translation. */
static void bundle_run(struct ofbundle *);
static void bundle_wait(struct ofbundle *);
static struct ofbundle *lookup_input_bundle(struct ofproto_dpif *,
- uint16_t in_port, bool warn);
+ uint16_t in_port, bool warn,
+ struct ofport_dpif **in_ofportp);
/* A controller may use OFPP_NONE as the ingress port to indicate that
* it did not arrive on a "real" port. 'ofpp_none_bundle' exists for
uint16_t sflow_odp_port; /* Output port for composing sFlow action. */
uint16_t user_cookie_offset;/* Used for user_action_cookie fixup. */
bool exit; /* No further actions should be processed. */
+ struct flow orig_flow; /* Copy of original flow. */
};
static void action_xlate_ctx_init(struct action_xlate_ctx *,
static void subfacet_update_stats(struct subfacet *,
const struct dpif_flow_stats *);
static void subfacet_make_actions(struct subfacet *,
- const struct ofpbuf *packet);
+ const struct ofpbuf *packet,
+ struct ofpbuf *odp_actions);
static int subfacet_install(struct subfacet *,
const struct nlattr *actions, size_t actions_len,
struct dpif_flow_stats *);
struct hmap bundles; /* Contains "struct ofbundle"s. */
struct mac_learning *ml;
struct ofmirror *mirrors[MAX_MIRRORS];
+ bool has_mirrors;
bool has_bonded_bundles;
/* Expiration. */
/* Facets. */
struct hmap facets;
struct hmap subfacets;
+ struct governor *governor;
/* Revalidation. */
struct table_dpif tables[N_TABLES];
hmap_init(&ofproto->facets);
hmap_init(&ofproto->subfacets);
+ ofproto->governor = NULL;
for (i = 0; i < N_TABLES; i++) {
struct table_dpif *table = &ofproto->tables[i];
ofproto_dpif_unixctl_init();
+ ofproto->has_mirrors = false;
ofproto->has_bundle_action = false;
hmap_init(&ofproto->vlandev_map);
hmap_destroy(&ofproto->facets);
hmap_destroy(&ofproto->subfacets);
+ governor_destroy(ofproto->governor);
hmap_destroy(&ofproto->vlandev_map);
hmap_destroy(&ofproto->realdev_vid_map);
}
}
+ if (ofproto->governor) {
+ size_t n_subfacets;
+
+ governor_run(ofproto->governor);
+
+ /* If the governor has shrunk to its minimum size and the number of
+ * subfacets has dwindled, then drop the governor entirely.
+ *
+ * For hysteresis, the number of subfacets to drop the governor is
+ * smaller than the number needed to trigger its creation. */
+ n_subfacets = hmap_count(&ofproto->subfacets);
+ if (n_subfacets * 4 < ofproto->up.flow_eviction_threshold
+ && governor_is_idle(ofproto->governor)) {
+ governor_destroy(ofproto->governor);
+ ofproto->governor = NULL;
+ }
+ }
+
return 0;
}
} else {
timer_wait(&ofproto->next_expiration);
}
+ if (ofproto->governor) {
+ governor_wait(ofproto->governor);
+ }
}
static void
}
ofproto->need_revalidate = true;
+ ofproto->has_mirrors = true;
mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
mirror_update_dups(ofproto);
struct ofproto_dpif *ofproto;
mirror_mask_t mirror_bit;
struct ofbundle *bundle;
+ int i;
if (!mirror) {
return;
free(mirror);
mirror_update_dups(ofproto);
+
+ ofproto->has_mirrors = false;
+ for (i = 0; i < MAX_MIRRORS; i++) {
+ if (ofproto->mirrors[i]) {
+ ofproto->has_mirrors = true;
+ break;
+ }
+ }
}
static int
struct flow_miss_op {
struct dpif_op dpif_op;
- struct subfacet *subfacet;
+ struct subfacet *subfacet; /* Subfacet */
+ void *garbage; /* Pointer to pass to free(), NULL if none. */
+ uint64_t stub[1024 / 8]; /* Temporary buffer. */
};
/* Sends an OFPT_PACKET_IN message for 'packet' of type OFPR_NO_MATCH to each
return NULL;
}
+/* Partially Initializes 'op' as an "execute" operation for 'miss' and
+ * 'packet'. The caller must initialize op->actions and op->actions_len. If
+ * 'miss' is associated with a subfacet the caller must also initialize the
+ * returned op->subfacet, and if anything needs to be freed after processing
+ * the op, the caller must initialize op->garbage also. */
static void
-handle_flow_miss(struct ofproto_dpif *ofproto, struct flow_miss *miss,
- struct flow_miss_op *ops, size_t *n_ops)
+init_flow_miss_execute_op(struct flow_miss *miss, struct ofpbuf *packet,
+ struct flow_miss_op *op)
+{
+ if (miss->flow.vlan_tci != miss->initial_tci) {
+ /* This packet was received on a VLAN splinter port. We
+ * added a VLAN to the packet to make the packet resemble
+ * the flow, but the actions were composed assuming that
+ * the packet contained no VLAN. So, we must remove the
+ * VLAN header from the packet before trying to execute the
+ * actions. */
+ eth_pop_vlan(packet);
+ }
+
+ op->subfacet = NULL;
+ op->garbage = NULL;
+ op->dpif_op.type = DPIF_OP_EXECUTE;
+ op->dpif_op.u.execute.key = miss->key;
+ op->dpif_op.u.execute.key_len = miss->key_len;
+ op->dpif_op.u.execute.packet = packet;
+}
+
+/* Helper for handle_flow_miss_without_facet() and
+ * handle_flow_miss_with_facet(). */
+static void
+handle_flow_miss_common(struct rule_dpif *rule,
+ struct ofpbuf *packet, const struct flow *flow)
{
- const struct flow *flow = &miss->flow;
- struct subfacet *subfacet;
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
+
+ ofproto->n_matches++;
+
+ if (rule->up.cr.priority == FAIL_OPEN_PRIORITY) {
+ /*
+ * Extra-special case for fail-open mode.
+ *
+ * We are in fail-open mode and the packet matched the fail-open
+ * rule, but we are connected to a controller too. We should send
+ * the packet up to the controller in the hope that it will try to
+ * set up a flow and thereby allow us to exit fail-open.
+ *
+ * See the top-level comment in fail-open.c for more information.
+ */
+ send_packet_in_miss(ofproto, packet, flow);
+ }
+}
+
+/* Figures out whether a flow that missed in 'ofproto', whose details are in
+ * 'miss', is likely to be worth tracking in detail in userspace and (usually)
+ * installing a datapath flow. The answer is usually "yes" (a return value of
+ * true). However, for short flows the cost of bookkeeping is much higher than
+ * the benefits, so when the datapath holds a large number of flows we impose
+ * some heuristics to decide which flows are likely to be worth tracking. */
+static bool
+flow_miss_should_make_facet(struct ofproto_dpif *ofproto,
+ struct flow_miss *miss, uint32_t hash)
+{
+ if (!ofproto->governor) {
+ size_t n_subfacets;
+
+ n_subfacets = hmap_count(&ofproto->subfacets);
+ if (n_subfacets * 2 <= ofproto->up.flow_eviction_threshold) {
+ return true;
+ }
+
+ ofproto->governor = governor_create(ofproto->up.name);
+ }
+
+ return governor_should_install_flow(ofproto->governor, hash,
+ list_size(&miss->packets));
+}
+
+/* Handles 'miss', which matches 'rule', without creating a facet or subfacet
+ * or creating any datapath flow. May add an "execute" operation to 'ops' and
+ * increment '*n_ops'. */
+static void
+handle_flow_miss_without_facet(struct flow_miss *miss,
+ struct rule_dpif *rule,
+ struct flow_miss_op *ops, size_t *n_ops)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
+ struct action_xlate_ctx ctx;
struct ofpbuf *packet;
- struct facet *facet;
- uint32_t hash;
- /* The caller must ensure that miss->hmap_node.hash contains
- * flow_hash(miss->flow, 0). */
- hash = miss->hmap_node.hash;
+ LIST_FOR_EACH (packet, list_node, &miss->packets) {
+ struct flow_miss_op *op = &ops[*n_ops];
+ struct dpif_flow_stats stats;
+ struct ofpbuf odp_actions;
- facet = facet_lookup_valid(ofproto, flow, hash);
- if (!facet) {
- struct rule_dpif *rule;
+ COVERAGE_INC(facet_suppress);
- rule = rule_dpif_lookup(ofproto, flow, 0);
- if (!rule) {
- /* Don't send a packet-in if OFPUTIL_PC_NO_PACKET_IN asserted. */
- struct ofport_dpif *port = get_ofp_port(ofproto, flow->in_port);
- if (port) {
- if (port->up.pp.config & OFPUTIL_PC_NO_PACKET_IN) {
- COVERAGE_INC(ofproto_dpif_no_packet_in);
- /* XXX install 'drop' flow entry */
- return;
- }
- } else {
- VLOG_WARN_RL(&rl, "packet-in on unknown port %"PRIu16,
- flow->in_port);
- }
+ ofpbuf_use_stub(&odp_actions, op->stub, sizeof op->stub);
- LIST_FOR_EACH (packet, list_node, &miss->packets) {
- send_packet_in_miss(ofproto, packet, flow);
- }
+ dpif_flow_stats_extract(&miss->flow, packet, &stats);
+ rule_credit_stats(rule, &stats);
- return;
- }
+ action_xlate_ctx_init(&ctx, ofproto, &miss->flow, miss->initial_tci,
+ rule, 0, packet);
+ ctx.resubmit_stats = &stats;
+ xlate_actions(&ctx, rule->up.actions, rule->up.n_actions,
+ &odp_actions);
- facet = facet_create(rule, flow, hash);
+ if (odp_actions.size) {
+ struct dpif_execute *execute = &op->dpif_op.u.execute;
+
+ init_flow_miss_execute_op(miss, packet, op);
+ execute->actions = odp_actions.data;
+ execute->actions_len = odp_actions.size;
+ op->garbage = ofpbuf_get_uninit_pointer(&odp_actions);
+
+ (*n_ops)++;
+ } else {
+ ofpbuf_uninit(&odp_actions);
+ }
}
+}
+
+/* Handles 'miss', which matches 'facet'. May add any required datapath
+ * operations to 'ops', incrementing '*n_ops' for each new op. */
+static void
+handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet,
+ struct flow_miss_op *ops, size_t *n_ops)
+{
+ struct subfacet *subfacet;
+ struct ofpbuf *packet;
subfacet = subfacet_create(facet,
miss->key_fitness, miss->key, miss->key_len,
miss->initial_tci);
LIST_FOR_EACH (packet, list_node, &miss->packets) {
+ struct flow_miss_op *op = &ops[*n_ops];
struct dpif_flow_stats stats;
- struct flow_miss_op *op;
- struct dpif_execute *execute;
-
- ofproto->n_matches++;
+ struct ofpbuf odp_actions;
- if (facet->rule->up.cr.priority == FAIL_OPEN_PRIORITY) {
- /*
- * Extra-special case for fail-open mode.
- *
- * We are in fail-open mode and the packet matched the fail-open
- * rule, but we are connected to a controller too. We should send
- * the packet up to the controller in the hope that it will try to
- * set up a flow and thereby allow us to exit fail-open.
- *
- * See the top-level comment in fail-open.c for more information.
- */
- send_packet_in_miss(ofproto, packet, flow);
- }
+ handle_flow_miss_common(facet->rule, packet, &miss->flow);
+ ofpbuf_use_stub(&odp_actions, op->stub, sizeof op->stub);
if (!facet->may_install || !subfacet->actions) {
- subfacet_make_actions(subfacet, packet);
+ subfacet_make_actions(subfacet, packet, &odp_actions);
}
dpif_flow_stats_extract(&facet->flow, packet, &stats);
subfacet_update_stats(subfacet, &stats);
- if (!subfacet->actions_len) {
- /* No actions to execute, so skip talking to the dpif. */
- continue;
- }
+ if (subfacet->actions_len) {
+ struct dpif_execute *execute = &op->dpif_op.u.execute;
- if (flow->vlan_tci != subfacet->initial_tci) {
- /* This packet was received on a VLAN splinter port. We added
- * a VLAN to the packet to make the packet resemble the flow,
- * but the actions were composed assuming that the packet
- * contained no VLAN. So, we must remove the VLAN header from
- * the packet before trying to execute the actions. */
- eth_pop_vlan(packet);
- }
+ init_flow_miss_execute_op(miss, packet, op);
+ op->subfacet = subfacet;
+ if (facet->may_install) {
+ execute->actions = subfacet->actions;
+ execute->actions_len = subfacet->actions_len;
+ ofpbuf_uninit(&odp_actions);
+ } else {
+ execute->actions = odp_actions.data;
+ execute->actions_len = odp_actions.size;
+ op->garbage = ofpbuf_get_uninit_pointer(&odp_actions);
+ }
- op = &ops[(*n_ops)++];
- execute = &op->dpif_op.u.execute;
- op->subfacet = subfacet;
- op->dpif_op.type = DPIF_OP_EXECUTE;
- execute->key = miss->key;
- execute->key_len = miss->key_len;
- execute->actions = (facet->may_install
- ? subfacet->actions
- : xmemdup(subfacet->actions,
- subfacet->actions_len));
- execute->actions_len = subfacet->actions_len;
- execute->packet = packet;
+ (*n_ops)++;
+ } else {
+ ofpbuf_uninit(&odp_actions);
+ }
}
if (facet->may_install && subfacet->key_fitness != ODP_FIT_TOO_LITTLE) {
struct dpif_flow_put *put = &op->dpif_op.u.flow_put;
op->subfacet = subfacet;
+ op->garbage = NULL;
op->dpif_op.type = DPIF_OP_FLOW_PUT;
put->flags = DPIF_FP_CREATE | DPIF_FP_MODIFY;
put->key = miss->key;
}
}
+/* Handles flow miss 'miss' on 'ofproto'. The flow does not match any flow in
+ * the OpenFlow flow table. */
+static void
+handle_flow_miss_no_rule(struct ofproto_dpif *ofproto, struct flow_miss *miss)
+{
+ uint16_t in_port = miss->flow.in_port;
+ struct ofport_dpif *port = get_ofp_port(ofproto, in_port);
+
+ if (!port) {
+ VLOG_WARN_RL(&rl, "packet-in on unknown port %"PRIu16, in_port);
+ }
+
+ if (port && port->up.pp.config & OFPUTIL_PC_NO_PACKET_IN) {
+ /* XXX install 'drop' flow entry */
+ COVERAGE_INC(ofproto_dpif_no_packet_in);
+ } else {
+ const struct ofpbuf *packet;
+
+ LIST_FOR_EACH (packet, list_node, &miss->packets) {
+ send_packet_in_miss(ofproto, packet, &miss->flow);
+ }
+ }
+}
+
+/* Handles flow miss 'miss' on 'ofproto'. May add any required datapath
+ * operations to 'ops', incrementing '*n_ops' for each new op. */
+static void
+handle_flow_miss(struct ofproto_dpif *ofproto, struct flow_miss *miss,
+ struct flow_miss_op *ops, size_t *n_ops)
+{
+ struct facet *facet;
+ uint32_t hash;
+
+ /* The caller must ensure that miss->hmap_node.hash contains
+ * flow_hash(miss->flow, 0). */
+ hash = miss->hmap_node.hash;
+
+ facet = facet_lookup_valid(ofproto, &miss->flow, hash);
+ if (!facet) {
+ struct rule_dpif *rule = rule_dpif_lookup(ofproto, &miss->flow, 0);
+ if (!rule) {
+ handle_flow_miss_no_rule(ofproto, miss);
+ return;
+ } else if (!flow_miss_should_make_facet(ofproto, miss, hash)) {
+ handle_flow_miss_without_facet(miss, rule, ops, n_ops);
+ return;
+ }
+
+ facet = facet_create(rule, &miss->flow, hash);
+ }
+ handle_flow_miss_with_facet(miss, facet, ops, n_ops);
+}
+
/* Like odp_flow_key_to_flow(), this function converts the 'key_len' bytes of
* OVS_KEY_ATTR_* attributes in 'key' to a flow structure in 'flow' and returns
* an ODP_FIT_* value that indicates how well 'key' fits our expectations for
/* Free memory and update facets. */
for (i = 0; i < n_ops; i++) {
struct flow_miss_op *op = &flow_miss_ops[i];
- struct dpif_execute *execute;
switch (op->dpif_op.type) {
case DPIF_OP_EXECUTE:
- execute = &op->dpif_op.u.execute;
- if (op->subfacet->actions != execute->actions) {
- free((struct nlattr *) execute->actions);
- }
break;
case DPIF_OP_FLOW_PUT:
case DPIF_OP_FLOW_DEL:
NOT_REACHED();
}
+
+ free(op->garbage);
}
hmap_destroy(&todo);
}
}
}
-/* Composes the datapath actions for 'subfacet' based on its rule's actions. */
+/* Composes the datapath actions for 'subfacet' based on its rule's actions.
+ * Translates the actions into 'odp_actions', which the caller must have
+ * initialized and is responsible for uninitializing. */
static void
-subfacet_make_actions(struct subfacet *subfacet, const struct ofpbuf *packet)
+subfacet_make_actions(struct subfacet *subfacet, const struct ofpbuf *packet,
+ struct ofpbuf *odp_actions)
{
struct facet *facet = subfacet->facet;
struct rule_dpif *rule = facet->rule;
struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
struct action_xlate_ctx ctx;
- uint64_t odp_actions_stub[1024 / 8];
- struct ofpbuf odp_actions;
- ofpbuf_use_stub(&odp_actions, odp_actions_stub, sizeof odp_actions_stub);
action_xlate_ctx_init(&ctx, ofproto, &facet->flow, subfacet->initial_tci,
rule, 0, packet);
- xlate_actions(&ctx, rule->up.actions, rule->up.n_actions, &odp_actions);
+ xlate_actions(&ctx, rule->up.actions, rule->up.n_actions, odp_actions);
facet->tags = ctx.tags;
facet->may_install = ctx.may_set_up_flow;
facet->has_learn = ctx.has_learn;
facet->nf_flow.output_iface = ctx.nf_output_iface;
facet->mirrors = ctx.mirrors;
- if (subfacet->actions_len != odp_actions.size
- || memcmp(subfacet->actions, odp_actions.data, odp_actions.size)) {
+ if (subfacet->actions_len != odp_actions->size
+ || memcmp(subfacet->actions, odp_actions->data, odp_actions->size)) {
free(subfacet->actions);
- subfacet->actions_len = odp_actions.size;
- subfacet->actions = xmemdup(odp_actions.data, odp_actions.size);
+ subfacet->actions_len = odp_actions->size;
+ subfacet->actions = xmemdup(odp_actions->data, odp_actions->size);
}
-
- ofpbuf_uninit(&odp_actions);
}
/* Updates 'subfacet''s datapath flow, setting its actions to 'actions_len'
const union ofp_action *in, size_t n_in,
struct ofpbuf *odp_actions)
{
- struct flow orig_flow = ctx->flow;
+ /* Normally false. Set to true if we ever hit MAX_RESUBMIT_RECURSION, so
+ * that in the future we always keep a copy of the original flow for
+ * tracing purposes. */
+ static bool hit_resubmit_limit;
COVERAGE_INC(ofproto_dpif_xlate);
ctx->table_id = 0;
ctx->exit = false;
+ if (ctx->ofproto->has_mirrors || hit_resubmit_limit) {
+ /* Do this conditionally because the copy is expensive enough that it
+ * shows up in profiles.
+ *
+ * We keep orig_flow in 'ctx' only because I couldn't make GCC 4.4
+ * believe that I wasn't using it without initializing it if I kept it
+ * in a local variable. */
+ ctx->orig_flow = ctx->flow;
+ }
+
if (ctx->flow.nw_frag & FLOW_NW_FRAG_ANY) {
switch (ctx->ofproto->up.frag_handling) {
case OFPC_FRAG_NORMAL:
ctx->may_set_up_flow = false;
} else {
static struct vlog_rate_limit trace_rl = VLOG_RATE_LIMIT_INIT(1, 1);
- struct flow original_flow = ctx->flow;
ovs_be16 initial_tci = ctx->base_flow.vlan_tci;
add_sflow_action(ctx);
do_xlate_actions(in, n_in, ctx);
- if (ctx->max_resubmit_trigger && !ctx->resubmit_hook
- && !VLOG_DROP_ERR(&trace_rl)) {
- struct ds ds = DS_EMPTY_INITIALIZER;
-
- ofproto_trace(ctx->ofproto, &original_flow, ctx->packet,
- initial_tci, &ds);
- VLOG_ERR("Trace triggered by excessive resubmit recursion:\n%s",
- ds_cstr(&ds));
- ds_destroy(&ds);
+ if (ctx->max_resubmit_trigger && !ctx->resubmit_hook) {
+ if (!hit_resubmit_limit) {
+ /* We didn't record the original flow. Make sure we do from
+ * now on. */
+ hit_resubmit_limit = true;
+ } else if (!VLOG_DROP_ERR(&trace_rl)) {
+ struct ds ds = DS_EMPTY_INITIALIZER;
+
+ ofproto_trace(ctx->ofproto, &ctx->orig_flow, ctx->packet,
+ initial_tci, &ds);
+ VLOG_ERR("Trace triggered by excessive resubmit "
+ "recursion:\n%s", ds_cstr(&ds));
+ ds_destroy(&ds);
+ }
}
if (!connmgr_may_set_up_flow(ctx->ofproto->up.connmgr, &ctx->flow,
compose_output_action(ctx, OFPP_LOCAL);
}
}
- add_mirror_actions(ctx, &orig_flow);
+ if (ctx->ofproto->has_mirrors) {
+ add_mirror_actions(ctx, &ctx->orig_flow);
+ }
fix_sflow_action(ctx);
}
}
size_t left;
in_bundle = lookup_input_bundle(ctx->ofproto, orig_flow->in_port,
- ctx->packet != NULL);
+ ctx->packet != NULL, NULL);
if (!in_bundle) {
return;
}
}
static struct ofbundle *
-lookup_input_bundle(struct ofproto_dpif *ofproto, uint16_t in_port, bool warn)
+lookup_input_bundle(struct ofproto_dpif *ofproto, uint16_t in_port, bool warn,
+ struct ofport_dpif **in_ofportp)
{
struct ofport_dpif *ofport;
- /* Special-case OFPP_NONE, which a controller may use as the ingress
- * port for traffic that it is sourcing. */
- if (in_port == OFPP_NONE) {
- return &ofpp_none_bundle;
- }
-
/* Find the port and bundle for the received packet. */
ofport = get_ofp_port(ofproto, in_port);
+ if (in_ofportp) {
+ *in_ofportp = ofport;
+ }
if (ofport && ofport->bundle) {
return ofport->bundle;
}
+ /* Special-case OFPP_NONE, which a controller may use as the ingress
+ * port for traffic that it is sourcing. */
+ if (in_port == OFPP_NONE) {
+ return &ofpp_none_bundle;
+ }
+
/* Odd. A few possible reasons here:
*
* - We deleted a port but there are still a few packets queued up
ctx->has_normal = true;
in_bundle = lookup_input_bundle(ctx->ofproto, ctx->flow.in_port,
- ctx->packet != NULL);
+ ctx->packet != NULL, &in_port);
if (!in_bundle) {
return;
}
- /* We know 'in_port' exists unless it is "ofpp_none_bundle",
- * since lookup_input_bundle() succeeded. */
- in_port = get_ofp_port(ctx->ofproto, ctx->flow.in_port);
-
/* Drop malformed frames. */
if (ctx->flow.dl_type == htons(ETH_TYPE_VLAN) &&
!(ctx->flow.vlan_tci & htons(VLAN_CFI))) {