diff mbox series

[ovs-dev,v2] DNS: allow defining records that owned by OVN only

Message ID 20231113123448.1206245-1-mheib@redhat.com
State Accepted
Delegated to: Dumitru Ceara
Headers show
Series [ovs-dev,v2] DNS: allow defining records that owned by OVN only | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Mohammad Heib Nov. 13, 2023, 12:34 p.m. UTC
Currently OVN allows users to create DNS records
and define domains within these records.

These domains can be associated with IPV4 or IPv6
or both, when the user creates a domain with both
IPv4 and IPv6 ovn will answer each query for this
domain immediately and everything works as expected.

But if the user only creates a domain with only IPv4
or IPv6 this will cause the DNS queries respond take
longer than the usual since OVN will forward query and
user will keep waiting for an answer until someone else
replies for this query or timeout occur.

The above behavior is a bit problematic if the user knows
that this domain is only configured in OVN we should not
forward queries for this domain to the outside.

This patch adds an option:ovn-owned for the DNS table
that can be set to "true" when creating a DNS.

When setting this option to "true" all the domains within
this table will be treated as local domains only,
and queries for domains within this table that don't have
an accurate IP will be refused immediately to save the time
of waiting for timeout.

Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1946662
Signed-off-by: Mohammad Heib <mheib@redhat.com>
---
 NEWS                 |  4 +++
 controller/pinctrl.c | 43 ++++++++++++++++++++++++++++-
 northd/northd.c      | 13 +++++++++
 ovn-nb.ovsschema     |  9 ++++--
 ovn-nb.xml           | 14 ++++++++++
 ovn-sb.ovsschema     |  9 ++++--
 ovn-sb.xml           |  6 ++++
 tests/ovn.at         | 65 ++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 158 insertions(+), 5 deletions(-)

Comments

Dumitru Ceara Nov. 20, 2023, 7:49 p.m. UTC | #1
On 11/13/23 13:34, Mohammad Heib wrote:
> Currently OVN allows users to create DNS records
> and define domains within these records.
> 
> These domains can be associated with IPV4 or IPv6
> or both, when the user creates a domain with both
> IPv4 and IPv6 ovn will answer each query for this
> domain immediately and everything works as expected.
> 
> But if the user only creates a domain with only IPv4
> or IPv6 this will cause the DNS queries respond take
> longer than the usual since OVN will forward query and
> user will keep waiting for an answer until someone else
> replies for this query or timeout occur.
> 
> The above behavior is a bit problematic if the user knows
> that this domain is only configured in OVN we should not
> forward queries for this domain to the outside.
> 
> This patch adds an option:ovn-owned for the DNS table
> that can be set to "true" when creating a DNS.
> 
> When setting this option to "true" all the domains within
> this table will be treated as local domains only,
> and queries for domains within this table that don't have
> an accurate IP will be refused immediately to save the time
> of waiting for timeout.
> 
> Reported-at: https://bugzilla.redhat.com/show_bug.cgi?id=1946662
> Signed-off-by: Mohammad Heib <mheib@redhat.com>
> ---

With a couple of minor changes (see below) I applied this to the main
branch.  Thanks, Mohammad!

Regards,
Dumitru

