From: Karthik Chandrashekar <[email protected]>

Add a new NB MAC_Binding table. This allows programming of
static mac_bindings for a logical_router. OVN northd is
responsible for propagating these static entries to the
existing SB MAC_Binding table.

Signed-off-by: Karthik Chandrashekar <[email protected]>
---
 controller/pinctrl.c    |  20 +----
 lib/automake.mk         |   2 +
 lib/mac-binding-index.c |  43 +++++++++
 lib/mac-binding-index.h |  29 ++++++
 northd/northd.c         |  31 +++++++
 northd/northd.h         |   1 +
 northd/ovn-northd.c     |   5 ++
 northd/ovn_northd.dl    |   9 ++
 ovn-nb.ovsschema        |  13 ++-
 ovn-nb.xml              |  25 ++++++
 tests/ovn-nbctl.at      |  69 ++++++++++++++
 tests/ovn-northd.at     |  26 ++++++
 utilities/ovn-nbctl.c   | 193 +++++++++++++++++++++++++++++++++++++++-
 13 files changed, 441 insertions(+), 25 deletions(-)
 create mode 100644 lib/mac-binding-index.c
 create mode 100644 lib/mac-binding-index.h

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 7d01b18a3..af12909fa 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -48,6 +48,7 @@
 #include "ovn/lex.h"
 #include "lib/acl-log.h"
 #include "lib/ip-mcast-index.h"
+#include "lib/mac-binding-index.h"
 #include "lib/mcast-group-index.h"
 #include "lib/ovn-l7.h"
 #include "lib/ovn-util.h"
@@ -4090,25 +4091,6 @@ send_mac_binding_buffered_pkts(struct rconn *swconn)
     ovs_list_init(&buffered_mac_bindings);
 }
 
-static const struct sbrec_mac_binding *
-mac_binding_lookup(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                   const char *logical_port,
-                   const char *ip)
-{
-    struct sbrec_mac_binding *mb = sbrec_mac_binding_index_init_row(
-        sbrec_mac_binding_by_lport_ip);
-    sbrec_mac_binding_index_set_logical_port(mb, logical_port);
-    sbrec_mac_binding_index_set_ip(mb, ip);
-
-    const struct sbrec_mac_binding *retval
-        = sbrec_mac_binding_index_find(sbrec_mac_binding_by_lport_ip,
-                                       mb);
-
-    sbrec_mac_binding_index_destroy_row(mb);
-
-    return retval;
-}
-
 /* Update or add an IP-MAC binding for 'logical_port'.
  * Caller should make sure that 'ovnsb_idl_txn' is valid. */
 static void
diff --git a/lib/automake.mk b/lib/automake.mk
index 9f9f447d5..b58acf1d0 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -21,6 +21,8 @@ lib_libovn_la_SOURCES = \
        lib/ovn-parallel-hmap.c \
        lib/ip-mcast-index.c \
        lib/ip-mcast-index.h \
+       lib/mac-binding-index.c \
+       lib/mac-binding-index.h \
        lib/mcast-group-index.c \
        lib/mcast-group-index.h \
        lib/lex.c \
