+static void
+xlate_set_queue_action(struct action_xlate_ctx *ctx, uint32_t queue_id)
+{
+ uint32_t skb_priority;
+
+ if (!dpif_queue_to_priority(ctx->ofproto->backer->dpif,
+ queue_id, &skb_priority)) {
+ ctx->flow.skb_priority = skb_priority;
+ } else {
+ /* Couldn't translate queue to a priority. Nothing to do. A warning
+ * has already been logged. */
+ }
+}
+
+struct xlate_reg_state {
+ ovs_be16 vlan_tci;
+ ovs_be64 tun_id;
+};
+
+static void
+xlate_autopath(struct action_xlate_ctx *ctx,
+ const struct ofpact_autopath *ap)
+{
+ uint16_t ofp_port = ap->port;
+ struct ofport_dpif *port = get_ofp_port(ctx->ofproto, ofp_port);
+
+ if (!port || !port->bundle) {
+ ofp_port = OFPP_NONE;
+ } else if (port->bundle->bond) {
+ /* Autopath does not support VLAN hashing. */
+ struct ofport_dpif *slave = bond_choose_output_slave(
+ port->bundle->bond, &ctx->flow, 0, &ctx->tags);
+ if (slave) {
+ ofp_port = slave->up.ofp_port;
+ }
+ }
+ nxm_reg_load(&ap->dst, ofp_port, &ctx->flow);
+}
+
+static bool
+slave_enabled_cb(uint16_t ofp_port, void *ofproto_)
+{
+ struct ofproto_dpif *ofproto = ofproto_;
+ struct ofport_dpif *port;
+
+ switch (ofp_port) {
+ case OFPP_IN_PORT:
+ case OFPP_TABLE:
+ case OFPP_NORMAL:
+ case OFPP_FLOOD:
+ case OFPP_ALL:
+ case OFPP_NONE:
+ return true;
+ case OFPP_CONTROLLER: /* Not supported by the bundle action. */
+ return false;
+ default:
+ port = get_ofp_port(ofproto, ofp_port);
+ return port ? port->may_enable : false;
+ }
+}
+
+static void
+xlate_bundle_action(struct action_xlate_ctx *ctx,
+ const struct ofpact_bundle *bundle)
+{
+ uint16_t port;
+
+ port = bundle_execute(bundle, &ctx->flow, slave_enabled_cb, ctx->ofproto);
+ if (bundle->dst.field) {
+ nxm_reg_load(&bundle->dst, port, &ctx->flow);
+ } else {
+ xlate_output_action(ctx, port, 0, false);
+ }
+}
+
+static void
+xlate_learn_action(struct action_xlate_ctx *ctx,
+ const struct ofpact_learn *learn)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ struct ofputil_flow_mod fm;
+ uint64_t ofpacts_stub[1024 / 8];
+ struct ofpbuf ofpacts;
+ int error;
+
+ ofpbuf_use_stack(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
+ learn_execute(learn, &ctx->flow, &fm, &ofpacts);
+
+ error = ofproto_flow_mod(&ctx->ofproto->up, &fm);
+ if (error && !VLOG_DROP_WARN(&rl)) {
+ VLOG_WARN("learning action failed to modify flow table (%s)",
+ ofperr_get_name(error));
+ }
+
+ ofpbuf_uninit(&ofpacts);
+}
+
+/* Reduces '*timeout' to no more than 'max'. A value of zero in either case
+ * means "infinite". */
+static void
+reduce_timeout(uint16_t max, uint16_t *timeout)
+{
+ if (max && (!*timeout || *timeout > max)) {
+ *timeout = max;
+ }
+}
+
+static void
+xlate_fin_timeout(struct action_xlate_ctx *ctx,
+ const struct ofpact_fin_timeout *oft)
+{
+ if (ctx->tcp_flags & (TCP_FIN | TCP_RST) && ctx->rule) {
+ struct rule_dpif *rule = ctx->rule;
+
+ reduce_timeout(oft->fin_idle_timeout, &rule->up.idle_timeout);
+ reduce_timeout(oft->fin_hard_timeout, &rule->up.hard_timeout);
+ }
+}
+
+static bool
+may_receive(const struct ofport_dpif *port, struct action_xlate_ctx *ctx)
+{
+ if (port->up.pp.config & (eth_addr_equals(ctx->flow.dl_dst, eth_addr_stp)
+ ? OFPUTIL_PC_NO_RECV_STP
+ : OFPUTIL_PC_NO_RECV)) {
+ return false;
+ }
+
+ /* Only drop packets here if both forwarding and learning are
+ * disabled. If just learning is enabled, we need to have
+ * OFPP_NORMAL and the learning action have a look at the packet
+ * before we can drop it. */
+ if (!stp_forward_in_state(port->stp_state)
+ && !stp_learn_in_state(port->stp_state)) {
+ return false;
+ }
+
+ return true;
+}
+
+static void
+do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
+ struct action_xlate_ctx *ctx)
+{
+ const struct ofport_dpif *port;
+ bool was_evictable = true;
+ const struct ofpact *a;
+
+ port = get_ofp_port(ctx->ofproto, ctx->flow.in_port);
+ if (port && !may_receive(port, ctx)) {
+ /* Drop this flow. */
+ return;
+ }
+
+ if (ctx->rule) {
+ /* Don't let the rule we're working on get evicted underneath us. */
+ was_evictable = ctx->rule->up.evictable;
+ ctx->rule->up.evictable = false;
+ }
+ OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
+ struct ofpact_controller *controller;
+ const struct ofpact_metadata *metadata;
+
+ if (ctx->exit) {
+ break;
+ }
+
+ switch (a->type) {
+ case OFPACT_OUTPUT:
+ xlate_output_action(ctx, ofpact_get_OUTPUT(a)->port,
+ ofpact_get_OUTPUT(a)->max_len, true);
+ break;
+
+ case OFPACT_CONTROLLER:
+ controller = ofpact_get_CONTROLLER(a);
+ execute_controller_action(ctx, controller->max_len,
+ controller->reason,
+ controller->controller_id);
+ break;
+
+ case OFPACT_ENQUEUE:
+ xlate_enqueue_action(ctx, ofpact_get_ENQUEUE(a));
+ break;
+
+ case OFPACT_SET_VLAN_VID:
+ ctx->flow.vlan_tci &= ~htons(VLAN_VID_MASK);
+ ctx->flow.vlan_tci |= (htons(ofpact_get_SET_VLAN_VID(a)->vlan_vid)
+ | htons(VLAN_CFI));
+ break;
+
+ case OFPACT_SET_VLAN_PCP:
+ ctx->flow.vlan_tci &= ~htons(VLAN_PCP_MASK);
+ ctx->flow.vlan_tci |= htons((ofpact_get_SET_VLAN_PCP(a)->vlan_pcp
+ << VLAN_PCP_SHIFT)
+ | VLAN_CFI);
+ break;
+
+ case OFPACT_STRIP_VLAN:
+ ctx->flow.vlan_tci = htons(0);
+ break;
+
+ case OFPACT_PUSH_VLAN:
+ /* TODO:XXX 802.1AD(QinQ) */
+ ctx->flow.vlan_tci = htons(VLAN_CFI);
+ break;
+
+ case OFPACT_SET_ETH_SRC:
+ memcpy(ctx->flow.dl_src, ofpact_get_SET_ETH_SRC(a)->mac,
+ ETH_ADDR_LEN);
+ break;
+
+ case OFPACT_SET_ETH_DST:
+ memcpy(ctx->flow.dl_dst, ofpact_get_SET_ETH_DST(a)->mac,
+ ETH_ADDR_LEN);
+ break;
+
+ case OFPACT_SET_IPV4_SRC:
+ ctx->flow.nw_src = ofpact_get_SET_IPV4_SRC(a)->ipv4;
+ break;
+
+ case OFPACT_SET_IPV4_DST:
+ ctx->flow.nw_dst = ofpact_get_SET_IPV4_DST(a)->ipv4;
+ break;
+
+ case OFPACT_SET_IPV4_DSCP:
+ /* OpenFlow 1.0 only supports IPv4. */
+ if (ctx->flow.dl_type == htons(ETH_TYPE_IP)) {
+ ctx->flow.nw_tos &= ~IP_DSCP_MASK;
+ ctx->flow.nw_tos |= ofpact_get_SET_IPV4_DSCP(a)->dscp;
+ }
+ break;
+
+ case OFPACT_SET_L4_SRC_PORT:
+ ctx->flow.tp_src = htons(ofpact_get_SET_L4_SRC_PORT(a)->port);
+ break;
+
+ case OFPACT_SET_L4_DST_PORT:
+ ctx->flow.tp_dst = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
+ break;
+
+ case OFPACT_RESUBMIT:
+ xlate_ofpact_resubmit(ctx, ofpact_get_RESUBMIT(a));
+ break;
+
+ case OFPACT_SET_TUNNEL:
+ ctx->flow.tunnel.tun_id = htonll(ofpact_get_SET_TUNNEL(a)->tun_id);
+ break;
+
+ case OFPACT_SET_QUEUE:
+ xlate_set_queue_action(ctx, ofpact_get_SET_QUEUE(a)->queue_id);
+ break;
+
+ case OFPACT_POP_QUEUE:
+ ctx->flow.skb_priority = ctx->orig_skb_priority;
+ break;
+
+ case OFPACT_REG_MOVE:
+ nxm_execute_reg_move(ofpact_get_REG_MOVE(a), &ctx->flow);
+ break;
+
+ case OFPACT_REG_LOAD:
+ nxm_execute_reg_load(ofpact_get_REG_LOAD(a), &ctx->flow);
+ break;
+
+ case OFPACT_DEC_TTL:
+ if (compose_dec_ttl(ctx, ofpact_get_DEC_TTL(a))) {
+ goto out;
+ }
+ break;
+
+ case OFPACT_NOTE:
+ /* Nothing to do. */
+ break;
+
+ case OFPACT_MULTIPATH:
+ multipath_execute(ofpact_get_MULTIPATH(a), &ctx->flow);
+ break;
+
+ case OFPACT_AUTOPATH:
+ xlate_autopath(ctx, ofpact_get_AUTOPATH(a));
+ break;
+
+ case OFPACT_BUNDLE:
+ ctx->ofproto->has_bundle_action = true;
+ xlate_bundle_action(ctx, ofpact_get_BUNDLE(a));
+ break;
+
+ case OFPACT_OUTPUT_REG:
+ xlate_output_reg_action(ctx, ofpact_get_OUTPUT_REG(a));
+ break;
+
+ case OFPACT_LEARN:
+ ctx->has_learn = true;
+ if (ctx->may_learn) {
+ xlate_learn_action(ctx, ofpact_get_LEARN(a));
+ }
+ break;
+
+ 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