When a Logical_Router_Port has DHCP relay enabled and multiple IPv4
addresses configured, ovn-northd needs to pick one of them as the
relay agent address (giaddr).  This choice was hard-coded to
lrp_networks.ipv4_addrs[0] which depends entirely on internal
ordering.

The 'networks' column on Logical_Router_Port is an OVSDB set whose
elements are sorted by strcmp() on the raw CIDR strings.
This makes lrp_networks.ipv4_addrs[0] simply whichever CIDR sorts
first as a string, which may not match the address intended as
the "DHCP relay" address of the port.

Add a new key 'options:dhcpv4_primary_ip' on Logical_Router_Port.
When set, it must be an IPv4 host address that equals one of the
port's configured addresses; ovn-northd then uses that address as
the giaddr in all DHCP relay flows it emits for the port.  When
unset, behavior is unchanged: ovn-northd falls back to
lrp_networks.ipv4_addrs[0] (whichever CIDR sorts first as a string),
so existing deployments are unaffected on upgrade.

If 'options:dhcpv4_primary_ip' is set but is not a valid IPv4
address or does not match any address in 'networks', ovn-northd
treats it as a misconfiguration: it logs a warning and skips
emitting DHCP relay flows for the port rather than silently falling
back to the default address.

Reported-by: Dumitru Ceara <[email protected]>
Signed-off-by: Naveen Yerramneni <[email protected]>
Acked-by: Aditya Mehakare <[email protected]>
Acked-by: Huzaifa Calcuttawala <[email protected]>
Acked-by: Lorenzo Bianconi <[email protected]>
Assisted-by: Claude Opus 4.7, Cursor
---
 NEWS                |   8 ++++
 northd/northd.c     |  99 +++++++++++++++++++++++++++++++++--------
 ovn-nb.xml          |  24 ++++++++++
 tests/ovn-northd.at | 104 ++++++++++++++++++++++++++++++++++++++------
 4 files changed, 202 insertions(+), 33 deletions(-)

diff --git a/NEWS b/NEWS
index b233ee01a..e22c983c2 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,13 @@
 Post v26.03.0
 -------------
+   - Logical_Router_Port: Added a new "options:dhcpv4_primary_ip" key
+     that selects which of the port's IPv4 addresses ovn-northd uses as
+     the relay agent address (giaddr) in relayed DHCP packets.  When
+     unset, ovn-northd picks the same address it picked before
+     (whichever CIDR in "networks" sorts first as a string), so
+     existing deployments are unaffected; deployments that need a
+     specific address on a multi-IP port can pin it via the new
+     option.
    - OVN Interconnection now supports advertising Address_Sets to remote
      availability zones.  Set options:ic-adv=true on an NB Address_Set to
      have ovn-ic publish it to the IC-SB database and import it into the
diff --git a/northd/northd.c b/northd/northd.c
index 4ca9820d4..b00141ae4 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -10209,6 +10209,9 @@ ls_dhcp_relay_port(const struct ovn_datapath *od)
     return smap_get(&od->nbs->other_config, "dhcp_relay_port");
 }
 
+static const struct ipv4_netaddr *lrp_get_dhcpv4_primary_ip(
+    const struct ovn_port *op);
+
 static void
 build_lswitch_dhcp_relay_flows(struct ovn_port *op,
                                const struct hmap *ls_ports,
@@ -10248,6 +10251,12 @@ build_lswitch_dhcp_relay_flows(struct ovn_port *op,
         return;
     }
 
+    /* Resolve the LRP's DHCPv4 primary IP (giaddr). */
+    const struct ipv4_netaddr *lrp_ipv4_addr = lrp_get_dhcpv4_primary_ip(rp);
+    if (!lrp_ipv4_addr) {
+        return;
+    }
+
     char *server_ip_str = NULL;
     uint16_t port;
     int addr_family;
@@ -10271,8 +10280,7 @@ build_lswitch_dhcp_relay_flows(struct ovn_port *op,
         "ip4.src == {0.0.0.0, %s/%u} && ip4.dst == 255.255.255.255 && "
         "udp.src == 68 && udp.dst == 67",
         op->json_key, op->lsp_addrs[0].ea_s,
-        rp->lrp_networks.ipv4_addrs[0].network_s,
-        rp->lrp_networks.ipv4_addrs[0].plen);
+        lrp_ipv4_addr->network_s, lrp_ipv4_addr->plen);
     ds_put_format(actions,
                   "eth.dst = %s; outport = %s; next; /* DHCP_RELAY_REQ */",
                   rp->lrp_networks.ea_s, sp->json_key);
