diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 6106a9661..86f5a3f9d 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -2480,6 +2480,9 @@ main(int argc, char *argv[])
         = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
                                   &sbrec_fdb_col_mac,
                                   &sbrec_fdb_col_dp_key);
+    struct ovsdb_idl_index *sbrec_mac_binding_by_chassis
+        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
+                                  &sbrec_mac_binding_col_chassis_name);
 
     ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
     ovsdb_idl_omit_alert(ovnsb_idl_loop.idl,
@@ -2904,6 +2907,7 @@ main(int argc, char *argv[])
                                     sbrec_port_binding_by_key,
                                     sbrec_port_binding_by_name,
                                     sbrec_mac_binding_by_lport_ip,
+                                    sbrec_mac_binding_by_chassis,
                                     sbrec_igmp_group,
                                     sbrec_ip_multicast,
                                     sbrec_fdb_by_dp_key_mac,
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 523a45b9a..6a4199b8d 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -205,6 +205,7 @@ static void send_garp_rarp_prepare(
     struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
     struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+    struct ovsdb_idl_index *sbrec_mac_binding_by_chassis,
     const struct ovsrec_bridge *,
     const struct sbrec_chassis *,
     const struct hmap *local_datapaths,
@@ -3318,6 +3319,7 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_port_binding_by_key,
             struct ovsdb_idl_index *sbrec_port_binding_by_name,
             struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+            struct ovsdb_idl_index *sbrec_mac_binding_by_chassis,
             struct ovsdb_idl_index *sbrec_igmp_groups,
             struct ovsdb_idl_index *sbrec_ip_multicast_opts,
             struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
@@ -3339,7 +3341,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                            sbrec_port_binding_by_key, chassis);
     send_garp_rarp_prepare(ovnsb_idl_txn, sbrec_port_binding_by_datapath,
                            sbrec_port_binding_by_name,
-                           sbrec_mac_binding_by_lport_ip, br_int, chassis,
+                           sbrec_mac_binding_by_lport_ip,
+                           sbrec_mac_binding_by_chassis, br_int, chassis,
                            local_datapaths, active_tunnels);
     prepare_ipv6_ras(local_datapaths);
     prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
@@ -4008,7 +4011,8 @@ mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
                       const char *logical_port,
                       const struct sbrec_datapath_binding *dp,
                       struct eth_addr ea, const char *ip,
-                      bool update_only)
+                      bool update_only,
+                      const struct sbrec_chassis *chassis)
 {
     /* Convert ethernet argument to string form for database. */
     char mac_string[ETH_ADDR_STRLEN + 1];
@@ -4025,49 +4029,20 @@ mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
         sbrec_mac_binding_set_ip(b, ip);
         sbrec_mac_binding_set_mac(b, mac_string);
         sbrec_mac_binding_set_datapath(b, dp);
-    } else if (strcmp(b->mac, mac_string)) {
-        sbrec_mac_binding_set_mac(b, mac_string);
-    }
-}
-
-/* Simulate the effect of a GARP on local datapaths, i.e., create MAC_Bindings
- * on peer router datapaths.
- */
-static void
-send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                  struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                  const struct hmap *local_datapaths,
-                  const struct sbrec_port_binding *in_pb,
-                  struct eth_addr ea, ovs_be32 ip)
-{
-    if (!ovnsb_idl_txn) {
-        return;
-    }
-
-    const struct local_datapath *ldp =
-        get_local_datapath(local_datapaths, in_pb->datapath->tunnel_key);
-
-    ovs_assert(ldp);
-    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
-        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
-        const struct sbrec_port_binding *remote = ldp->peer_ports[i].remote;
-
-        /* Skip "ingress" port. */
-        if (local == in_pb) {
-            continue;
+        if (chassis) {
+            sbrec_mac_binding_set_chassis_name(b, chassis->name);
+        }
+    } else {
+        if (strcmp(b->mac, mac_string)) {
+            sbrec_mac_binding_set_mac(b, mac_string);
+        }
+        if (b->chassis_name[0]) {
+            if (chassis && strcmp(b->chassis_name, chassis->name)) {
+                sbrec_mac_binding_set_chassis_name(b, chassis->name);
+            } else if (!chassis) {
+                sbrec_mac_binding_set_chassis_name(b, "");
+            }
         }
-
-        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
-                                          "always_learn_from_arp_request",
-                                          true);
-
-        struct ds ip_s = DS_EMPTY_INITIALIZER;
-
-        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
-        mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                              remote->logical_port, remote->datapath,
-                              ea, ds_cstr(&ip_s), update_only);
-        ds_destroy(&ip_s);
     }
 }
 
