OVN's native DNS responder answered every A/AAAA/ANY/PTR query with a
hardcoded resource-record TTL of 3600 seconds.  In dynamic environments
caching a DNS response for 1h is probably not desirable, in any case
it's better if the TTL is operator configurable.

Add a per-row "options:ttl" key on the NB DNS table.  ovn-northd
validates the value in sync_dns_entries() and, like the existing
"ovn-owned" option, propagates it to the SB DNS table.  ovn-controller
reads the option back and uses it when building DNS replies for
that record.

The default remains 3600 seconds.

Reported-at: https://redhat.atlassian.net/browse/FDP-2956
Assisted-by: Qwen3.8-27B-FP8, opencode
Signed-off-by: Dumitru Ceara <[email protected]>
---
 NEWS                 |   2 +
 controller/ovn-dns.c |   7 ++-
 controller/ovn-dns.h |   2 +-
 controller/pinctrl.c |  27 +++++----
 lib/ovn-l7.h         |   1 +
 northd/northd.c      |  22 +++++++
 ovn-nb.xml           |  14 +++++
 ovn-sb.xml           |   9 +++
 tests/ovn-northd.at  |  60 +++++++++++++++++++
 tests/ovn.at         | 138 +++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 268 insertions(+), 14 deletions(-)

diff --git a/NEWS b/NEWS
index aadf6416d2..f1c56dd147 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,7 @@
 Post v26.09.0
 --------------
+   - Added a new "options:ttl" key on the NB DNS table to make the TTL of
+     DNS replies from OVN's native DNS resolver configurable per row.
    - Removed implementations of the commit_ecmp_nh, chk_ecmp_nh, and
      chk_ecmp_nh_mac actions from the code.
    - Mark tunnel ports as transient (other_config:transient=true) when the
diff --git a/controller/ovn-dns.c b/controller/ovn-dns.c
index bfeef5a747..416f399d78 100644
--- a/controller/ovn-dns.c
+++ b/controller/ovn-dns.c
@@ -22,6 +22,7 @@
 #include "openvswitch/vlog.h"
 
 /* OVN includes. */
+#include "lib/ovn-l7.h"
 #include "lib/ovn-sb-idl.h"
 #include "ovn-dns.h"
 
@@ -112,12 +113,14 @@ ovn_dns_update_cache(const struct sbrec_dns_table 
*dns_table)
 }
 
 const char *
-ovn_dns_lookup(const char *query_name, uint64_t dp_key, bool *ovn_owned)
+ovn_dns_lookup(const char *query_name, uint64_t dp_key, bool *ovn_owned,
+               uint32_t *ttl)
 {
     const char *answer_data = NULL;
     struct dns_data *dns_data;
 
     *ovn_owned = false;
+    *ttl = DNS_DEFAULT_RR_TTL;
 
     CMAP_FOR_EACH (dns_data, cmap_node, &dns_cache_) {
         for (size_t i = 0; i < dns_data->n_dps; i++) {
@@ -131,6 +134,8 @@ ovn_dns_lookup(const char *query_name, uint64_t dp_key, 
bool *ovn_owned)
                 if (answer_data) {
                     *ovn_owned = smap_get_bool(&dns_data->options, "ovn-owned",
                                                false);
+                    *ttl = smap_get_uint(&dns_data->options, "ttl",
+                                         DNS_DEFAULT_RR_TTL);
                     break;
                 }
             }
diff --git a/controller/ovn-dns.h b/controller/ovn-dns.h
index 8eca6ad0eb..6c86ccc858 100644
--- a/controller/ovn-dns.h
+++ b/controller/ovn-dns.h
@@ -24,6 +24,6 @@ void ovn_dns_cache_destroy(void);
 void ovn_dns_sync_cache(const struct sbrec_dns_table *);
 void ovn_dns_update_cache(const struct sbrec_dns_table *);
 const char *ovn_dns_lookup(const char *query_name, uint64_t dp_key,
-                           bool *ovn_owned);
+                           bool *ovn_owned, uint32_t *ttl);
 
 #endif /* OVN_DNS_H */
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 333dcedb6e..e100912510 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -3394,22 +3394,22 @@ put_be32(struct ofpbuf *buf, ovs_be32 x)
 static void
 dns_build_base_answer(
     struct ofpbuf *dns_answer, const uint8_t *in_queryname,
-    uint16_t query_length, int query_type)
+    uint16_t query_length, int query_type, uint32_t ttl)
 {
     ofpbuf_put(dns_answer, in_queryname, query_length);
     put_be16(dns_answer, htons(query_type));
     put_be16(dns_answer, htons(DNS_CLASS_IN));
-    put_be32(dns_answer, htonl(DNS_DEFAULT_RR_TTL));
+    put_be32(dns_answer, htonl(ttl));
 }
 
 /* Populates dns_answer struct with a TYPE A answer. */
 static void
 dns_build_a_answer(
     struct ofpbuf *dns_answer, const uint8_t *in_queryname,
-    uint16_t query_length, const ovs_be32 addr)
+    uint16_t query_length, const ovs_be32 addr, uint32_t ttl)
 {
     dns_build_base_answer(dns_answer, in_queryname, query_length,
-                          DNS_QUERY_TYPE_A);
+                          DNS_QUERY_TYPE_A, ttl);
     put_be16(dns_answer, htons(sizeof(ovs_be32)));
     put_be32(dns_answer, addr);
 }