@@ -16901,6 +16909,59 @@ build_dhcpv6_reply_flows_for_lrouter_port(
     }
 }
 
+/* Resolve the LRP's DHCPv4 "primary" IPv4 address - the address used
+ * by DHCP relay as the relay agent address (giaddr) in relayed DHCP
+ * packets on this port.
+ *
+ * If 'options:dhcpv4_primary_ip' is set, returns the entry from
+ * 'lrp_networks' whose address matches it.  If the option is unset,
+ * returns lrp_networks.ipv4_addrs[0] - the first address from
+ * 'networks' in OVSDB sort order (set members are sorted by strcmp()
+ * on their string form).
+ *
+ * Returns NULL when the port has no IPv4 address at all, or when
+ * 'options:dhcpv4_primary_ip' is set but is not a valid IPv4 address
+ * or does not match any address on the port.  Callers must skip
+ * emitting DHCP relay flows in those cases.
+ */
+static const struct ipv4_netaddr *
+lrp_get_dhcpv4_primary_ip(const struct ovn_port *op)
+{
+    if (!op->lrp_networks.n_ipv4_addrs) {
+        return NULL;
+    }
+
+    const char *primary_ip = smap_get(&op->nbrp->options,
+                                      "dhcpv4_primary_ip");
+
+    if (!primary_ip || !primary_ip[0]) {
+        return &op->lrp_networks.ipv4_addrs[0];
+    }
+
+    ovs_be32 primary_ip4;
+    if (!ip_parse(primary_ip, &primary_ip4)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl,
+                     "Logical_Router_Port %s: options:dhcpv4_primary_ip=%s "
+                     "is not a valid IPv4 address.",
+                     op->nbrp->name, primary_ip);
+        return NULL;
+    }
+
+    for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+        if (op->lrp_networks.ipv4_addrs[i].addr == primary_ip4) {
+            return &op->lrp_networks.ipv4_addrs[i];
+        }
+    }
+
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+    VLOG_WARN_RL(&rl,
+                 "Logical_Router_Port %s: options:dhcpv4_primary_ip=%s "
+                 "does not match any address in 'networks'.",
+                 op->nbrp->name, primary_ip);
+    return NULL;
+}
+
 static void
 build_dhcp_relay_flows_for_lrouter_port(struct ovn_port *op,
                                         struct lflow_table *lflows,
@@ -16925,6 +16986,12 @@ build_dhcp_relay_flows_for_lrouter_port(struct 
ovn_port *op,
         return;
     }
 
+    /* Resolve the LRP's DHCPv4 primary IP (giaddr). */
+    const struct ipv4_netaddr *lrp_ipv4_addr = lrp_get_dhcpv4_primary_ip(op);
+    if (!lrp_ipv4_addr) {
+        return;
+    }
+
     int addr_family;
     /* currently not supporting custom port,
      * dhcp server port is always set to 67 when installing flows */
@@ -16948,14 +17015,12 @@ build_dhcp_relay_flows_for_lrouter_port(struct 
ovn_port *op,
         match, "inport == %s && "
         "ip4.src == {0.0.0.0, %s/%u} && ip4.dst == 255.255.255.255 && "
         "ip.frag == 0 && udp.src == 68 && udp.dst == 67",
-        op->json_key,
-        op->lrp_networks.ipv4_addrs[0].network_s,
-        op->lrp_networks.ipv4_addrs[0].plen);
+        op->json_key, lrp_ipv4_addr->network_s, lrp_ipv4_addr->plen);
     ds_put_format(actions,
                   REGBIT_DHCP_RELAY_REQ_CHK
                   " = dhcp_relay_req_chk(%s, %s);"
                   "next; /* DHCP_RELAY_REQ */",
-                  op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+                  lrp_ipv4_addr->addr_s, server_ip_str);
 
     ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, ds_cstr(match),
                   ds_cstr(actions), lflow_ref,