@@ -4099,7 +4074,7 @@ run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
     ipv6_format_mapped(&mb->ip, &ip_s);
     mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
                           pb->logical_port, pb->datapath, mb->mac,
-                          ds_cstr(&ip_s), false);
+                          ds_cstr(&ip_s), false, NULL);
     ds_destroy(&ip_s);
 }
 
@@ -4233,9 +4208,7 @@ add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip,
 
 /* Add or update a vif for which GARPs need to be announced. */
 static void
-send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                      struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                      const struct hmap *local_datapaths,
+send_garp_rarp_update(const struct hmap *local_datapaths,
                       const struct sbrec_port_binding *binding_rec,
                       struct shash *nat_addresses)
 {
@@ -4246,6 +4219,13 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
         return;
     }
 
+    struct local_datapath *ldp = get_local_datapath(
+            local_datapaths, binding_rec->datapath->tunnel_key);
+    ovs_assert(ldp);
+    if (!ldp->localnet_port) {
+        return;
+    }
+
     /* Update GARP for NAT IP if it exists.  Consider port bindings with type
      * "l3gateway" for logical switch ports attached to gateway routers, and
      * port bindings with type "patch" for logical switch ports attached to
@@ -4268,11 +4248,6 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
                                   laddrs->ipv4_addrs[i].addr,
                                   binding_rec->datapath->tunnel_key,
                                   binding_rec->tunnel_key);
-                    send_garp_locally(ovnsb_idl_txn,
-                                      sbrec_mac_binding_by_lport_ip,
-                                      local_datapaths, binding_rec, laddrs->ea,
-                                      laddrs->ipv4_addrs[i].addr);
-
                 }
                 free(name);
             }
@@ -4308,10 +4283,6 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
                       laddrs.ea, ip,
                       binding_rec->datapath->tunnel_key,
                       binding_rec->tunnel_key);
-        if (ip) {
-            send_garp_locally(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                              local_datapaths, binding_rec, laddrs.ea, ip);
-        }
 
         destroy_lport_addresses(&laddrs);
         break;
@@ -5424,10 +5395,6 @@ get_localnet_vifs_l3gwports(
     HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
         const struct sbrec_port_binding *pb;
 
-        if (!ld->localnet_port) {
-            continue;
-        }
-
         /* Get l3gw ports.  Consider port bindings with type "l3gateway"
          * that connect to gateway routers (if local), and consider port
          * bindings of type "patch" since they might connect to
@@ -5551,7 +5518,8 @@ get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                            struct sset *local_l3gw_ports,
                            const struct sbrec_chassis *chassis,
                            const struct sset *active_tunnels,
-                           struct shash *nat_addresses)
+                           struct shash *garp_addresses,
+                           struct shash *router_addresses)
 {
     const char *gw_port;
     SSET_FOR_EACH(gw_port, local_l3gw_ports) {
@@ -5563,12 +5531,12 @@ get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
         }
 
         if (pb->n_nat_addresses) {
-            for (int i = 0; i < pb->n_nat_addresses; i++) {
+            for (size_t i = 0; i < pb->n_nat_addresses; i++) {
                 consider_nat_address(sbrec_port_binding_by_name,
                                      pb->nat_addresses[i], pb,
                                      nat_address_keys, chassis,
                                      active_tunnels,
-                                     nat_addresses);
+                                     garp_addresses);
             }
         } else {
             /* Continue to support options:nat-addresses for version
@@ -5580,9 +5548,16 @@ get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                                      nat_addresses_options, pb,
                                      nat_address_keys, chassis,
                                      active_tunnels,
-                                     nat_addresses);
+                                     garp_addresses);
             }
         }
+
+        for (size_t i = 0; i < pb->n_router_addresses; i++) {
+            consider_nat_address(sbrec_port_binding_by_name,
+                                 pb->router_addresses[i], pb,
+                                 nat_address_keys, chassis,
+                                 active_tunnels, router_addresses);
+        }
     }
 }
 
@@ -5618,6 +5593,165 @@ send_garp_rarp_run(struct rconn *swconn, long long int *send_garp_rarp_time)
     }
 }
 
+struct chassis_mac_binding {
+    struct hmap_node hmap_node;
+    const struct sbrec_mac_binding *binding;
+    uint32_t hash;
+};
+
+static uint32_t
+chassis_mac_binding_hash_(const char *ip, const char *logical_port)
+{
+    return hash_string(ip, hash_string(logical_port, 0));
+}
+
+static uint32_t
+chassis_mac_binding_hash(const struct sbrec_mac_binding *binding)
+{
+    return chassis_mac_binding_hash_(binding->ip, binding->logical_port);
+}
+
+static struct chassis_mac_binding *
+chassis_mac_binding_alloc(const struct sbrec_mac_binding *binding)
+{
+    struct chassis_mac_binding *cmb = xmalloc(sizeof *cmb);
+    cmb->binding = binding;
+    cmb->hash = chassis_mac_binding_hash(binding);
+    return cmb;
+}
+
+static void
+chassis_mac_binding_remove(struct hmap *chassis_mac_bindings,
+                           const char *ip, const char *logical_port)
+{
+    uint32_t hash = chassis_mac_binding_hash_(ip, logical_port);
+    struct chassis_mac_binding *cmb;
+    HMAP_FOR_EACH_WITH_HASH (cmb, hmap_node, hash, chassis_mac_bindings) {
+        if (!strcmp(cmb->binding->ip, ip)
+            && !strcmp(cmb->binding->logical_port, logical_port)) {
+            hmap_remove(chassis_mac_bindings, &cmb->hmap_node);
+            free(cmb);
+            break;
+        }
+    }
+}
+
+static void
+delete_old_chassis_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                                struct hmap *chassis_mac_bindings)
+{
+    if (!ovnsb_idl_txn) {
+        return;
+    }
+    struct chassis_mac_binding *cmb;
+    HMAP_FOR_EACH_POP (cmb, hmap_node, chassis_mac_bindings) {
+        sbrec_mac_binding_delete(cmb->binding);
+        free(cmb);
+    }
+}
+
+static void
+get_chassis_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                         struct ovsdb_idl_index *sbrec_mac_binding_by_chassis,
+                         const struct sbrec_chassis *chassis,
+                         struct hmap *chassis_mac_bindings)
+{
+    if (!ovnsb_idl_txn) {
+        return;
+    }
+    const struct sbrec_mac_binding *mac_binding;
+
+    struct sbrec_mac_binding *mb_row = sbrec_mac_binding_index_init_row(
+        sbrec_mac_binding_by_chassis);
+    sbrec_mac_binding_index_set_chassis_name(mb_row, chassis->name);
+
+    SBREC_MAC_BINDING_FOR_EACH_EQUAL (mac_binding, mb_row,
+                                      sbrec_mac_binding_by_chassis) {
+        struct chassis_mac_binding *ch_binding =
+            chassis_mac_binding_alloc(mac_binding);
+        hmap_insert(chassis_mac_bindings, &ch_binding->hmap_node, ch_binding->hash);
+    }
+    sbrec_mac_binding_index_destroy_row(mb_row);
+}
+
+/* Simulate the effect of a GARP on local datapaths, i.e., create MAC_Bindings
+ * on peer router datapaths.
+ */
+static void
+install_chassis_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                            struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+                            const struct local_datapath *ldp,
+                            const struct sbrec_port_binding *in_pb,
+                            struct eth_addr ea, ovs_be32 ip,
+                            const struct sbrec_chassis *chassis,
+                            struct hmap *old_chassis_mac_bindings)
+{
+    ovs_assert(ovnsb_idl_txn && ldp);
+
+    struct ds ip_s = DS_EMPTY_INITIALIZER;
+
+    ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
+    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
+        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
+        const struct sbrec_port_binding *remote = ldp->peer_ports[i].remote;
+
+        /* Skip "ingress" port. */
+        if (local == in_pb) {
+            continue;
+        }
+
+        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
+                                          "always_learn_from_arp_request",
+                                          true);
+
+        mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
+                              remote->logical_port, remote->datapath,
+                              ea, ds_cstr(&ip_s), update_only, chassis);
+
+        chassis_mac_binding_remove(old_chassis_mac_bindings, ds_cstr(&ip_s),
+                                   remote->logical_port);
+
+    }
+    ds_destroy(&ip_s);
+}
+
+static void
+install_chassis_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                             struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+                             const struct hmap *local_datapaths,
+                             const struct sbrec_port_binding *in_pb,
+                             struct shash *router_addresses,
+                             const struct sbrec_chassis *chassis,
+                             struct hmap *old_chassis_mac_bindings)
+{
+    if (!ovnsb_idl_txn) {
+        return;
+    }
+
+    if (strcmp(in_pb->type, "l3gateway")
+        && strcmp(in_pb->type, "patch")) {
+        return;
+    }
+
+    const struct local_datapath *ldp =
+        get_local_datapath(local_datapaths, in_pb->datapath->tunnel_key);
+
+    struct lport_addresses *laddrs = NULL;
+    while ((laddrs = shash_find_and_delete(router_addresses,
+                                           in_pb->logical_port))) {
+        int i;
+        for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
+            install_chassis_mac_binding(ovnsb_idl_txn,
+                                        sbrec_mac_binding_by_lport_ip,
+                                        ldp, in_pb, laddrs->ea,
+                                        laddrs->ipv4_addrs[i].addr,
+                                        chassis, old_chassis_mac_bindings);
+        }
+        destroy_lport_addresses(laddrs);
+        free(laddrs);
+    }
+}
+
 /* Called by pinctrl_run(). Runs with in the main ovn-controller
  * thread context. */
 static void