@@ -3418,10 +3418,10 @@ dns_build_a_answer(
 static void
 dns_build_aaaa_answer(
     struct ofpbuf *dns_answer, const uint8_t *in_queryname,
-    uint16_t query_length, const struct in6_addr *addr)
+    uint16_t query_length, const struct in6_addr *addr, uint32_t ttl)
 {
     dns_build_base_answer(dns_answer, in_queryname, query_length,
-                          DNS_QUERY_TYPE_AAAA);
+                          DNS_QUERY_TYPE_AAAA, ttl);
     put_be16(dns_answer, htons(sizeof(*addr)));
     ofpbuf_put(dns_answer, addr, sizeof(*addr));
 }
@@ -3430,10 +3430,10 @@ dns_build_aaaa_answer(
 static void
 dns_build_ptr_answer(
     struct ofpbuf *dns_answer, const uint8_t *in_queryname,
-    uint16_t query_length, const char *answer_data)
+    uint16_t query_length, const char *answer_data, uint32_t ttl)
 {
     dns_build_base_answer(dns_answer, in_queryname, query_length,
-                          DNS_QUERY_TYPE_PTR);
+                          DNS_QUERY_TYPE_PTR, ttl);
 
     size_t encoded_len = 0;
     char *encoded = encode_fqdn_string(answer_data, &encoded_len);
