diff mbox series

[ovs-dev,v3,15/16] Implement RARP activation strategy for ports

Message ID 20220217151712.2292329-16-ihrachys@redhat.com
State Changes Requested
Headers show
Series Support additional-chassis for ports | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Ihar Hrachyshka Feb. 17, 2022, 3:17 p.m. UTC
When options:activation-strategy is set to "rarp" for LSP, when used in
combination with options:requested-additional-chassis, the additional
chassis will install special flows that would block all ingress and
egress traffic for the port until a special activation event happens.

For "rarp" strategy, an observation of a RARP packet sent from the port
on the additional chassis is such an event. When it occurs, a special
flow passes control to a controller() action handler that removes the
installed blocking flows and also marks the port as
options:additional-chassis-activated in southbound db. Once vswitchd
processes the flow mod request, the port is ready to communicate from
the new location.

This feature is useful in live migration scenarios where it's not
advisable to unlock the destination port location prematurily to avoid
duplicate packets originating from the port.

Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>
---
 controller/physical.c |  74 +++++++++++++++++++
 controller/pinctrl.c  | 161 +++++++++++++++++++++++++++++++++++++++++-
 controller/pinctrl.h  |   2 +
 include/ovn/actions.h |   9 +++
 lib/actions.c         |  40 ++++++++++-
 northd/northd.c       |   7 ++
 northd/ovn-northd.c   |   5 +-
 northd/ovn_northd.dl  |  27 ++++++-
 ovn-nb.xml            |  10 +++
 ovn-sb.xml            |  15 ++++
 tests/ovn.at          | 161 ++++++++++++++++++++++++++++++++++++++++++
 utilities/ovn-trace.c |   3 +
 12 files changed, 507 insertions(+), 7 deletions(-)
diff mbox series

Patch

diff --git a/controller/physical.c b/controller/physical.c
index 47ad3da33..f80665573 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -40,7 +40,9 @@ 
 #include "lib/mcast-group-index.h"
 #include "lib/ovn-sb-idl.h"
 #include "lib/ovn-util.h"
+#include "ovn/actions.h"
 #include "physical.h"
+#include "pinctrl.h"
 #include "openvswitch/shash.h"
 #include "simap.h"
 #include "smap.h"
@@ -978,6 +980,59 @@  get_additional_tunnel(const struct sbrec_port_binding *binding,
     return tun;
 }
 
+static void
+setup_rarp_activation_strategy(const struct sbrec_port_binding *binding,
+                               struct ovn_desired_flow_table *flow_table,
+                               struct ofpbuf *ofpacts_p)
+{
+    struct match match = MATCH_CATCHALL_INITIALIZER;
+    uint32_t dp_key = binding->datapath->tunnel_key;
+    uint32_t port_key = binding->tunnel_key;
+
+    /* Unblock the port on ingress RARP. */
+    match_set_metadata(&match, htonll(dp_key));
+    match_set_dl_type(&match, htons(ETH_TYPE_RARP));
+    match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key);
+    ofpbuf_clear(ofpacts_p);
+
+    size_t ofs = ofpacts_p->size;
+    struct ofpact_controller *oc = ofpact_put_CONTROLLER(ofpacts_p);
+    oc->max_len = UINT16_MAX;
+    oc->reason = OFPR_ACTION;
+    oc->pause = true;
+
+    struct action_header ah = {
+        .opcode = htonl(ACTION_OPCODE_ACTIVATION_STRATEGY_RARP)
+    };
+    ofpbuf_put(ofpacts_p, &ah, sizeof ah);
+
+    ofpacts_p->header = oc;
+    oc->userdata_len = ofpacts_p->size - (ofs + sizeof *oc);
+    ofpact_finish_CONTROLLER(ofpacts_p, &oc);
+
+    ofctrl_add_flow(flow_table, OFTABLE_LOG_INGRESS_PIPELINE, 1010,
+                    binding->header_.uuid.parts[0],
+                    &match, ofpacts_p, &binding->header_.uuid);
+    ofpbuf_clear(ofpacts_p);
+
+    /* Block all non-RARP traffic for the port, both directions. */
+    match_init_catchall(&match);
+    match_set_metadata(&match, htonll(dp_key));
+    match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, port_key);
+
+    ofctrl_add_flow(flow_table, OFTABLE_LOG_INGRESS_PIPELINE, 1000,
+                    binding->header_.uuid.parts[0],
+                    &match, ofpacts_p, &binding->header_.uuid);
+
+    match_init_catchall(&match);
+    match_set_metadata(&match, htonll(dp_key));
+    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, port_key);
+
+    ofctrl_add_flow(flow_table, OFTABLE_LOG_EGRESS_PIPELINE, 1000,
+                    binding->header_.uuid.parts[0],
+                    &match, ofpacts_p, &binding->header_.uuid);
+}
+
 static void
 consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                       enum mf_field_id mff_ovn_geneve,