@@ -16972,13 +17037,11 @@ build_dhcp_relay_flows_for_lrouter_port(struct 
ovn_port *op,
         "ip4.src == {0.0.0.0, %s/%u} && ip4.dst == 255.255.255.255 && "
         "udp.src == 68 && udp.dst == 67 && "
         REGBIT_DHCP_RELAY_REQ_CHK,
-        op->json_key,
-        op->lrp_networks.ipv4_addrs[0].network_s,
-        op->lrp_networks.ipv4_addrs[0].plen);
+        op->json_key, lrp_ipv4_addr->network_s, lrp_ipv4_addr->plen);
     ds_put_format(actions,
                   "ip4.src = %s; ip4.dst = %s; udp.src = 67; next; "
                   "/* DHCP_RELAY_REQ */",
-                  op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+                  lrp_ipv4_addr->addr_s, server_ip_str);
 
     ovn_lflow_add(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_REQ, 100,
                   ds_cstr(match), ds_cstr(actions), lflow_ref,
@@ -16992,9 +17055,7 @@ build_dhcp_relay_flows_for_lrouter_port(struct ovn_port 
*op,
         "ip4.src == {0.0.0.0, %s/%u} && ip4.dst == 255.255.255.255 && "
         "udp.src == 68 && udp.dst == 67 && "
         REGBIT_DHCP_RELAY_REQ_CHK" == 0",
-        op->json_key,
-        op->lrp_networks.ipv4_addrs[0].network_s,
-        op->lrp_networks.ipv4_addrs[0].plen);
+        op->json_key, lrp_ipv4_addr->network_s, lrp_ipv4_addr->plen);
     ds_put_format(actions, "drop; /* DHCP_RELAY_REQ */");
 
     ovn_lflow_add(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_REQ, 1,
@@ -17007,7 +17068,7 @@ build_dhcp_relay_flows_for_lrouter_port(struct ovn_port 
*op,
     ds_put_format(
         match, "ip4.src == %s && ip4.dst == %s && "
         "ip.frag == 0 && udp.src == 67 && udp.dst == 67",
-        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+        server_ip_str, lrp_ipv4_addr->addr_s);
     ds_put_format(actions, "next; /* DHCP_RELAY_RESP */");
     ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
                   ds_cstr(match), ds_cstr(actions), lflow_ref,
@@ -17019,12 +17080,12 @@ build_dhcp_relay_flows_for_lrouter_port(struct 
ovn_port *op,
     ds_put_format(
         match, "ip4.src == %s && ip4.dst == %s && "
         "udp.src == 67 && udp.dst == 67",
-        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+        server_ip_str, lrp_ipv4_addr->addr_s);
     ds_put_format(actions,
           REG_DHCP_RELAY_DIP_IPV4" = ip4.dst; "
           REGBIT_DHCP_RELAY_RESP_CHK
           " = dhcp_relay_resp_chk(%s, %s); next; /* DHCP_RELAY_RESP */",
-          op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+          lrp_ipv4_addr->addr_s, server_ip_str);
 
     ovn_lflow_add(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP_CHK, 100,
                   ds_cstr(match), ds_cstr(actions), lflow_ref,
@@ -17042,11 +17103,11 @@ build_dhcp_relay_flows_for_lrouter_port(struct 
ovn_port *op,
         REG_DHCP_RELAY_DIP_IPV4" == %s && "
         "udp.src == 67 && udp.dst == 67 && "
         REGBIT_DHCP_RELAY_RESP_CHK,
-        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+        server_ip_str, lrp_ipv4_addr->addr_s);
     ds_put_format(actions,
                   "ip4.src = %s; udp.dst = 68; "
                   "outport = %s; output; /* DHCP_RELAY_RESP */",
-                  op->lrp_networks.ipv4_addrs[0].addr_s, op->json_key);
+                  lrp_ipv4_addr->addr_s, op->json_key);
     ovn_lflow_add(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP, 100,
                   ds_cstr(match), ds_cstr(actions), lflow_ref,
                   WITH_HINT(&op->nbrp->header_));
@@ -17058,7 +17119,7 @@ build_dhcp_relay_flows_for_lrouter_port(struct ovn_port 
*op,
                   REG_DHCP_RELAY_DIP_IPV4" == %s && "
                   "udp.src == 67 && udp.dst == 67 && "
                   REGBIT_DHCP_RELAY_RESP_CHK" == 0",
-                  server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+                  server_ip_str, lrp_ipv4_addr->addr_s);
     ds_put_format(actions, "drop; /* DHCP_RELAY_RESP */");
     ovn_lflow_add(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP, 1,
                   ds_cstr(match), ds_cstr(actions), lflow_ref,
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 9657c849d..a576c318e 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -4193,6 +4193,30 @@ or
       to <ref table="DHCP_Relay"/> table.
     </column>
 
+    <column name="options" key="dhcpv4_primary_ip">
+      <p>
+        Optional.  Selects which of the logical router port's IPv4
+        addresses <code>ovn-northd</code> uses as the relay agent
+        address (<code>giaddr</code>) in DHCP packets relayed via this
+        port (see <ref column="dhcp_relay"/>).  The value must be a
+        bare IPv4 host address (no prefix length, e.g.
+        <code>10.0.0.1</code>) that equals one of the addresses
+        configured in <ref column="networks"/>.  If the value is not a
+        valid IPv4 address or does not match one of the addresses in
+        <ref column="networks"/>, <code>ovn-northd</code> skips
+        emitting DHCP relay flows for this port.
+      </p>
+      <p>
+        When unset, <code>ovn-northd</code> defaults to the first
+        address in <ref column="networks"/> as ordered by OVSDB: the
+        CIDR that sorts first as a string.  This default may not match
+        the address intended as the DHCP relay address of the port, so
+        on multi-IP ports this option should be set explicitly.  On
+        single-IP ports the only configured address is always used and
+        the option may be left unset.
+      </p>
+    </column>
+
     <group title="Distributed Gateway Ports">
       <p>
         Gateways, as documented under <code>Gateways</code> in the OVN
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 810e9f2d7..08582a83b 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -13857,14 +13857,14 @@ check ovn-nbctl --wait=sb sync
 ovn-sbctl lflow-list > lflows
 AT_CAPTURE_FILE([lflows])
 
-AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0], [dnl
-  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && ip.frag 
== 0 && udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = 
dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
-  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 192.168.1.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next; /* DHCP_RELAY_RESP */)
-  table=??(lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src = 192.168.1.1; ip4.dst = 
172.16.1.1; udp.src = 67; next; /* DHCP_RELAY_REQ */)
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
   table=??(lr_in_dhcp_relay_req), priority=1    , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]] == 0), action=(drop; /* DHCP_RELAY_REQ */)