diff --git a/lib/mac-binding-index.c b/lib/mac-binding-index.c
new file mode 100644
index 000000000..1d34dfbdc
--- /dev/null
+++ b/lib/mac-binding-index.c
@@ -0,0 +1,43 @@
+/* Copyright (c) 2021
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lib/mac-binding-index.h"
+#include "lib/ovn-sb-idl.h"
+
+struct ovsdb_idl_index *
+mac_binding_index_create(struct ovsdb_idl *idl)
+{
+    return ovsdb_idl_index_create2(idl,
+                                   &sbrec_mac_binding_col_logical_port,
+                                   &sbrec_mac_binding_col_ip);
+}
+
+const struct sbrec_mac_binding *
+mac_binding_lookup(struct ovsdb_idl_index *mac_binding_index,
+                   const char *logical_port, const char *ip)
+{
+    struct sbrec_mac_binding *target = sbrec_mac_binding_index_init_row(
+        mac_binding_index);
+    sbrec_mac_binding_index_set_logical_port(target, logical_port);
+    sbrec_mac_binding_index_set_ip(target, ip);
+
+    struct sbrec_mac_binding *mac_binding =
+        sbrec_mac_binding_index_find(mac_binding_index, target);
+    sbrec_mac_binding_index_destroy_row(target);
+
+    return mac_binding;
+}
diff --git a/lib/mac-binding-index.h b/lib/mac-binding-index.h
new file mode 100644
index 000000000..d7434f688
--- /dev/null
+++ b/lib/mac-binding-index.h
@@ -0,0 +1,29 @@
+/* Copyright (c) 2021
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_MAC_BINDING_INDEX_H
+#define OVN_MAC_BINDING_INDEX_H 1
+
+struct ovsdb_idl;
+
+struct sbrec_datapath_binding;
+
+struct ovsdb_idl_index *mac_binding_index_create(struct ovsdb_idl *);
+const struct sbrec_mac_binding *mac_binding_lookup(
+    struct ovsdb_idl_index *mac_binding_index,
+    const char *logical_port,
+    const char *ip);
+
+#endif /* lib/mac-binding-index.h */
diff --git a/northd/northd.c b/northd/northd.c
index 0bf66e0b2..b2270fd0e 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -29,6 +29,7 @@
 #include "lib/chassis-index.h"
 #include "lib/ip-mcast-index.h"
 #include "lib/copp.h"
+#include "lib/mac-binding-index.h"
 #include "lib/mcast-group-index.h"
 #include "lib/ovn-l7.h"
 #include "lib/ovn-nb-idl.h"
@@ -8368,6 +8369,35 @@ build_bfd_table(struct northd_context *ctx, struct hmap 
*bfd_connections,
     bitmap_free(bfd_src_ports);
 }
 
+static void
+build_mac_binding_table(struct northd_context *ctx, struct hmap *ports)
+{
+    const struct nbrec_mac_binding *nb_mac_binding;
+    NBREC_MAC_BINDING_FOR_EACH (nb_mac_binding, ctx->ovnnb_idl) {
+        struct ovn_port *op = ovn_port_find(
+            ports, nb_mac_binding->logical_port);
+        if (op && op->nbrp) {
+            struct ovn_datapath *od = op->od;
+            if (od && od->sb) {
+                const struct sbrec_mac_binding *mb =
+                    mac_binding_lookup(ctx->sbrec_mac_binding_by_lport_ip,
+                                       nb_mac_binding->logical_port,
+                                       nb_mac_binding->ip);
+                if (!mb) {
+                    mb = sbrec_mac_binding_insert(ctx->ovnsb_txn);
+                    sbrec_mac_binding_set_logical_port(
+                        mb, nb_mac_binding->logical_port);
+                    sbrec_mac_binding_set_ip(mb, nb_mac_binding->ip);
+                    sbrec_mac_binding_set_mac(mb, nb_mac_binding->mac);
+                    sbrec_mac_binding_set_datapath(mb, od->sb);
+                } else {
+                    sbrec_mac_binding_set_mac(mb, nb_mac_binding->mac);
+                }
+            }
+        }
+    }
+}
+
 /* Returns a string of the IP address of the router port 'op' that
  * overlaps with 'ip_s".  If one is not found, returns NULL.
  *
@@ -14465,6 +14495,7 @@ ovnnb_db_run(struct northd_context *ctx,
     build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups);
     build_meter_groups(ctx, &meter_groups);
     build_bfd_table(ctx, &bfd_connections, ports);
+    build_mac_binding_table(ctx, ports);
     stopwatch_stop(BUILD_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
     stopwatch_start(BUILD_LFLOWS_STOPWATCH_NAME, time_msec());
     build_lflows(ctx, datapaths, ports, &port_groups, &mcast_groups,
diff --git a/northd/northd.h b/northd/northd.h
index ffa2bbb4e..59f407332 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -27,6 +27,7 @@ struct northd_context {
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
     struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp;
     struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
+    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip;
 
     bool use_parallel_build;
 };
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 39aa96055..5fba98812 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -23,6 +23,7 @@
 #include "daemon.h"
 #include "fatal-signal.h"
 #include "lib/ip-mcast-index.h"
+#include "lib/mac-binding-index.h"
 #include "lib/mcast-group-index.h"
 #include "memory.h"
 #include "northd.h"
@@ -904,6 +905,9 @@ main(int argc, char *argv[])
     struct ovsdb_idl_index *sbrec_ip_mcast_by_dp
         = ip_mcast_index_create(ovnsb_idl_loop.idl);
 
+    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip
+        = mac_binding_index_create(ovnsb_idl_loop.idl);
+
     unixctl_command_register("sb-connection-status", "", 0, 0,
                              ovn_conn_show, ovnsb_idl_loop.idl);
 
@@ -959,6 +963,7 @@ main(int argc, char *argv[])
                 .sbrec_ha_chassis_grp_by_name = sbrec_ha_chassis_grp_by_name,
                 .sbrec_mcast_group_by_name_dp = sbrec_mcast_group_by_name_dp,
                 .sbrec_ip_mcast_by_dp = sbrec_ip_mcast_by_dp,
+                .sbrec_mac_binding_by_lport_ip = sbrec_mac_binding_by_lport_ip,
                 .use_parallel_build = use_parallel_build,
             };
 
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 99772932d..0e96b5de1 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -1018,6 +1018,15 @@ sb::Out_MAC_Binding (._uuid        = mb._uuid,
     sb::Out_Port_Binding(.logical_port = mb.logical_port),
     sb::Out_Datapath_Binding(._uuid = mb.datapath).
 
+sb::Out_MAC_Binding (._uuid        = hash,
+                    .logical_port = nb.logical_port,
+                    .ip           = nb.ip,
+                    .mac          = nb.mac,
+                    .datapath     = router._uuid) :-
+    nb in nb::MAC_Binding(),
+    rp in &RouterPort(.router = router, .json_name = 
json_escape(nb.logical_port)),
+    var hash = hash128((nb.logical_port, nb.ip)).
+
 /*
  * DHCP options: fixed table
  */
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 5dee04fe9..27dc91438 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Northbound",
-    "version": "5.33.1",
-    "cksum": "1931852754 30731",
+    "version": "5.34.0",
+    "cksum": "1872933846 30998",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -595,5 +595,12 @@
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
             "indexes": [["logical_port", "dst_ip"]],