@@ -1194,6 +1249,25 @@  consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
         }
     }
 
+    if (binding->additional_chassis == chassis) {
+        const char *strategy = smap_get(&binding->options,
+                                        "activation-strategy");
+        if (strategy
+                && !smap_get_bool(&binding->options,
+                                  "additional-chassis-activated", false)
+                && !pinctrl_is_port_activated(dp_key, port_key)) {
+            if (!strcmp(strategy, "rarp")) {
+                setup_rarp_activation_strategy(binding, flow_table, ofpacts_p);
+            } else {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(&rl,
+                             "Unknown activation strategy defined for "
+                             "port %s: %s", binding->logical_port, strategy);
+                goto out;
+            }
+        }
+    }
+
     /* Clone packets to additional chassis if needed. */
     const struct chassis_tunnel *additional_tun;
     additional_tun = get_additional_tunnel(binding, chassis, chassis_tunnels);
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index fd0bccdb6..af9279bb4 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -29,10 +29,12 @@ 
 #include "lport.h"
 #include "mac-learn.h"
 #include "nx-match.h"
+#include "ofctrl.h"
 #include "latch.h"
 #include "lib/packets.h"
 #include "lib/sset.h"
 #include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-flow.h"
 #include "openvswitch/ofp-msgs.h"
 #include "openvswitch/ofp-packet.h"
 #include "openvswitch/ofp-print.h"
@@ -152,8 +154,8 @@  VLOG_DEFINE_THIS_MODULE(pinctrl);
  *  and pinctrl_run().
  *  'pinctrl_handler_seq' is used by pinctrl_run() to
  *  wake up pinctrl_handler thread from poll_block() if any changes happened
- *  in 'send_garp_rarp_data', 'ipv6_ras' and 'buffered_mac_bindings'
- *  structures.
+ *  in 'send_garp_rarp_data', 'ipv6_ras', 'activated_ports' and
+ *  'buffered_mac_bindings' structures.
  *
  *  'pinctrl_main_seq' is used by pinctrl_handler() thread to wake up
  *  the main thread from poll_block() when mac bindings/igmp groups need to
@@ -198,6 +200,17 @@  static void wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn);
 static void send_mac_binding_buffered_pkts(struct rconn *swconn)
     OVS_REQUIRES(pinctrl_mutex);
 
+static void pinctrl_rarp_activation_strategy_handler(struct rconn *swconn,
+                                                     const struct match *md);
+static void init_activated_ports(void);
+static void destroy_activated_ports(void);
+static void wait_activated_ports(struct ovsdb_idl_txn *ovnsb_idl_txn);
+static void run_activated_ports(
+    struct ovsdb_idl_txn *ovnsb_idl_txn,
+    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name)
+    OVS_REQUIRES(pinctrl_mutex);
+
 static void init_send_garps_rarps(void);
 static void destroy_send_garps_rarps(void);
 static void send_garp_rarp_wait(long long int send_garp_rarp_time);
@@ -522,6 +535,7 @@  pinctrl_init(void)
     init_ipv6_ras();
     init_ipv6_prefixd();
     init_buffered_packets_map();
+    init_activated_ports();
     init_event_table();
     ip_mcast_snoop_init();
     init_put_vport_bindings();
@@ -3242,6 +3256,12 @@  process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
         ovs_mutex_unlock(&pinctrl_mutex);
         break;
 
+    case ACTION_OPCODE_ACTIVATION_STRATEGY_RARP:
+        ovs_mutex_lock(&pinctrl_mutex);
+        pinctrl_rarp_activation_strategy_handler(swconn, &pin.flow_metadata);
+        ovs_mutex_unlock(&pinctrl_mutex);
+        break;
+
     default:
         VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
                      ntohl(ah->opcode));
@@ -3506,6 +3526,8 @@  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
     bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
                     chassis, active_tunnels);
     run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac);