@@ -5625,6 +5759,7 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
                        struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
                        struct ovsdb_idl_index *sbrec_port_binding_by_name,
                        struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+                       struct ovsdb_idl_index *sbrec_mac_binding_by_chassis,
                        const struct ovsrec_bridge *br_int,
                        const struct sbrec_chassis *chassis,
                        const struct hmap *local_datapaths,
@@ -5635,8 +5770,15 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
     struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
     struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
     struct shash nat_addresses;
+    struct shash router_addresses;
+    struct hmap old_chassis_mac_bindings;
 
     shash_init(&nat_addresses);
+    shash_init(&router_addresses);
+    hmap_init(&old_chassis_mac_bindings);
+
+    get_chassis_mac_bindings(ovnsb_idl_txn, sbrec_mac_binding_by_chassis,
+                             chassis, &old_chassis_mac_bindings);
 
     get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
                                 sbrec_port_binding_by_name,
@@ -5646,7 +5788,8 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
     get_nat_addresses_and_keys(sbrec_port_binding_by_name,
                                &nat_ip_keys, &local_l3gw_ports,
                                chassis, active_tunnels,
-                               &nat_addresses);
+                               &nat_addresses,
+                               &router_addresses);
     /* For deleted ports and deleted nat ips, remove from
      * send_garp_rarp_data. */
     struct shash_node *iter, *next;
