+static void
+recv_S_NEW(const struct ofp_header *oh OVS_UNUSED,
+ enum ofptype type OVS_UNUSED)
+{
+ OVS_NOT_REACHED();
+}
+\f
+/* S_TLV_TABLE_REQUESTED, when NXT_TLV_TABLE_REQUEST has been sent
+ * and we're waiting for a reply.
+ *
+ * If we receive an NXT_TLV_TABLE_REPLY:
+ *
+ * - If it contains our tunnel metadata option, assign its field ID to
+ * mff_ovn_geneve and transition to S_CLEAR_FLOWS.
+ *
+ * - Otherwise, if there is an unused tunnel metadata field ID, send
+ * NXT_TLV_TABLE_MOD and OFPT_BARRIER_REQUEST, and transition to
+ * S_TLV_TABLE_MOD_SENT.
+ *
+ * - Otherwise, log an error, disable Geneve, and transition to
+ * S_CLEAR_FLOWS.
+ *
+ * If we receive an OFPT_ERROR:
+ *
+ * - Log an error, disable Geneve, and transition to S_CLEAR_FLOWS. */
+
+static void
+run_S_TLV_TABLE_REQUESTED(void)
+{
+}
+
+static void
+recv_S_TLV_TABLE_REQUESTED(const struct ofp_header *oh, enum ofptype type)
+{
+ if (oh->xid != xid) {
+ ofctrl_recv(oh, type);
+ } else if (type == OFPTYPE_NXT_TLV_TABLE_REPLY) {
+ struct ofputil_tlv_table_reply reply;
+ enum ofperr error = ofputil_decode_tlv_table_reply(oh, &reply);
+ if (error) {
+ VLOG_ERR("failed to decode TLV table request (%s)",
+ ofperr_to_string(error));
+ goto error;
+ }
+
+ const struct ofputil_tlv_map *map;
+ uint64_t md_free = UINT64_MAX;
+ BUILD_ASSERT(TUN_METADATA_NUM_OPTS == 64);
+
+ LIST_FOR_EACH (map, list_node, &reply.mappings) {
+ if (map->option_class == OVN_GENEVE_CLASS
+ && map->option_type == OVN_GENEVE_TYPE
+ && map->option_len == OVN_GENEVE_LEN) {
+ if (map->index >= TUN_METADATA_NUM_OPTS) {
+ VLOG_ERR("desired Geneve tunnel option 0x%"PRIx16","
+ "%"PRIu8",%"PRIu8" already in use with "
+ "unsupported index %"PRIu16,
+ map->option_class, map->option_type,
+ map->option_len, map->index);
+ goto error;
+ } else {
+ mff_ovn_geneve = MFF_TUN_METADATA0 + map->index;
+ state = S_CLEAR_FLOWS;
+ return;
+ }
+ }
+
+ if (map->index < TUN_METADATA_NUM_OPTS) {
+ md_free &= ~(UINT64_C(1) << map->index);
+ }
+ }
+
+ VLOG_DBG("OVN Geneve option not found");
+ if (!md_free) {
+ VLOG_ERR("no Geneve options free for use by OVN");
+ goto error;
+ }
+
+ unsigned int index = rightmost_1bit_idx(md_free);
+ mff_ovn_geneve = MFF_TUN_METADATA0 + index;
+ struct ofputil_tlv_map tm;
+ tm.option_class = OVN_GENEVE_CLASS;
+ tm.option_type = OVN_GENEVE_TYPE;
+ tm.option_len = OVN_GENEVE_LEN;
+ tm.index = index;
+
+ struct ofputil_tlv_table_mod ttm;
+ ttm.command = NXTTMC_ADD;
+ list_init(&ttm.mappings);
+ list_push_back(&ttm.mappings, &tm.list_node);
+
+ xid = queue_msg(ofputil_encode_tlv_table_mod(OFP13_VERSION, &ttm));
+ xid2 = queue_msg(ofputil_encode_barrier_request(OFP13_VERSION));
+ state = S_TLV_TABLE_MOD_SENT;
+ } else if (type == OFPTYPE_ERROR) {
+ VLOG_ERR("switch refused to allocate Geneve option (%s)",
+ ofperr_to_string(ofperr_decode_msg(oh, NULL)));
+ goto error;
+ } else {
+ char *s = ofp_to_string(oh, ntohs(oh->length), 1);
+ VLOG_ERR("unexpected reply to TLV table request (%s)",
+ s);
+ free(s);
+ goto error;
+ }
+ return;
+
+error:
+ mff_ovn_geneve = 0;
+ state = S_CLEAR_FLOWS;
+}
+\f
+/* S_TLV_TABLE_MOD_SENT, when NXT_TLV_TABLE_MOD and OFPT_BARRIER_REQUEST
+ * have been sent and we're waiting for a reply to one or the other.
+ *
+ * If we receive an OFPT_ERROR:
+ *
+ * - If the error is NXTTMFC_ALREADY_MAPPED or NXTTMFC_DUP_ENTRY, we
+ * raced with some other controller. Transition to S_NEW.
+ *
+ * - Otherwise, log an error, disable Geneve, and transition to
+ * S_CLEAR_FLOWS.
+ *
+ * If we receive OFPT_BARRIER_REPLY:
+ *
+ * - Set the tunnel metadata field ID to the one that we requested.
+ * Transition to S_CLEAR_FLOWS.
+ */
+
+static void
+run_S_TLV_TABLE_MOD_SENT(void)
+{
+}
+
+static void
+recv_S_TLV_TABLE_MOD_SENT(const struct ofp_header *oh, enum ofptype type)
+{
+ if (oh->xid != xid && oh->xid != xid2) {
+ ofctrl_recv(oh, type);
+ } else if (oh->xid == xid2 && type == OFPTYPE_BARRIER_REPLY) {
+ state = S_CLEAR_FLOWS;
+ } else if (oh->xid == xid && type == OFPTYPE_ERROR) {
+ enum ofperr error = ofperr_decode_msg(oh, NULL);
+ if (error == OFPERR_NXTTMFC_ALREADY_MAPPED ||
+ error == OFPERR_NXTTMFC_DUP_ENTRY) {
+ VLOG_INFO("raced with another controller adding "
+ "Geneve option (%s); trying again",
+ ofperr_to_string(error));
+ state = S_NEW;
+ } else {
+ VLOG_ERR("error adding Geneve option (%s)",
+ ofperr_to_string(error));
+ goto error;
+ }
+ } else {
+ char *s = ofp_to_string(oh, ntohs(oh->length), 1);
+ VLOG_ERR("unexpected reply to Geneve option allocation request (%s)",
+ s);
+ free(s);
+ goto error;
+ }
+ return;
+
+error:
+ state = S_CLEAR_FLOWS;
+}
+\f
+/* S_CLEAR_FLOWS, after we've established a Geneve metadata field ID and it's
+ * time to set up some flows.
+ *
+ * Sends an OFPT_TABLE_MOD to clear all flows, then transitions to
+ * S_UPDATE_FLOWS. */
+
+static void
+run_S_CLEAR_FLOWS(void)
+{
+ /* Send a flow_mod to delete all flows. */
+ struct ofputil_flow_mod fm = {
+ .match = MATCH_CATCHALL_INITIALIZER,
+ .table_id = OFPTT_ALL,
+ .command = OFPFC_DELETE,
+ };
+ queue_flow_mod(&fm);
+ VLOG_DBG("clearing all flows");
+
+ /* Clear installed_flows, to match the state of the switch. */
+ ovn_flow_table_clear(&installed_flows);
+
+ state = S_UPDATE_FLOWS;
+}
+
+static void
+recv_S_CLEAR_FLOWS(const struct ofp_header *oh, enum ofptype type)