+    run_activated_ports(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
+                        sbrec_port_binding_by_key);
     ovs_mutex_unlock(&pinctrl_mutex);
 }
 
@@ -4034,6 +4056,7 @@  pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
     int64_t new_seq = seq_read(pinctrl_main_seq);
     seq_wait(pinctrl_main_seq, new_seq);
     wait_put_fdbs(ovnsb_idl_txn);
+    wait_activated_ports(ovnsb_idl_txn);
 }
 
 /* Called by ovn-controller. */
@@ -4048,6 +4071,7 @@  pinctrl_destroy(void)
     destroy_ipv6_ras();
     destroy_ipv6_prefixd();
     destroy_buffered_packets_map();
+    destroy_activated_ports();
     event_table_destroy();
     destroy_put_mac_bindings();
     destroy_put_vport_bindings();
@@ -7727,6 +7751,139 @@  pinctrl_handle_svc_check(struct rconn *swconn, const struct flow *ip_flow,
     }
 }
 
+static struct ofpbuf *
+encode_flow_mod(struct ofputil_flow_mod *fm)
+{
+    fm->buffer_id = UINT32_MAX;
+    fm->out_port = OFPP_ANY;
+    fm->out_group = OFPG_ANY;
+    return ofputil_encode_flow_mod(fm, OFPUTIL_P_OF15_OXM);
+}
+
+struct port_pair {
+    uint32_t dp_key;
+    uint32_t port_key;
+    struct ovs_list list;
+};
+
+static struct ovs_list activated_ports;
+
+static void
+init_activated_ports(void)
+{
+    ovs_list_init(&activated_ports);
+}
+
+static void
+destroy_activated_ports(void)
+{
+    struct port_pair *pp;
+    LIST_FOR_EACH_POP (pp, list, &activated_ports) {
+        free(pp);
+    }
+}
+
+static void
+wait_activated_ports(struct ovsdb_idl_txn *ovnsb_idl_txn)
+{
+    if (ovnsb_idl_txn && !ovs_list_is_empty(&activated_ports)) {
+        poll_immediate_wake();
+    }
+}
+
+static void
+run_activated_ports(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+                    struct ovsdb_idl_index *sbrec_port_binding_by_key)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    if (!ovnsb_idl_txn) {
+        return;
+    }
+
+    const struct port_pair *pp;
+    LIST_FOR_EACH (pp, list, &activated_ports) {
+        const struct sbrec_port_binding *pb = lport_lookup_by_key(
+            sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
+            pp->dp_key, pp->port_key);
+        if (pb) {
+            sbrec_port_binding_update_options_setkey(
+                pb, "additional-chassis-activated", "true");
+        }
+    }
+    destroy_activated_ports();
+}
+
+bool pinctrl_is_port_activated(int64_t dp_key, int64_t port_key)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    const struct port_pair *pp;
+    LIST_FOR_EACH (pp, list, &activated_ports) {
+        if (pp->dp_key == dp_key && pp->port_key == port_key) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static void
+pinctrl_rarp_activation_strategy_handler(struct rconn *swconn,
+                                         const struct match *md)
+    OVS_REQUIRES(pinctrl_mutex)
+{
+    struct match match;
+    struct minimatch mmatch;
+
+    /* Delete inport controller flow (the one that got us here */
+    match_init_catchall(&match);
+    match_set_metadata(&match, md->flow.metadata);
+    match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0,
+                  md->flow.regs[MFF_LOG_INPORT - MFF_REG0]);
+    match_set_dl_type(&match, htons(ETH_TYPE_RARP));
+    minimatch_init(&mmatch, &match);
+
+    struct ofputil_flow_mod fm = {
+        .match = mmatch,
+        .priority = 1010,
+        .table_id = OFTABLE_LOG_INGRESS_PIPELINE,
+        .command = OFPFC_DELETE_STRICT,
+    };
+    queue_msg(swconn, encode_flow_mod(&fm));
+    minimatch_destroy(&mmatch);
+
+    /* Delete ingress/egress drop flows to unblock the port. */
+    match_init_catchall(&match);
+    match_set_metadata(&match, md->flow.metadata);
+    match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0,
+                  md->flow.regs[MFF_LOG_INPORT - MFF_REG0]);
+    minimatch_init(&mmatch, &match);
+
+    fm.match = mmatch;
+    fm.priority = 1000;
+    queue_msg(swconn, encode_flow_mod(&fm));
+    minimatch_destroy(&mmatch);
+
+    match_init_catchall(&match);
+    match_set_metadata(&match, md->flow.metadata);
+    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0,
+                  md->flow.regs[MFF_LOG_INPORT - MFF_REG0]);
+    minimatch_init(&mmatch, &match);
+
+    fm.match = mmatch;
+    fm.table_id = OFTABLE_LOG_EGRESS_PIPELINE;
+    queue_msg(swconn, encode_flow_mod(&fm));
+    minimatch_destroy(&mmatch);
+
+    /* Tag the port as activated in-memory. */
+    struct port_pair *pp = xmalloc(sizeof *pp);
+    pp->port_key = md->flow.regs[MFF_LOG_INPORT - MFF_REG0];
+    pp->dp_key = ntohll(md->flow.metadata);
+    ovs_list_push_front(&activated_ports, &pp->list);
+
+    /* Notify main thread on pending additional-chassis-activated updates. */
+    notify_pinctrl_main();
+}
+
 static struct hmap put_fdbs;
 
 /* MAC learning (fdb) related functions.  Runs within the main
diff --git a/controller/pinctrl.h b/controller/pinctrl.h
index 88f18e983..c6cc66e85 100644
--- a/controller/pinctrl.h
+++ b/controller/pinctrl.h
@@ -56,4 +56,6 @@  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
 void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
 void pinctrl_destroy(void);
 void pinctrl_set_br_int_name(char *br_int_name);
+
+bool pinctrl_is_port_activated(int64_t dp_key, int64_t port_key);
 #endif /* controller/pinctrl.h */
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index cdef5fb03..524185095 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -113,6 +113,7 @@  struct ovn_extend_table;
     OVNACT(PUT_FDB,           ovnact_put_fdb)         \
     OVNACT(GET_FDB,           ovnact_get_fdb)         \
     OVNACT(LOOKUP_FDB,        ovnact_lookup_fdb)      \