-            "isRoot": true}}
+            "isRoot": true},
+        "MAC_Binding": {
+            "columns": {
+                "logical_port": {"type": "string"},
+                "ip": {"type": "string"},
+                "mac": {"type": "string"}},
+            "indexes": [["logical_port", "ip"]],
+             "isRoot": true}}
     }
diff --git a/ovn-nb.xml b/ovn-nb.xml
index e31578fb6..327039d95 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -4130,4 +4130,29 @@
       </column>
     </group>
   </table>
+
+  <table name="MAC_Binding">
+    <p>
+      Each record represents a MAC Binding entry.
+    </p>
+
+    <group title="Configuration">
+      <p>
+        <code>ovn-northd</code> reads configuration from these columns
+        and propagates the value to SBDB.
+      </p>
+
+      <column name="logical_port">
+        The logical port on which the binding was discovered.
+      </column>
+
+      <column name="ip">
+        The bound IP address.
+      </column>
+
+      <column name="mac">
+        The Ethernet address to which the IP is bound.
+      </column>
+    </group>
+  </table>
 </database>
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 9b80ae410..673c9637d 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -2057,6 +2057,75 @@ AT_CHECK([ovn-nbctl list forwarding_group], [0], [])
 
 dnl ---------------------------------------------------------------------
 