@@ -3586,8 +3586,9 @@ pinctrl_handle_dns_lookup(
 
     uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
     bool ovn_owned = false;
+    uint32_t ttl = DNS_DEFAULT_RR_TTL;
     const char *answer_data = ovn_dns_lookup(ds_cstr(&query_name), dp_key,
-                                             &ovn_owned);
+                                             &ovn_owned, &ttl);
     ds_destroy(&query_name);
     if (!answer_data) {
         COVERAGE_INC(dns_cache_miss);
@@ -3601,7 +3602,8 @@ pinctrl_handle_dns_lookup(
     struct ofpbuf dns_answer = OFPBUF_STUB_INITIALIZER(dns_ans_stub);
 
     if (query_type == DNS_QUERY_TYPE_PTR) {
-        dns_build_ptr_answer(&dns_answer, in_queryname, idx, answer_data);
+        dns_build_ptr_answer(&dns_answer, in_queryname, idx, answer_data,
+                             ttl);
         ancount++;
     } else {
         struct lport_addresses ip_addrs;
@@ -3617,7 +3619,7 @@ pinctrl_handle_dns_lookup(
             query_type == DNS_QUERY_TYPE_ANY) {
             for (size_t i = 0; i < ip_addrs.n_ipv4_addrs; i++) {
                 ovs_be32 addr = ip_addrs.ipv4_addrs[ipv4_order[i]].addr;
-                dns_build_a_answer(&dns_answer, in_queryname, idx, addr);
+                dns_build_a_answer(&dns_answer, in_queryname, idx, addr, ttl);
                 ancount++;
             }
         }
@@ -3627,7 +3629,8 @@ pinctrl_handle_dns_lookup(
             for (size_t i = 0; i < ip_addrs.n_ipv6_addrs; i++) {
                 struct in6_addr *addr =
                     &ip_addrs.ipv6_addrs[ipv6_order[i]].addr;
-                dns_build_aaaa_answer(&dns_answer, in_queryname, idx, addr);
+                dns_build_aaaa_answer(&dns_answer, in_queryname, idx, addr,
+                                      ttl);
                 ancount++;
             }
         }
diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
index 5f52e1791f..58b8a4a1c3 100644
--- a/lib/ovn-l7.h
+++ b/lib/ovn-l7.h
@@ -52,6 +52,7 @@ BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct bfd_msg));
 
 #define DNS_CLASS_IN            0x01
 #define DNS_DEFAULT_RR_TTL      3600
+#define DNS_MAX_RR_TTL          2147483647U /* 2^31-1, per RFC 2181. */
 
 /* Generic options map which is used to store dhcpv4 opts and dhcpv6 opts. */
 struct gen_opts_map {
diff --git a/northd/northd.c b/northd/northd.c
index 1c9e5d0703..a0b90646f0 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -21618,6 +21618,28 @@ sync_dns_entries(struct ovsdb_idl_txn *ovnsb_txn,
                                        "ovn-owned", false);
         smap_replace(&options, "ovn-owned",
                  ovn_owned? "true" : "false");
+
+        /* Sync the per-row TTL option to SB.  Only values in the range
+         * 0..DNS_MAX_RR_TTL are valid.
+         */
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        const char *ttl = smap_get(&dns_info->nb_dns->options, "ttl");
+        if (ttl) {
+            unsigned int ttl_val;
+            if (str_to_uint(ttl, 10, &ttl_val) && ttl_val <= DNS_MAX_RR_TTL) {
+                smap_replace(&options, "ttl", ttl);
+            } else {
+                VLOG_WARN_RL(&rl, "Ignoring invalid \"options:ttl\" %s "
+                                  "(expected 0-%"PRIu32") for DNS record "
+                                  UUID_FMT, ttl, DNS_MAX_RR_TTL,
+                                  UUID_ARGS(&dns_info->nb_dns->header_.uuid));
+                smap_remove(&options, "ttl");
+            }
+        } else {
+            /* Remove any stale value so the default TTL is used. */
+            smap_remove(&options, "ttl");
+        }
+
         sbrec_dns_set_options(dns_info->sb_dns, &options);
         smap_destroy(&options);
 
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 57b81d4b44..078bd60687 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -6464,6 +6464,20 @@ or
       </p>
     </column>
 
+    <column name="options" key="ttl">
+      The time-to-live (in seconds) set in the TTL field of DNS replies
+      generated by OVN for the records in this row.
+
+      <p>
+        The default is <code>3600</code> seconds.  Set this to a smaller
+        value in dynamic environments (e.g. VM fail-over) so that
+        clients do not cache stale records for long.  Valid values are 0
+        to 2147483647 (inclusive).  An invalid or out-of-range value is
+        rejected with a warning by ovn-northd and the default of 3600
+        seconds is used instead.
+      </p>
+    </column>
+
     <column name="external_ids">
       See <em>External IDs</em> at the beginning of this document.
     </column>
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 808694fe0a..2096fc3e3c 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -5015,6 +5015,15 @@ tcp.flags = RST;
       will be answered locally by either an IP address or
       <code>DNS rejection</code>.
 </column>
+
+    <column name="options" key="ttl">
+      The time-to-live (in seconds) set in the TTL field of DNS replies for
+      the records in this row.  This is synced automatically by ovn-northd
+      from the corresponding
+      <ref table="DNS" column="options" key="ttl" db="OVN_Northbound"/>
+      option and should not be set directly.
+    </column>
+
     <group title="Common Columns">
       <column name="external_ids">
         See <em>External IDs</em> at the beginning of this document.
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index af231fb871..7d9bd68541 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -12354,6 +12354,66 @@ wait_row_count sb:DNS 0
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([DNS options:ttl synchronization to SB])
+ovn_start
+
+check ovn-nbctl ls-add sw0
+dns_uuid=$(ovn-nbctl create DNS records={})
+check ovn-nbctl set DNS $dns_uuid records:vm1.ovn.org="10.0.0.4"
+check ovn-nbctl set Logical_Switch sw0 dns_records="$dns_uuid"
+check ovn-nbctl --wait=sb sync
+
+wait_row_count sb:DNS 1
+sb_dns_uuid=$(fetch_column sb:DNS _uuid)
+
+# Without "options:ttl", the key is not present in SB.
+AT_CHECK([test -z "$(ovn-sbctl --if-exists get DNS $sb_dns_uuid options:ttl)"])
+
+# Valid values are synced to SB, including the boundary values 0 and the
+# RFC 2181 maximum of 2147483647.
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=30
+AT_CHECK([ovn-sbctl get DNS $sb_dns_uuid options:ttl], [0], [dnl
+"30"
+])
+
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=0
+AT_CHECK([ovn-sbctl get DNS $sb_dns_uuid options:ttl], [0], [dnl
+"0"
+])
+
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=2147483647
+AT_CHECK([ovn-sbctl get DNS $sb_dns_uuid options:ttl], [0], [dnl
+"2147483647"
+])
+
+# Invalid values are rejected and the key is removed from SB.
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=garbage
+AT_CHECK([test -z "$(ovn-sbctl --if-exists get DNS $sb_dns_uuid options:ttl)"])
+
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=-5
+AT_CHECK([test -z "$(ovn-sbctl --if-exists get DNS $sb_dns_uuid options:ttl)"])
+
+# A value above the 2^31-1 cap is out of range and rejected.
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=2147483648
+AT_CHECK([test -z "$(ovn-sbctl --if-exists get DNS $sb_dns_uuid options:ttl)"])
+
+# A value that overflows the 32-bit unsigned range is rejected as well.
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=99999999999
+AT_CHECK([test -z "$(ovn-sbctl --if-exists get DNS $sb_dns_uuid options:ttl)"])
+
+# Removing the option clears any previously synced value.
+check ovn-nbctl --wait=sb set DNS $dns_uuid options:ttl=30
+AT_CHECK([ovn-sbctl get DNS $sb_dns_uuid options:ttl], [0], [dnl
+"30"
+])
+check ovn-nbctl --wait=sb remove DNS $dns_uuid options ttl
+AT_CHECK([test -z "$(ovn-sbctl --if-exists get DNS $sb_dns_uuid options:ttl)"])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ACL/Meter incremental processing - no northd recompute])
 ovn_start