+    OVNACT(ACTIVATION_STRATEGY_RARP, ovnact_activation_strategy_rarp) \
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -411,6 +412,11 @@  struct ovnact_handle_svc_check {
     struct expr_field port;     /* Logical port name. */
 };
 
+/* OVNACT_ACTIVATION_STRATEGY_RARP. */
+struct ovnact_activation_strategy_rarp {
+    struct ovnact ovnact;
+};
+
 /* OVNACT_FWD_GROUP. */
 struct ovnact_fwd_group {
     struct ovnact ovnact;
@@ -672,6 +678,9 @@  enum action_opcode {
     /* put_fdb(inport, eth.src).
      */
     ACTION_OPCODE_PUT_FDB,
+
+    /* activation_strategy_rarp() */
+    ACTION_OPCODE_ACTIVATION_STRATEGY_RARP,
 };
 
 /* Header. */
diff --git a/lib/actions.c b/lib/actions.c
index d5d8391bb..cddb6993d 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -3565,6 +3565,41 @@  ovnact_handle_svc_check_free(struct ovnact_handle_svc_check *sc OVS_UNUSED)
 {
 }
 
+static void
+parse_activation_strategy_rarp(struct action_context *ctx OVS_UNUSED)
+{
+     if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) {
+        return;
+    }
+
+    ovnact_put_ACTIVATION_STRATEGY_RARP(ctx->ovnacts);
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+format_ACTIVATION_STRATEGY_RARP(
+        const struct ovnact_activation_strategy_rarp *activation OVS_UNUSED,
+        struct ds *s)
+{
+    ds_put_cstr(s, "activation_strategy_rarp();");
+}
+
+static void
+encode_ACTIVATION_STRATEGY_RARP(
+        const struct ovnact_activation_strategy_rarp *activation OVS_UNUSED,
+        const struct ovnact_encode_params *ep,
+        struct ofpbuf *ofpacts)
+{
+    encode_controller_op(ACTION_OPCODE_ACTIVATION_STRATEGY_RARP,
+                         ep->ctrl_meter_id, ofpacts);
+}
+
+static void
+ovnact_activation_strategy_rarp_free(
+    struct ovnact_activation_strategy_rarp *activation OVS_UNUSED)
+{
+}
+
 static void
 parse_fwd_group_action(struct action_context *ctx)
 {
@@ -4113,6 +4148,8 @@  parse_action(struct action_context *ctx)
         parse_bind_vport(ctx);
     } else if (lexer_match_id(ctx->lexer, "handle_svc_check")) {
         parse_handle_svc_check(ctx);
+    } else if (lexer_match_id(ctx->lexer, "activation_strategy_rarp")) {
+        parse_activation_strategy_rarp(ctx);
     } else if (lexer_match_id(ctx->lexer, "fwd_group")) {
         parse_fwd_group_action(ctx);
     } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) {
@@ -4356,7 +4393,8 @@  ovnact_op_to_string(uint32_t ovnact_opc)
         ACTION_OPCODE(BIND_VPORT)                   \
         ACTION_OPCODE(DHCP6_SERVER)                 \
         ACTION_OPCODE(HANDLE_SVC_CHECK)             \
-        ACTION_OPCODE(BFD_MSG)
+        ACTION_OPCODE(BFD_MSG)                      \
+        ACTION_OPCODE(ACTIVATION_STRATEGY_RARP)
 #define ACTION_OPCODE(ENUM) \
     case ACTION_OPCODE_##ENUM: return xstrdup(#ENUM);
     ACTION_OPCODES
diff --git a/northd/northd.c b/northd/northd.c
index 108ccb14f..ba6ac4cb8 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3370,6 +3370,13 @@  ovn_port_update_sbrec(struct northd_input *input_data,
                 smap_add(&options, "vlan-passthru", "true");
             }
 
+            /* Retain activated flag. */
+            if (op->sb->requested_additional_chassis
+                    && smap_get_bool(&op->sb->options,
+                                     "additional-chassis-activated", false)) {
+                smap_add(&options, "additional-chassis-activated", "true");
+            }
+
             sbrec_port_binding_set_options(op->sb, &options);
             smap_destroy(&options);
             if (ovn_is_known_nb_lsp_type(op->nbsp->type)) {
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 984105a5f..b1f840895 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -102,7 +102,10 @@  static const char *rbac_port_binding_auth[] =
 static const char *rbac_port_binding_update[] =
     {"chassis", "additional_chassis",
      "encap", "additional_encap",
-     "up", "virtual_parent"};
+     "up", "virtual_parent",
+     /* NOTE: we only need to update the additional-chassis-activated key,
+      * but RBAC_Role doesn't support mutate operation for subkeys. */
+     "options"};
 
 static const char *rbac_mac_binding_auth[] =
     {""};
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 16b33a661..6df9ca84c 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -372,13 +372,19 @@  OutProxy_Port_Binding(._uuid              = lsp._uuid,
         };
         eids
     },