-  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst; reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 
172.16.1.1); next; /* DHCP_RELAY_RESP */)
-  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]]), action=(ip4.src = 192.168.1.1; udp.dst = 68; outport = "lrp1"; 
output; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src = 192.168.1.1; ip4.dst = 
172.16.1.1; udp.src = 67; next; /* DHCP_RELAY_REQ */)
   table=??(lr_in_dhcp_relay_resp), priority=1    , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]] == 0), action=(drop; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]]), action=(ip4.src = 192.168.1.1; udp.dst = 68; outport = "lrp1"; 
output; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst; reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 
172.16.1.1); next; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && ip.frag 
== 0 && udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = 
dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 192.168.1.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next; /* DHCP_RELAY_RESP */)
   table=??(ls_in_l2_lkup      ), priority=100  , match=(inport == "ls0-port1" 
&& eth.src == 02:00:00:00:00:10 && ip4.src == {0.0.0.0, 192.168.1.0/24} && 
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(eth.dst 
= 02:00:00:00:00:01; outport = "lrp1-attachment"; next; /* DHCP_RELAY_REQ */)
 ])
 
@@ -13874,14 +13874,14 @@ check ovn-nbctl --wait=sb set Logical_Switch ls0 
other_config:dhcp_relay_port=fo
 ovn-sbctl lflow-list > lflows
 AT_CAPTURE_FILE([lflows])
 
-AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0], [dnl
-  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && ip.frag 
== 0 && udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = 
dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
-  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 192.168.1.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next; /* DHCP_RELAY_RESP */)
-  table=??(lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src = 192.168.1.1; ip4.dst = 
172.16.1.1; udp.src = 67; next; /* DHCP_RELAY_REQ */)
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
   table=??(lr_in_dhcp_relay_req), priority=1    , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]] == 0), action=(drop; /* DHCP_RELAY_REQ */)
-  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst; reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 
172.16.1.1); next; /* DHCP_RELAY_RESP */)
-  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]]), action=(ip4.src = 192.168.1.1; udp.dst = 68; outport = "lrp1"; 
output; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src = 192.168.1.1; ip4.dst = 
172.16.1.1; udp.src = 67; next; /* DHCP_RELAY_REQ */)
   table=??(lr_in_dhcp_relay_resp), priority=1    , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]] == 0), action=(drop; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]]), action=(ip4.src = 192.168.1.1; udp.dst = 68; outport = "lrp1"; 
output; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst; reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 
172.16.1.1); next; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && ip.frag 
== 0 && udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = 
dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 192.168.1.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next; /* DHCP_RELAY_RESP */)
 ])
 
 # Clear the dhcp_relay from lrp1
@@ -13890,7 +13890,7 @@ check ovn-nbctl --wait=sb clear Logical_Router_port 
lrp1 dhcp_relay
 ovn-sbctl lflow-list > lflows
 AT_CAPTURE_FILE([lflows])
 
-AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0], [dnl
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
 ])
 
 # set the dhcp_relay_port in ls0 to proper one.
@@ -13899,7 +13899,83 @@ check ovn-nbctl --wait=sb set Logical_Switch ls0 
other_config:dhcp_relay_port=lr
 ovn-sbctl lflow-list > lflows
 AT_CAPTURE_FILE([lflows])
 
-AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0], [dnl
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
+])
+
+# Multi-IP LRP: re-enable DHCP relay on lrp1 and add a second IPv4
+# network.  options:dhcpv4_primary_ip is unset, so ovn-northd falls
+# back to the first address returned by OVSDB - whichever CIDR sorts
+# first as a string.  For ["10.0.0.1/24","192.168.1.1/24"] that is
+# 10.0.0.1/24, so DHCP relay flows are emitted using 10.0.0.1.
+check ovn-nbctl --wait=sb set Logical_Router_port lrp1 dhcp_relay=$dhcp_relay
+check ovn-nbctl --wait=sb set Logical_Router_port lrp1 \
+    'networks=["10.0.0.1/24","192.168.1.1/24"]'
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_dhcp_relay_req), priority=1    , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst == 255.255.255.255 && udp.src == 
68 && udp.dst == 67 && reg9[[7]] == 0), action=(drop; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst == 255.255.255.255 && udp.src == 
68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src = 10.0.0.1; ip4.dst = 
172.16.1.1; udp.src = 67; next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_dhcp_relay_resp), priority=1    , match=(ip4.src == 
172.16.1.1 && reg2 == 10.0.0.1 && udp.src == 67 && udp.dst == 67 && reg9[[8]] 
== 0), action=(drop; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 10.0.0.1 && udp.src == 67 && udp.dst == 67 && reg9[[8]]), 
action=(ip4.src = 10.0.0.1; udp.dst = 68; outport = "lrp1"; output; /* 
DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 10.0.0.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst; reg9[[8]] = dhcp_relay_resp_chk(10.0.0.1, 172.16.1.1); 
next; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst == 255.255.255.255 && ip.frag == 0 
&& udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = 
dhcp_relay_req_chk(10.0.0.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 10.0.0.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next; /* DHCP_RELAY_RESP */)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(inport == "ls0-port1" 
&& eth.src == 02:00:00:00:00:10 && ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst 
== 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(eth.dst = 
02:00:00:00:00:01; outport = "lrp1-attachment"; next; /* DHCP_RELAY_REQ */)
+])
+
+# Pin the DHCPv4 primary IP via options:dhcpv4_primary_ip.  Every DHCP
+# relay flow should now reference 192.168.1.1/24 instead of 10.0.0.1/24.
+check ovn-nbctl --wait=sb set Logical_Router_port lrp1 \
+    options:dhcpv4_primary_ip=192.168.1.1
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_dhcp_relay_req), priority=1    , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]] == 0), action=(drop; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && udp.src 
== 68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src = 192.168.1.1; ip4.dst = 
172.16.1.1; udp.src = 67; next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_dhcp_relay_resp), priority=1    , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]] == 0), action=(drop; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && 
reg9[[8]]), action=(ip4.src = 192.168.1.1; udp.dst = 68; outport = "lrp1"; 
output; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst; reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 
172.16.1.1); next; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 192.168.1.0/24} && ip4.dst == 255.255.255.255 && ip.frag 
== 0 && udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = 
dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 192.168.1.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next; /* DHCP_RELAY_RESP */)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(inport == "ls0-port1" 
&& eth.src == 02:00:00:00:00:10 && ip4.src == {0.0.0.0, 192.168.1.0/24} && 
ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(eth.dst 
= 02:00:00:00:00:01; outport = "lrp1-attachment"; next; /* DHCP_RELAY_REQ */)
+])
+
+# Setting options:dhcpv4_primary_ip to an address that does not belong
+# to 'networks' is a misconfiguration: ovn-northd logs a warning and
+# skips DHCP relay flow generation for the port.
+check ovn-nbctl --wait=sb set Logical_Router_port lrp1 \
+    options:dhcpv4_primary_ip=10.99.99.99
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
+])
+AT_CHECK([grep -q "options:dhcpv4_primary_ip=10.99.99.99 does not match any 
address in 'networks'" northd/ovn-northd.log])
+
+# Reducing 'networks' back to a single IPv4 makes the option no longer
+# mandatory; clearing it should restore DHCP relay flows that reference
+# the remaining address.
+check ovn-nbctl --wait=sb set Logical_Router_port lrp1 \
+    'networks=["10.0.0.1/24"]'
+check ovn-nbctl --wait=sb remove Logical_Router_port lrp1 options 
dhcpv4_primary_ip
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | ovn_strip_lflows], [0], [dnl
+  table=??(lr_in_dhcp_relay_req), priority=1    , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst == 255.255.255.255 && udp.src == 
68 && udp.dst == 67 && reg9[[7]] == 0), action=(drop; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_dhcp_relay_req), priority=100  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst == 255.255.255.255 && udp.src == 
68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src = 10.0.0.1; ip4.dst = 
172.16.1.1; udp.src = 67; next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_dhcp_relay_resp), priority=1    , match=(ip4.src == 
172.16.1.1 && reg2 == 10.0.0.1 && udp.src == 67 && udp.dst == 67 && reg9[[8]] 
== 0), action=(drop; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp), priority=100  , match=(ip4.src == 
172.16.1.1 && reg2 == 10.0.0.1 && udp.src == 67 && udp.dst == 67 && reg9[[8]]), 
action=(ip4.src = 10.0.0.1; udp.dst = 68; outport = "lrp1"; output; /* 
DHCP_RELAY_RESP */)
+  table=??(lr_in_dhcp_relay_resp_chk), priority=100  , match=(ip4.src == 
172.16.1.1 && ip4.dst == 10.0.0.1 && udp.src == 67 && udp.dst == 67), 
action=(reg2 = ip4.dst; reg9[[8]] = dhcp_relay_resp_chk(10.0.0.1, 172.16.1.1); 
next; /* DHCP_RELAY_RESP */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(inport == "lrp1" && 
ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst == 255.255.255.255 && ip.frag == 0 
&& udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = 
dhcp_relay_req_chk(10.0.0.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
+  table=??(lr_in_ip_input     ), priority=110  , match=(ip4.src == 172.16.1.1 
&& ip4.dst == 10.0.0.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), 
action=(next; /* DHCP_RELAY_RESP */)
+  table=??(ls_in_l2_lkup      ), priority=100  , match=(inport == "ls0-port1" 
&& eth.src == 02:00:00:00:00:10 && ip4.src == {0.0.0.0, 10.0.0.0/24} && ip4.dst 
== 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(eth.dst = 
02:00:00:00:00:01; outport = "lrp1-attachment"; next; /* DHCP_RELAY_REQ */)
 ])
 
 OVN_CLEANUP_NORTHD
-- 
2.43.5

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

Reply via email to