+OVN_NBCTL_TEST([ovn_nbctl_mac_binding], [lr mac_binding], [
+
+AT_CHECK([ovn-nbctl lr-add lr0])
+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24])
+AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p1 00:00:02:02:03:04 192.168.11.1/24])
+
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p0 192.168.10.10 00:00:11:22:33:44])
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:44:55])
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p0 10.0.0.10 00:00:33:44:55:66])
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:88])
+
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p0 foo 00:00:44:55:66:88], [1], [],
+  [ovn-nbctl: foo: Not a valid IPv4 or IPv6 address.
+])
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p0 172.16.0.200 foo], [1], [],
+  [ovn-nbctl: invalid mac address foo.
+])
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p0 172.16.0.11 00:00:44:55:66:77], 
[1], [],
+  [ovn-nbctl: lr0-p0, 172.16.0.11: a MAC_Binding with this logical_port and ip 
already exists
+])
+
+AT_CHECK([ovn-nbctl --may-exist mac-binding-add lr0-p0 172.16.0.11 
00:00:44:55:66:77])
+
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p1 10.0.0.10 00:00:33:44:55:66])
+AT_CHECK([ovn-nbctl mac-binding-add lr0-p1 172.16.0.11 00:00:44:55:66:88])
+
+AT_CHECK([ovn-nbctl mac-binding-list], [0], [dnl
+LOGICAL_PORT             IP                       MAC
+lr0-p0                   10.0.0.10                00:00:33:44:55:66
+lr0-p0                   172.16.0.11              00:00:44:55:66:77
+lr0-p0                   192.168.10.10            00:00:11:22:33:44
+lr0-p0                   192.168.10.100           00:00:22:33:44:55
+lr0-p1                   10.0.0.10                00:00:33:44:55:66
+lr0-p1                   172.16.0.11              00:00:44:55:66:88
+])
+
+AT_CHECK([ovn-nbctl mac-binding-del lr0-p0 foo], [1], [],
+  [ovn-nbctl: foo: Not a valid IPv4 or IPv6 address.
+])
+
+AT_CHECK([ovn-nbctl mac-binding-del lr0-p1 10.0.0.100], [1], [],
+  [ovn-nbctl: no matching MAC_Binding with port (lr0-p1) and ip (10.0.0.100)
+])
+
+AT_CHECK([ovn-nbctl --if-exists mac-binding-del lr0-p1 10.0.0.100])
+
+AT_CHECK([ovn-nbctl mac-binding-del lr0-p0 10.0.0.10])
+AT_CHECK([ovn-nbctl mac-binding-del lr0-p0 192.168.10.100])
+
+AT_CHECK([ovn-nbctl mac-binding-list], [0], [dnl
+LOGICAL_PORT             IP                       MAC
+lr0-p0                   172.16.0.11              00:00:44:55:66:77
+lr0-p0                   192.168.10.10            00:00:11:22:33:44
+lr0-p1                   10.0.0.10                00:00:33:44:55:66
+lr0-p1                   172.16.0.11              00:00:44:55:66:88
+])
+
+AT_CHECK([ovn-nbctl mac-binding-del lr0-p1 10.0.0.10])
+AT_CHECK([ovn-nbctl mac-binding-list], [0], [dnl
+LOGICAL_PORT             IP                       MAC
+lr0-p0                   172.16.0.11              00:00:44:55:66:77
+lr0-p0                   192.168.10.10            00:00:11:22:33:44
+lr0-p1                   172.16.0.11              00:00:44:55:66:88
+])
+
+])
+
+dnl ---------------------------------------------------------------------
+
 OVN_NBCTL_TEST([ovn_nbctl_negative], [basic negative tests], [
 AT_CHECK([ovn-nbctl --id=@ls create logical_switch name=foo -- \
           set logical_switch foo1 name=bar],
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 8b9049899..2e6a51f2e 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -5401,3 +5401,29 @@ AT_CHECK([grep lr_in_gw_redirect lrflows | grep cr-DR | 
sed 's/table=../table=??
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([LR NB MAC_Binding table])
+ovn_start
+
+# Create logical routers
+ovn-nbctl lr-add lr0
+ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24
+ovn-nbctl lrp-add lr0 lr0-p1 00:00:02:02:03:04 192.168.11.1/24
+
+ovn-nbctl mac-binding-add lr0-p0 192.168.10.10 00:00:11:22:33:44
+ovn-nbctl mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:44:55
+
+wait_row_count nb:MAC_Binding 2 logical_port=lr0-p0
+wait_row_count MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.10 
mac="00\:00\:11\:22\:33\:44"
+wait_row_count MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.100 
mac="00\:00\:22\:33\:44\:55"
+
+ovn-nbctl mac-binding-add lr0-p1 10.0.0.10 00:00:33:44:55:66
+wait_row_count nb:MAC_Binding 1 logical_port=lr0-p1
+wait_row_count MAC_Binding 1 logical_port=lr0-p1 ip=10.0.0.10 
mac="00\:00\:33\:44\:55\:66"
+
+ovn-nbctl --may-exist mac-binding-add lr0-p0 192.168.10.100 00:00:22:33:55:66
+wait_row_count MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.100 
mac="00\:00\:22\:33\:55\:66"
+
+AT_CLEANUP
+])
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index b04b24d4b..527fe1644 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -365,7 +365,8 @@ Policy commands:\n\
   lr-policy-del ROUTER [{PRIORITY | UUID} [MATCH]]\n\
                             remove policies from ROUTER\n\
   lr-policy-list ROUTER     print policies for ROUTER\n\
-\n\
+\n\n",program_name, program_name);
+    printf("\
 NAT commands:\n\
   [--stateless]\n\
   [--portrange]\n\
@@ -408,8 +409,7 @@ Connection commands:\n\
   del-connection             delete the connections\n\
   [--inactivity-probe=MSECS]\n\
   set-connection TARGET...   set the list of connections to TARGET...\n\
-\n\n",program_name, program_name);
-    printf("\
+\n\
 SSL commands:\n\
   get-ssl                     print the SSL configuration\n\
   del-ssl                     delete the SSL configuration\n\
@@ -450,6 +450,13 @@ Control Plane Protection Policy commands:\n\
                             List all copp policies defined for control\n\
                             protocols on ROUTER.\n\
 \n\
+MAC_Binding commands:\n\
+  mac-binding-add LOGICAL_PORT IP MAC \n\
+                                    Add a MAC_Binding entry\n\
+  mac-binding-del LOGICAL_PORT IP \n\
+                                    Delete MAC_Binding entry\n\
+  mac-binding-list                  List all MAC_Binding \n\
+\n\
 %s\
 %s\
 \n\
@@ -5602,6 +5609,176 @@ nbctl_lrp_get_redirect_type(struct ctl_context *ctx)
                   !redirect_type ? "overlay": redirect_type);
 }
 
+static const struct nbrec_mac_binding *
+mac_binding_by_port_ip(struct ctl_context *ctx,
+                           const char *logical_port, const char *ip)
+{
+    const struct nbrec_mac_binding *nb_mac_binding = NULL;
+
+    NBREC_MAC_BINDING_FOR_EACH(nb_mac_binding, ctx->idl) {
+        if (!strcmp(nb_mac_binding->logical_port, logical_port) &&
+            !strcmp(nb_mac_binding->ip, ip)) {
+            break;
+        }
+    }
+
+    return nb_mac_binding;
+}
+
+static void
+nbctl_pre_mac_binding_add(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
+
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_logical_port);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_ip);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_mac);
+}
+
+static void
+nbctl_mac_binding_add(struct ctl_context *ctx)
+{
+    const char *logical_port = ctx->argv[1];
+    const char *ip = ctx->argv[2];
+    const char *mac = ctx->argv[3];
+    char *new_ip = NULL;
+
+    const struct nbrec_logical_router_port *lrp;
+    char *error = lrp_by_name_or_uuid(ctx, logical_port, true, &lrp);
+    if (error) {
+        ctx->error = error;
+        goto cleanup;
+    }
+
+    new_ip = normalize_addr_str(ip);
+    if (!new_ip) {
+        ctl_error(ctx, "%s: Not a valid IPv4 or IPv6 address.", ip);
+        return;
+    }
+
+    struct eth_addr ea;
+    if (!eth_addr_from_string(mac, &ea)) {
+        ctl_error(ctx, "invalid mac address %s.", mac);
+        goto cleanup;
+    }
+
+    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+    const struct nbrec_mac_binding *nb_mac_binding = mac_binding_by_port_ip(
+        ctx, logical_port, ip);
+    if (nb_mac_binding) {
+        char *old_ip;
+        bool should_return = false;
+        old_ip = normalize_addr_str(nb_mac_binding->ip);
+
+        if (!strcmp(nb_mac_binding->logical_port, logical_port)) {
+            if (!strcmp(old_ip, new_ip)) {
+                if (may_exist) {
+                    nbrec_mac_binding_verify_mac(nb_mac_binding);
+                    nbrec_mac_binding_set_mac(nb_mac_binding, mac);
+                    should_return = true;
+                } else {
+                    ctl_error(ctx, "%s, %s: a MAC_Binding with this "
+                              "logical_port and ip already exists",
+                              logical_port, new_ip);
+                    should_return = true;
+                }
+            }
+        }
+        free(old_ip);
+        if (should_return) {
+            goto cleanup;
+        }
+    }
+
+    /* Create MAC_Binding entry */
+    nb_mac_binding = nbrec_mac_binding_insert(ctx->txn);
+    nbrec_mac_binding_set_logical_port(nb_mac_binding, logical_port);
+    nbrec_mac_binding_set_ip(nb_mac_binding, new_ip);
+    nbrec_mac_binding_set_mac(nb_mac_binding, mac);
+
+cleanup:
+    free(new_ip);
+}
+
+static void
+nbctl_pre_mac_binding_del(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
+
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_logical_port);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_ip);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_mac);
+}
+
+static void
+nbctl_mac_binding_del(struct ctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const char *logical_port = ctx->argv[1];
+    const struct nbrec_logical_router_port *lrp;
+    char *error = lrp_by_name_or_uuid(ctx, logical_port, true, &lrp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    char *ip = normalize_addr_str(ctx->argv[2]);
+    if (!ip) {
+        ctl_error(ctx, "%s: Not a valid IPv4 or IPv6 address.", ctx->argv[2]);
+        return;
+    }
+
+    const struct nbrec_mac_binding *nb_mac_binding = mac_binding_by_port_ip(
+        ctx, logical_port, ip);
+
+    if (nb_mac_binding) {
+        /* Remove the matching MAC_Binding. */
+        nbrec_mac_binding_delete(nb_mac_binding);
+        goto cleanup;
+    }
+
+    if (must_exist) {
+        ctl_error(ctx, "no matching MAC_Binding with port (%s) and ip (%s)",
+                  logical_port, ip);
+    }
+
+cleanup:
+    free(ip);
+}
+
+static void
+nbctl_pre_mac_binding_list(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_name);
+
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_logical_port);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_ip);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mac_binding_col_mac);
+}
+
+static void
+nbctl_mac_binding_list(struct ctl_context *ctx)
+{
+    struct smap lr_mac_bindings = SMAP_INITIALIZER(&lr_mac_bindings);
+    const struct nbrec_mac_binding *nb_mac_binding = NULL;
+    NBREC_MAC_BINDING_FOR_EACH(nb_mac_binding, ctx->idl) {
+        char *key = xasprintf("%-25s%-25s", nb_mac_binding->logical_port,
+                              nb_mac_binding->ip);
+        smap_add_format(&lr_mac_bindings, key, "%s", nb_mac_binding->mac);
+        free(key);
+    }
+
+    const struct smap_node **nodes = smap_sort(&lr_mac_bindings);
+    if (nodes) {
+        ds_put_format(&ctx->output, "%-25s%-25s%s\n",
+                      "LOGICAL_PORT", "IP", "MAC");
+        for (size_t i = 0; i < smap_count(&lr_mac_bindings); i++) {
+            const struct smap_node *node = nodes[i];
+            ds_put_format(&ctx->output, "%-25s%s\n", node->key, node->value);
+        }
+    }
+}
+
 static const struct nbrec_forwarding_group *
 fwd_group_by_name_or_uuid(struct ctl_context *ctx, const char *id)
 {
@@ -7063,6 +7240,16 @@ static const struct ctl_command_syntax nbctl_commands[] 
= {
      pre_ha_ch_grp_set_chassis_prio, cmd_ha_ch_grp_set_chassis_prio, NULL,
      "", RW },
 
+    /* MAC_Binding commands */
+    { "mac-binding-add", 3, 3, "LOGICAL_PORT IP MAC",
+      nbctl_pre_mac_binding_add, nbctl_mac_binding_add, NULL,
+      "--may-exist", RW },
+    { "mac-binding-del", 2, 2, "LOGICAL_PORT IP",
+      nbctl_pre_mac_binding_del, nbctl_mac_binding_del,
+      NULL, "--if-exists", RW },
+    { "mac-binding-list", 0, 1, "[LOGICAL_PORT]",
+      nbctl_pre_mac_binding_list, nbctl_mac_binding_list, NULL, "", RO },
+
     {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO},
 };
 
-- 
2.25.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to