>  NEWS                 |  4 +++
>  controller/pinctrl.c | 43 ++++++++++++++++++++++++++++-
>  northd/northd.c      | 13 +++++++++
>  ovn-nb.ovsschema     |  9 ++++--
>  ovn-nb.xml           | 14 ++++++++++
>  ovn-sb.ovsschema     |  9 ++++--
>  ovn-sb.xml           |  6 ++++
>  tests/ovn.at         | 65 ++++++++++++++++++++++++++++++++++++++++++++
>  8 files changed, 158 insertions(+), 5 deletions(-)
> 
> diff --git a/NEWS b/NEWS
> index 30f6edb28..e10fb79dd 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,9 @@
>  Post v23.09.0
>  -------------
> +  - DNS now have an "options" column for configuration of extra options.
> +  - A new DNS option "ovn-owned" has been added to allow defining domains
> +    that are owned only by ovn, queries for that domain will not be processed
> +    externally.
>    - Disable OpenFlow inactivity probing between ovn-controller and OVS.
>      OF connection is established over unix socket, which is a reliable
>      connection method and doesn't require additional probing.
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index d88e951a6..cf48089b6 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -179,6 +179,7 @@ struct pinctrl {
>      struct latch pinctrl_thread_exit;
>      bool mac_binding_can_timestamp;
>      bool fdb_can_timestamp;
> +    bool dns_supports_ovn_owned;
>  };
>  
>  static struct pinctrl pinctrl;
> @@ -2713,6 +2714,7 @@ struct dns_data {
>      uint64_t *dps;
>      size_t n_dps;
>      struct smap records;
> +    struct smap options;
>      bool delete;
>  };
>  
> @@ -2741,6 +2743,7 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
>          if (!dns_data) {
>              dns_data = xmalloc(sizeof *dns_data);
>              smap_init(&dns_data->records);
> +            smap_init(&dns_data->options);
>              shash_add(&dns_cache, dns_id, dns_data);
>              dns_data->n_dps = 0;
>              dns_data->dps = NULL;
> @@ -2755,6 +2758,12 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
>              smap_clone(&dns_data->records, &sbrec_dns->records);
>          }
>  
> +        if (pinctrl.dns_supports_ovn_owned
> +            && !smap_equal(&dns_data->options, &sbrec_dns->options)) {
> +            smap_destroy(&dns_data->options);
> +            smap_clone(&dns_data->options, &sbrec_dns->options);
> +        }
> +
>          dns_data->n_dps = sbrec_dns->n_datapaths;
>          dns_data->dps = xcalloc(dns_data->n_dps, sizeof(uint64_t));
>          for (size_t i = 0; i < sbrec_dns->n_datapaths; i++) {
> @@ -2767,6 +2776,7 @@ sync_dns_cache(const struct sbrec_dns_table *dns_table)
>          if (d->delete) {
>              shash_delete(&dns_cache, iter);
>              smap_destroy(&d->records);
> +            smap_destroy(&d->options);
>              free(d->dps);
>              free(d);
>          }
> @@ -2781,6 +2791,7 @@ destroy_dns_cache(void)
>          struct dns_data *d = iter->data;
>          shash_delete(&dns_cache, iter);
>          smap_destroy(&d->records);
> +        smap_destroy(&d->options);
>          free(d->dps);
>          free(d);
>      }
> @@ -2854,6 +2865,8 @@ dns_build_ptr_answer(
>      free(encoded);
>  }
>  
> +#define DNS_RCODE_SERVER_REFUSE 0x5
> +
>  /* Called with in the pinctrl_handler thread context. */
>  static void
>  pinctrl_handle_dns_lookup(
> @@ -2867,6 +2880,7 @@ pinctrl_handle_dns_lookup(
>      enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
>      struct dp_packet *pkt_out_ptr = NULL;
>      uint32_t success = 0;
> +    bool send_refuse = false;
>  
>      /* Parse result field. */
>      const struct mf_field *f;
> @@ -2966,6 +2980,7 @@ pinctrl_handle_dns_lookup(
>  
>      uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
>      const char *answer_data = NULL;
> +    bool ovn_owned = false;
>      struct shash_node *iter;
>      SHASH_FOR_EACH (iter, &dns_cache) {
>          struct dns_data *d = iter->data;
> @@ -2978,6 +2993,7 @@ pinctrl_handle_dns_lookup(
>                  answer_data = smap_get(&d->records, query_name_lower);
>                  free(query_name_lower);
>                  if (answer_data) {
> +                    ovn_owned = smap_get_bool(&d->options, "ovn-owned", false);

I moved this out of the inner loop.

>                      break;
>                  }
>              }
> @@ -3024,10 +3040,22 @@ pinctrl_handle_dns_lookup(
>                  ancount++;
>              }
>          }
> +
> +        /* DNS is configured with a record for this domain with
> +         * an IPv4/IPV6 only, so instead of ignoring this A/AAAA query,
> +         * we can reply with  RCODE = 5 (server refuses) and that
> +         * will speed up the DNS process by not letting the customer
> +         * wait for a timeout.
> +         */
> +        if (ovn_owned && (query_type == DNS_QUERY_TYPE_AAAA ||
> +            query_type == DNS_QUERY_TYPE_A) && !ancount) {
> +            send_refuse = true;
> +        }
> +
>          destroy_lport_addresses(&ip_addrs);
>      }
>  
> -    if (!ancount) {
> +    if (!ancount && !send_refuse) {
>          ofpbuf_uninit(&dns_answer);
>          goto exit;
>      }
> @@ -3062,6 +3090,10 @@ pinctrl_handle_dns_lookup(
>  
>      /* Set the answer RRs. */
>      out_dns_header->ancount = htons(ancount);
> +    if (send_refuse) {
> +        /* set RCODE = 5 (server refuses). */
> +        out_dns_header->hi_flag |= DNS_RCODE_SERVER_REFUSE;
> +    }
>      out_dns_header->arcount = 0;
>  
>      /* Copy the Query section. */
> @@ -3530,6 +3562,15 @@ pinctrl_update(const struct ovsdb_idl *idl, const char *br_int_name)
>          notify_pinctrl_handler();
>      }
>  
> +    bool dns_supports_ovn_owned = sbrec_server_has_dns_table_col_options(idl);
> +    if (dns_supports_ovn_owned != pinctrl.dns_supports_ovn_owned) {
> +        pinctrl.dns_supports_ovn_owned = dns_supports_ovn_owned;
> +
> +        /* Notify pinctrl_handler that fdb timestamp column
> +       * availability has changed. */
> +        notify_pinctrl_handler();
> +    }
> +
>      ovs_mutex_unlock(&pinctrl_mutex);
>  }
>  
> diff --git a/northd/northd.c b/northd/northd.c
> index f8b046d83..e93d0c8f8 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -17301,6 +17301,19 @@ sync_dns_entries(struct ovsdb_idl_txn *ovnsb_txn,
>              free(dns_id);
>          }
>  
> +        /* Copy DNS options to SB*/
> +        struct smap options = SMAP_INITIALIZER(&options);
> +        if (!smap_is_empty(&dns_info->sb_dns->options)) {
> +            smap_clone(&options, &dns_info->sb_dns->options);
> +        }
> +
> +        bool ovn_owned = smap_get_bool(&dns_info->nb_dns->options,
> +                                       "ovn-owned", false);
> +        smap_replace(&options, "ovn-owned",
> +                 ovn_owned? "true" : "false");
> +        sbrec_dns_set_options(dns_info->sb_dns, &options);
> +        smap_destroy(&options);
> +
>          /* Set the datapaths and records. If nothing has changed, then
>           * this will be a no-op.
>           */
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index e103360ec..b2e0993e0 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "7.1.0",
> -    "cksum": "217362582 33949",
> +    "version": "7.2.0",
> +    "cksum": "1069338687 34162",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -560,6 +560,11 @@
>                                       "value": "string",
>                                       "min": 0,
>                                       "max": "unlimited"}},
> +                "options": {
> +                    "type": {"key": "string",
> +                             "value": "string",
> +                             "min": 0,
> +                             "max": "unlimited"}},
>                  "external_ids": {"type": {"key": "string",
>                                   "value": "string",
>                                   "min": 0,
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 1de0c3041..4aa8107a5 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -4542,6 +4542,20 @@ or
>        <p><b>Example: </b> "4.0.0.10.in-addr.arpa" = "vm1.ovn.org"</p>
>      </column>
>  
> +    <column name="options" key="ovn-owned">
> +      If set to true, then the OVN will be the main responsible for
> +      <code>DNS Records</code> within this Table.
> +
> +      <p> A <code>DNS Table</code> with this option set to <code>true</code>
> +          can be created for domains that the user needs to configure
> +          locally and don't care about IPv6 only interested in IPv4 or
> +          vice versa.
> +
> +           This will let ovn send IPv4 DNS reply and reject/ignore IPv6
> +           queries to save the waiting for a timeout on those uninteresting
> +           queries.</p>

I fixed up indentation here.

> +    </column>
> +
>      <column name="external_ids">
>        See <em>External IDs</em> at the beginning of this document.
>      </column>
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index a512e00e5..72e230b75 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.29.0",
> -    "cksum": "3967183656 30959",
> +    "version": "20.30.0",
> +    "cksum": "2972392849 31172",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -363,6 +363,11 @@
>                                                 "refTable": "Datapath_Binding"},
>                                         "min": 1,
>                                         "max": "unlimited"}},
> +                "options": {
> +                    "type": {"key": "string",
> +                             "value": "string",
> +                             "min": 0,
> +                             "max": "unlimited"}},
>                  "external_ids": {"type": {"key": "string",
>                                            "value": "string",
>                                            "min": 0,
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 8d0bed94d..e581d6965 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -4500,6 +4500,12 @@ tcp.flags = RST;
>        in this column.
>      </column>
>  
> +    <column name="options" key="ovn-owned">
> +      This column indicates that all the <code>Domains</code> in this table
> +      are owned by OVN, and all <code>DNS queries</code> for those domains
> +      will be answered locally by either an IP address or
> +      <code>DNS rejection</code>.
> +</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.at b/tests/ovn.at
> index b8c61f87f..543eca9d0 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -11281,6 +11281,71 @@ reset_pcap_file hv1-vif2 hv1/vif2
>  rm -f 1.expected
>  rm -f 2.expected
>  
> +# send AAAA query for a server known domain that don't have
> +# any IPV6 address associated with this domain, and expected
> +# server refused DNS reply to save the sender time of waiting for timeout.
> +AS_BOX([Test IPv6 (AAAA records) NO timeout.])
> +# Add back the DNS options for ls1-lp1 without ipv6.
> +check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
> +check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4"
> +check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
> +ovn-sbctl list DNS > dns6
> +AT_CAPTURE_FILE([dns6])
> +ovn-sbctl dump-flows > sbflows6
> +AT_CAPTURE_FILE([sbflows6])
> +
> +set_dns_params vm1_ipv6_only
> +src_ip=`ip_to_hex 10 0 0 6`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=1
> +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
> +
> +# NXT_RESUMEs should be 13.
> +OVS_WAIT_UNTIL([test 13 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +# dns hdr with server refuse RCODE
> +echo "01028125"  > expout
> +#only check for the DNS HDR flags since we are not getting any DNS answer
> +AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [0], [expout])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +reset_pcap_file hv1-vif2 hv1/vif2
> +rm -f 1.expected
> +rm -f 2.expected
> +
> +# send A query for a server known domain that don't have
> +# any IPv4 address associated with this domain, and expected
> +# server refused DNS reply to save the sender time of waiting for timeout.
> +AS_BOX([Test IPv4 (A records) NO timeout.])
> +# Add back the DNS options for ls1-lp1 without ipv4.
> +check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
> +check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="aef0::4"
> +check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
> +ovn-sbctl list DNS > dns7
> +AT_CAPTURE_FILE([dns7])
> +ovn-sbctl dump-flows > sbflows7
> +AT_CAPTURE_FILE([sbflows7])
> +
> +set_dns_params vm1
> +src_ip=`ip_to_hex 10 0 0 6`
> +dst_ip=`ip_to_hex 10 0 0 1`
> +dns_reply=1
> +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
> +
> +# NXT_RESUMEs should be 14.
> +OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
> +
> +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
> +# dns hdr with server refuse RCODE
> +echo "01028125"  > expout
> +#only check for the DNS HDR flags since we are not getting any DNS answer
> +AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [0], [expout])
> +
> +reset_pcap_file hv1-vif1 hv1/vif1
> +reset_pcap_file hv1-vif2 hv1/vif2
> +rm -f 1.expected
> +rm -f 2.expected
>  OVN_CLEANUP([hv1])
>  
>  AT_CLEANUP
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 30f6edb28..e10fb79dd 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,9 @@ 
 Post v23.09.0
 -------------
+  - DNS now have an "options" column for configuration of extra options.
+  - A new DNS option "ovn-owned" has been added to allow defining domains
+    that are owned only by ovn, queries for that domain will not be processed
+    externally.
   - Disable OpenFlow inactivity probing between ovn-controller and OVS.
     OF connection is established over unix socket, which is a reliable
     connection method and doesn't require additional probing.
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index d88e951a6..cf48089b6 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -179,6 +179,7 @@  struct pinctrl {
     struct latch pinctrl_thread_exit;
     bool mac_binding_can_timestamp;
     bool fdb_can_timestamp;
+    bool dns_supports_ovn_owned;
 };
 
 static struct pinctrl pinctrl;
@@ -2713,6 +2714,7 @@  struct dns_data {
     uint64_t *dps;
     size_t n_dps;
     struct smap records;
+    struct smap options;
     bool delete;
 };
 
@@ -2741,6 +2743,7 @@  sync_dns_cache(const struct sbrec_dns_table *dns_table)
         if (!dns_data) {
             dns_data = xmalloc(sizeof *dns_data);
             smap_init(&dns_data->records);
+            smap_init(&dns_data->options);
             shash_add(&dns_cache, dns_id, dns_data);
             dns_data->n_dps = 0;
             dns_data->dps = NULL;
@@ -2755,6 +2758,12 @@  sync_dns_cache(const struct sbrec_dns_table *dns_table)
             smap_clone(&dns_data->records, &sbrec_dns->records);
         }
 
+        if (pinctrl.dns_supports_ovn_owned
+            && !smap_equal(&dns_data->options, &sbrec_dns->options)) {
+            smap_destroy(&dns_data->options);
+            smap_clone(&dns_data->options, &sbrec_dns->options);
+        }
+
         dns_data->n_dps = sbrec_dns->n_datapaths;
         dns_data->dps = xcalloc(dns_data->n_dps, sizeof(uint64_t));
         for (size_t i = 0; i < sbrec_dns->n_datapaths; i++) {
@@ -2767,6 +2776,7 @@  sync_dns_cache(const struct sbrec_dns_table *dns_table)
         if (d->delete) {
             shash_delete(&dns_cache, iter);
             smap_destroy(&d->records);
+            smap_destroy(&d->options);
             free(d->dps);
             free(d);
         }
@@ -2781,6 +2791,7 @@  destroy_dns_cache(void)
         struct dns_data *d = iter->data;
         shash_delete(&dns_cache, iter);
         smap_destroy(&d->records);
+        smap_destroy(&d->options);
         free(d->dps);
         free(d);
     }
@@ -2854,6 +2865,8 @@  dns_build_ptr_answer(
     free(encoded);
 }
 
+#define DNS_RCODE_SERVER_REFUSE 0x5
+
 /* Called with in the pinctrl_handler thread context. */
 static void
 pinctrl_handle_dns_lookup(
@@ -2867,6 +2880,7 @@  pinctrl_handle_dns_lookup(
     enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
     struct dp_packet *pkt_out_ptr = NULL;
     uint32_t success = 0;
+    bool send_refuse = false;
 
     /* Parse result field. */
     const struct mf_field *f;
@@ -2966,6 +2980,7 @@  pinctrl_handle_dns_lookup(
 
     uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
     const char *answer_data = NULL;
+    bool ovn_owned = false;
     struct shash_node *iter;
     SHASH_FOR_EACH (iter, &dns_cache) {
         struct dns_data *d = iter->data;
@@ -2978,6 +2993,7 @@  pinctrl_handle_dns_lookup(
                 answer_data = smap_get(&d->records, query_name_lower);
                 free(query_name_lower);
                 if (answer_data) {
+                    ovn_owned = smap_get_bool(&d->options, "ovn-owned", false);
                     break;
                 }
             }
@@ -3024,10 +3040,22 @@  pinctrl_handle_dns_lookup(
                 ancount++;
             }
         }
+
+        /* DNS is configured with a record for this domain with
+         * an IPv4/IPV6 only, so instead of ignoring this A/AAAA query,
+         * we can reply with  RCODE = 5 (server refuses) and that
+         * will speed up the DNS process by not letting the customer
+         * wait for a timeout.
+         */
+        if (ovn_owned && (query_type == DNS_QUERY_TYPE_AAAA ||
+            query_type == DNS_QUERY_TYPE_A) && !ancount) {
+            send_refuse = true;
+        }
+
         destroy_lport_addresses(&ip_addrs);
     }
 
-    if (!ancount) {
+    if (!ancount && !send_refuse) {
         ofpbuf_uninit(&dns_answer);
         goto exit;
     }
@@ -3062,6 +3090,10 @@  pinctrl_handle_dns_lookup(
 
     /* Set the answer RRs. */
     out_dns_header->ancount = htons(ancount);
+    if (send_refuse) {
+        /* set RCODE = 5 (server refuses). */
+        out_dns_header->hi_flag |= DNS_RCODE_SERVER_REFUSE;
+    }
     out_dns_header->arcount = 0;
 
     /* Copy the Query section. */
@@ -3530,6 +3562,15 @@  pinctrl_update(const struct ovsdb_idl *idl, const char *br_int_name)
         notify_pinctrl_handler();
     }
 
+    bool dns_supports_ovn_owned = sbrec_server_has_dns_table_col_options(idl);
+    if (dns_supports_ovn_owned != pinctrl.dns_supports_ovn_owned) {
+        pinctrl.dns_supports_ovn_owned = dns_supports_ovn_owned;
+
+        /* Notify pinctrl_handler that fdb timestamp column
+       * availability has changed. */
+        notify_pinctrl_handler();
+    }
+
     ovs_mutex_unlock(&pinctrl_mutex);
 }
 
diff --git a/northd/northd.c b/northd/northd.c
index f8b046d83..e93d0c8f8 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -17301,6 +17301,19 @@  sync_dns_entries(struct ovsdb_idl_txn *ovnsb_txn,
             free(dns_id);
         }
 
+        /* Copy DNS options to SB*/
+        struct smap options = SMAP_INITIALIZER(&options);
+        if (!smap_is_empty(&dns_info->sb_dns->options)) {
+            smap_clone(&options, &dns_info->sb_dns->options);
+        }
+
+        bool ovn_owned = smap_get_bool(&dns_info->nb_dns->options,
+                                       "ovn-owned", false);
+        smap_replace(&options, "ovn-owned",
+                 ovn_owned? "true" : "false");
+        sbrec_dns_set_options(dns_info->sb_dns, &options);
+        smap_destroy(&options);
+
         /* Set the datapaths and records. If nothing has changed, then
          * this will be a no-op.
          */
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index e103360ec..b2e0993e0 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "7.1.0",
-    "cksum": "217362582 33949",
+    "version": "7.2.0",
+    "cksum": "1069338687 34162",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -560,6 +560,11 @@ 
                                      "value": "string",
                                      "min": 0,
                                      "max": "unlimited"}},
+                "options": {
+                    "type": {"key": "string",
+                             "value": "string",
+                             "min": 0,
+                             "max": "unlimited"}},
                 "external_ids": {"type": {"key": "string",
                                  "value": "string",
                                  "min": 0,
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 1de0c3041..4aa8107a5 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -4542,6 +4542,20 @@  or
       <p><b>Example: </b> "4.0.0.10.in-addr.arpa" = "vm1.ovn.org"</p>
     </column>
 
+    <column name="options" key="ovn-owned">
+      If set to true, then the OVN will be the main responsible for
+      <code>DNS Records</code> within this Table.
+
+      <p> A <code>DNS Table</code> with this option set to <code>true</code>
+          can be created for domains that the user needs to configure
+          locally and don't care about IPv6 only interested in IPv4 or
+          vice versa.
+
+           This will let ovn send IPv4 DNS reply and reject/ignore IPv6
+           queries to save the waiting for a timeout on those uninteresting
+           queries.</p>
+    </column>
+
     <column name="external_ids">
       See <em>External IDs</em> at the beginning of this document.
     </column>
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index a512e00e5..72e230b75 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "20.29.0",
-    "cksum": "3967183656 30959",
+    "version": "20.30.0",
+    "cksum": "2972392849 31172",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -363,6 +363,11 @@ 
                                                "refTable": "Datapath_Binding"},
                                        "min": 1,
                                        "max": "unlimited"}},
+                "options": {
+                    "type": {"key": "string",
+                             "value": "string",
+                             "min": 0,
+                             "max": "unlimited"}},
                 "external_ids": {"type": {"key": "string",
                                           "value": "string",
                                           "min": 0,
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 8d0bed94d..e581d6965 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -4500,6 +4500,12 @@  tcp.flags = RST;
       in this column.
     </column>
 
+    <column name="options" key="ovn-owned">
+      This column indicates that all the <code>Domains</code> in this table
+      are owned by OVN, and all <code>DNS queries</code> for those domains
+      will be answered locally by either an IP address or
+      <code>DNS rejection</code>.
+</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.at b/tests/ovn.at
index b8c61f87f..543eca9d0 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -11281,6 +11281,71 @@  reset_pcap_file hv1-vif2 hv1/vif2
 rm -f 1.expected
 rm -f 2.expected
 
+# send AAAA query for a server known domain that don't have
+# any IPV6 address associated with this domain, and expected
+# server refused DNS reply to save the sender time of waiting for timeout.
+AS_BOX([Test IPv6 (AAAA records) NO timeout.])
+# Add back the DNS options for ls1-lp1 without ipv6.
+check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
+check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4"
+check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
+ovn-sbctl list DNS > dns6
+AT_CAPTURE_FILE([dns6])
+ovn-sbctl dump-flows > sbflows6
+AT_CAPTURE_FILE([sbflows6])
+
+set_dns_params vm1_ipv6_only
+src_ip=`ip_to_hex 10 0 0 6`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=1
+test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
+
+# NXT_RESUMEs should be 13.
+OVS_WAIT_UNTIL([test 13 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+# dns hdr with server refuse RCODE
+echo "01028125"  > expout
+#only check for the DNS HDR flags since we are not getting any DNS answer
+AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [0], [expout])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
+
+# send A query for a server known domain that don't have
+# any IPv4 address associated with this domain, and expected
+# server refused DNS reply to save the sender time of waiting for timeout.
+AS_BOX([Test IPv4 (A records) NO timeout.])
+# Add back the DNS options for ls1-lp1 without ipv4.
+check ovn-nbctl remove DNS $DNS1 records vm1.ovn.org
+check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="aef0::4"
+check ovn-nbctl --wait=hv set DNS $DNS1 options:ovn-owned=true
+ovn-sbctl list DNS > dns7
+AT_CAPTURE_FILE([dns7])
+ovn-sbctl dump-flows > sbflows7
+AT_CAPTURE_FILE([sbflows7])
+
+set_dns_params vm1
+src_ip=`ip_to_hex 10 0 0 6`
+dst_ip=`ip_to_hex 10 0 0 1`
+dns_reply=1
+test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data
+
+# NXT_RESUMEs should be 14.
+OVS_WAIT_UNTIL([test 14 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+
+$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+# dns hdr with server refuse RCODE
+echo "01028125"  > expout
+#only check for the DNS HDR flags since we are not getting any DNS answer
+AT_CHECK([cat 2.packets | cut -c -92 | cut -c 85-], [0], [expout])
+
+reset_pcap_file hv1-vif1 hv1/vif1
+reset_pcap_file hv1-vif2 hv1/vif2
+rm -f 1.expected
+rm -f 2.expected
 OVN_CLEANUP([hv1])
 
 AT_CLEANUP