@@ -5663,8 +5806,11 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
         const struct sbrec_port_binding *pb = lport_lookup_by_name(
             sbrec_port_binding_by_name, iface_id);
         if (pb) {
-            send_garp_rarp_update(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                                  local_datapaths, pb, &nat_addresses);
+            send_garp_rarp_update(local_datapaths, pb, &nat_addresses);
+            install_chassis_mac_bindings(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
+                                         local_datapaths, pb,
+                                         &router_addresses, chassis,
+                                         &old_chassis_mac_bindings);
         }
     }
 
@@ -5674,11 +5820,17 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
         const struct sbrec_port_binding *pb
             = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
         if (pb) {
-            send_garp_rarp_update(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                                  local_datapaths, pb, &nat_addresses);
+            send_garp_rarp_update(local_datapaths, pb, &nat_addresses);
+            install_chassis_mac_bindings(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
+                                         local_datapaths, pb,
+                                         &router_addresses, chassis,
+                                         &old_chassis_mac_bindings);
         }
     }
 
+    delete_old_chassis_mac_bindings(ovnsb_idl_txn, &old_chassis_mac_bindings);
+    hmap_destroy(&old_chassis_mac_bindings);
+
     /* pinctrl_handler thread will send the GARPs. */
 
     sset_destroy(&localnet_vifs);