-    var options = {
+    var options0 = {
         var options = lsp.options;
         if (sw.other_config.get(i"vlan-passthru") == Some{i"true"}) {
             options.insert(i"vlan-passthru", i"true")
         };
         options
-    }.
+    },
+    PreserveAdditionalChassisActivated(lsp._uuid, activated),
+    var options1 = match (activated) {
+        None -> map_empty(),
+        Some{value} -> [i"additional-chassis-activated" -> value]
+    },
+    var options = options0.union(options1).
 
 relation SwitchPortLBIPs(
     port: Intern<SwitchPort>,
@@ -1592,6 +1598,8 @@  sb::Out_RBAC_Permission (
     .update         = [i"type", i"options", i"ip"].to_set()
 ).
 
+/* NOTE: we only need to update the additional-chassis-activated key,
+ * but RBAC_Role doesn't support mutate operation for subkeys. */
 sb::Out_RBAC_Permission (
     ._uuid          = 128'hd8ceff1a_2b11_48bd_802f_4a991aa4e908,
     .table          = i"Port_Binding",
@@ -1599,7 +1607,8 @@  sb::Out_RBAC_Permission (
     .insert_delete  = false,
     .update         = [i"chassis", i"additional_chassis",
                        i"encap", i"additional_encap",
-                       i"up", i"virtual_parent"].to_set()
+                       i"up", i"virtual_parent",
+                       i"options"].to_set()
 ).
 
 sb::Out_RBAC_Permission (
@@ -8895,6 +8904,18 @@  PreserveIPv6RAPDList(lrp_uuid, None) :-
     &nb::Logical_Router_Port(._uuid = lrp_uuid),
     not sb::Port_Binding(._uuid = lrp_uuid).
 
+/*
+ * This allows ovn-northd to preserve options:additional-chassis-activated,
+ * which is set by ovn-controller.
+ */
+relation PreserveAdditionalChassisActivated(lsp_uuid: uuid, activated: Option<istring>)
+PreserveAdditionalChassisActivated(lsp_uuid, activated) :-
+    sb::Port_Binding(._uuid = lsp_uuid, .options = options),
+    var activated = options.get(i"additional-chassis-activated").
+PreserveAdditionalChassisActivated(lsp_uuid, None) :-
+    &nb::Logical_Switch_Port(._uuid = lsp_uuid),
+    not sb::Port_Binding(._uuid = lsp_uuid).
+
 /*
  * Tag allocation for nested containers.
  */
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 2ec3b90aa..5ef23b1e7 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1013,6 +1013,16 @@ 
           useful in live migration scenarios, for port mirroring, etc.
         </column>
 
+        <column name="options" key="activation-strategy">
+          If set and used with <ref column="requested-additional-chassis"/>,
+          specifies an activation strategy for the additional chassis. By
+          default, no activation strategy is used, meaning the additional port
+          location is immediately available for use. When set to "rarp", the
+          port is blocked for ingress and egress communication until a RARP
+          packet is sent from the new location. The "rarp" strategy is useful
+          in live migration scenarios for virtual machines.
+        </column>
+
         <column name="options" key="iface-id-ver">
           If set, this port will be bound by <code>ovn-controller</code>
           only if this same key and value is configured in the
diff --git a/ovn-sb.xml b/ovn-sb.xml
index f43bb6804..4c70657b5 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -3266,6 +3266,21 @@  tcp.flags = RST;
         useful in live migration scenarios, for port mirroring, etc.
       </column>
 
+      <column name="options" key="activation-strategy">
+        If set and used with <ref column="requested-additional-chassis"/>,
+        specifies an activation strategy for the additional chassis. By
+        default, no activation strategy is used, meaning the additional port
+        location is immediately available for use. When set to "rarp", the
+        port is blocked for ingress and egress communication until a RARP
+        packet is sent from the new location. The "rarp" strategy is useful
+        in live migration scenarios for virtual machines.
+      </column>
+
+      <column name="options" key="additional-chassis-activated">
+        When <ref column="activation-strategy"/> is set, this option indicates
+        that the port was activated using the strategy specified.
+      </column>
+
       <column name="options" key="iface-id-ver">
         If set, this port will be bound by <code>ovn-controller</code>
         only if this same key and value is configured in the
diff --git a/tests/ovn.at b/tests/ovn.at
index 1ccec492d..a0698c362 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -14383,6 +14383,167 @@  OVN_CLEANUP([hv1],[hv2],[hv3])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([options:activation-strategy for logical port])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.11
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.12
+
+sim_add hv3
+as hv3
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.13
+
+# Disable local ARP responder to pass ARP requests through tunnels
+check ovn-nbctl ls-add ls0 -- add Logical_Switch ls0 other_config vlan-passthru=true
+
+check ovn-nbctl lsp-add ls0 migrator
+check ovn-nbctl lsp-set-options migrator requested-chassis=hv1 \
+                                         requested-additional-chassis=hv2 \
+                                         activation-strategy=rarp
+
+check ovn-nbctl lsp-add ls0 outside
+check ovn-nbctl lsp-set-options outside requested-chassis=hv3
+
+check ovn-nbctl lsp-set-addresses migrator "00:00:00:00:00:01 10.0.0.1"
+check ovn-nbctl lsp-set-addresses outside "00:00:00:00:00:02 10.0.0.2"
+
+for hv in hv1 hv2; do
+    as $hv check ovs-vsctl -- add-port br-int migrator -- \
+        set Interface migrator external-ids:iface-id=migrator \
+                               options:tx_pcap=$hv/migrator-tx.pcap \
+                               options:rxq_pcap=$hv/migrator-rx.pcap
+done
+as hv3 check ovs-vsctl -- add-port br-int outside -- \
+    set Interface outside external-ids:iface-id=outside
+
+wait_row_count Chassis 1 name=hv1
+wait_row_count Chassis 1 name=hv2
+hv1_uuid=$(fetch_column Chassis _uuid name=hv1)
+hv2_uuid=$(fetch_column Chassis _uuid name=hv2)
+
+wait_column "$hv1_uuid" Port_Binding chassis logical_port=migrator
+wait_column "$hv1_uuid" Port_Binding requested_chassis logical_port=migrator
+wait_column "$hv2_uuid" Port_Binding additional_chassis logical_port=migrator
+wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=migrator
+
+OVN_POPULATE_ARP
+
+send_arp() {
+    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
+    local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
+    as ${hv} ovs-appctl netdev-dummy/receive $inport $request
+    echo "${request}"
+}
+
+send_rarp() {
+    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
+    local request=${eth_dst}${eth_src}80350001080006040001${eth_src}${spa}${eth_dst}${tpa}
+    as ${hv} ovs-appctl netdev-dummy/receive $inport $request
+}
+
+reset_pcap_file() {
+    local hv=$1
+    local iface=$2
+    local pcap_file=$3
+    as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+                                                   options:rxq_pcap=dummy-rx.pcap
+    check rm -f ${pcap_file}*.pcap
+    as $hv check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+                                                   options:rxq_pcap=${pcap_file}-rx.pcap
+}
+
+reset_env() {
+    reset_pcap_file hv1 migrator hv1/migrator
+    reset_pcap_file hv2 migrator hv2/migrator
+    reset_pcap_file hv3 outside hv3/outside
+
+    for port in hv1/migrator hv2/migrator hv3/outside; do
+        : > $port.expected
+    done
+}
+
+check_packets() {
+    OVN_CHECK_PACKETS([hv1/migrator-tx.pcap], [hv1/migrator.expected])
+    OVN_CHECK_PACKETS([hv2/migrator-tx.pcap], [hv2/migrator.expected])
+    OVN_CHECK_PACKETS([hv3/outside-tx.pcap], [hv3/outside.expected])
+}
+
+migrator_spa=$(ip_to_hex 10 0 0 1)
+outside_spa=$(ip_to_hex 10 0 0 2)
+
+reset_env
+
+# Packet from hv3:Outside arrives to hv1:Migrator
+# hv3:Outside cannot reach hv2:Migrator because it is blocked by RARP strategy
+request=$(send_arp hv3 outside 000000000002 000000000001 $outside_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+
+check_packets
+reset_env
+
+# Packet from hv1:Migrator arrives to hv3:Outside
+request=$(send_arp hv1 migrator 000000000001 000000000002 $migrator_spa $outside_spa)
+echo $request >> hv3/outside.expected
+
+check_packets
+reset_env
+
+# hv2:Migrator cannot reach to hv3:Outside because it is blocked by RARP strategy
+request=$(send_arp hv2 migrator 000000000001 000000000002 $migrator_spa $outside_spa)
+
+check_packets
+reset_env
+
+AT_CHECK([ovn-sbctl find port_binding logical_port=migrator | grep -q additional-chassis-activated], [1])
+
+# Now activate hv2:Migrator location
+send_rarp hv2 migrator 000000000001 ffffffffffff $migrator_spa $migrator_spa
+reset_env
+
+pb_uuid=$(ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=migrator)
+OVS_WAIT_UNTIL([test xtrue = x$(ovn-sbctl get Port_Binding $pb_uuid options:additional-chassis-activated | tr -d '""')])
+
+# Now packet arrives to both locations
+request=$(send_arp hv3 outside 000000000002 000000000001 $outside_spa $migrator_spa)
+echo $request >> hv1/migrator.expected
+echo $request >> hv2/migrator.expected
+
+check_packets
+reset_env
+
+# Packet from hv1:Migrator still arrives to hv3:Outside
+request=$(send_arp hv1 migrator 000000000001 000000000002 $migrator_spa $outside_spa)
+echo $request >> hv3/outside.expected
+
+check_packets
+reset_env
+
+# hv2:Migrator can now reach to hv3:Outside because RARP strategy activated it
+request=$(send_arp hv2 migrator 000000000001 000000000002 $migrator_spa $outside_spa)
+echo $request >> hv3/outside.expected
+
+check_packets
+
+# complete port migration and check that -activated flag is reset
+check ovn-nbctl lsp-set-options migrator requested-chassis=hv2
+OVS_WAIT_UNTIL([test x = x$(ovn-sbctl get Port_Binding $pb_uuid options:additional-chassis-activated)])
+
+OVN_CLEANUP([hv1],[hv2])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([options:requested-chassis for logical port])
 ovn_start
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 0795913d3..097d8c9bb 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -2811,6 +2811,9 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
             /* Nothing to do for tracing. */
             break;
 
+        case OVNACT_ACTIVATION_STRATEGY_RARP:
+            break;
+
         case OVNACT_GET_FDB:
             execute_get_fdb(ovnact_get_GET_FDB(a), dp, uflow);
             break;