+
+ case OFPACT_EXIT:
+ ctx->exit = true;
+ break;
+
+ case OFPACT_FIN_TIMEOUT:
+ ctx->has_fin_timeout = true;
+ xlate_fin_timeout(ctx, ofpact_get_FIN_TIMEOUT(a));
+ break;
+
+ case OFPACT_CLEAR_ACTIONS:
+ /* TODO:XXX
+ * Nothing to do because writa-actions is not supported for now.
+ * When writa-actions is supported, clear-actions also must
+ * be supported at the same time.
+ */
+ break;
+
+ case OFPACT_WRITE_METADATA:
+ metadata = ofpact_get_WRITE_METADATA(a);
+ ctx->flow.metadata &= ~metadata->mask;
+ ctx->flow.metadata |= metadata->metadata & metadata->mask;
+ break;
+
+ case OFPACT_GOTO_TABLE: {
+ /* TODO:XXX remove recursion */
+ /* It is assumed that goto-table is last action */
+ struct ofpact_goto_table *ogt = ofpact_get_GOTO_TABLE(a);
+ assert(ctx->table_id < ogt->table_id);
+ xlate_table_action(ctx, ctx->flow.in_port, ogt->table_id, true);
+ break;
+ }
+ }
+ }
+
+out:
+ /* We've let OFPP_NORMAL and the learning action look at the packet,
+ * so drop it now if forwarding is disabled. */
+ if (port && !stp_forward_in_state(port->stp_state)) {
+ ofpbuf_clear(ctx->odp_actions);
+ add_sflow_action(ctx);
+ }
+ if (ctx->rule) {
+ ctx->rule->up.evictable = was_evictable;
+ }
+}
+
+static void
+action_xlate_ctx_init(struct action_xlate_ctx *ctx,
+ struct ofproto_dpif *ofproto, const struct flow *flow,
+ ovs_be16 initial_tci, struct rule_dpif *rule,
+ uint8_t tcp_flags, const struct ofpbuf *packet)
+{
+ ctx->ofproto = ofproto;
+ ctx->flow = *flow;
+ ctx->base_flow = ctx->flow;
+ memset(&ctx->base_flow.tunnel, 0, sizeof ctx->base_flow.tunnel);
+ ctx->base_flow.vlan_tci = initial_tci;
+ ctx->rule = rule;
+ ctx->packet = packet;
+ ctx->may_learn = packet != NULL;
+ ctx->tcp_flags = tcp_flags;
+ ctx->resubmit_hook = NULL;
+ ctx->report_hook = NULL;
+ ctx->resubmit_stats = NULL;
+}
+
+/* Translates the 'ofpacts_len' bytes of "struct ofpacts" starting at 'ofpacts'
+ * into datapath actions in 'odp_actions', using 'ctx'. */
+static void
+xlate_actions(struct action_xlate_ctx *ctx,
+ const struct ofpact *ofpacts, size_t ofpacts_len,
+ struct ofpbuf *odp_actions)
+{
+ /* 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;
+
+ enum slow_path_reason special;
+
+ COVERAGE_INC(ofproto_dpif_xlate);
+
+ ofpbuf_clear(odp_actions);
+ ofpbuf_reserve(odp_actions, NL_A_U32_SIZE);
+
+ ctx->odp_actions = odp_actions;
+ ctx->tags = 0;
+ ctx->slow = 0;
+ ctx->has_learn = false;
+ ctx->has_normal = false;
+ ctx->has_fin_timeout = false;
+ ctx->nf_output_iface = NF_OUT_DROP;
+ ctx->mirrors = 0;
+ ctx->recurse = 0;
+ ctx->max_resubmit_trigger = false;
+ ctx->orig_skb_priority = ctx->flow.skb_priority;
+ 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:
+ /* We must pretend that transport ports are unavailable. */
+ ctx->flow.tp_src = ctx->base_flow.tp_src = htons(0);
+ ctx->flow.tp_dst = ctx->base_flow.tp_dst = htons(0);
+ break;
+
+ case OFPC_FRAG_DROP:
+ return;
+
+ case OFPC_FRAG_REASM:
+ NOT_REACHED();
+
+ case OFPC_FRAG_NX_MATCH:
+ /* Nothing to do. */
+ break;
+
+ case OFPC_INVALID_TTL_TO_CONTROLLER:
+ NOT_REACHED();
+ }
+ }
+
+ special = process_special(ctx->ofproto, &ctx->flow, ctx->packet);
+ if (special) {
+ ctx->slow |= special;
+ } else {
+ static struct vlog_rate_limit trace_rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ ovs_be16 initial_tci = ctx->base_flow.vlan_tci;
+
+ add_sflow_action(ctx);
+ do_xlate_actions(ofpacts, ofpacts_len, ctx);
+
+ 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,
+ ctx->odp_actions->data,
+ ctx->odp_actions->size)) {
+ ctx->slow |= SLOW_IN_BAND;
+ if (ctx->packet
+ && connmgr_msg_in_hook(ctx->ofproto->up.connmgr, &ctx->flow,
+ ctx->packet)) {
+ compose_output_action(ctx, OFPP_LOCAL);
+ }
+ }
+ if (ctx->ofproto->has_mirrors) {
+ add_mirror_actions(ctx, &ctx->orig_flow);
+ }
+ fix_sflow_action(ctx);
+ }
+}
+
+/* Translates the 'ofpacts_len' bytes of "struct ofpact"s starting at 'ofpacts'
+ * into datapath actions, using 'ctx', and discards the datapath actions. */
+static void
+xlate_actions_for_side_effects(struct action_xlate_ctx *ctx,
+ const struct ofpact *ofpacts,
+ size_t ofpacts_len)
+{
+ uint64_t odp_actions_stub[1024 / 8];
+ struct ofpbuf odp_actions;
+
+ ofpbuf_use_stub(&odp_actions, odp_actions_stub, sizeof odp_actions_stub);
+ xlate_actions(ctx, ofpacts, ofpacts_len, &odp_actions);
+ ofpbuf_uninit(&odp_actions);
+}
+
+static void
+xlate_report(struct action_xlate_ctx *ctx, const char *s)
+{
+ if (ctx->report_hook) {
+ ctx->report_hook(ctx, s);
+ }
+}
+\f
+/* OFPP_NORMAL implementation. */
+
+static struct ofport_dpif *ofbundle_get_a_port(const struct ofbundle *);
+
+/* Given 'vid', the VID obtained from the 802.1Q header that was received as
+ * part of a packet (specify 0 if there was no 802.1Q header), and 'in_bundle',
+ * the bundle on which the packet was received, returns the VLAN to which the
+ * packet belongs.
+ *
+ * Both 'vid' and the return value are in the range 0...4095. */
+static uint16_t
+input_vid_to_vlan(const struct ofbundle *in_bundle, uint16_t vid)
+{
+ switch (in_bundle->vlan_mode) {
+ case PORT_VLAN_ACCESS:
+ return in_bundle->vlan;
+ break;
+
+ case PORT_VLAN_TRUNK:
+ return vid;
+
+ case PORT_VLAN_NATIVE_UNTAGGED:
+ case PORT_VLAN_NATIVE_TAGGED:
+ return vid ? vid : in_bundle->vlan;
+
+ default:
+ NOT_REACHED();
+ }
+}
+
+/* Checks whether a packet with the given 'vid' may ingress on 'in_bundle'.
+ * If so, returns true. Otherwise, returns false and, if 'warn' is true, logs
+ * a warning.
+ *
+ * 'vid' should be the VID obtained from the 802.1Q header that was received as
+ * part of a packet (specify 0 if there was no 802.1Q header), in the range
+ * 0...4095. */
+static bool
+input_vid_is_valid(uint16_t vid, struct ofbundle *in_bundle, bool warn)
+{
+ /* Allow any VID on the OFPP_NONE port. */
+ if (in_bundle == &ofpp_none_bundle) {
+ return true;
+ }
+
+ switch (in_bundle->vlan_mode) {
+ case PORT_VLAN_ACCESS:
+ if (vid) {
+ if (warn) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %"PRIu16" tagged "
+ "packet received on port %s configured as VLAN "
+ "%"PRIu16" access port",
+ in_bundle->ofproto->up.name, vid,
+ in_bundle->name, in_bundle->vlan);
+ }
+ return false;
+ }
+ return true;
+
+ case PORT_VLAN_NATIVE_UNTAGGED:
+ case PORT_VLAN_NATIVE_TAGGED:
+ if (!vid) {
+ /* Port must always carry its native VLAN. */
+ return true;
+ }
+ /* Fall through. */
+ case PORT_VLAN_TRUNK:
+ if (!ofbundle_includes_vlan(in_bundle, vid)) {
+ if (warn) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %"PRIu16" packet "
+ "received on port %s not configured for trunking "
+ "VLAN %"PRIu16,
+ in_bundle->ofproto->up.name, vid,
+ in_bundle->name, vid);
+ }
+ return false;
+ }
+ return true;
+
+ default:
+ NOT_REACHED();
+ }
+
+}
+
+/* Given 'vlan', the VLAN that a packet belongs to, and
+ * 'out_bundle', a bundle on which the packet is to be output, returns the VID
+ * that should be included in the 802.1Q header. (If the return value is 0,
+ * then the 802.1Q header should only be included in the packet if there is a
+ * nonzero PCP.)
+ *
+ * Both 'vlan' and the return value are in the range 0...4095. */
+static uint16_t
+output_vlan_to_vid(const struct ofbundle *out_bundle, uint16_t vlan)
+{
+ switch (out_bundle->vlan_mode) {
+ case PORT_VLAN_ACCESS:
+ return 0;
+
+ case PORT_VLAN_TRUNK:
+ case PORT_VLAN_NATIVE_TAGGED:
+ return vlan;
+
+ case PORT_VLAN_NATIVE_UNTAGGED:
+ return vlan == out_bundle->vlan ? 0 : vlan;
+
+ default:
+ NOT_REACHED();
+ }
+}
+
+static void
+output_normal(struct action_xlate_ctx *ctx, const struct ofbundle *out_bundle,
+ uint16_t vlan)
+{
+ struct ofport_dpif *port;
+ uint16_t vid;
+ ovs_be16 tci, old_tci;
+
+ vid = output_vlan_to_vid(out_bundle, vlan);
+ if (!out_bundle->bond) {
+ port = ofbundle_get_a_port(out_bundle);
+ } else {
+ port = bond_choose_output_slave(out_bundle->bond, &ctx->flow,
+ vid, &ctx->tags);
+ if (!port) {
+ /* No slaves enabled, so drop packet. */
+ return;
+ }
+ }
+
+ old_tci = ctx->flow.vlan_tci;
+ tci = htons(vid);
+ if (tci || out_bundle->use_priority_tags) {
+ tci |= ctx->flow.vlan_tci & htons(VLAN_PCP_MASK);
+ if (tci) {
+ tci |= htons(VLAN_CFI);
+ }
+ }
+ ctx->flow.vlan_tci = tci;
+
+ compose_output_action(ctx, port->up.ofp_port);
+ ctx->flow.vlan_tci = old_tci;
+}
+
+static int
+mirror_mask_ffs(mirror_mask_t mask)
+{
+ BUILD_ASSERT_DECL(sizeof(unsigned int) >= sizeof(mask));
+ return ffs(mask);
+}
+
+static bool
+ofbundle_trunks_vlan(const struct ofbundle *bundle, uint16_t vlan)
+{
+ return (bundle->vlan_mode != PORT_VLAN_ACCESS
+ && (!bundle->trunks || bitmap_is_set(bundle->trunks, vlan)));
+}
+
+static bool
+ofbundle_includes_vlan(const struct ofbundle *bundle, uint16_t vlan)
+{
+ return vlan == bundle->vlan || ofbundle_trunks_vlan(bundle, vlan);
+}
+
+/* Returns an arbitrary interface within 'bundle'. */
+static struct ofport_dpif *
+ofbundle_get_a_port(const struct ofbundle *bundle)
+{
+ return CONTAINER_OF(list_front(&bundle->ports),
+ struct ofport_dpif, bundle_node);
+}
+
+static bool
+vlan_is_mirrored(const struct ofmirror *m, int vlan)
+{
+ return !m->vlans || bitmap_is_set(m->vlans, vlan);
+}
+
+static void
+add_mirror_actions(struct action_xlate_ctx *ctx, const struct flow *orig_flow)
+{
+ struct ofproto_dpif *ofproto = ctx->ofproto;
+ mirror_mask_t mirrors;
+ struct ofbundle *in_bundle;
+ uint16_t vlan;
+ uint16_t vid;
+ const struct nlattr *a;
+ size_t left;
+
+ in_bundle = lookup_input_bundle(ctx->ofproto, orig_flow->in_port,
+ ctx->packet != NULL, NULL);
+ if (!in_bundle) {
+ return;
+ }
+ mirrors = in_bundle->src_mirrors;
+
+ /* Drop frames on bundles reserved for mirroring. */
+ if (in_bundle->mirror_out) {
+ if (ctx->packet != NULL) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "bridge %s: dropping packet received on port "
+ "%s, which is reserved exclusively for mirroring",
+ ctx->ofproto->up.name, in_bundle->name);
+ }
+ return;
+ }
+
+ /* Check VLAN. */
+ vid = vlan_tci_to_vid(orig_flow->vlan_tci);
+ if (!input_vid_is_valid(vid, in_bundle, ctx->packet != NULL)) {
+ return;
+ }
+ vlan = input_vid_to_vlan(in_bundle, vid);
+
+ /* Look at the output ports to check for destination selections. */
+
+ NL_ATTR_FOR_EACH (a, left, ctx->odp_actions->data,
+ ctx->odp_actions->size) {
+ enum ovs_action_attr type = nl_attr_type(a);
+ struct ofport_dpif *ofport;
+
+ if (type != OVS_ACTION_ATTR_OUTPUT) {
+ continue;
+ }
+
+ ofport = get_odp_port(ofproto, nl_attr_get_u32(a));
+ if (ofport && ofport->bundle) {
+ mirrors |= ofport->bundle->dst_mirrors;
+ }
+ }
+
+ if (!mirrors) {
+ return;
+ }
+
+ /* Restore the original packet before adding the mirror actions. */
+ ctx->flow = *orig_flow;
+
+ while (mirrors) {
+ struct ofmirror *m;
+
+ m = ofproto->mirrors[mirror_mask_ffs(mirrors) - 1];
+
+ if (!vlan_is_mirrored(m, vlan)) {
+ mirrors = zero_rightmost_1bit(mirrors);
+ continue;
+ }
+
+ mirrors &= ~m->dup_mirrors;
+ ctx->mirrors |= m->dup_mirrors;
+ if (m->out) {
+ output_normal(ctx, m->out, vlan);
+ } else if (vlan != m->out_vlan
+ && !eth_addr_is_reserved(orig_flow->dl_dst)) {
+ struct ofbundle *bundle;
+
+ HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
+ if (ofbundle_includes_vlan(bundle, m->out_vlan)
+ && !bundle->mirror_out) {
+ output_normal(ctx, bundle, m->out_vlan);
+ }
+ }
+ }
+ }
+}
+
+static void
+update_mirror_stats(struct ofproto_dpif *ofproto, mirror_mask_t mirrors,
+ uint64_t packets, uint64_t bytes)
+{
+ if (!mirrors) {
+ return;
+ }
+
+ for (; mirrors; mirrors = zero_rightmost_1bit(mirrors)) {
+ struct ofmirror *m;
+
+ m = ofproto->mirrors[mirror_mask_ffs(mirrors) - 1];
+
+ if (!m) {
+ /* In normal circumstances 'm' will not be NULL. However,
+ * if mirrors are reconfigured, we can temporarily get out
+ * of sync in facet_revalidate(). We could "correct" the
+ * mirror list before reaching here, but doing that would
+ * not properly account the traffic stats we've currently
+ * accumulated for previous mirror configuration. */
+ continue;
+ }
+
+ m->packet_count += packets;
+ m->byte_count += bytes;
+ }
+}
+
+/* A VM broadcasts a gratuitous ARP to indicate that it has resumed after
+ * migration. Older Citrix-patched Linux DomU used gratuitous ARP replies to
+ * indicate this; newer upstream kernels use gratuitous ARP requests. */
+static bool
+is_gratuitous_arp(const struct flow *flow)
+{
+ return (flow->dl_type == htons(ETH_TYPE_ARP)
+ && eth_addr_is_broadcast(flow->dl_dst)
+ && (flow->nw_proto == ARP_OP_REPLY
+ || (flow->nw_proto == ARP_OP_REQUEST
+ && flow->nw_src == flow->nw_dst)));
+}
+
+static void
+update_learning_table(struct ofproto_dpif *ofproto,
+ const struct flow *flow, int vlan,
+ struct ofbundle *in_bundle)
+{
+ struct mac_entry *mac;
+
+ /* Don't learn the OFPP_NONE port. */
+ if (in_bundle == &ofpp_none_bundle) {
+ return;
+ }
+
+ if (!mac_learning_may_learn(ofproto->ml, flow->dl_src, vlan)) {
+ return;
+ }
+
+ mac = mac_learning_insert(ofproto->ml, flow->dl_src, vlan);
+ if (is_gratuitous_arp(flow)) {
+ /* We don't want to learn from gratuitous ARP packets that are
+ * reflected back over bond slaves so we lock the learning table. */
+ if (!in_bundle->bond) {
+ mac_entry_set_grat_arp_lock(mac);
+ } else if (mac_entry_is_grat_arp_locked(mac)) {
+ return;
+ }
+ }
+
+ if (mac_entry_is_new(mac) || mac->port.p != in_bundle) {
+ /* The log messages here could actually be useful in debugging,
+ * so keep the rate limit relatively high. */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
+ VLOG_DBG_RL(&rl, "bridge %s: learned that "ETH_ADDR_FMT" is "
+ "on port %s in VLAN %d",
+ ofproto->up.name, ETH_ADDR_ARGS(flow->dl_src),
+ in_bundle->name, vlan);
+
+ mac->port.p = in_bundle;
+ tag_set_add(&ofproto->revalidate_set,
+ mac_learning_changed(ofproto->ml, mac));
+ }
+}
+
+static struct ofbundle *
+lookup_input_bundle(const struct ofproto_dpif *ofproto, uint16_t in_port,
+ bool warn, struct ofport_dpif **in_ofportp)
+{
+ struct ofport_dpif *ofport;
+
+ /* 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
+ * from it.
+ *
+ * - Someone externally added a port (e.g. "ovs-dpctl add-if") that
+ * we don't know about.
+ *
+ * - The ofproto client didn't configure the port as part of a bundle.
+ * This is particularly likely to happen if a packet was received on the
+ * port after it was created, but before the client had a chance to
+ * configure its bundle.
+ */
+ if (warn) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+ VLOG_WARN_RL(&rl, "bridge %s: received packet on unknown "
+ "port %"PRIu16, ofproto->up.name, in_port);
+ }
+ return NULL;
+}
+
+/* Determines whether packets in 'flow' within 'ofproto' should be forwarded or
+ * dropped. Returns true if they may be forwarded, false if they should be
+ * dropped.
+ *
+ * 'in_port' must be the ofport_dpif that corresponds to flow->in_port.
+ * 'in_port' must be part of a bundle (e.g. in_port->bundle must be nonnull).
+ *
+ * 'vlan' must be the VLAN that corresponds to flow->vlan_tci on 'in_port', as
+ * returned by input_vid_to_vlan(). It must be a valid VLAN for 'in_port', as
+ * checked by input_vid_is_valid().
+ *
+ * May also add tags to '*tags', although the current implementation only does
+ * so in one special case.
+ */
+static bool
+is_admissible(struct action_xlate_ctx *ctx, struct ofport_dpif *in_port,
+ uint16_t vlan)
+{
+ struct ofproto_dpif *ofproto = ctx->ofproto;
+ struct flow *flow = &ctx->flow;
+ struct ofbundle *in_bundle = in_port->bundle;
+
+ /* Drop frames for reserved multicast addresses
+ * only if forward_bpdu option is absent. */
+ if (!ofproto->up.forward_bpdu && eth_addr_is_reserved(flow->dl_dst)) {
+ xlate_report(ctx, "packet has reserved destination MAC, dropping");
+ return false;
+ }
+
+ if (in_bundle->bond) {
+ struct mac_entry *mac;
+
+ switch (bond_check_admissibility(in_bundle->bond, in_port,
+ flow->dl_dst, &ctx->tags)) {
+ case BV_ACCEPT:
+ break;
+
+ case BV_DROP:
+ xlate_report(ctx, "bonding refused admissibility, dropping");
+ return false;
+
+ case BV_DROP_IF_MOVED:
+ mac = mac_learning_lookup(ofproto->ml, flow->dl_src, vlan, NULL);
+ if (mac && mac->port.p != in_bundle &&
+ (!is_gratuitous_arp(flow)
+ || mac_entry_is_grat_arp_locked(mac))) {
+ xlate_report(ctx, "SLB bond thinks this packet looped back, "
+ "dropping");
+ return false;
+ }
+ break;
+ }
+ }
+
+ return true;
+}
+
+static void
+xlate_normal(struct action_xlate_ctx *ctx)
+{
+ struct ofport_dpif *in_port;
+ struct ofbundle *in_bundle;
+ struct mac_entry *mac;
+ uint16_t vlan;
+ uint16_t vid;
+
+ ctx->has_normal = true;
+
+ in_bundle = lookup_input_bundle(ctx->ofproto, ctx->flow.in_port,
+ ctx->packet != NULL, &in_port);
+ if (!in_bundle) {
+ xlate_report(ctx, "no input bundle, dropping");
+ return;
+ }
+
+ /* Drop malformed frames. */
+ if (ctx->flow.dl_type == htons(ETH_TYPE_VLAN) &&
+ !(ctx->flow.vlan_tci & htons(VLAN_CFI))) {
+ if (ctx->packet != NULL) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "bridge %s: dropping packet with partial "
+ "VLAN tag received on port %s",
+ ctx->ofproto->up.name, in_bundle->name);
+ }
+ xlate_report(ctx, "partial VLAN tag, dropping");
+ return;
+ }
+
+ /* Drop frames on bundles reserved for mirroring. */
+ if (in_bundle->mirror_out) {
+ if (ctx->packet != NULL) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "bridge %s: dropping packet received on port "
+ "%s, which is reserved exclusively for mirroring",
+ ctx->ofproto->up.name, in_bundle->name);
+ }
+ xlate_report(ctx, "input port is mirror output port, dropping");
+ return;
+ }
+
+ /* Check VLAN. */
+ vid = vlan_tci_to_vid(ctx->flow.vlan_tci);
+ if (!input_vid_is_valid(vid, in_bundle, ctx->packet != NULL)) {
+ xlate_report(ctx, "disallowed VLAN VID for this input port, dropping");
+ return;
+ }
+ vlan = input_vid_to_vlan(in_bundle, vid);
+
+ /* Check other admissibility requirements. */
+ if (in_port && !is_admissible(ctx, in_port, vlan)) {
+ return;
+ }
+
+ /* Learn source MAC. */
+ if (ctx->may_learn) {
+ update_learning_table(ctx->ofproto, &ctx->flow, vlan, in_bundle);
+ }
+
+ /* Determine output bundle. */
+ mac = mac_learning_lookup(ctx->ofproto->ml, ctx->flow.dl_dst, vlan,
+ &ctx->tags);
+ if (mac) {
+ if (mac->port.p != in_bundle) {
+ xlate_report(ctx, "forwarding to learned port");
+ output_normal(ctx, mac->port.p, vlan);
+ } else {
+ xlate_report(ctx, "learned port is input port, dropping");
+ }
+ } else {
+ struct ofbundle *bundle;
+
+ xlate_report(ctx, "no learned MAC for destination, flooding");
+ HMAP_FOR_EACH (bundle, hmap_node, &ctx->ofproto->bundles) {
+ if (bundle != in_bundle
+ && ofbundle_includes_vlan(bundle, vlan)
+ && bundle->floodable
+ && !bundle->mirror_out) {
+ output_normal(ctx, bundle, vlan);
+ }
+ }
+ ctx->nf_output_iface = NF_OUT_FLOOD;
+ }
+}
+\f
+/* Optimized flow revalidation.
+ *
+ * It's a difficult problem, in general, to tell which facets need to have
+ * their actions recalculated whenever the OpenFlow flow table changes. We
+ * don't try to solve that general problem: for most kinds of OpenFlow flow
+ * table changes, we recalculate the actions for every facet. This is
+ * relatively expensive, but it's good enough if the OpenFlow flow table
+ * doesn't change very often.
+ *
+ * However, we can expect one particular kind of OpenFlow flow table change to
+ * happen frequently: changes caused by MAC learning. To avoid wasting a lot
+ * of CPU on revalidating every facet whenever MAC learning modifies the flow
+ * table, we add a special case that applies to flow tables in which every rule
+ * has the same form (that is, the same wildcards), except that the table is
+ * also allowed to have a single "catch-all" flow that matches all packets. We
+ * optimize this case by tagging all of the facets that resubmit into the table
+ * and invalidating the same tag whenever a flow changes in that table. The
+ * end result is that we revalidate just the facets that need it (and sometimes
+ * a few more, but not all of the facets or even all of the facets that
+ * resubmit to the table modified by MAC learning). */
+
+/* Calculates the tag to use for 'flow' and mask 'mask' when it is inserted
+ * into an OpenFlow table with the given 'basis'. */
+static tag_type
+rule_calculate_tag(const struct flow *flow, const struct minimask *mask,
+ uint32_t secret)
+{
+ if (minimask_is_catchall(mask)) {
+ return 0;
+ } else {
+ uint32_t hash = flow_hash_in_minimask(flow, mask, secret);
+ return tag_create_deterministic(hash);
+ }
+}
+
+/* Following a change to OpenFlow table 'table_id' in 'ofproto', update the
+ * taggability of that table.
+ *
+ * This function must be called after *each* change to a flow table. If you
+ * skip calling it on some changes then the pointer comparisons at the end can
+ * be invalid if you get unlucky. For example, if a flow removal causes a
+ * cls_table to be destroyed and then a flow insertion causes a cls_table with
+ * different wildcards to be created with the same address, then this function
+ * will incorrectly skip revalidation. */
+static void
+table_update_taggable(struct ofproto_dpif *ofproto, uint8_t table_id)
+{
+ struct table_dpif *table = &ofproto->tables[table_id];
+ const struct oftable *oftable = &ofproto->up.tables[table_id];
+ struct cls_table *catchall, *other;
+ struct cls_table *t;
+
+ catchall = other = NULL;
+
+ switch (hmap_count(&oftable->cls.tables)) {
+ case 0:
+ /* We could tag this OpenFlow table but it would make the logic a
+ * little harder and it's a corner case that doesn't seem worth it
+ * yet. */
+ break;
+
+ case 1:
+ case 2:
+ HMAP_FOR_EACH (t, hmap_node, &oftable->cls.tables) {
+ if (cls_table_is_catchall(t)) {
+ catchall = t;
+ } else if (!other) {
+ other = t;
+ } else {
+ /* Indicate that we can't tag this by setting both tables to
+ * NULL. (We know that 'catchall' is already NULL.) */
+ other = NULL;
+ }
+ }
+ break;
+
+ default:
+ /* Can't tag this table. */
+ break;
+ }
+
+ if (table->catchall_table != catchall || table->other_table != other) {
+ table->catchall_table = catchall;
+ table->other_table = other;
+ ofproto->need_revalidate = REV_FLOW_TABLE;
+ }
+}
+
+/* Given 'rule' that has changed in some way (either it is a rule being
+ * inserted, a rule being deleted, or a rule whose actions are being
+ * modified), marks facets for revalidation to ensure that packets will be
+ * forwarded correctly according to the new state of the flow table.
+ *
+ * This function must be called after *each* change to a flow table. See
+ * the comment on table_update_taggable() for more information. */
+static void
+rule_invalidate(const struct rule_dpif *rule)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
+
+ table_update_taggable(ofproto, rule->up.table_id);
+
+ if (!ofproto->need_revalidate) {
+ struct table_dpif *table = &ofproto->tables[rule->up.table_id];
+
+ if (table->other_table && rule->tag) {
+ tag_set_add(&ofproto->revalidate_set, rule->tag);
+ } else {
+ ofproto->need_revalidate = REV_FLOW_TABLE;
+ }
+ }
+}
+\f
+static bool
+set_frag_handling(struct ofproto *ofproto_,
+ enum ofp_config_flags frag_handling)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+ if (frag_handling != OFPC_FRAG_REASM) {
+ ofproto->need_revalidate = REV_RECONFIGURE;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static enum ofperr
+packet_out(struct ofproto *ofproto_, struct ofpbuf *packet,
+ const struct flow *flow,
+ const struct ofpact *ofpacts, size_t ofpacts_len)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+ struct odputil_keybuf keybuf;
+ struct dpif_flow_stats stats;
+
+ struct ofpbuf key;
+
+ struct action_xlate_ctx ctx;
+ uint64_t odp_actions_stub[1024 / 8];
+ struct ofpbuf odp_actions;
+
+ ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
+ odp_flow_key_from_flow(&key, flow,
+ ofp_port_to_odp_port(ofproto, flow->in_port));
+
+ dpif_flow_stats_extract(flow, packet, time_msec(), &stats);
+
+ action_xlate_ctx_init(&ctx, ofproto, flow, flow->vlan_tci, NULL,
+ packet_get_tcp_flags(packet, flow), packet);
+ ctx.resubmit_stats = &stats;
+
+ ofpbuf_use_stub(&odp_actions,
+ odp_actions_stub, sizeof odp_actions_stub);
+ xlate_actions(&ctx, ofpacts, ofpacts_len, &odp_actions);
+ dpif_execute(ofproto->backer->dpif, key.data, key.size,
+ odp_actions.data, odp_actions.size, packet);
+ ofpbuf_uninit(&odp_actions);
+
+ return 0;
+}
+\f
+/* NetFlow. */
+
+static int
+set_netflow(struct ofproto *ofproto_,
+ const struct netflow_options *netflow_options)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+ if (netflow_options) {
+ if (!ofproto->netflow) {
+ ofproto->netflow = netflow_create();
+ }
+ return netflow_set_options(ofproto->netflow, netflow_options);
+ } else {
+ netflow_destroy(ofproto->netflow);
+ ofproto->netflow = NULL;
+ return 0;
+ }
+}
+
+static void
+get_netflow_ids(const struct ofproto *ofproto_,
+ uint8_t *engine_type, uint8_t *engine_id)
+{
+ struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+ dpif_get_netflow_ids(ofproto->backer->dpif, engine_type, engine_id);
+}
+
+static void
+send_active_timeout(struct ofproto_dpif *ofproto, struct facet *facet)
+{
+ if (!facet_is_controller_flow(facet) &&
+ netflow_active_timeout_expired(ofproto->netflow, &facet->nf_flow)) {
+ struct subfacet *subfacet;
+ struct ofexpired expired;
+
+ LIST_FOR_EACH (subfacet, list_node, &facet->subfacets) {
+ if (subfacet->path == SF_FAST_PATH) {
+ struct dpif_flow_stats stats;
+
+ subfacet_reinstall(subfacet, &stats);
+ subfacet_update_stats(subfacet, &stats);
+ }
+ }
+
+ expired.flow = facet->flow;
+ expired.packet_count = facet->packet_count;
+ expired.byte_count = facet->byte_count;
+ expired.used = facet->used;
+ netflow_expire(ofproto->netflow, &facet->nf_flow, &expired);
+ }
+}
+
+static void
+send_netflow_active_timeouts(struct ofproto_dpif *ofproto)
+{
+ struct facet *facet;
+
+ HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
+ send_active_timeout(ofproto, facet);
+ }
+}
+\f
+static struct ofproto_dpif *
+ofproto_dpif_lookup(const char *name)
+{
+ struct ofproto_dpif *ofproto;
+
+ HMAP_FOR_EACH_WITH_HASH (ofproto, all_ofproto_dpifs_node,
+ hash_string(name, 0), &all_ofproto_dpifs) {
+ if (!strcmp(ofproto->up.name, name)) {
+ return ofproto;
+ }
+ }
+ return NULL;
+}
+
+static void
+ofproto_unixctl_fdb_flush(struct unixctl_conn *conn, int argc,
+ const char *argv[], void *aux OVS_UNUSED)
+{
+ struct ofproto_dpif *ofproto;
+
+ if (argc > 1) {
+ ofproto = ofproto_dpif_lookup(argv[1]);
+ if (!ofproto) {
+ unixctl_command_reply_error(conn, "no such bridge");
+ return;
+ }
+ mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+ } else {
+ HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
+ mac_learning_flush(ofproto->ml, &ofproto->revalidate_set);
+ }
+ }
+
+ unixctl_command_reply(conn, "table successfully flushed");
+}
+
+static void
+ofproto_unixctl_fdb_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[], void *aux OVS_UNUSED)
+{
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ const struct ofproto_dpif *ofproto;
+ const struct mac_entry *e;
+
+ ofproto = ofproto_dpif_lookup(argv[1]);
+ if (!ofproto) {
+ unixctl_command_reply_error(conn, "no such bridge");
+ return;
+ }
+
+ ds_put_cstr(&ds, " port VLAN MAC Age\n");
+ LIST_FOR_EACH (e, lru_node, &ofproto->ml->lrus) {
+ struct ofbundle *bundle = e->port.p;
+ ds_put_format(&ds, "%5d %4d "ETH_ADDR_FMT" %3d\n",
+ ofbundle_get_a_port(bundle)->odp_port,
+ e->vlan, ETH_ADDR_ARGS(e->mac),
+ mac_entry_age(ofproto->ml, e));
+ }
+ unixctl_command_reply(conn, ds_cstr(&ds));
+ ds_destroy(&ds);
+}
+
+struct trace_ctx {
+ struct action_xlate_ctx ctx;
+ struct flow flow;
+ struct ds *result;
+};
+
+static void
+trace_format_rule(struct ds *result, uint8_t table_id, int level,
+ const struct rule_dpif *rule)
+{
+ ds_put_char_multiple(result, '\t', level);
+ if (!rule) {
+ ds_put_cstr(result, "No match\n");
+ return;
+ }
+
+ ds_put_format(result, "Rule: table=%"PRIu8" cookie=%#"PRIx64" ",
+ table_id, ntohll(rule->up.flow_cookie));
+ cls_rule_format(&rule->up.cr, result);
+ ds_put_char(result, '\n');
+
+ ds_put_char_multiple(result, '\t', level);
+ ds_put_cstr(result, "OpenFlow ");
+ ofpacts_format(rule->up.ofpacts, rule->up.ofpacts_len, result);
+ ds_put_char(result, '\n');
+}
+
+static void
+trace_format_flow(struct ds *result, int level, const char *title,
+ struct trace_ctx *trace)
+{
+ ds_put_char_multiple(result, '\t', level);
+ ds_put_format(result, "%s: ", title);
+ if (flow_equal(&trace->ctx.flow, &trace->flow)) {
+ ds_put_cstr(result, "unchanged");
+ } else {
+ flow_format(result, &trace->ctx.flow);
+ trace->flow = trace->ctx.flow;
+ }
+ ds_put_char(result, '\n');
+}
+
+static void
+trace_format_regs(struct ds *result, int level, const char *title,
+ struct trace_ctx *trace)
+{
+ size_t i;
+
+ ds_put_char_multiple(result, '\t', level);
+ ds_put_format(result, "%s:", title);
+ for (i = 0; i < FLOW_N_REGS; i++) {
+ ds_put_format(result, " reg%zu=0x%"PRIx32, i, trace->flow.regs[i]);
+ }
+ ds_put_char(result, '\n');
+}
+
+static void
+trace_format_odp(struct ds *result, int level, const char *title,
+ struct trace_ctx *trace)
+{
+ struct ofpbuf *odp_actions = trace->ctx.odp_actions;
+
+ ds_put_char_multiple(result, '\t', level);
+ ds_put_format(result, "%s: ", title);
+ format_odp_actions(result, odp_actions->data, odp_actions->size);
+ ds_put_char(result, '\n');
+}
+
+static void
+trace_resubmit(struct action_xlate_ctx *ctx, struct rule_dpif *rule)
+{
+ struct trace_ctx *trace = CONTAINER_OF(ctx, struct trace_ctx, ctx);
+ struct ds *result = trace->result;
+
+ ds_put_char(result, '\n');
+ trace_format_flow(result, ctx->recurse + 1, "Resubmitted flow", trace);
+ trace_format_regs(result, ctx->recurse + 1, "Resubmitted regs", trace);
+ trace_format_odp(result, ctx->recurse + 1, "Resubmitted odp", trace);
+ trace_format_rule(result, ctx->table_id, ctx->recurse + 1, rule);
+}
+
+static void
+trace_report(struct action_xlate_ctx *ctx, const char *s)
+{
+ struct trace_ctx *trace = CONTAINER_OF(ctx, struct trace_ctx, ctx);
+ struct ds *result = trace->result;
+
+ ds_put_char_multiple(result, '\t', ctx->recurse);
+ ds_put_cstr(result, s);
+ ds_put_char(result, '\n');
+}
+
+static void
+ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
+ void *aux OVS_UNUSED)
+{
+ const char *dpname = argv[1];
+ struct ofproto_dpif *ofproto;
+ struct ofpbuf odp_key;
+ struct ofpbuf *packet;
+ ovs_be16 initial_tci;
+ struct ds result;
+ struct flow flow;
+ char *s;
+
+ packet = NULL;
+ ofpbuf_init(&odp_key, 0);
+ ds_init(&result);
+
+ ofproto = ofproto_dpif_lookup(dpname);
+ if (!ofproto) {
+ unixctl_command_reply_error(conn, "Unknown ofproto (use ofproto/list "
+ "for help)");
+ goto exit;
+ }
+ if (argc == 3 || (argc == 4 && !strcmp(argv[3], "-generate"))) {
+ /* ofproto/trace dpname flow [-generate] */
+ const char *flow_s = argv[2];
+ const char *generate_s = argv[3];
+
+ /* Allow 'flow_s' to be either a datapath flow or an OpenFlow-like
+ * flow. We guess which type it is based on whether 'flow_s' contains
+ * an '(', since a datapath flow always contains '(') but an
+ * OpenFlow-like flow should not (in fact it's allowed but I believe
+ * that's not documented anywhere).
+ *
+ * An alternative would be to try to parse 'flow_s' both ways, but then
+ * it would be tricky giving a sensible error message. After all, do
+ * you just say "syntax error" or do you present both error messages?
+ * Both choices seem lousy. */
+ if (strchr(flow_s, '(')) {
+ enum odp_key_fitness fitness;
+ int error;
+
+ /* Convert string to datapath key. */
+ ofpbuf_init(&odp_key, 0);
+ error = odp_flow_key_from_string(flow_s, NULL, &odp_key);
+ if (error) {
+ unixctl_command_reply_error(conn, "Bad flow syntax");
+ goto exit;
+ }
+
+ fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
+ flow.in_port = odp_port_to_ofp_port(ofproto, flow.in_port);
+
+ /* Convert odp_key to flow. */
+ error = ofproto_dpif_vsp_adjust(ofproto, fitness, &flow,
+ &initial_tci, NULL);
+ if (error == ODP_FIT_ERROR) {
+ unixctl_command_reply_error(conn, "Invalid flow");
+ goto exit;
+ }
+ } else {
+ char *error_s;
+
+ error_s = parse_ofp_exact_flow(&flow, argv[2]);
+ if (error_s) {
+ unixctl_command_reply_error(conn, error_s);
+ free(error_s);
+ goto exit;
+ }
+
+ initial_tci = flow.vlan_tci;
+ vsp_adjust_flow(ofproto, &flow);
+ }
+
+ /* Generate a packet, if requested. */
+ if (generate_s) {
+ packet = ofpbuf_new(0);
+ flow_compose(packet, &flow);
+ }
+ } else if (argc == 6) {
+ /* ofproto/trace dpname priority tun_id in_port packet */
+ const char *priority_s = argv[2];
+ const char *tun_id_s = argv[3];
+ const char *in_port_s = argv[4];
+ const char *packet_s = argv[5];
+ uint32_t in_port = atoi(in_port_s);
+ ovs_be64 tun_id = htonll(strtoull(tun_id_s, NULL, 0));
+ uint32_t priority = atoi(priority_s);
+ const char *msg;
+
+ msg = eth_from_hex(packet_s, &packet);
+ if (msg) {
+ unixctl_command_reply_error(conn, msg);
+ goto exit;
+ }
+
+ ds_put_cstr(&result, "Packet: ");
+ s = ofp_packet_to_string(packet->data, packet->size);
+ ds_put_cstr(&result, s);
+ free(s);
+
+ flow_extract(packet, priority, NULL, in_port, &flow);
+ flow.tunnel.tun_id = tun_id;
+ initial_tci = flow.vlan_tci;
+ } else {
+ unixctl_command_reply_error(conn, "Bad command syntax");
+ goto exit;
+ }
+
+ ofproto_trace(ofproto, &flow, packet, initial_tci, &result);
+ unixctl_command_reply(conn, ds_cstr(&result));
+
+exit:
+ ds_destroy(&result);
+ ofpbuf_delete(packet);
+ ofpbuf_uninit(&odp_key);
+}
+
+static void
+ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
+ const struct ofpbuf *packet, ovs_be16 initial_tci,
+ struct ds *ds)
+{
+ struct rule_dpif *rule;
+
+ ds_put_cstr(ds, "Flow: ");
+ flow_format(ds, flow);
+ ds_put_char(ds, '\n');
+
+ rule = rule_dpif_lookup(ofproto, flow);
+
+ trace_format_rule(ds, 0, 0, rule);
+ if (rule == ofproto->miss_rule) {
+ ds_put_cstr(ds, "\nNo match, flow generates \"packet in\"s.\n");
+ } else if (rule == ofproto->no_packet_in_rule) {
+ ds_put_cstr(ds, "\nNo match, packets dropped because "
+ "OFPPC_NO_PACKET_IN is set on in_port.\n");
+ }
+
+ if (rule) {
+ uint64_t odp_actions_stub[1024 / 8];
+ struct ofpbuf odp_actions;
+
+ struct trace_ctx trace;
+ uint8_t tcp_flags;
+
+ tcp_flags = packet ? packet_get_tcp_flags(packet, flow) : 0;
+ trace.result = ds;
+ trace.flow = *flow;
+ ofpbuf_use_stub(&odp_actions,
+ odp_actions_stub, sizeof odp_actions_stub);
+ action_xlate_ctx_init(&trace.ctx, ofproto, flow, initial_tci,
+ rule, tcp_flags, packet);
+ trace.ctx.resubmit_hook = trace_resubmit;
+ trace.ctx.report_hook = trace_report;
+ xlate_actions(&trace.ctx, rule->up.ofpacts, rule->up.ofpacts_len,
+ &odp_actions);
+
+ ds_put_char(ds, '\n');
+ trace_format_flow(ds, 0, "Final flow", &trace);
+ ds_put_cstr(ds, "Datapath actions: ");
+ format_odp_actions(ds, odp_actions.data, odp_actions.size);
+ ofpbuf_uninit(&odp_actions);
+
+ if (trace.ctx.slow) {
+ enum slow_path_reason slow;
+
+ ds_put_cstr(ds, "\nThis flow is handled by the userspace "
+ "slow path because it:");
+ for (slow = trace.ctx.slow; slow; ) {
+ enum slow_path_reason bit = rightmost_1bit(slow);
+
+ switch (bit) {
+ case SLOW_CFM:
+ ds_put_cstr(ds, "\n\t- Consists of CFM packets.");
+ break;
+ case SLOW_LACP:
+ ds_put_cstr(ds, "\n\t- Consists of LACP packets.");
+ break;
+ case SLOW_STP:
+ ds_put_cstr(ds, "\n\t- Consists of STP packets.");
+ break;
+ case SLOW_IN_BAND:
+ ds_put_cstr(ds, "\n\t- Needs in-band special case "
+ "processing.");
+ if (!packet) {
+ ds_put_cstr(ds, "\n\t (The datapath actions are "
+ "incomplete--for complete actions, "
+ "please supply a packet.)");
+ }
+ break;
+ case SLOW_CONTROLLER:
+ ds_put_cstr(ds, "\n\t- Sends \"packet-in\" messages "
+ "to the OpenFlow controller.");
+ break;
+ case SLOW_MATCH:
+ ds_put_cstr(ds, "\n\t- Needs more specific matching "
+ "than the datapath supports.");
+ break;
+ }
+
+ slow &= ~bit;
+ }
+
+ if (slow & ~SLOW_MATCH) {
+ ds_put_cstr(ds, "\nThe datapath actions above do not reflect "
+ "the special slow-path processing.");
+ }