@@ -5692,6 +5844,14 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
     }
     shash_destroy(&nat_addresses);
 
+    SHASH_FOR_EACH_SAFE (iter, next, &router_addresses) {
+        struct lport_addresses *laddrs = iter->data;
+        destroy_lport_addresses(laddrs);
+        shash_delete(&router_addresses, iter);
+        free(laddrs);
+    }
+    shash_destroy(&router_addresses);
+
     sset_destroy(&nat_ip_keys);
 }
 
diff --git a/controller/pinctrl.h b/controller/pinctrl.h
index cc0a51984..5d4f32b4a 100644
--- a/controller/pinctrl.h
+++ b/controller/pinctrl.h
@@ -40,6 +40,7 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                  struct ovsdb_idl_index *sbrec_port_binding_by_key,
                  struct ovsdb_idl_index *sbrec_port_binding_by_name,
                  struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+                 struct ovsdb_idl_index *sbrec_mac_binding_by_chassis,
                  struct ovsdb_idl_index *sbrec_igmp_groups,
                  struct ovsdb_idl_index *sbrec_ip_multicast_opts,
                  struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 38de13090..3a4d5323f 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -13693,7 +13693,7 @@ static const char *rbac_port_binding_update[] =
 static const char *rbac_mac_binding_auth[] =
     {""};
 static const char *rbac_mac_binding_update[] =
-    {"logical_port", "ip", "mac", "datapath"};
+    {"logical_port", "ip", "mac", "datapath", "chassis_name"};
 
 static const char *rbac_svc_monitor_auth[] =
     {""};
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index b792a175d..96101213d 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -965,7 +965,8 @@ sb::Out_MAC_Binding (._uuid        = mb._uuid,
                     .logical_port = mb.logical_port,
                     .ip           = mb.ip,
                     .mac          = mb.mac,
-                    .datapath     = mb.datapath) :-
+                    .datapath     = mb.datapath,
+                    .chassis_name = mb.chassis_name) :-
     sb::MAC_Binding[mb],
     sb::Out_Port_Binding(.logical_port = mb.logical_port),
     sb::Out_Datapath_Binding(._uuid = mb.datapath).
