diff mbox series

[ovs-dev,v4,4/5] pinctrl: Add Chassis MAC_Bindings for all router addresses.

Message ID 20210414205429.3074290-5-mmichels@redhat.com
State Superseded
Headers show
Series ARP and Floating IP Fixes | expand

Commit Message

Mark Michelson April 14, 2021, 8:54 p.m. UTC
Previous behavior of ovn-controller was to only create MAC bindings for
the same addresses for which we would send gARPs. That is:

* A logical switch has its "nat_addresses" configured.
* That logical switch has a localnet port.

This makes sense for gARPs: we only want to send gARPs for addresses
accessible from outside of OVN, and we only want to send gARPs for
addresses explicitly enabled by the administrator.

For directly adding MAC bindings, though, this makes less sense. In the
case where a VM or pod on the underlay network wishes to reach another
VM or pod via a DNAT or load balancer address, an ARP was required to
learn the MAC binding. When the information is all available in the
database, this seems like an unnecessary step.

This commit makes the following changes:
* MAC_Binding now has a "chassis_name" column. This is used to identify
  the chassis that added the MAC Binding IF the MAC Binding was auto-
  generated by ovn-controller. This is also used to ensure that stale
  bindings can be cleaned up.
* The term "local garp" has been replaced with "chassis MAC binding".
  Removing the word "garp" helps to remove any implication of packets
  being transmitted. The word "chassis" precedes "MAC binding" to
  indicate it is a MAC binding added by a chassis and with a
  chassis_name in its records. MAC bindings with no chassis_name were
  added dynamically due to reception of ARP/gARP packets.
* Chassis MAC Binding code has been separated from gARP code since they
  do not operate using the same sets of addresses.
* Chassis MAC Bindings are added for all router_addresses on a port
  binding. This means all NAT, Load Balancer, and router interface
  addresses are added for routers that are residents of the chassis.

Signed-off-by: Mark Michelson <mmichels@redhat.com>
---
 controller/ovn-controller.c |   4 +
 controller/pinctrl.c        | 295 +++++++++++++++++++++++++++---------
 controller/pinctrl.h        |   1 +
 northd/ovn-northd.c         |   2 +-
 northd/ovn_northd.dl        |   5 +-
 ovn-sb.ovsschema            |   5 +-
 ovn-sb.xml                  |   7 +
 tests/ovn-controller.at     | 179 ++++++++++++++++++++++
 8 files changed, 424 insertions(+), 74 deletions(-)
diff mbox series

Patch

diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 16c8ecb21..872d68799 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -2507,6 +2507,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,
@@ -2931,6 +2934,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..03d706d89 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) {
@@ -5568,7 +5536,7 @@  get_nat_addresses_and_keys(struct ovsdb_idl_index *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 (int 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,162 @@  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);
+            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);
+        }
+    }
+}
+
 /* Called by pinctrl_run(). Runs with in the main ovn-controller
  * thread context. */
 static void
@@ -5625,6 +5756,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 +5767,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 +5785,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 +5803,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 +5817,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 +5841,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 10a0cc2f4..ad62ae220 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -13695,7 +13695,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 2be6591e9..089947516 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -955,7 +955,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).
@@ -1312,7 +1313,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/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
+])