diff --git a/tests/ovn.at b/tests/ovn.at
index 8acfecb7bc..7f823de1da 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -12677,6 +12677,144 @@ OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([dns lookup : configurable TTL])
+CHECK_SCAPY
+ovn_start
+
+check ovn-nbctl ls-add ls \
+    -- lsp-add ls lsp \
+    -- lsp-set-addresses lsp "00:00:00:00:00:01 10.0.0.1"
+
+d=$(ovn-nbctl create DNS records={})
+check ovn-nbctl set DNS $d records:foo.ovn.org="10.0.0.42 aef0::42"
+check ovn-nbctl set DNS $d records:42.0.0.10.in-addr.arpa="foo.ovn.org"
+check ovn-nbctl set Logical_switch ls dns_records="$d"
+
+net_add n1
+sim_add hv1
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+check ovs-vsctl add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=lsp \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+dns_req_a=$(fmt_pkt "Ether(dst='00:00:00:00:00:02', src='00:00:00:00:00:01') / 
\
+                    IP(dst='10.0.0.254', src='10.0.0.1') / \
+                    UDP(sport=42424, dport=53) / \
+                    DNS(rd=1, qd=DNSQR(qname='foo.ovn.org', qtype='A'))")
+dns_req_aaaa=$(fmt_pkt "Ether(dst='00:00:00:00:00:02', 
src='00:00:00:00:00:01') / \
+                       IP(dst='10.0.0.254', src='10.0.0.1') / \
+                       UDP(sport=42424, dport=53) / \
+                       DNS(rd=1, qd=DNSQR(qname='foo.ovn.org', qtype='AAAA'))")
+dns_req_ptr=$(fmt_pkt "Ether(dst='00:00:00:00:00:02', src='00:00:00:00:00:01') 
/ \
+                      IP(dst='10.0.0.254', src='10.0.0.1') / \
+                      UDP(sport=42424, dport=53) / \
+                      DNS(rd=1, qd=DNSQR(qname='42.0.0.10.in-addr.arpa', 
qtype='PTR'))")
+dns_req_any=$(fmt_pkt "Ether(dst='00:00:00:00:00:02', src='00:00:00:00:00:01') 
/ \
+                       IP(dst='10.0.0.254', src='10.0.0.1') / \
+                       UDP(sport=42424, dport=53) / \
+                       DNS(rd=1, qd=DNSQR(qname='foo.ovn.org', qtype='ALL'))")
+
+# Without "options:ttl", the reply uses the default TTL of 3600.
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / 
\
+                     IP(dst='10.0.0.1', src='10.0.0.254') / \
+                     UDP(sport=53, dport=42424, chksum=0) / \
+                     DNS(qr=1, qd=DNSQR(qname='foo.ovn.org', qtype='A'), \
+                         an=DNSRR(rrname='foo.ovn.org', type='A', ttl=3600, \
+                                  rdata='10.0.0.42'))")
+echo ${dns_reply} > expected
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req_a}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+# options:ttl=30 is applied to the A, AAAA and PTR replies alike.
+check ovn-nbctl --wait=hv set DNS $d options:ttl=30
+
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / 
\
+                     IP(dst='10.0.0.1', src='10.0.0.254') / \
+                     UDP(sport=53, dport=42424, chksum=0) / \
+                     DNS(qr=1, qd=DNSQR(qname='foo.ovn.org', qtype='A'), \
+                         an=DNSRR(rrname='foo.ovn.org', type='A', ttl=30, \
+                                  rdata='10.0.0.42'))")
+echo ${dns_reply} > expected
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req_a}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / 
\
+                     IP(dst='10.0.0.1', src='10.0.0.254') / \
+                     UDP(sport=53, dport=42424, chksum=0) / \
+                     DNS(qr=1, qd=DNSQR(qname='foo.ovn.org', qtype='AAAA'), \
+                         an=DNSRR(rrname='foo.ovn.org', type='AAAA', ttl=30, \
+                                  rdata='aef0::42'))")
+echo ${dns_reply} > expected
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req_aaaa}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / 
\
+                     IP(dst='10.0.0.1', src='10.0.0.254') / \
+                     UDP(sport=53, dport=42424, chksum=0) / \
+                     DNS(qr=1, qd=DNSQR(qname='42.0.0.10.in-addr.arpa', 
qtype='PTR'), \
+                         an=DNSRR(rrname='42.0.0.10.in-addr.arpa', type='PTR', 
\
+                                  ttl=30, rdata='foo.ovn.org'))")
+echo ${dns_reply} > expected
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req_ptr}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+# The ANY reply carries the A and AAAA RRs, each with the per-row TTL.
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / 
\
+                      IP(dst='10.0.0.1', src='10.0.0.254') / \
+                      UDP(sport=53, dport=42424, chksum=0) / \
+                      DNS(qr=1, qd=DNSQR(qname='foo.ovn.org', qtype='ALL'), \
+                          an=[[DNSRR(rrname='foo.ovn.org', type='A', \
+                                    ttl=30, rdata='10.0.0.42'), \
+                               DNSRR(rrname='foo.ovn.org', type='AAAA', \
+                                    ttl=30, rdata='aef0::42')]])")
+echo ${dns_reply} > expected
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req_any}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+# Removing the option restores the default TTL of 3600.
+check ovn-nbctl --wait=hv remove DNS $d options ttl
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / 
\
+                     IP(dst='10.0.0.1', src='10.0.0.254') / \
+                     UDP(sport=53, dport=42424, chksum=0) / \
+                     DNS(qr=1, qd=DNSQR(qname='foo.ovn.org', qtype='A'), \
+                         an=DNSRR(rrname='foo.ovn.org', type='A', ttl=3600, \
+                                  rdata='10.0.0.42'))")
+echo ${dns_reply} > expected
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req_a}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+# options:ttl=0 is honored (a reply TTL of 0).
+check ovn-nbctl --wait=hv set DNS $d options:ttl=0
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / 
\
+                     IP(dst='10.0.0.1', src='10.0.0.254') / \
+                     UDP(sport=53, dport=42424, chksum=0) / \
+                     DNS(qr=1, qd=DNSQR(qname='foo.ovn.org', qtype='A'), \
+                         an=DNSRR(rrname='foo.ovn.org', type='A', ttl=0, \
+                                  rdata='10.0.0.42'))")
+echo ${dns_reply} > expected
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req_a}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([4 HV, 1 LS, 1 LR, packet test with HA distributed router gateway 
port])
 ovn_start
-- 
2.55.0

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

Reply via email to