@@ -1322,7 +1323,7 @@ sb::Out_RBAC_Permission (
     .table          = "MAC_Binding",
     .authorization  = set_singleton(""),
     .insert_delete  = true,
-    .update         = ["logical_port", "ip", "mac", "datapath"].to_set()
+    .update         = ["logical_port", "ip", "mac", "datapath", "chassis_name"].to_set()
 ).
 
 sb::Out_RBAC_Permission (
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index b879e6228..182a95528 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
     "version": "20.17.0",
-    "cksum": "367711378 26723",
+    "cksum": "394417570 26775",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -241,7 +241,8 @@
                 "ip": {"type": "string"},
                 "mac": {"type": "string"},
                 "datapath": {"type": {"key": {"type": "uuid",
-                                              "refTable": "Datapath_Binding"}}}},
+                                              "refTable": "Datapath_Binding"}}},
+                "chassis_name": {"type": "string"}},
             "indexes": [["logical_port", "ip"]],
             "isRoot": true},
         "DHCP_Options": {
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 610dca17c..7648a9e78 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -3345,6 +3345,13 @@ tcp.flags = RST;
     <column name="datapath">
       The logical datapath to which the logical port belongs.
     </column>
+    <column name="chassis_name">
+      This column indicates the chassis on which the binding was added. This
+      column is only filled in when ovn-controller generates the MAC binding
+      internally. MAC bindings learned due to ARP requests will not have this
+      column set. This is used internally by ovn-controller to ensure that
+      defunct bindings are removed.
+    </column>
   </table>
 
   <table name="DHCP_Options" title="DHCP Options supported by native OVN DHCP">
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index 23d793a95..434d037de 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -268,6 +268,10 @@ check_logs () {
     # we ignore the messages that were rate-limited, we can end up failing just
     # because of the announcement that rate-limiting happened (and in a racy,
     # timing-dependent way, too).
+	#
+	# "transaction error" messages are currently being ignored. This is because
+	# it is possible to insert the same record twice before the server has notified
+	# a client that the first record was inserted.
     sed -n "$1
 /reset by peer/d
 /Broken pipe/d
@@ -278,6 +282,7 @@ check_logs () {
 /timeval.*context switches: [[0-9]]* voluntary, [[0-9]]* involuntary/d
 /ovs_rcu.*blocked [[0-9]]* ms waiting for .* to quiesce/d
 /Dropped [[0-9]]* log messages/d
+/transaction error/d
 /|WARN|/p
 /|ERR|/p
 /|EMER|/p" ${logs}
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index d1d758c49..b37e135f9 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -549,3 +549,182 @@ done
 
 OVN_CLEANUP([hv1])
 AT_CLEANUP
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn -- MAC bindings from router addresses])
+ovn_start
+
+net_add n1
+for i in 1 2; do
+    sim_add hv$i
+    as hv$i
+    ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.$i.1
+done
+
+AS_BOX([Setting up the logical network])
+# This test ties in heavily with the ovn-northd "Router Address
+# Propagation" test. That test ensures that Port_Binding
+# router_addresses are filled in as expected based on the logical
+# network configuration.
+#
+# This test focuses on ensuring that router_addresses get
+# converted to the expected MAC_Bindings, specifically with regards
+# to ensuring they are created based on is_chassis_resident
+# restrictions. Therefore, it is not necessary to ensure that an
+# exhaustive set of logical network configurations is attempted for
+# this test.
+
+check ovn-nbctl ls-add sw
+
+check ovn-nbctl lr-add ro1
+check ovn-nbctl lrp-add ro1 ro1-sw 00:00:00:00:00:01 10.0.0.1/24
+check ovn-nbctl lsp-add sw sw-ro1
+
+check ovn-nbctl lr-add ro2
+check ovn-nbctl lrp-add ro2 ro2-sw 00:00:00:00:00:02 20.0.0.1/24
+check ovn-nbctl --wait=sb lsp-add sw sw-ro2
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add ls1 vm1
+check ovn-nbctl lsp-set-addresses vm1 "00:00:00:00:01:02 172.16.1.2"
+check ovn-nbctl lrp-add ro1 ro1-ls1 00:00:00:00:01:01 172.16.1.1/24
+check ovn-nbctl lsp-add ls1 ls1-ro1
+check ovn-nbctl lsp-set-type ls1-ro1 router
+check ovn-nbctl lsp-set-addresses ls1-ro1 router
+check ovn-nbctl lsp-set-options ls1-ro1 router-port=ro1-ls1
+
+check ovn-nbctl ls-add ls2
+check ovn-nbctl lsp-add ls2 vm2
+check ovn-nbctl lsp-set-addresses vm2 "00:00:00:00:02:02 172.16.2.2"
+check ovn-nbctl lrp-add ro2 ro2-ls2 00:00:00:00:02:01 172.16.2.1/24
+check ovn-nbctl lsp-add ls2 ls2-ro2
+check ovn-nbctl lsp-set-type ls2-ro2 router
+check ovn-nbctl lsp-set-addresses ls2-ro2 router
+check ovn-nbctl --wait=hv lsp-set-options ls2-ro2 router-port=ro2-ls2
+
+as hv1
+check ovs-vsctl add-port br-int vm1 -- set Interface vm1 external_ids:iface-id=vm1
+as hv2
+check ovs-vsctl add-port br-int vm2 -- set Interface vm2 external_ids:iface-id=vm2
+
+AS_BOX([Checking that no router addresses results in no chassis-specific MAC Bindings])
+
+check_row_count MAC_Binding 0 chassis_name=hv1
+check_row_count MAC_Binding 0 chassis_name=hv2
+
+AS_BOX([Checking that connecting routers results in no chassis-specific MAC Bindings])
+
+check ovn-nbctl lsp-set-type sw-ro1 router
+check ovn-nbctl lsp-set-addresses sw-ro1 router
+check ovn-nbctl lsp-set-options sw-ro1 router-port=ro1-sw
+
+check ovn-nbctl lsp-set-type sw-ro2 router
+check ovn-nbctl lsp-set-addresses sw-ro2 router
+check ovn-nbctl --wait=hv lsp-set-options sw-ro2 router-port=ro2-sw
+
+check_row_count MAC_Binding 0 chassis_name=hv1
+check_row_count MAC_Binding 0 chassis_name=hv2
+
+AS_BOX([Checking that l3dgw routers have chassis-specific MAC Bindings])
+
+check ovn-nbctl lrp-set-gateway-chassis ro1-sw hv1 100
+check ovn-nbctl --wait=hv lrp-set-gateway-chassis ro2-sw hv2 100
+
+check_row_count MAC_Binding 1 chassis_name=hv1
+check_row_count MAC_Binding 1 chassis_name=hv2
+
+check_column "10.0.0.1" MAC_Binding ip chassis_name=hv1
+check_column "00:00:00:00:00:01" MAC_Binding mac chassis_name=hv1
+check_column "ro2-sw" MAC_Binding logical_port chassis_name=hv1
+
+check_column "20.0.0.1" MAC_Binding ip chassis_name=hv2
+check_column "00:00:00:00:00:02" MAC_Binding mac chassis_name=hv2
+check_column "ro1-sw" MAC_Binding logical_port chassis_name=hv2
+
+AS_BOX([Checking that NAT addresses result in chassis-specific MAC Bindings])
+
+check ovn-nbctl lr-nat-add ro1 dnat 10.0.0.100 172.16.1.100
+check ovn-nbctl --wait=hv lr-nat-add ro1 snat 10.0.0.200 172.16.1.200/30
+
+# hv1 installs MAC bindings for the ro1's router address and two NAT addresses
+check_row_count MAC_Binding 3 chassis_name=hv1
+# hv2 only installs MAC bindings for ro2's router address
+check_row_count MAC_Binding 1 chassis_name=hv2
+
+check_column "10.0.0.1 10.0.0.100 10.0.0.200" MAC_Binding ip chassis_name=hv1
+check_column "00:00:00:00:00:01 00:00:00:00:00:01 00:00:00:00:00:01" MAC_Binding mac chassis_name=hv1
+check_column "ro2-sw ro2-sw ro2-sw" MAC_Binding logical_port chassis_name=hv1
+
+check_column "20.0.0.1" MAC_Binding ip chassis_name=hv2
+check_column "00:00:00:00:00:02" MAC_Binding mac chassis_name=hv2
+check_column "ro1-sw" MAC_Binding logical_port chassis_name=hv2
+
+AS_BOX([Checking that changing which router has NAT addresses results in correct MAC Bindings])
+
+check ovn-nbctl lr-nat-del ro1
+
+check ovn-nbctl lr-nat-add ro2 dnat 20.0.0.100 172.16.2.100
+check ovn-nbctl --wait=hv lr-nat-add ro2 snat 20.0.0.200 172.16.2.200/30
+
+# hv1 installs MAC bindings for ro1's router address
+check_row_count MAC_Binding 1 chassis_name=hv1
+# hv2 installs MAC bindings for ro2's router address and two NAT addresses
+check_row_count MAC_Binding 3 chassis_name=hv2
+
+check_column "10.0.0.1" MAC_Binding ip chassis_name=hv1
+check_column "00:00:00:00:00:01" MAC_Binding mac chassis_name=hv1
+check_column "ro2-sw" MAC_Binding logical_port chassis_name=hv1
+
+check_column "20.0.0.1 20.0.0.100 20.0.0.200" MAC_Binding ip chassis_name=hv2
+check_column "00:00:00:00:00:02 00:00:00:00:00:02 00:00:00:00:00:02" MAC_Binding mac chassis_name=hv2
+check_column "ro1-sw ro1-sw ro1-sw" MAC_Binding logical_port chassis_name=hv2
+
+AS_BOX([Checking that Floating IP NAT address configured on router results in MAC Binding])
+
+check ovn-nbctl lr-nat-del ro2
+
+check ovn-nbctl --wait=hv lr-nat-add ro1 dnat_and_snat 10.0.0.100 172.16.1.100 vm1 00:00:00:00:00:01
+
+# HV1 will have MAC bindings for the router address and the Floating IP NAT since vm1 is bound on hv1
+check_row_count MAC_Binding 2 chassis_name=hv1
+check_row_count MAC_Binding 1 chassis_name=hv2
+
+check_column "10.0.0.1 10.0.0.100" MAC_Binding ip chassis_name=hv1
+check_column "00:00:00:00:00:01 00:00:00:00:00:01" MAC_Binding mac chassis_name=hv1
+check_column "ro2-sw ro2-sw" MAC_Binding logical_port chassis_name=hv1
+
+check_column "20.0.0.1" MAC_Binding ip chassis_name=hv2
+check_column "00:00:00:00:00:02" MAC_Binding mac chassis_name=hv2
+check_column "ro1-sw" MAC_Binding logical_port chassis_name=hv2
+
+AS_BOX([Checking that un-binding vm results in removal of Floating IP MAC Binding])
+
+as hv1
+check ovs-vsctl del-port br-int vm1
+
+# This will be exactly as if no NATs were configured.
+# Use wait_row_count instead of check_row_count here since
+# we don't have an equivalent to --wait=hv in ovs-vsctl.
+wait_row_count MAC_Binding 1 chassis_name=hv1
+check_row_count MAC_Binding 1 chassis_name=hv2
+
+check_column "10.0.0.1" MAC_Binding ip chassis_name=hv1
+check_column "00:00:00:00:00:01" MAC_Binding mac chassis_name=hv1
+check_column "ro2-sw" MAC_Binding logical_port chassis_name=hv1
+
+check_column "20.0.0.1" MAC_Binding ip chassis_name=hv2
+check_column "00:00:00:00:00:02" MAC_Binding mac chassis_name=hv2
+check_column "ro1-sw" MAC_Binding logical_port chassis_name=hv2
+
+AS_BOX([Ensure that disconnecting routers results in removal of all MAC Bindings])
+
+check ovn-nbctl clear logical_switch_port sw-ro1 options
+check ovn-nbctl --wait=hv clear logical_switch_port sw-ro2 options
+
+check_row_count MAC_Binding 0 chassis_name=hv1
+check_row_count MAC_Binding 0 chassis_name=hv2
+
+OVN_CLEANUP([hv1],[hv2])
+AT_CLEANUP
+])
